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