From 35f21e426b2fec968ebd0970b743d43ac6fd012f Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Fri, 21 Feb 2025 23:46:56 -0500 Subject: [PATCH] Move more bindings code to script_bindings (#35578) * Move JSContext wrapper to script_bindings. Signed-off-by: Josh Matthews * Move webidl constant bindings to script_bindings. Signed-off-by: Josh Matthews * Move CanGc to script_bindings. Signed-off-by: Josh Matthews * Move Dom and Root types to script_bindings. Signed-off-by: Josh Matthews * Formatting. Signed-off-by: Josh Matthews * Extra docs for new traits. Signed-off-by: Josh Matthews * Fix clippy warnings. Signed-off-by: Josh Matthews --------- Signed-off-by: Josh Matthews --- components/script/dom/bindings/conversions.rs | 140 +----- components/script/dom/bindings/error.rs | 2 +- components/script/dom/bindings/import.rs | 2 +- components/script/dom/bindings/interface.rs | 2 +- components/script/dom/bindings/mod.rs | 1 - components/script/dom/bindings/namespace.rs | 2 +- components/script/dom/bindings/root.rs | 402 +--------------- components/script/dom/bindings/trace.rs | 26 +- components/script/dom/document.rs | 2 +- components/script/dom/element.rs | 80 +++- components/script/dom/htmlcanvaselement.rs | 2 +- components/script/dom/messageport.rs | 3 +- components/script/dom/node.rs | 22 +- .../script/dom/webgl2renderingcontext.rs | 2 +- components/script/dom/workerglobalscope.rs | 2 +- components/script/script_runtime.rs | 49 +- components/script/script_thread.rs | 2 +- .../bindings => script_bindings}/constant.rs | 13 +- components/script_bindings/conversions.rs | 165 ++++++- components/script_bindings/lib.rs | 4 +- components/script_bindings/root.rs | 432 ++++++++++++++++++ components/script_bindings/script_runtime.rs | 45 ++ components/script_bindings/trace.rs | 29 ++ 23 files changed, 788 insertions(+), 641 deletions(-) rename components/{script/dom/bindings => script_bindings}/constant.rs (86%) create mode 100644 components/script_bindings/root.rs diff --git a/components/script/dom/bindings/conversions.rs b/components/script/dom/bindings/conversions.rs index 7d276923c7c..11d838230d3 100644 --- a/components/script/dom/bindings/conversions.rs +++ b/components/script/dom/bindings/conversions.rs @@ -32,17 +32,17 @@ //! | sequences | `Vec` | | //! | union types | `T` | | -use std::{ffi, ptr}; +use std::ffi; pub(crate) use js::conversions::{ ConversionBehavior, ConversionResult, FromJSValConvertible, ToJSValConvertible, }; use js::error::throw_type_error; -use js::glue::{GetProxyReservedSlot, IsWrapper, JS_GetReservedSlot, UnwrapObjectDynamic}; +use js::glue::GetProxyReservedSlot; use js::jsapi::{Heap, IsWindowProxy, JSContext, JSObject, JS_IsExceptionPending}; use js::jsval::UndefinedValue; use js::rust::wrappers::{IsArrayObject, JS_GetProperty, JS_HasProperty}; -use js::rust::{is_dom_object, HandleId, HandleObject, HandleValue, MutableHandleValue}; +use js::rust::{HandleId, HandleObject, HandleValue, MutableHandleValue}; use num_traits::Float; pub(crate) use script_bindings::conversions::*; @@ -93,21 +93,6 @@ impl> FromJSValConvertible for Fini } } -impl FromJSValConvertible for DomRoot { - type Config = (); - - unsafe fn from_jsval( - cx: *mut JSContext, - value: HandleValue, - _config: Self::Config, - ) -> Result>, ()> { - Ok(match root_from_handlevalue(value, cx) { - Ok(result) => ConversionResult::Success(result), - Err(()) => ConversionResult::Failure("value is not an object".into()), - }) - } -} - impl ToJSValConvertible for RootedTraceableBox { #[inline] unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { @@ -157,79 +142,6 @@ pub(crate) unsafe fn jsid_to_string(cx: *mut JSContext, id: HandleId) -> Option< pub(crate) use script_bindings::conversions::is_dom_proxy; -/// The index of the slot wherein a pointer to the reflected DOM object is -/// stored for non-proxy bindings. -// We use slot 0 for holding the raw object. This is safe for both -// globals and non-globals. -pub(crate) const DOM_OBJECT_SLOT: u32 = 0; - -/// Get the private pointer of a DOM object from a given reflector. -pub(crate) unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void { - let mut value = UndefinedValue(); - if is_dom_object(obj) { - JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value); - } else { - debug_assert!(is_dom_proxy(obj)); - GetProxyReservedSlot(obj, 0, &mut value); - }; - if value.is_undefined() { - ptr::null() - } else { - value.to_private() - } -} - -pub(crate) enum PrototypeCheck { - Derive(fn(&'static DOMClass) -> bool), - Depth { depth: usize, proto_id: u16 }, -} - -/// Get a `*const libc::c_void` for the given DOM object, unwrapping any -/// wrapper around it first, and checking if the object is of the correct type. -/// -/// Returns Err(()) if `obj` is an opaque security wrapper or if the object is -/// not an object for a DOM object of the given type (as defined by the -/// proto_id and proto_depth). -#[inline] -pub(crate) unsafe fn private_from_proto_check( - mut obj: *mut JSObject, - cx: *mut JSContext, - proto_check: PrototypeCheck, -) -> Result<*const libc::c_void, ()> { - let dom_class = get_dom_class(obj).or_else(|_| { - if IsWrapper(obj) { - trace!("found wrapper"); - obj = UnwrapObjectDynamic(obj, cx, /* stopAtWindowProxy = */ false); - if obj.is_null() { - trace!("unwrapping security wrapper failed"); - Err(()) - } else { - assert!(!IsWrapper(obj)); - trace!("unwrapped successfully"); - get_dom_class(obj) - } - } else { - trace!("not a dom wrapper"); - Err(()) - } - })?; - - let prototype_matches = match proto_check { - PrototypeCheck::Derive(f) => (f)(dom_class), - PrototypeCheck::Depth { depth, proto_id } => { - dom_class.interface_chain[depth] as u16 == proto_id - }, - }; - - if prototype_matches { - trace!("good prototype"); - Ok(private_from_object(obj)) - } else { - trace!("bad prototype"); - Err(()) - } -} - /// Get a `*const libc::c_void` for the given DOM object, unless it is a DOM /// wrapper, and checking if the object is of the correct type. /// @@ -250,17 +162,6 @@ unsafe fn private_from_proto_check_static( } } -/// Get a `*const T` for a DOM object accessible from a `JSObject`. -pub(crate) fn native_from_object(obj: *mut JSObject, cx: *mut JSContext) -> Result<*const T, ()> -where - T: DomObject + IDLInterface, -{ - unsafe { - private_from_proto_check(obj, cx, PrototypeCheck::Derive(T::derives)) - .map(|ptr| ptr as *const T) - } -} - /// Get a `*const T` for a DOM object accessible from a `JSObject`, where the DOM object /// is guaranteed not to be a wrapper. pub(crate) fn native_from_object_static(obj: *mut JSObject) -> Result<*const T, ()> @@ -270,19 +171,6 @@ where unsafe { private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T) } } -/// Get a `DomRoot` for the given DOM object, unwrapping any wrapper -/// around it first, and checking if the object is of the correct type. -/// -/// Returns Err(()) if `obj` is an opaque security wrapper or if the object is -/// not a reflector for a DOM object of the given type (as defined by the -/// proto_id and proto_depth). -pub(crate) fn root_from_object(obj: *mut JSObject, cx: *mut JSContext) -> Result, ()> -where - T: DomObject + IDLInterface, -{ - native_from_object(obj, cx).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) }) -} - /// Get a `DomRoot` for the given DOM object, unwrapping any wrapper /// around it first, and checking if the object is of the correct type. /// @@ -305,19 +193,7 @@ where if !v.get().is_object() { return Err(()); } - native_from_object(v.get().to_object(), cx) -} - -/// Get a `DomRoot` for a DOM object accessible from a `HandleValue`. -/// Caller is responsible for throwing a JS exception if needed in case of error. -pub(crate) fn root_from_handlevalue(v: HandleValue, cx: *mut JSContext) -> Result, ()> -where - T: DomObject + IDLInterface, -{ - if !v.get().is_object() { - return Err(()); - } - root_from_object(v.get().to_object(), cx) + unsafe { native_from_object(v.get().to_object(), cx) } } /// Get a `DomRoot` for a DOM object accessible from a `HandleObject`. @@ -328,13 +204,7 @@ pub(crate) fn root_from_handleobject( where T: DomObject + IDLInterface, { - root_from_object(obj.get(), cx) -} - -impl ToJSValConvertible for DomRoot { - unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { - self.reflector().to_jsval(cx, rval); - } + unsafe { root_from_object(obj.get(), cx) } } /// Returns whether `value` is an array-like object (Array, FileList, diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs index e809e2df552..abf394b6ed9 100644 --- a/components/script/dom/bindings/error.rs +++ b/components/script/dom/bindings/error.rs @@ -220,7 +220,7 @@ impl ErrorInfo { } fn from_dom_exception(object: HandleObject, cx: SafeJSContext) -> Option { - let exception = match root_from_object::(object.get(), *cx) { + let exception = match unsafe { root_from_object::(object.get(), *cx) } { Ok(exception) => exception, Err(_) => return None, }; diff --git a/components/script/dom/bindings/import.rs b/components/script/dom/bindings/import.rs index c24de0363a1..2afe1b0e534 100644 --- a/components/script/dom/bindings/import.rs +++ b/components/script/dom/bindings/import.rs @@ -97,6 +97,7 @@ pub(crate) mod module { jsapi, typedarray, JSCLASS_GLOBAL_SLOT_COUNT, JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL, JSCLASS_RESERVED_SLOTS_MASK, JS_CALLEE, }; + pub(crate) use script_bindings::constant::{ConstantSpec, ConstantVal}; pub(crate) use servo_config::pref; pub(crate) use super::base::*; @@ -109,7 +110,6 @@ pub(crate) mod module { pub(crate) use crate::dom::bindings::codegen::{ InterfaceObjectMap, PrototypeList, RegisterBindings, }; - pub(crate) use crate::dom::bindings::constant::{ConstantSpec, ConstantVal}; pub(crate) use crate::dom::bindings::constructor::{ call_default_constructor, call_html_constructor, pop_current_element_queue, push_new_element_queue, diff --git a/components/script/dom/bindings/interface.rs b/components/script/dom/bindings/interface.rs index 168abfc1503..45a5b9ffbb7 100644 --- a/components/script/dom/bindings/interface.rs +++ b/components/script/dom/bindings/interface.rs @@ -31,11 +31,11 @@ use js::rust::{ define_methods, define_properties, get_object_class, is_dom_class, maybe_wrap_object, HandleObject, HandleValue, MutableHandleObject, RealmOptions, }; +use script_bindings::constant::{define_constants, ConstantSpec}; use servo_url::MutableOrigin; use crate::dom::bindings::codegen::InterfaceObjectMap::Globals; use crate::dom::bindings::codegen::PrototypeList; -use crate::dom::bindings::constant::{define_constants, ConstantSpec}; use crate::dom::bindings::conversions::{get_dom_class, DOM_OBJECT_SLOT}; use crate::dom::bindings::guard::Guard; use crate::dom::bindings::principals::ServoJSPrincipals; diff --git a/components/script/dom/bindings/mod.rs b/components/script/dom/bindings/mod.rs index e37cb3e485c..e29a18a5917 100644 --- a/components/script/dom/bindings/mod.rs +++ b/components/script/dom/bindings/mod.rs @@ -138,7 +138,6 @@ pub(crate) mod buffer_source; pub(crate) mod callback; #[allow(dead_code)] pub(crate) mod cell; -pub(crate) mod constant; pub(crate) mod constructor; pub(crate) mod conversions; pub(crate) mod error; diff --git a/components/script/dom/bindings/namespace.rs b/components/script/dom/bindings/namespace.rs index 3f5e9d846d5..2eca9dbd3f2 100644 --- a/components/script/dom/bindings/namespace.rs +++ b/components/script/dom/bindings/namespace.rs @@ -9,8 +9,8 @@ use std::ptr; use js::jsapi::{JSClass, JSFunctionSpec}; use js::rust::{HandleObject, MutableHandleObject}; +use script_bindings::constant::ConstantSpec; -use super::constant::ConstantSpec; use crate::dom::bindings::guard::Guard; use crate::dom::bindings::interface::{create_object, define_on_global_object}; use crate::script_runtime::JSContext; diff --git a/components/script/dom/bindings/root.rs b/components/script/dom/bindings/root.rs index 912ed9acee4..4e8cbf21882 100644 --- a/components/script/dom/bindings/root.rs +++ b/components/script/dom/bindings/root.rs @@ -24,232 +24,24 @@ //! originating `DomRoot`. //! -use std::cell::{Cell, OnceCell, UnsafeCell}; +use std::cell::{OnceCell, UnsafeCell}; use std::default::Default; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; -use std::ops::Deref; use std::{mem, ptr}; use js::jsapi::{JSObject, JSTracer}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +pub(crate) use script_bindings::root::*; use script_layout_interface::TrustedNodeAddress; use style::thread_state; use crate::dom::bindings::conversions::DerivedFrom; use crate::dom::bindings::inheritance::Castable; -use crate::dom::bindings::reflector::{DomObject, MutDomObject, Reflector}; -use crate::dom::bindings::trace::{trace_reflector, JSTraceable}; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::trace::JSTraceable; use crate::dom::node::Node; -/// A rooted value. -#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_interior)] -pub(crate) struct Root { - /// The value to root. - value: T, - /// List that ensures correct dynamic root ordering - root_list: *const RootCollection, -} - -impl Root -where - T: StableTraceObject + 'static, -{ - /// Create a new stack-bounded root for the provided value. - /// It cannot outlive its associated `RootCollection`, and it gives - /// out references which cannot outlive this new `Root`. - #[cfg_attr(crown, allow(crown::unrooted_must_root))] - pub(crate) unsafe fn new(value: T) -> Self { - unsafe fn add_to_root_list(object: *const dyn JSTraceable) -> *const RootCollection { - assert_in_script(); - STACK_ROOTS.with(|root_list| { - let root_list = &*root_list.get().unwrap(); - root_list.root(object); - root_list - }) - } - - let root_list = add_to_root_list(value.stable_trace_object()); - Root { value, root_list } - } -} - -/// `StableTraceObject` represents values that can be rooted through a stable address that will -/// not change for their whole lifetime. -/// It is an unsafe trait that requires implementors to ensure certain safety guarantees. -/// -/// # Safety -/// -/// Implementors of this trait must ensure that the `trace` method correctly accounts for all -/// owned and referenced objects, so that the garbage collector can accurately determine which -/// objects are still in use. Failing to adhere to this contract may result in undefined behavior, -/// such as use-after-free errors. -pub(crate) unsafe trait StableTraceObject { - /// Returns a stable trace object which address won't change for the whole - /// lifetime of the value. - fn stable_trace_object(&self) -> *const dyn JSTraceable; -} - -unsafe impl StableTraceObject for Dom -where - T: DomObject, -{ - fn stable_trace_object(&self) -> *const dyn JSTraceable { - // The JSTraceable impl for Reflector doesn't actually do anything, - // so we need this shenanigan to actually trace the reflector of the - // T pointer in Dom. - #[cfg_attr(crown, allow(crown::unrooted_must_root))] - struct ReflectorStackRoot(Reflector); - unsafe impl JSTraceable for ReflectorStackRoot { - unsafe fn trace(&self, tracer: *mut JSTracer) { - trace_reflector(tracer, "on stack", &self.0); - } - } - unsafe { &*(self.reflector() as *const Reflector as *const ReflectorStackRoot) } - } -} - -unsafe impl StableTraceObject for MaybeUnreflectedDom -where - T: DomObject, -{ - fn stable_trace_object(&self) -> *const dyn JSTraceable { - // The JSTraceable impl for Reflector doesn't actually do anything, - // so we need this shenanigan to actually trace the reflector of the - // T pointer in Dom. - #[cfg_attr(crown, allow(crown::unrooted_must_root))] - struct MaybeUnreflectedStackRoot(T); - unsafe impl JSTraceable for MaybeUnreflectedStackRoot - where - T: DomObject, - { - unsafe fn trace(&self, tracer: *mut JSTracer) { - if self.0.reflector().get_jsobject().is_null() { - self.0.trace(tracer); - } else { - trace_reflector(tracer, "on stack", self.0.reflector()); - } - } - } - unsafe { &*(self.ptr.as_ptr() as *const T as *const MaybeUnreflectedStackRoot) } - } -} - -impl Deref for Root -where - T: Deref + StableTraceObject, -{ - type Target = ::Target; - - fn deref(&self) -> &Self::Target { - assert_in_script(); - &self.value - } -} - -impl Drop for Root -where - T: StableTraceObject, -{ - fn drop(&mut self) { - unsafe { - (*self.root_list).unroot(self.value.stable_trace_object()); - } - } -} - -/// A rooted reference to a DOM object. -pub(crate) type DomRoot = Root>; - -impl DomRoot { - /// Cast a DOM object root upwards to one of the interfaces it derives from. - pub(crate) fn upcast(root: DomRoot) -> DomRoot - where - U: Castable, - T: DerivedFrom, - { - unsafe { mem::transmute::, DomRoot>(root) } - } - - /// Cast a DOM object root downwards to one of the interfaces it might implement. - pub(crate) fn downcast(root: DomRoot) -> Option> - where - U: DerivedFrom, - { - if root.is::() { - Some(unsafe { mem::transmute::, DomRoot>(root) }) - } else { - None - } - } -} - -impl DomRoot { - /// Generate a new root from a reference - pub(crate) fn from_ref(unrooted: &T) -> DomRoot { - unsafe { DomRoot::new(Dom::from_ref(unrooted)) } - } - - /// Create a traced version of this rooted object. - /// - /// # Safety - /// - /// This should never be used to create on-stack values. Instead these values should always - /// end up as members of other DOM objects. - #[cfg_attr(crown, allow(crown::unrooted_must_root))] - pub(crate) fn as_traced(&self) -> Dom { - Dom::from_ref(self) - } -} - -impl MallocSizeOf for DomRoot -where - T: DomObject + MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - (**self).size_of(ops) - } -} - -impl PartialEq for DomRoot -where - T: DomObject, -{ - fn eq(&self, other: &Self) -> bool { - self.value == other.value - } -} - -impl Clone for DomRoot -where - T: DomObject, -{ - fn clone(&self) -> DomRoot { - DomRoot::from_ref(self) - } -} - -unsafe impl JSTraceable for DomRoot -where - T: DomObject, -{ - unsafe fn trace(&self, _: *mut JSTracer) { - // Already traced. - } -} - -/// A rooting mechanism for reflectors on the stack. -/// LIFO is not required. -/// -/// See also [*Exact Stack Rooting - Storing a GCPointer on the CStack*][cstack]. -/// -/// [cstack]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Internals/GC/Exact_Stack_Rooting -pub(crate) struct RootCollection { - roots: UnsafeCell>, -} - -thread_local!(static STACK_ROOTS: Cell> = const { Cell::new(None) }); - pub(crate) struct ThreadLocalStackRoots<'a>(PhantomData<&'a u32>); impl<'a> ThreadLocalStackRoots<'a> { @@ -265,48 +57,6 @@ impl Drop for ThreadLocalStackRoots<'_> { } } -impl RootCollection { - /// Create an empty collection of roots - pub(crate) fn new() -> RootCollection { - assert_in_script(); - RootCollection { - roots: UnsafeCell::new(vec![]), - } - } - - /// Starts tracking a trace object. - unsafe fn root(&self, object: *const dyn JSTraceable) { - assert_in_script(); - (*self.roots.get()).push(object); - } - - /// Stops tracking a trace object, asserting if it isn't found. - unsafe fn unroot(&self, object: *const dyn JSTraceable) { - assert_in_script(); - let roots = &mut *self.roots.get(); - match roots - .iter() - .rposition(|r| std::ptr::addr_eq(*r as *const (), object as *const ())) - { - Some(idx) => { - roots.remove(idx); - }, - None => panic!("Can't remove a root that was never rooted!"), - } - } -} - -/// SM Callback that traces the rooted reflectors -pub(crate) unsafe fn trace_roots(tracer: *mut JSTracer) { - trace!("tracing stack roots"); - STACK_ROOTS.with(|collection| { - let collection = &*(*collection.get().unwrap()).roots.get(); - for root in collection { - (**root).trace(tracer); - } - }); -} - /// Get a slice of references to DOM objects. pub(crate) trait DomSlice where @@ -327,121 +77,24 @@ where } } -/// A traced reference to a DOM object -/// -/// This type is critical to making garbage collection work with the DOM, -/// but it is very dangerous; if garbage collection happens with a `Dom` -/// on the stack, the `Dom` can point to freed memory. -/// -/// This should only be used as a field in other DOM objects. -#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -#[repr(transparent)] -pub(crate) struct Dom { - ptr: ptr::NonNull, -} - -// Dom is similar to Rc, in that it's not always clear how to avoid double-counting. -// For now, we choose not to follow any such pointers. -impl MallocSizeOf for Dom { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -impl Dom { +pub(crate) trait ToLayout { /// Returns `LayoutDom` containing the same pointer. /// /// # Safety /// /// The `self` parameter to this method must meet all the requirements of [`ptr::NonNull::as_ref`]. - pub(crate) unsafe fn to_layout(&self) -> LayoutDom { + unsafe fn to_layout(&self) -> LayoutDom; +} + +impl ToLayout for Dom { + unsafe fn to_layout(&self) -> LayoutDom { assert_in_layout(); LayoutDom { - value: self.ptr.as_ref(), + value: self.as_ptr().as_ref().unwrap(), } } } -impl Dom { - /// Create a `Dom` from a `&T` - #[cfg_attr(crown, allow(crown::unrooted_must_root))] - pub(crate) fn from_ref(obj: &T) -> Dom { - assert_in_script(); - Dom { - ptr: ptr::NonNull::from(obj), - } - } - - /// Return a rooted version of this DOM object ([`DomRoot`]) suitable for use on the stack. - pub(crate) fn as_rooted(&self) -> DomRoot { - DomRoot::from_ref(self) - } -} - -impl Deref for Dom { - type Target = T; - - fn deref(&self) -> &T { - assert_in_script(); - // We can only have &Dom from a rooted thing, so it's safe to deref - // it to &T. - unsafe { &*self.ptr.as_ptr() } - } -} - -unsafe impl JSTraceable for Dom { - unsafe fn trace(&self, trc: *mut JSTracer) { - let trace_string; - let trace_info = if cfg!(debug_assertions) { - trace_string = format!("for {} on heap", ::std::any::type_name::()); - &trace_string[..] - } else { - "for DOM object on heap" - }; - trace_reflector(trc, trace_info, (*self.ptr.as_ptr()).reflector()); - } -} - -/// A traced reference to a DOM object that may not be reflected yet. -#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -pub(crate) struct MaybeUnreflectedDom { - ptr: ptr::NonNull, -} - -impl MaybeUnreflectedDom -where - T: DomObject, -{ - #[cfg_attr(crown, allow(crown::unrooted_must_root))] - pub(crate) unsafe fn from_box(value: Box) -> Self { - Self { - ptr: Box::leak(value).into(), - } - } -} - -impl Root> -where - T: DomObject, -{ - pub(crate) fn as_ptr(&self) -> *const T { - self.value.ptr.as_ptr() - } -} - -impl Root> -where - T: MutDomObject, -{ - pub(crate) unsafe fn reflect_with(self, obj: *mut JSObject) -> DomRoot { - let ptr = self.as_ptr(); - drop(self); - let root = DomRoot::from_ref(&*ptr); - root.init_reflector(obj); - root - } -} - /// An unrooted reference to a DOM object for use in layout. `Layout*Helpers` /// traits must be implemented on this. #[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_interior)] @@ -498,20 +151,6 @@ where impl Copy for LayoutDom<'_, T> {} -impl PartialEq for Dom { - fn eq(&self, other: &Dom) -> bool { - self.ptr.as_ptr() == other.ptr.as_ptr() - } -} - -impl<'a, T: DomObject> PartialEq<&'a T> for Dom { - fn eq(&self, other: &&'a T) -> bool { - *self == Dom::from_ref(*other) - } -} - -impl Eq for Dom {} - impl PartialEq for LayoutDom<'_, T> { fn eq(&self, other: &Self) -> bool { std::ptr::eq(self.value, other.value) @@ -520,27 +159,12 @@ impl PartialEq for LayoutDom<'_, T> { impl Eq for LayoutDom<'_, T> {} -impl Hash for Dom { - fn hash(&self, state: &mut H) { - self.ptr.as_ptr().hash(state) - } -} - impl Hash for LayoutDom<'_, T> { fn hash(&self, state: &mut H) { (self.value as *const T).hash(state) } } -impl Clone for Dom { - #[inline] - #[cfg_attr(crown, allow(crown::unrooted_must_root))] - fn clone(&self) -> Self { - assert_in_script(); - Dom { ptr: self.ptr } - } -} - impl Clone for LayoutDom<'_, T> { #[inline] #[allow(clippy::non_canonical_clone_impl)] @@ -616,10 +240,6 @@ impl PartialEq for MutDom { } } -pub(crate) fn assert_in_script() { - debug_assert!(thread_state::get().is_script()); -} - pub(crate) fn assert_in_layout() { debug_assert!(thread_state::get().is_layout()); } diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index fc9df6e89e9..a25cfa83774 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -42,12 +42,13 @@ use indexmap::IndexMap; /// A trait to allow tracing (only) DOM objects. pub(crate) use js::gc::Traceable as JSTraceable; pub(crate) use js::gc::{RootableVec, RootedVec}; -use js::glue::{CallObjectTracer, CallScriptTracer, CallStringTracer, CallValueTracer}; -use js::jsapi::{GCTraceKindToAscii, Heap, JSObject, JSScript, JSString, JSTracer, TraceKind}; +use js::glue::{CallScriptTracer, CallStringTracer, CallValueTracer}; +use js::jsapi::{GCTraceKindToAscii, Heap, JSScript, JSString, JSTracer, TraceKind}; use js::jsval::JSVal; use js::rust::{GCMethods, Handle}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use parking_lot::RwLock; +pub(crate) use script_bindings::trace::*; use servo_arc::Arc as ServoArc; use smallvec::SmallVec; use style::author_styles::AuthorStyles; @@ -61,7 +62,7 @@ use webxr_api::{Finger, Hand}; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::error::Error; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; -use crate::dom::bindings::reflector::{DomObject, Reflector}; +use crate::dom::bindings::reflector::DomObject; use crate::dom::htmlimageelement::SourceSet; use crate::dom::htmlmediaelement::HTMLMediaElementFetchContext; use crate::dom::windowproxy::WindowProxyHandler; @@ -285,25 +286,6 @@ pub(crate) fn trace_jsval(tracer: *mut JSTracer, description: &str, val: &Heap) { - unsafe { - trace!("tracing {}", description); - CallObjectTracer( - tracer, - obj.ptr.get() as *mut _, - GCTraceKindToAscii(TraceKind::Object), - ); - } -} - #[allow(dead_code)] /// Trace a `JSString`. pub(crate) fn trace_string(tracer: *mut JSTracer, description: &str, s: &Heap<*mut JSString>) { diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index ac8c86cfb71..2b746ec75a1 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -111,7 +111,7 @@ use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementType use crate::dom::bindings::num::Finite; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomGlobal}; -use crate::dom::bindings::root::{Dom, DomRoot, DomSlice, LayoutDom, MutNullableDom}; +use crate::dom::bindings::root::{Dom, DomRoot, DomSlice, LayoutDom, MutNullableDom, ToLayout}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::trace::{HashMapTracedValues, NoTrace}; #[cfg(feature = "webgpu")] diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 7a6936611df..dbdff1f5586 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -86,7 +86,7 @@ use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::DomObject; -use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom, ToLayout}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::xmlname::{ matches_name_production, namespace_from_domstring, validate_and_extract, @@ -162,7 +162,7 @@ use crate::task::TaskOnce; /// #[dom_struct] -pub(crate) struct Element { +pub struct Element { node: Node, #[no_trace] local_name: LocalName, @@ -203,12 +203,6 @@ impl fmt::Debug for Element { } } -impl fmt::Debug for DomRoot { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - (**self).fmt(f) - } -} - #[derive(MallocSizeOf, PartialEq)] pub(crate) enum ElementCreator { ParserCreated(u64), @@ -3025,7 +3019,11 @@ impl ElementMethods for Element { let quirks_mode = doc.quirks_mode(); let element = DomRoot::from_ref(self); - Ok(dom_apis::element_matches(&element, &selectors, quirks_mode)) + Ok(dom_apis::element_matches( + &SelectorWrapper::Borrowed(&element), + &selectors, + quirks_mode, + )) } // https://dom.spec.whatwg.org/#dom-element-webkitmatchesselector @@ -3047,10 +3045,11 @@ impl ElementMethods for Element { let quirks_mode = doc.quirks_mode(); Ok(dom_apis::element_closest( - DomRoot::from_ref(self), + SelectorWrapper::Owned(DomRoot::from_ref(self)), &selectors, quirks_mode, - )) + ) + .map(SelectorWrapper::into_owned)) } // https://dom.spec.whatwg.org/#dom-element-insertadjacentelement @@ -3831,7 +3830,43 @@ impl VirtualMethods for Element { } } -impl SelectorsElement for DomRoot { +#[derive(Clone, PartialEq)] +/// A type that wraps a DomRoot value so we can implement the SelectorsElement +/// trait without violating the orphan rule. Since the trait assumes that the +/// return type and self type of various methods is the same type that it is +/// implemented against, we need to be able to represent multiple ownership styles. +pub enum SelectorWrapper<'a> { + Borrowed(&'a DomRoot), + Owned(DomRoot), +} + +impl fmt::Debug for SelectorWrapper<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.deref().fmt(f) + } +} + +impl Deref for SelectorWrapper<'_> { + type Target = DomRoot; + + fn deref(&self) -> &Self::Target { + match self { + SelectorWrapper::Owned(r) => r, + SelectorWrapper::Borrowed(r) => r, + } + } +} + +impl SelectorWrapper<'_> { + fn into_owned(self) -> DomRoot { + match self { + SelectorWrapper::Owned(r) => r, + SelectorWrapper::Borrowed(r) => r.clone(), + } + } +} + +impl SelectorsElement for SelectorWrapper<'_> { type Impl = SelectorImpl; #[allow(unsafe_code)] @@ -3839,8 +3874,10 @@ impl SelectorsElement for DomRoot { ::selectors::OpaqueElement::new(unsafe { &*self.reflector().get_jsobject().get() }) } - fn parent_element(&self) -> Option> { - self.upcast::().GetParentElement() + fn parent_element(&self) -> Option { + self.upcast::() + .GetParentElement() + .map(SelectorWrapper::Owned) } fn parent_node_is_shadow_root(&self) -> bool { @@ -3853,6 +3890,7 @@ impl SelectorsElement for DomRoot { fn containing_shadow_host(&self) -> Option { self.containing_shadow_root() .map(|shadow_root| shadow_root.Host()) + .map(SelectorWrapper::Owned) } fn is_pseudo_element(&self) -> bool { @@ -3867,22 +3905,24 @@ impl SelectorsElement for DomRoot { false } - fn prev_sibling_element(&self) -> Option> { + fn prev_sibling_element(&self) -> Option { self.node .preceding_siblings() .filter_map(DomRoot::downcast) .next() + .map(SelectorWrapper::Owned) } - fn next_sibling_element(&self) -> Option> { + fn next_sibling_element(&self) -> Option { self.node .following_siblings() .filter_map(DomRoot::downcast) .next() + .map(SelectorWrapper::Owned) } - fn first_element_child(&self) -> Option> { - self.GetFirstElementChild() + fn first_element_child(&self) -> Option { + self.GetFirstElementChild().map(SelectorWrapper::Owned) } fn attr_matches( @@ -4039,7 +4079,7 @@ impl SelectorsElement for DomRoot { if !self_flags.is_empty() { #[allow(unsafe_code)] unsafe { - Dom::from_ref(self.deref()) + Dom::from_ref(&***self) .to_layout() .insert_selector_flags(self_flags); } @@ -4051,7 +4091,7 @@ impl SelectorsElement for DomRoot { if let Some(p) = self.parent_element() { #[allow(unsafe_code)] unsafe { - Dom::from_ref(p.deref()) + Dom::from_ref(&**p) .to_layout() .insert_selector_flags(parent_flags); } diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index 4e36395707a..8ab61de9d26 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -46,7 +46,7 @@ use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::num::Finite; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::{DomGlobal, DomObject}; -use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom}; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, ToLayout}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::blob::Blob; use crate::dom::canvasrenderingcontext2d::{ diff --git a/components/script/dom/messageport.rs b/components/script/dom/messageport.rs index bfbd043fc8e..6fbd787e173 100644 --- a/components/script/dom/messageport.rs +++ b/components/script/dom/messageport.rs @@ -96,6 +96,7 @@ impl MessagePort { } /// + #[allow(unsafe_code)] fn post_message_impl( &self, cx: SafeJSContext, @@ -115,7 +116,7 @@ impl MessagePort { let ports = transfer .iter() - .filter_map(|&obj| root_from_object::(obj, *cx).ok()); + .filter_map(|&obj| unsafe { root_from_object::(obj, *cx).ok() }); for port in ports { // Step 2 if port.message_port_id() == self.message_port_id() { diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 806c95e7ab7..73467d8da37 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -76,7 +76,7 @@ use crate::dom::bindings::inheritance::{ }; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, DomObjectWrap}; -use crate::dom::bindings::root::{Dom, DomRoot, DomSlice, LayoutDom, MutNullableDom}; +use crate::dom::bindings::root::{Dom, DomRoot, DomSlice, LayoutDom, MutNullableDom, ToLayout}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::xmlname::namespace_from_domstring; use crate::dom::characterdata::{CharacterData, LayoutCharacterDataHelpers}; @@ -85,7 +85,7 @@ use crate::dom::customelementregistry::{try_upgrade_element, CallbackReaction}; use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument}; use crate::dom::documentfragment::DocumentFragment; use crate::dom::documenttype::DocumentType; -use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator}; +use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator, SelectorWrapper}; use crate::dom::event::{Event, EventBubbles, EventCancelable}; use crate::dom::eventtarget::EventTarget; use crate::dom::htmlbodyelement::HTMLBodyElement; @@ -178,12 +178,6 @@ impl fmt::Debug for Node { } } -impl fmt::Debug for DomRoot { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - (**self).fmt(f) - } -} - /// Flags for node items #[derive(Clone, Copy, JSTraceable, MallocSizeOf)] pub(crate) struct NodeFlags(u16); @@ -523,7 +517,11 @@ impl Iterator for QuerySelectorIterator { MatchingForInvalidation::No, ); if let Some(element) = DomRoot::downcast(node) { - if matches_selector_list(selectors, &element, &mut ctx) { + if matches_selector_list( + selectors, + &SelectorWrapper::Borrowed(&element), + &mut ctx, + ) { return Some(DomRoot::upcast(element)); } } @@ -1042,9 +1040,9 @@ impl Node { let mut descendants = self.traverse_preorder(ShadowIncluding::No); // Skip the root of the tree. assert!(&*descendants.next().unwrap() == self); - Ok(descendants - .filter_map(DomRoot::downcast) - .find(|element| matches_selector_list(&selectors, element, &mut ctx))) + Ok(descendants.filter_map(DomRoot::downcast).find(|element| { + matches_selector_list(&selectors, &SelectorWrapper::Borrowed(element), &mut ctx) + })) }, } } diff --git a/components/script/dom/webgl2renderingcontext.rs b/components/script/dom/webgl2renderingcontext.rs index c7cc25526d5..d7f122d24ce 100644 --- a/components/script/dom/webgl2renderingcontext.rs +++ b/components/script/dom/webgl2renderingcontext.rs @@ -40,7 +40,7 @@ use crate::dom::bindings::codegen::UnionTypes::{ }; use crate::dom::bindings::error::{ErrorResult, Fallible}; use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal, Reflector}; -use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom, ToLayout}; use crate::dom::bindings::str::DOMString; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlcanvaselement::LayoutCanvasRenderingContextHelpers; diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index 20b7895c0ea..d7ee456ba8b 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -59,7 +59,7 @@ use crate::dom::workernavigator::WorkerNavigator; use crate::fetch; use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender}; use crate::realms::{enter_realm, InRealm}; -use crate::script_runtime::{CanGc, JSContext, Runtime}; +use crate::script_runtime::{CanGc, JSContext, JSContextHelper, Runtime}; use crate::task::TaskCanceller; use crate::timers::{IsInterval, TimerCallback}; diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs index 71e93671f47..584555c2d0f 100644 --- a/components/script/script_runtime.rs +++ b/components/script/script_runtime.rs @@ -931,27 +931,21 @@ unsafe fn set_gc_zeal_options(cx: *mut RawJSContext) { #[cfg(not(feature = "debugmozjs"))] unsafe fn set_gc_zeal_options(_: *mut RawJSContext) {} -#[derive(Clone, Copy)] -#[repr(transparent)] -pub(crate) struct JSContext(*mut RawJSContext); +pub(crate) use script_bindings::script_runtime::JSContext; -#[allow(unsafe_code)] -impl JSContext { - /// Create a new [`JSContext`] object from the given raw pointer. - /// - /// # Safety - /// - /// The `RawJSContext` argument must point to a valid `RawJSContext` in memory. - pub(crate) unsafe fn from_ptr(raw_js_context: *mut RawJSContext) -> Self { - JSContext(raw_js_context) - } +/// Extra methods for the JSContext type defined in script_bindings, when +/// the methods are only called by code in the script crate. +pub(crate) trait JSContextHelper { + fn get_reports(&self, path_seg: String) -> Vec; +} +impl JSContextHelper for JSContext { #[allow(unsafe_code)] - pub(crate) fn get_reports(&self, path_seg: String) -> Vec { + fn get_reports(&self, path_seg: String) -> Vec { SEEN_POINTERS.with(|pointers| pointers.borrow_mut().clear()); let stats = unsafe { let mut stats = ::std::mem::zeroed(); - if !CollectServoSizes(self.0, &mut stats, Some(get_size)) { + if !CollectServoSizes(**self, &mut stats, Some(get_size)) { return vec![]; } stats @@ -1007,15 +1001,6 @@ impl JSContext { } } -#[allow(unsafe_code)] -impl Deref for JSContext { - type Target = *mut RawJSContext; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - pub(crate) struct StreamConsumer(*mut JSStreamConsumer); #[allow(unsafe_code)] @@ -1171,18 +1156,4 @@ impl Runnable { } } -#[derive(Clone, Copy, Debug)] -/// A compile-time marker that there are operations that could trigger a JS garbage collection -/// operation within the current stack frame. It is trivially copyable, so it should be passed -/// as a function argument and reused when calling other functions whenever possible. Since it -/// is only meaningful within the current stack frame, it is impossible to move it to a different -/// thread or into a task that will execute asynchronously. -pub(crate) struct CanGc(std::marker::PhantomData<*mut ()>); - -impl CanGc { - /// Create a new CanGc value, representing that a GC operation is possible within the - /// current stack frame. - pub(crate) fn note() -> CanGc { - CanGc(std::marker::PhantomData) - } -} +pub(crate) use script_bindings::script_runtime::CanGc; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index b11c814b604..9938a818e96 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -149,7 +149,7 @@ use crate::navigation::{InProgressLoad, NavigationListener}; use crate::realms::enter_realm; use crate::script_module::ScriptFetchOptions; use crate::script_runtime::{ - CanGc, JSContext, Runtime, ScriptThreadEventCategory, ThreadSafeJSContext, + CanGc, JSContext, JSContextHelper, Runtime, ScriptThreadEventCategory, ThreadSafeJSContext, }; use crate::task_queue::TaskQueue; use crate::task_source::{SendableTaskSource, TaskSourceName}; diff --git a/components/script/dom/bindings/constant.rs b/components/script_bindings/constant.rs similarity index 86% rename from components/script/dom/bindings/constant.rs rename to components/script_bindings/constant.rs index 7364ee2086d..810c302fa8c 100644 --- a/components/script/dom/bindings/constant.rs +++ b/components/script_bindings/constant.rs @@ -8,6 +8,7 @@ use std::ffi::CStr; use js::jsapi::{JSPROP_ENUMERATE, JSPROP_PERMANENT, JSPROP_READONLY}; use js::jsval::{BooleanValue, DoubleValue, Int32Value, JSVal, NullValue, UInt32Value}; +use js::rooted; use js::rust::wrappers::JS_DefineProperty; use js::rust::HandleObject; @@ -15,17 +16,17 @@ use crate::script_runtime::JSContext; /// Representation of an IDL constant. #[derive(Clone)] -pub(crate) struct ConstantSpec { +pub struct ConstantSpec { /// name of the constant. - pub(crate) name: &'static CStr, + pub name: &'static CStr, /// value of the constant. - pub(crate) value: ConstantVal, + pub value: ConstantVal, } /// Representation of an IDL constant value. #[derive(Clone)] #[allow(dead_code)] -pub(crate) enum ConstantVal { +pub enum ConstantVal { /// `long` constant. Int(i32), /// `unsigned long` constant. @@ -40,7 +41,7 @@ pub(crate) enum ConstantVal { impl ConstantSpec { /// Returns a `JSVal` that represents the value of this `ConstantSpec`. - pub(crate) fn get_value(&self) -> JSVal { + pub fn get_value(&self) -> JSVal { match self.value { ConstantVal::Null => NullValue(), ConstantVal::Int(i) => Int32Value(i), @@ -53,7 +54,7 @@ impl ConstantSpec { /// Defines constants on `obj`. /// Fails on JSAPI failure. -pub(crate) fn define_constants(cx: JSContext, obj: HandleObject, constants: &[ConstantSpec]) { +pub fn define_constants(cx: JSContext, obj: HandleObject, constants: &[ConstantSpec]) { for spec in constants { rooted!(in(*cx) let value = spec.get_value()); unsafe { diff --git a/components/script_bindings/conversions.rs b/components/script_bindings/conversions.rs index e135acd2af5..43754a61ab9 100644 --- a/components/script_bindings/conversions.rs +++ b/components/script_bindings/conversions.rs @@ -8,18 +8,23 @@ use js::conversions::{ latin1_to_string, ConversionResult, FromJSValConvertible, ToJSValConvertible, }; use js::error::throw_type_error; -use js::glue::{GetProxyHandlerExtra, IsProxyHandlerFamily}; +use js::glue::{ + GetProxyHandlerExtra, GetProxyReservedSlot, IsProxyHandlerFamily, IsWrapper, + JS_GetReservedSlot, UnwrapObjectDynamic, +}; use js::jsapi::{ JSContext, JSObject, JSString, JS_DeprecatedStringHasLatin1Chars, JS_GetLatin1StringCharsAndLength, JS_GetTwoByteStringCharsAndLength, JS_NewStringCopyN, }; -use js::jsval::{ObjectValue, StringValue}; +use js::jsval::{ObjectValue, StringValue, UndefinedValue}; use js::rust::{ - get_object_class, is_dom_class, maybe_wrap_value, HandleValue, MutableHandleValue, ToString, + get_object_class, is_dom_class, is_dom_object, maybe_wrap_value, HandleValue, + MutableHandleValue, ToString, }; use crate::inheritance::Castable; -use crate::reflector::Reflector; +use crate::reflector::{DomObject, Reflector}; +use crate::root::DomRoot; use crate::str::{ByteString, DOMString, USVString}; use crate::utils::{DOMClass, DOMJSClass}; @@ -199,6 +204,27 @@ impl ToJSValConvertible for Reflector { } } +impl FromJSValConvertible for DomRoot { + type Config = (); + + unsafe fn from_jsval( + cx: *mut JSContext, + value: HandleValue, + _config: Self::Config, + ) -> Result>, ()> { + Ok(match root_from_handlevalue(value, cx) { + Ok(result) => ConversionResult::Success(result), + Err(()) => ConversionResult::Failure("value is not an object".into()), + }) + } +} + +impl ToJSValConvertible for DomRoot { + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { + self.reflector().to_jsval(cx, rval); + } +} + /// Get the `DOMClass` from `obj`, or `Err(())` if `obj` is not a DOM object. /// /// # Safety @@ -230,3 +256,134 @@ pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool { ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj) } } + +/// The index of the slot wherein a pointer to the reflected DOM object is +/// stored for non-proxy bindings. +// We use slot 0 for holding the raw object. This is safe for both +// globals and non-globals. +pub const DOM_OBJECT_SLOT: u32 = 0; + +/// Get the private pointer of a DOM object from a given reflector. +/// +/// # Safety +/// obj must point to a valid non-null JS object. +pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void { + let mut value = UndefinedValue(); + if is_dom_object(obj) { + JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value); + } else { + debug_assert!(is_dom_proxy(obj)); + GetProxyReservedSlot(obj, 0, &mut value); + }; + if value.is_undefined() { + ptr::null() + } else { + value.to_private() + } +} + +pub enum PrototypeCheck { + Derive(fn(&'static DOMClass) -> bool), + Depth { depth: usize, proto_id: u16 }, +} + +/// Get a `*const libc::c_void` for the given DOM object, unwrapping any +/// wrapper around it first, and checking if the object is of the correct type. +/// +/// Returns Err(()) if `obj` is an opaque security wrapper or if the object is +/// not an object for a DOM object of the given type (as defined by the +/// proto_id and proto_depth). +/// +/// # Safety +/// obj must point to a valid, non-null JS object. +/// cx must point to a valid, non-null JS context. +#[inline] +#[allow(clippy::result_unit_err)] +pub unsafe fn private_from_proto_check( + mut obj: *mut JSObject, + cx: *mut JSContext, + proto_check: PrototypeCheck, +) -> Result<*const libc::c_void, ()> { + let dom_class = get_dom_class(obj).or_else(|_| { + if IsWrapper(obj) { + trace!("found wrapper"); + obj = UnwrapObjectDynamic(obj, cx, /* stopAtWindowProxy = */ false); + if obj.is_null() { + trace!("unwrapping security wrapper failed"); + Err(()) + } else { + assert!(!IsWrapper(obj)); + trace!("unwrapped successfully"); + get_dom_class(obj) + } + } else { + trace!("not a dom wrapper"); + Err(()) + } + })?; + + let prototype_matches = match proto_check { + PrototypeCheck::Derive(f) => (f)(dom_class), + PrototypeCheck::Depth { depth, proto_id } => { + dom_class.interface_chain[depth] as u16 == proto_id + }, + }; + + if prototype_matches { + trace!("good prototype"); + Ok(private_from_object(obj)) + } else { + trace!("bad prototype"); + Err(()) + } +} + +/// Get a `*const T` for a DOM object accessible from a `JSObject`. +/// +/// # Safety +/// obj must point to a valid, non-null JS object. +/// cx must point to a valid, non-null JS context. +#[allow(clippy::result_unit_err)] +pub unsafe fn native_from_object(obj: *mut JSObject, cx: *mut JSContext) -> Result<*const T, ()> +where + T: DomObject + IDLInterface, +{ + unsafe { + private_from_proto_check(obj, cx, PrototypeCheck::Derive(T::derives)) + .map(|ptr| ptr as *const T) + } +} + +/// Get a `DomRoot` for the given DOM object, unwrapping any wrapper +/// around it first, and checking if the object is of the correct type. +/// +/// Returns Err(()) if `obj` is an opaque security wrapper or if the object is +/// not a reflector for a DOM object of the given type (as defined by the +/// proto_id and proto_depth). +/// +/// # Safety +/// obj must point to a valid, non-null JS object. +/// cx must point to a valid, non-null JS context. +#[allow(clippy::result_unit_err)] +pub unsafe fn root_from_object(obj: *mut JSObject, cx: *mut JSContext) -> Result, ()> +where + T: DomObject + IDLInterface, +{ + native_from_object(obj, cx).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) }) +} + +/// Get a `DomRoot` for a DOM object accessible from a `HandleValue`. +/// Caller is responsible for throwing a JS exception if needed in case of error. +/// +/// # Safety +/// cx must point to a valid, non-null JS context. +#[allow(clippy::result_unit_err)] +pub unsafe fn root_from_handlevalue(v: HandleValue, cx: *mut JSContext) -> Result, ()> +where + T: DomObject + IDLInterface, +{ + if !v.get().is_object() { + return Err(()); + } + root_from_object(v.get().to_object(), cx) +} diff --git a/components/script_bindings/lib.rs b/components/script_bindings/lib.rs index 1fb5a5f48b0..7503580b73d 100644 --- a/components/script_bindings/lib.rs +++ b/components/script_bindings/lib.rs @@ -18,12 +18,14 @@ extern crate log; extern crate malloc_size_of_derive; pub mod callback; +pub mod constant; pub mod conversions; pub mod inheritance; pub mod reflector; +pub mod root; pub mod script_runtime; pub mod str; -mod trace; +pub mod trace; pub mod utils; #[allow(non_snake_case)] diff --git a/components/script_bindings/root.rs b/components/script_bindings/root.rs new file mode 100644 index 00000000000..f1d1510b83c --- /dev/null +++ b/components/script_bindings/root.rs @@ -0,0 +1,432 @@ +/* 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/. */ + +use std::cell::{Cell, UnsafeCell}; +use std::hash::{Hash, Hasher}; +use std::ops::Deref; +use std::{fmt, mem, ptr}; + +use js::gc::Traceable as JSTraceable; +use js::jsapi::{JSObject, JSTracer}; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use style::thread_state; + +use crate::conversions::DerivedFrom; +use crate::inheritance::Castable; +use crate::reflector::{DomObject, MutDomObject, Reflector}; +use crate::trace::trace_reflector; + +/// A rooted value. +#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_interior)] +pub struct Root { + /// The value to root. + value: T, + /// List that ensures correct dynamic root ordering + root_list: *const RootCollection, +} + +impl Root +where + T: StableTraceObject + 'static, +{ + /// Create a new stack-bounded root for the provided value. + /// It gives out references which cannot outlive this new `Root`. + /// + /// # Safety + /// It must not outlive its associated `RootCollection`. + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub unsafe fn new(value: T) -> Self { + unsafe fn add_to_root_list(object: *const dyn JSTraceable) -> *const RootCollection { + assert_in_script(); + STACK_ROOTS.with(|root_list| { + let root_list = &*root_list.get().unwrap(); + root_list.root(object); + root_list + }) + } + + let root_list = add_to_root_list(value.stable_trace_object()); + Root { value, root_list } + } +} + +/// `StableTraceObject` represents values that can be rooted through a stable address that will +/// not change for their whole lifetime. +/// It is an unsafe trait that requires implementors to ensure certain safety guarantees. +/// +/// # Safety +/// +/// Implementors of this trait must ensure that the `trace` method correctly accounts for all +/// owned and referenced objects, so that the garbage collector can accurately determine which +/// objects are still in use. Failing to adhere to this contract may result in undefined behavior, +/// such as use-after-free errors. +pub unsafe trait StableTraceObject { + /// Returns a stable trace object which address won't change for the whole + /// lifetime of the value. + fn stable_trace_object(&self) -> *const dyn JSTraceable; +} + +unsafe impl StableTraceObject for Dom +where + T: DomObject, +{ + fn stable_trace_object(&self) -> *const dyn JSTraceable { + // The JSTraceable impl for Reflector doesn't actually do anything, + // so we need this shenanigan to actually trace the reflector of the + // T pointer in Dom. + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + struct ReflectorStackRoot(Reflector); + unsafe impl JSTraceable for ReflectorStackRoot { + unsafe fn trace(&self, tracer: *mut JSTracer) { + trace_reflector(tracer, "on stack", &self.0); + } + } + unsafe { &*(self.reflector() as *const Reflector as *const ReflectorStackRoot) } + } +} + +unsafe impl StableTraceObject for MaybeUnreflectedDom +where + T: DomObject, +{ + fn stable_trace_object(&self) -> *const dyn JSTraceable { + // The JSTraceable impl for Reflector doesn't actually do anything, + // so we need this shenanigan to actually trace the reflector of the + // T pointer in Dom. + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + struct MaybeUnreflectedStackRoot(T); + unsafe impl JSTraceable for MaybeUnreflectedStackRoot + where + T: DomObject, + { + unsafe fn trace(&self, tracer: *mut JSTracer) { + if self.0.reflector().get_jsobject().is_null() { + self.0.trace(tracer); + } else { + trace_reflector(tracer, "on stack", self.0.reflector()); + } + } + } + unsafe { &*(self.ptr.as_ptr() as *const T as *const MaybeUnreflectedStackRoot) } + } +} + +impl Deref for Root +where + T: Deref + StableTraceObject, +{ + type Target = ::Target; + + fn deref(&self) -> &Self::Target { + assert_in_script(); + &self.value + } +} + +impl Drop for Root +where + T: StableTraceObject, +{ + fn drop(&mut self) { + unsafe { + (*self.root_list).unroot(self.value.stable_trace_object()); + } + } +} + +impl fmt::Debug for Root { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.value.fmt(f) + } +} + +impl fmt::Debug for Dom { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + (**self).fmt(f) + } +} + +/// A traced reference to a DOM object +/// +/// This type is critical to making garbage collection work with the DOM, +/// but it is very dangerous; if garbage collection happens with a `Dom` +/// on the stack, the `Dom` can point to freed memory. +/// +/// This should only be used as a field in other DOM objects. +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +#[repr(transparent)] +pub struct Dom { + ptr: ptr::NonNull, +} + +// Dom is similar to Rc, in that it's not always clear how to avoid double-counting. +// For now, we choose not to follow any such pointers. +impl MallocSizeOf for Dom { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + 0 + } +} + +impl PartialEq for Dom { + fn eq(&self, other: &Dom) -> bool { + self.ptr.as_ptr() == other.ptr.as_ptr() + } +} + +impl<'a, T: DomObject> PartialEq<&'a T> for Dom { + fn eq(&self, other: &&'a T) -> bool { + *self == Dom::from_ref(*other) + } +} + +impl Eq for Dom {} + +impl Hash for Dom { + fn hash(&self, state: &mut H) { + self.ptr.as_ptr().hash(state) + } +} + +impl Clone for Dom { + #[inline] + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + fn clone(&self) -> Self { + assert_in_script(); + Dom { ptr: self.ptr } + } +} + +impl Dom { + /// Create a `Dom` from a `&T` + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub fn from_ref(obj: &T) -> Dom { + assert_in_script(); + Dom { + ptr: ptr::NonNull::from(obj), + } + } + + /// Return a rooted version of this DOM object ([`DomRoot`]) suitable for use on the stack. + pub fn as_rooted(&self) -> DomRoot { + DomRoot::from_ref(self) + } + + pub fn as_ptr(&self) -> *const T { + self.ptr.as_ptr() + } +} + +impl Deref for Dom { + type Target = T; + + fn deref(&self) -> &T { + assert_in_script(); + // We can only have &Dom from a rooted thing, so it's safe to deref + // it to &T. + unsafe { &*self.ptr.as_ptr() } + } +} + +unsafe impl JSTraceable for Dom { + unsafe fn trace(&self, trc: *mut JSTracer) { + let trace_string; + let trace_info = if cfg!(debug_assertions) { + trace_string = format!("for {} on heap", ::std::any::type_name::()); + &trace_string[..] + } else { + "for DOM object on heap" + }; + trace_reflector(trc, trace_info, (*self.ptr.as_ptr()).reflector()); + } +} + +/// A traced reference to a DOM object that may not be reflected yet. +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +pub struct MaybeUnreflectedDom { + ptr: ptr::NonNull, +} + +impl MaybeUnreflectedDom +where + T: DomObject, +{ + /// Create a new MaybeUnreflectedDom value from the given boxed DOM object. + /// + /// # Safety + /// TODO: unclear why this is marked unsafe. + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub unsafe fn from_box(value: Box) -> Self { + Self { + ptr: Box::leak(value).into(), + } + } +} + +impl Root> +where + T: DomObject, +{ + pub fn as_ptr(&self) -> *const T { + self.value.ptr.as_ptr() + } +} + +impl Root> +where + T: MutDomObject, +{ + /// Treat the given JS object as the reflector of this unreflected object. + /// + /// # Safety + /// obj must point to a valid, non-null JS object. + pub unsafe fn reflect_with(self, obj: *mut JSObject) -> DomRoot { + let ptr = self.as_ptr(); + drop(self); + let root = DomRoot::from_ref(&*ptr); + root.init_reflector(obj); + root + } +} + +/// A rooted reference to a DOM object. +pub type DomRoot = Root>; + +impl DomRoot { + /// Cast a DOM object root upwards to one of the interfaces it derives from. + pub fn upcast(root: DomRoot) -> DomRoot + where + U: Castable, + T: DerivedFrom, + { + unsafe { mem::transmute::, DomRoot>(root) } + } + + /// Cast a DOM object root downwards to one of the interfaces it might implement. + pub fn downcast(root: DomRoot) -> Option> + where + U: DerivedFrom, + { + if root.is::() { + Some(unsafe { mem::transmute::, DomRoot>(root) }) + } else { + None + } + } +} + +impl DomRoot { + /// Generate a new root from a reference + pub fn from_ref(unrooted: &T) -> DomRoot { + unsafe { DomRoot::new(Dom::from_ref(unrooted)) } + } + + /// Create a traced version of this rooted object. + /// + /// # Safety + /// + /// This should never be used to create on-stack values. Instead these values should always + /// end up as members of other DOM objects. + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub fn as_traced(&self) -> Dom { + Dom::from_ref(self) + } +} + +impl MallocSizeOf for DomRoot +where + T: DomObject + MallocSizeOf, +{ + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + (**self).size_of(ops) + } +} + +impl PartialEq for DomRoot +where + T: DomObject, +{ + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl Clone for DomRoot +where + T: DomObject, +{ + fn clone(&self) -> DomRoot { + DomRoot::from_ref(self) + } +} + +unsafe impl JSTraceable for DomRoot +where + T: DomObject, +{ + unsafe fn trace(&self, _: *mut JSTracer) { + // Already traced. + } +} + +/// A rooting mechanism for reflectors on the stack. +/// LIFO is not required. +/// +/// See also [*Exact Stack Rooting - Storing a GCPointer on the CStack*][cstack]. +/// +/// [cstack]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Internals/GC/Exact_Stack_Rooting +pub struct RootCollection { + roots: UnsafeCell>, +} + +impl RootCollection { + /// Create an empty collection of roots + #[allow(clippy::new_without_default)] + pub fn new() -> RootCollection { + assert_in_script(); + RootCollection { + roots: UnsafeCell::new(vec![]), + } + } + + /// Starts tracking a trace object. + unsafe fn root(&self, object: *const dyn JSTraceable) { + assert_in_script(); + (*self.roots.get()).push(object); + } + + /// Stops tracking a trace object, asserting if it isn't found. + unsafe fn unroot(&self, object: *const dyn JSTraceable) { + assert_in_script(); + let roots = &mut *self.roots.get(); + match roots + .iter() + .rposition(|r| std::ptr::addr_eq(*r as *const (), object as *const ())) + { + Some(idx) => { + roots.remove(idx); + }, + None => panic!("Can't remove a root that was never rooted!"), + } + } +} + +thread_local!(pub static STACK_ROOTS: Cell> = const { Cell::new(None) }); + +/// SM Callback that traces the rooted reflectors +/// +/// # Safety +/// tracer must point to a valid, non-null JS tracer object. +pub unsafe fn trace_roots(tracer: *mut JSTracer) { + trace!("tracing stack roots"); + STACK_ROOTS.with(|collection| { + let collection = &*(*collection.get().unwrap()).roots.get(); + for root in collection { + (**root).trace(tracer); + } + }); +} + +pub fn assert_in_script() { + debug_assert!(thread_state::get().is_script()); +} diff --git a/components/script_bindings/script_runtime.rs b/components/script_bindings/script_runtime.rs index e5cbde74c9e..035972764e3 100644 --- a/components/script_bindings/script_runtime.rs +++ b/components/script_bindings/script_runtime.rs @@ -3,6 +3,35 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::Cell; +use std::marker::PhantomData; +use std::ops::Deref; + +use js::jsapi::JSContext as RawJSContext; + +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct JSContext(*mut RawJSContext); + +#[allow(unsafe_code)] +impl JSContext { + /// Create a new [`JSContext`] object from the given raw pointer. + /// + /// # Safety + /// + /// The `RawJSContext` argument must point to a valid `RawJSContext` in memory. + pub unsafe fn from_ptr(raw_js_context: *mut RawJSContext) -> Self { + JSContext(raw_js_context) + } +} + +#[allow(unsafe_code)] +impl Deref for JSContext { + type Target = *mut RawJSContext; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} thread_local!( static THREAD_ACTIVE: Cell = const { Cell::new(true) }; @@ -15,3 +44,19 @@ pub fn runtime_is_alive() -> bool { pub fn mark_runtime_dead() { THREAD_ACTIVE.with(|t| t.set(false)); } + +#[derive(Clone, Copy, Debug)] +/// A compile-time marker that there are operations that could trigger a JS garbage collection +/// operation within the current stack frame. It is trivially copyable, so it should be passed +/// as a function argument and reused when calling other functions whenever possible. Since it +/// is only meaningful within the current stack frame, it is impossible to move it to a different +/// thread or into a task that will execute asynchronously. +pub struct CanGc(PhantomData<*mut ()>); + +impl CanGc { + /// Create a new CanGc value, representing that a GC operation is possible within the + /// current stack frame. + pub fn note() -> CanGc { + CanGc(PhantomData) + } +} diff --git a/components/script_bindings/trace.rs b/components/script_bindings/trace.rs index 29317e7fda6..74d7fe1a932 100644 --- a/components/script_bindings/trace.rs +++ b/components/script_bindings/trace.rs @@ -2,8 +2,37 @@ * 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/. */ +use js::glue::CallObjectTracer; +use js::jsapi::{GCTraceKindToAscii, Heap, JSObject, JSTracer, TraceKind}; + +use crate::reflector::Reflector; use crate::str::{DOMString, USVString}; +/// Trace the `JSObject` held by `reflector`. +/// +/// # Safety +/// tracer must point to a valid, non-null JS tracer. +#[cfg_attr(crown, allow(crown::unrooted_must_root))] +pub unsafe fn trace_reflector(tracer: *mut JSTracer, description: &str, reflector: &Reflector) { + trace!("tracing reflector {}", description); + trace_object(tracer, description, reflector.rootable()) +} + +/// Trace a `JSObject`. +/// +/// # Safety +/// tracer must point to a valid, non-null JS tracer. +pub unsafe fn trace_object(tracer: *mut JSTracer, description: &str, obj: &Heap<*mut JSObject>) { + unsafe { + trace!("tracing {}", description); + CallObjectTracer( + tracer, + obj.ptr.get() as *mut _, + GCTraceKindToAscii(TraceKind::Object), + ); + } +} + /// For use on non-jsmanaged types /// Use #[derive(JSTraceable)] on JS managed types macro_rules! unsafe_no_jsmanaged_fields(