script: Create a debugger script for the SpiderMonkey Debugger API

Co-authored-by: atbrakhi <atbrakhi@igalia.com>
Signed-off-by: Delan Azabani <dazabani@igalia.com>
This commit is contained in:
Delan Azabani 2025-07-29 19:12:00 +08:00
parent e8c50390f6
commit 4d7a514923
10 changed files with 179 additions and 6 deletions

View file

@ -0,0 +1,119 @@
/* 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::sync::Arc;
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::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.
///
/// <https://firefox-source-docs.mozilla.org/js/Debugger/>
pub(crate) struct DebuggerGlobalScope {
global_scope: GlobalScope,
script_chan: Sender<MainThreadScriptMsg>,
}
impl DebuggerGlobalScope {
/// Create a new heap-allocated `DebuggerGlobalScope`.
#[allow(unsafe_code)]
pub(crate) fn new(
runtime: &Runtime,
script_chan: Sender<MainThreadScriptMsg>,
devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
mem_profiler_chan: mem::ProfilerChan,
time_profiler_chan: time::ProfilerChan,
script_to_constellation_chan: ScriptToConstellationChan,
resource_threads: ResourceThreads,
#[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
) -> DomRoot<Self> {
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(),
gpu_id_hub,
None,
false,
),
script_chan,
});
let global = unsafe {
DebuggerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(
JSContext::from_ptr(runtime.cx()),
global,
)
};
let realm = enter_realm(&*global);
define_all_exposed_interfaces(global.upcast(), InRealm::entered(&realm), CanGc::note());
// TODO: what invariants do we need to uphold for the unsafe call?
assert!(unsafe {
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) {
warn!("Failed to execute debugger request");
}
}
}

View file

@ -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::<DebuggerGlobalScope>() {
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::<DebuggerGlobalScope>() {
return self.creation_url.clone();
}
unreachable!();
}

View file

@ -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;

View file

@ -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<AtomicBool>,
debugger_global: Dom<DebuggerGlobalScope>,
}
struct BHMExitSignal {
@ -928,6 +931,29 @@ 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(),
);
debugger_global.execute(CanGc::note());
ScriptThread {
documents: DomRefCell::new(DocumentCollection::default()),
last_render_opportunity_time: Default::default(),
@ -942,8 +968,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 +993,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(),
}
}