From c09e117bfec2ddda98fa072f4d60191fa59692a7 Mon Sep 17 00:00:00 2001 From: shuppy Date: Thu, 31 Jul 2025 14:17:23 +0800 Subject: [PATCH] script: Create a debugger script for the SpiderMonkey Debugger API (#38331) to use the [SpiderMonkey Debugger API](https://firefox-source-docs.mozilla.org/js/Debugger/), we need to call it from an internal debugger script that we will supply. this script must run in the same runtime as the debuggee(s), but in a separate [compartment](https://udn.realityripple.com/docs/Mozilla/Projects/SpiderMonkey/Compartments) ([more details](https://hacks.mozilla.org/2020/03/future-proofing-firefoxs-javascript-debugger-implementation/)). this patch defines a new DebuggerGlobalScope type and a new debugger script resource. when creating each script thread, we create a debugger global, load the debugger script from resources/debugger.js, and run that script in the global to initialise the Debugger API. subsequent patches will use the debugger script as an RPC mechanism for the Debugger API. Testing: no testable effects yet, but will be used in #37667 Fixes: part of #36027 --------- Signed-off-by: Delan Azabani Co-authored-by: atbrakhi --- components/script/dom/debuggerglobalscope.rs | 122 ++++++++++++++++++ components/script/dom/globalscope.rs | 8 +- components/script/dom/mod.rs | 1 + components/script/script_thread.rs | 34 ++++- .../script_bindings/codegen/Bindings.conf | 4 + .../webidls/DebuggerGlobalScope.webidl | 9 ++ .../webidls/EventTarget.webidl | 2 +- .../webidls/GlobalScope.webidl | 2 +- components/shared/embedder/resources.rs | 4 + ports/servoshell/egl/android/resources.rs | 1 + resources/debugger.js | 3 + 11 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 components/script/dom/debuggerglobalscope.rs create mode 100644 components/script_bindings/webidls/DebuggerGlobalScope.webidl create mode 100644 resources/debugger.js diff --git a/components/script/dom/debuggerglobalscope.rs b/components/script/dom/debuggerglobalscope.rs new file mode 100644 index 00000000000..0c69b8106d5 --- /dev/null +++ b/components/script/dom/debuggerglobalscope.rs @@ -0,0 +1,122 @@ +/* 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 base::id::PipelineId; +use constellation_traits::ScriptToConstellationChan; +use crossbeam_channel::Sender; +use devtools_traits::ScriptToDevtoolsControlMsg; +use dom_struct::dom_struct; +use embedder_traits::resources::{self, Resource}; +use ipc_channel::ipc::IpcSender; +use js::jsval::UndefinedValue; +use js::rust::Runtime; +use js::rust::wrappers::JS_DefineDebuggerObject; +use net_traits::ResourceThreads; +use profile_traits::{mem, time}; +use script_bindings::realms::InRealm; +use script_bindings::reflector::DomObject; +use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; + +use crate::dom::bindings::codegen::Bindings::DebuggerGlobalScopeBinding; +use crate::dom::bindings::error::report_pending_exception; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::trace::CustomTraceable; +use crate::dom::bindings::utils::define_all_exposed_interfaces; +use crate::dom::globalscope::GlobalScope; +#[cfg(feature = "testbinding")] +#[cfg(feature = "webgpu")] +use crate::dom::webgpu::identityhub::IdentityHub; +use crate::messaging::MainThreadScriptMsg; +use crate::realms::enter_realm; +use crate::script_module::ScriptFetchOptions; +use crate::script_runtime::{CanGc, JSContext}; + +#[dom_struct] +/// Global scope for interacting with the devtools Debugger API. +/// +/// +pub(crate) struct DebuggerGlobalScope { + global_scope: GlobalScope, + script_chan: Sender, +} + +impl DebuggerGlobalScope { + /// Create a new heap-allocated `DebuggerGlobalScope`. + #[allow(unsafe_code, clippy::too_many_arguments)] + pub(crate) fn new( + runtime: &Runtime, + script_chan: Sender, + devtools_chan: Option>, + mem_profiler_chan: mem::ProfilerChan, + time_profiler_chan: time::ProfilerChan, + script_to_constellation_chan: ScriptToConstellationChan, + resource_threads: ResourceThreads, + #[cfg(feature = "webgpu")] gpu_id_hub: std::sync::Arc, + can_gc: CanGc, + ) -> DomRoot { + let global = Box::new(Self { + global_scope: GlobalScope::new_inherited( + PipelineId::new(), + devtools_chan, + mem_profiler_chan, + time_profiler_chan, + script_to_constellation_chan, + resource_threads, + MutableOrigin::new(ImmutableOrigin::new_opaque()), + ServoUrl::parse_with_base(None, "about:internal/debugger") + .expect("Guaranteed by argument"), + None, + Default::default(), + #[cfg(feature = "webgpu")] + gpu_id_hub, + None, + false, + ), + script_chan, + }); + let global = unsafe { + DebuggerGlobalScopeBinding::Wrap::( + JSContext::from_ptr(runtime.cx()), + global, + ) + }; + + let realm = enter_realm(&*global); + define_all_exposed_interfaces(global.upcast(), InRealm::entered(&realm), can_gc); + assert!(unsafe { + // Invariants: `cx` must be a non-null, valid JSContext pointer, + // and `obj` must be a handle to a JS global object. + JS_DefineDebuggerObject( + *Self::get_cx(), + global.global_scope.reflector().get_jsobject(), + ) + }); + + global + } + + /// Get the JS context. + pub(crate) fn get_cx() -> JSContext { + GlobalScope::get_cx() + } + + fn evaluate_js(&self, script: &str, can_gc: CanGc) -> bool { + rooted!(in (*Self::get_cx()) let mut rval = UndefinedValue()); + self.global_scope.evaluate_js_on_global_with_result( + script, + rval.handle_mut(), + ScriptFetchOptions::default_classic_script(&self.global_scope), + self.global_scope.api_base_url(), + can_gc, + ) + } + + pub(crate) fn execute(&self, can_gc: CanGc) { + if !self.evaluate_js(&resources::read_string(Resource::DebuggerJS), can_gc) { + let ar = enter_realm(self); + report_pending_exception(Self::get_cx(), true, InRealm::Entered(&ar), can_gc); + } + } +} diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 01ff74c7c49..b2ab674e29d 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -114,7 +114,7 @@ use crate::dom::reportingobserver::ReportingObserver; use crate::dom::serviceworker::ServiceWorker; use crate::dom::serviceworkerregistration::ServiceWorkerRegistration; use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory; -use crate::dom::types::MessageEvent; +use crate::dom::types::{DebuggerGlobalScope, MessageEvent}; use crate::dom::underlyingsourcecontainer::UnderlyingSourceType; #[cfg(feature = "webgpu")] use crate::dom::webgpu::gpudevice::GPUDevice; @@ -2530,6 +2530,9 @@ impl GlobalScope { // https://drafts.css-houdini.org/worklets/#script-settings-for-worklets return worklet.base_url(); } + if let Some(_debugger_global) = self.downcast::() { + return self.creation_url.clone(); + } unreachable!(); } @@ -2545,6 +2548,9 @@ impl GlobalScope { // TODO: is this the right URL to return? return worklet.base_url(); } + if let Some(_debugger_global) = self.downcast::() { + return self.creation_url.clone(); + } unreachable!(); } diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 20f26e19655..b98cb689b11 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -289,6 +289,7 @@ pub(crate) mod customevent; pub(crate) mod datatransfer; pub(crate) mod datatransferitem; pub(crate) mod datatransferitemlist; +pub(crate) mod debuggerglobalscope; pub(crate) mod dedicatedworkerglobalscope; pub(crate) mod defaultteereadrequest; pub(crate) mod defaultteeunderlyingsource; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 1d4b8bd304c..f291b23c89d 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -133,6 +133,7 @@ use crate::dom::htmlslotelement::HTMLSlotElement; use crate::dom::mutationobserver::MutationObserver; use crate::dom::node::{Node, NodeTraits, ShadowIncluding}; use crate::dom::servoparser::{ParserContext, ServoParser}; +use crate::dom::types::DebuggerGlobalScope; #[cfg(feature = "webgpu")] use crate::dom::webgpu::identityhub::IdentityHub; use crate::dom::window::Window; @@ -348,6 +349,8 @@ pub struct ScriptThread { /// itself is managing animations the the timer fired triggering a [`ScriptThread`]-based /// animation tick. has_pending_animation_tick: Arc, + + debugger_global: Dom, } struct BHMExitSignal { @@ -928,6 +931,30 @@ impl ScriptThread { content_process_shutdown_sender: state.content_process_shutdown_sender, }; + let microtask_queue = runtime.microtask_queue.clone(); + let js_runtime = Rc::new(runtime); + #[cfg(feature = "webgpu")] + let gpu_id_hub = Arc::new(IdentityHub::default()); + + let pipeline_id = PipelineId::new(); + let script_to_constellation_chan = ScriptToConstellationChan { + sender: senders.pipeline_to_constellation_sender.clone(), + pipeline_id, + }; + let debugger_global = DebuggerGlobalScope::new( + &js_runtime.clone(), + senders.self_sender.clone(), + senders.devtools_server_sender.clone(), + senders.memory_profiler_sender.clone(), + senders.time_profiler_sender.clone(), + script_to_constellation_chan, + state.resource_threads.clone(), + #[cfg(feature = "webgpu")] + gpu_id_hub.clone(), + CanGc::note(), + ); + debugger_global.execute(CanGc::note()); + ScriptThread { documents: DomRefCell::new(DocumentCollection::default()), last_render_opportunity_time: Default::default(), @@ -942,8 +969,8 @@ impl ScriptThread { background_hang_monitor, closing, timer_scheduler: Default::default(), - microtask_queue: runtime.microtask_queue.clone(), - js_runtime: Rc::new(runtime), + microtask_queue, + js_runtime, topmost_mouse_over_target: MutNullableDom::new(Default::default()), closed_pipelines: DomRefCell::new(HashSet::new()), mutation_observer_microtask_queued: Default::default(), @@ -967,12 +994,13 @@ impl ScriptThread { pipeline_to_node_ids: Default::default(), is_user_interacting: Cell::new(false), #[cfg(feature = "webgpu")] - gpu_id_hub: Arc::new(IdentityHub::default()), + gpu_id_hub, inherited_secure_context: state.inherited_secure_context, layout_factory, relative_mouse_down_point: Cell::new(Point2D::zero()), scheduled_script_thread_animation_timer: Default::default(), has_pending_animation_tick: Arc::new(AtomicBool::new(false)), + debugger_global: debugger_global.as_traced(), } } diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 87486fcab45..2a4aaa82439 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -166,6 +166,10 @@ DOMInterfaces = { 'canGc': ['IndexedGetter', 'Add', 'Add_'] }, +'DebuggerGlobalScope': { + 'useSystemCompartment': True, +}, + 'Document': { 'additionalTraits': ["crate::interfaces::DocumentHelpers"], 'canGc': ['Close', 'CreateElement', 'CreateElementNS', 'ImportNode', 'SetTitle', 'Write', 'Writeln', 'CreateEvent', 'CreateRange', 'Open', 'Open_', 'CreateComment', 'CreateAttribute', 'CreateAttributeNS', 'CreateDocumentFragment', 'CreateTextNode', 'CreateCDATASection', 'CreateProcessingInstruction', 'Prepend', 'Append', 'ReplaceChildren', 'SetBgColor', 'SetFgColor', 'Fonts', 'ElementFromPoint', 'ElementsFromPoint', 'GetScrollingElement', 'ExitFullscreen', 'CreateExpression', 'CreateNSResolver', 'Evaluate', 'StyleSheets', 'Implementation', 'GetElementsByTagName', 'GetElementsByTagNameNS', 'GetElementsByClassName', 'AdoptNode', 'CreateNodeIterator', 'SetBody', 'GetElementsByName', 'Images', 'Embeds', 'Plugins', 'Links', 'Forms', 'Scripts', 'Anchors', 'Applets', 'Children', 'GetSelection', 'NamedGetter', 'AdoptedStyleSheets'], diff --git a/components/script_bindings/webidls/DebuggerGlobalScope.webidl b/components/script_bindings/webidls/DebuggerGlobalScope.webidl new file mode 100644 index 00000000000..4f85602a157 --- /dev/null +++ b/components/script_bindings/webidls/DebuggerGlobalScope.webidl @@ -0,0 +1,9 @@ +/* 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/. */ + +// This interface is entirely internal to Servo, and should not be accessible to +// web pages. +[Global=DebuggerGlobalScope, Exposed=DebuggerGlobalScope] +interface DebuggerGlobalScope: GlobalScope { +}; diff --git a/components/script_bindings/webidls/EventTarget.webidl b/components/script_bindings/webidls/EventTarget.webidl index a8a63cc2b32..411fb962d32 100644 --- a/components/script_bindings/webidls/EventTarget.webidl +++ b/components/script_bindings/webidls/EventTarget.webidl @@ -5,7 +5,7 @@ * https://dom.spec.whatwg.org/#interface-eventtarget */ -[Exposed=(Window,Worker,Worklet,DissimilarOriginWindow)] +[Exposed=(Window,Worker,Worklet,DissimilarOriginWindow,DebuggerGlobalScope)] interface EventTarget { [Throws] constructor(); undefined addEventListener( diff --git a/components/script_bindings/webidls/GlobalScope.webidl b/components/script_bindings/webidls/GlobalScope.webidl index 57206d13e6a..dfa44738781 100644 --- a/components/script_bindings/webidls/GlobalScope.webidl +++ b/components/script_bindings/webidls/GlobalScope.webidl @@ -5,6 +5,6 @@ // This interface is entirely internal to Servo, and should not be accessible to // web pages. -[Exposed=(Window,Worker,Worklet,DissimilarOriginWindow), +[Exposed=(Window,Worker,Worklet,DissimilarOriginWindow,DebuggerGlobalScope), Inline] interface GlobalScope : EventTarget {}; diff --git a/components/shared/embedder/resources.rs b/components/shared/embedder/resources.rs index 28464a95d1a..7b7a1b61ae0 100644 --- a/components/shared/embedder/resources.rs +++ b/components/shared/embedder/resources.rs @@ -109,6 +109,8 @@ pub enum Resource { DirectoryListingHTML, /// A HTML page that is used for the about:memory url. AboutMemoryHTML, + /// RPC script for the Debugger API on behalf of devtools. + DebuggerJS, } impl Resource { @@ -123,6 +125,7 @@ impl Resource { Resource::CrashHTML => "crash.html", Resource::DirectoryListingHTML => "directory-listing.html", Resource::AboutMemoryHTML => "about-memory.html", + Resource::DebuggerJS => "debugger.js", } } } @@ -167,6 +170,7 @@ fn resources_for_tests() -> Box { Resource::AboutMemoryHTML => { &include_bytes!("../../../resources/about-memory.html")[..] }, + Resource::DebuggerJS => &include_bytes!("../../../resources/debugger.js")[..], } .to_owned() } diff --git a/ports/servoshell/egl/android/resources.rs b/ports/servoshell/egl/android/resources.rs index 2de3790060f..f8d6accc39a 100644 --- a/ports/servoshell/egl/android/resources.rs +++ b/ports/servoshell/egl/android/resources.rs @@ -33,6 +33,7 @@ impl ResourceReaderMethods for ResourceReaderInstance { Resource::AboutMemoryHTML => { &include_bytes!("../../../../resources/about-memory.html")[..] }, + Resource::DebuggerJS => &include_bytes!("../../../../resources/debugger.js")[..], }) } diff --git a/resources/debugger.js b/resources/debugger.js new file mode 100644 index 00000000000..6d1e4b19bc8 --- /dev/null +++ b/resources/debugger.js @@ -0,0 +1,3 @@ +if (!("dbg" in this)) { + dbg = new Debugger; +}