mirror of
https://github.com/servo/servo.git
synced 2025-08-11 08:25:32 +01:00
script: Add new worker globals as debuggees (#38551)
to debug workers in a page with the [SpiderMonkey Debugger API](https://firefox-source-docs.mozilla.org/js/Debugger/), we need to pass the worker’s global object to [Debugger.prototype.**addDebuggee()**](https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.html#adddebuggee-global). we could pick up the global via the [onNewGlobalObject() hook](https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.html#onnewglobalobject-global), but if our script system passes the global to the debugger script instead, we can later add details like the PipelineId that will help servo identify debuggees that the API is notifying us about (#38334). this patch creates a debugger global in worker threads, runs the debugger script in those new globals, and plumbs new worker globals from those threads into addDebuggee() via the debugger script. since worker threads can’t generate PipelineId values, but they only ever run workers on behalf of one pipeline, we use that pipeline’s PipelineId as the PipelineId of the debugger global, rather than generating a unique PipelineId like we do in script threads. Testing: will undergo many automated tests in #38334 Fixes: part of #36027 Signed-off-by: Delan Azabani <dazabani@igalia.com> Co-authored-by: atbrakhi <atbrakhi@igalia.com>
This commit is contained in:
parent
d50f02fa73
commit
a3e0a34802
9 changed files with 178 additions and 24 deletions
|
@ -8,13 +8,14 @@ use dom_struct::dom_struct;
|
|||
use js::jsapi::{JSObject, Value};
|
||||
use script_bindings::conversions::SafeToJSValConvertible;
|
||||
use script_bindings::reflector::DomObject;
|
||||
use script_bindings::str::DOMString;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::DebuggerEventBinding::DebuggerEventMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
|
||||
use crate::dom::bindings::reflector::reflect_dom_object;
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::event::Event;
|
||||
use crate::dom::types::GlobalScope;
|
||||
use crate::dom::types::{GlobalScope, PipelineId};
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
|
@ -22,17 +23,23 @@ use crate::script_runtime::CanGc;
|
|||
pub(crate) struct DebuggerEvent {
|
||||
event: Event,
|
||||
global: Dom<GlobalScope>,
|
||||
pipeline_id: Dom<PipelineId>,
|
||||
worker_id: Option<DOMString>,
|
||||
}
|
||||
|
||||
impl DebuggerEvent {
|
||||
pub(crate) fn new(
|
||||
debugger_global: &GlobalScope,
|
||||
global: &GlobalScope,
|
||||
pipeline_id: &PipelineId,
|
||||
worker_id: Option<DOMString>,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<Self> {
|
||||
let result = Box::new(Self {
|
||||
event: Event::new_inherited(),
|
||||
global: Dom::from_ref(global),
|
||||
pipeline_id: Dom::from_ref(pipeline_id),
|
||||
worker_id,
|
||||
});
|
||||
let result = reflect_dom_object(result, debugger_global, can_gc);
|
||||
result.event.init_event("addDebuggee".into(), false, false);
|
||||
|
@ -53,6 +60,16 @@ impl DebuggerEventMethods<crate::DomTypeHolder> for DebuggerEvent {
|
|||
NonNull::new(result.to_object()).unwrap()
|
||||
}
|
||||
|
||||
fn PipelineId(
|
||||
&self,
|
||||
) -> DomRoot<<crate::DomTypeHolder as script_bindings::DomTypes>::PipelineId> {
|
||||
DomRoot::from_ref(&self.pipeline_id)
|
||||
}
|
||||
|
||||
fn GetWorkerId(&self) -> Option<DOMString> {
|
||||
self.worker_id.clone()
|
||||
}
|
||||
|
||||
fn IsTrusted(&self) -> bool {
|
||||
self.event.IsTrusted()
|
||||
}
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
|
||||
use base::id::PipelineId;
|
||||
use constellation_traits::ScriptToConstellationChan;
|
||||
use crossbeam_channel::Sender;
|
||||
use devtools_traits::ScriptToDevtoolsControlMsg;
|
||||
use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
|
||||
use dom_struct::dom_struct;
|
||||
use embedder_traits::resources::{self, Resource};
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
|
@ -22,14 +21,12 @@ 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;
|
||||
use crate::dom::types::{DebuggerEvent, Event};
|
||||
#[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};
|
||||
|
@ -40,15 +37,20 @@ use crate::script_runtime::{CanGc, JSContext};
|
|||
/// <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`.
|
||||
///
|
||||
/// `debugger_pipeline_id` is the pipeline id to use when creating the debugger’s [`GlobalScope`]:
|
||||
/// - in normal script threads, it should be set to `PipelineId::new()`, because those threads can generate
|
||||
/// pipeline ids, and they may contain debuggees from more than one pipeline
|
||||
/// - in web worker threads, it should be set to the pipeline id of the page that created the thread, because
|
||||
/// those threads can’t generate pipeline ids, and they only contain one debuggee from one pipeline
|
||||
#[allow(unsafe_code, clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
runtime: &Runtime,
|
||||
script_chan: Sender<MainThreadScriptMsg>,
|
||||
debugger_pipeline_id: PipelineId,
|
||||
devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
|
||||
mem_profiler_chan: mem::ProfilerChan,
|
||||
time_profiler_chan: time::ProfilerChan,
|
||||
|
@ -59,7 +61,7 @@ impl DebuggerGlobalScope {
|
|||
) -> DomRoot<Self> {
|
||||
let global = Box::new(Self {
|
||||
global_scope: GlobalScope::new_inherited(
|
||||
PipelineId::new(),
|
||||
debugger_pipeline_id,
|
||||
devtools_chan,
|
||||
mem_profiler_chan,
|
||||
time_profiler_chan,
|
||||
|
@ -75,7 +77,6 @@ impl DebuggerGlobalScope {
|
|||
None,
|
||||
false,
|
||||
),
|
||||
script_chan,
|
||||
});
|
||||
let global = unsafe {
|
||||
DebuggerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(
|
||||
|
@ -122,10 +123,24 @@ impl DebuggerGlobalScope {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fire_add_debuggee(&self, can_gc: CanGc, global: &GlobalScope) {
|
||||
pub(crate) fn fire_add_debuggee(
|
||||
&self,
|
||||
can_gc: CanGc,
|
||||
debuggee_global: &GlobalScope,
|
||||
debuggee_pipeline_id: PipelineId,
|
||||
debuggee_worker_id: Option<WorkerId>,
|
||||
) {
|
||||
let debuggee_pipeline_id =
|
||||
crate::dom::pipelineid::PipelineId::new(self.upcast(), debuggee_pipeline_id, can_gc);
|
||||
let event = DomRoot::upcast::<Event>(DebuggerEvent::new(
|
||||
self.upcast(),
|
||||
debuggee_global,
|
||||
&debuggee_pipeline_id,
|
||||
debuggee_worker_id.map(|id| id.to_string().into()),
|
||||
can_gc,
|
||||
));
|
||||
assert!(
|
||||
DomRoot::upcast::<Event>(DebuggerEvent::new(self.upcast(), global, can_gc))
|
||||
.fire(self.upcast(), can_gc),
|
||||
DomRoot::upcast::<Event>(event).fire(self.upcast(), can_gc),
|
||||
"Guaranteed by DebuggerEvent::new"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ use crate::dom::eventtarget::EventTarget;
|
|||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::messageevent::MessageEvent;
|
||||
use crate::dom::reportingendpoint::ReportingEndpoint;
|
||||
use crate::dom::types::DebuggerGlobalScope;
|
||||
#[cfg(feature = "webgpu")]
|
||||
use crate::dom::webgpu::identityhub::IdentityHub;
|
||||
use crate::dom::worker::{TrustedWorkerAddress, Worker};
|
||||
|
@ -427,6 +428,19 @@ impl DedicatedWorkerGlobalScope {
|
|||
};
|
||||
Runtime::new_with_parent(Some(parent), Some(task_source))
|
||||
};
|
||||
let debugger_global = DebuggerGlobalScope::new(
|
||||
&runtime,
|
||||
pipeline_id,
|
||||
init.to_devtools_sender.clone(),
|
||||
init.mem_profiler_chan.clone(),
|
||||
init.time_profiler_chan.clone(),
|
||||
init.script_to_constellation_chan.clone(),
|
||||
init.resource_threads.clone(),
|
||||
#[cfg(feature = "webgpu")]
|
||||
gpu_id_hub.clone(),
|
||||
CanGc::note(),
|
||||
);
|
||||
debugger_global.execute(CanGc::note());
|
||||
|
||||
let context_for_interrupt = runtime.thread_safe_js_context();
|
||||
let _ = context_sender.send(context_for_interrupt);
|
||||
|
@ -453,6 +467,7 @@ impl DedicatedWorkerGlobalScope {
|
|||
}
|
||||
}
|
||||
|
||||
let worker_id = init.worker_id;
|
||||
let global = DedicatedWorkerGlobalScope::new(
|
||||
init,
|
||||
DOMString::from_string(worker_name),
|
||||
|
@ -471,6 +486,12 @@ impl DedicatedWorkerGlobalScope {
|
|||
control_receiver,
|
||||
insecure_requests_policy,
|
||||
);
|
||||
debugger_global.fire_add_debuggee(
|
||||
CanGc::note(),
|
||||
global.upcast(),
|
||||
pipeline_id,
|
||||
Some(worker_id),
|
||||
);
|
||||
// FIXME(njn): workers currently don't have a unique ID suitable for using in reporter
|
||||
// registration (#6631), so we instead use a random number and cross our fingers.
|
||||
let scope = global.upcast::<WorkerGlobalScope>();
|
||||
|
|
|
@ -500,6 +500,7 @@ pub(crate) mod performancepainttiming;
|
|||
pub(crate) mod performanceresourcetiming;
|
||||
pub(crate) mod permissions;
|
||||
pub(crate) mod permissionstatus;
|
||||
pub(crate) mod pipelineid;
|
||||
pub(crate) mod plugin;
|
||||
pub(crate) mod pluginarray;
|
||||
#[allow(dead_code)]
|
||||
|
|
46
components/script/dom/pipelineid.rs
Normal file
46
components/script/dom/pipelineid.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
/* 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 dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::DebuggerEventBinding::PipelineIdMethods;
|
||||
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
pub(crate) struct PipelineId {
|
||||
reflector_: Reflector,
|
||||
#[no_trace]
|
||||
inner: base::id::PipelineId,
|
||||
}
|
||||
|
||||
impl PipelineId {
|
||||
pub(crate) fn new(
|
||||
global: &GlobalScope,
|
||||
pipeline_id: base::id::PipelineId,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<Self> {
|
||||
reflect_dom_object(
|
||||
Box::new(Self {
|
||||
reflector_: Reflector::new(),
|
||||
inner: pipeline_id,
|
||||
}),
|
||||
global,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PipelineIdMethods<crate::DomTypeHolder> for PipelineId {
|
||||
// check-tidy: no specs after this line
|
||||
fn NamespaceId(&self) -> u32 {
|
||||
self.inner.namespace_id.0
|
||||
}
|
||||
|
||||
fn Index(&self) -> u32 {
|
||||
self.inner.index.0.get()
|
||||
}
|
||||
}
|
|
@ -950,7 +950,7 @@ impl ScriptThread {
|
|||
};
|
||||
let debugger_global = DebuggerGlobalScope::new(
|
||||
&js_runtime.clone(),
|
||||
senders.self_sender.clone(),
|
||||
PipelineId::new(),
|
||||
senders.devtools_server_sender.clone(),
|
||||
senders.memory_profiler_sender.clone(),
|
||||
senders.time_profiler_sender.clone(),
|
||||
|
@ -3446,8 +3446,12 @@ impl ScriptThread {
|
|||
incomplete.load_data.inherited_secure_context,
|
||||
incomplete.theme,
|
||||
);
|
||||
self.debugger_global
|
||||
.fire_add_debuggee(can_gc, window.upcast());
|
||||
self.debugger_global.fire_add_debuggee(
|
||||
can_gc,
|
||||
window.upcast(),
|
||||
incomplete.pipeline_id,
|
||||
None,
|
||||
);
|
||||
|
||||
let _realm = enter_realm(&*window);
|
||||
|
||||
|
|
|
@ -7,4 +7,12 @@
|
|||
[Exposed=DebuggerGlobalScope]
|
||||
interface DebuggerEvent : Event {
|
||||
readonly attribute object global;
|
||||
readonly attribute PipelineId pipelineId;
|
||||
readonly attribute DOMString? workerId;
|
||||
};
|
||||
|
||||
[Exposed=DebuggerGlobalScope]
|
||||
interface PipelineId {
|
||||
readonly attribute unsigned long namespaceId;
|
||||
readonly attribute unsigned long index;
|
||||
};
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
|
||||
use core::fmt;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
use std::net::TcpStream;
|
||||
use std::str::FromStr;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use base::cross_process_instant::CrossProcessInstant;
|
||||
|
@ -488,6 +490,18 @@ impl StartedTimelineMarker {
|
|||
}
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
|
||||
pub struct WorkerId(pub Uuid);
|
||||
impl Display for WorkerId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
impl FromStr for WorkerId {
|
||||
type Err = uuid::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(s.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
|
@ -1,14 +1,42 @@
|
|||
if (!("dbg" in this)) {
|
||||
dbg = new Debugger;
|
||||
if ("dbg" in this) {
|
||||
throw new Error("Debugger script must not run more than once!");
|
||||
}
|
||||
|
||||
const dbg = new Debugger;
|
||||
const debuggeesToPipelineIds = new Map;
|
||||
const debuggeesToWorkerIds = new Map;
|
||||
|
||||
dbg.onNewGlobalObject = function(global) {
|
||||
};
|
||||
|
||||
dbg.onNewScript = function(script, global) {
|
||||
};
|
||||
dbg.onNewScript = function(script, /* undefined; seems to be `script.global` now */ global) {
|
||||
try {
|
||||
// TODO: notify script system about new source
|
||||
/* notifyNewSource */({
|
||||
pipelineId: debuggeesToPipelineIds.get(script.global),
|
||||
workerId: debuggeesToWorkerIds.get(script.global),
|
||||
spidermonkeyId: script.source.id,
|
||||
url: script.source.url,
|
||||
urlOverride: script.source.displayURL,
|
||||
text: script.source.text,
|
||||
introductionType: script.source.introductionType ?? null,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
}
|
||||
};
|
||||
|
||||
addEventListener("addDebuggee", event => {
|
||||
const {global} = event;
|
||||
const {global, pipelineId: {namespaceId, index}, workerId} = event;
|
||||
dbg.addDebuggee(global);
|
||||
const debuggerObject = dbg.addDebuggee(global);
|
||||
debuggeesToPipelineIds.set(debuggerObject, {
|
||||
namespaceId,
|
||||
index,
|
||||
});
|
||||
debuggeesToWorkerIds.set(debuggerObject, workerId);
|
||||
});
|
||||
|
||||
function logError(error) {
|
||||
console.log(`[debugger] ERROR at ${error.fileName}:${error.lineNumber}:${error.columnNumber}: ${error.name}: ${error.message}`);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue