aboutsummaryrefslogtreecommitdiffstats
path: root/components/script_bindings/interface.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script_bindings/interface.rs')
-rw-r--r--components/script_bindings/interface.rs697
1 files changed, 697 insertions, 0 deletions
diff --git a/components/script_bindings/interface.rs b/components/script_bindings/interface.rs
new file mode 100644
index 00000000000..08ee0a4f420
--- /dev/null
+++ b/components/script_bindings/interface.rs
@@ -0,0 +1,697 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Machinery to initialise interface prototype objects and interface objects.
+
+use std::convert::TryFrom;
+use std::ffi::CStr;
+use std::ptr;
+
+use js::error::throw_type_error;
+use js::glue::UncheckedUnwrapObject;
+use js::jsapi::JS::CompartmentIterResult;
+use js::jsapi::{
+ CallArgs, CheckedUnwrapStatic, Compartment, CompartmentSpecifier, CurrentGlobalOrNull,
+ GetFunctionRealm, GetNonCCWObjectGlobal, GetRealmGlobalOrNull, GetWellKnownSymbol,
+ HandleObject as RawHandleObject, IsSharableCompartment, IsSystemCompartment,
+ JS_AtomizeAndPinString, JS_GetFunctionObject, JS_GetProperty, JS_IterateCompartments,
+ JS_NewFunction, JS_NewGlobalObject, JS_NewObject, JS_NewPlainObject, JS_NewStringCopyN,
+ JS_SetReservedSlot, JS_WrapObject, JSAutoRealm, JSClass, JSClassOps, JSContext,
+ JSFUN_CONSTRUCTOR, JSFunctionSpec, JSObject, JSPROP_PERMANENT, JSPROP_READONLY,
+ JSPROP_RESOLVING, JSPropertySpec, JSString, JSTracer, ObjectOps, OnNewGlobalHookOption,
+ SymbolCode, TrueHandleValue, Value, jsid,
+};
+use js::jsval::{JSVal, NullValue, PrivateValue};
+use js::rust::wrappers::{
+ JS_DefineProperty, JS_DefineProperty3, JS_DefineProperty4, JS_DefineProperty5,
+ JS_DefinePropertyById5, JS_FireOnNewGlobalObject, JS_LinkConstructorAndPrototype,
+ JS_NewObjectWithGivenProto, RUST_SYMBOL_TO_JSID,
+};
+use js::rust::{
+ HandleObject, HandleValue, MutableHandleObject, RealmOptions, define_methods,
+ define_properties, get_object_class, is_dom_class, maybe_wrap_object,
+};
+use servo_url::MutableOrigin;
+
+use crate::DomTypes;
+use crate::codegen::Globals::Globals;
+use crate::codegen::PrototypeList;
+use crate::constant::{ConstantSpec, define_constants};
+use crate::conversions::{DOM_OBJECT_SLOT, get_dom_class};
+use crate::guard::Guard;
+use crate::principals::ServoJSPrincipals;
+use crate::script_runtime::JSContext as SafeJSContext;
+use crate::utils::{
+ DOM_PROTOTYPE_SLOT, DOMJSClass, JSCLASS_DOM_GLOBAL, ProtoOrIfaceArray, get_proto_or_iface_array,
+};
+
+/// The class of a non-callback interface object.
+#[derive(Clone, Copy)]
+pub(crate) struct NonCallbackInterfaceObjectClass {
+ /// The SpiderMonkey class structure.
+ pub(crate) _class: JSClass,
+ /// The prototype id of that interface, used in the hasInstance hook.
+ pub(crate) _proto_id: PrototypeList::ID,
+ /// The prototype depth of that interface, used in the hasInstance hook.
+ pub(crate) _proto_depth: u16,
+ /// The string representation of the object.
+ pub(crate) representation: &'static [u8],
+}
+
+unsafe impl Sync for NonCallbackInterfaceObjectClass {}
+
+impl NonCallbackInterfaceObjectClass {
+ /// Create a new `NonCallbackInterfaceObjectClass` structure.
+ pub(crate) const fn new(
+ constructor_behavior: &'static InterfaceConstructorBehavior,
+ string_rep: &'static [u8],
+ proto_id: PrototypeList::ID,
+ proto_depth: u16,
+ ) -> NonCallbackInterfaceObjectClass {
+ NonCallbackInterfaceObjectClass {
+ _class: JSClass {
+ name: c"Function".as_ptr(),
+ flags: 0,
+ cOps: &constructor_behavior.0,
+ spec: 0 as *const _,
+ ext: 0 as *const _,
+ oOps: &OBJECT_OPS,
+ },
+ _proto_id: proto_id,
+ _proto_depth: proto_depth,
+ representation: string_rep,
+ }
+ }
+
+ /// cast own reference to `JSClass` reference
+ pub(crate) fn as_jsclass(&self) -> &JSClass {
+ unsafe { &*(self as *const _ as *const JSClass) }
+ }
+}
+
+/// A constructor class hook.
+pub(crate) type ConstructorClassHook =
+ unsafe extern "C" fn(cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool;
+
+/// The constructor behavior of a non-callback interface object.
+pub(crate) struct InterfaceConstructorBehavior(JSClassOps);
+
+impl InterfaceConstructorBehavior {
+ /// An interface constructor that unconditionally throws a type error.
+ pub(crate) const fn throw() -> Self {
+ InterfaceConstructorBehavior(JSClassOps {
+ addProperty: None,
+ delProperty: None,
+ enumerate: None,
+ newEnumerate: None,
+ resolve: None,
+ mayResolve: None,
+ finalize: None,
+ call: Some(invalid_constructor),
+ construct: Some(invalid_constructor),
+ trace: None,
+ })
+ }
+
+ /// An interface constructor that calls a native Rust function.
+ pub(crate) const fn call(hook: ConstructorClassHook) -> Self {
+ InterfaceConstructorBehavior(JSClassOps {
+ addProperty: None,
+ delProperty: None,
+ enumerate: None,
+ newEnumerate: None,
+ resolve: None,
+ mayResolve: None,
+ finalize: None,
+ call: Some(non_new_constructor),
+ construct: Some(hook),
+ trace: None,
+ })
+ }
+}
+
+/// A trace hook.
+pub(crate) type TraceHook = unsafe extern "C" fn(trc: *mut JSTracer, obj: *mut JSObject);
+
+/// Create a global object with the given class.
+pub(crate) unsafe fn create_global_object<D: DomTypes>(
+ cx: SafeJSContext,
+ class: &'static JSClass,
+ private: *const libc::c_void,
+ trace: TraceHook,
+ mut rval: MutableHandleObject,
+ origin: &MutableOrigin,
+) {
+ assert!(rval.is_null());
+
+ let mut options = RealmOptions::default();
+ options.creationOptions_.traceGlobal_ = Some(trace);
+ options.creationOptions_.sharedMemoryAndAtomics_ = false;
+ select_compartment(cx, &mut options);
+
+ let principal = ServoJSPrincipals::new::<D>(origin);
+
+ rval.set(JS_NewGlobalObject(
+ *cx,
+ class,
+ principal.as_raw(),
+ OnNewGlobalHookOption::DontFireOnNewGlobalHook,
+ &*options,
+ ));
+ assert!(!rval.is_null());
+
+ // Initialize the reserved slots before doing anything that can GC, to
+ // avoid getting trace hooks called on a partially initialized object.
+ let private_val = PrivateValue(private);
+ JS_SetReservedSlot(rval.get(), DOM_OBJECT_SLOT, &private_val);
+ let proto_array: Box<ProtoOrIfaceArray> =
+ Box::new([ptr::null_mut::<JSObject>(); PrototypeList::PROTO_OR_IFACE_LENGTH]);
+ let val = PrivateValue(Box::into_raw(proto_array) as *const libc::c_void);
+ JS_SetReservedSlot(rval.get(), DOM_PROTOTYPE_SLOT, &val);
+
+ let _ac = JSAutoRealm::new(*cx, rval.get());
+ JS_FireOnNewGlobalObject(*cx, rval.handle());
+}
+
+/// Choose the compartment to create a new global object in.
+fn select_compartment(cx: SafeJSContext, options: &mut RealmOptions) {
+ type Data = *mut Compartment;
+ unsafe extern "C" fn callback(
+ _cx: *mut JSContext,
+ data: *mut libc::c_void,
+ compartment: *mut Compartment,
+ ) -> CompartmentIterResult {
+ let data = data as *mut Data;
+
+ if !IsSharableCompartment(compartment) || IsSystemCompartment(compartment) {
+ return CompartmentIterResult::KeepGoing;
+ }
+
+ // Choose any sharable, non-system compartment in this context to allow
+ // same-agent documents to share JS and DOM objects.
+ *data = compartment;
+ CompartmentIterResult::Stop
+ }
+
+ let mut compartment: Data = ptr::null_mut();
+ unsafe {
+ JS_IterateCompartments(
+ *cx,
+ (&mut compartment) as *mut Data as *mut libc::c_void,
+ Some(callback),
+ );
+ }
+
+ if compartment.is_null() {
+ options.creationOptions_.compSpec_ = CompartmentSpecifier::NewCompartmentAndZone;
+ } else {
+ options.creationOptions_.compSpec_ = CompartmentSpecifier::ExistingCompartment;
+ options.creationOptions_.__bindgen_anon_1.comp_ = compartment;
+ }
+}
+
+/// Create and define the interface object of a callback interface.
+pub(crate) fn create_callback_interface_object<D: DomTypes>(
+ cx: SafeJSContext,
+ global: HandleObject,
+ constants: &[Guard<&[ConstantSpec]>],
+ name: &CStr,
+ mut rval: MutableHandleObject,
+) {
+ assert!(!constants.is_empty());
+ unsafe {
+ rval.set(JS_NewObject(*cx, ptr::null()));
+ }
+ assert!(!rval.is_null());
+ define_guarded_constants::<D>(cx, rval.handle(), constants, global);
+ define_name(cx, rval.handle(), name);
+ define_on_global_object(cx, global, name, rval.handle());
+}
+
+/// Create the interface prototype object of a non-callback interface.
+#[allow(clippy::too_many_arguments)]
+pub(crate) fn create_interface_prototype_object<D: DomTypes>(
+ cx: SafeJSContext,
+ global: HandleObject,
+ proto: HandleObject,
+ class: &'static JSClass,
+ regular_methods: &[Guard<&'static [JSFunctionSpec]>],
+ regular_properties: &[Guard<&'static [JSPropertySpec]>],
+ constants: &[Guard<&[ConstantSpec]>],
+ unscopable_names: &[&CStr],
+ mut rval: MutableHandleObject,
+) {
+ create_object::<D>(
+ cx,
+ global,
+ proto,
+ class,
+ regular_methods,
+ regular_properties,
+ constants,
+ rval.reborrow(),
+ );
+
+ if !unscopable_names.is_empty() {
+ rooted!(in(*cx) let mut unscopable_obj = ptr::null_mut::<JSObject>());
+ create_unscopable_object(cx, unscopable_names, unscopable_obj.handle_mut());
+ unsafe {
+ let unscopable_symbol = GetWellKnownSymbol(*cx, SymbolCode::unscopables);
+ assert!(!unscopable_symbol.is_null());
+
+ rooted!(in(*cx) let mut unscopable_id: jsid);
+ RUST_SYMBOL_TO_JSID(unscopable_symbol, unscopable_id.handle_mut());
+
+ assert!(JS_DefinePropertyById5(
+ *cx,
+ rval.handle(),
+ unscopable_id.handle(),
+ unscopable_obj.handle(),
+ JSPROP_READONLY as u32
+ ))
+ }
+ }
+}
+
+/// Create and define the interface object of a non-callback interface.
+#[allow(clippy::too_many_arguments)]
+pub(crate) fn create_noncallback_interface_object<D: DomTypes>(
+ cx: SafeJSContext,
+ global: HandleObject,
+ proto: HandleObject,
+ class: &'static NonCallbackInterfaceObjectClass,
+ static_methods: &[Guard<&'static [JSFunctionSpec]>],
+ static_properties: &[Guard<&'static [JSPropertySpec]>],
+ constants: &[Guard<&[ConstantSpec]>],
+ interface_prototype_object: HandleObject,
+ name: &CStr,
+ length: u32,
+ legacy_window_alias_names: &[&CStr],
+ mut rval: MutableHandleObject,
+) {
+ create_object::<D>(
+ cx,
+ global,
+ proto,
+ class.as_jsclass(),
+ static_methods,
+ static_properties,
+ constants,
+ rval.reborrow(),
+ );
+ unsafe {
+ assert!(JS_LinkConstructorAndPrototype(
+ *cx,
+ rval.handle(),
+ interface_prototype_object
+ ));
+ }
+ define_name(cx, rval.handle(), name);
+ define_length(cx, rval.handle(), i32::try_from(length).expect("overflow"));
+ define_on_global_object(cx, global, name, rval.handle());
+
+ if is_exposed_in(global, Globals::WINDOW) {
+ for legacy_window_alias in legacy_window_alias_names {
+ define_on_global_object(cx, global, legacy_window_alias, rval.handle());
+ }
+ }
+}
+
+/// Create and define the named constructors of a non-callback interface.
+pub(crate) fn create_named_constructors(
+ cx: SafeJSContext,
+ global: HandleObject,
+ named_constructors: &[(ConstructorClassHook, &CStr, u32)],
+ interface_prototype_object: HandleObject,
+) {
+ rooted!(in(*cx) let mut constructor = ptr::null_mut::<JSObject>());
+
+ for &(native, name, arity) in named_constructors {
+ unsafe {
+ let fun = JS_NewFunction(*cx, Some(native), arity, JSFUN_CONSTRUCTOR, name.as_ptr());
+ assert!(!fun.is_null());
+ constructor.set(JS_GetFunctionObject(fun));
+ assert!(!constructor.is_null());
+
+ assert!(JS_DefineProperty3(
+ *cx,
+ constructor.handle(),
+ c"prototype".as_ptr(),
+ interface_prototype_object,
+ (JSPROP_PERMANENT | JSPROP_READONLY) as u32
+ ));
+ }
+
+ define_on_global_object(cx, global, name, constructor.handle());
+ }
+}
+
+/// Create a new object with a unique type.
+#[allow(clippy::too_many_arguments)]
+pub(crate) fn create_object<D: DomTypes>(
+ cx: SafeJSContext,
+ global: HandleObject,
+ proto: HandleObject,
+ class: &'static JSClass,
+ methods: &[Guard<&'static [JSFunctionSpec]>],
+ properties: &[Guard<&'static [JSPropertySpec]>],
+ constants: &[Guard<&[ConstantSpec]>],
+ mut rval: MutableHandleObject,
+) {
+ unsafe {
+ rval.set(JS_NewObjectWithGivenProto(*cx, class, proto));
+ }
+ assert!(!rval.is_null());
+ define_guarded_methods::<D>(cx, rval.handle(), methods, global);
+ define_guarded_properties::<D>(cx, rval.handle(), properties, global);
+ define_guarded_constants::<D>(cx, rval.handle(), constants, global);
+}
+
+/// Conditionally define constants on an object.
+pub(crate) fn define_guarded_constants<D: DomTypes>(
+ cx: SafeJSContext,
+ obj: HandleObject,
+ constants: &[Guard<&[ConstantSpec]>],
+ global: HandleObject,
+) {
+ for guard in constants {
+ if let Some(specs) = guard.expose::<D>(cx, obj, global) {
+ define_constants(cx, obj, specs);
+ }
+ }
+}
+
+/// Conditionally define methods on an object.
+pub(crate) fn define_guarded_methods<D: DomTypes>(
+ cx: SafeJSContext,
+ obj: HandleObject,
+ methods: &[Guard<&'static [JSFunctionSpec]>],
+ global: HandleObject,
+) {
+ for guard in methods {
+ if let Some(specs) = guard.expose::<D>(cx, obj, global) {
+ unsafe {
+ define_methods(*cx, obj, specs).unwrap();
+ }
+ }
+ }
+}
+
+/// Conditionally define properties on an object.
+pub(crate) fn define_guarded_properties<D: DomTypes>(
+ cx: SafeJSContext,
+ obj: HandleObject,
+ properties: &[Guard<&'static [JSPropertySpec]>],
+ global: HandleObject,
+) {
+ for guard in properties {
+ if let Some(specs) = guard.expose::<D>(cx, obj, global) {
+ unsafe {
+ define_properties(*cx, obj, specs).unwrap();
+ }
+ }
+ }
+}
+
+/// Returns whether an interface with exposure set given by `globals` should
+/// be exposed in the global object `obj`.
+pub(crate) fn is_exposed_in(object: HandleObject, globals: Globals) -> bool {
+ unsafe {
+ let unwrapped = UncheckedUnwrapObject(object.get(), /* stopAtWindowProxy = */ false);
+ let dom_class = get_dom_class(unwrapped).unwrap();
+ globals.contains(dom_class.global)
+ }
+}
+
+/// Define a property with a given name on the global object. Should be called
+/// through the resolve hook.
+pub(crate) fn define_on_global_object(
+ cx: SafeJSContext,
+ global: HandleObject,
+ name: &CStr,
+ obj: HandleObject,
+) {
+ unsafe {
+ assert!(JS_DefineProperty3(
+ *cx,
+ global,
+ name.as_ptr(),
+ obj,
+ JSPROP_RESOLVING
+ ));
+ }
+}
+
+const OBJECT_OPS: ObjectOps = ObjectOps {
+ lookupProperty: None,
+ defineProperty: None,
+ hasProperty: None,
+ getProperty: None,
+ setProperty: None,
+ getOwnPropertyDescriptor: None,
+ deleteProperty: None,
+ getElements: None,
+ funToString: Some(fun_to_string_hook),
+};
+
+unsafe extern "C" fn fun_to_string_hook(
+ cx: *mut JSContext,
+ obj: RawHandleObject,
+ _is_to_source: bool,
+) -> *mut JSString {
+ let js_class = get_object_class(obj.get());
+ assert!(!js_class.is_null());
+ let repr = (*(js_class as *const NonCallbackInterfaceObjectClass)).representation;
+ assert!(!repr.is_empty());
+ let ret = JS_NewStringCopyN(cx, repr.as_ptr() as *const libc::c_char, repr.len());
+ assert!(!ret.is_null());
+ ret
+}
+
+fn create_unscopable_object(cx: SafeJSContext, names: &[&CStr], mut rval: MutableHandleObject) {
+ assert!(!names.is_empty());
+ assert!(rval.is_null());
+ unsafe {
+ rval.set(JS_NewPlainObject(*cx));
+ assert!(!rval.is_null());
+ for &name in names {
+ assert!(JS_DefineProperty(
+ *cx,
+ rval.handle(),
+ name.as_ptr(),
+ HandleValue::from_raw(TrueHandleValue),
+ JSPROP_READONLY as u32,
+ ));
+ }
+ }
+}
+
+fn define_name(cx: SafeJSContext, obj: HandleObject, name: &CStr) {
+ unsafe {
+ rooted!(in(*cx) let name = JS_AtomizeAndPinString(*cx, name.as_ptr()));
+ assert!(!name.is_null());
+ assert!(JS_DefineProperty4(
+ *cx,
+ obj,
+ c"name".as_ptr(),
+ name.handle(),
+ JSPROP_READONLY as u32
+ ));
+ }
+}
+
+fn define_length(cx: SafeJSContext, obj: HandleObject, length: i32) {
+ unsafe {
+ assert!(JS_DefineProperty5(
+ *cx,
+ obj,
+ c"length".as_ptr(),
+ length,
+ JSPROP_READONLY as u32
+ ));
+ }
+}
+
+unsafe extern "C" fn invalid_constructor(
+ cx: *mut JSContext,
+ _argc: libc::c_uint,
+ _vp: *mut JSVal,
+) -> bool {
+ throw_type_error(cx, "Illegal constructor.");
+ false
+}
+
+unsafe extern "C" fn non_new_constructor(
+ cx: *mut JSContext,
+ _argc: libc::c_uint,
+ _vp: *mut JSVal,
+) -> bool {
+ throw_type_error(cx, "This constructor needs to be called with `new`.");
+ false
+}
+
+pub(crate) enum ProtoOrIfaceIndex {
+ ID(PrototypeList::ID),
+ Constructor(PrototypeList::Constructor),
+}
+
+impl From<ProtoOrIfaceIndex> for usize {
+ fn from(index: ProtoOrIfaceIndex) -> usize {
+ match index {
+ ProtoOrIfaceIndex::ID(id) => id as usize,
+ ProtoOrIfaceIndex::Constructor(constructor) => constructor as usize,
+ }
+ }
+}
+
+pub(crate) fn get_per_interface_object_handle(
+ cx: SafeJSContext,
+ global: HandleObject,
+ id: ProtoOrIfaceIndex,
+ creator: unsafe fn(SafeJSContext, HandleObject, *mut ProtoOrIfaceArray),
+ mut rval: MutableHandleObject,
+) {
+ unsafe {
+ assert!(((*get_object_class(global.get())).flags & JSCLASS_DOM_GLOBAL) != 0);
+
+ /* Check to see whether the interface objects are already installed */
+ let proto_or_iface_array = get_proto_or_iface_array(global.get());
+ let index: usize = id.into();
+ rval.set((*proto_or_iface_array)[index]);
+ if !rval.get().is_null() {
+ return;
+ }
+
+ creator(cx, global, proto_or_iface_array);
+ rval.set((*proto_or_iface_array)[index]);
+ assert!(!rval.get().is_null());
+ }
+}
+
+pub(crate) fn define_dom_interface(
+ cx: SafeJSContext,
+ global: HandleObject,
+ id: ProtoOrIfaceIndex,
+ creator: unsafe fn(SafeJSContext, HandleObject, *mut ProtoOrIfaceArray),
+ enabled: fn(SafeJSContext, HandleObject) -> bool,
+) {
+ assert!(!global.get().is_null());
+
+ if !enabled(cx, global) {
+ return;
+ }
+
+ rooted!(in(*cx) let mut proto = ptr::null_mut::<JSObject>());
+ get_per_interface_object_handle(cx, global, id, creator, proto.handle_mut());
+ assert!(!proto.is_null());
+}
+
+fn get_proto_id_for_new_target(new_target: HandleObject) -> Option<PrototypeList::ID> {
+ unsafe {
+ let new_target_class = get_object_class(*new_target);
+ if is_dom_class(&*new_target_class) {
+ let domjsclass: *const DOMJSClass = new_target_class as *const DOMJSClass;
+ let dom_class = &(*domjsclass).dom_class;
+ return Some(dom_class.interface_chain[dom_class.depth as usize]);
+ }
+ None
+ }
+}
+
+#[allow(clippy::result_unit_err)]
+pub fn get_desired_proto(
+ cx: SafeJSContext,
+ args: &CallArgs,
+ proto_id: PrototypeList::ID,
+ creator: unsafe fn(SafeJSContext, HandleObject, *mut ProtoOrIfaceArray),
+ mut desired_proto: MutableHandleObject,
+) -> Result<(), ()> {
+ unsafe {
+ // This basically implements
+ // https://heycam.github.io/webidl/#internally-create-a-new-object-implementing-the-interface
+ // step 3.
+
+ assert!(args.is_constructing());
+
+ // The desired prototype depends on the actual constructor that was invoked,
+ // which is passed to us as the newTarget in the callargs. We want to do
+ // something akin to the ES6 specification's GetProtototypeFromConstructor (so
+ // get .prototype on the newTarget, with a fallback to some sort of default).
+
+ // First, a fast path for the case when the the constructor is in fact one of
+ // our DOM constructors. This is safe because on those the "constructor"
+ // property is non-configurable and non-writable, so we don't have to do the
+ // slow JS_GetProperty call.
+ rooted!(in(*cx) let mut new_target = args.new_target().to_object());
+ rooted!(in(*cx) let original_new_target = *new_target);
+ // See whether we have a known DOM constructor here, such that we can take a
+ // fast path.
+ let target_proto_id = get_proto_id_for_new_target(new_target.handle()).or_else(|| {
+ // We might still have a cross-compartment wrapper for a known DOM
+ // constructor. CheckedUnwrapStatic is fine here, because we're looking for
+ // DOM constructors and those can't be cross-origin objects.
+ new_target.set(CheckedUnwrapStatic(*new_target));
+ if !new_target.is_null() && *new_target != *original_new_target {
+ get_proto_id_for_new_target(new_target.handle())
+ } else {
+ None
+ }
+ });
+
+ if let Some(proto_id) = target_proto_id {
+ let global = GetNonCCWObjectGlobal(*new_target);
+ let proto_or_iface_cache = get_proto_or_iface_array(global);
+ desired_proto.set((*proto_or_iface_cache)[proto_id as usize]);
+ if *new_target != *original_new_target && !JS_WrapObject(*cx, desired_proto.into()) {
+ return Err(());
+ }
+ return Ok(());
+ }
+
+ // Slow path. This basically duplicates the ES6 spec's
+ // GetPrototypeFromConstructor except that instead of taking a string naming
+ // the fallback prototype we determine the fallback based on the proto id we
+ // were handed.
+ rooted!(in(*cx) let mut proto_val = NullValue());
+ if !JS_GetProperty(
+ *cx,
+ original_new_target.handle().into(),
+ c"prototype".as_ptr(),
+ proto_val.handle_mut().into(),
+ ) {
+ return Err(());
+ }
+
+ if proto_val.is_object() {
+ desired_proto.set(proto_val.to_object());
+ return Ok(());
+ }
+
+ // Fall back to getting the proto for our given proto id in the realm that
+ // GetFunctionRealm(newTarget) returns.
+ let realm = GetFunctionRealm(*cx, new_target.handle().into());
+
+ if realm.is_null() {
+ return Err(());
+ }
+
+ {
+ let _realm = JSAutoRealm::new(*cx, GetRealmGlobalOrNull(realm));
+ rooted!(in(*cx) let global = CurrentGlobalOrNull(*cx));
+ get_per_interface_object_handle(
+ cx,
+ global.handle(),
+ ProtoOrIfaceIndex::ID(proto_id),
+ creator,
+ desired_proto.reborrow(),
+ );
+ if desired_proto.is_null() {
+ return Err(());
+ }
+ }
+
+ maybe_wrap_object(*cx, desired_proto);
+ Ok(())
+ }
+}