aboutsummaryrefslogtreecommitdiffstats
path: root/components/script_bindings
diff options
context:
space:
mode:
authorJosh Matthews <josh@joshmatthews.net>2025-03-30 07:06:30 -0400
committerGitHub <noreply@github.com>2025-03-30 11:06:30 +0000
commitb445053a7c5372320c53e2084c274362bc204357 (patch)
tree0699f64435a23ddaae0a2f81b10959047be1467c /components/script_bindings
parent971490084e5ab994fb6b1e651044ad1f5afa4169 (diff)
downloadservo-b445053a7c5372320c53e2084c274362bc204357.tar.gz
servo-b445053a7c5372320c53e2084c274362bc204357.zip
More miscellaneous script splitting changes (#36220)
* script: Move HasParent to script_bindings and update imports for InheritTypes. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Make principal creation generic over DOM interface. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Move a bunch of proxy-related code to script_bindings. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Make some proxy-related code generic over the DOM interface. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Move DomSlice to script_bindings. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Move some utility bindings code to script_bindings. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Make enumerating and resolving globals generic over the DOM interface. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Make realm helpers generic over the DOM interface. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Move implementations on concrete DOM types to concrete bindings. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Make additional codegen helpers generic over the DOM interface. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Make iterator creation generic over the DOM interface. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Make reporting an exception a generic operation. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Move AsCCharPtrPtr to script_bindings. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Formatting. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Address clippy warnings. Signed-off-by: Josh Matthews <josh@joshmatthews.net> --------- Signed-off-by: Josh Matthews <josh@joshmatthews.net>
Diffstat (limited to 'components/script_bindings')
-rw-r--r--components/script_bindings/codegen/Bindings.conf1
-rw-r--r--components/script_bindings/codegen/CodegenRust.py107
-rw-r--r--components/script_bindings/inheritance.rs7
-rw-r--r--components/script_bindings/interfaces.rs10
-rw-r--r--components/script_bindings/lib.rs1
-rw-r--r--components/script_bindings/proxyhandler.rs494
-rw-r--r--components/script_bindings/root.rs20
-rw-r--r--components/script_bindings/utils.rs46
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
+ }
+}