diff options
author | bors-servo <servo-ops@mozilla.com> | 2021-08-01 10:31:40 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-01 10:31:40 -0400 |
commit | bd92fad81a24d08208a5739cad4bde6eb58d6ce8 (patch) | |
tree | e0e84609080a1d237bfdcaa10e0bef18cdea26ea /components/script | |
parent | 052278d0580ec1cbd2c9887e65d259dbf5efa775 (diff) | |
parent | afbe2fa1f259411b6c49a3ec3b2baccfbf1664d9 (diff) | |
download | servo-bd92fad81a24d08208a5739cad4bde6eb58d6ce8.tar.gz servo-bd92fad81a24d08208a5739cad4bde6eb58d6ce8.zip |
Auto merge of #28546 - yvt:feat-cow-infra, r=jdm
Implement `Location`'s custom internal methods
This PR partly resurrects #16501 and introduces the use of principals object to associate objects and Realms with origins. Using this infrastructure, this PR implements [the custom internal methods][1] of the `Location` interface, which is "maybe-cross-origin".
Unimplemented/incomplete things:
- Other maybe-cross-origin interfaces, namely `WindowProxy` and `DissimilarWindowLocation`, aren't implemented correctly yet (causing most test cases of `tests/wpt/web-platform-tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html` to fail).
- `WindowProxy`: #28556
- [The "perform a security check" operation][2] and `Location`'s non-cross-origin properties' relevant `Document` origin checks aren't implemented either (not sure if they are covered by the existing tests).
- There are a slight deviation from the standard and inefficiency in `CrossOriginGetOwnPropertyHelper`'s current implementation.
- #28557
[1]: https://html.spec.whatwg.org/multipage/#the-location-interface
[2]: https://html.spec.whatwg.org/multipage/browsers.html#integration-with-idl
---
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix #16243 and make some progress in #2382
---
- [x] There are tests for these changes OR
- [ ] These changes do not require tests because ___
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/bindings/codegen/CodegenRust.py | 325 | ||||
-rw-r--r-- | components/script/dom/bindings/codegen/Configuration.py | 8 | ||||
-rw-r--r-- | components/script/dom/bindings/interface.rs | 7 | ||||
-rw-r--r-- | components/script/dom/bindings/mod.rs | 1 | ||||
-rw-r--r-- | components/script/dom/bindings/principals.rs | 139 | ||||
-rw-r--r-- | components/script/dom/bindings/proxyhandler.rs | 605 | ||||
-rw-r--r-- | components/script/dom/bindings/utils.rs | 40 | ||||
-rw-r--r-- | components/script/dom/webidls/Location.webidl | 6 | ||||
-rw-r--r-- | components/script/dom/webidls/Window.webidl | 29 | ||||
-rw-r--r-- | components/script/dom/windowproxy.rs | 3 | ||||
-rw-r--r-- | components/script/script_runtime.rs | 12 | ||||
-rw-r--r-- | components/script/script_thread.rs | 3 |
12 files changed, 1081 insertions, 97 deletions
diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 353e690baaf..03998017f36 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -1642,6 +1642,33 @@ class PropertyDefiner: + "];\n") % (name, specType) return specsArray + prefArray + def generateUnguardedArray(self, array, name, specTemplate, specTerminator, + specType, getCondition, getDataTuple): + """ + Takes the same set of parameters as generateGuardedArray but instead + generates a single, flat array of type `&[specType]` that contains all + provided members. The provided members' conditions shall be homogeneous, + or else this method will fail. + """ + + # this method can't handle heterogeneous condition + groups = groupby(array, lambda m: getCondition(m, self.descriptor)) + assert len(list(groups)) == 1 + + origTemplate = specTemplate + if isinstance(specTemplate, str): + specTemplate = lambda _: origTemplate # noqa + + specsArray = [specTemplate(m) % getDataTuple(m) for m in array] + specsArray.append(specTerminator) + + return dedent( + """ + const %s: &[%s] = &[ + %s + ]; + """) % (name, specType, ',\n'.join(specsArray)) + # The length of a method is the minimum of the lengths of the # argument lists of all its overloads. @@ -1656,10 +1683,15 @@ class MethodDefiner(PropertyDefiner): """ A class for defining methods on a prototype object. """ - def __init__(self, descriptor, name, static, unforgeable): + def __init__(self, descriptor, name, static, unforgeable, crossorigin=False): assert not (static and unforgeable) + assert not (static and crossorigin) + assert not (unforgeable and crossorigin) PropertyDefiner.__init__(self, descriptor, name) + # TODO: Separate the `(static, unforgeable, crossorigin) = (False, False, True)` case + # to a separate class or something. + # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822 # We should be able to check for special operations without an # identifier. For now we check if the name starts with __ @@ -1668,14 +1700,15 @@ class MethodDefiner(PropertyDefiner): if not descriptor.interface.isCallback() or static: methods = [m for m in descriptor.interface.members if m.isMethod() and m.isStatic() == static + and (bool(m.getExtendedAttribute("CrossOriginCallable")) or not crossorigin) and not m.isIdentifierLess() - and MemberIsUnforgeable(m, descriptor) == unforgeable] + and (MemberIsUnforgeable(m, descriptor) == unforgeable or crossorigin)] else: methods = [] self.regular = [{"name": m.identifier.name, "methodInfo": not m.isStatic(), "length": methodLength(m), - "flags": "JSPROP_ENUMERATE", + "flags": "JSPROP_READONLY" if crossorigin else "JSPROP_ENUMERATE", "condition": PropertyDefiner.getControllingCondition(m, descriptor)} for m in methods] @@ -1693,6 +1726,7 @@ class MethodDefiner(PropertyDefiner): # neither. if (not static and not unforgeable + and not crossorigin and descriptor.supportsIndexedProperties()): # noqa if hasIterator(methods, self.regular): # noqa raise TypeError("Cannot have indexed getter/attr on " @@ -1709,7 +1743,7 @@ class MethodDefiner(PropertyDefiner): # Generate the keys/values/entries aliases for value iterables. maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable - if (not static and not unforgeable + if (not static and not unforgeable and not crossorigin and maplikeOrSetlikeOrIterable and maplikeOrSetlikeOrIterable.isIterable() and maplikeOrSetlikeOrIterable.isValueIterator()): @@ -1754,7 +1788,7 @@ class MethodDefiner(PropertyDefiner): }) isUnforgeableInterface = bool(descriptor.interface.getExtendedAttribute("Unforgeable")) - if not static and unforgeable == isUnforgeableInterface: + if not static and unforgeable == isUnforgeableInterface and not crossorigin: stringifier = descriptor.operations['Stringifier'] if stringifier: self.regular.append({ @@ -1765,6 +1799,7 @@ class MethodDefiner(PropertyDefiner): "condition": PropertyDefiner.getControllingCondition(stringifier, descriptor) }) self.unforgeable = unforgeable + self.crossorigin = crossorigin def generateArray(self, array, name): if len(array) == 0: @@ -1796,36 +1831,54 @@ class MethodDefiner(PropertyDefiner): jitinfo = "ptr::null()" accessor = 'Some(%s)' % m.get("nativeName", m["name"]) if m["name"].startswith("@@"): + assert not self.crossorigin name = 'JSPropertySpec_Name { symbol_: SymbolCode::%s as usize + 1 }' % m["name"][2:] else: name = ('JSPropertySpec_Name { string_: %s as *const u8 as *const libc::c_char }' % str_to_const_array(m["name"])) return (name, accessor, jitinfo, m["length"], flags, selfHostedName) - return self.generateGuardedArray( - array, name, + specTemplate = ( ' JSFunctionSpec {\n' ' name: %s,\n' ' call: JSNativeWrapper { op: %s, info: %s },\n' ' nargs: %s,\n' ' flags: (%s) as u16,\n' ' selfHostedName: %s\n' - ' }', + ' }') + specTerminator = ( ' JSFunctionSpec {\n' ' name: JSPropertySpec_Name { string_: ptr::null() },\n' ' call: JSNativeWrapper { op: None, info: ptr::null() },\n' ' nargs: 0,\n' ' flags: 0,\n' ' selfHostedName: ptr::null()\n' - ' }', - 'JSFunctionSpec', - condition, specData) + ' }') + + if self.crossorigin: + return self.generateUnguardedArray( + array, name, + specTemplate, specTerminator, + 'JSFunctionSpec', + condition, specData) + else: + return self.generateGuardedArray( + array, name, + specTemplate, specTerminator, + 'JSFunctionSpec', + condition, specData) class AttrDefiner(PropertyDefiner): - def __init__(self, descriptor, name, static, unforgeable): + def __init__(self, descriptor, name, static, unforgeable, crossorigin=False): assert not (static and unforgeable) + assert not (static and crossorigin) + assert not (unforgeable and crossorigin) PropertyDefiner.__init__(self, descriptor, name) + + # TODO: Separate the `(static, unforgeable, crossorigin) = (False, False, True)` case + # to a separate class or something. + self.name = name self.descriptor = descriptor self.regular = [ @@ -1837,12 +1890,16 @@ class AttrDefiner(PropertyDefiner): } for m in descriptor.interface.members if m.isAttr() and m.isStatic() == static - and MemberIsUnforgeable(m, descriptor) == unforgeable + and (MemberIsUnforgeable(m, descriptor) == unforgeable or crossorigin) + and (not crossorigin + or m.getExtendedAttribute("CrossOriginReadable") + or m.getExtendedAttribute("CrossOriginWritable")) ] self.static = static self.unforgeable = unforgeable + self.crossorigin = crossorigin - if not static and not unforgeable and not ( + if not static and not unforgeable and not crossorigin and not ( descriptor.interface.isNamespace() or descriptor.interface.isCallback() ): self.regular.append({ @@ -1858,6 +1915,10 @@ class AttrDefiner(PropertyDefiner): def getter(attr): attr = attr['attr'] + + if self.crossorigin and not attr.getExtendedAttribute("CrossOriginReadable"): + return "JSNativeWrapper { op: None, info: 0 as *const JSJitInfo }" + if self.static: accessor = 'get_' + self.descriptor.internalNameFor(attr.identifier.name) jitinfo = "0 as *const JSJitInfo" @@ -1874,8 +1935,11 @@ class AttrDefiner(PropertyDefiner): def setter(attr): attr = attr['attr'] - if (attr.readonly and not attr.getExtendedAttribute("PutForwards") - and not attr.getExtendedAttribute("Replaceable")): + + if ((self.crossorigin and not attr.getExtendedAttribute("CrossOriginReadable")) + or (attr.readonly + and not attr.getExtendedAttribute("PutForwards") + and not attr.getExtendedAttribute("Replaceable"))): return "JSNativeWrapper { op: None, info: 0 as *const JSJitInfo }" if self.static: @@ -1941,12 +2005,20 @@ class AttrDefiner(PropertyDefiner): } """ - return self.generateGuardedArray( - array, name, - template, - ' JSPropertySpec::ZERO', - 'JSPropertySpec', - condition, specData) + if self.crossorigin: + return self.generateUnguardedArray( + array, name, + template, + ' JSPropertySpec::ZERO', + 'JSPropertySpec', + condition, specData) + else: + return self.generateGuardedArray( + array, name, + template, + ' JSPropertySpec::ZERO', + 'JSPropertySpec', + condition, specData) class ConstDefiner(PropertyDefiner): @@ -2821,6 +2893,13 @@ class CGWrapMethod(CGAbstractMethod): def definition_body(self): unforgeable = CopyUnforgeablePropertiesToInstance(self.descriptor) if self.descriptor.proxy: + if self.descriptor.isMaybeCrossOriginObject(): + proto = "ptr::null_mut()" + lazyProto = "true" # Our proxy handler will manage the prototype + else: + proto = "proto.get()" + lazyProto = "false" + create = """ let handler: *const libc::c_void = RegisterBindings::proxy_handlers::%(concreteType)s @@ -2829,8 +2908,9 @@ rooted!(in(*cx) let obj = NewProxyObject( *cx, handler, Handle::from_raw(UndefinedHandleValue), - proto.get(), + %(proto)s, ptr::null(), + %(lazyProto)s, )); assert!(!obj.is_null()); SetProxyReservedSlot( @@ -2839,7 +2919,11 @@ SetProxyReservedSlot( &PrivateValue(raw.as_ptr() as *const %(concreteType)s as *const libc::c_void), ); """ + create = create % {"concreteType": self.descriptor.concreteType, + "proto": proto, + "lazyProto": lazyProto} else: + lazyProto = None create = """ rooted!(in(*cx) let obj = JS_NewObjectWithGivenProto( *cx, @@ -2853,7 +2937,7 @@ JS_SetReservedSlot( &PrivateValue(raw.as_ptr() as *const %(concreteType)s as *const libc::c_void), ); """ - create = create % {"concreteType": self.descriptor.concreteType} + create = create % {"concreteType": self.descriptor.concreteType} if self.descriptor.weakReferenceable: create += """ let val = PrivateValue(ptr::null()); @@ -2912,6 +2996,7 @@ class CGWrapGlobalMethod(CGAbstractMethod): return CGGeneric("""\ let raw = Root::new(MaybeUnreflectedDom::from_box(object)); +let origin = (*raw.as_ptr()).upcast::<GlobalScope>().origin(); rooted!(in(*cx) let mut obj = ptr::null_mut::<JSObject>()); create_global_object( @@ -2919,7 +3004,8 @@ create_global_object( &Class.base, raw.as_ptr() as *const %(concreteType)s as *const libc::c_void, _trace, - obj.handle_mut()); + obj.handle_mut(), + origin); assert!(!obj.is_null()); let root = raw.reflect_with(obj.get()); @@ -3069,6 +3155,25 @@ class PropertyArrays(): return define +class CGCrossOriginProperties(CGThing): + def __init__(self, descriptor): + self.methods = MethodDefiner(descriptor, "CrossOriginMethods", static=False, + unforgeable=False, crossorigin=True) + self.attributes = AttrDefiner(descriptor, "CrossOriginAttributes", static=False, + unforgeable=False, crossorigin=True) + + def define(self): + return str(self.methods) + str(self.attributes) + dedent( + """ + const CROSS_ORIGIN_PROPERTIES: proxyhandler::CrossOriginProperties = + proxyhandler::CrossOriginProperties { + attributes: sCrossOriginAttributes, + methods: sCrossOriginMethods, + }; + """ + ) + + class CGCollectJSONAttributesMethod(CGAbstractMethod): """ Generate the CollectJSONAttributes method for an interface descriptor @@ -3449,20 +3554,45 @@ class CGDefineProxyHandler(CGAbstractMethod): def definition_body(self): customDefineProperty = 'proxyhandler::define_property' - if self.descriptor.operations['IndexedSetter'] or self.descriptor.operations['NamedSetter']: + if self.descriptor.isMaybeCrossOriginObject() or self.descriptor.operations['IndexedSetter'] or \ + self.descriptor.operations['NamedSetter']: customDefineProperty = 'defineProperty' customDelete = 'proxyhandler::delete' - if self.descriptor.operations['NamedDeleter']: + if self.descriptor.isMaybeCrossOriginObject() or self.descriptor.operations['NamedDeleter']: customDelete = 'delete' + customGetPrototypeIfOrdinary = 'Some(proxyhandler::get_prototype_if_ordinary)' + customGetPrototype = 'None' + customSetPrototype = 'None' + if self.descriptor.isMaybeCrossOriginObject(): + customGetPrototypeIfOrdinary = 'Some(proxyhandler::maybe_cross_origin_get_prototype_if_ordinary_rawcx)' + customGetPrototype = 'Some(getPrototype)' + customSetPrototype = 'Some(proxyhandler::maybe_cross_origin_set_prototype_rawcx)' + # The base class `BaseProxyHandler`'s `setImmutablePrototype` (not to be + # confused with ECMAScript's `[[SetImmutablePrototype]]`) always fails. + # This is the desired behavior, so we don't override it. + + customSet = 'None' + if self.descriptor.isMaybeCrossOriginObject(): + # `maybe_cross_origin_set_rawcx` doesn't support legacy platform objects' + # `[[Set]]` (https://heycam.github.io/webidl/#legacy-platform-object-set) (yet). + assert not self.descriptor.operations['IndexedGetter'] + assert not self.descriptor.operations['NamedGetter'] + customSet = 'Some(proxyhandler::maybe_cross_origin_set_rawcx)' + getOwnEnumerablePropertyKeys = "own_property_keys" - if self.descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties"): + if self.descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties") or \ + self.descriptor.isMaybeCrossOriginObject(): getOwnEnumerablePropertyKeys = "getOwnEnumerablePropertyKeys" args = { "defineProperty": customDefineProperty, "delete": customDelete, + "getPrototypeIfOrdinary": customGetPrototypeIfOrdinary, + "getPrototype": customGetPrototype, + "setPrototype": customSetPrototype, + "set": customSet, "getOwnEnumerablePropertyKeys": getOwnEnumerablePropertyKeys, "trace": TRACE_HOOK_NAME, "finalize": FINALIZE_HOOK_NAME, @@ -3476,15 +3606,15 @@ let traps = ProxyTraps { ownPropertyKeys: Some(own_property_keys), delete_: Some(%(delete)s), enumerate: None, - getPrototypeIfOrdinary: Some(proxyhandler::get_prototype_if_ordinary), - getPrototype: None, - setPrototype: None, + getPrototypeIfOrdinary: %(getPrototypeIfOrdinary)s, + getPrototype: %(getPrototype)s, + setPrototype: %(setPrototype)s, setImmutablePrototype: None, preventExtensions: Some(proxyhandler::prevent_extensions), isExtensible: Some(proxyhandler::is_extensible), has: None, get: Some(get), - set: None, + set: %(set)s, call: None, construct: None, hasOwn: Some(hasOwn), @@ -5351,6 +5481,26 @@ class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): indexedGetter = self.descriptor.operations['IndexedGetter'] get = "let cx = SafeJSContext::from_ptr(cx);\n" + + if self.descriptor.isMaybeCrossOriginObject(): + get += dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + if !proxyhandler::cross_origin_get_own_property_helper( + cx, proxy, &CROSS_ORIGIN_PROPERTIES, id, desc + ) { + return false; + } + if desc.obj.is_null() { + return proxyhandler::cross_origin_property_fallback(cx, proxy, id, desc); + } + return true; + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + if indexedGetter: get += "let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n" @@ -5448,6 +5598,17 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): def getBody(self): set = "let cx = SafeJSContext::from_ptr(cx);\n" + if self.descriptor.isMaybeCrossOriginObject(): + set += dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::report_cross_origin_denial(cx, id, "define"); + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + indexedSetter = self.descriptor.operations['IndexedSetter'] if indexedSetter: set += ("let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n" @@ -5495,6 +5656,18 @@ class CGDOMJSProxyHandler_delete(CGAbstractExternMethod): def getBody(self): set = "let cx = SafeJSContext::from_ptr(cx);\n" + + if self.descriptor.isMaybeCrossOriginObject(): + set += dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::report_cross_origin_denial(cx, id, "delete"); + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + if self.descriptor.operations['NamedDeleter']: if self.descriptor.hasUnforgeableMembers: raise TypeError("Can't handle a deleter on an interface that has " @@ -5522,6 +5695,17 @@ class CGDOMJSProxyHandler_ownPropertyKeys(CGAbstractExternMethod): let unwrapped_proxy = UnwrapProxy(proxy); """) + if self.descriptor.isMaybeCrossOriginObject(): + body += dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::cross_origin_own_property_keys(cx, proxy, &CROSS_ORIGIN_PROPERTIES, props); + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + if self.descriptor.operations['IndexedGetter']: body += dedent( """ @@ -5566,7 +5750,8 @@ class CGDOMJSProxyHandler_ownPropertyKeys(CGAbstractExternMethod): class CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(CGAbstractExternMethod): def __init__(self, descriptor): assert (descriptor.operations["IndexedGetter"] - and descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties")) + and descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties") + or descriptor.isMaybeCrossOriginObject()) args = [Argument('*mut JSContext', 'cx'), Argument('RawHandleObject', 'proxy'), Argument('RawMutableHandleIdVector', 'props')] @@ -5581,6 +5766,18 @@ class CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(CGAbstractExternMethod): let unwrapped_proxy = UnwrapProxy(proxy); """) + if self.descriptor.isMaybeCrossOriginObject(): + body += dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + // There are no enumerable cross-origin props, so we're done. + return true; + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + if self.descriptor.operations['IndexedGetter']: body += dedent( """ @@ -5619,6 +5816,18 @@ class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod): def getBody(self): indexedGetter = self.descriptor.operations['IndexedGetter'] indexed = "let cx = SafeJSContext::from_ptr(cx);\n" + + if self.descriptor.isMaybeCrossOriginObject(): + indexed += dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::cross_origin_has_own(cx, proxy, &CROSS_ORIGIN_PROPERTIES, id, bp); + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + if indexedGetter: indexed += ("let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n" + "if let Some(index) = index {\n" @@ -5680,6 +5889,18 @@ class CGDOMJSProxyHandler_get(CGAbstractExternMethod): # https://heycam.github.io/webidl/#LegacyPlatformObjectGetOwnProperty def getBody(self): + if self.descriptor.isMaybeCrossOriginObject(): + maybeCrossOriginGet = dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::cross_origin_get(cx, proxy, receiver, id, vp); + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + else: + maybeCrossOriginGet = "" getFromExpando = """\ rooted!(in(*cx) let mut expando = ptr::null_mut::<JSObject>()); get_expando_object(proxy, expando.handle_mut()); @@ -5735,6 +5956,9 @@ if !expando.is_null() { //MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), //"Should not have a XrayWrapper here"); let cx = SafeJSContext::from_ptr(cx); + +%s + let proxy_lt = Handle::from_raw(proxy); let vp_lt = MutableHandle::from_raw(vp); let id_lt = Handle::from_raw(id); @@ -5751,7 +5975,26 @@ if found { } %s vp.set(UndefinedValue()); -return true;""" % (getIndexedOrExpando, getNamed) +return true;""" % (maybeCrossOriginGet, getIndexedOrExpando, getNamed) + + def definition_body(self): + return CGGeneric(self.getBody()) + + +class CGDOMJSProxyHandler_getPrototype(CGAbstractExternMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('RawHandleObject', 'proxy'), + Argument('RawMutableHandleObject', 'proto')] + CGAbstractExternMethod.__init__(self, descriptor, "getPrototype", "bool", args) + assert descriptor.isMaybeCrossOriginObject() + self.descriptor = descriptor + + def getBody(self): + return dedent( + """ + let cx = SafeJSContext::from_ptr(cx); + proxyhandler::maybe_cross_origin_get_prototype(cx, proxy, GetProtoObject, proto) + """) def definition_body(self): return CGGeneric(self.getBody()) @@ -6390,6 +6633,9 @@ class CGDescriptor(CGThing): if descriptor.proxy: cgThings.append(CGDefineProxyHandler(descriptor)) + if descriptor.isMaybeCrossOriginObject(): + cgThings.append(CGCrossOriginProperties(descriptor)) + properties = PropertyArrays(descriptor) if defaultToJSONMethod: @@ -6401,22 +6647,27 @@ class CGDescriptor(CGThing): cgThings.append(CGProxyUnwrap(descriptor)) cgThings.append(CGDOMJSProxyHandlerDOMClass(descriptor)) cgThings.append(CGDOMJSProxyHandler_ownPropertyKeys(descriptor)) - if descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties"): + if descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties") or \ + descriptor.isMaybeCrossOriginObject(): cgThings.append(CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(descriptor)) cgThings.append(CGDOMJSProxyHandler_getOwnPropertyDescriptor(descriptor)) cgThings.append(CGDOMJSProxyHandler_className(descriptor)) cgThings.append(CGDOMJSProxyHandler_get(descriptor)) cgThings.append(CGDOMJSProxyHandler_hasOwn(descriptor)) - if descriptor.operations['IndexedSetter'] or descriptor.operations['NamedSetter']: + if descriptor.isMaybeCrossOriginObject() or descriptor.operations['IndexedSetter'] or \ + descriptor.operations['NamedSetter']: cgThings.append(CGDOMJSProxyHandler_defineProperty(descriptor)) # We want to prevent indexed deleters from compiling at all. assert not descriptor.operations['IndexedDeleter'] - if descriptor.operations['NamedDeleter']: + if descriptor.isMaybeCrossOriginObject() or descriptor.operations['NamedDeleter']: cgThings.append(CGDOMJSProxyHandler_delete(descriptor)) + if descriptor.isMaybeCrossOriginObject(): + cgThings.append(CGDOMJSProxyHandler_getPrototype(descriptor)) + # cgThings.append(CGDOMJSProxyHandler(descriptor)) # cgThings.append(CGIsMethod(descriptor)) pass diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py index b92f68af3b9..cf6885be265 100644 --- a/components/script/dom/bindings/codegen/Configuration.py +++ b/components/script/dom/bindings/codegen/Configuration.py @@ -299,6 +299,9 @@ class Descriptor(DescriptorProvider): if iface: iface.setUserData('hasConcreteDescendant', True) + if self.isMaybeCrossOriginObject(): + self.proxy = True + if self.proxy: iface = self.interface while iface.parent: @@ -404,6 +407,11 @@ class Descriptor(DescriptorProvider): def supportsIndexedProperties(self): return self.operations['IndexedGetter'] is not None + def isMaybeCrossOriginObject(self): + # If we're isGlobal and have cross-origin members, we're a Window, and + # that's not a cross-origin object. The WindowProxy is. + return self.concrete and self.interface.hasCrossOriginMembers and not self.isGlobal() + def hasDescendants(self): return (self.interface.getUserData("hasConcreteDescendant", False) or self.interface.getUserData("hasProxyDescendant", False)) diff --git a/components/script/dom/bindings/interface.rs b/components/script/dom/bindings/interface.rs index 82303f800ba..f73152c9df7 100644 --- a/components/script/dom/bindings/interface.rs +++ b/components/script/dom/bindings/interface.rs @@ -9,6 +9,7 @@ use crate::dom::bindings::codegen::PrototypeList; use crate::dom::bindings::constant::{define_constants, ConstantSpec}; use crate::dom::bindings::conversions::{get_dom_class, DOM_OBJECT_SLOT}; use crate::dom::bindings::guard::Guard; +use crate::dom::bindings::principals::ServoJSPrincipals; use crate::dom::bindings::utils::{ProtoOrIfaceArray, DOM_PROTOTYPE_SLOT}; use crate::script_runtime::JSContext as SafeJSContext; use js::error::throw_type_error; @@ -36,6 +37,7 @@ use js::rust::wrappers::{JS_DefineProperty3, JS_DefineProperty4, JS_DefineProper use js::rust::wrappers::{JS_LinkConstructorAndPrototype, JS_NewObjectWithGivenProto}; use js::rust::{define_methods, define_properties, get_object_class}; use js::rust::{HandleObject, HandleValue, MutableHandleObject, RealmOptions}; +use servo_url::MutableOrigin; use std::convert::TryFrom; use std::ptr; @@ -136,6 +138,7 @@ pub unsafe fn create_global_object( private: *const libc::c_void, trace: TraceHook, mut rval: MutableHandleObject, + origin: &MutableOrigin, ) { assert!(rval.is_null()); @@ -145,10 +148,12 @@ pub unsafe fn create_global_object( options.creationOptions_.streams_ = true; select_compartment(cx, &mut options); + let principal = ServoJSPrincipals::new(origin); + rval.set(JS_NewGlobalObject( *cx, class, - ptr::null_mut(), + principal.as_raw(), OnNewGlobalHookOption::DontFireOnNewGlobalHook, &*options, )); diff --git a/components/script/dom/bindings/mod.rs b/components/script/dom/bindings/mod.rs index 8b3f9682544..e446af3693f 100644 --- a/components/script/dom/bindings/mod.rs +++ b/components/script/dom/bindings/mod.rs @@ -145,6 +145,7 @@ pub mod interface; pub mod iterable; pub mod namespace; pub mod num; +pub mod principals; pub mod proxyhandler; pub mod record; pub mod refcounted; diff --git a/components/script/dom/bindings/principals.rs b/components/script/dom/bindings/principals.rs new file mode 100644 index 00000000000..b16bc8859db --- /dev/null +++ b/components/script/dom/bindings/principals.rs @@ -0,0 +1,139 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use js::{ + glue::{ + CreateRustJSPrincipals, DestroyRustJSPrincipals, GetRustJSPrincipalsPrivate, + JSPrincipalsCallbacks, + }, + jsapi::{JSPrincipals, JS_DropPrincipals, JS_HoldPrincipals}, + rust::Runtime, +}; +use servo_url::MutableOrigin; +use std::{marker::PhantomData, mem::ManuallyDrop, ops::Deref, ptr::NonNull}; + +/// An owned reference to Servo's `JSPrincipals` instance. +#[repr(transparent)] +pub struct ServoJSPrincipals(NonNull<JSPrincipals>); + +impl ServoJSPrincipals { + pub fn new(origin: &MutableOrigin) -> Self { + unsafe { + let private: Box<MutableOrigin> = Box::new(origin.clone()); + let raw = CreateRustJSPrincipals(&PRINCIPALS_CALLBACKS, Box::into_raw(private) as _); + // The created `JSPrincipals` object has an initial reference + // count of zero, so the following code will set it to one + Self::from_raw_nonnull(NonNull::new_unchecked(raw)) + } + } + + /// Construct `Self` from a raw `*mut JSPrincipals`, incrementing its + /// reference count. + #[inline] + pub unsafe fn from_raw_nonnull(raw: NonNull<JSPrincipals>) -> Self { + JS_HoldPrincipals(raw.as_ptr()); + Self(raw) + } + + #[inline] + pub unsafe fn origin(&self) -> MutableOrigin { + let origin = GetRustJSPrincipalsPrivate(self.0.as_ptr()) as *mut MutableOrigin; + (*origin).clone() + } + + #[inline] + pub fn as_raw_nonnull(&self) -> NonNull<JSPrincipals> { + self.0 + } + + #[inline] + pub fn as_raw(&self) -> *mut JSPrincipals { + self.0.as_ptr() + } +} + +impl Clone for ServoJSPrincipals { + #[inline] + fn clone(&self) -> Self { + unsafe { Self::from_raw_nonnull(self.as_raw_nonnull()) } + } +} + +impl Drop for ServoJSPrincipals { + #[inline] + fn drop(&mut self) { + unsafe { JS_DropPrincipals(Runtime::get(), self.as_raw()) }; + } +} + +/// A borrowed reference to Servo's `JSPrincipals` instance. Does not update the +/// reference count on creation and deletion. +pub struct ServoJSPrincipalsRef<'a>(ManuallyDrop<ServoJSPrincipals>, PhantomData<&'a ()>); + +impl ServoJSPrincipalsRef<'_> { + /// Construct `Self` from a raw `NonNull<JSPrincipals>`. + /// + /// # Safety + /// + /// `ServoJSPrincipalsRef` does not update the reference count of the + /// wrapped `JSPrincipals` object. It's up to the caller to ensure the + /// returned `ServoJSPrincipalsRef` object or any clones are not used past + /// the lifetime of the wrapped object. + #[inline] + pub unsafe fn from_raw_nonnull(raw: NonNull<JSPrincipals>) -> Self { + // Don't use `ServoJSPrincipals::from_raw_nonnull`; we don't want to + // update the reference count + Self(ManuallyDrop::new(ServoJSPrincipals(raw)), PhantomData) + } + + /// Construct `Self` from a raw `*mut JSPrincipals`. + /// + /// # Safety + /// + /// The behavior is undefined if `raw` is null. See also + /// [`Self::from_raw_nonnull`]. + #[inline] + pub unsafe fn from_raw_unchecked(raw: *mut JSPrincipals) -> Self { + Self::from_raw_nonnull(NonNull::new_unchecked(raw)) + } +} + +impl Clone for ServoJSPrincipalsRef<'_> { + #[inline] + fn clone(&self) -> Self { + Self(ManuallyDrop::new(ServoJSPrincipals(self.0 .0)), PhantomData) + } +} + +impl Deref for ServoJSPrincipalsRef<'_> { + type Target = ServoJSPrincipals; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub unsafe extern "C" fn destroy_servo_jsprincipal(principals: *mut JSPrincipals) { + Box::from_raw(GetRustJSPrincipalsPrivate(principals) as *mut MutableOrigin); + DestroyRustJSPrincipals(principals); +} + +const PRINCIPALS_CALLBACKS: JSPrincipalsCallbacks = JSPrincipalsCallbacks { + write: None, + isSystemOrAddonPrincipal: Some(principals_is_system_or_addon_principal), +}; + +unsafe extern "C" fn principals_is_system_or_addon_principal(_: *mut JSPrincipals) -> bool { + false +} + +//TODO is same_origin_domain equivalent to subsumes for our purposes +pub unsafe extern "C" fn subsumes(obj: *mut JSPrincipals, other: *mut JSPrincipals) -> bool { + let obj = ServoJSPrincipalsRef::from_raw_unchecked(obj); + let other = ServoJSPrincipalsRef::from_raw_unchecked(other); + let obj_origin = obj.origin(); + let other_origin = other.origin(); + obj_origin.same_origin_domain(&other_origin) +} diff --git a/components/script/dom/bindings/proxyhandler.rs b/components/script/dom/bindings/proxyhandler.rs index 11913c03642..3650df6186b 100644 --- a/components/script/dom/bindings/proxyhandler.rs +++ b/components/script/dom/bindings/proxyhandler.rs @@ -6,25 +6,47 @@ #![deny(missing_docs)] -use crate::dom::bindings::conversions::is_dom_proxy; +use crate::dom::bindings::conversions::{is_dom_proxy, jsid_to_string, jsstring_to_str}; +use crate::dom::bindings::error::{throw_dom_exception, Error}; +use crate::dom::bindings::principals::ServoJSPrincipalsRef; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::str::DOMString; use crate::dom::bindings::utils::delete_property_by_id; -use js::glue::GetProxyHandlerFamily; +use crate::dom::globalscope::GlobalScope; +use crate::realms::{AlreadyInRealm, InRealm}; +use crate::script_runtime::JSContext as SafeJSContext; +use js::conversions::ToJSValConvertible; +use js::glue::{ + GetProxyHandler, GetProxyHandlerFamily, InvokeGetOwnPropertyDescriptor, RUST_SYMBOL_TO_JSID, +}; use js::glue::{GetProxyPrivate, SetProxyPrivate}; +use js::jsapi; use js::jsapi::GetStaticPrototype; use js::jsapi::Handle as RawHandle; use js::jsapi::HandleId as RawHandleId; use js::jsapi::HandleObject as RawHandleObject; +use js::jsapi::HandleValue as RawHandleValue; +use js::jsapi::JSAutoRealm; +use js::jsapi::JS_AtomizeAndPinString; use js::jsapi::JS_DefinePropertyById; +use js::jsapi::JS_GetOwnPropertyDescriptorById; +use js::jsapi::JS_IsExceptionPending; +use js::jsapi::MutableHandle as RawMutableHandle; +use js::jsapi::MutableHandleIdVector as RawMutableHandleIdVector; use js::jsapi::MutableHandleObject as RawMutableHandleObject; +use js::jsapi::MutableHandleValue as RawMutableHandleValue; use js::jsapi::ObjectOpResult; +use js::jsapi::{jsid, GetObjectRealmOrNull, GetRealmPrincipals, JSFunctionSpec, JSPropertySpec}; use js::jsapi::{DOMProxyShadowsResult, JSContext, JSObject, PropertyDescriptor}; +use js::jsapi::{GetWellKnownSymbol, SymbolCode}; use js::jsapi::{JSErrNum, SetDOMProxyInformation}; use js::jsval::ObjectValue; use js::jsval::UndefinedValue; use js::rust::wrappers::JS_AlreadyHasOwnPropertyById; use js::rust::wrappers::JS_NewObjectWithGivenProto; -use js::rust::{Handle, HandleObject, MutableHandle, MutableHandleObject}; -use std::ptr; +use js::rust::wrappers::{AppendToIdVector, RUST_INTERNED_STRING_TO_JSID}; +use js::rust::{get_context_realm, Handle, HandleObject, MutableHandle, MutableHandleObject}; +use std::{ffi::CStr, os::raw::c_char, ptr}; /// Determine if this id shadows any existing properties for this proxy. pub unsafe extern "C" fn shadow_check_callback( @@ -120,7 +142,7 @@ pub unsafe extern "C" fn is_extensible( /// /// This implementation always handles the case of the ordinary /// `[[GetPrototypeOf]]` behavior. An alternative implementation will be -/// necessary for the Location object. +/// necessary for maybe-cross-origin objects. pub unsafe extern "C" fn get_prototype_if_ordinary( _: *mut JSContext, proxy: RawHandleObject, @@ -177,3 +199,576 @@ pub fn fill_property_descriptor( desc.getter = None; desc.setter = None; } + +/// <https://html.spec.whatwg.org/multipage/#isplatformobjectsameorigin-(-o-)> +pub unsafe fn is_platform_object_same_origin(cx: SafeJSContext, obj: RawHandleObject) -> bool { + let subject_realm = get_context_realm(*cx); + let obj_realm = GetObjectRealmOrNull(*obj); + assert!(!obj_realm.is_null()); + + let subject_principals = + ServoJSPrincipalsRef::from_raw_unchecked(GetRealmPrincipals(subject_realm)); + let obj_principals = ServoJSPrincipalsRef::from_raw_unchecked(GetRealmPrincipals(obj_realm)); + + let subject_origin = subject_principals.origin(); + let obj_origin = obj_principals.origin(); + + let result = subject_origin.same_origin_domain(&obj_origin); + log::trace!( + "object {:p} (realm = {:p}, principalls = {:p}, origin = {:?}) is {} \ + with reference to the current Realm (realm = {:p}, principals = {:p}, \ + origin = {:?})", + obj.get(), + obj_realm, + obj_principals.as_raw(), + obj_origin.immutable(), + ["NOT same domain-origin", "same domain-origin"][result as usize], + subject_realm, + subject_principals.as_raw(), + subject_origin.immutable() + ); + + result +} + +/// Report a cross-origin denial for a property, Always returns `false`, so it +/// can be used as `return report_cross_origin_denial(...);`. +/// +/// What this function does corresponds to the operations in +/// <https://html.spec.whatwg.org/multipage/#the-location-interface> denoted as +/// "Throw a `SecurityError` DOMException". +pub unsafe fn report_cross_origin_denial(cx: SafeJSContext, id: RawHandleId, access: &str) -> bool { + debug!( + "permission denied to {} property {} on cross-origin object", + access, + id_to_source(cx, id).as_deref().unwrap_or("< error >"), + ); + let in_realm_proof = AlreadyInRealm::assert_for_cx(cx); + if !JS_IsExceptionPending(*cx) { + let global = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)); + // TODO: include `id` and `access` in the exception message + throw_dom_exception(cx, &*global, Error::Security); + } + false +} + +unsafe fn id_to_source(cx: SafeJSContext, id: RawHandleId) -> Option<DOMString> { + rooted!(in(*cx) let mut value = UndefinedValue()); + rooted!(in(*cx) let mut jsstr = ptr::null_mut::<jsapi::JSString>()); + jsapi::JS_IdToValue(*cx, id.get(), value.handle_mut().into()) + .then(|| { + jsstr.set(jsapi::JS_ValueToSource(*cx, value.handle().into())); + jsstr.get() + }) + .filter(|jsstr| !jsstr.is_null()) + .map(|jsstr| jsstring_to_str(*cx, jsstr)) +} + +/// Property and method specs that correspond to the elements of +/// [`CrossOriginProperties(O)`]. +/// +/// [`CrossOriginProperties(O)`]: https://html.spec.whatwg.org/multipage/#crossoriginproperties-(-o-) +pub struct CrossOriginProperties { + pub attributes: &'static [JSPropertySpec], + pub methods: &'static [JSFunctionSpec], +} + +impl CrossOriginProperties { + /// Enumerate the property keys defined by `self`. + fn keys(&self) -> impl Iterator<Item = *const c_char> + '_ { + // Safety: All cross-origin property keys are strings, not symbols + self.attributes + .iter() + .map(|spec| unsafe { spec.name.string_ }) + .chain(self.methods.iter().map(|spec| unsafe { spec.name.string_ })) + .filter(|ptr| !ptr.is_null()) + } +} + +/// Implementation of [`CrossOriginOwnPropertyKeys`]. +/// +/// [`CrossOriginOwnPropertyKeys`]: https://html.spec.whatwg.org/multipage/#crossoriginownpropertykeys-(-o-) +pub unsafe fn cross_origin_own_property_keys( + cx: SafeJSContext, + _proxy: RawHandleObject, + cross_origin_properties: &'static CrossOriginProperties, + props: RawMutableHandleIdVector, +) -> bool { + // > 2. For each `e` of `! CrossOriginProperties(O)`, append + // > `e.[[Property]]` to `keys`. + for key in cross_origin_properties.keys() { + rooted!(in(*cx) let rooted = JS_AtomizeAndPinString(*cx, key)); + rooted!(in(*cx) let mut rooted_jsid: jsid); + RUST_INTERNED_STRING_TO_JSID(*cx, rooted.handle().get(), rooted_jsid.handle_mut()); + AppendToIdVector(props, rooted_jsid.handle()); + } + + // > 3. Return the concatenation of `keys` and `« "then", @@toStringTag, + // > @@hasInstance, @@isConcatSpreadable »`. + append_cross_origin_allowlisted_prop_keys(cx, props); + + true +} + +/// Implementation of `[[Set]]` for [`Location`]. +/// +/// [`Location`]: https://html.spec.whatwg.org/multipage/#location-set +pub unsafe extern "C" fn maybe_cross_origin_set_rawcx( + cx: *mut JSContext, + proxy: RawHandleObject, + id: RawHandleId, + v: RawHandleValue, + receiver: RawHandleValue, + result: *mut ObjectOpResult, +) -> bool { + let cx = SafeJSContext::from_ptr(cx); + + if !is_platform_object_same_origin(cx, proxy) { + return cross_origin_set(cx, proxy, id, v, receiver, result); + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + + // OrdinarySet + // <https://tc39.es/ecma262/#sec-ordinaryset> + rooted!(in(*cx) let mut own_desc = PropertyDescriptor::default()); + if !InvokeGetOwnPropertyDescriptor( + GetProxyHandler(*proxy), + *cx, + proxy, + id, + own_desc.handle_mut().into(), + ) { + return false; + } + + js::jsapi::SetPropertyIgnoringNamedGetter( + *cx, + proxy, + id, + v, + receiver, + own_desc.handle().into(), + result, + ) +} + +pub unsafe extern "C" fn maybe_cross_origin_get_prototype_if_ordinary_rawcx( + _: *mut JSContext, + _proxy: RawHandleObject, + is_ordinary: *mut bool, + _proto: RawMutableHandleObject, +) -> bool { + // We have a custom `[[GetPrototypeOf]]`, so return `false` + *is_ordinary = false; + true +} + +/// Implementation of `[[GetPrototypeOf]]` for [`Location`]. +/// +/// [`Location`]: https://html.spec.whatwg.org/multipage/#location-getprototypeof +pub unsafe fn maybe_cross_origin_get_prototype( + cx: SafeJSContext, + proxy: RawHandleObject, + get_proto_object: unsafe fn(cx: SafeJSContext, global: HandleObject, rval: MutableHandleObject), + proto: RawMutableHandleObject, +) -> bool { + // > 1. If ! IsPlatformObjectSameOrigin(this) is true, then return ! OrdinaryGetPrototypeOf(this). + if is_platform_object_same_origin(cx, proxy) { + let ac = JSAutoRealm::new(*cx, proxy.get()); + let global = GlobalScope::from_context(*cx, InRealm::Entered(&ac)); + get_proto_object( + cx, + global.reflector().get_jsobject(), + MutableHandleObject::from_raw(proto), + ); + return !proto.is_null(); + } + + // > 2. Return null. + proto.set(ptr::null_mut()); + true +} + +/// Implementation of `[[SetPrototypeOf]]` for [`Location`] and [`WindowProxy`]. +/// +/// [`Location`]: https://html.spec.whatwg.org/multipage/#location-setprototypeof +/// [`WindowProxy`]: https://html.spec.whatwg.org/multipage/#windowproxy-setprototypeof +pub unsafe extern "C" fn maybe_cross_origin_set_prototype_rawcx( + cx: *mut JSContext, + proxy: RawHandleObject, + proto: RawHandleObject, + result: *mut ObjectOpResult, +) -> bool { + // > 1. Return `! SetImmutablePrototype(this, V)`. + // + // <https://tc39.es/ecma262/#sec-set-immutable-prototype>: + // + // > 1. Assert: Either `Type(V)` is Object or `Type(V)` is Null. + // + // > 2. Let current be `? O.[[GetPrototypeOf]]()`. + rooted!(in(cx) let mut current = ptr::null_mut::<JSObject>()); + if !jsapi::GetObjectProto(cx, proxy, current.handle_mut().into()) { + return false; + } + + // > 3. If `SameValue(V, current)` is true, return true. + if proto.get() == current.get() { + (*result).code_ = 0 /* OkCode */; + return true; + } + + // > 4. Return false. + (*result).code_ = JSErrNum::JSMSG_CANT_SET_PROTO as usize; + true +} + +/// Implementation of [`CrossOriginGet`]. +/// +/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy +/// for a maybe-cross-origin object. +/// +/// [`CrossOriginGet`]: https://html.spec.whatwg.org/multipage/#crossoriginget-(-o,-p,-receiver-) +pub unsafe fn cross_origin_get( + cx: SafeJSContext, + proxy: RawHandleObject, + receiver: RawHandleValue, + id: RawHandleId, + vp: RawMutableHandleValue, +) -> bool { + // > 1. Let `desc` be `? O.[[GetOwnProperty]](P)`. + rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default()); + if !InvokeGetOwnPropertyDescriptor( + GetProxyHandler(*proxy), + *cx, + proxy, + id, + descriptor.handle_mut().into(), + ) { + return false; + } + + // > 2. Assert: `desc` is not undefined. + assert!( + !descriptor.obj.is_null(), + "Callees should throw in all cases when they are not finding \ + a property decriptor" + ); + + // > 3. If `! IsDataDescriptor(desc)` is true, then return `desc.[[Value]]`. + if is_data_descriptor(&descriptor) { + vp.set(descriptor.value); + return true; + } + + // > 4. Assert: `IsAccessorDescriptor(desc)` is `true`. + assert!(is_accessor_descriptor(&descriptor)); + + // > 5. Let `getter` be `desc.[[Get]]`. + // > + // > 6. If `getter` is `undefined`, then throw a `SecurityError` + // > `DOMException`. + rooted!(in(*cx) let mut getter = ptr::null_mut::<JSObject>()); + get_getter_object(&descriptor, getter.handle_mut().into()); + if getter.get().is_null() { + return report_cross_origin_denial(cx, id, "get"); + } + + rooted!(in(*cx) let mut getter_jsval = UndefinedValue()); + getter.get().to_jsval(*cx, getter_jsval.handle_mut()); + + // > 7. Return `? Call(getter, Receiver)`. + jsapi::Call( + *cx, + receiver, + getter_jsval.handle().into(), + &jsapi::HandleValueArray::new(), + vp, + ) +} + +/// Implementation of [`CrossOriginSet`]. +/// +/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy +/// for a maybe-cross-origin object. +/// +/// [`CrossOriginSet`]: https://html.spec.whatwg.org/multipage/#crossoriginset-(-o,-p,-v,-receiver-) +pub unsafe fn cross_origin_set( + cx: SafeJSContext, + proxy: RawHandleObject, + id: RawHandleId, + v: RawHandleValue, + receiver: RawHandleValue, + result: *mut ObjectOpResult, +) -> bool { + // > 1. Let desc be ? O.[[GetOwnProperty]](P). + rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default()); + if !InvokeGetOwnPropertyDescriptor( + GetProxyHandler(*proxy), + *cx, + proxy, + id, + descriptor.handle_mut().into(), + ) { + return false; + } + + // > 2. Assert: desc is not undefined. + assert!( + !descriptor.obj.is_null(), + "Callees should throw in all cases when they are not finding \ + a property decriptor" + ); + + // > 3. If desc.[[Set]] is present and its value is not undefined, + // > then: [...] + rooted!(in(*cx) let mut setter = ptr::null_mut::<JSObject>()); + get_setter_object(&descriptor, setter.handle_mut().into()); + if setter.get().is_null() { + // > 4. Throw a "SecurityError" DOMException. + return report_cross_origin_denial(cx, id, "set"); + } + + rooted!(in(*cx) let mut setter_jsval = UndefinedValue()); + setter.get().to_jsval(*cx, setter_jsval.handle_mut()); + + // > 3.1. Perform ? Call(setter, Receiver, «V»). + // > + // > 3.2. Return true. + rooted!(in(*cx) let mut ignored = UndefinedValue()); + if !jsapi::Call( + *cx, + receiver, + setter_jsval.handle().into(), + // FIXME: Our binding lacks `HandleValueArray(Handle<Value>)` + // <https://searchfox.org/mozilla-central/rev/072710086ddfe25aa2962c8399fefb2304e8193b/js/public/ValueArray.h#54-55> + &jsapi::HandleValueArray { + length_: 1, + elements_: v.ptr, + }, + ignored.handle_mut().into(), + ) { + return false; + } + + (*result).code_ = 0 /* OkCode */; + true +} + +unsafe fn get_getter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) { + if (d.attrs & jsapi::JSPROP_GETTER as u32) != 0 { + out.set(std::mem::transmute(d.getter)); + } +} + +unsafe fn get_setter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) { + if (d.attrs & jsapi::JSPROP_SETTER as u32) != 0 { + out.set(std::mem::transmute(d.setter)); + } +} + +/// <https://tc39.es/ecma262/#sec-isaccessordescriptor> +fn is_accessor_descriptor(d: &PropertyDescriptor) -> bool { + d.attrs & (jsapi::JSPROP_GETTER as u32 | jsapi::JSPROP_SETTER as u32) != 0 +} + +/// <https://tc39.es/ecma262/#sec-isdatadescriptor> +fn is_data_descriptor(d: &PropertyDescriptor) -> bool { + let is_accessor = is_accessor_descriptor(d); + let is_generic = d.attrs & + (jsapi::JSPROP_GETTER as u32 | + jsapi::JSPROP_SETTER as u32 | + jsapi::JSPROP_IGNORE_READONLY | + jsapi::JSPROP_IGNORE_VALUE) == + jsapi::JSPROP_IGNORE_READONLY | jsapi::JSPROP_IGNORE_VALUE; + !is_accessor && !is_generic +} + +/// Evaluate `CrossOriginGetOwnPropertyHelper(proxy, id) != null`. +/// SpiderMonkey-specific. +/// +/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy +/// for a maybe-cross-origin object. +pub unsafe fn cross_origin_has_own( + cx: SafeJSContext, + _proxy: RawHandleObject, + cross_origin_properties: &'static CrossOriginProperties, + id: RawHandleId, + bp: *mut bool, +) -> bool { + // TODO: Once we have the slot for the holder, it'd be more efficient to + // use `ensure_cross_origin_property_holder`. We'll need `_proxy` to + // do that. + *bp = jsid_to_string(*cx, Handle::from_raw(id)).map_or(false, |key| { + cross_origin_properties.keys().any(|defined_key| { + let defined_key = CStr::from_ptr(defined_key); + defined_key.to_bytes() == key.as_bytes() + }) + }); + + true +} + +/// Implementation of [`CrossOriginGetOwnPropertyHelper`]. +/// +/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy +/// for a maybe-cross-origin object. +/// +/// [`CrossOriginGetOwnPropertyHelper`]: https://html.spec.whatwg.org/multipage/#crossorigingetownpropertyhelper-(-o,-p-) +pub unsafe fn cross_origin_get_own_property_helper( + cx: SafeJSContext, + proxy: RawHandleObject, + cross_origin_properties: &'static CrossOriginProperties, + id: RawHandleId, + mut desc: RawMutableHandle<PropertyDescriptor>, +) -> bool { + rooted!(in(*cx) let mut holder = ptr::null_mut::<JSObject>()); + + ensure_cross_origin_property_holder( + cx, + proxy, + cross_origin_properties, + holder.handle_mut().into(), + ); + + if !JS_GetOwnPropertyDescriptorById(*cx, holder.handle().into(), id, desc) { + return false; + } + + if !desc.obj.is_null() { + desc.obj = proxy.get(); + } + + true +} + +/// Implementation of [`CrossOriginPropertyFallback`]. +/// +/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy +/// for a maybe-cross-origin object. +/// +/// [`CrossOriginPropertyFallback`]: https://html.spec.whatwg.org/multipage/#crossoriginpropertyfallback-(-p-) +pub unsafe fn cross_origin_property_fallback( + cx: SafeJSContext, + proxy: RawHandleObject, + id: RawHandleId, + mut desc: RawMutableHandle<PropertyDescriptor>, +) -> bool { + assert!(desc.obj.is_null(), "why are we being called?"); + + // > 1. If P is `then`, `@@toStringTag`, `@@hasInstance`, or + // > `@@isConcatSpreadable`, then return `PropertyDescriptor{ [[Value]]: + // > undefined, [[Writable]]: false, [[Enumerable]]: false, + // > [[Configurable]]: true }`. + if is_cross_origin_allowlisted_prop(cx, id) { + *desc = PropertyDescriptor { + getter: None, + setter: None, + value: UndefinedValue(), + attrs: jsapi::JSPROP_READONLY as u32, + obj: proxy.get(), + }; + return true; + } + + // > 2. Throw a `SecurityError` `DOMException`. + report_cross_origin_denial(cx, id, "access") +} + +const ALLOWLISTED_SYMBOL_CODES: &[SymbolCode] = &[ + SymbolCode::toStringTag, + SymbolCode::hasInstance, + SymbolCode::isConcatSpreadable, +]; + +unsafe fn is_cross_origin_allowlisted_prop(cx: SafeJSContext, id: RawHandleId) -> bool { + if jsid_to_string(*cx, Handle::from_raw(id)).map_or(false, |st| st == "then") { + return true; + } + + rooted!(in(*cx) let mut allowed_id: jsid); + ALLOWLISTED_SYMBOL_CODES.iter().any(|&allowed_code| { + RUST_SYMBOL_TO_JSID( + GetWellKnownSymbol(*cx, allowed_code), + allowed_id.handle_mut().into(), + ); + // `jsid`s containing `JS::Symbol *` can be compared by + // referential equality + allowed_id.get().asBits == id.asBits + }) +} + +/// Append `« "then", @@toStringTag, @@hasInstance, @@isConcatSpreadable »` to +/// `props`. This is used to implement [`CrossOriginOwnPropertyKeys`]. +/// +/// [`CrossOriginOwnPropertyKeys`]: https://html.spec.whatwg.org/multipage/#crossoriginownpropertykeys-(-o-) +unsafe fn append_cross_origin_allowlisted_prop_keys( + cx: SafeJSContext, + props: RawMutableHandleIdVector, +) { + rooted!(in(*cx) let mut id: jsid); + + let jsstring = JS_AtomizeAndPinString(*cx, b"then\0".as_ptr() as *const c_char); + rooted!(in(*cx) let rooted = jsstring); + RUST_INTERNED_STRING_TO_JSID(*cx, rooted.handle().get(), id.handle_mut()); + AppendToIdVector(props, id.handle()); + + for &allowed_code in ALLOWLISTED_SYMBOL_CODES.iter() { + RUST_SYMBOL_TO_JSID( + GetWellKnownSymbol(*cx, allowed_code), + id.handle_mut().into(), + ); + AppendToIdVector(props, id.handle()); + } +} + +/// Get the holder for cross-origin properties for the current global of the +/// `JSContext`, creating one and storing it in a slot of the proxy object if it +/// doesn't exist yet. +/// +/// This essentially creates a cache of [`CrossOriginGetOwnPropertyHelper`]'s +/// results for all property keys. +/// +/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy +/// for a maybe-cross-origin object. The `out_holder` return value will always +/// be in the Realm of `cx`. +/// +/// [`CrossOriginGetOwnPropertyHelper`]: https://html.spec.whatwg.org/multipage/#crossorigingetownpropertyhelper-(-o,-p-) +unsafe fn ensure_cross_origin_property_holder( + cx: SafeJSContext, + _proxy: RawHandleObject, + cross_origin_properties: &'static CrossOriginProperties, + out_holder: RawMutableHandleObject, +) -> bool { + // TODO: We don't have the slot to store the holder yet. For now, + // the holder is constructed every time this function is called, + // which is not only inefficient but also deviates from the + // specification in a subtle yet observable way. + + // Create a holder for the current Realm + out_holder.set(jsapi::JS_NewObjectWithGivenProto( + *cx, + ptr::null_mut(), + RawHandleObject::null(), + )); + + if out_holder.get().is_null() || + !jsapi::JS_DefineProperties( + *cx, + out_holder.handle(), + cross_origin_properties.attributes.as_ptr(), + ) || + !jsapi::JS_DefineFunctions( + *cx, + out_holder.handle(), + cross_origin_properties.methods.as_ptr(), + ) + { + return false; + } + + // TODO: Store the holder in the slot that we don't have yet. + + true +} diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs index 36f255bc4ee..77192291416 100644 --- a/components/script/dom/bindings/utils.rs +++ b/components/script/dom/bindings/utils.rs @@ -17,8 +17,8 @@ use crate::dom::bindings::trace::trace_object; use crate::dom::windowproxy; use crate::script_runtime::JSContext as SafeJSContext; use js::conversions::ToJSValConvertible; +use js::glue::JS_GetReservedSlot; use js::glue::{CallJitGetterOp, CallJitMethodOp, CallJitSetterOp, IsWrapper}; -use js::glue::{GetCrossCompartmentWrapper, JS_GetReservedSlot, WrapperNew}; use js::glue::{UnwrapObjectDynamic, UnwrapObjectStatic, RUST_JSID_TO_INT, RUST_JSID_TO_STRING}; use js::glue::{ RUST_FUNCTION_VALUE_TO_JITINFO, RUST_JSID_IS_INT, RUST_JSID_IS_STRING, RUST_JSID_IS_VOID, @@ -26,12 +26,11 @@ use js::glue::{ use js::jsapi::HandleId as RawHandleId; use js::jsapi::HandleObject as RawHandleObject; use js::jsapi::MutableHandleIdVector as RawMutableHandleIdVector; -use js::jsapi::MutableHandleObject as RawMutableHandleObject; use js::jsapi::{AtomToLinearString, GetLinearStringCharAt, GetLinearStringLength}; use js::jsapi::{CallArgs, DOMCallbacks, GetNonCCWObjectGlobal}; -use js::jsapi::{Heap, JSAutoRealm, JSContext, JS_FreezeObject}; +use js::jsapi::{Heap, JSContext, JS_FreezeObject}; use js::jsapi::{JSAtom, JS_IsExceptionPending, JS_IsGlobalObject}; -use js::jsapi::{JSJitInfo, JSObject, JSTracer, JSWrapObjectCallbacks}; +use js::jsapi::{JSJitInfo, JSObject, JSTracer}; use js::jsapi::{ JS_DeprecatedStringHasLatin1Chars, JS_ResolveStandardClass, ObjectOpResult, StringIsArrayIndex, }; @@ -44,7 +43,7 @@ use js::rust::wrappers::JS_GetPrototype; use js::rust::wrappers::JS_HasProperty; use js::rust::wrappers::JS_HasPropertyById; use js::rust::wrappers::JS_SetProperty; -use js::rust::{get_object_class, is_dom_class, GCMethods, ToString, ToWindowProxyIfWindow}; +use js::rust::{get_object_class, is_dom_class, GCMethods, ToString}; use js::rust::{Handle, HandleId, HandleObject, HandleValue, MutableHandleValue}; use js::typedarray::{CreateWith, Float32Array}; use js::JS_CALLEE; @@ -53,6 +52,7 @@ use std::ffi::CString; use std::os::raw::{c_char, c_void}; use std::ptr; use std::slice; +use std::str; /// Proxy handler for a WindowProxy. pub struct WindowProxyHandler(pub *const libc::c_void); @@ -475,36 +475,6 @@ pub unsafe extern "C" fn resolve_global( true } -unsafe extern "C" fn wrap( - cx: *mut JSContext, - _existing: RawHandleObject, - obj: RawHandleObject, -) -> *mut JSObject { - // FIXME terrible idea. need security wrappers - // https://github.com/servo/servo/issues/2382 - WrapperNew(cx, obj, GetCrossCompartmentWrapper(), ptr::null(), false) -} - -unsafe extern "C" fn pre_wrap( - cx: *mut JSContext, - _scope: RawHandleObject, - _orig_obj: RawHandleObject, - obj: RawHandleObject, - _object_passed_to_wrap: RawHandleObject, - rval: RawMutableHandleObject, -) { - let _ac = JSAutoRealm::new(cx, obj.get()); - let obj = ToWindowProxyIfWindow(obj.get()); - assert!(!obj.is_null()); - rval.set(obj) -} - -/// Callback table for use with JS_SetWrapObjectCallbacks -pub static WRAP_CALLBACKS: JSWrapObjectCallbacks = JSWrapObjectCallbacks { - wrap: Some(wrap), - preWrap: Some(pre_wrap), -}; - /// Deletes the property `id` from `object`. pub unsafe fn delete_property_by_id( cx: *mut JSContext, diff --git a/components/script/dom/webidls/Location.webidl b/components/script/dom/webidls/Location.webidl index 4120fc2731d..4d9fe2239f2 100644 --- a/components/script/dom/webidls/Location.webidl +++ b/components/script/dom/webidls/Location.webidl @@ -4,7 +4,8 @@ // https://html.spec.whatwg.org/multipage/#location [Exposed=Window, Unforgeable] interface Location { - [Throws] stringifier attribute USVString href; + [Throws, CrossOriginWritable] + stringifier attribute USVString href; [Throws] readonly attribute USVString origin; [Throws] attribute USVString protocol; [Throws] attribute USVString host; @@ -15,7 +16,8 @@ [Throws] attribute USVString hash; [Throws] void assign(USVString url); - [Throws] void replace(USVString url); + [Throws, CrossOriginCallable] + void replace(USVString url); [Throws] void reload(); //[SameObject] readonly attribute USVString[] ancestorOrigins; diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl index fa3460e9889..289aa52cfb8 100644 --- a/components/script/dom/webidls/Window.webidl +++ b/components/script/dom/webidls/Window.webidl @@ -6,13 +6,14 @@ [Global=Window, Exposed=Window /*, LegacyUnenumerableNamedProperties */] /*sealed*/ interface Window : GlobalScope { // the current browsing context - [Unforgeable] readonly attribute WindowProxy window; - [BinaryName="Self_", Replaceable] readonly attribute WindowProxy self; + [Unforgeable, CrossOriginReadable] readonly attribute WindowProxy window; + [BinaryName="Self_", Replaceable, CrossOriginReadable] readonly attribute WindowProxy self; [Unforgeable] readonly attribute Document document; attribute DOMString name; - [PutForwards=href, Unforgeable] readonly attribute Location location; + [PutForwards=href, Unforgeable, CrossOriginReadable, CrossOriginWritable] + readonly attribute Location location; readonly attribute History history; [Pref="dom.customelements.enabled"] readonly attribute CustomElementRegistry customElements; @@ -23,22 +24,22 @@ //[Replaceable] readonly attribute BarProp statusbar; //[Replaceable] readonly attribute BarProp toolbar; attribute DOMString status; - void close(); - readonly attribute boolean closed; + [CrossOriginCallable] void close(); + [CrossOriginReadable] readonly attribute boolean closed; void stop(); - //void focus(); - //void blur(); + //[CrossOriginCallable] void focus(); + //[CrossOriginCallable] void blur(); // other browsing contexts - [Replaceable] readonly attribute WindowProxy frames; - [Replaceable] readonly attribute unsigned long length; + [Replaceable, CrossOriginReadable] readonly attribute WindowProxy frames; + [Replaceable, CrossOriginReadable] readonly attribute unsigned long length; // Note that this can return null in the case that the browsing context has been discarded. // https://github.com/whatwg/html/issues/2115 - [Unforgeable] readonly attribute WindowProxy? top; - attribute any opener; + [Unforgeable, CrossOriginReadable] readonly attribute WindowProxy? top; + [CrossOriginReadable] attribute any opener; // Note that this can return null in the case that the browsing context has been discarded. // https://github.com/whatwg/html/issues/2115 - [Replaceable] readonly attribute WindowProxy? parent; + [Replaceable, CrossOriginReadable] readonly attribute WindowProxy? parent; readonly attribute Element? frameElement; [Throws] WindowProxy? open(optional USVString url = "", optional DOMString target = "_blank", optional DOMString features = ""); @@ -63,9 +64,9 @@ unsigned long requestAnimationFrame(FrameRequestCallback callback); void cancelAnimationFrame(unsigned long handle); - [Throws] + [Throws, CrossOriginCallable] void postMessage(any message, USVString targetOrigin, optional sequence<object> transfer = []); - [Throws] + [Throws, CrossOriginCallable] void postMessage(any message, optional WindowPostMessageOptions options = {}); // also has obsolete members diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs index 244b42dc8a5..bccded0f43c 100644 --- a/components/script/dom/windowproxy.rs +++ b/components/script/dom/windowproxy.rs @@ -1084,6 +1084,9 @@ pub fn new_window_proxy_handler() -> WindowProxyHandler { // These traps often throw security errors, and only pass on calls to methods // defined in the DissimilarOriginWindow IDL. +// TODO: reuse the infrastructure in `proxyhandler.rs`. For starters, the calls +// to this function should be replaced with those to +// `report_cross_origin_denial`. #[allow(unsafe_code)] unsafe fn throw_security_error(cx: *mut JSContext, realm: InRealm) -> bool { if !JS_IsExceptionPending(cx) { diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs index c44b4b04676..5ce21f01366 100644 --- a/components/script/script_runtime.rs +++ b/components/script/script_runtime.rs @@ -16,6 +16,7 @@ use crate::dom::bindings::conversions::private_from_object; use crate::dom::bindings::conversions::root_from_handleobject; use crate::dom::bindings::error::{throw_dom_exception, Error}; use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::principals; use crate::dom::bindings::refcounted::{trace_refcounted_objects, LiveDOMReferences}; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::DomObject; @@ -61,6 +62,7 @@ use js::jsapi::{ JSJitCompilerOption, JS_SetOffthreadIonCompilationEnabled, JS_SetParallelParsingEnabled, }; use js::jsapi::{JSObject, PromiseRejectionHandlingState, SetPreserveWrapperCallbacks}; +use js::jsapi::{JSSecurityCallbacks, JS_InitDestroyPrincipalsCallback, JS_SetSecurityCallbacks}; use js::jsapi::{SetJobQueue, SetProcessBuildIdOp, SetPromiseRejectionTrackerCallback}; use js::jsval::UndefinedValue; use js::panic::wrap_panic; @@ -97,6 +99,12 @@ static JOB_QUEUE_TRAPS: JobQueueTraps = JobQueueTraps { empty: Some(empty), }; +static SECURITY_CALLBACKS: JSSecurityCallbacks = JSSecurityCallbacks { + // TODO: Content Security Policy <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP> + contentSecurityPolicyAllows: None, + subsumes: Some(principals::subsumes), +}; + /// Common messages used to control the event loops in both the script and the worker pub enum CommonScriptMsg { /// Requests that the script thread measure its memory usage. The results are sent back via the @@ -466,6 +474,10 @@ unsafe fn new_rt_and_cx_with_parent( JS_AddExtraGCRootsTracer(cx, Some(trace_rust_roots), ptr::null_mut()); + JS_SetSecurityCallbacks(cx, &SECURITY_CALLBACKS); + + JS_InitDestroyPrincipalsCallback(cx, Some(principals::destroy_servo_jsprincipal)); + // Needed for debug assertions about whether GC is running. if cfg!(debug_assertions) { JS_SetGCCallback(cx, Some(debug_gc_callback), ptr::null_mut()); diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 8b3ba539809..c1161674fc2 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -35,7 +35,6 @@ use crate::dom::bindings::root::ThreadLocalStackRoots; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom, RootCollection}; use crate::dom::bindings::str::DOMString; use crate::dom::bindings::trace::JSTraceable; -use crate::dom::bindings::utils::WRAP_CALLBACKS; use crate::dom::customelementregistry::{ CallbackReaction, CustomElementDefinition, CustomElementReactionStack, }; @@ -99,7 +98,6 @@ use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use js::glue::GetWindowProxyClass; -use js::jsapi::JS_SetWrapObjectCallbacks; use js::jsapi::{ JSContext as UnsafeJSContext, JSTracer, JS_AddInterruptCallback, SetWindowProxyClass, }; @@ -1295,7 +1293,6 @@ impl ScriptThread { let cx = runtime.cx(); unsafe { - JS_SetWrapObjectCallbacks(cx, &WRAP_CALLBACKS); SetWindowProxyClass(cx, GetWindowProxyClass()); JS_AddInterruptCallback(cx, Some(interrupt_callback)); } |