aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/bindings/proxyhandler.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/bindings/proxyhandler.rs')
-rw-r--r--components/script/dom/bindings/proxyhandler.rs605
1 files changed, 600 insertions, 5 deletions
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
+}