diff --git a/devtools/client/application/test/browser/browser_application_panel_list-domain-workers.js b/devtools/client/application/test/browser/browser_application_panel_list-domain-workers.js index 932c40bf5ccf..092d49c0b65b 100644 --- a/devtools/client/application/test/browser/browser_application_panel_list-domain-workers.js +++ b/devtools/client/application/test/browser/browser_application_panel_list-domain-workers.js @@ -15,6 +15,14 @@ const EMPTY_URL = (URL_ROOT + "resources/service-workers/empty.html").replace( "test2.example.com" ); +// FIXME bug 1575427 this rejection is very common. +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/PromiseTestUtils.jsm" +); +PromiseTestUtils.whitelistRejectionsGlobally( + /this._frontCreationListeners is null/ +); + add_task(async function() { await enableApplicationPanel(); diff --git a/devtools/client/debugger/src/client/firefox/events.js b/devtools/client/debugger/src/client/firefox/events.js index 2a1185eb5a71..623a4c987c8b 100644 --- a/devtools/client/debugger/src/client/firefox/events.js +++ b/devtools/client/debugger/src/client/firefox/events.js @@ -50,14 +50,12 @@ function setupEvents(dependencies: Dependencies) { sourceQueue.initialize(actions); addThreadEventListeners(threadFront); - tabTarget.on("workerListChanged", () => threadListChanged("worker")); - debuggerClient.mainRoot.on("processListChanged", () => - threadListChanged("contentProcess") - ); + tabTarget.on("workerListChanged", () => threadListChanged()); + debuggerClient.mainRoot.on("processListChanged", () => threadListChanged()); if (features.windowlessServiceWorkers || attachAllTargets(tabTarget)) { const workersListener = new WorkersListener(debuggerClient.mainRoot); - workersListener.addListener(() => threadListChanged("worker")); + workersListener.addListener(() => threadListChanged()); } } @@ -114,8 +112,8 @@ function newSource(threadFront: ThreadFront, { source }: SourcePacket) { }); } -function threadListChanged(type) { - actions.updateThreads(type); +function threadListChanged() { + actions.updateThreads(); } function replayFramePositions( diff --git a/devtools/client/debugger/src/client/firefox/targets.js b/devtools/client/debugger/src/client/firefox/targets.js index 93632017201c..779456076261 100644 --- a/devtools/client/debugger/src/client/firefox/targets.js +++ b/devtools/client/debugger/src/client/firefox/targets.js @@ -90,6 +90,7 @@ async function listWorkerTargets(args: Args) { } else { workers = (await currentTarget.listWorkers()).workers; if (currentTarget.url && features.windowlessServiceWorkers) { + allWorkers = await debuggerClient.mainRoot.listAllWorkerTargets(); const { registrations, } = await debuggerClient.mainRoot.listServiceWorkerRegistrations(); @@ -100,10 +101,16 @@ async function listWorkerTargets(args: Args) { } for (const front of serviceWorkerRegistrations) { - const { activeWorker, waitingWorker, installingWorker } = front; + const { + activeWorker, + waitingWorker, + installingWorker, + evaluatingWorker, + } = front; await maybeMarkServiceWorker(activeWorker, "active"); await maybeMarkServiceWorker(waitingWorker, "waiting"); await maybeMarkServiceWorker(installingWorker, "installing"); + await maybeMarkServiceWorker(evaluatingWorker, "evaluating"); } async function maybeMarkServiceWorker(info, status) { @@ -111,9 +118,6 @@ async function listWorkerTargets(args: Args) { return; } - if (!allWorkers) { - allWorkers = await debuggerClient.mainRoot.listAllWorkerTargets(); - } const worker = allWorkers.find(front => front && front.id == info.id); if (!worker) { return; @@ -128,20 +132,37 @@ async function listWorkerTargets(args: Args) { return workers; } -async function listProcessTargets(args: Args) { - const { currentTarget, debuggerClient } = args; - if (!attachAllTargets(currentTarget)) { - return []; - } - +async function getAllProcessTargets(args) { + const { debuggerClient } = args; const { processes } = await debuggerClient.mainRoot.listProcesses(); - const targets = await Promise.all( + return Promise.all( processes .filter(descriptor => !descriptor.isParent) .map(descriptor => descriptor.getTarget()) ); +} - return targets; +async function listProcessTargets(args: Args) { + const { currentTarget } = args; + if (!attachAllTargets(currentTarget)) { + if (currentTarget.url && features.windowlessServiceWorkers) { + // Service workers associated with our target's origin need to pause until + // we attach, regardless of which process they are running in. + const origin = new URL(currentTarget.url).origin; + const targets = await getAllProcessTargets(args); + try { + await Promise.all( + targets.map(t => t.pauseMatchingServiceWorkers({ origin })) + ); + } catch (e) { + // Old servers without pauseMatchingServiceWorkers will throw. + // @backward-compatibility: remove in Firefox 75 + } + } + return []; + } + + return getAllProcessTargets(args); } export async function updateTargets(args: Args) { diff --git a/devtools/client/debugger/test/mochitest/browser_dbg-windowless-service-workers.js b/devtools/client/debugger/test/mochitest/browser_dbg-windowless-service-workers.js index ce7f743f75a2..ae6989445d94 100644 --- a/devtools/client/debugger/test/mochitest/browser_dbg-windowless-service-workers.js +++ b/devtools/client/debugger/test/mochitest/browser_dbg-windowless-service-workers.js @@ -7,6 +7,14 @@ async function checkWorkerThreads(dbg, count) { ok(true, `Have ${count} threads`); } +async function checkWorkerStatus(dbg, status) { + await waitUntil(() => { + const threads = dbg.selectors.getThreads(); + return threads.some(t => t.serviceWorkerStatus == status); + }); + ok(true, `Have thread with status ${status}`); +} + // Test that we can detect a new service worker and hit breakpoints that we've // set in it. add_task(async function() { @@ -31,8 +39,9 @@ add_task(async function() { await waitForPaused(dbg); assertPausedAtSourceAndLine(dbg, workerSource.id, 13); - // Leave the breakpoint in place for the next subtest. + // Leave the breakpoint and worker in place for the next subtest. await resume(dbg); + await waitForRequestsToSettle(dbg); await removeTab(gBrowser.selectedTab); }); @@ -61,6 +70,7 @@ add_task(async function() { invokeInTab("unregisterWorker"); await checkWorkerThreads(dbg, 0); + await waitForRequestsToSettle(dbg); await removeTab(gBrowser.selectedTab); }); @@ -73,6 +83,7 @@ add_task(async function() { invokeInTab("registerWorker"); await checkWorkerThreads(dbg, 1); + await checkWorkerStatus(dbg, "active"); const firstTab = gBrowser.selectedTab; @@ -101,9 +112,13 @@ add_task(async function() { ok(content0.value.includes("newServiceWorker") != content1.value.includes("newServiceWorker"), "Got two different sources for service worker"); + // Add a breakpoint for the next subtest. + await addBreakpoint(dbg, "service-worker.sjs", 2); + invokeInTab("unregisterWorker"); await checkWorkerThreads(dbg, 0); + await waitForRequestsToSettle(dbg); await removeTab(firstTab); await removeTab(secondTab); @@ -111,3 +126,41 @@ add_task(async function() { await addTab(EXAMPLE_URL + "service-worker.sjs?setStatus="); await removeTab(gBrowser.selectedTab); }); + +// Test setting breakpoints while the service worker is starting up. +add_task(async function() { + info("Subtest #4"); + + const toolbox = await openNewTabAndToolbox(EXAMPLE_URL + "doc-service-workers.html", "jsdebugger"); + const dbg = createDebuggerContext(toolbox); + + invokeInTab("registerWorker"); + await checkWorkerThreads(dbg, 1); + + await waitForSource(dbg, "service-worker.sjs"); + const workerSource = findSource(dbg, "service-worker.sjs"); + + await waitForBreakpointCount(dbg, 1); + await waitForPaused(dbg); + assertPausedAtSourceAndLine(dbg, workerSource.id, 2); + await checkWorkerStatus(dbg, "evaluating"); + + await addBreakpoint(dbg, "service-worker.sjs", 19); + await resume(dbg); + await waitForPaused(dbg); + assertPausedAtSourceAndLine(dbg, workerSource.id, 19); + await checkWorkerStatus(dbg, "installing"); + + await addBreakpoint(dbg, "service-worker.sjs", 5); + await resume(dbg); + await waitForPaused(dbg); + assertPausedAtSourceAndLine(dbg, workerSource.id, 5); + await checkWorkerStatus(dbg, "active"); + + await resume(dbg); + invokeInTab("unregisterWorker"); + + await checkWorkerThreads(dbg, 0); + await waitForRequestsToSettle(dbg); + await removeTab(gBrowser.selectedTab); +}); diff --git a/devtools/client/debugger/test/mochitest/examples/service-worker.sjs b/devtools/client/debugger/test/mochitest/examples/service-worker.sjs index 8d4d3d2aa06e..b48bb9e9076a 100644 --- a/devtools/client/debugger/test/mochitest/examples/service-worker.sjs +++ b/devtools/client/debugger/test/mochitest/examples/service-worker.sjs @@ -19,6 +19,10 @@ self.addEventListener("fetch", event => { event.respondWith(response); } }); + +self.addEventListener("install", event => { + dump("Install\\n"); +}); `; function handleRequest(request, response) { @@ -30,17 +34,5 @@ function handleRequest(request, response) { } const status = getState("status"); - - let newBody = body.replace("STATUS", status); - - if (status == "stuckInstall") { -newBody += ` -self.addEventListener("install", event => { - dump("Install\\n"); - event.waitUntil(new Promise(resolve => {})); -}); -`; - } - - response.write(newBody); + response.write(body.replace("STATUS", status)); }