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.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).as_deref().unwrap_or("< error >"),
521    );
522    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
523    unsafe {
524        if !JS_IsExceptionPending(*cx) {
525            let global = D::GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
526            // TODO: include `id` and `access` in the exception message
527            <D as DomHelpers<D>>::throw_dom_exception(cx, &global, Error::Security, CanGc::note());
528        }
529    }
530    false
531}
532
533/// Implementation of `[[Set]]` for [`Location`].
534///
535/// [`Location`]: https://html.spec.whatwg.org/multipage/#location-set
536pub(crate) unsafe extern "C" fn maybe_cross_origin_set_rawcx<D: DomTypes>(
537    cx: *mut JSContext,
538    proxy: RawHandleObject,
539    id: RawHandleId,
540    v: RawHandleValue,
541    receiver: RawHandleValue,
542    result: *mut ObjectOpResult,
543) -> bool {
544    let cx = SafeJSContext::from_ptr(cx);
545
546    if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) {
547        return cross_origin_set::<D>(cx, proxy, id, v, receiver, result);
548    }
549
550    // Safe to enter the Realm of proxy now.
551    let _ac = JSAutoRealm::new(*cx, proxy.get());
552
553    // OrdinarySet
554    // <https://tc39.es/ecma262/#sec-ordinaryset>
555    rooted!(in(*cx) let mut own_desc = PropertyDescriptor::default());
556    let mut is_none = false;
557    if !InvokeGetOwnPropertyDescriptor(
558        GetProxyHandler(*proxy),
559        *cx,
560        proxy,
561        id,
562        own_desc.handle_mut().into(),
563        &mut is_none,
564    ) {
565        return false;
566    }
567
568    let own_desc_handle = own_desc.handle().into();
569    js::jsapi::SetPropertyIgnoringNamedGetter(
570        *cx,
571        proxy,
572        id,
573        v,
574        receiver,
575        if is_none {
576            ptr::null()
577        } else {
578            &own_desc_handle
579        },
580        result,
581    )
582}
583
584/// Implementation of `[[GetPrototypeOf]]` for [`Location`].
585///
586/// [`Location`]: https://html.spec.whatwg.org/multipage/#location-getprototypeof
587pub(crate) fn maybe_cross_origin_get_prototype<D: DomTypes>(
588    cx: SafeJSContext,
589    proxy: RawHandleObject,
590    get_proto_object: fn(cx: SafeJSContext, global: HandleObject, rval: MutableHandleObject),
591    proto: RawMutableHandleObject,
592) -> bool {
593    // > 1. If ! IsPlatformObjectSameOrigin(this) is true, then return ! OrdinaryGetPrototypeOf(this).
594    if <D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) {
595        let ac = JSAutoRealm::new(*cx, proxy.get());
596        let global = unsafe { D::GlobalScope::from_context(*cx, InRealm::Entered(&ac)) };
597        get_proto_object(cx, global.reflector().get_jsobject(), unsafe {
598            MutableHandleObject::from_raw(proto)
599        });
600        return !proto.is_null();
601    }
602
603    // > 2. Return null.
604    proto.set(ptr::null_mut());
605    true
606}
607
608/// Implementation of [`CrossOriginGet`].
609///
610/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
611/// for a maybe-cross-origin object.
612///
613/// [`CrossOriginGet`]: https://html.spec.whatwg.org/multipage/#crossoriginget-(-o,-p,-receiver-)
614pub(crate) fn cross_origin_get<D: DomTypes>(
615    cx: SafeJSContext,
616    proxy: RawHandleObject,
617    receiver: RawHandleValue,
618    id: RawHandleId,
619    vp: RawMutableHandleValue,
620) -> bool {
621    // > 1. Let `desc` be `? O.[[GetOwnProperty]](P)`.
622    rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default());
623    let mut is_none = false;
624    if !unsafe {
625        InvokeGetOwnPropertyDescriptor(
626            GetProxyHandler(*proxy),
627            *cx,
628            proxy,
629            id,
630            descriptor.handle_mut().into(),
631            &mut is_none,
632        )
633    } {
634        return false;
635    }
636
637    // > 2. Assert: `desc` is not undefined.
638    assert!(
639        !is_none,
640        "Callees should throw in all cases when they are not finding \
641        a property decriptor"
642    );
643
644    // > 3. If `! IsDataDescriptor(desc)` is true, then return `desc.[[Value]]`.
645    if is_data_descriptor(&descriptor) {
646        vp.set(descriptor.value_);
647        return true;
648    }
649
650    // > 4. Assert: `IsAccessorDescriptor(desc)` is `true`.
651    assert!(is_accessor_descriptor(&descriptor));
652
653    // > 5. Let `getter` be `desc.[[Get]]`.
654    // >
655    // > 6. If `getter` is `undefined`, then throw a `SecurityError`
656    // >    `DOMException`.
657    rooted!(in(*cx) let mut getter = ptr::null_mut::<JSObject>());
658    get_getter_object(&descriptor, getter.handle_mut().into());
659    if getter.get().is_null() {
660        return report_cross_origin_denial::<D>(cx, id, "get");
661    }
662
663    rooted!(in(*cx) let mut getter_jsval = UndefinedValue());
664    unsafe {
665        getter.get().to_jsval(*cx, getter_jsval.handle_mut());
666    }
667
668    // > 7. Return `? Call(getter, Receiver)`.
669    unsafe {
670        jsapi::Call(
671            *cx,
672            receiver,
673            getter_jsval.handle().into(),
674            &jsapi::HandleValueArray::empty(),
675            vp,
676        )
677    }
678}
679
680/// Implementation of [`CrossOriginSet`].
681///
682/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
683/// for a maybe-cross-origin object.
684///
685/// [`CrossOriginSet`]: https://html.spec.whatwg.org/multipage/#crossoriginset-(-o,-p,-v,-receiver-)
686pub(crate) unsafe fn cross_origin_set<D: DomTypes>(
687    cx: SafeJSContext,
688    proxy: RawHandleObject,
689    id: RawHandleId,
690    v: RawHandleValue,
691    receiver: RawHandleValue,
692    result: *mut ObjectOpResult,
693) -> bool {
694    // > 1. Let desc be ? O.[[GetOwnProperty]](P).
695    rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default());
696    let mut is_none = false;
697    if !InvokeGetOwnPropertyDescriptor(
698        GetProxyHandler(*proxy),
699        *cx,
700        proxy,
701        id,
702        descriptor.handle_mut().into(),
703        &mut is_none,
704    ) {
705        return false;
706    }
707
708    // > 2. Assert: desc is not undefined.
709    assert!(
710        !is_none,
711        "Callees should throw in all cases when they are not finding \
712        a property decriptor"
713    );
714
715    // > 3. If desc.[[Set]] is present and its value is not undefined,
716    // >    then: [...]
717    rooted!(in(*cx) let mut setter = ptr::null_mut::<JSObject>());
718    get_setter_object(&descriptor, setter.handle_mut().into());
719    if setter.get().is_null() {
720        // > 4. Throw a "SecurityError" DOMException.
721        return report_cross_origin_denial::<D>(cx, id, "set");
722    }
723
724    rooted!(in(*cx) let mut setter_jsval = UndefinedValue());
725    setter.get().to_jsval(*cx, setter_jsval.handle_mut());
726
727    // > 3.1. Perform ? Call(setter, Receiver, «V»).
728    // >
729    // > 3.2. Return true.
730    rooted!(in(*cx) let mut ignored = UndefinedValue());
731    if !jsapi::Call(
732        *cx,
733        receiver,
734        setter_jsval.handle().into(),
735        // FIXME: Our binding lacks `HandleValueArray(Handle<Value>)`
736        // <https://searchfox.org/mozilla-central/rev/072710086ddfe25aa2962c8399fefb2304e8193b/js/public/ValueArray.h#54-55>
737        &jsapi::HandleValueArray {
738            length_: 1,
739            elements_: v.ptr,
740        },
741        ignored.handle_mut().into(),
742    ) {
743        return false;
744    }
745
746    (*result).code_ = 0 /* OkCode */;
747    true
748}
749
750/// Implementation of [`CrossOriginPropertyFallback`].
751///
752/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
753/// for a maybe-cross-origin object.
754///
755/// [`CrossOriginPropertyFallback`]: https://html.spec.whatwg.org/multipage/#crossoriginpropertyfallback-(-p-)
756pub(crate) fn cross_origin_property_fallback<D: DomTypes>(
757    cx: SafeJSContext,
758    _proxy: RawHandleObject,
759    id: RawHandleId,
760    desc: RawMutableHandle<PropertyDescriptor>,
761    is_none: &mut bool,
762) -> bool {
763    assert!(*is_none, "why are we being called?");
764
765    // > 1. If P is `then`, `@@toStringTag`, `@@hasInstance`, or
766    // >    `@@isConcatSpreadable`, then return `PropertyDescriptor{ [[Value]]:
767    // >    undefined, [[Writable]]: false, [[Enumerable]]: false,
768    // >    [[Configurable]]: true }`.
769    if is_cross_origin_allowlisted_prop(cx, id) {
770        set_property_descriptor(
771            unsafe { MutableHandle::from_raw(desc) },
772            HandleValue::undefined(),
773            jsapi::JSPROP_READONLY as u32,
774            is_none,
775        );
776        return true;
777    }
778
779    // > 2. Throw a `SecurityError` `DOMException`.
780    report_cross_origin_denial::<D>(cx, id, "access")
781}