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

@ -577,6 +577,20 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
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)
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`.
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
@ -763,6 +777,55 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
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:
return os.path.join(DevtoolsTests.script_path, os.path.join("devtools_tests", path))

View file

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