зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1662129 - [devtools] Introduce breakpoint resource. r=nchevobbe,jdescottes
This is the simple step, where we move the ThreadFront code related to pauses from Debugger frontend to a legacy listener. The next patch is the hard one, which will replace paused/resumed events by resources. It may sound weird to have pause and resume to becomes resources, as they aren't really resources. These are transcient events, which we don't really want to record. We especially do not really care to save them in the ResourceWatcher cache. But, by becoming resources, we benefit from the resources framework, which allows to: * listen and emit such event as early as the page/target starts loading * do not depend on a particular front to be ready/attached on the frontend * once we communicate to the watcher we care about breakpoints, we do register the breakpoint listening code for all the targets. (we no longer have to do a per-target work in the frontend) Differential Revision: https://phabricator.services.mozilla.com/D88851
This commit is contained in:
Родитель
9b840135cd
Коммит
dd9ea44555
|
@ -5,9 +5,11 @@
|
|||
// @flow
|
||||
|
||||
import { setupCommands, clientCommands } from "./firefox/commands";
|
||||
import { setupEvents, clientEvents } from "./firefox/events";
|
||||
import { setupEvents, ensureSourceActor } from "./firefox/events";
|
||||
import { createPause, prepareSourcePayload } from "./firefox/create";
|
||||
import { features, prefs } from "../utils/prefs";
|
||||
import { prepareSourcePayload } from "./firefox/create";
|
||||
|
||||
import { recordEvent } from "../utils/telemetry";
|
||||
|
||||
let actions;
|
||||
let targetList;
|
||||
|
@ -45,9 +47,14 @@ 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() {
|
||||
|
@ -59,6 +66,9 @@ export function onDisconnect() {
|
|||
resourceWatcher.unwatchResources([resourceWatcher.TYPES.SOURCE], {
|
||||
onAvailable: onSourceAvailable,
|
||||
});
|
||||
resourceWatcher.unwatchResources([resourceWatcher.TYPES.BREAKPOINT], {
|
||||
onAvailable: onBreakpointAvailable,
|
||||
});
|
||||
}
|
||||
|
||||
async function onTargetAvailable({
|
||||
|
@ -123,7 +133,6 @@ async function onTargetAvailable({
|
|||
targetFront.isWebExtension
|
||||
);
|
||||
|
||||
await clientCommands.checkIfAlreadyPaused();
|
||||
await actions.addTarget(targetFront);
|
||||
}
|
||||
|
||||
|
@ -146,4 +155,23 @@ async function onSourceAvailable(sources) {
|
|||
await actions.newGeneratedSources(frontendSources);
|
||||
}
|
||||
|
||||
export { clientCommands, clientEvents };
|
||||
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 ensureSourceActor(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 };
|
||||
|
|
|
@ -5,12 +5,7 @@
|
|||
// @flow
|
||||
|
||||
import { createThread, createFrame } from "./create";
|
||||
import {
|
||||
addThreadEventListeners,
|
||||
clientEvents,
|
||||
removeThreadEventListeners,
|
||||
ensureSourceActor,
|
||||
} from "./events";
|
||||
import { ensureSourceActor } from "./events";
|
||||
import { makePendingLocationId } from "../../utils/breakpoint";
|
||||
|
||||
// $FlowIgnore
|
||||
|
@ -439,26 +434,6 @@ 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}`);
|
||||
|
@ -470,19 +445,11 @@ 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];
|
||||
}
|
||||
|
||||
|
@ -569,7 +536,6 @@ const clientCommands = {
|
|||
getFrames,
|
||||
pauseOnExceptions,
|
||||
toggleEventLogging,
|
||||
checkIfAlreadyPaused,
|
||||
registerSourceActor,
|
||||
addThread,
|
||||
removeThread,
|
||||
|
|
|
@ -4,103 +4,23 @@
|
|||
|
||||
// @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<ThreadFront, Array<Function>>;
|
||||
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 {
|
||||
actions = dependencies.actions;
|
||||
const 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,16 +50,4 @@ async function ensureSourceActor(sourceActor: string) {
|
|||
}
|
||||
}
|
||||
|
||||
const clientEvents = {
|
||||
paused,
|
||||
resumed,
|
||||
};
|
||||
|
||||
export {
|
||||
setupEvents,
|
||||
clientEvents,
|
||||
addThreadEventListeners,
|
||||
removeThreadEventListeners,
|
||||
attachAllTargets,
|
||||
ensureSourceActor,
|
||||
};
|
||||
export { setupEvents, ensureSourceActor };
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/* 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);
|
||||
}
|
||||
};
|
|
@ -3,6 +3,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
"breakpoint.js",
|
||||
"cache-storage.js",
|
||||
"console-messages.js",
|
||||
"cookie.js",
|
||||
|
|
|
@ -847,6 +847,7 @@ 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 };
|
||||
|
||||
|
@ -905,6 +906,8 @@ 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.
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Test breakpoint document</title>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
"use strict";
|
||||
/* eslint-disable */
|
||||
function testFunction() {
|
||||
console.log("test Function ran");
|
||||
}
|
||||
function runDebuggerStatement() {
|
||||
debugger;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -6,6 +6,7 @@ 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
|
||||
|
@ -26,6 +27,7 @@ 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]
|
||||
|
|
|
@ -0,0 +1,371 @@
|
|||
/* 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,<meta charset=utf8><script>a.b.c.d</script>"
|
||||
);
|
||||
|
||||
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");
|
||||
}
|
Загрузка…
Ссылка в новой задаче