diff options
author | Josh Matthews <josh@joshmatthews.net> | 2025-04-04 02:45:08 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-04 06:45:08 +0000 |
commit | b4079b3ff33a3f7e2b35ac3aacc4467f8da42242 (patch) | |
tree | af6ae87af10c1383a3a3fcdb28b1d2544bc2fc45 /components/script_bindings/interface.rs | |
parent | 277c0b82dd4411798b461a4e3a782e7869ae5740 (diff) | |
download | servo-b4079b3ff33a3f7e2b35ac3aacc4467f8da42242.tar.gz servo-b4079b3ff33a3f7e2b35ac3aacc4467f8da42242.zip |
Move generated bindings to script_bindings (#36323)
This is the final step of #1799, where the majority of the generated
code for the JS bindings is now compiled as part of the script_bindings
build step. The remaining pieces in script must live there because they
refer to concrete DOM types; all code in script_bindings is generic over
the
[DomTypes](https://doc.servo.org/script/dom/bindings/codegen/DomTypes/trait.DomTypes.html)
trait.
My testing with incremental builds shows me a 12 second reduction in
build times on my 2024 M4 Macbook Pro when modifying code in the script
crate after these changes. Before this PR those changes took 20 seconds
to rebuild Servo, and now they take 8 seconds.
Testing: Existing WPT tests ensure no regressions.
Fixes: #1799
---------
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
Diffstat (limited to 'components/script_bindings/interface.rs')
-rw-r--r-- | components/script_bindings/interface.rs | 697 |
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(()) + } +} |