diff options
author | Josh Matthews <josh@joshmatthews.net> | 2023-05-25 23:59:02 -0400 |
---|---|---|
committer | Josh Matthews <josh@joshmatthews.net> | 2023-05-28 23:23:12 -0400 |
commit | d9600ff50f3c1bdd8c44e2dfc15a18416d80cb82 (patch) | |
tree | 6a56ce1cf4458292f41791399e0ac269e3d6d46e /components | |
parent | 4ee789a85c50bb31521a00176b311ecdb8ccbcc5 (diff) | |
download | servo-d9600ff50f3c1bdd8c44e2dfc15a18416d80cb82.tar.gz servo-d9600ff50f3c1bdd8c44e2dfc15a18416d80cb82.zip |
Support arbitrary protos when wrapping EventTarget objects.
Diffstat (limited to 'components')
-rw-r--r-- | components/script/dom/bindings/codegen/CodegenRust.py | 55 | ||||
-rw-r--r-- | components/script/dom/bindings/interface.rs | 122 | ||||
-rw-r--r-- | components/script/dom/bindings/iterable.rs | 4 | ||||
-rw-r--r-- | components/script/dom/bindings/reflector.rs | 14 | ||||
-rw-r--r-- | components/script/dom/bindings/utils.rs | 4 | ||||
-rw-r--r-- | components/script/dom/eventtarget.rs | 8 |
6 files changed, 185 insertions, 22 deletions
diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 24cf71d8820..5ed330083b9 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -2313,10 +2313,11 @@ def DOMClass(descriptor): return """\ DOMClass { interface_chain: [ %s ], + depth: %d, type_id: %s, malloc_size_of: %s as unsafe fn(&mut _, _) -> _, global: InterfaceObjectMap::Globals::%s, -}""" % (prototypeChainString, DOMClassTypeId(descriptor), mallocSizeOf, globals_) +}""" % (prototypeChainString, descriptor.prototypeDepth, DOMClassTypeId(descriptor), mallocSizeOf, globals_) class CGDOMJSClass(CGThing): @@ -2865,7 +2866,7 @@ ensure_expando_object(*cx, obj.handle().into(), expando.handle_mut()); copyFunc = "JS_InitializePropertiesFromCompatibleNativeObject" copyCode += """\ let mut slot = UndefinedValue(); -JS_GetReservedSlot(proto.get(), DOM_PROTO_UNFORGEABLE_HOLDER_SLOT, &mut slot); +JS_GetReservedSlot(canonical_proto.get(), DOM_PROTO_UNFORGEABLE_HOLDER_SLOT, &mut slot); rooted!(in(*cx) let mut unforgeable_holder = ptr::null_mut::<JSObject>()); unforgeable_holder.handle_mut().set(slot.to_object()); assert!(%(copyFunc)s(*cx, %(obj)s.handle(), unforgeable_holder.handle())); @@ -2884,6 +2885,7 @@ class CGWrapMethod(CGAbstractMethod): assert not descriptor.isGlobal() args = [Argument('SafeJSContext', 'cx'), Argument('&GlobalScope', 'scope'), + Argument('Option<HandleObject>', 'given_proto'), Argument("Box<%s>" % descriptor.concreteType, 'object')] retval = 'DomRoot<%s>' % descriptor.concreteType CGAbstractMethod.__init__(self, descriptor, 'Wrap', retval, args, @@ -2896,7 +2898,7 @@ class CGWrapMethod(CGAbstractMethod): proto = "ptr::null_mut()" lazyProto = "true" # Our proxy handler will manage the prototype else: - proto = "proto.get()" + proto = "canonical_proto.get()" lazyProto = "false" create = """ @@ -2924,6 +2926,15 @@ SetProxyReservedSlot( else: lazyProto = None create = """ +rooted!(in(*cx) let mut proto = ptr::null_mut::<JSObject>()); +if let Some(given) = given_proto { + *proto = *given; + if get_context_realm(*cx) != get_object_realm(*given) { + assert!(JS_WrapObject(*cx, proto.handle_mut())); + } +} else { + *proto = *canonical_proto; +} rooted!(in(*cx) let obj = JS_NewObjectWithGivenProto( *cx, &Class.base, @@ -2951,9 +2962,9 @@ assert!(!scope.get().is_null()); assert!(((*get_object_class(scope.get())).flags & JSCLASS_IS_GLOBAL) != 0); let _ac = JSAutoRealm::new(*cx, scope.get()); -rooted!(in(*cx) let mut proto = ptr::null_mut::<JSObject>()); -GetProtoObject(cx, scope, proto.handle_mut()); -assert!(!proto.is_null()); +rooted!(in(*cx) let mut canonical_proto = ptr::null_mut::<JSObject>()); +GetProtoObject(cx, scope, canonical_proto.handle_mut()); +assert!(!canonical_proto.is_null()); %(createObject)s let root = raw.reflect_with(obj.get()); @@ -3010,9 +3021,9 @@ assert!(!obj.is_null()); let root = raw.reflect_with(obj.get()); let _ac = JSAutoRealm::new(*cx, obj.get()); -rooted!(in(*cx) let mut proto = ptr::null_mut::<JSObject>()); -GetProtoObject(cx, obj.handle(), proto.handle_mut()); -assert!(JS_SetPrototype(*cx, obj.handle(), proto.handle())); +rooted!(in(*cx) let mut canonical_proto = ptr::null_mut::<JSObject>()); +GetProtoObject(cx, obj.handle(), canonical_proto.handle_mut()); +assert!(JS_SetPrototype(*cx, obj.handle(), canonical_proto.handle())); let mut immutable = false; assert!(JS_SetImmutablePrototype(*cx, obj.handle(), &mut immutable)); assert!(immutable); @@ -3076,6 +3087,7 @@ impl DomObjectWrap for %s { const WRAP: unsafe fn( SafeJSContext, &GlobalScope, + Option<HandleObject>, Box<Self>, ) -> Root<Dom<Self>> = Wrap; } @@ -3098,6 +3110,7 @@ impl DomObjectIteratorWrap for %s { const ITER_WRAP: unsafe fn( SafeJSContext, &GlobalScope, + Option<HandleObject>, Box<IterableIterator<Self>>, ) -> Root<Dom<IterableIterator<Self>>> = Wrap; } @@ -6102,13 +6115,25 @@ class CGClassConstructHook(CGAbstractExternMethod): def definition_body(self): preamble = """let cx = SafeJSContext::from_ptr(cx); +let args = CallArgs::from_vp(vp, argc); let global = GlobalScope::from_object(JS_CALLEE(*cx, vp).to_object()); -""" +rooted!(in(*cx) let mut desired_proto = ptr::null_mut::<JSObject>()); +let proto_result = get_desired_proto( + cx, + &args, + PrototypeList::ID::%s, + CreateInterfaceObjects, + desired_proto.handle_mut(), +); + assert!(proto_result.is_ok()); +if proto_result.is_err() { + return false; +} +""" % (MakeNativeName(self.descriptor.name)) if len(self.exposureSet) == 1: preamble += """\ let global = DomRoot::downcast::<dom::types::%s>(global).unwrap(); """ % list(self.exposureSet)[0] - preamble += """let args = CallArgs::from_vp(vp, argc);\n""" preamble = CGGeneric(preamble) if self.constructor.isHTMLConstructor(): signatures = self.constructor.signatures() @@ -6122,7 +6147,10 @@ let global = DomRoot::downcast::<dom::types::%s>(global).unwrap(); else: name = self.constructor.identifier.name nativeName = MakeNativeName(self.descriptor.binaryNameFor(name)) - constructorCall = CGMethodCall(["&global"], nativeName, True, + args = ["&global"] + if self.descriptor.interface.identifier.name == "EventTarget": + args += ["desired_proto.handle()"] + constructorCall = CGMethodCall(args, nativeName, True, self.descriptor, self.constructor) return CGList([preamble, constructorCall]) @@ -6295,6 +6323,8 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'js::jsapi::CallArgs', 'js::jsapi::CurrentGlobalOrNull', 'js::rust::wrappers::GetPropertyKeys', + 'js::rust::get_object_realm', + 'js::rust::get_context_realm', 'js::jsapi::GCContext', 'js::jsapi::GetWellKnownSymbol', 'js::rust::Handle', @@ -6439,6 +6469,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'crate::dom::bindings::interface::define_guarded_properties', 'crate::dom::bindings::interface::is_exposed_in', 'crate::dom::bindings::interface::get_per_interface_object_handle', + 'crate::dom::bindings::interface::get_desired_proto', 'crate::dom::bindings::htmlconstructor::pop_current_element_queue', 'crate::dom::bindings::htmlconstructor::push_new_element_queue', 'crate::dom::bindings::iterable::Iterable', diff --git a/components/script/dom/bindings/interface.rs b/components/script/dom/bindings/interface.rs index 86b8f464629..14546d214ea 100644 --- a/components/script/dom/bindings/interface.rs +++ b/components/script/dom/bindings/interface.rs @@ -12,6 +12,7 @@ use crate::dom::bindings::guard::Guard; use crate::dom::bindings::principals::ServoJSPrincipals; use crate::dom::bindings::utils::{ get_proto_or_iface_array, ProtoOrIfaceArray, DOM_PROTOTYPE_SLOT, JSCLASS_DOM_GLOBAL, + DOMJSClass, }; use crate::script_runtime::JSContext as SafeJSContext; use js::error::throw_type_error; @@ -21,7 +22,7 @@ use js::jsapi::HandleObject as RawHandleObject; use js::jsapi::{jsid, JSClass, JSClassOps}; use js::jsapi::{ Compartment, CompartmentSpecifier, IsSharableCompartment, IsSystemCompartment, - JS_IterateCompartments, JS::CompartmentIterResult, + JS_IterateCompartments, JS::CompartmentIterResult, CallArgs, }; use js::jsapi::{JSAutoRealm, JSContext, JSFunctionSpec, JSObject, JSFUN_CONSTRUCTOR}; use js::jsapi::{JSPropertySpec, JSString, JSTracer, JS_AtomizeAndPinString}; @@ -31,13 +32,22 @@ use js::jsapi::{JS_NewStringCopyN, JS_SetReservedSlot}; use js::jsapi::{ObjectOps, OnNewGlobalHookOption, SymbolCode}; use js::jsapi::{TrueHandleValue, Value}; use js::jsapi::{JSPROP_PERMANENT, JSPROP_READONLY, JSPROP_RESOLVING}; +use js::jsapi::CheckedUnwrapStatic; +use js::jsapi::JS_GetProperty; +use js::jsapi::GetFunctionRealm; +use js::jsapi::GetRealmGlobalOrNull; +use js::jsapi::GetNonCCWObjectGlobal; +use js::jsapi::JS_WrapObject; +use js::jsapi::CurrentGlobalOrNull; use js::jsval::{JSVal, PrivateValue}; +use js::jsval::NullValue; +use js::rust::is_dom_class; use js::rust::wrappers::JS_FireOnNewGlobalObject; use js::rust::wrappers::RUST_SYMBOL_TO_JSID; use js::rust::wrappers::{JS_DefineProperty, JS_DefineProperty5}; use js::rust::wrappers::{JS_DefineProperty3, JS_DefineProperty4, JS_DefinePropertyById5}; use js::rust::wrappers::{JS_LinkConstructorAndPrototype, JS_NewObjectWithGivenProto}; -use js::rust::{define_methods, define_properties, get_object_class}; +use js::rust::{define_methods, define_properties, get_object_class, maybe_wrap_object}; use js::rust::{HandleObject, HandleValue, MutableHandleObject, RealmOptions}; use servo_url::MutableOrigin; use std::convert::TryFrom; @@ -592,3 +602,111 @@ pub fn define_dom_interface( 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]); + } + return None; + } +} + +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 = 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 { + if !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(), b"prototype\0".as_ptr() as *const libc::c_char, 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. + rooted!(in(*cx) 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 + ); + if desired_proto.is_null() { + return Err(()); + } + } + + maybe_wrap_object(*cx, desired_proto); + return Ok(()) + } +} diff --git a/components/script/dom/bindings/iterable.rs b/components/script/dom/bindings/iterable.rs index 2eac830a4bb..9c7da60b037 100644 --- a/components/script/dom/bindings/iterable.rs +++ b/components/script/dom/bindings/iterable.rs @@ -20,7 +20,7 @@ use dom_struct::dom_struct; use js::conversions::ToJSValConvertible; use js::jsapi::{Heap, JSObject}; use js::jsval::UndefinedValue; -use js::rust::{HandleValue, MutableHandleObject}; +use js::rust::{HandleValue, MutableHandleObject, HandleObject}; use std::cell::Cell; use std::ptr; use std::ptr::NonNull; @@ -118,7 +118,7 @@ impl<T: DomObjectIteratorWrap + JSTraceable + Iterable> IterableIterator<T> { } impl<T: DomObjectIteratorWrap + JSTraceable + Iterable> DomObjectWrap for IterableIterator<T> { - const WRAP: unsafe fn(JSContext, &GlobalScope, Box<Self>) -> Root<Dom<Self>> = T::ITER_WRAP; + const WRAP: unsafe fn(JSContext, &GlobalScope, Option<HandleObject>, Box<Self>) -> Root<Dom<Self>> = T::ITER_WRAP; } fn dict_return( diff --git a/components/script/dom/bindings/reflector.rs b/components/script/dom/bindings/reflector.rs index 8176989ed36..32c3d7645e1 100644 --- a/components/script/dom/bindings/reflector.rs +++ b/components/script/dom/bindings/reflector.rs @@ -23,7 +23,16 @@ where U: DerivedFrom<GlobalScope>, { let global_scope = global.upcast(); - unsafe { T::WRAP(GlobalScope::get_cx(), global_scope, obj) } + unsafe { T::WRAP(GlobalScope::get_cx(), global_scope, None, obj) } +} + +pub fn reflect_dom_object2<T, U>(obj: Box<T>, global: &U, proto: HandleObject) -> DomRoot<T> +where + T: DomObject + DomObjectWrap, + U: DerivedFrom<GlobalScope>, +{ + let global_scope = global.upcast(); + unsafe { T::WRAP(GlobalScope::get_cx(), global_scope, Some(proto), obj) } } /// A struct to store a reference to the reflector of a DOM object. @@ -109,7 +118,7 @@ impl MutDomObject for Reflector { /// A trait to provide a function pointer to wrap function for DOM objects. pub trait DomObjectWrap: Sized + DomObject { /// Function pointer to the general wrap function type - const WRAP: unsafe fn(JSContext, &GlobalScope, Box<Self>) -> Root<Dom<Self>>; + const WRAP: unsafe fn(JSContext, &GlobalScope, Option<HandleObject>, Box<Self>) -> Root<Dom<Self>>; } /// A trait to provide a function pointer to wrap function for @@ -119,6 +128,7 @@ pub trait DomObjectIteratorWrap: DomObjectWrap + JSTraceable + Iterable { const ITER_WRAP: unsafe fn( JSContext, &GlobalScope, + Option<HandleObject>, Box<IterableIterator<Self>>, ) -> Root<Dom<IterableIterator<Self>>>; } diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs index a0d3f75315d..87748da1ec4 100644 --- a/components/script/dom/bindings/utils.rs +++ b/components/script/dom/bindings/utils.rs @@ -99,6 +99,9 @@ pub struct DOMClass { /// derivedness. pub interface_chain: [PrototypeList::ID; MAX_PROTO_CHAIN_LENGTH], + /// The last valid index of `interface_chain`. + pub depth: u8, + /// The type ID of that interface. pub type_id: TopTypeId, @@ -112,6 +115,7 @@ unsafe impl Sync for DOMClass {} /// The JSClass used for DOM object reflectors. #[derive(Copy)] +#[repr(C)] pub struct DOMJSClass { /// The actual JSClass. pub base: js::jsapi::JSClass, diff --git a/components/script/dom/eventtarget.rs b/components/script/dom/eventtarget.rs index 9e9bf816c72..dd0079c47dd 100644 --- a/components/script/dom/eventtarget.rs +++ b/components/script/dom/eventtarget.rs @@ -21,7 +21,7 @@ use crate::dom::bindings::codegen::UnionTypes::EventListenerOptionsOrBoolean; use crate::dom::bindings::codegen::UnionTypes::EventOrString; use crate::dom::bindings::error::{report_pending_exception, Error, Fallible}; use crate::dom::bindings::inheritance::Castable; -use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::reflector::{reflect_dom_object, reflect_dom_object2, DomObject, Reflector}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::element::Element; @@ -39,7 +39,7 @@ use fnv::FnvHasher; use js::jsapi::JS_GetFunctionObject; use js::rust::transform_u16_to_source_text; use js::rust::wrappers::CompileFunction; -use js::rust::{CompileOptionsWrapper, RootedObjectVectorWrapper}; +use js::rust::{CompileOptionsWrapper, RootedObjectVectorWrapper, HandleObject}; use libc::c_char; use servo_atoms::Atom; use servo_url::ServoUrl; @@ -360,8 +360,8 @@ impl EventTarget { } #[allow(non_snake_case)] - pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<EventTarget>> { - Ok(EventTarget::new(global)) + pub fn Constructor(global: &GlobalScope, proto: HandleObject) -> Fallible<DomRoot<EventTarget>> { + Ok(reflect_dom_object2(Box::new(EventTarget::new_inherited()), global, proto)) } pub fn has_listeners_for(&self, type_: &Atom) -> bool { |