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