script_bindings/
proxyhandler.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Utilities for the implementation of JSAPI proxy handlers.
6
7use std::ffi::CStr;
8use std::os::raw::c_char;
9use std::ptr;
10use std::ptr::NonNull;
11
12use js::conversions::{ToJSValConvertible, jsstr_to_string};
13use js::glue::{
14    GetProxyHandler, GetProxyHandlerFamily, GetProxyPrivate, InvokeGetOwnPropertyDescriptor,
15    SetProxyPrivate,
16};
17use js::jsapi::{
18    DOMProxyShadowsResult, GetStaticPrototype, GetWellKnownSymbol, Handle as RawHandle,
19    HandleId as RawHandleId, HandleObject as RawHandleObject, HandleValue as RawHandleValue,
20    JS_AtomizeAndPinString, JS_DefinePropertyById, JS_GetOwnPropertyDescriptorById,
21    JS_IsExceptionPending, JSContext, JSErrNum, JSFunctionSpec, JSObject, JSPropertySpec,
22    MutableHandle as RawMutableHandle, MutableHandleIdVector as RawMutableHandleIdVector,
23    MutableHandleObject as RawMutableHandleObject, MutableHandleValue as RawMutableHandleValue,
24    ObjectOpResult, PropertyDescriptor, SetDOMProxyInformation, SymbolCode, jsid,
25};
26use js::jsid::SymbolId;
27use js::jsval::{ObjectValue, UndefinedValue};
28use js::realm::{AutoRealm, CurrentRealm};
29use js::rust::wrappers::{
30    AppendToIdVector, JS_AlreadyHasOwnPropertyById, JS_NewObjectWithGivenProto,
31    RUST_INTERNED_STRING_TO_JSID, SetDataPropertyDescriptor,
32};
33use js::rust::{Handle, HandleObject, HandleValue, IntoHandle, MutableHandle, MutableHandleObject};
34use js::{jsapi, rooted};
35
36use crate::DomTypes;
37use crate::conversions::{is_dom_proxy, jsid_to_string};
38use crate::error::Error;
39use crate::interfaces::{DomHelpers, GlobalScopeHelpers};
40use crate::realms::{AlreadyInRealm, InRealm};
41use crate::reflector::DomObject;
42use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
43use crate::str::DOMString;
44use crate::utils::delete_property_by_id;
45
46/// Determine if this id shadows any existing properties for this proxy.
47///
48/// # Safety
49/// `cx` must point to a valid, non-null JSContext.
50pub(crate) unsafe extern "C" fn shadow_check_callback(
51    cx: *mut JSContext,
52    object: RawHandleObject,
53    id: RawHandleId,
54) -> DOMProxyShadowsResult {
55    // TODO: support OverrideBuiltins when #12978 is fixed.
56
57    rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>());
58    get_expando_object(object, expando.handle_mut());
59    if !expando.get().is_null() {
60        let mut has_own = false;
61        let raw_id = Handle::from_raw(id);
62
63        if !JS_AlreadyHasOwnPropertyById(cx, expando.handle(), raw_id, &mut has_own) {
64            return DOMProxyShadowsResult::ShadowCheckFailed;
65        }
66
67        if has_own {
68            return DOMProxyShadowsResult::ShadowsViaDirectExpando;
69        }
70    }
71
72    // Our expando, if any, didn't shadow, so we're not shadowing at all.
73    DOMProxyShadowsResult::DoesntShadow
74}
75
76/// Initialize the infrastructure for DOM proxy objects.
77pub fn init() {
78    unsafe {
79        SetDOMProxyInformation(
80            GetProxyHandlerFamily(),
81            Some(shadow_check_callback),
82            ptr::null(),
83        );
84    }
85}
86
87/// Defines an expando on the given `proxy`.
88///
89/// # Safety
90/// `cx` must point to a valid, non-null JSContext.
91/// `result` must point to a valid, non-null ObjectOpResult.
92pub(crate) unsafe extern "C" fn define_property(
93    cx: *mut JSContext,
94    proxy: RawHandleObject,
95    id: RawHandleId,
96    desc: RawHandle<PropertyDescriptor>,
97    result: *mut ObjectOpResult,
98) -> bool {
99    rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>());
100    ensure_expando_object(cx, proxy, expando.handle_mut());
101    JS_DefinePropertyById(cx, expando.handle().into(), id, desc, result)
102}
103
104/// Deletes an expando off the given `proxy`.
105///
106/// # Safety
107/// `cx` must point to a valid, non-null JSContext.
108/// `bp` must point to a valid, non-null ObjectOpResult.
109pub(crate) unsafe extern "C" fn delete(
110    cx: *mut JSContext,
111    proxy: RawHandleObject,
112    id: RawHandleId,
113    bp: *mut ObjectOpResult,
114) -> bool {
115    rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>());
116    get_expando_object(proxy, expando.handle_mut());
117    if expando.is_null() {
118        (*bp).code_ = 0 /* OkCode */;
119        return true;
120    }
121
122    delete_property_by_id(cx, expando.handle(), Handle::from_raw(id), bp)
123}
124
125/// Controls whether the Extensible bit can be changed
126///
127/// # Safety
128/// `result` must point to a valid, non-null ObjectOpResult.
129pub(crate) unsafe extern "C" fn prevent_extensions(
130    _cx: *mut JSContext,
131    _proxy: RawHandleObject,
132    result: *mut ObjectOpResult,
133) -> bool {
134    (*result).code_ = JSErrNum::JSMSG_CANT_PREVENT_EXTENSIONS as ::libc::uintptr_t;
135    true
136}
137
138/// Reports whether the object is Extensible
139///
140/// # Safety
141/// `succeeded` must point to a valid, non-null bool.
142pub(crate) unsafe extern "C" fn is_extensible(
143    _cx: *mut JSContext,
144    _proxy: RawHandleObject,
145    succeeded: *mut bool,
146) -> bool {
147    *succeeded = true;
148    true
149}
150
151/// If `proxy` (underneath any functionally-transparent wrapper proxies) has as
152/// its `[[GetPrototypeOf]]` trap the ordinary `[[GetPrototypeOf]]` behavior
153/// defined for ordinary objects, set `*is_ordinary` to true and store `obj`'s
154/// prototype in `proto`.  Otherwise set `*isOrdinary` to false. In case of
155/// error, both outparams have unspecified value.
156///
157/// This implementation always handles the case of the ordinary
158/// `[[GetPrototypeOf]]` behavior. An alternative implementation will be
159/// necessary for maybe-cross-origin objects.
160///
161/// # Safety
162/// `is_ordinary` must point to a valid, non-null bool.
163pub(crate) unsafe extern "C" fn get_prototype_if_ordinary(
164    _: *mut JSContext,
165    proxy: RawHandleObject,
166    is_ordinary: *mut bool,
167    proto: RawMutableHandleObject,
168) -> bool {
169    *is_ordinary = true;
170    proto.set(GetStaticPrototype(proxy.get()));
171    true
172}
173
174/// Get the expando object, or null if there is none.
175pub(crate) fn get_expando_object(obj: RawHandleObject, mut expando: MutableHandleObject) {
176    unsafe {
177        assert!(is_dom_proxy(obj.get()));
178        let val = &mut UndefinedValue();
179        GetProxyPrivate(obj.get(), val);
180        expando.set(if val.is_undefined() {
181            ptr::null_mut()
182        } else {
183            val.to_object()
184        });
185    }
186}
187
188/// Get the expando object, or create it if it doesn't exist yet.
189/// Fails on JSAPI failure.
190///
191/// # Safety
192/// `cx` must point to a valid, non-null JSContext.
193pub(crate) unsafe fn ensure_expando_object(
194    cx: *mut JSContext,
195    obj: RawHandleObject,
196    mut expando: MutableHandleObject,
197) {
198    assert!(is_dom_proxy(obj.get()));
199    get_expando_object(obj, expando.reborrow());
200    if expando.is_null() {
201        expando.set(JS_NewObjectWithGivenProto(
202            cx,
203            ptr::null_mut(),
204            HandleObject::null(),
205        ));
206        assert!(!expando.is_null());
207
208        SetProxyPrivate(obj.get(), &ObjectValue(expando.get()));
209    }
210}
211
212/// Set the property descriptor's object to `obj` and set it to enumerable,
213/// and writable if `readonly` is true.
214pub fn set_property_descriptor(
215    desc: MutableHandle<PropertyDescriptor>,
216    value: HandleValue,
217    attrs: u32,
218    is_none: &mut bool,
219) {
220    unsafe {
221        SetDataPropertyDescriptor(desc, value, attrs);
222    }
223    *is_none = false;
224}
225
226pub(crate) fn id_to_source(cx: SafeJSContext, id: RawHandleId) -> Option<DOMString> {
227    unsafe {
228        rooted!(in(*cx) let mut value = UndefinedValue());
229        rooted!(in(*cx) let mut jsstr = ptr::null_mut::<jsapi::JSString>());
230        jsapi::JS_IdToValue(*cx, id.get(), value.handle_mut().into())
231            .then(|| {
232                jsstr.set(jsapi::JS_ValueToSource(*cx, value.handle().into()));
233                jsstr.get()
234            })
235            .and_then(ptr::NonNull::new)
236            .map(|jsstr| DOMString::from_string(jsstr_to_string(*cx, jsstr)))
237    }
238}
239
240/// Property and method specs that correspond to the elements of
241/// [`CrossOriginProperties(O)`].
242///
243/// [`CrossOriginProperties(O)`]: https://html.spec.whatwg.org/multipage/#crossoriginproperties-(-o-)
244pub(crate) struct CrossOriginProperties {
245    pub(crate) attributes: &'static [JSPropertySpec],
246    pub(crate) methods: &'static [JSFunctionSpec],
247}
248
249impl CrossOriginProperties {
250    /// Enumerate the property keys defined by `self`.
251    fn keys(&self) -> impl Iterator<Item = *const c_char> + '_ {
252        // Safety: All cross-origin property keys are strings, not symbols
253        self.attributes
254            .iter()
255            .map(|spec| unsafe { spec.name.string_ })
256            .chain(self.methods.iter().map(|spec| unsafe { spec.name.string_ }))
257            .filter(|ptr| !ptr.is_null())
258    }
259}
260
261/// Implementation of [`CrossOriginOwnPropertyKeys`].
262///
263/// [`CrossOriginOwnPropertyKeys`]: https://html.spec.whatwg.org/multipage/#crossoriginownpropertykeys-(-o-)
264pub(crate) fn cross_origin_own_property_keys(
265    cx: SafeJSContext,
266    _proxy: RawHandleObject,
267    cross_origin_properties: &'static CrossOriginProperties,
268    props: RawMutableHandleIdVector,
269) -> bool {
270    // > 2. For each `e` of `! CrossOriginProperties(O)`, append
271    // >    `e.[[Property]]` to `keys`.
272    for key in cross_origin_properties.keys() {
273        unsafe {
274            rooted!(in(*cx) let rooted = JS_AtomizeAndPinString(*cx, key));
275            rooted!(in(*cx) let mut rooted_jsid: jsid);
276            RUST_INTERNED_STRING_TO_JSID(*cx, rooted.handle().get(), rooted_jsid.handle_mut());
277            AppendToIdVector(props, rooted_jsid.handle());
278        }
279    }
280
281    // > 3. Return the concatenation of `keys` and `« "then", @@toStringTag,
282    // > @@hasInstance, @@isConcatSpreadable »`.
283    append_cross_origin_allowlisted_prop_keys(cx, props);
284
285    true
286}
287
288/// # Safety
289/// `is_ordinary` must point to a valid, non-null bool.
290pub(crate) unsafe extern "C" fn maybe_cross_origin_get_prototype_if_ordinary_rawcx(
291    _: *mut JSContext,
292    _proxy: RawHandleObject,
293    is_ordinary: *mut bool,
294    _proto: RawMutableHandleObject,
295) -> bool {
296    // We have a custom `[[GetPrototypeOf]]`, so return `false`
297    *is_ordinary = false;
298    true
299}
300
301/// Implementation of `[[SetPrototypeOf]]` for [`Location`] and [`WindowProxy`].
302///
303/// [`Location`]: https://html.spec.whatwg.org/multipage/#location-setprototypeof
304/// [`WindowProxy`]: https://html.spec.whatwg.org/multipage/#windowproxy-setprototypeof
305///
306/// # Safety
307/// `result` must point to a valid, non-null ObjectOpResult.
308pub(crate) unsafe extern "C" fn maybe_cross_origin_set_prototype_rawcx(
309    cx: *mut JSContext,
310    proxy: RawHandleObject,
311    proto: RawHandleObject,
312    result: *mut ObjectOpResult,
313) -> bool {
314    // > 1. Return `! SetImmutablePrototype(this, V)`.
315    //
316    // <https://tc39.es/ecma262/#sec-set-immutable-prototype>:
317    //
318    // > 1. Assert: Either `Type(V)` is Object or `Type(V)` is Null.
319    //
320    // > 2. Let current be `? O.[[GetPrototypeOf]]()`.
321    rooted!(in(cx) let mut current = ptr::null_mut::<JSObject>());
322    if !jsapi::GetObjectProto(cx, proxy, current.handle_mut().into()) {
323        return false;
324    }
325
326    // > 3. If `SameValue(V, current)` is true, return true.
327    if proto.get() == current.get() {
328        (*result).code_ = 0 /* OkCode */;
329        return true;
330    }
331
332    // > 4. Return false.
333    (*result).code_ = JSErrNum::JSMSG_CANT_SET_PROTO as usize;
334    true
335}
336
337pub(crate) fn get_getter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
338    if d.hasGetter_() {
339        out.set(d.getter_);
340    }
341}
342
343pub(crate) fn get_setter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
344    if d.hasSetter_() {
345        out.set(d.setter_);
346    }
347}
348
349/// <https://tc39.es/ecma262/#sec-isaccessordescriptor>
350pub(crate) fn is_accessor_descriptor(d: &PropertyDescriptor) -> bool {
351    d.hasSetter_() || d.hasGetter_()
352}
353
354/// <https://tc39.es/ecma262/#sec-isdatadescriptor>
355pub(crate) fn is_data_descriptor(d: &PropertyDescriptor) -> bool {
356    d.hasWritable_() || d.hasValue_()
357}
358
359/// Evaluate `CrossOriginGetOwnPropertyHelper(proxy, id) != null`.
360/// SpiderMonkey-specific.
361///
362/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
363/// for a maybe-cross-origin object.
364///
365/// # Safety
366/// `bp` must point to a valid, non-null bool.
367pub(crate) unsafe fn cross_origin_has_own(
368    cx: SafeJSContext,
369    _proxy: RawHandleObject,
370    cross_origin_properties: &'static CrossOriginProperties,
371    id: RawHandleId,
372    bp: *mut bool,
373) -> bool {
374    // TODO: Once we have the slot for the holder, it'd be more efficient to
375    //       use `ensure_cross_origin_property_holder`. We'll need `_proxy` to
376    //       do that.
377    *bp = jsid_to_string(*cx, Handle::from_raw(id)).is_some_and(|key| {
378        cross_origin_properties.keys().any(|defined_key| {
379            let defined_key = CStr::from_ptr(defined_key);
380            defined_key.to_bytes() == key.str().as_bytes()
381        })
382    });
383
384    true
385}
386
387/// Implementation of [`CrossOriginGetOwnPropertyHelper`].
388///
389/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
390/// for a maybe-cross-origin object.
391///
392/// [`CrossOriginGetOwnPropertyHelper`]: https://html.spec.whatwg.org/multipage/#crossorigingetownpropertyhelper-(-o,-p-)
393pub(crate) fn cross_origin_get_own_property_helper(
394    cx: SafeJSContext,
395    proxy: RawHandleObject,
396    cross_origin_properties: &'static CrossOriginProperties,
397    id: RawHandleId,
398    desc: RawMutableHandle<PropertyDescriptor>,
399    is_none: &mut bool,
400) -> bool {
401    rooted!(in(*cx) let mut holder = ptr::null_mut::<JSObject>());
402
403    ensure_cross_origin_property_holder(
404        cx,
405        proxy,
406        cross_origin_properties,
407        holder.handle_mut().into(),
408    );
409
410    unsafe { JS_GetOwnPropertyDescriptorById(*cx, holder.handle().into(), id, desc, is_none) }
411}
412
413const ALLOWLISTED_SYMBOL_CODES: &[SymbolCode] = &[
414    SymbolCode::toStringTag,
415    SymbolCode::hasInstance,
416    SymbolCode::isConcatSpreadable,
417];
418
419pub(crate) fn is_cross_origin_allowlisted_prop(cx: SafeJSContext, id: RawHandleId) -> bool {
420    unsafe {
421        if jsid_to_string(*cx, Handle::from_raw(id)).is_some_and(|st| st == "then") {
422            return true;
423        }
424
425        rooted!(in(*cx) let mut allowed_id: jsid);
426        ALLOWLISTED_SYMBOL_CODES.iter().any(|&allowed_code| {
427            allowed_id.set(SymbolId(GetWellKnownSymbol(*cx, allowed_code)));
428            // `jsid`s containing `JS::Symbol *` can be compared by
429            // referential equality
430            allowed_id.get().asBits_ == id.asBits_
431        })
432    }
433}
434
435/// Append `« "then", @@toStringTag, @@hasInstance, @@isConcatSpreadable »` to
436/// `props`. This is used to implement [`CrossOriginOwnPropertyKeys`].
437///
438/// [`CrossOriginOwnPropertyKeys`]: https://html.spec.whatwg.org/multipage/#crossoriginownpropertykeys-(-o-)
439fn append_cross_origin_allowlisted_prop_keys(cx: SafeJSContext, props: RawMutableHandleIdVector) {
440    unsafe {
441        rooted!(in(*cx) let mut id: jsid);
442
443        let jsstring = JS_AtomizeAndPinString(*cx, c"then".as_ptr());
444        rooted!(in(*cx) let rooted = jsstring);
445        RUST_INTERNED_STRING_TO_JSID(*cx, rooted.handle().get(), id.handle_mut());
446        AppendToIdVector(props, id.handle());
447
448        for &allowed_code in ALLOWLISTED_SYMBOL_CODES.iter() {
449            id.set(SymbolId(GetWellKnownSymbol(*cx, allowed_code)));
450            AppendToIdVector(props, id.handle());
451        }
452    }
453}
454
455/// Get the holder for cross-origin properties for the current global of the
456/// `JSContext`, creating one and storing it in a slot of the proxy object if it
457/// doesn't exist yet.
458///
459/// This essentially creates a cache of [`CrossOriginGetOwnPropertyHelper`]'s
460/// results for all property keys.
461///
462/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
463/// for a maybe-cross-origin object. The `out_holder` return value will always
464/// be in the Realm of `cx`.
465///
466/// [`CrossOriginGetOwnPropertyHelper`]: https://html.spec.whatwg.org/multipage/#crossorigingetownpropertyhelper-(-o,-p-)
467fn ensure_cross_origin_property_holder(
468    cx: SafeJSContext,
469    _proxy: RawHandleObject,
470    cross_origin_properties: &'static CrossOriginProperties,
471    out_holder: RawMutableHandleObject,
472) -> bool {
473    // TODO: We don't have the slot to store the holder yet. For now,
474    //       the holder is constructed every time this function is called,
475    //       which is not only inefficient but also deviates from the
476    //       specification in a subtle yet observable way.
477
478    // Create a holder for the current Realm
479    unsafe {
480        out_holder.set(jsapi::JS_NewObjectWithGivenProto(
481            *cx,
482            ptr::null_mut(),
483            RawHandleObject::null(),
484        ));
485
486        if out_holder.get().is_null() ||
487            !jsapi::JS_DefineProperties(
488                *cx,
489                out_holder.handle(),
490                cross_origin_properties.attributes.as_ptr(),
491            ) ||
492            !jsapi::JS_DefineFunctions(
493                *cx,
494                out_holder.handle(),
495                cross_origin_properties.methods.as_ptr(),
496            )
497        {
498            return false;
499        }
500    }
501
502    // TODO: Store the holder in the slot that we don't have yet.
503
504    true
505}
506
507/// Report a cross-origin denial for a property, Always returns `false`, so it
508/// can be used as `return report_cross_origin_denial(...);`.
509///
510/// What this function does corresponds to the operations in
511/// <https://html.spec.whatwg.org/multipage/#the-location-interface> denoted as
512/// "Throw a `SecurityError` DOMException".
513pub(crate) fn report_cross_origin_denial<D: DomTypes>(
514    cx: SafeJSContext,
515    id: RawHandleId,
516    access: &str,
517) -> bool {
518    debug!(
519        "permission denied to {} property {} on cross-origin object",
520        access,
521        id_to_source(cx, id)
522            .as_ref()
523            .map(|source| source.str())
524            .as_deref()
525            .unwrap_or("< error >"),
526    );
527    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
528    unsafe {
529        if !JS_IsExceptionPending(*cx) {
530            let global = D::GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
531            // TODO: include `id` and `access` in the exception message
532            <D as DomHelpers<D>>::throw_dom_exception(
533                cx,
534                &global,
535                Error::Security(None),
536                CanGc::note(),
537            );
538        }
539    }
540    false
541}
542
543/// Implementation of `[[Set]]` for [`Location`].
544///
545/// [`Location`]: https://html.spec.whatwg.org/multipage/#location-set
546pub(crate) unsafe extern "C" fn maybe_cross_origin_set_rawcx<D: DomTypes>(
547    cx: *mut JSContext,
548    proxy: RawHandleObject,
549    id: RawHandleId,
550    v: RawHandleValue,
551    receiver: RawHandleValue,
552    result: *mut ObjectOpResult,
553) -> bool {
554    let mut cx = js::context::JSContext::from_ptr(NonNull::new(cx).unwrap());
555    let mut realm = js::realm::CurrentRealm::assert(&mut cx);
556    let proxy_handle = unsafe { HandleObject::from_raw(proxy) };
557
558    if !<D as DomHelpers<D>>::is_platform_object_same_origin(&realm, proxy) {
559        return cross_origin_set::<D>(
560            SafeJSContext::from_ptr(realm.raw_cx()),
561            proxy,
562            id,
563            v,
564            receiver,
565            result,
566        );
567    }
568
569    // Safe to enter the Realm of proxy now.
570    let mut realm = js::realm::AutoRealm::new_from_handle(&mut realm, proxy_handle);
571
572    // OrdinarySet
573    // <https://tc39.es/ecma262/#sec-ordinaryset>
574    rooted!(&in(&mut realm) let mut own_desc = PropertyDescriptor::default());
575    let mut is_none = false;
576    if !js::glue::InvokeGetOwnPropertyDescriptor(
577        GetProxyHandler(*proxy),
578        realm.raw_cx(),
579        proxy,
580        id,
581        own_desc.handle_mut().into(),
582        &mut is_none,
583    ) {
584        return false;
585    }
586
587    let own_desc_handle = own_desc.handle().into();
588    js::jsapi::SetPropertyIgnoringNamedGetter(
589        realm.raw_cx(),
590        proxy,
591        id,
592        v,
593        receiver,
594        if is_none {
595            ptr::null()
596        } else {
597            &own_desc_handle
598        },
599        result,
600    )
601}
602
603/// Implementation of `[[GetPrototypeOf]]` for [`Location`].
604///
605/// [`Location`]: https://html.spec.whatwg.org/multipage/#location-getprototypeof
606pub(crate) fn maybe_cross_origin_get_prototype<D: DomTypes>(
607    cx: &mut CurrentRealm,
608    proxy: RawHandleObject,
609    get_proto_object: fn(cx: SafeJSContext, global: HandleObject, rval: MutableHandleObject),
610    proto: RawMutableHandleObject,
611) -> bool {
612    let proxy = unsafe { Handle::from_raw(proxy) };
613    // > 1. If ! IsPlatformObjectSameOrigin(this) is true, then return ! OrdinaryGetPrototypeOf(this).
614    if <D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy.into_handle()) {
615        let mut realm = AutoRealm::new_from_handle(cx, proxy);
616        let mut realm = realm.current_realm();
617        let global = D::GlobalScope::from_current_realm(&realm);
618        get_proto_object(
619            unsafe { SafeJSContext::from_ptr(realm.raw_cx()) },
620            global.reflector().get_jsobject(),
621            unsafe { MutableHandleObject::from_raw(proto) },
622        );
623        return !proto.is_null();
624    }
625
626    // > 2. Return null.
627    proto.set(ptr::null_mut());
628    true
629}
630
631/// Implementation of [`CrossOriginGet`].
632///
633/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
634/// for a maybe-cross-origin object.
635///
636/// [`CrossOriginGet`]: https://html.spec.whatwg.org/multipage/#crossoriginget-(-o,-p,-receiver-)
637pub(crate) fn cross_origin_get<D: DomTypes>(
638    cx: SafeJSContext,
639    proxy: RawHandleObject,
640    receiver: RawHandleValue,
641    id: RawHandleId,
642    vp: RawMutableHandleValue,
643) -> bool {
644    // > 1. Let `desc` be `? O.[[GetOwnProperty]](P)`.
645    rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default());
646    let mut is_none = false;
647    if !unsafe {
648        InvokeGetOwnPropertyDescriptor(
649            GetProxyHandler(*proxy),
650            *cx,
651            proxy,
652            id,
653            descriptor.handle_mut().into(),
654            &mut is_none,
655        )
656    } {
657        return false;
658    }
659
660    // > 2. Assert: `desc` is not undefined.
661    assert!(
662        !is_none,
663        "Callees should throw in all cases when they are not finding \
664        a property decriptor"
665    );
666
667    // > 3. If `! IsDataDescriptor(desc)` is true, then return `desc.[[Value]]`.
668    if is_data_descriptor(&descriptor) {
669        vp.set(descriptor.value_);
670        return true;
671    }
672
673    // > 4. Assert: `IsAccessorDescriptor(desc)` is `true`.
674    assert!(is_accessor_descriptor(&descriptor));
675
676    // > 5. Let `getter` be `desc.[[Get]]`.
677    // >
678    // > 6. If `getter` is `undefined`, then throw a `SecurityError`
679    // >    `DOMException`.
680    rooted!(in(*cx) let mut getter = ptr::null_mut::<JSObject>());
681    get_getter_object(&descriptor, getter.handle_mut().into());
682    if getter.get().is_null() {
683        return report_cross_origin_denial::<D>(cx, id, "get");
684    }
685
686    rooted!(in(*cx) let mut getter_jsval = UndefinedValue());
687    unsafe {
688        getter.get().to_jsval(*cx, getter_jsval.handle_mut());
689    }
690
691    // > 7. Return `? Call(getter, Receiver)`.
692    unsafe {
693        jsapi::Call(
694            *cx,
695            receiver,
696            getter_jsval.handle().into(),
697            &jsapi::HandleValueArray::empty(),
698            vp,
699        )
700    }
701}
702
703/// Implementation of [`CrossOriginSet`].
704///
705/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
706/// for a maybe-cross-origin object.
707///
708/// [`CrossOriginSet`]: https://html.spec.whatwg.org/multipage/#crossoriginset-(-o,-p,-v,-receiver-)
709pub(crate) unsafe fn cross_origin_set<D: DomTypes>(
710    cx: SafeJSContext,
711    proxy: RawHandleObject,
712    id: RawHandleId,
713    v: RawHandleValue,
714    receiver: RawHandleValue,
715    result: *mut ObjectOpResult,
716) -> bool {
717    // > 1. Let desc be ? O.[[GetOwnProperty]](P).
718    rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default());
719    let mut is_none = false;
720    if !InvokeGetOwnPropertyDescriptor(
721        GetProxyHandler(*proxy),
722        *cx,
723        proxy,
724        id,
725        descriptor.handle_mut().into(),
726        &mut is_none,
727    ) {
728        return false;
729    }
730
731    // > 2. Assert: desc is not undefined.
732    assert!(
733        !is_none,
734        "Callees should throw in all cases when they are not finding \
735        a property decriptor"
736    );
737
738    // > 3. If desc.[[Set]] is present and its value is not undefined,
739    // >    then: [...]
740    rooted!(in(*cx) let mut setter = ptr::null_mut::<JSObject>());
741    get_setter_object(&descriptor, setter.handle_mut().into());
742    if setter.get().is_null() {
743        // > 4. Throw a "SecurityError" DOMException.
744        return report_cross_origin_denial::<D>(cx, id, "set");
745    }
746
747    rooted!(in(*cx) let mut setter_jsval = UndefinedValue());
748    setter.get().to_jsval(*cx, setter_jsval.handle_mut());
749
750    // > 3.1. Perform ? Call(setter, Receiver, «V»).
751    // >
752    // > 3.2. Return true.
753    rooted!(in(*cx) let mut ignored = UndefinedValue());
754    if !jsapi::Call(
755        *cx,
756        receiver,
757        setter_jsval.handle().into(),
758        // FIXME: Our binding lacks `HandleValueArray(Handle<Value>)`
759        // <https://searchfox.org/mozilla-central/rev/072710086ddfe25aa2962c8399fefb2304e8193b/js/public/ValueArray.h#54-55>
760        &jsapi::HandleValueArray {
761            length_: 1,
762            elements_: v.ptr,
763        },
764        ignored.handle_mut().into(),
765    ) {
766        return false;
767    }
768
769    (*result).code_ = 0 /* OkCode */;
770    true
771}
772
773/// Implementation of [`CrossOriginPropertyFallback`].
774///
775/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
776/// for a maybe-cross-origin object.
777///
778/// [`CrossOriginPropertyFallback`]: https://html.spec.whatwg.org/multipage/#crossoriginpropertyfallback-(-p-)
779pub(crate) fn cross_origin_property_fallback<D: DomTypes>(
780    cx: SafeJSContext,
781    _proxy: RawHandleObject,
782    id: RawHandleId,
783    desc: RawMutableHandle<PropertyDescriptor>,
784    is_none: &mut bool,
785) -> bool {
786    assert!(*is_none, "why are we being called?");
787
788    // > 1. If P is `then`, `@@toStringTag`, `@@hasInstance`, or
789    // >    `@@isConcatSpreadable`, then return `PropertyDescriptor{ [[Value]]:
790    // >    undefined, [[Writable]]: false, [[Enumerable]]: false,
791    // >    [[Configurable]]: true }`.
792    if is_cross_origin_allowlisted_prop(cx, id) {
793        set_property_descriptor(
794            unsafe { MutableHandle::from_raw(desc) },
795            HandleValue::undefined(),
796            jsapi::JSPROP_READONLY as u32,
797            is_none,
798        );
799        return true;
800    }
801
802    // > 2. Throw a `SecurityError` `DOMException`.
803    report_cross_origin_denial::<D>(cx, id, "access")
804}