diff options
Diffstat (limited to 'components/script/dom/bindings/interface.rs')
-rw-r--r-- | components/script/dom/bindings/interface.rs | 696 |
1 files changed, 0 insertions, 696 deletions
diff --git a/components/script/dom/bindings/interface.rs b/components/script/dom/bindings/interface.rs deleted file mode 100644 index d1c191be3a4..00000000000 --- a/components/script/dom/bindings/interface.rs +++ /dev/null @@ -1,696 +0,0 @@ -/* 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 script_bindings::constant::{ConstantSpec, define_constants}; -use servo_url::MutableOrigin; - -use crate::DomTypes; -use crate::dom::bindings::codegen::InterfaceObjectMap::Globals; -use crate::dom::bindings::codegen::PrototypeList; -use crate::dom::bindings::conversions::{DOM_OBJECT_SLOT, get_dom_class}; -use crate::dom::bindings::guard::Guard; -use crate::dom::bindings::principals::ServoJSPrincipals; -use crate::dom::bindings::utils::{ - DOM_PROTOTYPE_SLOT, DOMJSClass, JSCLASS_DOM_GLOBAL, ProtoOrIfaceArray, get_proto_or_iface_array, -}; -use crate::script_runtime::JSContext as SafeJSContext; - -/// 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 - } -} - -pub(crate) 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(()) - } -} |