diff options
author | Josh Matthews <josh@joshmatthews.net> | 2025-02-21 07:17:11 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-21 12:17:11 +0000 |
commit | a433b202595bb0e2208571b0680be396937e092f (patch) | |
tree | 85df5479edf76809570c93aa46c72cef69b51b8b /components/script | |
parent | 2b0d2ecc7378f653e4755fd7d948dcc15914ffca (diff) | |
download | servo-a433b202595bb0e2208571b0680be396937e092f.tar.gz servo-a433b202595bb0e2208571b0680be396937e092f.zip |
script: Make callbacks generic over DOM interfaces. (#35459)
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/bindings/callback.rs | 82 | ||||
-rw-r--r-- | components/script/dom/bindings/settings_stack.rs | 57 | ||||
-rw-r--r-- | components/script/dom/bindings/utils.rs | 9 | ||||
-rw-r--r-- | components/script/dom/document.rs | 11 | ||||
-rw-r--r-- | components/script/dom/eventtarget.rs | 10 | ||||
-rw-r--r-- | components/script/dom/globalscope.rs | 12 |
6 files changed, 113 insertions, 68 deletions
diff --git a/components/script/dom/bindings/callback.rs b/components/script/dom/bindings/callback.rs index a33cc49e8f0..bd73ec37d52 100644 --- a/components/script/dom/bindings/callback.rs +++ b/components/script/dom/bindings/callback.rs @@ -20,12 +20,13 @@ use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::Wind use crate::dom::bindings::error::{report_pending_exception, Error, Fallible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::{Dom, DomRoot}; -use crate::dom::bindings::settings_stack::{AutoEntryScript, AutoIncumbentScript}; +use crate::dom::bindings::settings_stack::{GenericAutoEntryScript, GenericAutoIncumbentScript}; use crate::dom::bindings::utils::AsCCharPtrPtr; -use crate::dom::globalscope::GlobalScope; -use crate::dom::window::Window; +use crate::dom::document::DocumentHelpers; +use crate::dom::globalscope::GlobalScopeHelpers; use crate::realms::{enter_realm, InRealm}; use crate::script_runtime::{CanGc, JSContext}; +use crate::DomTypes; /// The exception handling used for a call. #[derive(Clone, Copy, PartialEq)] @@ -40,7 +41,7 @@ pub(crate) enum ExceptionHandling { /// callback interface types. #[derive(JSTraceable)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -pub(crate) struct CallbackObject { +pub(crate) struct CallbackObject<D: DomTypes> { /// The underlying `JSObject`. callback: Heap<*mut JSObject>, permanent_js_root: Heap<JSVal>, @@ -56,18 +57,18 @@ pub(crate) struct CallbackObject { /// /// ["callback context"]: https://heycam.github.io/webidl/#dfn-callback-context /// [sometimes]: https://github.com/whatwg/html/issues/2248 - incumbent: Option<Dom<GlobalScope>>, + incumbent: Option<Dom<D::GlobalScope>>, } -impl CallbackObject { +impl<D: DomTypes> CallbackObject<D> { #[cfg_attr(crown, allow(crown::unrooted_must_root))] // These are used by the bindings and do not need `default()` functions. #[allow(clippy::new_without_default)] - fn new() -> CallbackObject { - CallbackObject { + fn new() -> Self { + Self { callback: Heap::default(), permanent_js_root: Heap::default(), - incumbent: GlobalScope::incumbent().map(|i| Dom::from_ref(&*i)), + incumbent: D::GlobalScope::incumbent().map(|i| Dom::from_ref(&*i)), } } @@ -87,7 +88,7 @@ impl CallbackObject { } } -impl Drop for CallbackObject { +impl<D: DomTypes> Drop for CallbackObject<D> { #[allow(unsafe_code)] fn drop(&mut self) { unsafe { @@ -98,19 +99,19 @@ impl Drop for CallbackObject { } } -impl PartialEq for CallbackObject { - fn eq(&self, other: &CallbackObject) -> bool { +impl<D: DomTypes> PartialEq for CallbackObject<D> { + fn eq(&self, other: &CallbackObject<D>) -> bool { self.callback.get() == other.callback.get() } } /// A trait to be implemented by concrete IDL callback function and /// callback interface types. -pub(crate) trait CallbackContainer { +pub(crate) trait CallbackContainer<D: DomTypes> { /// Create a new CallbackContainer object for the given `JSObject`. unsafe fn new(cx: JSContext, callback: *mut JSObject) -> Rc<Self>; /// Returns the underlying `CallbackObject`. - fn callback_holder(&self) -> &CallbackObject; + fn callback_holder(&self) -> &CallbackObject<D>; /// Returns the underlying `JSObject`. fn callback(&self) -> *mut JSObject { self.callback_holder().get() @@ -119,7 +120,7 @@ pub(crate) trait CallbackContainer { /// incumbent global when calling the callback. /// /// ["callback context"]: https://heycam.github.io/webidl/#dfn-callback-context - fn incumbent(&self) -> Option<&GlobalScope> { + fn incumbent(&self) -> Option<&D::GlobalScope> { self.callback_holder().incumbent.as_deref() } } @@ -127,23 +128,23 @@ pub(crate) trait CallbackContainer { /// A common base class for representing IDL callback function types. #[derive(JSTraceable, PartialEq)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -pub(crate) struct CallbackFunction { - object: CallbackObject, +pub(crate) struct CallbackFunction<D: DomTypes> { + object: CallbackObject<D>, } -impl CallbackFunction { +impl<D: DomTypes> CallbackFunction<D> { /// Create a new `CallbackFunction` for this object. #[cfg_attr(crown, allow(crown::unrooted_must_root))] // These are used by the bindings and do not need `default()` functions. #[allow(clippy::new_without_default)] - pub(crate) fn new() -> CallbackFunction { - CallbackFunction { + pub(crate) fn new() -> Self { + Self { object: CallbackObject::new(), } } /// Returns the underlying `CallbackObject`. - pub(crate) fn callback_holder(&self) -> &CallbackObject { + pub(crate) fn callback_holder(&self) -> &CallbackObject<D> { &self.object } @@ -157,22 +158,22 @@ impl CallbackFunction { /// A common base class for representing IDL callback interface types. #[derive(JSTraceable, PartialEq)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -pub(crate) struct CallbackInterface { - object: CallbackObject, +pub(crate) struct CallbackInterface<D: DomTypes> { + object: CallbackObject<D>, } -impl CallbackInterface { +impl<D: DomTypes> CallbackInterface<D> { /// Create a new CallbackInterface object for the given `JSObject`. // These are used by the bindings and do not need `default()` functions. #[allow(clippy::new_without_default)] - pub(crate) fn new() -> CallbackInterface { - CallbackInterface { + pub(crate) fn new() -> Self { + Self { object: CallbackObject::new(), } } /// Returns the underlying `CallbackObject`. - pub(crate) fn callback_holder(&self) -> &CallbackObject { + pub(crate) fn callback_holder(&self) -> &CallbackObject<D> { &self.object } @@ -227,10 +228,10 @@ pub(crate) fn wrap_call_this_value<T: ThisReflector>( /// A class that performs whatever setup we need to safely make a call while /// this class is on the stack. After `new` returns, the call is safe to make. -pub(crate) struct CallSetup { +pub(crate) struct CallSetup<D: DomTypes> { /// The global for reporting exceptions. This is the global object of the /// (possibly wrapped) callback object. - exception_global: DomRoot<GlobalScope>, + exception_global: DomRoot<D::GlobalScope>, /// The `JSContext` used for the call. cx: JSContext, /// The realm we were in before the call. @@ -239,27 +240,24 @@ pub(crate) struct CallSetup { handling: ExceptionHandling, /// <https://heycam.github.io/webidl/#es-invoking-callback-functions> /// steps 8 and 18.2. - entry_script: Option<AutoEntryScript>, + entry_script: Option<GenericAutoEntryScript<D>>, /// <https://heycam.github.io/webidl/#es-invoking-callback-functions> /// steps 9 and 18.1. - incumbent_script: Option<AutoIncumbentScript>, + incumbent_script: Option<GenericAutoIncumbentScript<D>>, } -impl CallSetup { +impl<D: DomTypes> CallSetup<D> { /// Performs the setup needed to make a call. #[cfg_attr(crown, allow(crown::unrooted_must_root))] - pub(crate) fn new<T: CallbackContainer>( - callback: &T, - handling: ExceptionHandling, - ) -> CallSetup { - let global = unsafe { GlobalScope::from_object(callback.callback()) }; - if let Some(window) = global.downcast::<Window>() { + pub(crate) fn new<T: CallbackContainer<D>>(callback: &T, handling: ExceptionHandling) -> Self { + let global = unsafe { D::GlobalScope::from_object(callback.callback()) }; + if let Some(window) = global.downcast::<D::Window>() { window.Document().ensure_safe_to_run_script_or_layout(); } - let cx = GlobalScope::get_cx(); + let cx = D::GlobalScope::get_cx(); - let aes = AutoEntryScript::new(&global); - let ais = callback.incumbent().map(AutoIncumbentScript::new); + let aes = GenericAutoEntryScript::<D>::new(&global); + let ais = callback.incumbent().map(GenericAutoIncumbentScript::new); CallSetup { exception_global: global, cx, @@ -276,7 +274,7 @@ impl CallSetup { } } -impl Drop for CallSetup { +impl<D: DomTypes> Drop for CallSetup<D> { fn drop(&mut self) { unsafe { LeaveRealm(*self.cx, self.old_realm); diff --git a/components/script/dom/bindings/settings_stack.rs b/components/script/dom/bindings/settings_stack.rs index 28e48ea4d93..1f566a59052 100644 --- a/components/script/dom/bindings/settings_stack.rs +++ b/components/script/dom/bindings/settings_stack.rs @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::RefCell; +use std::marker::PhantomData; use std::thread; use js::jsapi::{GetScriptedCallerGlobal, HideScriptedCaller, JSTracer, UnhideScriptedCaller}; @@ -10,10 +11,14 @@ use js::rust::Runtime; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::trace::JSTraceable; -use crate::dom::globalscope::GlobalScope; +use crate::dom::bindings::utils::DomHelpers; +use crate::dom::globalscope::{GlobalScope, GlobalScopeHelpers}; use crate::script_runtime::CanGc; +use crate::DomTypes; -thread_local!(static STACK: RefCell<Vec<StackEntry>> = const { RefCell::new(Vec::new()) }); +thread_local!(pub(super) static STACK: RefCell<Vec<StackEntry<crate::DomTypeHolder>>> = const { + RefCell::new(Vec::new()) +}); #[derive(Debug, Eq, JSTraceable, PartialEq)] enum StackEntryKind { @@ -23,8 +28,8 @@ enum StackEntryKind { #[cfg_attr(crown, allow(crown::unrooted_must_root))] #[derive(JSTraceable)] -struct StackEntry { - global: Dom<GlobalScope>, +pub(crate) struct StackEntry<D: DomTypes> { + global: Dom<D::GlobalScope>, kind: StackEntryKind, } @@ -39,25 +44,28 @@ pub(crate) fn is_execution_stack_empty() -> bool { STACK.with(|stack| stack.borrow().is_empty()) } +pub(crate) type AutoEntryScript = GenericAutoEntryScript<crate::DomTypeHolder>; + /// RAII struct that pushes and pops entries from the script settings stack. -pub(crate) struct AutoEntryScript { - global: DomRoot<GlobalScope>, +pub(crate) struct GenericAutoEntryScript<D: DomTypes> { + global: DomRoot<D::GlobalScope>, #[cfg(feature = "tracing")] #[allow(dead_code)] span: tracing::span::EnteredSpan, } -impl AutoEntryScript { +impl<D: DomTypes> GenericAutoEntryScript<D> { /// <https://html.spec.whatwg.org/multipage/#prepare-to-run-script> - pub(crate) fn new(global: &GlobalScope) -> Self { - STACK.with(|stack| { + pub(crate) fn new(global: &D::GlobalScope) -> Self { + let settings_stack = <D as DomHelpers<D>>::settings_stack(); + settings_stack.with(|stack| { trace!("Prepare to run script with {:p}", global); let mut stack = stack.borrow_mut(); stack.push(StackEntry { global: Dom::from_ref(global), kind: StackEntryKind::Entry, }); - AutoEntryScript { + Self { global: DomRoot::from_ref(global), #[cfg(feature = "tracing")] span: tracing::info_span!( @@ -71,14 +79,15 @@ impl AutoEntryScript { } } -impl Drop for AutoEntryScript { +impl<D: DomTypes> Drop for GenericAutoEntryScript<D> { /// <https://html.spec.whatwg.org/multipage/#clean-up-after-running-script> fn drop(&mut self) { - STACK.with(|stack| { + let settings_stack = <D as DomHelpers<D>>::settings_stack(); + settings_stack.with(|stack| { let mut stack = stack.borrow_mut(); let entry = stack.pop().unwrap(); assert_eq!( - &*entry.global as *const GlobalScope, &*self.global as *const GlobalScope, + &*entry.global as *const D::GlobalScope, &*self.global as *const D::GlobalScope, "Dropped AutoEntryScript out of order." ); assert_eq!(entry.kind, StackEntryKind::Entry); @@ -109,20 +118,24 @@ pub(crate) fn entry_global() -> DomRoot<GlobalScope> { } /// RAII struct that pushes and pops entries from the script settings stack. -pub(crate) struct AutoIncumbentScript { +pub(crate) struct GenericAutoIncumbentScript<D: DomTypes> { global: usize, + _marker: PhantomData<D>, } -impl AutoIncumbentScript { +pub(crate) type AutoIncumbentScript = GenericAutoIncumbentScript<crate::DomTypeHolder>; + +impl<D: DomTypes> GenericAutoIncumbentScript<D> { /// <https://html.spec.whatwg.org/multipage/#prepare-to-run-a-callback> - pub(crate) fn new(global: &GlobalScope) -> Self { + pub(crate) fn new(global: &D::GlobalScope) -> Self { // Step 2-3. unsafe { let cx = Runtime::get().expect("Creating a new incumbent script after runtime shutdown"); HideScriptedCaller(cx.as_ptr()); } - STACK.with(|stack| { + let settings_stack = <D as DomHelpers<D>>::settings_stack(); + settings_stack.with(|stack| { trace!("Prepare to run a callback with {:p}", global); // Step 1. let mut stack = stack.borrow_mut(); @@ -130,23 +143,25 @@ impl AutoIncumbentScript { global: Dom::from_ref(global), kind: StackEntryKind::Incumbent, }); - AutoIncumbentScript { + Self { global: global as *const _ as usize, + _marker: PhantomData, } }) } } -impl Drop for AutoIncumbentScript { +impl<D: DomTypes> Drop for GenericAutoIncumbentScript<D> { /// <https://html.spec.whatwg.org/multipage/#clean-up-after-running-a-callback> fn drop(&mut self) { - STACK.with(|stack| { + let settings_stack = <D as DomHelpers<D>>::settings_stack(); + settings_stack.with(|stack| { // Step 4. let mut stack = stack.borrow_mut(); let entry = stack.pop().unwrap(); // Step 3. assert_eq!( - &*entry.global as *const GlobalScope as usize, self.global, + &*entry.global as *const D::GlobalScope as usize, self.global, "Dropped AutoIncumbentScript out of order." ); assert_eq!(entry.kind, StackEntryKind::Incumbent); diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs index 631ba43c4d5..b3f3219cf1a 100644 --- a/components/script/dom/bindings/utils.rs +++ b/components/script/dom/bindings/utils.rs @@ -4,10 +4,12 @@ //! Various utilities to glue JavaScript and the DOM implementation together. +use std::cell::RefCell; use std::ffi::CString; use std::os::raw::c_char; use std::ptr::NonNull; use std::sync::OnceLock; +use std::thread::LocalKey; use std::{ptr, slice, str}; use js::conversions::ToJSValConvertible; @@ -44,6 +46,7 @@ use crate::dom::bindings::conversions::{ }; use crate::dom::bindings::error::{throw_dom_exception, throw_invalid_this, Error}; use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::settings_stack::{self, StackEntry}; use crate::dom::bindings::str::DOMString; use crate::dom::bindings::trace::trace_object; use crate::dom::windowproxy::WindowProxyHandler; @@ -676,6 +679,8 @@ pub(crate) trait DomHelpers<D: DomTypes> { creator: unsafe fn(SafeJSContext, HandleObject, *mut ProtoOrIfaceArray), can_gc: CanGc, ) -> bool; + + fn settings_stack() -> &'static LocalKey<RefCell<Vec<StackEntry<D>>>>; } impl DomHelpers<crate::DomTypeHolder> for crate::DomTypeHolder { @@ -699,4 +704,8 @@ impl DomHelpers<crate::DomTypeHolder> for crate::DomTypeHolder { ) -> bool { call_html_constructor::<T>(cx, args, global, proto_id, creator, can_gc) } + + fn settings_stack() -> &'static LocalKey<RefCell<Vec<StackEntry<crate::DomTypeHolder>>>> { + &settings_stack::STACK + } } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 5307a6f29cd..e7a0e6813b8 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -205,6 +205,7 @@ use crate::stylesheet_set::StylesheetSetRef; use crate::task::TaskBox; use crate::task_source::TaskSourceName; use crate::timers::OneshotTimerCallback; +use crate::DomTypes; /// The number of times we are allowed to see spurious `requestAnimationFrame()` calls before /// falling back to fake ones. @@ -6214,3 +6215,13 @@ fn is_named_element_with_id_attribute(elem: &Element) -> bool { // behaviour is actually implemented elem.is::<HTMLImageElement>() && elem.get_name().is_some_and(|name| !name.is_empty()) } + +pub(crate) trait DocumentHelpers<D: DomTypes> { + fn ensure_safe_to_run_script_or_layout(&self); +} + +impl DocumentHelpers<crate::DomTypeHolder> for Document { + fn ensure_safe_to_run_script_or_layout(&self) { + Document::ensure_safe_to_run_script_or_layout(self) + } +} diff --git a/components/script/dom/eventtarget.rs b/components/script/dom/eventtarget.rs index 0d879bca452..b66a54cb5a5 100644 --- a/components/script/dom/eventtarget.rs +++ b/components/script/dom/eventtarget.rs @@ -77,7 +77,7 @@ pub(crate) enum CommonEventHandler { } impl CommonEventHandler { - fn parent(&self) -> &CallbackFunction { + fn parent(&self) -> &CallbackFunction<crate::DomTypeHolder> { match *self { CommonEventHandler::EventHandler(ref handler) => &handler.parent, CommonEventHandler::ErrorEventHandler(ref handler) => &handler.parent, @@ -612,7 +612,7 @@ impl EventTarget { } #[allow(unsafe_code)] - pub(crate) fn set_event_handler_common<T: CallbackContainer>( + pub(crate) fn set_event_handler_common<T: CallbackContainer<crate::DomTypeHolder>>( &self, ty: &str, listener: Option<Rc<T>>, @@ -628,7 +628,7 @@ impl EventTarget { } #[allow(unsafe_code)] - pub(crate) fn set_error_event_handler<T: CallbackContainer>( + pub(crate) fn set_error_event_handler<T: CallbackContainer<crate::DomTypeHolder>>( &self, ty: &str, listener: Option<Rc<T>>, @@ -644,7 +644,7 @@ impl EventTarget { } #[allow(unsafe_code)] - pub(crate) fn set_beforeunload_event_handler<T: CallbackContainer>( + pub(crate) fn set_beforeunload_event_handler<T: CallbackContainer<crate::DomTypeHolder>>( &self, ty: &str, listener: Option<Rc<T>>, @@ -660,7 +660,7 @@ impl EventTarget { } #[allow(unsafe_code)] - pub(crate) fn get_event_handler_common<T: CallbackContainer>( + pub(crate) fn get_event_handler_common<T: CallbackContainer<crate::DomTypeHolder>>( &self, ty: &str, can_gc: CanGc, diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index b16e404ccbd..4cc27bea3f0 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -3348,6 +3348,10 @@ pub(crate) trait GlobalScopeHelpers<D: crate::DomTypes> { ) -> DomRoot<D::GlobalScope>; fn origin(&self) -> &MutableOrigin; + + fn incumbent() -> Option<DomRoot<D::GlobalScope>>; + + fn perform_a_microtask_checkpoint(&self, can_gc: CanGc); } #[allow(unsafe_code)] @@ -3375,4 +3379,12 @@ impl GlobalScopeHelpers<crate::DomTypeHolder> for GlobalScope { fn origin(&self) -> &MutableOrigin { GlobalScope::origin(self) } + + fn incumbent() -> Option<DomRoot<Self>> { + GlobalScope::incumbent() + } + + fn perform_a_microtask_checkpoint(&self, can_gc: CanGc) { + GlobalScope::perform_a_microtask_checkpoint(self, can_gc) + } } |