Skip to main content

script_bindings/
interface.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//! Machinery to initialise interface prototype objects and interface objects.
6
7use std::convert::TryFrom;
8use std::ffi::CStr;
9use std::ptr::{self, NonNull};
10
11use js::error::throw_type_error;
12use js::glue::UncheckedUnwrapObject;
13use js::jsapi::JS::CompartmentIterResult;
14use js::jsapi::{
15    CallArgs, CheckedUnwrapStatic, Compartment, CompartmentSpecifier, GetNonCCWObjectGlobal,
16    GetRealmGlobalOrNull, HandleObject as RawHandleObject, IsSharableCompartment,
17    IsSystemCompartment, JS_GetFunctionObject, JS_NewStringCopyN, JS_SetReservedSlot, JSClass,
18    JSClassOps, JSContext, JSFUN_CONSTRUCTOR, JSFunctionSpec, JSObject, JSPROP_PERMANENT,
19    JSPROP_READONLY, JSPROP_RESOLVING, JSPropertySpec, JSString, JSTracer, ObjectOps,
20    OnNewGlobalHookOption, SymbolCode, TrueHandleValue, Value, jsid,
21};
22use js::jsval::{JSVal, NullValue, PrivateValue};
23use js::realm::AutoRealm;
24use js::rust::wrappers2::{
25    GetWellKnownSymbol, JS_AtomizeAndPinString, JS_DefineProperty3, JS_DefineProperty4,
26    JS_DefineProperty5, JS_DefinePropertyById5, JS_FireOnNewGlobalObject, JS_IterateCompartments,
27    JS_LinkConstructorAndPrototype, JS_NewFunction, JS_NewGlobalObject, JS_SetTrustedPrincipals,
28    RUST_SYMBOL_TO_JSID,
29};
30use js::rust::{
31    HandleObject, HandleValue, MutableHandleObject, RealmOptions, define_methods,
32    define_properties, get_object_class, is_dom_class, maybe_wrap_object,
33};
34use servo_url::MutableOrigin;
35
36use crate::DomTypes;
37use crate::codegen::Globals::Globals;
38use crate::codegen::PrototypeList;
39use crate::constant::{ConstantSpec, define_constants};
40use crate::conversions::{DOM_OBJECT_SLOT, get_dom_class};
41use crate::guard::Guard;
42use crate::js::{JS_DefineProperty, JS_NewObject, JS_NewObjectWithGivenProto, PropertyFlags};
43use crate::principals::ServoJSPrincipals;
44use crate::utils::{
45    DOM_PROTOTYPE_SLOT, DOMJSClass, JSCLASS_DOM_GLOBAL, ProtoOrIfaceArray, get_proto_or_iface_array,
46};
47
48/// The class of a non-callback interface object.
49#[derive(Clone, Copy)]
50pub(crate) struct NonCallbackInterfaceObjectClass {
51    /// The SpiderMonkey class structure.
52    pub(crate) _class: JSClass,
53    /// The prototype id of that interface, used in the hasInstance hook.
54    pub(crate) _proto_id: PrototypeList::ID,
55    /// The prototype depth of that interface, used in the hasInstance hook.
56    pub(crate) _proto_depth: u16,
57    /// The string representation of the object.
58    pub(crate) representation: &'static [u8],
59}
60
61unsafe impl Sync for NonCallbackInterfaceObjectClass {}
62
63impl NonCallbackInterfaceObjectClass {
64    /// Create a new `NonCallbackInterfaceObjectClass` structure.
65    pub(crate) const fn new(
66        constructor_behavior: &'static InterfaceConstructorBehavior,
67        string_rep: &'static [u8],
68        proto_id: PrototypeList::ID,
69        proto_depth: u16,
70    ) -> NonCallbackInterfaceObjectClass {
71        NonCallbackInterfaceObjectClass {
72            _class: JSClass {
73                name: c"Function".as_ptr(),
74                flags: 0,
75                cOps: &constructor_behavior.0,
76                spec: ptr::null(),
77                ext: ptr::null(),
78                oOps: &OBJECT_OPS,
79            },
80            _proto_id: proto_id,
81            _proto_depth: proto_depth,
82            representation: string_rep,
83        }
84    }
85
86    /// cast own reference to `JSClass` reference
87    pub(crate) fn as_jsclass(&self) -> &JSClass {
88        unsafe { &*(self as *const _ as *const JSClass) }
89    }
90}
91
92/// A constructor class hook.
93pub(crate) type ConstructorClassHook =
94    unsafe extern "C" fn(cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool;
95
96/// The constructor behavior of a non-callback interface object.
97pub(crate) struct InterfaceConstructorBehavior(JSClassOps);
98
99impl InterfaceConstructorBehavior {
100    /// An interface constructor that unconditionally throws a type error.
101    pub(crate) const fn throw() -> Self {
102        InterfaceConstructorBehavior(JSClassOps {
103            addProperty: None,
104            delProperty: None,
105            enumerate: None,
106            newEnumerate: None,
107            resolve: None,
108            mayResolve: None,
109            finalize: None,
110            call: Some(invalid_constructor),
111            construct: Some(invalid_constructor),
112            trace: None,
113        })
114    }
115
116    /// An interface constructor that calls a native Rust function.
117    pub(crate) const fn call(hook: ConstructorClassHook) -> Self {
118        InterfaceConstructorBehavior(JSClassOps {
119            addProperty: None,
120            delProperty: None,
121            enumerate: None,
122            newEnumerate: None,
123            resolve: None,
124            mayResolve: None,
125            finalize: None,
126            call: Some(non_new_constructor),
127            construct: Some(hook),
128            trace: None,
129        })
130    }
131}
132
133/// A trace hook.
134pub(crate) type TraceHook = unsafe extern "C" fn(trc: *mut JSTracer, obj: *mut JSObject);
135
136/// Create a global object with the given class.
137pub(crate) unsafe fn create_global_object<D: DomTypes>(
138    cx: &mut js::context::JSContext,
139    class: &'static JSClass,
140    private: *const libc::c_void,
141    trace: TraceHook,
142    mut rval: MutableHandleObject,
143    origin: &MutableOrigin,
144    use_system_compartment: bool,
145) {
146    assert!(rval.is_null());
147
148    let mut options = RealmOptions::default();
149    options.creationOptions_.traceGlobal_ = Some(trace);
150    options.creationOptions_.sharedMemoryAndAtomics_ = false;
151    if use_system_compartment {
152        options.creationOptions_.compSpec_ = CompartmentSpecifier::NewCompartmentAndZone;
153        options.creationOptions_.__bindgen_anon_1.comp_ = std::ptr::null_mut();
154    } else {
155        select_compartment(cx, &mut options);
156    }
157
158    // “System or addon” principals control JIT policy (IsBaselineJitEnabled, IsIonEnabled) and WASM policy
159    // (IsSimdPrivilegedContext, HasSupport). This is unrelated to the concept of “system” compartments, though WASM
160    // HasSupport describes checking this flag as “check trusted principals”, which seems to be a mistake.
161    // Servo currently creates all principals as non-system-or-addon principals.
162    let principal = ServoJSPrincipals::new::<D>(origin);
163    if use_system_compartment {
164        // “System” compartments are those that have all “system” realms, which in turn are those that were
165        // created with the runtime’s global “trusted” principals. This influences the IsSystemCompartment() check
166        // in select_compartment() below [1], preventing compartment reuse in either direction between this global
167        // and any globals created with `use_system_compartment` set to false.
168        // [1] IsSystemCompartment() → Realm::isSystem() → Realm::isSystem_ → principals == trustedPrincipals()
169        unsafe { JS_SetTrustedPrincipals(cx, principal.as_raw()) };
170    }
171
172    rval.set(unsafe {
173        JS_NewGlobalObject(
174            cx,
175            class,
176            principal.as_raw(),
177            OnNewGlobalHookOption::DontFireOnNewGlobalHook,
178            &*options,
179        )
180    });
181    assert!(!rval.is_null());
182
183    // Initialize the reserved slots before doing anything that can GC, to
184    // avoid getting trace hooks called on a partially initialized object.
185    let private_val = PrivateValue(private);
186    unsafe { JS_SetReservedSlot(rval.get(), DOM_OBJECT_SLOT, &private_val) };
187    let proto_array: Box<ProtoOrIfaceArray> =
188        Box::new([ptr::null_mut::<JSObject>(); PrototypeList::PROTO_OR_IFACE_LENGTH]);
189    let val = PrivateValue(Box::into_raw(proto_array) as *const libc::c_void);
190    unsafe { JS_SetReservedSlot(rval.get(), DOM_PROTOTYPE_SLOT, &val) };
191
192    let mut cx = AutoRealm::new_from_handle(cx, rval.handle());
193    let cx = &mut cx;
194
195    unsafe { JS_FireOnNewGlobalObject(cx, rval.handle()) };
196}
197
198/// Choose the compartment to create a new global object in.
199fn select_compartment(cx: &mut js::context::JSContext, options: &mut RealmOptions) {
200    type Data = *mut Compartment;
201    unsafe extern "C" fn callback(
202        _cx: *mut JSContext,
203        data: *mut libc::c_void,
204        compartment: *mut Compartment,
205    ) -> CompartmentIterResult {
206        let data = data as *mut Data;
207
208        unsafe {
209            if !IsSharableCompartment(compartment) || IsSystemCompartment(compartment) {
210                return CompartmentIterResult::KeepGoing;
211            }
212
213            // Choose any sharable, non-system compartment in this context to allow
214            // same-agent documents to share JS and DOM objects.
215            *data = compartment;
216        }
217
218        CompartmentIterResult::Stop
219    }
220
221    let mut compartment: Data = ptr::null_mut();
222    unsafe {
223        JS_IterateCompartments(
224            cx,
225            (&mut compartment) as *mut Data as *mut libc::c_void,
226            Some(callback),
227        );
228    }
229
230    if compartment.is_null() {
231        options.creationOptions_.compSpec_ = CompartmentSpecifier::NewCompartmentAndZone;
232    } else {
233        options.creationOptions_.compSpec_ = CompartmentSpecifier::ExistingCompartment;
234        options.creationOptions_.__bindgen_anon_1.comp_ = compartment;
235    }
236}
237
238/// Create and define the interface object of a callback interface.
239pub(crate) fn create_callback_interface_object<D: DomTypes>(
240    cx: &mut js::context::JSContext,
241    global: HandleObject,
242    constants: &[Guard<&[ConstantSpec]>],
243    name: &CStr,
244    mut rval: MutableHandleObject,
245) {
246    assert!(!constants.is_empty());
247    rval.set(JS_NewObject(cx, None));
248    assert!(!rval.is_null());
249    define_guarded_constants::<D>(cx, rval.handle(), constants, global);
250    define_name(cx, rval.handle(), name);
251    define_on_global_object(cx, global, name, rval.handle());
252}
253
254/// Create the interface prototype object of a non-callback interface.
255#[expect(clippy::too_many_arguments)]
256pub(crate) fn create_interface_prototype_object<D: DomTypes>(
257    cx: &mut js::context::JSContext,
258    global: HandleObject,
259    proto: HandleObject,
260    class: &'static JSClass,
261    regular_methods: &[Guard<&'static [JSFunctionSpec]>],
262    regular_properties: &[Guard<&'static [JSPropertySpec]>],
263    constants: &[Guard<&[ConstantSpec]>],
264    unscopable_names: &[&CStr],
265    mut rval: MutableHandleObject,
266) {
267    create_object::<D>(
268        cx,
269        global,
270        proto,
271        class,
272        regular_methods,
273        regular_properties,
274        constants,
275        rval.reborrow(),
276    );
277
278    if !unscopable_names.is_empty() {
279        rooted!(&in(cx) let mut unscopable_obj = ptr::null_mut::<JSObject>());
280        create_unscopable_object(cx, unscopable_names, unscopable_obj.handle_mut());
281        unsafe {
282            let unscopable_symbol = GetWellKnownSymbol(cx, SymbolCode::unscopables);
283            assert!(!unscopable_symbol.is_null());
284
285            rooted!(&in(cx) let mut unscopable_id: jsid);
286            RUST_SYMBOL_TO_JSID(unscopable_symbol, unscopable_id.handle_mut());
287
288            assert!(JS_DefinePropertyById5(
289                cx,
290                rval.handle(),
291                unscopable_id.handle(),
292                unscopable_obj.handle(),
293                JSPROP_READONLY as u32
294            ))
295        }
296    }
297}
298
299/// Create and define the interface object of a non-callback interface.
300#[expect(clippy::too_many_arguments)]
301pub(crate) fn create_noncallback_interface_object<D: DomTypes>(
302    cx: &mut js::context::JSContext,
303    global: HandleObject,
304    proto: HandleObject,
305    class: &'static NonCallbackInterfaceObjectClass,
306    static_methods: &[Guard<&'static [JSFunctionSpec]>],
307    static_properties: &[Guard<&'static [JSPropertySpec]>],
308    constants: &[Guard<&[ConstantSpec]>],
309    interface_prototype_object: HandleObject,
310    name: &CStr,
311    length: u32,
312    legacy_window_alias_names: &[&CStr],
313    mut rval: MutableHandleObject,
314) {
315    create_object::<D>(
316        cx,
317        global,
318        proto,
319        class.as_jsclass(),
320        static_methods,
321        static_properties,
322        constants,
323        rval.reborrow(),
324    );
325    unsafe {
326        assert!(JS_LinkConstructorAndPrototype(
327            cx,
328            rval.handle(),
329            interface_prototype_object
330        ));
331    }
332    define_name(cx, rval.handle(), name);
333    define_length(cx, rval.handle(), i32::try_from(length).expect("overflow"));
334    define_on_global_object(cx, global, name, rval.handle());
335
336    if is_exposed_in(global, Globals::WINDOW) {
337        for legacy_window_alias in legacy_window_alias_names {
338            define_on_global_object(cx, global, legacy_window_alias, rval.handle());
339        }
340    }
341}
342
343/// Create and define the named constructors of a non-callback interface.
344pub(crate) fn create_named_constructors(
345    cx: &mut js::context::JSContext,
346    global: HandleObject,
347    named_constructors: &[(ConstructorClassHook, &CStr, u32)],
348    interface_prototype_object: HandleObject,
349) {
350    rooted!(&in(cx) let mut constructor = ptr::null_mut::<JSObject>());
351
352    for &(native, name, arity) in named_constructors {
353        unsafe {
354            let fun = JS_NewFunction(cx, Some(native), arity, JSFUN_CONSTRUCTOR, name.as_ptr());
355            assert!(!fun.is_null());
356            constructor.set(JS_GetFunctionObject(fun));
357            assert!(!constructor.is_null());
358
359            assert!(JS_DefineProperty3(
360                cx,
361                constructor.handle(),
362                c"prototype".as_ptr(),
363                interface_prototype_object,
364                (JSPROP_PERMANENT | JSPROP_READONLY) as u32
365            ));
366        }
367
368        define_on_global_object(cx, global, name, constructor.handle());
369    }
370}
371
372/// Create a new object with a unique type.
373#[expect(clippy::too_many_arguments)]
374pub(crate) fn create_object<D: DomTypes>(
375    cx: &mut js::context::JSContext,
376    global: HandleObject,
377    proto: HandleObject,
378    class: &'static JSClass,
379    methods: &[Guard<&'static [JSFunctionSpec]>],
380    properties: &[Guard<&'static [JSPropertySpec]>],
381    constants: &[Guard<&[ConstantSpec]>],
382    mut rval: MutableHandleObject,
383) {
384    rval.set(JS_NewObjectWithGivenProto(cx, Some(class), proto));
385    assert!(!rval.is_null());
386    define_guarded_methods::<D>(cx, rval.handle(), methods, global);
387    define_guarded_properties::<D>(cx, rval.handle(), properties, global);
388    define_guarded_constants::<D>(cx, rval.handle(), constants, global);
389}
390
391/// Conditionally define constants on an object.
392pub(crate) fn define_guarded_constants<D: DomTypes>(
393    cx: &mut js::context::JSContext,
394    obj: HandleObject,
395    constants: &[Guard<&[ConstantSpec]>],
396    global: HandleObject,
397) {
398    for guard in constants {
399        if let Some(specs) = guard.expose::<D>(cx, obj, global) {
400            define_constants(cx, obj, specs);
401        }
402    }
403}
404
405/// Conditionally define methods on an object.
406pub(crate) fn define_guarded_methods<D: DomTypes>(
407    cx: &mut js::context::JSContext,
408    obj: HandleObject,
409    methods: &[Guard<&'static [JSFunctionSpec]>],
410    global: HandleObject,
411) {
412    for guard in methods {
413        if let Some(specs) = guard.expose::<D>(cx, obj, global) {
414            unsafe {
415                define_methods(cx, obj, specs).unwrap();
416            }
417        }
418    }
419}
420
421/// Conditionally define properties on an object.
422pub(crate) fn define_guarded_properties<D: DomTypes>(
423    cx: &mut js::context::JSContext,
424    obj: HandleObject,
425    properties: &[Guard<&'static [JSPropertySpec]>],
426    global: HandleObject,
427) {
428    for guard in properties {
429        if let Some(specs) = guard.expose::<D>(cx, obj, global) {
430            unsafe {
431                define_properties(cx, obj, specs).unwrap();
432            }
433        }
434    }
435}
436
437/// Returns whether an interface with exposure set given by `globals` should
438/// be exposed in the global object `obj`.
439pub(crate) fn is_exposed_in(object: HandleObject, globals: Globals) -> bool {
440    unsafe {
441        let unwrapped = UncheckedUnwrapObject(object.get(), /* stopAtWindowProxy = */ false);
442        let dom_class = get_dom_class(unwrapped).unwrap();
443        globals.contains(dom_class.global)
444    }
445}
446
447/// Define a property with a given name on the global object. Should be called
448/// through the resolve hook.
449pub(crate) fn define_on_global_object(
450    cx: &mut js::context::JSContext,
451    global: HandleObject,
452    name: &CStr,
453    obj: HandleObject,
454) {
455    unsafe {
456        assert!(JS_DefineProperty3(
457            cx,
458            global,
459            name.as_ptr(),
460            obj,
461            JSPROP_RESOLVING
462        ));
463    }
464}
465
466const OBJECT_OPS: ObjectOps = ObjectOps {
467    lookupProperty: None,
468    defineProperty: None,
469    hasProperty: None,
470    getProperty: None,
471    setProperty: None,
472    getOwnPropertyDescriptor: None,
473    deleteProperty: None,
474    getElements: None,
475    funToString: Some(fun_to_string_hook),
476};
477
478unsafe extern "C" fn fun_to_string_hook(
479    cx: *mut JSContext,
480    obj: RawHandleObject,
481    _is_to_source: bool,
482) -> *mut JSString {
483    unsafe {
484        let js_class = get_object_class(obj.get());
485        assert!(!js_class.is_null());
486        let repr = (*(js_class as *const NonCallbackInterfaceObjectClass)).representation;
487        assert!(!repr.is_empty());
488        let ret = JS_NewStringCopyN(cx, repr.as_ptr() as *const libc::c_char, repr.len());
489        assert!(!ret.is_null());
490        ret
491    }
492}
493
494fn create_unscopable_object(
495    cx: &mut js::context::JSContext,
496    names: &[&CStr],
497    mut rval: MutableHandleObject,
498) {
499    assert!(!names.is_empty());
500    assert!(rval.is_null());
501    unsafe {
502        rval.set(JS_NewObjectWithGivenProto(cx, None, HandleObject::null()));
503        assert!(!rval.is_null());
504        for &name in names {
505            assert!(JS_DefineProperty(
506                cx,
507                rval.handle(),
508                name,
509                HandleValue::from_raw(TrueHandleValue),
510                PropertyFlags::ENUMERABLE | PropertyFlags::WRITABLE | PropertyFlags::CONFIGURABLE,
511            ));
512        }
513    }
514}
515
516fn define_name(cx: &mut js::context::JSContext, obj: HandleObject, name: &CStr) {
517    unsafe {
518        rooted!(&in(cx) let name = JS_AtomizeAndPinString(cx, name.as_ptr()));
519        assert!(!name.is_null());
520        assert!(JS_DefineProperty4(
521            cx,
522            obj,
523            c"name".as_ptr(),
524            name.handle(),
525            JSPROP_READONLY as u32
526        ));
527    }
528}
529
530fn define_length(cx: &mut js::context::JSContext, obj: HandleObject, length: i32) {
531    unsafe {
532        assert!(JS_DefineProperty5(
533            cx,
534            obj,
535            c"length".as_ptr(),
536            length,
537            JSPROP_READONLY as u32
538        ));
539    }
540}
541
542unsafe extern "C" fn invalid_constructor(
543    cx: *mut JSContext,
544    _argc: libc::c_uint,
545    _vp: *mut JSVal,
546) -> bool {
547    // SAFETY: it is safe to construct a JSContext from engine hook.
548    let mut cx = unsafe { js::context::JSContext::from_ptr(NonNull::new(cx).unwrap()) };
549    throw_type_error(&mut cx, c"Illegal constructor.");
550    false
551}
552
553unsafe extern "C" fn non_new_constructor(
554    cx: *mut JSContext,
555    _argc: libc::c_uint,
556    _vp: *mut JSVal,
557) -> bool {
558    // SAFETY: it is safe to construct a JSContext from engine hook.
559    let mut cx = unsafe { js::context::JSContext::from_ptr(NonNull::new(cx).unwrap()) };
560    throw_type_error(&mut cx, c"This constructor needs to be called with `new`.");
561    false
562}
563
564pub(crate) enum ProtoOrIfaceIndex {
565    ID(PrototypeList::ID),
566    Constructor(PrototypeList::Constructor),
567}
568
569impl From<ProtoOrIfaceIndex> for usize {
570    fn from(index: ProtoOrIfaceIndex) -> usize {
571        match index {
572            ProtoOrIfaceIndex::ID(id) => id as usize,
573            ProtoOrIfaceIndex::Constructor(constructor) => constructor as usize,
574        }
575    }
576}
577
578pub(crate) fn get_per_interface_object_handle(
579    cx: &mut js::context::JSContext,
580    global: HandleObject,
581    id: ProtoOrIfaceIndex,
582    creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
583    mut rval: MutableHandleObject,
584) {
585    unsafe {
586        assert!(((*get_object_class(global.get())).flags & JSCLASS_DOM_GLOBAL) != 0);
587
588        /* Check to see whether the interface objects are already installed */
589        let proto_or_iface_array = get_proto_or_iface_array(global.get());
590        let index: usize = id.into();
591        rval.set((*proto_or_iface_array)[index]);
592        if !rval.get().is_null() {
593            return;
594        }
595
596        creator(cx, global, proto_or_iface_array);
597        rval.set((*proto_or_iface_array)[index]);
598        assert!(!rval.get().is_null());
599    }
600}
601
602pub(crate) fn define_dom_interface(
603    cx: &mut js::context::JSContext,
604    global: HandleObject,
605    id: ProtoOrIfaceIndex,
606    creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
607    enabled: fn(&mut js::context::JSContext, HandleObject) -> bool,
608) {
609    assert!(!global.get().is_null());
610
611    if !enabled(cx, global) {
612        return;
613    }
614
615    rooted!(&in(cx) let mut proto = ptr::null_mut::<JSObject>());
616    get_per_interface_object_handle(cx, global, id, creator, proto.handle_mut());
617    assert!(!proto.is_null());
618}
619
620fn get_proto_id_for_new_target(new_target: HandleObject) -> Option<PrototypeList::ID> {
621    unsafe {
622        let new_target_class = get_object_class(*new_target);
623        if is_dom_class(&*new_target_class) {
624            let domjsclass: *const DOMJSClass = new_target_class as *const DOMJSClass;
625            let dom_class = &(*domjsclass).dom_class;
626            return Some(dom_class.interface_chain[dom_class.depth as usize]);
627        }
628        None
629    }
630}
631
632#[allow(clippy::result_unit_err)]
633pub fn get_desired_proto(
634    cx: &mut js::context::JSContext,
635    args: &CallArgs,
636    proto_id: PrototypeList::ID,
637    creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
638    mut desired_proto: MutableHandleObject,
639) -> Result<(), ()> {
640    unsafe {
641        // This basically implements
642        // https://heycam.github.io/webidl/#internally-create-a-new-object-implementing-the-interface
643        // step 3.
644
645        assert!(args.is_constructing());
646
647        // The desired prototype depends on the actual constructor that was invoked,
648        // which is passed to us as the newTarget in the callargs.  We want to do
649        // something akin to the ES6 specification's GetProtototypeFromConstructor (so
650        // get .prototype on the newTarget, with a fallback to some sort of default).
651
652        // First, a fast path for the case when the constructor is in fact one of
653        // our DOM constructors.  This is safe because on those the "constructor"
654        // property is non-configurable and non-writable, so we don't have to do the
655        // slow JS_GetProperty call.
656        rooted!(&in(cx) let mut new_target = args.new_target().to_object());
657        rooted!(&in(cx) let original_new_target = *new_target);
658        // See whether we have a known DOM constructor here, such that we can take a
659        // fast path.
660        let target_proto_id = get_proto_id_for_new_target(new_target.handle()).or_else(|| {
661            // We might still have a cross-compartment wrapper for a known DOM
662            // constructor.  CheckedUnwrapStatic is fine here, because we're looking for
663            // DOM constructors and those can't be cross-origin objects.
664            new_target.set(CheckedUnwrapStatic(*new_target));
665            if !new_target.is_null() && *new_target != *original_new_target {
666                get_proto_id_for_new_target(new_target.handle())
667            } else {
668                None
669            }
670        });
671
672        if let Some(proto_id) = target_proto_id {
673            let global = GetNonCCWObjectGlobal(*new_target);
674            let proto_or_iface_cache = get_proto_or_iface_array(global);
675            desired_proto.set((*proto_or_iface_cache)[proto_id as usize]);
676            if *new_target != *original_new_target &&
677                !js::rust::wrappers2::JS_WrapObject(cx, desired_proto)
678            {
679                return Err(());
680            }
681            return Ok(());
682        }
683
684        // Slow path.  This basically duplicates the ES6 spec's
685        // GetPrototypeFromConstructor except that instead of taking a string naming
686        // the fallback prototype we determine the fallback based on the proto id we
687        // were handed.
688        rooted!(&in(cx) let mut proto_val = NullValue());
689        if !crate::js::JS_GetProperty(
690            cx,
691            original_new_target.handle(),
692            c"prototype",
693            proto_val.handle_mut(),
694        ) {
695            return Err(());
696        }
697
698        if proto_val.is_object() {
699            desired_proto.set(proto_val.to_object());
700            return Ok(());
701        }
702
703        // Fall back to getting the proto for our given proto id in the realm that
704        // GetFunctionRealm(newTarget) returns.
705        let realm = js::rust::wrappers2::GetFunctionRealm(cx, new_target.handle());
706
707        if realm.is_null() {
708            return Err(());
709        }
710
711        {
712            let mut realm = AutoRealm::new(cx, NonNull::new(GetRealmGlobalOrNull(realm)).unwrap());
713            let (global, realm) = realm.global_and_reborrow();
714            get_per_interface_object_handle(
715                realm,
716                global,
717                ProtoOrIfaceIndex::ID(proto_id),
718                creator,
719                desired_proto.reborrow(),
720            );
721            if desired_proto.is_null() {
722                return Err(());
723            }
724        }
725
726        maybe_wrap_object(cx, desired_proto);
727        Ok(())
728    }
729}