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