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_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_NewObjectWithGivenProto,
28    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::js::{JS_DefineProperty, 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    unsafe {
248        rval.set(JS_NewObject(cx.raw_cx(), ptr::null()));
249    }
250    assert!(!rval.is_null());
251    define_guarded_constants::<D>(cx, rval.handle(), constants, global);
252    define_name(cx, rval.handle(), name);
253    define_on_global_object(cx, global, name, rval.handle());
254}
255
256/// Create the interface prototype object of a non-callback interface.
257#[expect(clippy::too_many_arguments)]
258pub(crate) fn create_interface_prototype_object<D: DomTypes>(
259    cx: &mut js::context::JSContext,
260    global: HandleObject,
261    proto: HandleObject,
262    class: &'static JSClass,
263    regular_methods: &[Guard<&'static [JSFunctionSpec]>],
264    regular_properties: &[Guard<&'static [JSPropertySpec]>],
265    constants: &[Guard<&[ConstantSpec]>],
266    unscopable_names: &[&CStr],
267    mut rval: MutableHandleObject,
268) {
269    create_object::<D>(
270        cx,
271        global,
272        proto,
273        class,
274        regular_methods,
275        regular_properties,
276        constants,
277        rval.reborrow(),
278    );
279
280    if !unscopable_names.is_empty() {
281        rooted!(&in(cx) let mut unscopable_obj = ptr::null_mut::<JSObject>());
282        create_unscopable_object(cx, unscopable_names, unscopable_obj.handle_mut());
283        unsafe {
284            let unscopable_symbol = GetWellKnownSymbol(cx, SymbolCode::unscopables);
285            assert!(!unscopable_symbol.is_null());
286
287            rooted!(&in(cx) let mut unscopable_id: jsid);
288            RUST_SYMBOL_TO_JSID(unscopable_symbol, unscopable_id.handle_mut());
289
290            assert!(JS_DefinePropertyById5(
291                cx,
292                rval.handle(),
293                unscopable_id.handle(),
294                unscopable_obj.handle(),
295                JSPROP_READONLY as u32
296            ))
297        }
298    }
299}
300
301/// Create and define the interface object of a non-callback interface.
302#[expect(clippy::too_many_arguments)]
303pub(crate) fn create_noncallback_interface_object<D: DomTypes>(
304    cx: &mut js::context::JSContext,
305    global: HandleObject,
306    proto: HandleObject,
307    class: &'static NonCallbackInterfaceObjectClass,
308    static_methods: &[Guard<&'static [JSFunctionSpec]>],
309    static_properties: &[Guard<&'static [JSPropertySpec]>],
310    constants: &[Guard<&[ConstantSpec]>],
311    interface_prototype_object: HandleObject,
312    name: &CStr,
313    length: u32,
314    legacy_window_alias_names: &[&CStr],
315    mut rval: MutableHandleObject,
316) {
317    create_object::<D>(
318        cx,
319        global,
320        proto,
321        class.as_jsclass(),
322        static_methods,
323        static_properties,
324        constants,
325        rval.reborrow(),
326    );
327    unsafe {
328        assert!(JS_LinkConstructorAndPrototype(
329            cx,
330            rval.handle(),
331            interface_prototype_object
332        ));
333    }
334    define_name(cx, rval.handle(), name);
335    define_length(cx, rval.handle(), i32::try_from(length).expect("overflow"));
336    define_on_global_object(cx, global, name, rval.handle());
337
338    if is_exposed_in(global, Globals::WINDOW) {
339        for legacy_window_alias in legacy_window_alias_names {
340            define_on_global_object(cx, global, legacy_window_alias, rval.handle());
341        }
342    }
343}
344
345/// Create and define the named constructors of a non-callback interface.
346pub(crate) fn create_named_constructors(
347    cx: &mut js::context::JSContext,
348    global: HandleObject,
349    named_constructors: &[(ConstructorClassHook, &CStr, u32)],
350    interface_prototype_object: HandleObject,
351) {
352    rooted!(&in(cx) let mut constructor = ptr::null_mut::<JSObject>());
353
354    for &(native, name, arity) in named_constructors {
355        unsafe {
356            let fun = JS_NewFunction(cx, Some(native), arity, JSFUN_CONSTRUCTOR, name.as_ptr());
357            assert!(!fun.is_null());
358            constructor.set(JS_GetFunctionObject(fun));
359            assert!(!constructor.is_null());
360
361            assert!(JS_DefineProperty3(
362                cx,
363                constructor.handle(),
364                c"prototype".as_ptr(),
365                interface_prototype_object,
366                (JSPROP_PERMANENT | JSPROP_READONLY) as u32
367            ));
368        }
369
370        define_on_global_object(cx, global, name, constructor.handle());
371    }
372}
373
374/// Create a new object with a unique type.
375#[expect(clippy::too_many_arguments)]
376pub(crate) fn create_object<D: DomTypes>(
377    cx: &mut js::context::JSContext,
378    global: HandleObject,
379    proto: HandleObject,
380    class: &'static JSClass,
381    methods: &[Guard<&'static [JSFunctionSpec]>],
382    properties: &[Guard<&'static [JSPropertySpec]>],
383    constants: &[Guard<&[ConstantSpec]>],
384    mut rval: MutableHandleObject,
385) {
386    unsafe {
387        rval.set(JS_NewObjectWithGivenProto(cx, class, proto));
388    }
389    assert!(!rval.is_null());
390    define_guarded_methods::<D>(cx, rval.handle(), methods, global);
391    define_guarded_properties::<D>(cx, rval.handle(), properties, global);
392    define_guarded_constants::<D>(cx, rval.handle(), constants, global);
393}
394
395/// Conditionally define constants on an object.
396pub(crate) fn define_guarded_constants<D: DomTypes>(
397    cx: &mut js::context::JSContext,
398    obj: HandleObject,
399    constants: &[Guard<&[ConstantSpec]>],
400    global: HandleObject,
401) {
402    for guard in constants {
403        if let Some(specs) = guard.expose::<D>(cx, obj, global) {
404            define_constants(cx, obj, specs);
405        }
406    }
407}
408
409/// Conditionally define methods on an object.
410pub(crate) fn define_guarded_methods<D: DomTypes>(
411    cx: &mut js::context::JSContext,
412    obj: HandleObject,
413    methods: &[Guard<&'static [JSFunctionSpec]>],
414    global: HandleObject,
415) {
416    for guard in methods {
417        if let Some(specs) = guard.expose::<D>(cx, obj, global) {
418            unsafe {
419                define_methods(cx, obj, specs).unwrap();
420            }
421        }
422    }
423}
424
425/// Conditionally define properties on an object.
426pub(crate) fn define_guarded_properties<D: DomTypes>(
427    cx: &mut js::context::JSContext,
428    obj: HandleObject,
429    properties: &[Guard<&'static [JSPropertySpec]>],
430    global: HandleObject,
431) {
432    for guard in properties {
433        if let Some(specs) = guard.expose::<D>(cx, obj, global) {
434            unsafe {
435                define_properties(cx, obj, specs).unwrap();
436            }
437        }
438    }
439}
440
441/// Returns whether an interface with exposure set given by `globals` should
442/// be exposed in the global object `obj`.
443pub(crate) fn is_exposed_in(object: HandleObject, globals: Globals) -> bool {
444    unsafe {
445        let unwrapped = UncheckedUnwrapObject(object.get(), /* stopAtWindowProxy = */ false);
446        let dom_class = get_dom_class(unwrapped).unwrap();
447        globals.contains(dom_class.global)
448    }
449}
450
451/// Define a property with a given name on the global object. Should be called
452/// through the resolve hook.
453pub(crate) fn define_on_global_object(
454    cx: &mut js::context::JSContext,
455    global: HandleObject,
456    name: &CStr,
457    obj: HandleObject,
458) {
459    unsafe {
460        assert!(JS_DefineProperty3(
461            cx,
462            global,
463            name.as_ptr(),
464            obj,
465            JSPROP_RESOLVING
466        ));
467    }
468}
469
470const OBJECT_OPS: ObjectOps = ObjectOps {
471    lookupProperty: None,
472    defineProperty: None,
473    hasProperty: None,
474    getProperty: None,
475    setProperty: None,
476    getOwnPropertyDescriptor: None,
477    deleteProperty: None,
478    getElements: None,
479    funToString: Some(fun_to_string_hook),
480};
481
482unsafe extern "C" fn fun_to_string_hook(
483    cx: *mut JSContext,
484    obj: RawHandleObject,
485    _is_to_source: bool,
486) -> *mut JSString {
487    unsafe {
488        let js_class = get_object_class(obj.get());
489        assert!(!js_class.is_null());
490        let repr = (*(js_class as *const NonCallbackInterfaceObjectClass)).representation;
491        assert!(!repr.is_empty());
492        let ret = JS_NewStringCopyN(cx, repr.as_ptr() as *const libc::c_char, repr.len());
493        assert!(!ret.is_null());
494        ret
495    }
496}
497
498fn create_unscopable_object(
499    cx: &mut js::context::JSContext,
500    names: &[&CStr],
501    mut rval: MutableHandleObject,
502) {
503    assert!(!names.is_empty());
504    assert!(rval.is_null());
505    unsafe {
506        rval.set(JS_NewObjectWithGivenProto(
507            cx,
508            ptr::null(),
509            HandleObject::null(),
510        ));
511        assert!(!rval.is_null());
512        for &name in names {
513            assert!(JS_DefineProperty(
514                cx,
515                rval.handle(),
516                name,
517                HandleValue::from_raw(TrueHandleValue),
518                PropertyFlags::ENUMERABLE | PropertyFlags::WRITABLE | PropertyFlags::CONFIGURABLE,
519            ));
520        }
521    }
522}
523
524fn define_name(cx: &mut js::context::JSContext, obj: HandleObject, name: &CStr) {
525    unsafe {
526        rooted!(&in(cx) let name = JS_AtomizeAndPinString(cx, name.as_ptr()));
527        assert!(!name.is_null());
528        assert!(JS_DefineProperty4(
529            cx,
530            obj,
531            c"name".as_ptr(),
532            name.handle(),
533            JSPROP_READONLY as u32
534        ));
535    }
536}
537
538fn define_length(cx: &mut js::context::JSContext, obj: HandleObject, length: i32) {
539    unsafe {
540        assert!(JS_DefineProperty5(
541            cx,
542            obj,
543            c"length".as_ptr(),
544            length,
545            JSPROP_READONLY as u32
546        ));
547    }
548}
549
550unsafe extern "C" fn invalid_constructor(
551    cx: *mut JSContext,
552    _argc: libc::c_uint,
553    _vp: *mut JSVal,
554) -> bool {
555    // SAFETY: it is safe to construct a JSContext from engine hook.
556    let mut cx = unsafe { js::context::JSContext::from_ptr(NonNull::new(cx).unwrap()) };
557    throw_type_error(&mut cx, c"Illegal constructor.");
558    false
559}
560
561unsafe extern "C" fn non_new_constructor(
562    cx: *mut JSContext,
563    _argc: libc::c_uint,
564    _vp: *mut JSVal,
565) -> bool {
566    // SAFETY: it is safe to construct a JSContext from engine hook.
567    let mut cx = unsafe { js::context::JSContext::from_ptr(NonNull::new(cx).unwrap()) };
568    throw_type_error(&mut cx, c"This constructor needs to be called with `new`.");
569    false
570}
571
572pub(crate) enum ProtoOrIfaceIndex {
573    ID(PrototypeList::ID),
574    Constructor(PrototypeList::Constructor),
575}
576
577impl From<ProtoOrIfaceIndex> for usize {
578    fn from(index: ProtoOrIfaceIndex) -> usize {
579        match index {
580            ProtoOrIfaceIndex::ID(id) => id as usize,
581            ProtoOrIfaceIndex::Constructor(constructor) => constructor as usize,
582        }
583    }
584}
585
586pub(crate) fn get_per_interface_object_handle(
587    cx: &mut js::context::JSContext,
588    global: HandleObject,
589    id: ProtoOrIfaceIndex,
590    creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
591    mut rval: MutableHandleObject,
592) {
593    unsafe {
594        assert!(((*get_object_class(global.get())).flags & JSCLASS_DOM_GLOBAL) != 0);
595
596        /* Check to see whether the interface objects are already installed */
597        let proto_or_iface_array = get_proto_or_iface_array(global.get());
598        let index: usize = id.into();
599        rval.set((*proto_or_iface_array)[index]);
600        if !rval.get().is_null() {
601            return;
602        }
603
604        creator(cx, global, proto_or_iface_array);
605        rval.set((*proto_or_iface_array)[index]);
606        assert!(!rval.get().is_null());
607    }
608}
609
610pub(crate) fn define_dom_interface(
611    cx: &mut js::context::JSContext,
612    global: HandleObject,
613    id: ProtoOrIfaceIndex,
614    creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
615    enabled: fn(&mut js::context::JSContext, HandleObject) -> bool,
616) {
617    assert!(!global.get().is_null());
618
619    if !enabled(cx, global) {
620        return;
621    }
622
623    rooted!(&in(cx) let mut proto = ptr::null_mut::<JSObject>());
624    get_per_interface_object_handle(cx, global, id, creator, proto.handle_mut());
625    assert!(!proto.is_null());
626}
627
628fn get_proto_id_for_new_target(new_target: HandleObject) -> Option<PrototypeList::ID> {
629    unsafe {
630        let new_target_class = get_object_class(*new_target);
631        if is_dom_class(&*new_target_class) {
632            let domjsclass: *const DOMJSClass = new_target_class as *const DOMJSClass;
633            let dom_class = &(*domjsclass).dom_class;
634            return Some(dom_class.interface_chain[dom_class.depth as usize]);
635        }
636        None
637    }
638}
639
640#[allow(clippy::result_unit_err)]
641pub fn get_desired_proto(
642    cx: &mut js::context::JSContext,
643    args: &CallArgs,
644    proto_id: PrototypeList::ID,
645    creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
646    mut desired_proto: MutableHandleObject,
647) -> Result<(), ()> {
648    unsafe {
649        // This basically implements
650        // https://heycam.github.io/webidl/#internally-create-a-new-object-implementing-the-interface
651        // step 3.
652
653        assert!(args.is_constructing());
654
655        // The desired prototype depends on the actual constructor that was invoked,
656        // which is passed to us as the newTarget in the callargs.  We want to do
657        // something akin to the ES6 specification's GetProtototypeFromConstructor (so
658        // get .prototype on the newTarget, with a fallback to some sort of default).
659
660        // First, a fast path for the case when the constructor is in fact one of
661        // our DOM constructors.  This is safe because on those the "constructor"
662        // property is non-configurable and non-writable, so we don't have to do the
663        // slow JS_GetProperty call.
664        rooted!(&in(cx) let mut new_target = args.new_target().to_object());
665        rooted!(&in(cx) let original_new_target = *new_target);
666        // See whether we have a known DOM constructor here, such that we can take a
667        // fast path.
668        let target_proto_id = get_proto_id_for_new_target(new_target.handle()).or_else(|| {
669            // We might still have a cross-compartment wrapper for a known DOM
670            // constructor.  CheckedUnwrapStatic is fine here, because we're looking for
671            // DOM constructors and those can't be cross-origin objects.
672            new_target.set(CheckedUnwrapStatic(*new_target));
673            if !new_target.is_null() && *new_target != *original_new_target {
674                get_proto_id_for_new_target(new_target.handle())
675            } else {
676                None
677            }
678        });
679
680        if let Some(proto_id) = target_proto_id {
681            let global = GetNonCCWObjectGlobal(*new_target);
682            let proto_or_iface_cache = get_proto_or_iface_array(global);
683            desired_proto.set((*proto_or_iface_cache)[proto_id as usize]);
684            if *new_target != *original_new_target &&
685                !js::rust::wrappers2::JS_WrapObject(cx, desired_proto)
686            {
687                return Err(());
688            }
689            return Ok(());
690        }
691
692        // Slow path.  This basically duplicates the ES6 spec's
693        // GetPrototypeFromConstructor except that instead of taking a string naming
694        // the fallback prototype we determine the fallback based on the proto id we
695        // were handed.
696        rooted!(&in(cx) let mut proto_val = NullValue());
697        if !js::rust::wrappers2::JS_GetProperty(
698            cx,
699            original_new_target.handle(),
700            c"prototype".as_ptr(),
701            proto_val.handle_mut(),
702        ) {
703            return Err(());
704        }
705
706        if proto_val.is_object() {
707            desired_proto.set(proto_val.to_object());
708            return Ok(());
709        }
710
711        // Fall back to getting the proto for our given proto id in the realm that
712        // GetFunctionRealm(newTarget) returns.
713        let realm = js::rust::wrappers2::GetFunctionRealm(cx, new_target.handle());
714
715        if realm.is_null() {
716            return Err(());
717        }
718
719        {
720            let mut realm = AutoRealm::new(cx, NonNull::new(GetRealmGlobalOrNull(realm)).unwrap());
721            let (global, realm) = realm.global_and_reborrow();
722            get_per_interface_object_handle(
723                realm,
724                global,
725                ProtoOrIfaceIndex::ID(proto_id),
726                creator,
727                desired_proto.reborrow(),
728            );
729            if desired_proto.is_null() {
730                return Err(());
731            }
732        }
733
734        maybe_wrap_object(cx, desired_proto);
735        Ok(())
736    }
737}