Skip to main content

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::wrappers2::{
30    JS_IdToValue, JS_IsExceptionPending, JS_ValueToSource, RUST_JSID_IS_VOID,
31};
32use js::rust::{
33    Handle, HandleId, HandleObject, HandleValue, IntoHandle, MutableHandle, MutableHandleObject,
34};
35use js::{jsapi, rooted};
36
37use crate::DomTypes;
38use crate::conversions::{is_dom_proxy, jsid_to_string, native_from_object};
39use crate::error::Error;
40use crate::interfaces::{DomHelpers, GlobalScopeHelpers};
41use crate::reflector::DomObject;
42use crate::script_runtime::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
226fn id_to_source(cx: &mut js::context::JSContext, id: HandleId) -> Option<DOMString> {
227    unsafe {
228        if RUST_JSID_IS_VOID(id) {
229            return None;
230        }
231        rooted!(&in(cx) let mut value = UndefinedValue());
232        rooted!(&in(cx) let mut jsstr = ptr::null_mut::<jsapi::JSString>());
233        JS_IdToValue(cx, id.get(), value.handle_mut())
234            .then(|| {
235                jsstr.set(JS_ValueToSource(cx, value.handle()));
236                jsstr.get()
237            })
238            .and_then(ptr::NonNull::new)
239            .map(|jsstr| jsstr_to_string(cx, jsstr).into())
240    }
241}
242
243/// Property and method specs that correspond to the elements of
244/// [`CrossOriginProperties(O)`].
245///
246/// [`CrossOriginProperties(O)`]: https://html.spec.whatwg.org/multipage/#crossoriginproperties-(-o-)
247pub(crate) struct CrossOriginProperties {
248    pub(crate) attributes: &'static [JSPropertySpec],
249    pub(crate) methods: &'static [JSFunctionSpec],
250}
251
252impl CrossOriginProperties {
253    /// Enumerate the property keys defined by `self`.
254    fn keys(&self) -> impl Iterator<Item = *const c_char> + '_ {
255        // Safety: All cross-origin property keys are strings, not symbols
256        self.attributes
257            .iter()
258            .map(|spec| unsafe { spec.name.string_ })
259            .chain(self.methods.iter().map(|spec| unsafe { spec.name.string_ }))
260            .filter(|ptr| !ptr.is_null())
261    }
262}
263
264/// Implementation of [`CrossOriginOwnPropertyKeys`].
265///
266/// [`CrossOriginOwnPropertyKeys`]: https://html.spec.whatwg.org/multipage/#crossoriginownpropertykeys-(-o-)
267pub(crate) fn cross_origin_own_property_keys(
268    cx: &mut js::context::JSContext,
269    _proxy: RawHandleObject,
270    cross_origin_properties: &'static CrossOriginProperties,
271    props: RawMutableHandleIdVector,
272) -> bool {
273    // > 2. For each `e` of `! CrossOriginProperties(O)`, append
274    // >    `e.[[Property]]` to `keys`.
275    for key in cross_origin_properties.keys() {
276        unsafe {
277            rooted!(&in(cx) let rooted = js::rust::wrappers2::JS_AtomizeAndPinString(cx, key));
278            rooted!(&in(cx) let mut rooted_jsid: jsid);
279            js::rust::wrappers2::RUST_INTERNED_STRING_TO_JSID(
280                cx,
281                rooted.handle().get(),
282                rooted_jsid.handle_mut(),
283            );
284            AppendToIdVector(props, rooted_jsid.handle());
285        }
286    }
287
288    // > 3. Return the concatenation of `keys` and `« "then", @@toStringTag,
289    // > @@hasInstance, @@isConcatSpreadable »`.
290    append_cross_origin_allowlisted_prop_keys(cx, props);
291
292    true
293}
294
295/// # Safety
296/// `is_ordinary` must point to a valid, non-null bool.
297pub(crate) unsafe extern "C" fn maybe_cross_origin_get_prototype_if_ordinary_rawcx(
298    _: *mut JSContext,
299    _proxy: RawHandleObject,
300    is_ordinary: *mut bool,
301    _proto: RawMutableHandleObject,
302) -> bool {
303    // We have a custom `[[GetPrototypeOf]]`, so return `false`
304    *is_ordinary = false;
305    true
306}
307
308/// Implementation of `[[SetPrototypeOf]]` for [`Location`] and [`WindowProxy`].
309///
310/// [`Location`]: https://html.spec.whatwg.org/multipage/#location-setprototypeof
311/// [`WindowProxy`]: https://html.spec.whatwg.org/multipage/#windowproxy-setprototypeof
312///
313/// # Safety
314/// `result` must point to a valid, non-null ObjectOpResult.
315pub(crate) unsafe extern "C" fn maybe_cross_origin_set_prototype_rawcx(
316    cx: *mut JSContext,
317    proxy: RawHandleObject,
318    proto: RawHandleObject,
319    result: *mut ObjectOpResult,
320) -> bool {
321    // > 1. Return `! SetImmutablePrototype(this, V)`.
322    //
323    // <https://tc39.es/ecma262/#sec-set-immutable-prototype>:
324    //
325    // > 1. Assert: Either `Type(V)` is Object or `Type(V)` is Null.
326    //
327    // > 2. Let current be `? O.[[GetPrototypeOf]]()`.
328    rooted!(in(cx) let mut current = ptr::null_mut::<JSObject>());
329    if !jsapi::GetObjectProto(cx, proxy, current.handle_mut().into()) {
330        return false;
331    }
332
333    // > 3. If `SameValue(V, current)` is true, return true.
334    if proto.get() == current.get() {
335        (*result).code_ = 0 /* OkCode */;
336        return true;
337    }
338
339    // > 4. Return false.
340    (*result).code_ = JSErrNum::JSMSG_CANT_SET_PROTO as usize;
341    true
342}
343
344pub(crate) fn get_getter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
345    if d.hasGetter_() {
346        out.set(d.getter_);
347    }
348}
349
350pub(crate) fn get_setter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
351    if d.hasSetter_() {
352        out.set(d.setter_);
353    }
354}
355
356/// <https://tc39.es/ecma262/#sec-isaccessordescriptor>
357pub(crate) fn is_accessor_descriptor(d: &PropertyDescriptor) -> bool {
358    d.hasSetter_() || d.hasGetter_()
359}
360
361/// <https://tc39.es/ecma262/#sec-isdatadescriptor>
362pub(crate) fn is_data_descriptor(d: &PropertyDescriptor) -> bool {
363    d.hasWritable_() || d.hasValue_()
364}
365
366/// Evaluate `CrossOriginGetOwnPropertyHelper(proxy, id) != null`.
367/// SpiderMonkey-specific.
368///
369/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
370/// for a maybe-cross-origin object.
371///
372/// # Safety
373/// `bp` must point to a valid, non-null bool.
374pub(crate) unsafe fn cross_origin_has_own(
375    cx: &mut js::realm::CurrentRealm,
376    _proxy: RawHandleObject,
377    cross_origin_properties: &'static CrossOriginProperties,
378    id: RawHandleId,
379    bp: *mut bool,
380) -> bool {
381    // TODO: Once we have the slot for the holder, it'd be more efficient to
382    //       use `ensure_cross_origin_property_holder`. We'll need `_proxy` to
383    //       do that.
384    *bp = jsid_to_string(cx, Handle::from_raw(id)).is_some_and(|key| {
385        cross_origin_properties.keys().any(|defined_key| {
386            let defined_key = CStr::from_ptr(defined_key);
387            defined_key.to_bytes() == key.str().as_bytes()
388        })
389    });
390
391    true
392}
393
394/// Implementation of [`CrossOriginGetOwnPropertyHelper`].
395///
396/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
397/// for a maybe-cross-origin object.
398///
399/// [`CrossOriginGetOwnPropertyHelper`]: https://html.spec.whatwg.org/multipage/#crossorigingetownpropertyhelper-(-o,-p-)
400pub(crate) fn cross_origin_get_own_property_helper(
401    cx: &mut CurrentRealm,
402    proxy: RawHandleObject,
403    cross_origin_properties: &'static CrossOriginProperties,
404    id: RawHandleId,
405    desc: RawMutableHandle<PropertyDescriptor>,
406    is_none: &mut bool,
407) -> bool {
408    rooted!(&in(cx) let mut holder = ptr::null_mut::<JSObject>());
409    let id = unsafe { Handle::from_raw(id) };
410    let proxy = unsafe { Handle::from_raw(proxy) };
411    let desc = unsafe { MutableHandle::from_raw(desc) };
412
413    ensure_cross_origin_property_holder(cx, proxy, cross_origin_properties, holder.handle_mut());
414
415    unsafe {
416        js::rust::wrappers2::JS_GetOwnPropertyDescriptorById(cx, holder.handle(), id, desc, is_none)
417    }
418}
419
420const ALLOWLISTED_SYMBOL_CODES: &[SymbolCode] = &[
421    SymbolCode::toStringTag,
422    SymbolCode::hasInstance,
423    SymbolCode::isConcatSpreadable,
424];
425
426pub(crate) fn is_cross_origin_allowlisted_prop(
427    cx: &mut js::context::JSContext,
428    id: RawHandleId,
429) -> bool {
430    unsafe {
431        if jsid_to_string(cx, Handle::from_raw(id)).is_some_and(|st| st == "then") {
432            return true;
433        }
434
435        rooted!(&in(cx) let mut allowed_id: jsid);
436        ALLOWLISTED_SYMBOL_CODES.iter().any(|&allowed_code| {
437            allowed_id.set(SymbolId(GetWellKnownSymbol(cx.raw_cx(), allowed_code)));
438            // `jsid`s containing `JS::Symbol *` can be compared by
439            // referential equality
440            allowed_id.get().asBits_ == id.asBits_
441        })
442    }
443}
444
445/// Append `« "then", @@toStringTag, @@hasInstance, @@isConcatSpreadable »` to
446/// `props`. This is used to implement [`CrossOriginOwnPropertyKeys`].
447///
448/// [`CrossOriginOwnPropertyKeys`]: https://html.spec.whatwg.org/multipage/#crossoriginownpropertykeys-(-o-)
449fn append_cross_origin_allowlisted_prop_keys(
450    cx: &mut js::context::JSContext,
451    props: RawMutableHandleIdVector,
452) {
453    unsafe {
454        rooted!(&in(cx) let mut id: jsid);
455
456        let jsstring = js::rust::wrappers2::JS_AtomizeAndPinString(cx, c"then".as_ptr());
457        rooted!(&in(cx) let rooted = jsstring);
458        js::rust::wrappers2::RUST_INTERNED_STRING_TO_JSID(
459            cx,
460            rooted.handle().get(),
461            id.handle_mut(),
462        );
463        AppendToIdVector(props, id.handle());
464
465        for &allowed_code in ALLOWLISTED_SYMBOL_CODES.iter() {
466            id.set(SymbolId(js::rust::wrappers2::GetWellKnownSymbol(
467                cx,
468                allowed_code,
469            )));
470            AppendToIdVector(props, id.handle());
471        }
472    }
473}
474
475/// Get the holder for cross-origin properties for the current global of the
476/// `JSContext`, creating one and storing it in a slot of the proxy object if it
477/// doesn't exist yet.
478///
479/// This essentially creates a cache of [`CrossOriginGetOwnPropertyHelper`]'s
480/// results for all property keys.
481///
482/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
483/// for a maybe-cross-origin object. The `out_holder` return value will always
484/// be in the Realm of `cx`.
485///
486/// [`CrossOriginGetOwnPropertyHelper`]: https://html.spec.whatwg.org/multipage/#crossorigingetownpropertyhelper-(-o,-p-)
487fn ensure_cross_origin_property_holder(
488    cx: &mut CurrentRealm,
489    _proxy: HandleObject,
490    cross_origin_properties: &'static CrossOriginProperties,
491    mut out_holder: MutableHandleObject,
492) -> bool {
493    // TODO: We don't have the slot to store the holder yet. For now,
494    //       the holder is constructed every time this function is called,
495    //       which is not only inefficient but also deviates from the
496    //       specification in a subtle yet observable way.
497
498    // Create a holder for the current Realm
499    unsafe {
500        out_holder.set(js::rust::wrappers2::JS_NewObjectWithGivenProto(
501            cx,
502            ptr::null_mut(),
503            HandleObject::null(),
504        ));
505
506        if out_holder.get().is_null() ||
507            !js::rust::wrappers2::JS_DefineProperties(
508                cx,
509                out_holder.handle(),
510                cross_origin_properties.attributes.as_ptr(),
511            ) ||
512            !js::rust::wrappers2::JS_DefineFunctions(
513                cx,
514                out_holder.handle(),
515                cross_origin_properties.methods.as_ptr(),
516            )
517        {
518            return false;
519        }
520    }
521
522    // TODO: Store the holder in the slot that we don't have yet.
523
524    true
525}
526
527/// Check if `obj` is a `Location` or `Window` object.
528///
529/// IDL operations on a cross-origin object involve [a security check][1].
530///
531/// [1]: https://html.spec.whatwg.org/multipage/#integration-with-idl
532pub(crate) fn is_cross_origin_object<D: DomTypes>(cx: SafeJSContext, obj: RawHandleObject) -> bool {
533    unsafe {
534        jsapi::IsWindowProxy(*obj) ||
535            native_from_object::<D::Location>(*obj, *cx).is_ok() ||
536            native_from_object::<D::DissimilarOriginLocation>(*obj, *cx).is_ok()
537    }
538}
539
540/// Report a cross-origin denial for a property, Always returns `false`, so it
541/// can be used as `return report_cross_origin_denial(...);`.
542///
543/// What this function does corresponds to the operations in
544/// <https://html.spec.whatwg.org/multipage/#the-location-interface> denoted as
545/// "Throw a `SecurityError` DOMException".
546pub(crate) fn report_cross_origin_denial<D: DomTypes>(
547    cx: &mut CurrentRealm,
548    id: HandleId,
549    access: &str,
550) -> bool {
551    if let Some(id) = id_to_source(cx, id) {
552        debug!(
553            "permission denied to {} property {} on cross-origin object",
554            access,
555            &*id.str(),
556        );
557    } else {
558        debug!("permission denied to {} on cross-origin object", access);
559    }
560    unsafe {
561        if !JS_IsExceptionPending(cx) {
562            let global = D::GlobalScope::from_current_realm(cx);
563            // TODO: include `id` and `access` in the exception message
564            <D as DomHelpers<D>>::throw_dom_exception(cx, &global, Error::Security(None));
565        }
566    }
567    false
568}
569
570/// Implementation of `[[Set]]` for [`Location`].
571///
572/// [`Location`]: https://html.spec.whatwg.org/multipage/#location-set
573pub(crate) unsafe extern "C" fn maybe_cross_origin_set_rawcx<D: DomTypes>(
574    cx: *mut JSContext,
575    proxy: RawHandleObject,
576    id: RawHandleId,
577    v: RawHandleValue,
578    receiver: RawHandleValue,
579    result: *mut ObjectOpResult,
580) -> bool {
581    unsafe {
582        let mut cx = js::context::JSContext::from_ptr(NonNull::new(cx).unwrap());
583        let mut realm = js::realm::CurrentRealm::assert(&mut cx);
584        let proxy = HandleObject::from_raw(proxy);
585        let id = Handle::from_raw(id);
586        let v = Handle::from_raw(v);
587        let receiver = Handle::from_raw(receiver);
588
589        if !<D as DomHelpers<D>>::is_platform_object_same_origin(&realm, proxy.into_handle()) {
590            return cross_origin_set::<D>(&mut realm, proxy, id, v.into_handle(), receiver, result);
591        }
592
593        // Safe to enter the Realm of proxy now.
594        let mut realm = js::realm::AutoRealm::new_from_handle(&mut realm, proxy);
595
596        // OrdinarySet
597        // <https://tc39.es/ecma262/#sec-ordinaryset>
598        rooted!(&in(&mut realm) let mut own_desc = PropertyDescriptor::default());
599        let mut is_none = false;
600        if !js::rust::wrappers2::InvokeGetOwnPropertyDescriptor(
601            GetProxyHandler(*proxy),
602            &mut realm,
603            proxy,
604            id,
605            own_desc.handle_mut(),
606            &mut is_none,
607        ) {
608            return false;
609        }
610
611        js::rust::wrappers2::SetPropertyIgnoringNamedGetter(
612            &mut realm,
613            proxy,
614            id,
615            v,
616            receiver,
617            if is_none {
618                None
619            } else {
620                Some(own_desc.handle())
621            },
622            result,
623        )
624    }
625}
626
627/// Implementation of `[[GetPrototypeOf]]` for [`Location`].
628///
629/// [`Location`]: https://html.spec.whatwg.org/multipage/#location-getprototypeof
630pub(crate) fn maybe_cross_origin_get_prototype<D: DomTypes>(
631    cx: &mut CurrentRealm,
632    proxy: RawHandleObject,
633    get_proto_object: fn(
634        cx: &mut js::context::JSContext,
635        global: HandleObject,
636        rval: MutableHandleObject,
637    ),
638    proto: RawMutableHandleObject,
639) -> bool {
640    let proxy = unsafe { Handle::from_raw(proxy) };
641    // > 1. If ! IsPlatformObjectSameOrigin(this) is true, then return ! OrdinaryGetPrototypeOf(this).
642    if <D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy.into_handle()) {
643        let mut realm = AutoRealm::new_from_handle(cx, proxy);
644        let mut realm = realm.current_realm();
645        let global = D::GlobalScope::from_current_realm(&realm);
646        get_proto_object(&mut realm, global.reflector().get_jsobject(), unsafe {
647            MutableHandleObject::from_raw(proto)
648        });
649        return !proto.is_null();
650    }
651
652    // > 2. Return null.
653    proto.set(ptr::null_mut());
654    true
655}
656
657/// Implementation of [`CrossOriginGet`].
658///
659/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
660/// for a maybe-cross-origin object.
661///
662/// [`CrossOriginGet`]: https://html.spec.whatwg.org/multipage/#crossoriginget-(-o,-p,-receiver-)
663pub(crate) fn cross_origin_get<D: DomTypes>(
664    cx: &mut CurrentRealm,
665    proxy: RawHandleObject,
666    receiver: RawHandleValue,
667    id: RawHandleId,
668    vp: RawMutableHandleValue,
669) -> bool {
670    // > 1. Let `desc` be `? O.[[GetOwnProperty]](P)`.
671    rooted!(&in(cx) let mut descriptor = PropertyDescriptor::default());
672    let proxy = unsafe { Handle::from_raw(proxy) };
673    let receiver = unsafe { Handle::from_raw(receiver) };
674    let id = unsafe { Handle::from_raw(id) };
675    let mut vp = unsafe { MutableHandle::from_raw(vp) };
676    let mut is_none = false;
677    if !unsafe {
678        js::rust::wrappers2::InvokeGetOwnPropertyDescriptor(
679            GetProxyHandler(*proxy),
680            cx,
681            proxy,
682            id,
683            descriptor.handle_mut(),
684            &mut is_none,
685        )
686    } {
687        return false;
688    }
689
690    // > 2. Assert: `desc` is not undefined.
691    assert!(
692        !is_none,
693        "Callees should throw in all cases when they are not finding \
694        a property decriptor"
695    );
696
697    // > 3. If `! IsDataDescriptor(desc)` is true, then return `desc.[[Value]]`.
698    if is_data_descriptor(&descriptor) {
699        vp.set(descriptor.value_);
700        return true;
701    }
702
703    // > 4. Assert: `IsAccessorDescriptor(desc)` is `true`.
704    assert!(is_accessor_descriptor(&descriptor));
705
706    // > 5. Let `getter` be `desc.[[Get]]`.
707    // >
708    // > 6. If `getter` is `undefined`, then throw a `SecurityError`
709    // >    `DOMException`.
710    rooted!(&in(cx) let mut getter = ptr::null_mut::<JSObject>());
711    get_getter_object(&descriptor, getter.handle_mut().into());
712    if getter.get().is_null() {
713        return report_cross_origin_denial::<D>(cx, id, "get");
714    }
715
716    rooted!(&in(cx) let mut getter_jsval = UndefinedValue());
717    unsafe {
718        getter
719            .get()
720            .to_jsval(cx.raw_cx(), getter_jsval.handle_mut());
721    }
722
723    // > 7. Return `? Call(getter, Receiver)`.
724    unsafe {
725        js::rust::wrappers2::Call(
726            cx,
727            receiver,
728            getter_jsval.handle(),
729            &jsapi::HandleValueArray::empty(),
730            vp,
731        )
732    }
733}
734
735/// Implementation of [`CrossOriginSet`].
736///
737/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
738/// for a maybe-cross-origin object.
739///
740/// [`CrossOriginSet`]: https://html.spec.whatwg.org/multipage/#crossoriginset-(-o,-p,-v,-receiver-)
741pub(crate) unsafe fn cross_origin_set<D: DomTypes>(
742    cx: &mut CurrentRealm,
743    proxy: HandleObject,
744    id: HandleId,
745    v: RawHandleValue,
746    receiver: HandleValue,
747    result: *mut ObjectOpResult,
748) -> bool {
749    // > 1. Let desc be ? O.[[GetOwnProperty]](P).
750    rooted!(&in(cx) let mut descriptor = PropertyDescriptor::default());
751    let mut is_none = false;
752    if !js::rust::wrappers2::InvokeGetOwnPropertyDescriptor(
753        GetProxyHandler(*proxy),
754        cx,
755        proxy,
756        id,
757        descriptor.handle_mut(),
758        &mut is_none,
759    ) {
760        return false;
761    }
762
763    // > 2. Assert: desc is not undefined.
764    assert!(
765        !is_none,
766        "Callees should throw in all cases when they are not finding \
767        a property decriptor"
768    );
769
770    // > 3. If desc.[[Set]] is present and its value is not undefined,
771    // >    then: [...]
772    rooted!(&in(cx) let mut setter = ptr::null_mut::<JSObject>());
773    get_setter_object(&descriptor, setter.handle_mut().into());
774    if setter.get().is_null() {
775        // > 4. Throw a "SecurityError" DOMException.
776        return report_cross_origin_denial::<D>(cx, id, "set");
777    }
778
779    rooted!(&in(cx) let mut setter_jsval = UndefinedValue());
780    setter
781        .get()
782        .to_jsval(cx.raw_cx(), setter_jsval.handle_mut());
783
784    // > 3.1. Perform ? Call(setter, Receiver, «V»).
785    // >
786    // > 3.2. Return true.
787    rooted!(&in(cx) let mut ignored = UndefinedValue());
788    if !js::rust::wrappers2::Call(
789        cx,
790        receiver,
791        setter_jsval.handle(),
792        // FIXME: Our binding lacks `HandleValueArray(Handle<Value>)`
793        // <https://searchfox.org/mozilla-central/rev/072710086ddfe25aa2962c8399fefb2304e8193b/js/public/ValueArray.h#54-55>
794        &jsapi::HandleValueArray {
795            length_: 1,
796            elements_: v.ptr,
797        },
798        ignored.handle_mut(),
799    ) {
800        return false;
801    }
802
803    (*result).code_ = 0 /* OkCode */;
804    true
805}
806
807/// Implementation of [`CrossOriginPropertyFallback`].
808///
809/// `cx` and `proxy` are expected to be different-Realm here. `proxy` is a proxy
810/// for a maybe-cross-origin object.
811///
812/// [`CrossOriginPropertyFallback`]: https://html.spec.whatwg.org/multipage/#crossoriginpropertyfallback-(-p-)
813pub(crate) fn cross_origin_property_fallback<D: DomTypes>(
814    cx: &mut CurrentRealm,
815    _proxy: RawHandleObject,
816    id: RawHandleId,
817    desc: RawMutableHandle<PropertyDescriptor>,
818    is_none: &mut bool,
819) -> bool {
820    assert!(*is_none, "why are we being called?");
821
822    // > 1. If P is `then`, `@@toStringTag`, `@@hasInstance`, or
823    // >    `@@isConcatSpreadable`, then return `PropertyDescriptor{ [[Value]]:
824    // >    undefined, [[Writable]]: false, [[Enumerable]]: false,
825    // >    [[Configurable]]: true }`.
826    if is_cross_origin_allowlisted_prop(cx, id) {
827        set_property_descriptor(
828            unsafe { MutableHandle::from_raw(desc) },
829            HandleValue::undefined(),
830            jsapi::JSPROP_READONLY as u32,
831            is_none,
832        );
833        return true;
834    }
835
836    let id = unsafe { Handle::from_raw(id) };
837
838    // > 2. Throw a `SecurityError` `DOMException`.
839    report_cross_origin_denial::<D>(cx, id, "access")
840}