diff options
Diffstat (limited to 'components/script_bindings')
-rw-r--r-- | components/script_bindings/codegen/Bindings.conf | 1 | ||||
-rw-r--r-- | components/script_bindings/codegen/CodegenRust.py | 107 | ||||
-rw-r--r-- | components/script_bindings/inheritance.rs | 7 | ||||
-rw-r--r-- | components/script_bindings/interfaces.rs | 10 | ||||
-rw-r--r-- | components/script_bindings/lib.rs | 1 | ||||
-rw-r--r-- | components/script_bindings/proxyhandler.rs | 494 | ||||
-rw-r--r-- | components/script_bindings/root.rs | 20 | ||||
-rw-r--r-- | components/script_bindings/utils.rs | 46 |
8 files changed, 631 insertions, 55 deletions
diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 3f356d5dff0..f305b74ab01 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -588,6 +588,7 @@ DOMInterfaces = { 'Window': { 'canGc': ['Stop', 'Fetch', 'Scroll', 'Scroll_','ScrollBy', 'ScrollBy_', 'Stop', 'Fetch', 'Open', 'CreateImageBitmap'], 'inRealms': ['Fetch', 'GetOpener'], + 'additionalTraits': ['script_bindings::interfaces::WindowHelpers'], }, 'WindowProxy' : { diff --git a/components/script_bindings/codegen/CodegenRust.py b/components/script_bindings/codegen/CodegenRust.py index 783590aad70..09a4bd4139f 100644 --- a/components/script_bindings/codegen/CodegenRust.py +++ b/components/script_bindings/codegen/CodegenRust.py @@ -2279,8 +2279,6 @@ class CGImports(CGWrapper): if t.isInterface() or t.isNamespace(): name = getIdentifier(t).name descriptor = descriptorProvider.getDescriptor(name) - if name != 'GlobalScope': - extras += [descriptor.path] parentName = descriptor.getParentName() while parentName: descriptor = descriptorProvider.getDescriptor(parentName) @@ -2289,7 +2287,7 @@ class CGImports(CGWrapper): elif t.isType() and t.isRecord(): extras += ['script_bindings::record::Record'] elif isinstance(t, IDLPromiseType): - extras += ['crate::dom::promise::Promise'] + pass else: if t.isEnum(): extras += [f'{getModuleFromObject(t)}::{getIdentifier(t).name}Values'] @@ -2339,15 +2337,15 @@ def DOMClassTypeId(desc): inner = "" if desc.hasDescendants(): if desc.interface.getExtendedAttribute("Abstract"): - return "crate::dom::bindings::codegen::InheritTypes::TopTypeId { abstract_: () }" + return "script_bindings::codegen::InheritTypes::TopTypeId { abstract_: () }" name = desc.interface.identifier.name - inner = f"(crate::dom::bindings::codegen::InheritTypes::{name}TypeId::{name})" + inner = f"(script_bindings::codegen::InheritTypes::{name}TypeId::{name})" elif len(protochain) == 1: - return "crate::dom::bindings::codegen::InheritTypes::TopTypeId { alone: () }" + return "script_bindings::codegen::InheritTypes::TopTypeId { alone: () }" reversed_protochain = list(reversed(protochain)) for (child, parent) in zip(reversed_protochain, reversed_protochain[1:]): - inner = f"(crate::dom::bindings::codegen::InheritTypes::{parent}TypeId::{child}{inner})" - return f"crate::dom::bindings::codegen::InheritTypes::TopTypeId {{ {protochain[0].lower()}: {inner} }}" + inner = f"(script_bindings::codegen::InheritTypes::{parent}TypeId::{child}{inner})" + return f"script_bindings::codegen::InheritTypes::TopTypeId {{ {protochain[0].lower()}: {inner} }}" def DOMClass(descriptor): @@ -2398,10 +2396,10 @@ class CGDOMJSClass(CGThing): } if self.descriptor.isGlobal(): assert not self.descriptor.weakReferenceable - args["enumerateHook"] = "Some(enumerate_global)" + args["enumerateHook"] = "Some(enumerate_global::<D>)" args["flags"] = "JSCLASS_IS_GLOBAL | JSCLASS_DOM_GLOBAL | JSCLASS_FOREGROUND_FINALIZE" args["slots"] = "JSCLASS_GLOBAL_SLOT_COUNT + 1" - args["resolveHook"] = "Some(resolve_global)" + args["resolveHook"] = "Some(resolve_global::<D>)" args["traceHook"] = "js::jsapi::JS_GlobalObjectTraceHook" elif self.descriptor.weakReferenceable: args["slots"] = "2" @@ -2423,7 +2421,7 @@ pub(crate) fn init_class_ops<D: DomTypes>() {{ }}); }} -static Class: ThreadUnsafeOnceLock<DOMJSClass> = ThreadUnsafeOnceLock::new(); +pub(crate) static Class: ThreadUnsafeOnceLock<DOMJSClass> = ThreadUnsafeOnceLock::new(); pub(crate) fn init_domjs_class<D: DomTypes>() {{ init_class_ops::<D>(); @@ -3185,7 +3183,7 @@ let raw = Root::new(MaybeUnreflectedDom::from_box(object)); let origin = (*raw.as_ptr()).upcast::<D::GlobalScope>().origin(); rooted!(in(*cx) let mut obj = ptr::null_mut::<JSObject>()); -create_global_object( +create_global_object::<D>( cx, &Class.get().base, raw.as_ptr() as *const libc::c_void, @@ -3229,14 +3227,15 @@ class CGIDLInterface(CGThing): def define(self): interface = self.descriptor.interface name = interface.identifier.name + bindingModule = f"crate::dom::bindings::codegen::GenericBindings::{toBindingPath(self.descriptor)}" if (interface.getUserData("hasConcreteDescendant", False) or interface.getUserData("hasProxyDescendant", False)): depth = self.descriptor.prototypeDepth check = f"class.interface_chain[{depth}] == PrototypeList::ID::{name}" elif self.descriptor.proxy: - check = "ptr::eq(class, unsafe { Class.get() })" + check = f"ptr::eq(class, unsafe {{ {bindingModule}::Class.get() }})" else: - check = "ptr::eq(class, unsafe { &Class.get().dom_class })" + check = f"ptr::eq(class, unsafe {{ &{bindingModule}::Class.get().dom_class }})" return f""" impl IDLInterface for {name} {{ #[inline] @@ -3279,6 +3278,7 @@ class CGDomObjectWrap(CGThing): def define(self): ifaceName = self.descriptor.interface.identifier.name + bindingModule = f"crate::dom::bindings::codegen::GenericBindings::{toBindingPath(self.descriptor)}" return f""" impl DomObjectWrap<crate::DomTypeHolder> for {firstCap(ifaceName)} {{ const WRAP: unsafe fn( @@ -3287,7 +3287,7 @@ impl DomObjectWrap<crate::DomTypeHolder> for {firstCap(ifaceName)} {{ Option<HandleObject>, Box<Self>, CanGc, - ) -> Root<Dom<Self>> = Wrap::<crate::DomTypeHolder>; + ) -> Root<Dom<Self>> = {bindingModule}::Wrap::<crate::DomTypeHolder>; }} """ @@ -3303,6 +3303,7 @@ class CGDomObjectIteratorWrap(CGThing): def define(self): assert self.descriptor.interface.isIteratorInterface() name = self.descriptor.interface.iterableInterface.identifier.name + bindingModule = f"crate::dom::bindings::codegen::GenericBindings::{toBindingPath(self.descriptor)}" return f""" impl DomObjectIteratorWrap<crate::DomTypeHolder> for {name} {{ const ITER_WRAP: unsafe fn( @@ -3311,7 +3312,7 @@ impl DomObjectIteratorWrap<crate::DomTypeHolder> for {name} {{ Option<HandleObject>, Box<IterableIterator<crate::DomTypeHolder, Self>>, CanGc, - ) -> Root<Dom<IterableIterator<crate::DomTypeHolder, Self>>> = Wrap::<crate::DomTypeHolder>; + ) -> Root<Dom<IterableIterator<crate::DomTypeHolder, Self>>> = {bindingModule}::Wrap::<crate::DomTypeHolder>; }} """ @@ -3516,7 +3517,7 @@ assert!(!prototype_proto.is_null());""")] assert not self.haveUnscopables code.append(CGGeneric(f""" rooted!(in(*cx) let mut prototype_proto_proto = prototype_proto.get()); -dom::types::{name}::create_named_properties_object(cx, prototype_proto_proto.handle(), prototype_proto.handle_mut()); +D::{name}::create_named_properties_object(cx, prototype_proto_proto.handle(), prototype_proto.handle_mut()); assert!(!prototype_proto.is_null());""")) properties = { @@ -3797,7 +3798,7 @@ class CGDefineProxyHandler(CGAbstractMethod): # `[[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)' + customSet = 'Some(proxyhandler::maybe_cross_origin_set_rawcx::<D>)' getOwnEnumerablePropertyKeys = "own_property_keys::<D>" if self.descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties") or \ @@ -3944,7 +3945,7 @@ class CGCallGenerator(CGThing): call = CGList([call, CGWrapper(args, pre="(", post=")")]) if hasCEReactions: - self.cgRoot.append(CGGeneric("push_new_element_queue();\n")) + self.cgRoot.append(CGGeneric("<D as DomHelpers<D>>::push_new_element_queue();\n")) self.cgRoot.append(CGList([ CGGeneric("let result: "), @@ -3955,7 +3956,7 @@ class CGCallGenerator(CGThing): ])) if hasCEReactions: - self.cgRoot.append(CGGeneric("pop_current_element_queue(CanGc::note());\n")) + self.cgRoot.append(CGGeneric("<D as DomHelpers<D>>::pop_current_element_queue(CanGc::note());\n")) if isFallible: if static: @@ -3967,7 +3968,7 @@ class CGCallGenerator(CGThing): "let result = match result {\n" " Ok(result) => result,\n" " Err(e) => {\n" - f" <D as DomHelpers<D>>::throw_dom_exception(cx, {glob}, e);\n" + f" <D as DomHelpers<D>>::throw_dom_exception(cx, {glob}, e, CanGc::note());\n" f" return{errorResult};\n" " },\n" "};")) @@ -5911,14 +5912,14 @@ class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): if self.descriptor.isMaybeCrossOriginObject(): get += dedent( """ - if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) { if !proxyhandler::cross_origin_get_own_property_helper( cx, proxy, CROSS_ORIGIN_PROPERTIES.get(), id, desc, &mut *is_none ) { return false; } if *is_none { - return proxyhandler::cross_origin_property_fallback(cx, proxy, id, desc, &mut *is_none); + return proxyhandler::cross_origin_property_fallback::<D>(cx, proxy, id, desc, &mut *is_none); } return true; } @@ -6033,8 +6034,8 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): if self.descriptor.isMaybeCrossOriginObject(): set += dedent( """ - if !proxyhandler::is_platform_object_same_origin(cx, proxy) { - return proxyhandler::report_cross_origin_denial(cx, id, "define"); + if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::report_cross_origin_denial::<D>(cx, id, "define"); } // Safe to enter the Realm of proxy now. @@ -6092,8 +6093,8 @@ class CGDOMJSProxyHandler_delete(CGAbstractExternMethod): if self.descriptor.isMaybeCrossOriginObject(): set += dedent( """ - if !proxyhandler::is_platform_object_same_origin(cx, proxy) { - return proxyhandler::report_cross_origin_denial(cx, id, "delete"); + if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::report_cross_origin_denial::<D>(cx, id, "delete"); } // Safe to enter the Realm of proxy now. @@ -6131,7 +6132,7 @@ class CGDOMJSProxyHandler_ownPropertyKeys(CGAbstractExternMethod): if self.descriptor.isMaybeCrossOriginObject(): body += dedent( """ - if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) { return proxyhandler::cross_origin_own_property_keys( cx, proxy, CROSS_ORIGIN_PROPERTIES.get(), props ); @@ -6204,7 +6205,7 @@ class CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(CGAbstractExternMethod): if self.descriptor.isMaybeCrossOriginObject(): body += dedent( """ - if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) { // There are no enumerable cross-origin props, so we're done. return true; } @@ -6255,7 +6256,7 @@ class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod): if self.descriptor.isMaybeCrossOriginObject(): indexed += dedent( """ - if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) { return proxyhandler::cross_origin_has_own( cx, proxy, CROSS_ORIGIN_PROPERTIES.get(), id, bp ); @@ -6328,8 +6329,8 @@ class CGDOMJSProxyHandler_get(CGAbstractExternMethod): 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); + if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::cross_origin_get::<D>(cx, proxy, receiver, id, vp); } // Safe to enter the Realm of proxy now. @@ -6589,7 +6590,7 @@ class CGDOMJSProxyHandlerDOMClass(CGThing): def define(self): return f""" -static Class: ThreadUnsafeOnceLock<DOMClass> = ThreadUnsafeOnceLock::new(); +pub(crate) static Class: ThreadUnsafeOnceLock<DOMClass> = ThreadUnsafeOnceLock::new(); pub(crate) fn init_proxy_handler_dom_class<D: DomTypes>() {{ Class.set({DOMClass(self.descriptor)}); @@ -6990,18 +6991,11 @@ class CGDescriptor(CGThing): pass else: cgThings.append(CGDOMJSClass(descriptor)) - if not descriptor.interface.isIteratorInterface(): - cgThings.append(CGAssertInheritance(descriptor)) - pass if descriptor.isGlobal(): cgThings.append(CGWrapGlobalMethod(descriptor, properties)) else: cgThings.append(CGWrapMethod(descriptor)) - if descriptor.interface.isIteratorInterface(): - cgThings.append(CGDomObjectIteratorWrap(descriptor)) - else: - cgThings.append(CGDomObjectWrap(descriptor)) reexports.append('Wrap') haveUnscopables = False @@ -7013,16 +7007,6 @@ class CGDescriptor(CGThing): CGIndenter(CGList([CGGeneric(str_to_cstr(name)) for name in unscopableNames], ",\n")), CGGeneric("];\n")], "\n")) - if ( - (descriptor.concrete or descriptor.hasDescendants()) - and not descriptor.interface.isIteratorInterface() - ): - cgThings.append(CGIDLInterface(descriptor)) - if descriptor.interface.isIteratorInterface(): - cgThings.append(CGIteratorDerives(descriptor)) - - if descriptor.weakReferenceable: - cgThings.append(CGWeakReferenceableTrait(descriptor)) if not descriptor.interface.isCallback(): interfaceTrait = CGInterfaceTrait(descriptor, config.getDescriptorProvider()) @@ -7590,6 +7574,27 @@ class CGConcreteBindingRoot(CGThing): f"pub(crate) use {originalBinding}::{firstCap(ifaceName)}_Binding as {firstCap(ifaceName)}_Binding;" ), ] + + if d.concrete: + if not d.interface.isIteratorInterface(): + cgthings.append(CGAssertInheritance(d)) + else: + cgthings.append(CGIteratorDerives(d)) + + if ( + (d.concrete or d.hasDescendants()) + and not d.interface.isIteratorInterface() + ): + cgthings.append(CGIDLInterface(d)) + + if d.interface.isIteratorInterface(): + cgthings.append(CGDomObjectIteratorWrap(d)) + elif d.concrete and not d.isGlobal(): + cgthings.append(CGDomObjectWrap(d)) + + if d.weakReferenceable: + cgthings.append(CGWeakReferenceableTrait(d)) + if not d.interface.isCallback(): traitName = f"{ifaceName}Methods" cgthings += [ @@ -7625,7 +7630,7 @@ pub(crate) fn GetConstructorObject( curr = CGImports(curr, descriptors=[], callbacks=[], dictionaries=[], enums=[], typedefs=[], imports=[ - 'crate::dom::bindings::import::base::*', + 'crate::dom::bindings::import::module::*', 'crate::dom::types::*'], config=config) diff --git a/components/script_bindings/inheritance.rs b/components/script_bindings/inheritance.rs index 5ddbf072215..baa70c81395 100644 --- a/components/script_bindings/inheritance.rs +++ b/components/script_bindings/inheritance.rs @@ -51,3 +51,10 @@ pub trait Castable: IDLInterface + DomObject + Sized { } } } + +#[allow(missing_docs)] +pub trait HasParent { + #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] + type Parent; + fn as_parent(&self) -> &Self::Parent; +} diff --git a/components/script_bindings/interfaces.rs b/components/script_bindings/interfaces.rs index 2e7f6017e75..ce8fc71f23b 100644 --- a/components/script_bindings/interfaces.rs +++ b/components/script_bindings/interfaces.rs @@ -2,7 +2,7 @@ * 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::rust::HandleObject; +use js::rust::{HandleObject, MutableHandleObject}; use crate::script_runtime::JSContext; @@ -22,3 +22,11 @@ pub trait TestBindingHelpers { pub trait WebGL2RenderingContextHelpers { fn is_webgl2_enabled(cx: JSContext, global: HandleObject) -> bool; } + +pub trait WindowHelpers { + fn create_named_properties_object( + cx: JSContext, + proto: HandleObject, + object: MutableHandleObject, + ); +} diff --git a/components/script_bindings/lib.rs b/components/script_bindings/lib.rs index 17dc827b872..0ecad1ca07c 100644 --- a/components/script_bindings/lib.rs +++ b/components/script_bindings/lib.rs @@ -28,6 +28,7 @@ pub mod iterable; pub mod like; pub mod lock; pub mod num; +pub mod proxyhandler; pub mod record; pub mod reflector; pub mod root; diff --git a/components/script_bindings/proxyhandler.rs b/components/script_bindings/proxyhandler.rs new file mode 100644 index 00000000000..7614479ac59 --- /dev/null +++ b/components/script_bindings/proxyhandler.rs @@ -0,0 +1,494 @@ +/* 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/. */ + +//! Utilities for the implementation of JSAPI proxy handlers. + +use std::ffi::CStr; +use std::os::raw::c_char; +use std::ptr; + +use js::glue::{GetProxyHandlerFamily, GetProxyPrivate, SetProxyPrivate}; +use js::jsapi::{ + DOMProxyShadowsResult, GetStaticPrototype, GetWellKnownSymbol, Handle as RawHandle, + HandleId as RawHandleId, HandleObject as RawHandleObject, JS_AtomizeAndPinString, + JS_DefinePropertyById, JS_GetOwnPropertyDescriptorById, JSContext, JSErrNum, JSFunctionSpec, + JSObject, JSPropertySpec, MutableHandle as RawMutableHandle, + MutableHandleIdVector as RawMutableHandleIdVector, + MutableHandleObject as RawMutableHandleObject, ObjectOpResult, PropertyDescriptor, + SetDOMProxyInformation, SymbolCode, jsid, +}; +use js::jsid::SymbolId; +use js::jsval::{ObjectValue, UndefinedValue}; +use js::rust::wrappers::{ + AppendToIdVector, JS_AlreadyHasOwnPropertyById, JS_NewObjectWithGivenProto, + RUST_INTERNED_STRING_TO_JSID, SetDataPropertyDescriptor, +}; +use js::rust::{Handle, HandleObject, HandleValue, MutableHandle, MutableHandleObject}; +use js::{jsapi, rooted}; + +use crate::conversions::{is_dom_proxy, jsid_to_string, jsstring_to_str}; +use crate::script_runtime::JSContext as SafeJSContext; +use crate::str::DOMString; +use crate::utils::delete_property_by_id; + +/// Determine if this id shadows any existing properties for this proxy. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +pub unsafe extern "C" fn shadow_check_callback( + cx: *mut JSContext, + object: RawHandleObject, + id: RawHandleId, +) -> DOMProxyShadowsResult { + // TODO: support OverrideBuiltins when #12978 is fixed. + + rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>()); + get_expando_object(object, expando.handle_mut()); + if !expando.get().is_null() { + let mut has_own = false; + let raw_id = Handle::from_raw(id); + + if !JS_AlreadyHasOwnPropertyById(cx, expando.handle(), raw_id, &mut has_own) { + return DOMProxyShadowsResult::ShadowCheckFailed; + } + + if has_own { + return DOMProxyShadowsResult::ShadowsViaDirectExpando; + } + } + + // Our expando, if any, didn't shadow, so we're not shadowing at all. + DOMProxyShadowsResult::DoesntShadow +} + +/// Initialize the infrastructure for DOM proxy objects. +pub fn init() { + unsafe { + SetDOMProxyInformation( + GetProxyHandlerFamily(), + Some(shadow_check_callback), + ptr::null(), + ); + } +} + +/// Defines an expando on the given `proxy`. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +/// `result` must point to a valid, non-null ObjectOpResult. +pub unsafe extern "C" fn define_property( + cx: *mut JSContext, + proxy: RawHandleObject, + id: RawHandleId, + desc: RawHandle<PropertyDescriptor>, + result: *mut ObjectOpResult, +) -> bool { + rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>()); + ensure_expando_object(cx, proxy, expando.handle_mut()); + JS_DefinePropertyById(cx, expando.handle().into(), id, desc, result) +} + +/// Deletes an expando off the given `proxy`. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +/// `bp` must point to a valid, non-null ObjectOpResult. +pub unsafe extern "C" fn delete( + cx: *mut JSContext, + proxy: RawHandleObject, + id: RawHandleId, + bp: *mut ObjectOpResult, +) -> bool { + rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>()); + get_expando_object(proxy, expando.handle_mut()); + if expando.is_null() { + (*bp).code_ = 0 /* OkCode */; + return true; + } + + delete_property_by_id(cx, expando.handle(), Handle::from_raw(id), bp) +} + +/// Controls whether the Extensible bit can be changed +/// +/// # Safety +/// `result` must point to a valid, non-null ObjectOpResult. +pub unsafe extern "C" fn prevent_extensions( + _cx: *mut JSContext, + _proxy: RawHandleObject, + result: *mut ObjectOpResult, +) -> bool { + (*result).code_ = JSErrNum::JSMSG_CANT_PREVENT_EXTENSIONS as ::libc::uintptr_t; + true +} + +/// Reports whether the object is Extensible +/// +/// # Safety +/// `succeeded` must point to a valid, non-null bool. +pub unsafe extern "C" fn is_extensible( + _cx: *mut JSContext, + _proxy: RawHandleObject, + succeeded: *mut bool, +) -> bool { + *succeeded = true; + true +} + +/// If `proxy` (underneath any functionally-transparent wrapper proxies) has as +/// its `[[GetPrototypeOf]]` trap the ordinary `[[GetPrototypeOf]]` behavior +/// defined for ordinary objects, set `*is_ordinary` to true and store `obj`'s +/// prototype in `proto`. Otherwise set `*isOrdinary` to false. In case of +/// error, both outparams have unspecified value. +/// +/// This implementation always handles the case of the ordinary +/// `[[GetPrototypeOf]]` behavior. An alternative implementation will be +/// necessary for maybe-cross-origin objects. +/// +/// # Safety +/// `is_ordinary` must point to a valid, non-null bool. +pub unsafe extern "C" fn get_prototype_if_ordinary( + _: *mut JSContext, + proxy: RawHandleObject, + is_ordinary: *mut bool, + proto: RawMutableHandleObject, +) -> bool { + *is_ordinary = true; + proto.set(GetStaticPrototype(proxy.get())); + true +} + +/// Get the expando object, or null if there is none. +pub fn get_expando_object(obj: RawHandleObject, mut expando: MutableHandleObject) { + unsafe { + assert!(is_dom_proxy(obj.get())); + let val = &mut UndefinedValue(); + GetProxyPrivate(obj.get(), val); + expando.set(if val.is_undefined() { + ptr::null_mut() + } else { + val.to_object() + }); + } +} + +/// Get the expando object, or create it if it doesn't exist yet. +/// Fails on JSAPI failure. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +pub unsafe fn ensure_expando_object( + cx: *mut JSContext, + obj: RawHandleObject, + mut expando: MutableHandleObject, +) { + assert!(is_dom_proxy(obj.get())); + get_expando_object(obj, expando.reborrow()); + if expando.is_null() { + expando.set(JS_NewObjectWithGivenProto( + cx, + ptr::null_mut(), + HandleObject::null(), + )); + assert!(!expando.is_null()); + + SetProxyPrivate(obj.get(), &ObjectValue(expando.get())); + } +} + +/// Set the property descriptor's object to `obj` and set it to enumerable, +/// and writable if `readonly` is true. +pub fn set_property_descriptor( + desc: MutableHandle<PropertyDescriptor>, + value: HandleValue, + attrs: u32, + is_none: &mut bool, +) { + unsafe { + SetDataPropertyDescriptor(desc, value, attrs); + } + *is_none = false; +} + +pub fn id_to_source(cx: SafeJSContext, id: RawHandleId) -> Option<DOMString> { + unsafe { + 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() + }) + .and_then(ptr::NonNull::new) + .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 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() { + unsafe { + 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 +} + +/// # Safety +/// `is_ordinary` must point to a valid, non-null bool. +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 `[[SetPrototypeOf]]` for [`Location`] and [`WindowProxy`]. +/// +/// [`Location`]: https://html.spec.whatwg.org/multipage/#location-setprototypeof +/// [`WindowProxy`]: https://html.spec.whatwg.org/multipage/#windowproxy-setprototypeof +/// +/// # Safety +/// `result` must point to a valid, non-null ObjectOpResult. +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 +} + +pub fn get_getter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) { + if d.hasGetter_() { + out.set(d.getter_); + } +} + +pub fn get_setter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) { + if d.hasSetter_() { + out.set(d.setter_); + } +} + +/// <https://tc39.es/ecma262/#sec-isaccessordescriptor> +pub fn is_accessor_descriptor(d: &PropertyDescriptor) -> bool { + d.hasSetter_() || d.hasGetter_() +} + +/// <https://tc39.es/ecma262/#sec-isdatadescriptor> +pub fn is_data_descriptor(d: &PropertyDescriptor) -> bool { + d.hasWritable_() || d.hasValue_() +} + +/// 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. +/// +/// # Safety +/// `bp` must point to a valid, non-null bool. +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)).is_some_and(|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 fn cross_origin_get_own_property_helper( + cx: SafeJSContext, + proxy: RawHandleObject, + cross_origin_properties: &'static CrossOriginProperties, + id: RawHandleId, + desc: RawMutableHandle<PropertyDescriptor>, + is_none: &mut bool, +) -> 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(), + ); + + unsafe { JS_GetOwnPropertyDescriptorById(*cx, holder.handle().into(), id, desc, is_none) } +} + +const ALLOWLISTED_SYMBOL_CODES: &[SymbolCode] = &[ + SymbolCode::toStringTag, + SymbolCode::hasInstance, + SymbolCode::isConcatSpreadable, +]; + +pub fn is_cross_origin_allowlisted_prop(cx: SafeJSContext, id: RawHandleId) -> bool { + unsafe { + if jsid_to_string(*cx, Handle::from_raw(id)).is_some_and(|st| st == "then") { + return true; + } + + rooted!(in(*cx) let mut allowed_id: jsid); + ALLOWLISTED_SYMBOL_CODES.iter().any(|&allowed_code| { + allowed_id.set(SymbolId(GetWellKnownSymbol(*cx, allowed_code))); + // `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-) +fn append_cross_origin_allowlisted_prop_keys(cx: SafeJSContext, props: RawMutableHandleIdVector) { + unsafe { + rooted!(in(*cx) let mut id: jsid); + + let jsstring = JS_AtomizeAndPinString(*cx, c"then".as_ptr()); + 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() { + id.set(SymbolId(GetWellKnownSymbol(*cx, allowed_code))); + 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-) +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 + unsafe { + 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_bindings/root.rs b/components/script_bindings/root.rs index cc3be843593..51bc979908f 100644 --- a/components/script_bindings/root.rs +++ b/components/script_bindings/root.rs @@ -440,3 +440,23 @@ pub unsafe fn trace_roots(tracer: *mut JSTracer) { pub fn assert_in_script() { debug_assert!(thread_state::get().is_script()); } + +/// Get a slice of references to DOM objects. +pub trait DomSlice<T> +where + T: JSTraceable + DomObject, +{ + /// Returns the slice of `T` references. + fn r(&self) -> &[&T]; +} + +impl<T> DomSlice<T> for [Dom<T>] +where + T: JSTraceable + DomObject, +{ + #[inline] + fn r(&self) -> &[&T] { + let _ = mem::transmute::<Dom<T>, &T>; + unsafe { &*(self as *const [Dom<T>] as *const [&T]) } + } +} diff --git a/components/script_bindings/utils.rs b/components/script_bindings/utils.rs index 2317f80dbfa..001360e07ff 100644 --- a/components/script_bindings/utils.rs +++ b/components/script_bindings/utils.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::ffi::CString; -use std::os::raw::c_void; +use std::os::raw::{c_char, c_void}; use std::ptr::{self, NonNull}; use js::conversions::ToJSValConvertible; @@ -13,9 +13,9 @@ use js::glue::{ }; use js::jsapi::{ AtomToLinearString, CallArgs, ExceptionStackBehavior, GetLinearStringCharAt, - GetLinearStringLength, GetNonCCWObjectGlobal, HandleObject as RawHandleObject, + GetLinearStringLength, GetNonCCWObjectGlobal, HandleObject as RawHandleObject, Heap, JS_ClearPendingException, JS_IsExceptionPending, JSAtom, JSContext, JSJitInfo, JSObject, - MutableHandleValue as RawMutableHandleValue, ObjectOpResult, StringIsArrayIndex, + JSTracer, MutableHandleValue as RawMutableHandleValue, ObjectOpResult, StringIsArrayIndex, }; use js::jsval::{JSVal, UndefinedValue}; use js::rust::wrappers::{ @@ -36,6 +36,7 @@ use crate::conversions::{PrototypeCheck, jsstring_to_str, private_from_proto_che use crate::error::throw_invalid_this; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; use crate::str::DOMString; +use crate::trace::trace_object; /// The struct that holds inheritance information for DOM object reflectors. #[derive(Clone, Copy)] @@ -526,3 +527,42 @@ pub unsafe fn exception_to_promise( false } } + +/// Trace the resources held by reserved slots of a global object +/// +/// # Safety +/// `tracer` must point to a valid, non-null JSTracer. +/// `obj` must point to a valid, non-null JSObject. +pub unsafe fn trace_global(tracer: *mut JSTracer, obj: *mut JSObject) { + let array = get_proto_or_iface_array(obj); + for proto in (*array).iter() { + if !proto.is_null() { + trace_object( + tracer, + "prototype", + &*(proto as *const *mut JSObject as *const Heap<*mut JSObject>), + ); + } + } +} + +// Generic method for returning libc::c_void from caller +pub trait AsVoidPtr { + fn as_void_ptr(&self) -> *const libc::c_void; +} +impl<T> AsVoidPtr for T { + fn as_void_ptr(&self) -> *const libc::c_void { + self as *const T as *const libc::c_void + } +} + +// Generic method for returning c_char from caller +pub trait AsCCharPtrPtr { + fn as_c_char_ptr(&self) -> *const c_char; +} + +impl AsCCharPtrPtr for [u8] { + fn as_c_char_ptr(&self) -> *const c_char { + self as *const [u8] as *const c_char + } +} |