mirror of
https://github.com/servo/servo.git
synced 2025-09-27 23:30:08 +01:00
devtools: Show clients where they can set breakpoints (#37667)
devtools clients query source actors to determine where the user can set breakpoints in a source. there are two relevant requests here: `getBreakableLines` controls which line numbers can be clicked in the margin, and once a line number is clicked, `getBreakpointPositionsCompressed` controls where to show breakpoint buttons within that line. this patch handles those requests by querying the [SpiderMonkey Debugger API](https://firefox-source-docs.mozilla.org/js/Debugger/) for that information: - devtools sends its script thread a GetPossibleBreakpoints message for the source’s [`spidermonkey_id`](https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Source.html#id) - the script thread fires a `getPossibleBreakpoints` event into its debugger global - the debugger script looks up the [root](https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.html#onnewscript-script-global) [Debugger.Script](https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Script.html#getpossiblebreakpoints-query) for that source, calls [getPossibleBreakpoints()](https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Script.html#getpossiblebreakpoints-query), and returns the result via DebuggerGlobalScope#getPossibleBreakpointsResult() - that method takes the pending result sender, and sends the result back to devtools - devtools massages the result into the format required by the request, and replies to the client as a result, users of the Firefox devtools client can now set breakpoints, though they don’t have any effect. Testing: this patch adds new devtools tests Fixes: part of #36027 <img width="1433" height="1328" alt="image" src="https://github.com/user-attachments/assets/f0cd31e0-742f-44d3-8c5d-ceedd9a2706d" /> --------- Signed-off-by: Delan Azabani <dazabani@igalia.com> Co-authored-by: atbrakhi <atbrakhi@igalia.com>
This commit is contained in:
parent
1995e22e19
commit
f5b631e270
14 changed files with 340 additions and 8 deletions
|
@ -31,6 +31,9 @@ impl Actor for BreakpointListActor {
|
||||||
_stream_id: crate::StreamId,
|
_stream_id: crate::StreamId,
|
||||||
) -> Result<(), ActorError> {
|
) -> Result<(), ActorError> {
|
||||||
match msg_type {
|
match msg_type {
|
||||||
|
// Client wants to set a breakpoint.
|
||||||
|
// Seems to be infallible, unlike the thread actor’s `setBreakpoint`.
|
||||||
|
// <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#breakpoints>
|
||||||
"setBreakpoint" => {
|
"setBreakpoint" => {
|
||||||
let msg = EmptyReplyMsg { from: self.name() };
|
let msg = EmptyReplyMsg { from: self.name() };
|
||||||
request.reply_final(&msg)?
|
request.reply_final(&msg)?
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use base::id::PipelineId;
|
use base::id::PipelineId;
|
||||||
|
use devtools_traits::DevtoolScriptControlMsg;
|
||||||
|
use ipc_channel::ipc::{IpcSender, channel};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
|
@ -61,6 +63,8 @@ pub struct SourceActor {
|
||||||
pub spidermonkey_id: u32,
|
pub spidermonkey_id: u32,
|
||||||
/// `introductionType` in SpiderMonkey `CompileOptionsWrapper`.
|
/// `introductionType` in SpiderMonkey `CompileOptionsWrapper`.
|
||||||
pub introduction_type: String,
|
pub introduction_type: String,
|
||||||
|
|
||||||
|
script_sender: IpcSender<DevtoolScriptControlMsg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -71,6 +75,24 @@ struct SourceContentReply {
|
||||||
source: String,
|
source: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct GetBreakableLinesReply {
|
||||||
|
from: String,
|
||||||
|
// Line numbers are one-based.
|
||||||
|
// <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#source-locations>
|
||||||
|
lines: BTreeSet<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct GetBreakpointPositionsCompressedReply {
|
||||||
|
from: String,
|
||||||
|
// Column numbers are in UTF-16 code units, not Unicode scalar values or grapheme clusters.
|
||||||
|
// Line number are one-based. Column numbers are zero-based.
|
||||||
|
// FIXME: the docs say column numbers are one-based, but this appears to be incorrect.
|
||||||
|
// <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#source-locations>
|
||||||
|
positions: BTreeMap<u32, BTreeSet<u32>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl SourceManager {
|
impl SourceManager {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -101,6 +123,7 @@ impl SourceActor {
|
||||||
content_type: Option<String>,
|
content_type: Option<String>,
|
||||||
spidermonkey_id: u32,
|
spidermonkey_id: u32,
|
||||||
introduction_type: String,
|
introduction_type: String,
|
||||||
|
script_sender: IpcSender<DevtoolScriptControlMsg>,
|
||||||
) -> SourceActor {
|
) -> SourceActor {
|
||||||
SourceActor {
|
SourceActor {
|
||||||
name,
|
name,
|
||||||
|
@ -110,9 +133,11 @@ impl SourceActor {
|
||||||
is_black_boxed: false,
|
is_black_boxed: false,
|
||||||
spidermonkey_id,
|
spidermonkey_id,
|
||||||
introduction_type,
|
introduction_type,
|
||||||
|
script_sender,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new_registered(
|
pub fn new_registered(
|
||||||
actors: &mut ActorRegistry,
|
actors: &mut ActorRegistry,
|
||||||
pipeline_id: PipelineId,
|
pipeline_id: PipelineId,
|
||||||
|
@ -121,6 +146,7 @@ impl SourceActor {
|
||||||
content_type: Option<String>,
|
content_type: Option<String>,
|
||||||
spidermonkey_id: u32,
|
spidermonkey_id: u32,
|
||||||
introduction_type: String,
|
introduction_type: String,
|
||||||
|
script_sender: IpcSender<DevtoolScriptControlMsg>,
|
||||||
) -> &SourceActor {
|
) -> &SourceActor {
|
||||||
let source_actor_name = actors.new_name("source");
|
let source_actor_name = actors.new_name("source");
|
||||||
|
|
||||||
|
@ -131,6 +157,7 @@ impl SourceActor {
|
||||||
content_type,
|
content_type,
|
||||||
spidermonkey_id,
|
spidermonkey_id,
|
||||||
introduction_type,
|
introduction_type,
|
||||||
|
script_sender,
|
||||||
);
|
);
|
||||||
actors.register(Box::new(source_actor));
|
actors.register(Box::new(source_actor));
|
||||||
actors.register_source_actor(pipeline_id, &source_actor_name);
|
actors.register_source_actor(pipeline_id, &source_actor_name);
|
||||||
|
@ -181,6 +208,54 @@ impl Actor for SourceActor {
|
||||||
};
|
};
|
||||||
request.reply_final(&reply)?
|
request.reply_final(&reply)?
|
||||||
},
|
},
|
||||||
|
// Client wants to know which lines can have breakpoints.
|
||||||
|
// Sent when opening a source in the Sources panel, and controls whether the line numbers can be clicked.
|
||||||
|
"getBreakableLines" => {
|
||||||
|
let (tx, rx) = channel().map_err(|_| ActorError::Internal)?;
|
||||||
|
self.script_sender
|
||||||
|
.send(DevtoolScriptControlMsg::GetPossibleBreakpoints(
|
||||||
|
self.spidermonkey_id,
|
||||||
|
tx,
|
||||||
|
))
|
||||||
|
.map_err(|_| ActorError::Internal)?;
|
||||||
|
let result = rx.recv().map_err(|_| ActorError::Internal)?;
|
||||||
|
let lines = result
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| entry.line_number)
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
let reply = GetBreakableLinesReply {
|
||||||
|
from: self.name(),
|
||||||
|
lines,
|
||||||
|
};
|
||||||
|
request.reply_final(&reply)?
|
||||||
|
},
|
||||||
|
// Client wants to know which columns in the line can have breakpoints.
|
||||||
|
// Sent when the user tries to set a breakpoint by clicking a line number in a source.
|
||||||
|
"getBreakpointPositionsCompressed" => {
|
||||||
|
let (tx, rx) = channel().map_err(|_| ActorError::Internal)?;
|
||||||
|
self.script_sender
|
||||||
|
.send(DevtoolScriptControlMsg::GetPossibleBreakpoints(
|
||||||
|
self.spidermonkey_id,
|
||||||
|
tx,
|
||||||
|
))
|
||||||
|
.map_err(|_| ActorError::Internal)?;
|
||||||
|
let result = rx.recv().map_err(|_| ActorError::Internal)?;
|
||||||
|
let mut positions: BTreeMap<u32, BTreeSet<u32>> = BTreeMap::default();
|
||||||
|
for entry in result {
|
||||||
|
// Line number are one-based. Column numbers are zero-based.
|
||||||
|
// FIXME: the docs say column numbers are one-based, but this appears to be incorrect.
|
||||||
|
// <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#source-locations>
|
||||||
|
positions
|
||||||
|
.entry(entry.line_number)
|
||||||
|
.or_default()
|
||||||
|
.insert(entry.column_number - 1);
|
||||||
|
}
|
||||||
|
let reply = GetBreakpointPositionsCompressedReply {
|
||||||
|
from: self.name(),
|
||||||
|
positions,
|
||||||
|
};
|
||||||
|
request.reply_final(&reply)?
|
||||||
|
},
|
||||||
_ => return Err(ActorError::UnrecognizedPacketType),
|
_ => return Err(ActorError::UnrecognizedPacketType),
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -256,9 +256,10 @@ impl DevtoolsInstance {
|
||||||
worker_id,
|
worker_id,
|
||||||
)) => self.handle_console_message(pipeline_id, worker_id, console_message),
|
)) => self.handle_console_message(pipeline_id, worker_id, console_message),
|
||||||
DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::CreateSourceActor(
|
DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::CreateSourceActor(
|
||||||
|
script_sender,
|
||||||
pipeline_id,
|
pipeline_id,
|
||||||
source_info,
|
source_info,
|
||||||
)) => self.handle_create_source_actor(pipeline_id, source_info),
|
)) => self.handle_create_source_actor(script_sender, pipeline_id, source_info),
|
||||||
DevtoolsControlMsg::FromScript(
|
DevtoolsControlMsg::FromScript(
|
||||||
ScriptToDevtoolsControlMsg::UpdateSourceContent(pipeline_id, source_content),
|
ScriptToDevtoolsControlMsg::UpdateSourceContent(pipeline_id, source_content),
|
||||||
) => self.handle_update_source_content(pipeline_id, source_content),
|
) => self.handle_update_source_content(pipeline_id, source_content),
|
||||||
|
@ -540,7 +541,12 @@ impl DevtoolsInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_create_source_actor(&mut self, pipeline_id: PipelineId, source_info: SourceInfo) {
|
fn handle_create_source_actor(
|
||||||
|
&mut self,
|
||||||
|
script_sender: IpcSender<DevtoolScriptControlMsg>,
|
||||||
|
pipeline_id: PipelineId,
|
||||||
|
source_info: SourceInfo,
|
||||||
|
) {
|
||||||
let mut actors = self.actors.lock().unwrap();
|
let mut actors = self.actors.lock().unwrap();
|
||||||
|
|
||||||
let source_content = source_info
|
let source_content = source_info
|
||||||
|
@ -554,6 +560,7 @@ impl DevtoolsInstance {
|
||||||
source_info.content_type,
|
source_info.content_type,
|
||||||
source_info.spidermonkey_id,
|
source_info.spidermonkey_id,
|
||||||
source_info.introduction_type,
|
source_info.introduction_type,
|
||||||
|
script_sender,
|
||||||
);
|
);
|
||||||
let source_actor_name = source_actor.name.clone();
|
let source_actor_name = source_actor.name.clone();
|
||||||
let source_form = source_actor.source_form();
|
let source_form = source_actor.source_form();
|
||||||
|
|
62
components/script/dom/debuggergetpossiblebreakpointsevent.rs
Normal file
62
components/script/dom/debuggergetpossiblebreakpointsevent.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/* 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::fmt::Debug;
|
||||||
|
|
||||||
|
use dom_struct::dom_struct;
|
||||||
|
|
||||||
|
use crate::dom::bindings::codegen::Bindings::DebuggerGetPossibleBreakpointsEventBinding::DebuggerGetPossibleBreakpointsEventMethods;
|
||||||
|
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
|
||||||
|
use crate::dom::bindings::reflector::reflect_dom_object;
|
||||||
|
use crate::dom::bindings::root::DomRoot;
|
||||||
|
use crate::dom::event::Event;
|
||||||
|
use crate::dom::types::GlobalScope;
|
||||||
|
use crate::script_runtime::CanGc;
|
||||||
|
|
||||||
|
#[dom_struct]
|
||||||
|
/// Event for Rust → JS calls in [`crate::dom::DebuggerGlobalScope`].
|
||||||
|
pub(crate) struct DebuggerGetPossibleBreakpointsEvent {
|
||||||
|
event: Event,
|
||||||
|
spidermonkey_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebuggerGetPossibleBreakpointsEvent {
|
||||||
|
pub(crate) fn new(
|
||||||
|
debugger_global: &GlobalScope,
|
||||||
|
spidermonkey_id: u32,
|
||||||
|
can_gc: CanGc,
|
||||||
|
) -> DomRoot<Self> {
|
||||||
|
let result = Box::new(Self {
|
||||||
|
event: Event::new_inherited(),
|
||||||
|
spidermonkey_id,
|
||||||
|
});
|
||||||
|
let result = reflect_dom_object(result, debugger_global, can_gc);
|
||||||
|
result
|
||||||
|
.event
|
||||||
|
.init_event("getPossibleBreakpoints".into(), false, false);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebuggerGetPossibleBreakpointsEventMethods<crate::DomTypeHolder>
|
||||||
|
for DebuggerGetPossibleBreakpointsEvent
|
||||||
|
{
|
||||||
|
// check-tidy: no specs after this line
|
||||||
|
fn SpidermonkeyId(&self) -> u32 {
|
||||||
|
self.spidermonkey_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn IsTrusted(&self) -> bool {
|
||||||
|
self.event.IsTrusted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for DebuggerGetPossibleBreakpointsEvent {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("DebuggerGetPossibleBreakpointsEvent")
|
||||||
|
.field("spidermonkey_id", &self.spidermonkey_id)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,11 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use base::id::{Index, PipelineId, PipelineNamespaceId};
|
use base::id::{Index, PipelineId, PipelineNamespaceId};
|
||||||
use constellation_traits::ScriptToConstellationChan;
|
use constellation_traits::ScriptToConstellationChan;
|
||||||
use devtools_traits::{ScriptToDevtoolsControlMsg, SourceInfo, WorkerId};
|
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, SourceInfo, WorkerId};
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use embedder_traits::JavaScriptEvaluationError;
|
use embedder_traits::JavaScriptEvaluationError;
|
||||||
use embedder_traits::resources::{self, Resource};
|
use embedder_traits::resources::{self, Resource};
|
||||||
|
@ -14,6 +16,7 @@ use js::rust::Runtime;
|
||||||
use js::rust::wrappers::JS_DefineDebuggerObject;
|
use js::rust::wrappers::JS_DefineDebuggerObject;
|
||||||
use net_traits::ResourceThreads;
|
use net_traits::ResourceThreads;
|
||||||
use profile_traits::{mem, time};
|
use profile_traits::{mem, time};
|
||||||
|
use script_bindings::codegen::GenericBindings::DebuggerGetPossibleBreakpointsEventBinding::RecommendedBreakpointLocation;
|
||||||
use script_bindings::codegen::GenericBindings::DebuggerGlobalScopeBinding::{
|
use script_bindings::codegen::GenericBindings::DebuggerGlobalScopeBinding::{
|
||||||
DebuggerGlobalScopeMethods, NotifyNewSource,
|
DebuggerGlobalScopeMethods, NotifyNewSource,
|
||||||
};
|
};
|
||||||
|
@ -27,7 +30,7 @@ use crate::dom::bindings::inheritance::Castable;
|
||||||
use crate::dom::bindings::root::DomRoot;
|
use crate::dom::bindings::root::DomRoot;
|
||||||
use crate::dom::bindings::utils::define_all_exposed_interfaces;
|
use crate::dom::bindings::utils::define_all_exposed_interfaces;
|
||||||
use crate::dom::globalscope::GlobalScope;
|
use crate::dom::globalscope::GlobalScope;
|
||||||
use crate::dom::types::{DebuggerAddDebuggeeEvent, Event};
|
use crate::dom::types::{DebuggerAddDebuggeeEvent, DebuggerGetPossibleBreakpointsEvent, Event};
|
||||||
#[cfg(feature = "testbinding")]
|
#[cfg(feature = "testbinding")]
|
||||||
#[cfg(feature = "webgpu")]
|
#[cfg(feature = "webgpu")]
|
||||||
use crate::dom::webgpu::identityhub::IdentityHub;
|
use crate::dom::webgpu::identityhub::IdentityHub;
|
||||||
|
@ -41,6 +44,11 @@ use crate::script_runtime::{CanGc, IntroductionType, JSContext};
|
||||||
/// <https://firefox-source-docs.mozilla.org/js/Debugger/>
|
/// <https://firefox-source-docs.mozilla.org/js/Debugger/>
|
||||||
pub(crate) struct DebuggerGlobalScope {
|
pub(crate) struct DebuggerGlobalScope {
|
||||||
global_scope: GlobalScope,
|
global_scope: GlobalScope,
|
||||||
|
#[no_trace]
|
||||||
|
devtools_to_script_sender: IpcSender<DevtoolScriptControlMsg>,
|
||||||
|
#[no_trace]
|
||||||
|
get_possible_breakpoints_result_sender:
|
||||||
|
RefCell<Option<IpcSender<Vec<devtools_traits::RecommendedBreakpointLocation>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DebuggerGlobalScope {
|
impl DebuggerGlobalScope {
|
||||||
|
@ -55,7 +63,8 @@ impl DebuggerGlobalScope {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
runtime: &Runtime,
|
runtime: &Runtime,
|
||||||
debugger_pipeline_id: PipelineId,
|
debugger_pipeline_id: PipelineId,
|
||||||
devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
|
script_to_devtools_sender: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
|
||||||
|
devtools_to_script_sender: IpcSender<DevtoolScriptControlMsg>,
|
||||||
mem_profiler_chan: mem::ProfilerChan,
|
mem_profiler_chan: mem::ProfilerChan,
|
||||||
time_profiler_chan: time::ProfilerChan,
|
time_profiler_chan: time::ProfilerChan,
|
||||||
script_to_constellation_chan: ScriptToConstellationChan,
|
script_to_constellation_chan: ScriptToConstellationChan,
|
||||||
|
@ -66,7 +75,7 @@ impl DebuggerGlobalScope {
|
||||||
let global = Box::new(Self {
|
let global = Box::new(Self {
|
||||||
global_scope: GlobalScope::new_inherited(
|
global_scope: GlobalScope::new_inherited(
|
||||||
debugger_pipeline_id,
|
debugger_pipeline_id,
|
||||||
devtools_chan,
|
script_to_devtools_sender,
|
||||||
mem_profiler_chan,
|
mem_profiler_chan,
|
||||||
time_profiler_chan,
|
time_profiler_chan,
|
||||||
script_to_constellation_chan,
|
script_to_constellation_chan,
|
||||||
|
@ -81,6 +90,8 @@ impl DebuggerGlobalScope {
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
devtools_to_script_sender,
|
||||||
|
get_possible_breakpoints_result_sender: RefCell::new(None),
|
||||||
});
|
});
|
||||||
let global = unsafe {
|
let global = unsafe {
|
||||||
DebuggerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(
|
DebuggerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(
|
||||||
|
@ -155,6 +166,28 @@ impl DebuggerGlobalScope {
|
||||||
"Guaranteed by DebuggerAddDebuggeeEvent::new"
|
"Guaranteed by DebuggerAddDebuggeeEvent::new"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fire_get_possible_breakpoints(
|
||||||
|
&self,
|
||||||
|
can_gc: CanGc,
|
||||||
|
spidermonkey_id: u32,
|
||||||
|
result_sender: IpcSender<Vec<devtools_traits::RecommendedBreakpointLocation>>,
|
||||||
|
) {
|
||||||
|
assert!(
|
||||||
|
self.get_possible_breakpoints_result_sender
|
||||||
|
.replace(Some(result_sender))
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
|
let event = DomRoot::upcast::<Event>(DebuggerGetPossibleBreakpointsEvent::new(
|
||||||
|
self.upcast(),
|
||||||
|
spidermonkey_id,
|
||||||
|
can_gc,
|
||||||
|
));
|
||||||
|
assert!(
|
||||||
|
DomRoot::upcast::<Event>(event).fire(self.upcast(), can_gc),
|
||||||
|
"Guaranteed by DebuggerGetPossibleBreakpointsEvent::new"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
|
impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
|
||||||
|
@ -242,6 +275,7 @@ impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
|
||||||
spidermonkey_id: args.spidermonkeyId,
|
spidermonkey_id: args.spidermonkeyId,
|
||||||
};
|
};
|
||||||
if let Err(error) = devtools_chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
|
if let Err(error) = devtools_chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
|
||||||
|
self.devtools_to_script_sender.clone(),
|
||||||
pipeline_id,
|
pipeline_id,
|
||||||
source_info,
|
source_info,
|
||||||
)) {
|
)) {
|
||||||
|
@ -251,4 +285,27 @@ impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
|
||||||
debug!("Not creating debuggee for script with no `introductionType`");
|
debug!("Not creating debuggee for script with no `introductionType`");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn GetPossibleBreakpointsResult(
|
||||||
|
&self,
|
||||||
|
event: &DebuggerGetPossibleBreakpointsEvent,
|
||||||
|
result: Vec<RecommendedBreakpointLocation>,
|
||||||
|
) {
|
||||||
|
info!("GetPossibleBreakpointsResult: {event:?} {result:?}");
|
||||||
|
let sender = self
|
||||||
|
.get_possible_breakpoints_result_sender
|
||||||
|
.take()
|
||||||
|
.expect("Guaranteed by Self::fire_get_possible_breakpoints()");
|
||||||
|
let _ = sender.send(
|
||||||
|
result
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| devtools_traits::RecommendedBreakpointLocation {
|
||||||
|
offset: entry.offset,
|
||||||
|
line_number: entry.lineNumber,
|
||||||
|
column_number: entry.columnNumber,
|
||||||
|
is_step_start: entry.isStepStart,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -430,6 +430,9 @@ impl DedicatedWorkerGlobalScope {
|
||||||
&runtime,
|
&runtime,
|
||||||
pipeline_id,
|
pipeline_id,
|
||||||
init.to_devtools_sender.clone(),
|
init.to_devtools_sender.clone(),
|
||||||
|
init.from_devtools_sender
|
||||||
|
.clone()
|
||||||
|
.expect("Guaranteed by Worker::Constructor"),
|
||||||
init.mem_profiler_chan.clone(),
|
init.mem_profiler_chan.clone(),
|
||||||
init.time_profiler_chan.clone(),
|
init.time_profiler_chan.clone(),
|
||||||
init.script_to_constellation_chan.clone(),
|
init.script_to_constellation_chan.clone(),
|
||||||
|
|
|
@ -290,6 +290,7 @@ pub(crate) mod datatransfer;
|
||||||
pub(crate) mod datatransferitem;
|
pub(crate) mod datatransferitem;
|
||||||
pub(crate) mod datatransferitemlist;
|
pub(crate) mod datatransferitemlist;
|
||||||
pub(crate) mod debuggeradddebuggeeevent;
|
pub(crate) mod debuggeradddebuggeeevent;
|
||||||
|
pub(crate) mod debuggergetpossiblebreakpointsevent;
|
||||||
pub(crate) mod debuggerglobalscope;
|
pub(crate) mod debuggerglobalscope;
|
||||||
pub(crate) mod dedicatedworkerglobalscope;
|
pub(crate) mod dedicatedworkerglobalscope;
|
||||||
pub(crate) mod defaultteereadrequest;
|
pub(crate) mod defaultteereadrequest;
|
||||||
|
|
|
@ -945,6 +945,7 @@ impl ScriptThread {
|
||||||
&js_runtime.clone(),
|
&js_runtime.clone(),
|
||||||
PipelineId::new(),
|
PipelineId::new(),
|
||||||
senders.devtools_server_sender.clone(),
|
senders.devtools_server_sender.clone(),
|
||||||
|
senders.devtools_client_to_script_thread_sender.clone(),
|
||||||
senders.memory_profiler_sender.clone(),
|
senders.memory_profiler_sender.clone(),
|
||||||
senders.time_profiler_sender.clone(),
|
senders.time_profiler_sender.clone(),
|
||||||
script_to_constellation_chan,
|
script_to_constellation_chan,
|
||||||
|
@ -2180,6 +2181,13 @@ impl ScriptThread {
|
||||||
DevtoolScriptControlMsg::HighlightDomNode(id, node_id) => {
|
DevtoolScriptControlMsg::HighlightDomNode(id, node_id) => {
|
||||||
devtools::handle_highlight_dom_node(&documents, id, node_id)
|
devtools::handle_highlight_dom_node(&documents, id, node_id)
|
||||||
},
|
},
|
||||||
|
DevtoolScriptControlMsg::GetPossibleBreakpoints(spidermonkey_id, result_sender) => {
|
||||||
|
self.debugger_global.fire_get_possible_breakpoints(
|
||||||
|
can_gc,
|
||||||
|
spidermonkey_id,
|
||||||
|
result_sender,
|
||||||
|
);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -836,6 +836,10 @@ Dictionaries = {
|
||||||
'derives': ['Clone'],
|
'derives': ['Clone'],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'RecommendedBreakpointLocation': {
|
||||||
|
'derives': ['Debug'],
|
||||||
|
},
|
||||||
|
|
||||||
'Report': {
|
'Report': {
|
||||||
'derives': ['Clone', 'MallocSizeOf'],
|
'derives': ['Clone', 'MallocSizeOf'],
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* 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.
|
||||||
|
[Exposed=DebuggerGlobalScope]
|
||||||
|
interface DebuggerGetPossibleBreakpointsEvent : Event {
|
||||||
|
readonly attribute unsigned long spidermonkeyId;
|
||||||
|
};
|
||||||
|
|
||||||
|
partial interface DebuggerGlobalScope {
|
||||||
|
undefined getPossibleBreakpointsResult(
|
||||||
|
DebuggerGetPossibleBreakpointsEvent event,
|
||||||
|
sequence<RecommendedBreakpointLocation> result);
|
||||||
|
};
|
||||||
|
|
||||||
|
// <https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Script.html#getpossiblebreakpoints-query>
|
||||||
|
dictionary RecommendedBreakpointLocation {
|
||||||
|
required unsigned long offset;
|
||||||
|
required unsigned long lineNumber;
|
||||||
|
required unsigned long columnNumber;
|
||||||
|
required boolean isStepStart;
|
||||||
|
};
|
|
@ -108,7 +108,7 @@ pub enum ScriptToDevtoolsControlMsg {
|
||||||
TitleChanged(PipelineId, String),
|
TitleChanged(PipelineId, String),
|
||||||
|
|
||||||
/// Get source information from script
|
/// Get source information from script
|
||||||
CreateSourceActor(PipelineId, SourceInfo),
|
CreateSourceActor(IpcSender<DevtoolScriptControlMsg>, PipelineId, SourceInfo),
|
||||||
|
|
||||||
UpdateSourceContent(PipelineId, String),
|
UpdateSourceContent(PipelineId, String),
|
||||||
}
|
}
|
||||||
|
@ -284,6 +284,8 @@ pub enum DevtoolScriptControlMsg {
|
||||||
SimulateColorScheme(PipelineId, Theme),
|
SimulateColorScheme(PipelineId, Theme),
|
||||||
/// Highlight the given DOM node
|
/// Highlight the given DOM node
|
||||||
HighlightDomNode(PipelineId, Option<String>),
|
HighlightDomNode(PipelineId, Option<String>),
|
||||||
|
|
||||||
|
GetPossibleBreakpoints(u32, IpcSender<Vec<RecommendedBreakpointLocation>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
@ -608,3 +610,12 @@ pub struct SourceInfo {
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
pub spidermonkey_id: u32,
|
pub spidermonkey_id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RecommendedBreakpointLocation {
|
||||||
|
pub offset: u32,
|
||||||
|
pub line_number: u32,
|
||||||
|
pub column_number: u32,
|
||||||
|
pub is_step_start: bool,
|
||||||
|
}
|
||||||
|
|
|
@ -577,6 +577,20 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
expected_content = open(self.get_test_path("sources_content_with_responsexml/test.html")).read()
|
expected_content = open(self.get_test_path("sources_content_with_responsexml/test.html")).read()
|
||||||
self.assert_source_content(Source("inlineScript", f"{self.base_urls[0]}/test.html"), expected_content)
|
self.assert_source_content(Source("inlineScript", f"{self.base_urls[0]}/test.html"), expected_content)
|
||||||
|
|
||||||
|
def test_source_breakable_lines_and_positions(self):
|
||||||
|
self.start_web_server(test_dir=self.get_test_path("sources_breakable_lines_and_positions"))
|
||||||
|
self.run_servoshell()
|
||||||
|
self.assert_source_breakable_lines_and_positions(
|
||||||
|
Source("inlineScript", f"{self.base_urls[0]}/test.html"),
|
||||||
|
[4, 5, 6, 7],
|
||||||
|
{
|
||||||
|
"4": [4, 12, 20, 28],
|
||||||
|
"5": [15, 23, 31, 39], # includes 3 surrogate pairs
|
||||||
|
"6": [15, 23, 31, 39], # includes 1 surrogate pair
|
||||||
|
"7": [0],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# Sets `base_url` and `web_server` and `web_server_thread`.
|
# Sets `base_url` and `web_server` and `web_server_thread`.
|
||||||
def start_web_server(self, *, test_dir=None, num_servers=2):
|
def start_web_server(self, *, test_dir=None, num_servers=2):
|
||||||
assert self.base_urls is None and self.web_servers is None and self.web_server_threads is None
|
assert self.base_urls is None and self.web_servers is None and self.web_server_threads is None
|
||||||
|
@ -763,6 +777,55 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
devtools.client.disconnect()
|
devtools.client.disconnect()
|
||||||
|
|
||||||
|
def assert_source_breakable_lines_and_positions(
|
||||||
|
self,
|
||||||
|
expected_source: Source,
|
||||||
|
expected_breakable_lines: list[int],
|
||||||
|
expected_positions: dict[int, list[int]],
|
||||||
|
*,
|
||||||
|
devtools: Optional[Devtools] = None,
|
||||||
|
):
|
||||||
|
if devtools is None:
|
||||||
|
devtools = self._setup_devtools_client()
|
||||||
|
|
||||||
|
done = Future()
|
||||||
|
source_actors = {}
|
||||||
|
|
||||||
|
def on_source_resource(data):
|
||||||
|
for [resource_type, sources] in data["array"]:
|
||||||
|
try:
|
||||||
|
self.assertEqual(resource_type, "source")
|
||||||
|
for source in sources:
|
||||||
|
if Source(source["introductionType"], source["url"]) == expected_source:
|
||||||
|
source_actors[expected_source] = source["actor"]
|
||||||
|
done.set_result(None)
|
||||||
|
except Exception as e:
|
||||||
|
done.set_result(e)
|
||||||
|
|
||||||
|
for target in devtools.targets:
|
||||||
|
devtools.client.add_event_listener(
|
||||||
|
target["actor"],
|
||||||
|
Events.Watcher.RESOURCES_AVAILABLE_ARRAY,
|
||||||
|
on_source_resource,
|
||||||
|
)
|
||||||
|
devtools.watcher.watch_resources([Resources.SOURCE])
|
||||||
|
|
||||||
|
result: Optional[Exception] = done.result(1)
|
||||||
|
if result:
|
||||||
|
raise result
|
||||||
|
|
||||||
|
# We found at least one source with the given url.
|
||||||
|
self.assertIn(expected_source, source_actors)
|
||||||
|
source_actor = source_actors[expected_source]
|
||||||
|
|
||||||
|
response = devtools.client.send_receive({"to": source_actor, "type": "getBreakableLines"})
|
||||||
|
self.assertEqual(response["lines"], expected_breakable_lines)
|
||||||
|
|
||||||
|
response = devtools.client.send_receive({"to": source_actor, "type": "getBreakpointPositionsCompressed"})
|
||||||
|
self.assertEqual(response["positions"], expected_positions)
|
||||||
|
|
||||||
|
devtools.client.disconnect()
|
||||||
|
|
||||||
def get_test_path(self, path: str) -> str:
|
def get_test_path(self, path: str) -> str:
|
||||||
return os.path.join(DevtoolsTests.script_path, os.path.join("devtools_tests", path))
|
return os.path.join(DevtoolsTests.script_path, os.path.join("devtools_tests", path))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!doctype html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script>
|
||||||
|
console.log(1); console.log(2);
|
||||||
|
/*💖💖💖*/ console.log(1); console.log(2);
|
||||||
|
/*🏳️⚧️*/ console.log(1); console.log(2);
|
||||||
|
</script>
|
|
@ -5,6 +5,7 @@ if ("dbg" in this) {
|
||||||
const dbg = new Debugger;
|
const dbg = new Debugger;
|
||||||
const debuggeesToPipelineIds = new Map;
|
const debuggeesToPipelineIds = new Map;
|
||||||
const debuggeesToWorkerIds = new Map;
|
const debuggeesToWorkerIds = new Map;
|
||||||
|
const sourceIdsToScripts = new Map;
|
||||||
|
|
||||||
dbg.uncaughtExceptionHook = function(error) {
|
dbg.uncaughtExceptionHook = function(error) {
|
||||||
console.error(`[debugger] Uncaught exception at ${error.fileName}:${error.lineNumber}:${error.columnNumber}: ${error.name}: ${error.message}`);
|
console.error(`[debugger] Uncaught exception at ${error.fileName}:${error.lineNumber}:${error.columnNumber}: ${error.name}: ${error.message}`);
|
||||||
|
@ -12,6 +13,7 @@ dbg.uncaughtExceptionHook = function(error) {
|
||||||
|
|
||||||
dbg.onNewScript = function(script, /* undefined; seems to be `script.global` now */ global) {
|
dbg.onNewScript = function(script, /* undefined; seems to be `script.global` now */ global) {
|
||||||
// TODO: handle wasm (`script.source.introductionType == wasm`)
|
// TODO: handle wasm (`script.source.introductionType == wasm`)
|
||||||
|
sourceIdsToScripts.set(script.source.id, script);
|
||||||
notifyNewSource({
|
notifyNewSource({
|
||||||
pipelineId: debuggeesToPipelineIds.get(script.global),
|
pipelineId: debuggeesToPipelineIds.get(script.global),
|
||||||
workerId: debuggeesToWorkerIds.get(script.global),
|
workerId: debuggeesToWorkerIds.get(script.global),
|
||||||
|
@ -33,3 +35,8 @@ addEventListener("addDebuggee", event => {
|
||||||
});
|
});
|
||||||
debuggeesToWorkerIds.set(debuggerObject, workerId);
|
debuggeesToWorkerIds.set(debuggerObject, workerId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addEventListener("getPossibleBreakpoints", event => {
|
||||||
|
const {spidermonkeyId} = event;
|
||||||
|
getPossibleBreakpointsResult(event, sourceIdsToScripts.get(spidermonkeyId).getPossibleBreakpoints(/* TODO: `query` */));
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue