Wire up getPossibleBreakpoints()

Signed-off-by: Delan Azabani <dazabani@igalia.com>
This commit is contained in:
Delan Azabani 2025-08-09 21:53:20 +08:00
parent 0b5a8fa693
commit 99568f4557
10 changed files with 175 additions and 32 deletions

View file

@ -6,6 +6,8 @@ use std::cell::RefCell;
use std::collections::{BTreeMap, BTreeSet};
use base::id::PipelineId;
use devtools_traits::DevtoolScriptControlMsg;
use ipc_channel::ipc::{IpcSender, channel};
use serde::Serialize;
use serde_json::{Map, Value};
use servo_url::ServoUrl;
@ -59,6 +61,8 @@ pub struct SourceActor {
pub spidermonkey_id: u32,
/// `introductionType` in SpiderMonkey `CompileOptionsWrapper`.
pub introduction_type: String,
script_sender: IpcSender<DevtoolScriptControlMsg>,
}
#[derive(Serialize)]
@ -72,13 +76,19 @@ struct SourceContentReply {
#[derive(Serialize)]
struct GetBreakableLinesReply {
from: String,
lines: Vec<usize>,
// 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,
positions: BTreeMap<usize, Vec<usize>>,
// 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 {
@ -111,6 +121,7 @@ impl SourceActor {
content_type: Option<String>,
spidermonkey_id: u32,
introduction_type: String,
script_sender: IpcSender<DevtoolScriptControlMsg>,
) -> SourceActor {
SourceActor {
name,
@ -120,6 +131,7 @@ impl SourceActor {
is_black_boxed: false,
spidermonkey_id,
introduction_type,
script_sender,
}
}
@ -131,6 +143,7 @@ impl SourceActor {
content_type: Option<String>,
spidermonkey_id: u32,
introduction_type: String,
script_sender: IpcSender<DevtoolScriptControlMsg>,
) -> &SourceActor {
let source_actor_name = actors.new_name("source");
@ -141,6 +154,7 @@ impl SourceActor {
content_type,
spidermonkey_id,
introduction_type,
script_sender,
);
actors.register(Box::new(source_actor));
actors.register_source_actor(pipeline_id, &source_actor_name);
@ -190,35 +204,44 @@ impl Actor for SourceActor {
// 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" => {
// Tell the client that every line is breakable.
// TODO: determine which lines are actually breakable.
let line_count = self
.content
.as_ref()
.map_or(0, |content| content.lines().count());
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(),
// Line numbers are one-based.
// <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#source-locations>
lines: (1..=line_count).collect(),
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" => {
// Tell the client that every column is breakable.
// TODO: determine which columns are actually breakable.
let mut positions = BTreeMap::default();
if let Some(content) = self.content.as_ref() {
for (line_number, line) in content.lines().enumerate() {
// Column numbers are in UTF-16 code units, not Unicode scalar values or grapheme clusters.
let column_count = line.encode_utf16().count();
// 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.insert(line_number + 1, (0..column_count).collect());
}
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(),

View file

@ -256,9 +256,10 @@ impl DevtoolsInstance {
worker_id,
)) => self.handle_console_message(pipeline_id, worker_id, console_message),
DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::CreateSourceActor(
script_sender,
pipeline_id,
source_info,
)) => self.handle_create_source_actor(pipeline_id, source_info),
)) => self.handle_create_source_actor(script_sender, pipeline_id, source_info),
DevtoolsControlMsg::FromScript(
ScriptToDevtoolsControlMsg::UpdateSourceContent(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 source_content = source_info
@ -554,6 +560,7 @@ impl DevtoolsInstance {
source_info.content_type,
source_info.spidermonkey_id,
source_info.introduction_type,
script_sender,
);
let source_actor_name = source_actor.name.clone();
let source_form = source_actor.source_form();

View file

@ -2,9 +2,11 @@
* 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::cell::RefCell;
use base::id::{Index, PipelineId, PipelineNamespaceId};
use constellation_traits::ScriptToConstellationChan;
use devtools_traits::{ScriptToDevtoolsControlMsg, SourceInfo, WorkerId};
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, SourceInfo, WorkerId};
use dom_struct::dom_struct;
use embedder_traits::resources::{self, Resource};
use ipc_channel::ipc::IpcSender;
@ -16,6 +18,7 @@ use profile_traits::{mem, time};
use script_bindings::codegen::GenericBindings::DebuggerGlobalScopeBinding::{
DebuggerGlobalScopeMethods, NotifyNewSource,
};
use script_bindings::codegen::GenericBindings::GetPossibleBreakpointsEventBinding::RecommendedBreakpointLocation;
use script_bindings::realms::InRealm;
use script_bindings::reflector::DomObject;
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
@ -26,7 +29,7 @@ use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::utils::define_all_exposed_interfaces;
use crate::dom::globalscope::GlobalScope;
use crate::dom::types::{AddDebuggeeEvent, Event};
use crate::dom::types::{AddDebuggeeEvent, Event, GetPossibleBreakpointsEvent};
#[cfg(feature = "testbinding")]
#[cfg(feature = "webgpu")]
use crate::dom::webgpu::identityhub::IdentityHub;
@ -40,6 +43,11 @@ use crate::script_runtime::{CanGc, JSContext};
/// <https://firefox-source-docs.mozilla.org/js/Debugger/>
pub(crate) struct DebuggerGlobalScope {
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 {
@ -54,7 +62,8 @@ impl DebuggerGlobalScope {
pub(crate) fn new(
runtime: &Runtime,
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,
time_profiler_chan: time::ProfilerChan,
script_to_constellation_chan: ScriptToConstellationChan,
@ -65,7 +74,7 @@ impl DebuggerGlobalScope {
let global = Box::new(Self {
global_scope: GlobalScope::new_inherited(
debugger_pipeline_id,
devtools_chan,
script_to_devtools_sender,
mem_profiler_chan,
time_profiler_chan,
script_to_constellation_chan,
@ -80,6 +89,8 @@ impl DebuggerGlobalScope {
None,
false,
),
devtools_to_script_sender,
get_possible_breakpoints_result_sender: RefCell::new(None),
});
let global = unsafe {
DebuggerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(
@ -151,6 +162,28 @@ impl DebuggerGlobalScope {
"Guaranteed by AddDebuggeeEvent::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>(GetPossibleBreakpointsEvent::new(
self.upcast(),
spidermonkey_id,
can_gc,
));
assert!(
DomRoot::upcast::<Event>(event).fire(self.upcast(), can_gc),
"Guaranteed by AddDebuggeeEvent::new"
);
}
}
impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
@ -228,6 +261,7 @@ impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
};
devtools_chan
.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
self.devtools_to_script_sender.clone(),
pipeline_id,
source_info,
))
@ -237,4 +271,27 @@ impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
}
}
}
fn GetPossibleBreakpointsResult(
&self,
event: &GetPossibleBreakpointsEvent,
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(),
);
}
}

View file

@ -432,6 +432,9 @@ impl DedicatedWorkerGlobalScope {
&runtime,
pipeline_id,
init.to_devtools_sender.clone(),
init.from_devtools_sender
.clone()
.expect("Guaranteed by Worker::Constructor"),
init.mem_profiler_chan.clone(),
init.time_profiler_chan.clone(),
init.script_to_constellation_chan.clone(),

View file

@ -2,10 +2,12 @@
* 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::GetPossibleBreakpointsEventBinding::GetPossibleBreakpointsEventMethods;
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
use crate::dom::bindings::codegen::Bindings::GetPossibleBreakpointsEventBinding::GetPossibleBreakpointsEventMethods;
use crate::dom::bindings::reflector::reflect_dom_object;
use crate::dom::bindings::root::DomRoot;
use crate::dom::event::Event;
@ -30,7 +32,9 @@ impl GetPossibleBreakpointsEvent {
spidermonkey_id,
});
let result = reflect_dom_object(result, debugger_global, can_gc);
result.event.init_event("getPossibleBreakpoints".into(), false, false);
result
.event
.init_event("getPossibleBreakpoints".into(), false, false);
result
}
@ -46,3 +50,11 @@ impl GetPossibleBreakpointsEventMethods<crate::DomTypeHolder> for GetPossibleBre
self.event.IsTrusted()
}
}
impl Debug for GetPossibleBreakpointsEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GetPossibleBreakpointsEvent")
.field("spidermonkey_id", &self.spidermonkey_id)
.finish()
}
}

View file

@ -952,6 +952,7 @@ impl ScriptThread {
&js_runtime.clone(),
PipelineId::new(),
senders.devtools_server_sender.clone(),
senders.devtools_client_to_script_thread_sender.clone(),
senders.memory_profiler_sender.clone(),
senders.time_profiler_sender.clone(),
script_to_constellation_chan,
@ -2242,6 +2243,13 @@ impl ScriptThread {
DevtoolScriptControlMsg::HighlightDomNode(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,
);
},
}
}

View file

@ -836,6 +836,10 @@ Dictionaries = {
'derives': ['Clone'],
},
'RecommendedBreakpointLocation': {
'derives': ['Debug'],
},
'Report': {
'derives': ['Clone', 'MallocSizeOf'],
},

View file

@ -8,3 +8,16 @@
interface GetPossibleBreakpointsEvent : Event {
readonly attribute unsigned long spidermonkeyId;
};
partial interface DebuggerGlobalScope {
undefined getPossibleBreakpointsResult(
GetPossibleBreakpointsEvent event,
sequence<RecommendedBreakpointLocation> result);
};
dictionary RecommendedBreakpointLocation {
required unsigned long offset;
required unsigned long lineNumber;
required unsigned long columnNumber;
required boolean isStepStart;
};

View file

@ -108,7 +108,7 @@ pub enum ScriptToDevtoolsControlMsg {
TitleChanged(PipelineId, String),
/// Get source information from script
CreateSourceActor(PipelineId, SourceInfo),
CreateSourceActor(IpcSender<DevtoolScriptControlMsg>, PipelineId, SourceInfo),
UpdateSourceContent(PipelineId, String),
}
@ -280,6 +280,8 @@ pub enum DevtoolScriptControlMsg {
SimulateColorScheme(PipelineId, Theme),
/// Highlight the given DOM node
HighlightDomNode(PipelineId, Option<String>),
GetPossibleBreakpoints(u32, IpcSender<Vec<RecommendedBreakpointLocation>>),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
@ -604,3 +606,12 @@ pub struct SourceInfo {
pub content_type: Option<String>,
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,
}

View file

@ -38,3 +38,8 @@ addEventListener("addDebuggee", event => {
});
debuggeesToWorkerIds.set(debuggerObject, workerId);
});
addEventListener("getPossibleBreakpoints", event => {
const {spidermonkeyId} = event;
getPossibleBreakpointsResult(event, sourceIdsToScripts.get(spidermonkeyId).getPossibleBreakpoints(/* TODO: `query` */));
});