From 910e64de92b51bbec4354c55fb317681f3b830d1 Mon Sep 17 00:00:00 2001 From: Mihai Alexandru Michis Date: Thu, 21 Jan 2021 11:38:45 +0200 Subject: [PATCH 1/2] Backed out changeset d4b644dd05b2 (bug 1657130) for causing worker crashes. a=backout DONTBUILD --- dom/workers/WorkerDebugger.cpp | 36 +++---------------- dom/workers/WorkerDebugger.h | 3 -- dom/workers/nsIWorkerDebugger.idl | 2 -- dom/workers/remoteworkers/RemoteWorkerChild.h | 2 -- 4 files changed, 5 insertions(+), 38 deletions(-) diff --git a/dom/workers/WorkerDebugger.cpp b/dom/workers/WorkerDebugger.cpp index b25f38bc3c2b..6ce14547ee90 100644 --- a/dom/workers/WorkerDebugger.cpp +++ b/dom/workers/WorkerDebugger.cpp @@ -269,45 +269,19 @@ WorkerDebugger::GetWindow(mozIDOMWindow** aResult) { return NS_ERROR_UNEXPECTED; } - nsCOMPtr window = DedicatedWorkerWindow(); - window.forget(aResult); - return NS_OK; -} - -NS_IMETHODIMP -WorkerDebugger::GetWindowIDs(nsTArray& aResult) { - AssertIsOnMainThread(); - - if (!mWorkerPrivate) { - return NS_ERROR_UNEXPECTED; - } - - if (mWorkerPrivate->IsDedicatedWorker()) { - const auto window = DedicatedWorkerWindow(); - aResult.AppendElement(window->WindowID()); - } else if (mWorkerPrivate->IsSharedWorker()) { - const RemoteWorkerChild* const controller = - mWorkerPrivate->GetRemoteWorkerController(); - MOZ_ASSERT(controller); - aResult = controller->WindowIDs().Clone(); - } - - return NS_OK; -} - -nsCOMPtr WorkerDebugger::DedicatedWorkerWindow() { - MOZ_ASSERT(mWorkerPrivate); - WorkerPrivate* worker = mWorkerPrivate; while (worker->GetParent()) { worker = worker->GetParent(); } if (!worker->IsDedicatedWorker()) { - return nullptr; + *aResult = nullptr; + return NS_OK; } - return worker->GetWindow(); + nsCOMPtr window = worker->GetWindow(); + window.forget(aResult); + return NS_OK; } NS_IMETHODIMP diff --git a/dom/workers/WorkerDebugger.h b/dom/workers/WorkerDebugger.h index e73641278c12..ca47a8500d6d 100644 --- a/dom/workers/WorkerDebugger.h +++ b/dom/workers/WorkerDebugger.h @@ -13,7 +13,6 @@ class mozIDOMWindow; class nsIPrincipal; -class nsPIDOMWindowInner; namespace mozilla { namespace dom { @@ -57,8 +56,6 @@ class WorkerDebugger : public nsIWorkerDebugger { void ReportErrorToDebuggerOnMainThread(const nsAString& aFilename, uint32_t aLineno, const nsAString& aMessage); - - nsCOMPtr DedicatedWorkerWindow(); }; } // namespace dom diff --git a/dom/workers/nsIWorkerDebugger.idl b/dom/workers/nsIWorkerDebugger.idl index 35b58e78e54f..fdd5a4b58ae0 100644 --- a/dom/workers/nsIWorkerDebugger.idl +++ b/dom/workers/nsIWorkerDebugger.idl @@ -37,8 +37,6 @@ interface nsIWorkerDebugger : nsISupports // nested workers) its top-level ancestral worker is associated with. readonly attribute mozIDOMWindow window; - readonly attribute Array windowIDs; - readonly attribute nsIPrincipal principal; readonly attribute unsigned long serviceWorkerID; diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.h b/dom/workers/remoteworkers/RemoteWorkerChild.h index eae3e30205f6..be81cecc8cea 100644 --- a/dom/workers/remoteworkers/RemoteWorkerChild.h +++ b/dom/workers/remoteworkers/RemoteWorkerChild.h @@ -66,8 +66,6 @@ class RemoteWorkerChild final RefPtr MaybeSendSetServiceWorkerSkipWaitingFlag(); - const nsTArray& WindowIDs() const { return mWindowIDs; } - private: class InitializeWorkerRunnable; From 584e130dbe21de2e6e9777d8a536c873d5d6cd13 Mon Sep 17 00:00:00 2001 From: Mihai Alexandru Michis Date: Thu, 21 Jan 2021 11:40:48 +0200 Subject: [PATCH 2/2] Backed out 3 changesets (bug 1662129) for causing devtools failures in browser_dbg-pause-points.js a=backout DONTBUILD Backed out changeset 8562cd4f988b (bug 1662129) Backed out changeset 9974b7eea47b (bug 1662129) Backed out changeset 3f4451fcdc25 (bug 1662129) --- .../client/debugger/src/client/firefox.js | 41 +- .../debugger/src/client/firefox/commands.js | 40 +- .../debugger/src/client/firefox/events.js | 98 ++++- .../test/mochitest/browser_dbg-expressions.js | 10 - .../client/debugger/test/mochitest/helpers.js | 4 +- .../resources/legacy-listeners/breakpoint.js | 82 ---- .../resources/legacy-listeners/moz.build | 1 - devtools/shared/resources/resource-watcher.js | 3 - .../resources/tests/breakpoint_document.html | 21 - devtools/shared/resources/tests/browser.ini | 2 - .../tests/browser_resources_breakpoints.js | 371 ------------------ 11 files changed, 137 insertions(+), 536 deletions(-) delete mode 100644 devtools/shared/resources/legacy-listeners/breakpoint.js delete mode 100644 devtools/shared/resources/tests/breakpoint_document.html delete mode 100644 devtools/shared/resources/tests/browser_resources_breakpoints.js diff --git a/devtools/client/debugger/src/client/firefox.js b/devtools/client/debugger/src/client/firefox.js index 496f1e36b2ae..261ded155d12 100644 --- a/devtools/client/debugger/src/client/firefox.js +++ b/devtools/client/debugger/src/client/firefox.js @@ -5,14 +5,9 @@ // @flow import { setupCommands, clientCommands } from "./firefox/commands"; -import { - setupEvents, - waitForSourceActorToBeRegisteredInStore, -} from "./firefox/events"; -import { createPause, prepareSourcePayload } from "./firefox/create"; +import { setupEvents, clientEvents } from "./firefox/events"; import { features, prefs } from "../utils/prefs"; - -import { recordEvent } from "../utils/telemetry"; +import { prepareSourcePayload } from "./firefox/create"; let actions; let targetList; @@ -50,14 +45,9 @@ export async function onConnect( onTargetDestroyed ); - // Use independant listeners for SOURCE and BREAKPOINT in order to ease - // doing batching and notify about a set of SOURCE's in one redux action. await resourceWatcher.watchResources([resourceWatcher.TYPES.SOURCE], { onAvailable: onSourceAvailable, }); - await resourceWatcher.watchResources([resourceWatcher.TYPES.BREAKPOINT], { - onAvailable: onBreakpointAvailable, - }); } export function onDisconnect() { @@ -69,9 +59,6 @@ export function onDisconnect() { resourceWatcher.unwatchResources([resourceWatcher.TYPES.SOURCE], { onAvailable: onSourceAvailable, }); - resourceWatcher.unwatchResources([resourceWatcher.TYPES.BREAKPOINT], { - onAvailable: onBreakpointAvailable, - }); } async function onTargetAvailable({ @@ -136,6 +123,7 @@ async function onTargetAvailable({ targetFront.isWebExtension ); + await clientCommands.checkIfAlreadyPaused(); await actions.addTarget(targetFront); } @@ -158,25 +146,4 @@ async function onSourceAvailable(sources) { await actions.newGeneratedSources(frontendSources); } -async function onBreakpointAvailable(breakpoints) { - for (const resource of breakpoints) { - const threadFront = await resource.targetFront.getFront("thread"); - if (resource.state == "paused") { - if (resource.frame) { - // When reloading we might receive a pause event before the - // top frame's source has arrived. - await waitForSourceActorToBeRegisteredInStore( - resource.frame.where.actor - ); - } - - const pause = createPause(threadFront.actor, resource); - actions.paused(pause); - recordEvent("pause", { reason: resource.why.type }); - } else if (resource.state == "resumed") { - actions.resumed(threadFront.actorID); - } - } -} - -export { clientCommands }; +export { clientCommands, clientEvents }; diff --git a/devtools/client/debugger/src/client/firefox/commands.js b/devtools/client/debugger/src/client/firefox/commands.js index 6155223459f1..508bddc99d28 100644 --- a/devtools/client/debugger/src/client/firefox/commands.js +++ b/devtools/client/debugger/src/client/firefox/commands.js @@ -5,7 +5,12 @@ // @flow import { createThread, createFrame } from "./create"; -import { waitForSourceActorToBeRegisteredInStore } from "./events"; +import { + addThreadEventListeners, + clientEvents, + removeThreadEventListeners, + ensureSourceActor, +} from "./events"; import { makePendingLocationId } from "../../utils/breakpoint"; // $FlowIgnore @@ -347,9 +352,7 @@ async function getFrames(thread: string) { // Ensure that each frame has its source already available. // Because of throttling, the source may be available a bit late. await Promise.all( - response.frames.map(frame => - waitForSourceActorToBeRegisteredInStore(frame.where.actor) - ) + response.frames.map(frame => ensureSourceActor(frame.where.actor)) ); return response.frames.map((frame, i) => @@ -436,6 +439,26 @@ async function toggleEventLogging(logEventBreakpoints: boolean) { ); } +function getAllThreadFronts(): ThreadFront[] { + const fronts = [currentThreadFront()]; + for (const { threadFront } of (Object.values(targets): any)) { + fronts.push(threadFront); + } + return fronts; +} + +// Check if any of the targets were paused before we opened +// the debugger. If one is paused. Fake a `pause` RDP event +// by directly calling the client event listener. +async function checkIfAlreadyPaused() { + for (const threadFront of getAllThreadFronts()) { + const pausedPacket = threadFront.getLastPausePacket(); + if (pausedPacket) { + clientEvents.paused(threadFront, pausedPacket); + } + } +} + function getSourceForActor(actor: ActorId) { if (!sourceActors[actor]) { throw new Error(`Unknown source actor: ${actor}`); @@ -447,11 +470,19 @@ async function addThread(targetFront: Target) { const threadActorID = targetFront.targetForm.threadActor; if (!targets[threadActorID]) { targets[threadActorID] = targetFront; + addThreadEventListeners(targetFront.threadFront); } return createThread(threadActorID, targetFront); } function removeThread(thread: Thread) { + const targetFront = targets[thread.actor]; + if (targetFront) { + // Note that if the target is already fully destroyed, threadFront will be + // null, but event listeners will already have been removed. + removeThreadEventListeners(targetFront.threadFront); + } + delete targets[thread.actor]; } @@ -538,6 +569,7 @@ const clientCommands = { getFrames, pauseOnExceptions, toggleEventLogging, + checkIfAlreadyPaused, registerSourceActor, addThread, removeThread, diff --git a/devtools/client/debugger/src/client/firefox/events.js b/devtools/client/debugger/src/client/firefox/events.js index 3cb72a72ba04..ebec1f1e0626 100644 --- a/devtools/client/debugger/src/client/firefox/events.js +++ b/devtools/client/debugger/src/client/firefox/events.js @@ -4,23 +4,103 @@ // @flow +import type { + PausedPacket, + ThreadFront, + Target, + DevToolsClient, +} from "./types"; + import Actions from "../../actions"; +import { createPause } from "./create"; import sourceQueue from "../../utils/source-queue"; +import { recordEvent } from "../../utils/telemetry"; +import { prefs } from "../../utils/prefs"; import { hasSourceActor } from "../../selectors"; import { stringToSourceActorId } from "../../reducers/source-actors"; type Dependencies = { actions: typeof Actions, + devToolsClient: DevToolsClient, store: any, }; +let actions: typeof Actions; +let isInterrupted: boolean; +let threadFrontListeners: WeakMap>; let store: any; +function addThreadEventListeners(thread: ThreadFront): void { + const removeListeners = []; + Object.keys(clientEvents).forEach(eventName => { + // EventEmitter.on returns a function that removes the event listener. + removeListeners.push( + thread.on(eventName, clientEvents[eventName].bind(null, thread)) + ); + }); + threadFrontListeners.set(thread, removeListeners); +} + +function removeThreadEventListeners(thread: ThreadFront): void { + const removeListeners = threadFrontListeners.get(thread) || []; + for (const removeListener of removeListeners) { + removeListener(); + } +} + +function attachAllTargets(currentTarget: Target): boolean { + return prefs.fission && currentTarget.isParentProcess; +} + function setupEvents(dependencies: Dependencies): void { - const actions = dependencies.actions; + actions = dependencies.actions; sourceQueue.initialize(actions); store = dependencies.store; + + threadFrontListeners = new WeakMap(); +} + +async function paused( + threadFront: ThreadFront, + packet: PausedPacket +): Promise<*> { + // If paused by an explicit interrupt, which are generated by the + // slow script dialog and internal events such as setting + // breakpoints, ignore the event. + const { why } = packet; + if (why.type === "interrupted" && !packet.why.onNext) { + isInterrupted = true; + return; + } + + // Ignore attached events because they are not useful to the user. + if (why.type == "alreadyPaused" || why.type == "attached") { + return; + } + + if (packet.frame) { + // When reloading we might receive a pause event before the + // top frame's source has arrived. + await ensureSourceActor(packet.frame.where.actor); + } + + const pause = createPause(threadFront.actor, packet); + + actions.paused(pause); + recordEvent("pause", { reason: why.type }); +} + +function resumed(threadFront: ThreadFront): void { + // NOTE: the client suppresses resumed events while interrupted + // to prevent unintentional behavior. + // see [client docs](../README.md#interrupted) for more information. + if (isInterrupted) { + isInterrupted = false; + return; + } + + actions.resumed(threadFront.actorID); } /** @@ -29,7 +109,7 @@ function setupEvents(dependencies: Dependencies): void { * @param {String} sourceActor * Actor ID of the source to be waiting for. */ -async function waitForSourceActorToBeRegisteredInStore(sourceActor: string) { +async function ensureSourceActor(sourceActor: string) { const sourceActorId = stringToSourceActorId(sourceActor); if (!hasSourceActor(store.getState(), sourceActorId)) { await new Promise(resolve => { @@ -50,4 +130,16 @@ async function waitForSourceActorToBeRegisteredInStore(sourceActor: string) { } } -export { setupEvents, waitForSourceActorToBeRegisteredInStore }; +const clientEvents = { + paused, + resumed, +}; + +export { + setupEvents, + clientEvents, + addThreadEventListeners, + removeThreadEventListeners, + attachAllTargets, + ensureSourceActor, +}; diff --git a/devtools/client/debugger/test/mochitest/browser_dbg-expressions.js b/devtools/client/debugger/test/mochitest/browser_dbg-expressions.js index be19c271499d..d9962c2e91f0 100644 --- a/devtools/client/debugger/test/mochitest/browser_dbg-expressions.js +++ b/devtools/client/debugger/test/mochitest/browser_dbg-expressions.js @@ -33,23 +33,13 @@ add_task(async function() { // can expand an expression await toggleExpressionNode(dbg, 2); - is(findAllElements(dbg, "expressionNodes").length, 35); - is(dbg.selectors.getExpressions(dbg.store.getState()).length, 2); - await deleteExpression(dbg, "foo"); await deleteExpression(dbg, "location"); is(findAllElements(dbg, "expressionNodes").length, 0); - is(dbg.selectors.getExpressions(dbg.store.getState()).length, 0); // Test expanding properties when the debuggee is active - // Wait for full evaluation of the expressions in order to avoid having - // mixed up code between the location being removed and the one being re-added - const evaluated = waitForDispatch(dbg, "EVALUATE_EXPRESSIONS"); await resume(dbg); - await evaluated; - await addExpression(dbg, "location"); - is(dbg.selectors.getExpressions(dbg.store.getState()).length, 1); is(findAllElements(dbg, "expressionNodes").length, 1); diff --git a/devtools/client/debugger/test/mochitest/helpers.js b/devtools/client/debugger/test/mochitest/helpers.js index 128561b3975b..073d8ffb33f1 100644 --- a/devtools/client/debugger/test/mochitest/helpers.js +++ b/devtools/client/debugger/test/mochitest/helpers.js @@ -1851,9 +1851,9 @@ async function addExpression(dbg, input) { } findElementWithSelector(dbg, selectors.expressionInput).focus(); type(dbg, input); - const evaluated = waitForDispatch(dbg, "EVALUATE_EXPRESSION"); pressKey(dbg, "Enter"); - await evaluated; + + await waitForDispatch(dbg, "EVALUATE_EXPRESSION"); } async function editExpression(dbg, input) { diff --git a/devtools/shared/resources/legacy-listeners/breakpoint.js b/devtools/shared/resources/legacy-listeners/breakpoint.js deleted file mode 100644 index d4e1afd2f526..000000000000 --- a/devtools/shared/resources/legacy-listeners/breakpoint.js +++ /dev/null @@ -1,82 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const { - ResourceWatcher, -} = require("devtools/shared/resources/resource-watcher"); - -module.exports = async function({ targetList, targetFront, onAvailable }) { - const isBrowserToolbox = targetList.targetFront.isParentProcess; - const isNonTopLevelFrameTarget = - !targetFront.isTopLevel && - targetFront.targetType === targetList.TYPES.FRAME; - - if (isBrowserToolbox && isNonTopLevelFrameTarget) { - // In the BrowserToolbox, non-top-level frame targets are already - // debugged via content-process targets. - return; - } - - // Wait for the thread actor to be attached, otherwise getFront(thread) will throw for worker targets - // This is because worker target are still kind of descriptors and are only resolved into real target - // after being attached. And the thread actor ID is only retrieved and available after being attached. - await targetFront.onThreadAttached; - - if (targetFront.isDestroyed()) { - return; - } - const threadFront = await targetFront.getFront("thread"); - - let isInterrupted = false; - const onPausedPacket = packet => { - // If paused by an explicit interrupt, which are generated by the - // slow script dialog and internal events such as setting - // breakpoints, ignore the event. - const { why } = packet; - if (why.type === "interrupted" && !why.onNext) { - isInterrupted = true; - return; - } - - // Ignore attached events because they are not useful to the user. - if (why.type == "alreadyPaused" || why.type == "attached") { - return; - } - - onAvailable([ - { - resourceType: ResourceWatcher.TYPES.BREAKPOINT, - state: "paused", - why, - frame: packet.frame, - }, - ]); - }; - threadFront.on("paused", onPausedPacket); - - threadFront.on("resumed", packet => { - // NOTE: the client suppresses resumed events while interrupted - // to prevent unintentional behavior. - // see [client docs](devtools/client/debugger/src/client/README.md#interrupted) for more information. - if (isInterrupted) { - isInterrupted = false; - return; - } - - onAvailable([ - { - resourceType: ResourceWatcher.TYPES.BREAKPOINT, - state: "resumed", - }, - ]); - }); - - // Notify about already paused thread - const pausedPacket = threadFront.getLastPausePacket(); - if (pausedPacket) { - onPausedPacket(pausedPacket); - } -}; diff --git a/devtools/shared/resources/legacy-listeners/moz.build b/devtools/shared/resources/legacy-listeners/moz.build index b152a88ddbc4..ca84256b66bd 100644 --- a/devtools/shared/resources/legacy-listeners/moz.build +++ b/devtools/shared/resources/legacy-listeners/moz.build @@ -3,7 +3,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( - "breakpoint.js", "cache-storage.js", "console-messages.js", "cookie.js", diff --git a/devtools/shared/resources/resource-watcher.js b/devtools/shared/resources/resource-watcher.js index 13b43c3a9f2b..02c7f07dcaf6 100644 --- a/devtools/shared/resources/resource-watcher.js +++ b/devtools/shared/resources/resource-watcher.js @@ -847,7 +847,6 @@ ResourceWatcher.TYPES = ResourceWatcher.prototype.TYPES = { INDEXED_DB: "indexed-db", NETWORK_EVENT_STACKTRACE: "network-event-stacktrace", SOURCE: "source", - BREAKPOINT: "breakpoint", }; module.exports = { ResourceWatcher, TYPES: ResourceWatcher.TYPES }; @@ -906,8 +905,6 @@ const LegacyListeners = { .NETWORK_EVENT_STACKTRACE]: require("devtools/shared/resources/legacy-listeners/network-event-stacktraces"), [ResourceWatcher.TYPES .SOURCE]: require("devtools/shared/resources/legacy-listeners/source"), - [ResourceWatcher.TYPES - .BREAKPOINT]: require("devtools/shared/resources/legacy-listeners/breakpoint"), }; // Optional transformers for each type of resource. diff --git a/devtools/shared/resources/tests/breakpoint_document.html b/devtools/shared/resources/tests/breakpoint_document.html deleted file mode 100644 index 229109464697..000000000000 --- a/devtools/shared/resources/tests/breakpoint_document.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Test breakpoint document - - - - - - diff --git a/devtools/shared/resources/tests/browser.ini b/devtools/shared/resources/tests/browser.ini index 63fa27635ab0..afbc008130e5 100644 --- a/devtools/shared/resources/tests/browser.ini +++ b/devtools/shared/resources/tests/browser.ini @@ -6,7 +6,6 @@ support-files = !/devtools/client/shared/test/telemetry-test-helpers.js !/devtools/client/shared/test/test-actor.js head.js - breakpoint_document.html network_document.html early_console_document.html fission_document.html @@ -27,7 +26,6 @@ support-files = worker-sources.js [browser_browser_resources_console_messages.js] -[browser_resources_breakpoints.js] [browser_resources_client_caching.js] [browser_resources_console_messages.js] [browser_resources_console_messages_workers.js] diff --git a/devtools/shared/resources/tests/browser_resources_breakpoints.js b/devtools/shared/resources/tests/browser_resources_breakpoints.js deleted file mode 100644 index 3599eaa46144..000000000000 --- a/devtools/shared/resources/tests/browser_resources_breakpoints.js +++ /dev/null @@ -1,371 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// Test the ResourceWatcher API around BREAKPOINT - -const { - ResourceWatcher, -} = require("devtools/shared/resources/resource-watcher"); - -const BREAKPOINT_TEST_URL = URL_ROOT_SSL + "breakpoint_document.html"; - -add_task(async function() { - await checkBreakpointBeforeWatchResources(); - - await checkBreakpointAfterWatchResources(); - - await checkRealBreakpoint(); - - await checkPauseOnException(); -}); - -async function checkBreakpointBeforeWatchResources() { - info( - "Check whether ResourceWatcher gets existing breakpoint, being hit before calling watchResources" - ); - - const tab = await addTab(BREAKPOINT_TEST_URL); - - const { client, resourceWatcher, targetList } = await initResourceWatcher( - tab - ); - - // Attach the thread actor before running the debugger statement, - // so that it is correctly catched by the thread actor. - info("Attach the top level target"); - await targetList.targetFront.attach(); - info("Attach the top level thread actor"); - const threadFront = await targetList.targetFront.attachThread(); - - info("Run the 'debugger' statement"); - // Note that we do not wait for the resolution of spawn as it will be paused - ContentTask.spawn(tab.linkedBrowser, null, () => { - content.window.wrappedJSObject.runDebuggerStatement(); - }); - - info("Call watchResources"); - const availableResources = []; - await resourceWatcher.watchResources([ResourceWatcher.TYPES.BREAKPOINT], { - onAvailable: resources => availableResources.push(...resources), - }); - - is( - availableResources.length, - 1, - "Got the breakpoint related to the debugger statement" - ); - const breakpoint = availableResources.pop(); - - assertPausedResource(breakpoint, { - state: "paused", - why: { - type: "debuggerStatement", - }, - frame: { - type: "call", - asyncCause: null, - state: "on-stack", - // this: object actor's form referring to `this` variable - displayName: "runDebuggerStatement", - // arguments: [] - where: { - line: 17, - column: 6, - }, - }, - }); - - await threadFront.resume(); - - await waitFor( - () => availableResources.length == 1, - "Wait until we receive the resumed event" - ); - - const resumed = availableResources.pop(); - - assertResumedResource(resumed); - - targetList.destroy(); - await client.close(); -} - -async function checkBreakpointAfterWatchResources() { - info( - "Check whether ResourceWatcher gets breakpoint hit after calling watchResources" - ); - - const tab = await addTab(BREAKPOINT_TEST_URL); - - const { client, resourceWatcher, targetList } = await initResourceWatcher( - tab - ); - - info("Call watchResources"); - const availableResources = []; - await resourceWatcher.watchResources([ResourceWatcher.TYPES.BREAKPOINT], { - onAvailable: resources => availableResources.push(...resources), - }); - - is( - availableResources.length, - 0, - "Got no breakpoint when calling watchResources" - ); - - info("Run the 'debugger' statement"); - // Note that we do not wait for the resolution of spawn as it will be paused - ContentTask.spawn(tab.linkedBrowser, null, () => { - content.window.wrappedJSObject.runDebuggerStatement(); - }); - - await waitFor( - () => availableResources.length == 1, - "Got the breakpoint related to the debugger statement" - ); - const breakpoint = availableResources.pop(); - - assertPausedResource(breakpoint, { - state: "paused", - why: { - type: "debuggerStatement", - }, - frame: { - type: "call", - asyncCause: null, - state: "on-stack", - // this: object actor's form referring to `this` variable - displayName: "runDebuggerStatement", - // arguments: [] - where: { - line: 17, - column: 6, - }, - }, - }); - - // treadFront is created and attached while calling watchResources - const { threadFront } = targetList.targetFront; - - await threadFront.resume(); - - await waitFor( - () => availableResources.length == 1, - "Wait until we receive the resumed event" - ); - - const resumed = availableResources.pop(); - - assertResumedResource(resumed); - - targetList.destroy(); - await client.close(); -} - -async function checkRealBreakpoint() { - info( - "Check whether ResourceWatcher gets breakpoint set via the thread Front (instead of just debugger statements)" - ); - - const tab = await addTab(BREAKPOINT_TEST_URL); - - const { client, resourceWatcher, targetList } = await initResourceWatcher( - tab - ); - - info("Call watchResources"); - const availableResources = []; - await resourceWatcher.watchResources([ResourceWatcher.TYPES.BREAKPOINT], { - onAvailable: resources => availableResources.push(...resources), - }); - - is( - availableResources.length, - 0, - "Got no breakpoint when calling watchResources" - ); - - // treadFront is created and attached while calling watchResources - const { threadFront } = targetList.targetFront; - - // We have to call `sources` request, otherwise the Thread Actor - // doesn't start watching for sources, and ignore the setBreakpoint call - // as it doesn't have any source registered. - await threadFront.getSources(); - - await threadFront.setBreakpoint( - { sourceUrl: BREAKPOINT_TEST_URL, line: 14 }, - {} - ); - - info("Run the test function where we set a breakpoint"); - // Note that we do not wait for the resolution of spawn as it will be paused - ContentTask.spawn(tab.linkedBrowser, null, () => { - content.window.wrappedJSObject.testFunction(); - }); - - await waitFor( - () => availableResources.length == 1, - "Got the breakpoint related to the debugger statement" - ); - const breakpoint = availableResources.pop(); - - assertPausedResource(breakpoint, { - state: "paused", - why: { - type: "breakpoint", - }, - frame: { - type: "call", - asyncCause: null, - state: "on-stack", - // this: object actor's form referring to `this` variable - displayName: "testFunction", - // arguments: [] - where: { - line: 14, - column: 6, - }, - }, - }); - - await threadFront.resume(); - - await waitFor( - () => availableResources.length == 1, - "Wait until we receive the resumed event" - ); - - const resumed = availableResources.pop(); - - assertResumedResource(resumed); - - targetList.destroy(); - await client.close(); -} - -async function checkPauseOnException() { - info( - "Check whether ResourceWatcher gets breakpoint for exception (when explicitly requested)" - ); - - const tab = await addTab( - "data:text/html," - ); - - const { client, resourceWatcher, targetList } = await initResourceWatcher( - tab - ); - - info("Call watchResources"); - const availableResources = []; - await resourceWatcher.watchResources([ResourceWatcher.TYPES.BREAKPOINT], { - onAvailable: resources => availableResources.push(...resources), - }); - - is( - availableResources.length, - 0, - "Got no breakpoint when calling watchResources" - ); - - // treadFront is created and attached while calling watchResources - const { threadFront } = targetList.targetFront; - await threadFront.reconfigure({ pauseOnExceptions: true }); - - info("Reload the page, in order to trigger exception on load"); - const reloaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser); - tab.linkedBrowser.reload(); - - await waitFor( - () => availableResources.length == 1, - "Got the breakpoint related to the debugger statement" - ); - const breakpoint = availableResources.pop(); - - assertPausedResource(breakpoint, { - state: "paused", - why: { - type: "exception", - }, - frame: { - type: "global", - asyncCause: null, - state: "on-stack", - // this: object actor's form referring to `this` variable - displayName: "(global)", - // arguments: [] - where: { - line: 1, - column: 0, - }, - }, - }); - - await threadFront.resume(); - info("Wait for page to finish reloading after resume"); - await reloaded; - - await waitFor( - () => availableResources.length == 1, - "Wait until we receive the resumed event" - ); - - const resumed = availableResources.pop(); - - assertResumedResource(resumed); - - targetList.destroy(); - await client.close(); -} - -async function assertPausedResource(resource, expected) { - is( - resource.resourceType, - ResourceWatcher.TYPES.BREAKPOINT, - "Resource type is correct" - ); - is(resource.state, "paused", "state attribute is correct"); - is(resource.why.type, expected.why.type, "why.type attribute is correct"); - is( - resource.frame.type, - expected.frame.type, - "frame.type attribute is correct" - ); - is( - resource.frame.asyncCause, - expected.frame.asyncCause, - "frame.asyncCause attribute is correct" - ); - is( - resource.frame.state, - expected.frame.state, - "frame.state attribute is correct" - ); - is( - resource.frame.displayName, - expected.frame.displayName, - "frame.displayName attribute is correct" - ); - is( - resource.frame.where.line, - expected.frame.where.line, - "frame.where.line attribute is correct" - ); - is( - resource.frame.where.column, - expected.frame.where.column, - "frame.where.column attribute is correct" - ); -} - -async function assertResumedResource(resource) { - is( - resource.resourceType, - ResourceWatcher.TYPES.BREAKPOINT, - "Resource type is correct" - ); - is(resource.state, "resumed", "state attribute is correct"); -}