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