Bug 1808574 - [devtools] Migrate old worker test to about:debugging test. r=jdescottes

This will help remove a few callsites still spawning toolboxes for regular workers.
Only service workers and chrome/parent process workers can be debugged independantly,
in a dedicated toolbox. That, only from about:debugging, by opening a toolbox in a tab.

Differential Revision: https://phabricator.services.mozilla.com/D165977
This commit is contained in:
Alexandre Poirot 2023-01-05 22:09:25 +00:00
Родитель 6ca7c21da2
Коммит 714cfde67b
10 изменённых файлов: 159 добавлений и 514 удалений

Просмотреть файл

@ -122,6 +122,8 @@ support-files =
[browser_aboutdebugging_runtime_usbclient_closed.js]
[browser_aboutdebugging_select_network_runtime.js]
[browser_aboutdebugging_select_page_with_serviceworker.js]
[browser_aboutdebugging_serviceworker_console.js]
https_first_disabled = true
[browser_aboutdebugging_serviceworker_fetch_flag.js]
https_first_disabled = true
skip-if =

Просмотреть файл

@ -0,0 +1,131 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* import-globals-from helper-serviceworker.js */
Services.scriptloader.loadSubScript(
CHROME_URL_ROOT + "helper-serviceworker.js",
this
);
/* import-globals-from ../../../debugger/test/mochitest/shared-head.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/shared-head.js",
this
);
const SW_TAB_URL = URL_ROOT + "resources/service-workers/controlled-sw.html";
const SW_URL = URL_ROOT + "resources/service-workers/controlled-sw.js";
/**
* Test various simple debugging operation against service workers debugged through about:debugging.
*/
add_task(async function() {
await enableServiceWorkerDebugging();
const { document, tab, window } = await openAboutDebugging({
enableWorkerUpdates: true,
});
await selectThisFirefoxPage(document, window.AboutDebugging.store);
// Open a tab that registers a basic service worker.
const swTab = await addTab(SW_TAB_URL);
// Wait for the registration to make sure service worker has been started, and that we
// are not just reading STOPPED as the initial state.
await waitForRegistration(swTab);
info("Open a toolbox to debug the worker");
const { devtoolsTab, devtoolsWindow } = await openAboutDevtoolsToolbox(
document,
tab,
window,
SW_URL
);
const toolbox = getToolbox(devtoolsWindow);
info("Assert the default tools displayed in worker toolboxes");
const toolTabs = toolbox.doc.querySelectorAll(".devtools-tab");
const activeTools = [...toolTabs].map(toolTab =>
toolTab.getAttribute("data-id")
);
is(
activeTools.join(","),
"webconsole,jsdebugger",
"Correct set of tools supported by worker"
);
const webconsole = await toolbox.selectTool("webconsole");
const { hud } = webconsole;
info("Evaluate location in the console");
await executeAndWaitForMessage(hud, "this.location.toString()", SW_URL);
ok(true, "Got the location logged in the console");
info(
"Evaluate Date and RegExp to ensure their formater also work from worker threads"
);
await executeAndWaitForMessage(
hud,
"new Date(2013, 3, 1)",
"Mon Apr 01 2013 00:00:00"
);
ok(true, "Date object has expected text content");
await executeAndWaitForMessage(hud, "new RegExp('.*')", "/.*/");
ok(true, "RegExp has expected text content");
await toolbox.selectTool("jsdebugger");
const dbg = createDebuggerContext(toolbox);
const {
selectors: { getIsWaitingOnBreak, getCurrentThread },
} = dbg;
info("Wait for next interupt in the worker thread");
await clickElement(dbg, "pause");
await waitForState(dbg, state => getIsWaitingOnBreak(getCurrentThread()));
info("Trigger some code in the worker and wait for pause");
await SpecialPowers.spawn(swTab.linkedBrowser, [], async function() {
content.wrappedJSObject.installServiceWorker();
});
await waitForPaused(dbg);
ok(true, "successfully paused");
info(
"Evaluate some variable only visible if we execute in the breakpoint frame"
);
await executeAndWaitForMessage(hud, "event.data", "install-service-worker");
info("Resume execution");
await resume(dbg);
info("Test pausing from console evaluation");
hud.ui.wrapper.dispatchEvaluateExpression("debugger; 42");
await waitForPaused(dbg);
ok(true, "successfully paused");
info("Immediately resume");
await resume(dbg);
await waitFor(() => findMessagesByType(hud, "42", ".result"));
ok("The paused console evaluation resumed and logged its magic number");
info("Destroy the toolbox");
await closeAboutDevtoolsToolbox(document, devtoolsTab, window);
info("Unregister service worker");
await unregisterServiceWorker(swTab);
info("Wait until the service worker disappears from about:debugging");
await waitUntil(() => !findDebugTargetByText(SW_URL, document));
info("Remove tabs");
await removeTab(swTab);
await removeTab(tab);
});
async function executeAndWaitForMessage(hud, evaluationString, expectedResult) {
hud.ui.wrapper.dispatchEvaluateExpression();
await waitFor(() => findMessagesByType(hud, expectedResult, ".result"));
}

Просмотреть файл

@ -43,7 +43,6 @@ support-files =
helper_color_data.js
helper_html_tooltip.js
helper_inplace_editor.js
helper_workers.js
highlighter-test-actor.js
leakhunt.js
shared-head.js
@ -206,11 +205,3 @@ skip-if = debug
skip-if = debug
[browser_dbg_target-scoped-actor-01.js]
[browser_dbg_target-scoped-actor-02.js]
[browser_dbg_worker-console-01.js]
skip-if = true # bug 1368569
[browser_dbg_worker-console-02.js]
skip-if = debug
[browser_dbg_worker-console-03.js]
skip-if = debug # bug 1334683
[browser_dbg_worker-console-04.js]
skip-if = debug

Просмотреть файл

@ -9,13 +9,6 @@ registerCleanupFunction(() => {
Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
});
// Import helpers for the workers
/* import-globals-from helper_workers.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/helper_workers.js",
this
);
var TAB_URL = EXAMPLE_URL + "doc_listworkers-tab.html";
var WORKER1_URL = "code_listworkers-worker1.js";
var WORKER2_URL = "code_listworkers-worker2.js";
@ -27,38 +20,42 @@ add_task(async function test() {
let { workers } = await listWorkers(target);
is(workers.length, 0);
executeSoon(() => {
evalInTab(tab, "var worker1 = new Worker('" + WORKER1_URL + "');");
let onWorkerListChanged = waitForWorkerListChanged(target);
await SpecialPowers.spawn(tab.linkedBrowser, [WORKER1_URL], workerUrl => {
content.worker1 = new content.Worker(workerUrl);
});
await waitForWorkerListChanged(target);
await onWorkerListChanged;
({ workers } = await listWorkers(target));
is(workers.length, 1);
is(workers[0].url, WORKER1_URL);
executeSoon(() => {
evalInTab(tab, "var worker2 = new Worker('" + WORKER2_URL + "');");
onWorkerListChanged = waitForWorkerListChanged(target);
await SpecialPowers.spawn(tab.linkedBrowser, [WORKER2_URL], workerUrl => {
content.worker2 = new content.Worker(workerUrl);
});
await waitForWorkerListChanged(target);
await onWorkerListChanged;
({ workers } = await listWorkers(target));
is(workers.length, 2);
is(workers[0].url, WORKER1_URL);
is(workers[1].url, WORKER2_URL);
executeSoon(() => {
evalInTab(tab, "worker1.terminate()");
onWorkerListChanged = waitForWorkerListChanged(target);
await SpecialPowers.spawn(tab.linkedBrowser, [WORKER2_URL], workerUrl => {
content.worker1.terminate();
});
await waitForWorkerListChanged(target);
await onWorkerListChanged;
({ workers } = await listWorkers(target));
is(workers.length, 1);
is(workers[0].url, WORKER2_URL);
executeSoon(() => {
evalInTab(tab, "worker2.terminate()");
onWorkerListChanged = waitForWorkerListChanged(target);
await SpecialPowers.spawn(tab.linkedBrowser, [WORKER2_URL], workerUrl => {
content.worker2.terminate();
});
await waitForWorkerListChanged(target);
await onWorkerListChanged;
({ workers } = await listWorkers(target));
is(workers.length, 0);
@ -66,3 +63,13 @@ add_task(async function test() {
await target.destroy();
finish();
});
function listWorkers(targetFront) {
info("Listing workers.");
return targetFront.listWorkers();
}
function waitForWorkerListChanged(targetFront) {
info("Waiting for worker list to change.");
return targetFront.once("workerListChanged");
}

Просмотреть файл

@ -1,36 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check to make sure that a worker can be attached to a toolbox
// and that the console works.
// Import helpers for the workers
/* import-globals-from helper_workers.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/helper_workers.js",
this
);
var TAB_URL = EXAMPLE_URL + "doc_WorkerTargetActor.attachThread-tab.html";
var WORKER_URL = "code_WorkerTargetActor.attachThread-worker.js";
add_task(async function testNormalExecution() {
const {
client,
tab,
workerDescriptorFront,
toolbox,
} = await initWorkerDebugger(TAB_URL, WORKER_URL);
const hud = await getSplitConsole(toolbox);
await executeAndWaitForMessage(hud, "this.location.toString()", WORKER_URL);
ok(true, "Evaluating the global's location works");
terminateWorkerInTab(tab, WORKER_URL);
await waitForWorkerClose(workerDescriptorFront);
await toolbox.destroy();
await close(client);
await removeTab(tab);
});

Просмотреть файл

@ -1,70 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check to make sure that a worker can be attached to a toolbox
// and that the console works.
// Import helpers for the workers
/* import-globals-from helper_workers.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/helper_workers.js",
this
);
var TAB_URL = EXAMPLE_URL + "doc_WorkerTargetActor.attachThread-tab.html";
var WORKER_URL = "code_WorkerTargetActor.attachThread-worker.js";
add_task(async function testWhilePaused() {
const dbg = await initWorkerDebugger(TAB_URL, WORKER_URL);
const { client, tab, workerDescriptorFront, toolbox } = dbg;
const {
selectors: { getIsWaitingOnBreak, getCurrentThread },
} = dbg;
await waitForSourcesInSourceTree(dbg, [WORKER_URL]);
// Execute some basic math to make sure evaluations are working.
const hud = await getSplitConsole(toolbox);
await executeAndWaitForMessage(hud, "10000+1", "10001");
ok(true, "Text for message appeared correct");
await clickElement(dbg, "pause");
await waitForState(dbg, state => getIsWaitingOnBreak(getCurrentThread()));
info("Posting message to worker, then waiting for a pause");
postMessageToWorkerInTab(tab, WORKER_URL, "ping");
await waitForPaused(dbg);
const command1 = executeAndWaitForMessage(hud, "10000+2", "10002");
const command2 = executeAndWaitForMessage(hud, "10000+3", "10003");
// throw an error
const command3 = executeAndWaitForMessage(
hud,
"foobar",
"ReferenceError: foobar is not defined",
"error"
);
info("Trying to get the result of command1");
let executed = await command1;
ok(executed, "command1 executed successfully");
info("Trying to get the result of command2");
executed = await command2;
ok(executed, "command2 executed successfully");
info("Trying to get the result of command3");
executed = await command3;
ok(executed, "command3 executed successfully");
await resume(dbg);
terminateWorkerInTab(tab, WORKER_URL);
await waitForWorkerClose(workerDescriptorFront);
await toolbox.destroy();
await close(client);
await removeTab(tab);
});

Просмотреть файл

@ -1,49 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check to make sure that a worker can be attached to a toolbox
// and that the console works.
// Import helpers for the workers
/* import-globals-from helper_workers.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/helper_workers.js",
this
);
var TAB_URL = EXAMPLE_URL + "doc_WorkerTargetActor.attachThread-tab.html";
var WORKER_URL = "code_WorkerTargetActor.attachThread-worker.js";
// Test to see if creating the pause from the console works.
add_task(async function testPausedByConsole() {
const dbg = await initWorkerDebugger(TAB_URL, WORKER_URL);
const { client, tab, workerDescriptorFront, toolbox } = dbg;
const console = await getSplitConsole(toolbox);
let executed = await executeAndWaitForMessage(console, "10000+1", "10001");
ok(executed, "Text for message appeared correct");
await clickElement(dbg, "pause");
const pausedExecution = executeAndWaitForMessage(console, "10000+2", "10002");
info("Executed a command with 'break on next' active, waiting for pause");
await waitForPaused(dbg);
executed = await executeAndWaitForMessage(console, "10000+3", "10003");
ok(executed, "Text for message appeared correct");
info("Waiting for a resume");
await clickElement(dbg, "resume");
executed = await pausedExecution;
ok(executed, "Text for message appeared correct");
terminateWorkerInTab(tab, WORKER_URL);
await waitForWorkerClose(workerDescriptorFront);
await toolbox.destroy();
await close(client);
await removeTab(tab);
});

Просмотреть файл

@ -1,57 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check to make sure that a worker can be attached to a toolbox
// and that the console works.
// Import helpers for the workers
/* import-globals-from helper_workers.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/helper_workers.js",
this
);
// Check that the date and regexp previewers work in the console of a worker debugger.
("use strict");
// There are shutdown issues for which multiple rejections are left uncaught.
// See bug 1018184 for resolving these issues.
PromiseTestUtils.allowMatchingRejectionsGlobally(/connection just closed/);
const TAB_URL = EXAMPLE_URL + "doc_WorkerTargetActor.attachThread-tab.html";
const WORKER_URL = "code_WorkerTargetActor.attachThread-worker.js";
add_task(async function testPausedByConsole() {
const {
client,
tab,
workerDescriptorFront,
toolbox,
} = await initWorkerDebugger(TAB_URL, WORKER_URL);
info("Check Date objects can be used in the console");
const console = await getSplitConsole(toolbox);
let executed = await executeAndWaitForMessage(
console,
"new Date(2013, 3, 1)",
"Mon Apr 01 2013 00:00:00"
);
ok(executed, "Date object has expected text content");
info("Check RegExp objects can be used in the console");
executed = await executeAndWaitForMessage(
console,
"new RegExp('.*')",
"/.*/"
);
ok(executed, "Text for message appeared correct");
terminateWorkerInTab(tab, WORKER_URL);
await waitForWorkerClose(workerDescriptorFront);
await toolbox.destroy();
await close(client);
await removeTab(tab);
});

Просмотреть файл

@ -1,250 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
// Assume that head.js was ran and imported shared-head.js
/* import-globals-from shared-head.js */
"use strict";
/* import-globals-from ../../debugger/test/mochitest/shared-head.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/shared-head.js",
this
);
var {
DevToolsServer,
} = require("resource://devtools/server/devtools-server.js");
var {
DevToolsClient,
} = require("resource://devtools/client/devtools-client.js");
var { Toolbox } = require("resource://devtools/client/framework/toolbox.js");
function createWorkerInTab(tab, url) {
info("Creating worker with url '" + url + "' in tab.");
return SpecialPowers.spawn(tab.linkedBrowser, [url], urlChild => {
return new Promise(resolve => {
const worker = new content.Worker(urlChild);
worker.addEventListener(
"message",
function() {
if (!content.workers) {
content.workers = [];
}
content.workers[urlChild] = worker;
resolve();
},
{ once: true }
);
});
});
}
function terminateWorkerInTab(tab, url) {
info("Terminating worker with url '" + url + "' in tab.");
return SpecialPowers.spawn(tab.linkedBrowser, [url], urlChild => {
content.workers[urlChild].terminate();
delete content.workers[urlChild];
});
}
function postMessageToWorkerInTab(tab, url, message) {
info("Posting message to worker with url '" + url + "' in tab.");
return SpecialPowers.spawn(
tab.linkedBrowser,
[url, message],
(urlChild, messageChild) => {
return new Promise(function(resolve) {
const worker = content.workers[urlChild];
worker.postMessage(messageChild);
worker.addEventListener(
"message",
function() {
resolve();
},
{ once: true }
);
});
}
);
}
function evalInTab(tab, string) {
info("Evalling string in tab.");
return SpecialPowers.spawn(tab.linkedBrowser, [string], stringChild => {
return content.eval(stringChild);
});
}
function connect(client) {
info("Connecting client.");
return client.connect();
}
function close(client) {
info("Waiting for client to close.\n");
return client.close();
}
function listTabs(client) {
info("Listing tabs.");
return client.mainRoot.listTabs();
}
function findTab(tabs, url) {
info("Finding tab with url '" + url + "'.");
for (const tab of tabs) {
if (tab.url === url) {
return tab;
}
}
return null;
}
function listWorkers(targetFront) {
info("Listing workers.");
return targetFront.listWorkers();
}
function waitForWorkerListChanged(targetFront) {
info("Waiting for worker list to change.");
return targetFront.once("workerListChanged");
}
async function waitForWorkerClose(workerDescriptorFront) {
info("Waiting for worker to close.");
await workerDescriptorFront.once("descriptor-destroyed");
info("Worker did close.");
}
// Return a promise with a reference to webconsole, opening the split
// console if necessary. This cleans up the split console pref so
// it won't pollute other tests.
async function getSplitConsole(toolbox, win) {
if (!win) {
win = toolbox.win;
}
if (!toolbox.splitConsole) {
EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
}
await toolbox.getPanelWhenReady("webconsole");
ok(toolbox.splitConsole, "Split console is shown.");
return toolbox.getPanel("webconsole");
}
function executeAndWaitForMessage(
webconsole,
expression,
expectedTextContent,
className = "result"
) {
const { ui } = webconsole.hud;
return new Promise(resolve => {
const onNewMessages = messages => {
for (const message of messages) {
if (
message.node.classList.contains(className) &&
message.node.textContent.includes(expectedTextContent)
) {
ui.off("new-messages", onNewMessages);
resolve(message.node);
}
}
};
ui.on("new-messages", onNewMessages);
ui.wrapper.dispatchEvaluateExpression(expression);
});
}
async function initWorkerDebugger(TAB_URL, WORKER_URL) {
const tab = await addTab(TAB_URL);
await createWorkerInTab(tab, WORKER_URL);
const commands = await CommandsFactory.forLocalTabWorker(tab, WORKER_URL);
const workerDescriptorFront = commands.descriptorFront;
const target = workerDescriptorFront.parentFront;
const client = commands.client;
const toolbox = await gDevTools.showToolbox(commands, {
toolId: "jsdebugger",
hostType: Toolbox.HostType.WINDOW,
});
const debuggerPanel = toolbox.getCurrentPanel();
const gDebugger = debuggerPanel.panelWin;
const context = createDebuggerContext(toolbox);
return {
...context,
client,
tab,
target,
workerDescriptorFront,
toolbox,
gDebugger,
};
}
// Override addTab/removeTab as defined by shared-head, since these have
// an extra window parameter and add a frame script
this.addTab = function addTab(url, win) {
info("Adding tab: " + url);
return new Promise(resolve => {
const targetWindow = win || window;
const targetBrowser = targetWindow.gBrowser;
targetWindow.focus();
const tab = (targetBrowser.selectedTab = BrowserTestUtils.addTab(
targetBrowser,
url
));
const linkedBrowser = tab.linkedBrowser;
BrowserTestUtils.browserLoaded(linkedBrowser).then(function() {
info("Tab added and finished loading: " + url);
resolve(tab);
});
});
};
this.removeTab = function removeTab(tab, win) {
info("Removing tab.");
return new Promise(resolve => {
const targetWindow = win || window;
const targetBrowser = targetWindow.gBrowser;
const tabContainer = targetBrowser.tabContainer;
tabContainer.addEventListener(
"TabClose",
function() {
info("Tab removed and finished closing.");
resolve();
},
{ once: true }
);
targetBrowser.removeTab(tab);
});
};
function pushPrefs(...aPrefs) {
return new Promise(resolve => {
SpecialPowers.pushPrefEnv({ set: aPrefs }, resolve);
});
}
function popPrefs() {
return new Promise(resolve => {
SpecialPowers.popPrefEnv(resolve);
});
}

Просмотреть файл

@ -127,30 +127,6 @@ exports.CommandsFactory = {
return commands;
},
/**
* Create commands for a worker in a local tab.
*
* @param {Tab} tab: The local tab into which the worker runs.
* @param {String} workerUrl: The URL of the worker to debug.
* @returns {Object} Commands
*/
async forLocalTabWorker(tab, workerUrl) {
// For now, the root actor doesn't support instantiating a worker descriptor
// for a worker running in a content process (it supports only for parent process workers).
// So that we have to first instantiate a commands for the related tab.
// Then we can fetch the worker descriptor via the top level target front.
const tabCommands = await this.forTab(tab);
await tabCommands.targetCommand.startListening();
const {
workers,
} = await tabCommands.targetCommand.targetFront.listWorkers();
const workerDescriptor = workers.find(worker => worker.url == workerUrl);
const commands = await createCommandsDictionary(workerDescriptor);
return commands;
},
/**
* Create commands for a Web Extension.
*