forked from golang/hotime
494 lines
11 KiB
Go
494 lines
11 KiB
Go
|
package otto
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
)
|
||
|
|
||
|
type _objectClass struct {
|
||
|
getOwnProperty func(*_object, string) *_property
|
||
|
getProperty func(*_object, string) *_property
|
||
|
get func(*_object, string) Value
|
||
|
canPut func(*_object, string) bool
|
||
|
put func(*_object, string, Value, bool)
|
||
|
hasProperty func(*_object, string) bool
|
||
|
hasOwnProperty func(*_object, string) bool
|
||
|
defineOwnProperty func(*_object, string, _property, bool) bool
|
||
|
delete func(*_object, string, bool) bool
|
||
|
enumerate func(*_object, bool, func(string) bool)
|
||
|
clone func(*_object, *_object, *_clone) *_object
|
||
|
marshalJSON func(*_object) json.Marshaler
|
||
|
}
|
||
|
|
||
|
func objectEnumerate(self *_object, all bool, each func(string) bool) {
|
||
|
for _, name := range self.propertyOrder {
|
||
|
if all || self.property[name].enumerable() {
|
||
|
if !each(name) {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
_classObject,
|
||
|
_classArray,
|
||
|
_classString,
|
||
|
_classArguments,
|
||
|
_classGoStruct,
|
||
|
_classGoMap,
|
||
|
_classGoArray,
|
||
|
_classGoSlice,
|
||
|
_ *_objectClass
|
||
|
)
|
||
|
|
||
|
func init() {
|
||
|
_classObject = &_objectClass{
|
||
|
objectGetOwnProperty,
|
||
|
objectGetProperty,
|
||
|
objectGet,
|
||
|
objectCanPut,
|
||
|
objectPut,
|
||
|
objectHasProperty,
|
||
|
objectHasOwnProperty,
|
||
|
objectDefineOwnProperty,
|
||
|
objectDelete,
|
||
|
objectEnumerate,
|
||
|
objectClone,
|
||
|
nil,
|
||
|
}
|
||
|
|
||
|
_classArray = &_objectClass{
|
||
|
objectGetOwnProperty,
|
||
|
objectGetProperty,
|
||
|
objectGet,
|
||
|
objectCanPut,
|
||
|
objectPut,
|
||
|
objectHasProperty,
|
||
|
objectHasOwnProperty,
|
||
|
arrayDefineOwnProperty,
|
||
|
objectDelete,
|
||
|
objectEnumerate,
|
||
|
objectClone,
|
||
|
nil,
|
||
|
}
|
||
|
|
||
|
_classString = &_objectClass{
|
||
|
stringGetOwnProperty,
|
||
|
objectGetProperty,
|
||
|
objectGet,
|
||
|
objectCanPut,
|
||
|
objectPut,
|
||
|
objectHasProperty,
|
||
|
objectHasOwnProperty,
|
||
|
objectDefineOwnProperty,
|
||
|
objectDelete,
|
||
|
stringEnumerate,
|
||
|
objectClone,
|
||
|
nil,
|
||
|
}
|
||
|
|
||
|
_classArguments = &_objectClass{
|
||
|
argumentsGetOwnProperty,
|
||
|
objectGetProperty,
|
||
|
argumentsGet,
|
||
|
objectCanPut,
|
||
|
objectPut,
|
||
|
objectHasProperty,
|
||
|
objectHasOwnProperty,
|
||
|
argumentsDefineOwnProperty,
|
||
|
argumentsDelete,
|
||
|
objectEnumerate,
|
||
|
objectClone,
|
||
|
nil,
|
||
|
}
|
||
|
|
||
|
_classGoStruct = &_objectClass{
|
||
|
goStructGetOwnProperty,
|
||
|
objectGetProperty,
|
||
|
objectGet,
|
||
|
goStructCanPut,
|
||
|
goStructPut,
|
||
|
objectHasProperty,
|
||
|
objectHasOwnProperty,
|
||
|
objectDefineOwnProperty,
|
||
|
objectDelete,
|
||
|
goStructEnumerate,
|
||
|
objectClone,
|
||
|
goStructMarshalJSON,
|
||
|
}
|
||
|
|
||
|
_classGoMap = &_objectClass{
|
||
|
goMapGetOwnProperty,
|
||
|
objectGetProperty,
|
||
|
objectGet,
|
||
|
objectCanPut,
|
||
|
objectPut,
|
||
|
objectHasProperty,
|
||
|
objectHasOwnProperty,
|
||
|
goMapDefineOwnProperty,
|
||
|
goMapDelete,
|
||
|
goMapEnumerate,
|
||
|
objectClone,
|
||
|
nil,
|
||
|
}
|
||
|
|
||
|
_classGoArray = &_objectClass{
|
||
|
goArrayGetOwnProperty,
|
||
|
objectGetProperty,
|
||
|
objectGet,
|
||
|
objectCanPut,
|
||
|
objectPut,
|
||
|
objectHasProperty,
|
||
|
objectHasOwnProperty,
|
||
|
goArrayDefineOwnProperty,
|
||
|
goArrayDelete,
|
||
|
goArrayEnumerate,
|
||
|
objectClone,
|
||
|
nil,
|
||
|
}
|
||
|
|
||
|
_classGoSlice = &_objectClass{
|
||
|
goSliceGetOwnProperty,
|
||
|
objectGetProperty,
|
||
|
objectGet,
|
||
|
objectCanPut,
|
||
|
objectPut,
|
||
|
objectHasProperty,
|
||
|
objectHasOwnProperty,
|
||
|
goSliceDefineOwnProperty,
|
||
|
goSliceDelete,
|
||
|
goSliceEnumerate,
|
||
|
objectClone,
|
||
|
nil,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Allons-y
|
||
|
|
||
|
// 8.12.1
|
||
|
func objectGetOwnProperty(self *_object, name string) *_property {
|
||
|
// Return a _copy_ of the property
|
||
|
property, exists := self._read(name)
|
||
|
if !exists {
|
||
|
return nil
|
||
|
}
|
||
|
return &property
|
||
|
}
|
||
|
|
||
|
// 8.12.2
|
||
|
func objectGetProperty(self *_object, name string) *_property {
|
||
|
property := self.getOwnProperty(name)
|
||
|
if property != nil {
|
||
|
return property
|
||
|
}
|
||
|
if self.prototype != nil {
|
||
|
return self.prototype.getProperty(name)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// 8.12.3
|
||
|
func objectGet(self *_object, name string) Value {
|
||
|
property := self.getProperty(name)
|
||
|
if property != nil {
|
||
|
return property.get(self)
|
||
|
}
|
||
|
return Value{}
|
||
|
}
|
||
|
|
||
|
// 8.12.4
|
||
|
func objectCanPut(self *_object, name string) bool {
|
||
|
canPut, _, _ := _objectCanPut(self, name)
|
||
|
return canPut
|
||
|
}
|
||
|
|
||
|
func _objectCanPut(self *_object, name string) (canPut bool, property *_property, setter *_object) {
|
||
|
property = self.getOwnProperty(name)
|
||
|
if property != nil {
|
||
|
switch propertyValue := property.value.(type) {
|
||
|
case Value:
|
||
|
canPut = property.writable()
|
||
|
return
|
||
|
case _propertyGetSet:
|
||
|
setter = propertyValue[1]
|
||
|
canPut = setter != nil
|
||
|
return
|
||
|
default:
|
||
|
panic(self.runtime.panicTypeError())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if self.prototype == nil {
|
||
|
return self.extensible, nil, nil
|
||
|
}
|
||
|
|
||
|
property = self.prototype.getProperty(name)
|
||
|
if property == nil {
|
||
|
return self.extensible, nil, nil
|
||
|
}
|
||
|
|
||
|
switch propertyValue := property.value.(type) {
|
||
|
case Value:
|
||
|
if !self.extensible {
|
||
|
return false, nil, nil
|
||
|
}
|
||
|
return property.writable(), nil, nil
|
||
|
case _propertyGetSet:
|
||
|
setter = propertyValue[1]
|
||
|
canPut = setter != nil
|
||
|
return
|
||
|
default:
|
||
|
panic(self.runtime.panicTypeError())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 8.12.5
|
||
|
func objectPut(self *_object, name string, value Value, throw bool) {
|
||
|
|
||
|
if true {
|
||
|
// Shortcut...
|
||
|
//
|
||
|
// So, right now, every class is using objectCanPut and every class
|
||
|
// is using objectPut.
|
||
|
//
|
||
|
// If that were to no longer be the case, we would have to have
|
||
|
// something to detect that here, so that we do not use an
|
||
|
// incompatible canPut routine
|
||
|
canPut, property, setter := _objectCanPut(self, name)
|
||
|
if !canPut {
|
||
|
self.runtime.typeErrorResult(throw)
|
||
|
} else if setter != nil {
|
||
|
setter.call(toValue(self), []Value{value}, false, nativeFrame)
|
||
|
} else if property != nil {
|
||
|
property.value = value
|
||
|
self.defineOwnProperty(name, *property, throw)
|
||
|
} else {
|
||
|
self.defineProperty(name, value, 0111, throw)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// The long way...
|
||
|
//
|
||
|
// Right now, code should never get here, see above
|
||
|
if !self.canPut(name) {
|
||
|
self.runtime.typeErrorResult(throw)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
property := self.getOwnProperty(name)
|
||
|
if property == nil {
|
||
|
property = self.getProperty(name)
|
||
|
if property != nil {
|
||
|
if getSet, isAccessor := property.value.(_propertyGetSet); isAccessor {
|
||
|
getSet[1].call(toValue(self), []Value{value}, false, nativeFrame)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
self.defineProperty(name, value, 0111, throw)
|
||
|
} else {
|
||
|
switch propertyValue := property.value.(type) {
|
||
|
case Value:
|
||
|
property.value = value
|
||
|
self.defineOwnProperty(name, *property, throw)
|
||
|
case _propertyGetSet:
|
||
|
if propertyValue[1] != nil {
|
||
|
propertyValue[1].call(toValue(self), []Value{value}, false, nativeFrame)
|
||
|
return
|
||
|
}
|
||
|
if throw {
|
||
|
panic(self.runtime.panicTypeError())
|
||
|
}
|
||
|
default:
|
||
|
panic(self.runtime.panicTypeError())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 8.12.6
|
||
|
func objectHasProperty(self *_object, name string) bool {
|
||
|
return self.getProperty(name) != nil
|
||
|
}
|
||
|
|
||
|
func objectHasOwnProperty(self *_object, name string) bool {
|
||
|
return self.getOwnProperty(name) != nil
|
||
|
}
|
||
|
|
||
|
// 8.12.9
|
||
|
func objectDefineOwnProperty(self *_object, name string, descriptor _property, throw bool) bool {
|
||
|
property, exists := self._read(name)
|
||
|
{
|
||
|
if !exists {
|
||
|
if !self.extensible {
|
||
|
goto Reject
|
||
|
}
|
||
|
if newGetSet, isAccessor := descriptor.value.(_propertyGetSet); isAccessor {
|
||
|
if newGetSet[0] == &_nilGetSetObject {
|
||
|
newGetSet[0] = nil
|
||
|
}
|
||
|
if newGetSet[1] == &_nilGetSetObject {
|
||
|
newGetSet[1] = nil
|
||
|
}
|
||
|
descriptor.value = newGetSet
|
||
|
}
|
||
|
self._write(name, descriptor.value, descriptor.mode)
|
||
|
return true
|
||
|
}
|
||
|
if descriptor.isEmpty() {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// TODO Per 8.12.9.6 - We should shortcut here (returning true) if
|
||
|
// the current and new (define) properties are the same
|
||
|
|
||
|
configurable := property.configurable()
|
||
|
if !configurable {
|
||
|
if descriptor.configurable() {
|
||
|
goto Reject
|
||
|
}
|
||
|
// Test that, if enumerable is set on the property descriptor, then it should
|
||
|
// be the same as the existing property
|
||
|
if descriptor.enumerateSet() && descriptor.enumerable() != property.enumerable() {
|
||
|
goto Reject
|
||
|
}
|
||
|
}
|
||
|
value, isDataDescriptor := property.value.(Value)
|
||
|
getSet, _ := property.value.(_propertyGetSet)
|
||
|
if descriptor.isGenericDescriptor() {
|
||
|
// GenericDescriptor
|
||
|
} else if isDataDescriptor != descriptor.isDataDescriptor() {
|
||
|
// DataDescriptor <=> AccessorDescriptor
|
||
|
if !configurable {
|
||
|
goto Reject
|
||
|
}
|
||
|
} else if isDataDescriptor && descriptor.isDataDescriptor() {
|
||
|
// DataDescriptor <=> DataDescriptor
|
||
|
if !configurable {
|
||
|
if !property.writable() && descriptor.writable() {
|
||
|
goto Reject
|
||
|
}
|
||
|
if !property.writable() {
|
||
|
if descriptor.value != nil && !sameValue(value, descriptor.value.(Value)) {
|
||
|
goto Reject
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
// AccessorDescriptor <=> AccessorDescriptor
|
||
|
newGetSet, _ := descriptor.value.(_propertyGetSet)
|
||
|
presentGet, presentSet := true, true
|
||
|
if newGetSet[0] == &_nilGetSetObject {
|
||
|
// Present, but nil
|
||
|
newGetSet[0] = nil
|
||
|
} else if newGetSet[0] == nil {
|
||
|
// Missing, not even nil
|
||
|
newGetSet[0] = getSet[0]
|
||
|
presentGet = false
|
||
|
}
|
||
|
if newGetSet[1] == &_nilGetSetObject {
|
||
|
// Present, but nil
|
||
|
newGetSet[1] = nil
|
||
|
} else if newGetSet[1] == nil {
|
||
|
// Missing, not even nil
|
||
|
newGetSet[1] = getSet[1]
|
||
|
presentSet = false
|
||
|
}
|
||
|
if !configurable {
|
||
|
if (presentGet && (getSet[0] != newGetSet[0])) || (presentSet && (getSet[1] != newGetSet[1])) {
|
||
|
goto Reject
|
||
|
}
|
||
|
}
|
||
|
descriptor.value = newGetSet
|
||
|
}
|
||
|
{
|
||
|
// This section will preserve attributes of
|
||
|
// the original property, if necessary
|
||
|
value1 := descriptor.value
|
||
|
if value1 == nil {
|
||
|
value1 = property.value
|
||
|
} else if newGetSet, isAccessor := descriptor.value.(_propertyGetSet); isAccessor {
|
||
|
if newGetSet[0] == &_nilGetSetObject {
|
||
|
newGetSet[0] = nil
|
||
|
}
|
||
|
if newGetSet[1] == &_nilGetSetObject {
|
||
|
newGetSet[1] = nil
|
||
|
}
|
||
|
value1 = newGetSet
|
||
|
}
|
||
|
mode1 := descriptor.mode
|
||
|
if mode1&0222 != 0 {
|
||
|
// TODO Factor this out into somewhere testable
|
||
|
// (Maybe put into switch ...)
|
||
|
mode0 := property.mode
|
||
|
if mode1&0200 != 0 {
|
||
|
if descriptor.isDataDescriptor() {
|
||
|
mode1 &= ^0200 // Turn off "writable" missing
|
||
|
mode1 |= (mode0 & 0100)
|
||
|
}
|
||
|
}
|
||
|
if mode1&020 != 0 {
|
||
|
mode1 |= (mode0 & 010)
|
||
|
}
|
||
|
if mode1&02 != 0 {
|
||
|
mode1 |= (mode0 & 01)
|
||
|
}
|
||
|
mode1 &= 0311 // 0311 to preserve the non-setting on "writable"
|
||
|
}
|
||
|
self._write(name, value1, mode1)
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
Reject:
|
||
|
if throw {
|
||
|
panic(self.runtime.panicTypeError())
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func objectDelete(self *_object, name string, throw bool) bool {
|
||
|
property_ := self.getOwnProperty(name)
|
||
|
if property_ == nil {
|
||
|
return true
|
||
|
}
|
||
|
if property_.configurable() {
|
||
|
self._delete(name)
|
||
|
return true
|
||
|
}
|
||
|
return self.runtime.typeErrorResult(throw)
|
||
|
}
|
||
|
|
||
|
func objectClone(in *_object, out *_object, clone *_clone) *_object {
|
||
|
*out = *in
|
||
|
|
||
|
out.runtime = clone.runtime
|
||
|
if out.prototype != nil {
|
||
|
out.prototype = clone.object(in.prototype)
|
||
|
}
|
||
|
out.property = make(map[string]_property, len(in.property))
|
||
|
out.propertyOrder = make([]string, len(in.propertyOrder))
|
||
|
copy(out.propertyOrder, in.propertyOrder)
|
||
|
for index, property := range in.property {
|
||
|
out.property[index] = clone.property(property)
|
||
|
}
|
||
|
|
||
|
switch value := in.value.(type) {
|
||
|
case _nativeFunctionObject:
|
||
|
out.value = value
|
||
|
case _bindFunctionObject:
|
||
|
out.value = _bindFunctionObject{
|
||
|
target: clone.object(value.target),
|
||
|
this: clone.value(value.this),
|
||
|
argumentList: clone.valueArray(value.argumentList),
|
||
|
}
|
||
|
case _nodeFunctionObject:
|
||
|
out.value = _nodeFunctionObject{
|
||
|
node: value.node,
|
||
|
stash: clone.stash(value.stash),
|
||
|
}
|
||
|
case _argumentsObject:
|
||
|
out.value = value.clone(clone)
|
||
|
}
|
||
|
|
||
|
return out
|
||
|
}
|