commit 9e78e273bf8c1a71fece48f072120cf3a3d0a847
parent 1fd5f4ea8bc7ac5c731a59a5b20f83a78c1ea418
Author: Hubert Boma Manilla <hmanilla@mozilla.com>
Date: Mon, 6 Oct 2025 17:23:02 +0000
Bug 1988710 - [devtools] Makes sure to scroll to selected matched text r=devtools-reviewers,nchevobbe
Differential Revision: https://phabricator.services.mozilla.com/D267382
Diffstat:
7 files changed, 155 insertions(+), 16 deletions(-)
diff --git a/devtools/client/netmonitor/src/actions/search.js b/devtools/client/netmonitor/src/actions/search.js
@@ -298,7 +298,7 @@ function navigate(searchResult) {
// for search result navigation within the side panels.
dispatch(setTargetSearchResult(searchResult));
- // Preselect the right side panel.
+ // Preselect the required side panel.
dispatch(selectDetailsPanelTab(searchResult.panel));
// Select related request in the UI (it also opens the
diff --git a/devtools/client/netmonitor/src/components/previews/SourcePreview.js b/devtools/client/netmonitor/src/components/previews/SourcePreview.js
@@ -160,6 +160,16 @@ class SourcePreview extends Component {
}
}
-module.exports = connect(null, dispatch => ({
- resetTargetSearchResult: () => dispatch(setTargetSearchResult(null)),
-}))(SourcePreview);
+module.exports = connect(
+ state => {
+ if (!state.search) {
+ return null;
+ }
+ return {
+ targetSearchResult: state.search.targetSearchResult,
+ };
+ },
+ dispatch => ({
+ resetTargetSearchResult: () => dispatch(setTargetSearchResult(null)),
+ })
+)(SourcePreview);
diff --git a/devtools/client/netmonitor/test/browser_net_search-results.js b/devtools/client/netmonitor/test/browser_net_search-results.js
@@ -349,6 +349,125 @@ add_task(async function searchWithRequestOnUnload() {
await teardown(monitor);
});
+// Asserts that the content is scrolled to show the correct matched content
+// on the line when a match is selected from the network search list.
+add_task(async function testContentIsScrolledWhenMatchIsSelected() {
+ const httpServer = createTestHTTPServer();
+ httpServer.registerPathHandler(`/`, function (request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(`<html><meta charset=utf8>
+ <script type="text/javascript" src="/script.js"></script>
+ <h1>Test matches in scrolled content</h1>
+ </html>`);
+ });
+
+ // The "data" path takes a size query parameter and will return a body of the
+ // requested size.
+ httpServer.registerPathHandler("/script.js", function (request, response) {
+ response.setHeader("Content-Type", "text/javascript");
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(
+ `${Array.from({ length: 40 }, (_, i) => `// line ${++i}x`).join("\n")}`
+ );
+ });
+
+ const TEST_URI = `http://localhost:${httpServer.identity.primaryPort}/`;
+
+ const { tab, monitor } = await initNetMonitor(TEST_URI, {
+ requestCount: 1,
+ });
+ info("Starting test... ");
+ const { document, store, windowRequire } = monitor.panelWin;
+
+ // Action should be processed synchronously in tests.
+ const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+ store.dispatch(Actions.batchEnable(false));
+
+ // reload tab, expect the html page and the script request
+ const waitForEvents = waitForNetworkEvents(monitor, 2);
+ tab.linkedBrowser.reload();
+ await waitForEvents;
+
+ // Open the Search panel
+ await store.dispatch(Actions.openSearch());
+
+ // Fill search input with text and check displayed messages.
+ const waitForResult = waitFor(() =>
+ document.querySelector(".search-panel-content .treeRow.resourceRow")
+ );
+ typeInNetmonitor("line 3x", monitor);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await waitForResult;
+
+ const result = document.querySelector(
+ ".search-panel-content .treeRow.resourceRow"
+ );
+ // Click the matches to
+ const waitForMatches = waitFor(
+ () =>
+ document.querySelectorAll(".search-panel-content .treeRow.resultRow")
+ ?.length == 1
+ );
+ clickElement(result, monitor);
+ await waitForMatches;
+
+ const matches = document.querySelectorAll(
+ ".search-panel-content .treeRow.resultRow"
+ );
+ // Click the first result match
+ const waitForResponsePanelContent = waitFor(() =>
+ document.querySelector(`#response-panel .cm-content`)
+ );
+ clickElement(matches[0], monitor);
+ await waitForResponsePanelContent;
+
+ const editor = getCMEditor(monitor);
+ is(
+ editor.getSelectionCursor().from.line,
+ 3,
+ "The content on line 3 is highlighted"
+ );
+ is(
+ editor.getText(editor.getSelectionCursor().from.line),
+ "// line 3x",
+ "The content on the line with cursor (line 3) is correct"
+ );
+
+ // Set the cursor to the bottom of the document
+ let onScrolled = waitForEditorScrolling(monitor);
+ await editor.setCursorAt(40, 0);
+ await onScrolled;
+
+ is(
+ editor.getSelectionCursor().from.line,
+ 40,
+ "The content on line 40 is highlighted"
+ );
+ is(
+ editor.getText(editor.getSelectionCursor().from.line),
+ "// line 40x",
+ "The content on the line with cursor (line 40) is correct"
+ );
+
+ // Click the first result match again
+ onScrolled = waitForEditorScrolling(monitor);
+ clickElement(matches[0], monitor);
+ await onScrolled;
+
+ // The editor should scroll back up and the matched content on line 3 should be highlighted
+
+ is(
+ editor.getSelectionCursor().from.line,
+ 3,
+ "The content on line 3 is highlighted"
+ );
+ is(
+ editor.getText(editor.getSelectionCursor().from.line),
+ "// line 3x",
+ "The content on the line with cursor (line 3) is correct"
+ );
+});
+
async function makeRequests(urls) {
await content.wrappedJSObject.get(urls[0]);
await content.wrappedJSObject.get(urls[1]);
diff --git a/devtools/client/netmonitor/test/head.js b/devtools/client/netmonitor/test/head.js
@@ -1193,6 +1193,16 @@ function getCodeMirrorValue(monitor) {
}
/**
+ * Waits for the currently triggered editor scroll to complete
+ *
+ * @param {*} monitor
+ * @returns {Promise}
+ */
+async function waitForEditorScrolling(monitor) {
+ return getCMEditor(monitor).once("cm-editor-scrolled");
+}
+
+/**
* Helper function opening the options menu
*/
function openSettingsMenu(monitor) {
diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_network_messages_expand.js b/devtools/client/webconsole/test/browser/browser_webconsole_network_messages_expand.js
@@ -239,12 +239,6 @@ async function waitForPayloadReady(hud) {
return hud.ui.once("network-request-payload-ready");
}
-async function waitForSourceEditor(panel) {
- return waitUntil(() => {
- return !!panel.querySelector(".cm-editor");
- });
-}
-
async function waitForRequestUpdates(hud) {
return hud.ui.once("network-messages-updated");
}
diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_network_messages_expand_before_updates.js b/devtools/client/webconsole/test/browser/browser_webconsole_network_messages_expand_before_updates.js
@@ -298,12 +298,6 @@ async function testSecurity(messageNode) {
);
}
-async function waitForSourceEditor(panel) {
- return waitUntil(() => {
- return !!panel.querySelector(".cm-content");
- });
-}
-
function expandXhrMessage(node) {
info(
"Click on XHR message and wait for the network detail panel to be displayed"
diff --git a/devtools/client/webconsole/test/browser/head.js b/devtools/client/webconsole/test/browser/head.js
@@ -270,6 +270,18 @@ async function waitForMessageByType(hud, text, typeSelector) {
}
/**
+ * Wait for the Source editor to be available.
+ *
+ * @param {Object} panel
+ * @returns
+ */
+async function waitForSourceEditor(panel) {
+ return waitUntil(() => {
+ return !!panel.querySelector(".cm-editor");
+ });
+}
+
+/**
* Execute an input expression.
*
* @param {Object} hud : The webconsole.