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:
shuppy 2025-08-12 12:53:53 +08:00 committed by GitHub
parent 1995e22e19
commit f5b631e270
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 340 additions and 8 deletions

View file

@ -31,6 +31,9 @@ impl Actor for BreakpointListActor {
_stream_id: crate::StreamId,
) -> Result<(), ActorError> {
match msg_type {
// Client wants to set a breakpoint.
// Seems to be infallible, unlike the thread actors `setBreakpoint`.
// <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#breakpoints>
"setBreakpoint" => {
let msg = EmptyReplyMsg { from: self.name() };
request.reply_final(&msg)?

View file

@ -3,9 +3,11 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::RefCell;
use std::collections::BTreeSet;
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;
@ -61,6 +63,8 @@ pub struct SourceActor {
pub spidermonkey_id: u32,
/// `introductionType` in SpiderMonkey `CompileOptionsWrapper`.
pub introduction_type: String,
script_sender: IpcSender<DevtoolScriptControlMsg>,
}
#[derive(Serialize)]
@ -71,6 +75,24 @@ struct SourceContentReply {
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 {
pub fn new() -> Self {
Self {
@ -101,6 +123,7 @@ impl SourceActor {
content_type: Option<String>,
spidermonkey_id: u32,
introduction_type: String,
script_sender: IpcSender<DevtoolScriptControlMsg>,
) -> SourceActor {
SourceActor {
name,
@ -110,9 +133,11 @@ impl SourceActor {
is_black_boxed: false,
spidermonkey_id,
introduction_type,
script_sender,
}
}
#[allow(clippy::too_many_arguments)]
pub fn new_registered(
actors: &mut ActorRegistry,
pipeline_id: PipelineId,
@ -121,6 +146,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");
@ -131,6 +157,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);
@ -181,6 +208,54 @@ impl Actor for SourceActor {
};
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),
};
Ok(())