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