forked from mirrors/gecko-dev
560 lines
15 KiB
JavaScript
560 lines
15 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
// Test the ResourceCommand API around THREAD_STATE
|
|
|
|
const ResourceCommand = require("resource://devtools/shared/commands/resource/resource-command.js");
|
|
|
|
const BREAKPOINT_TEST_URL = URL_ROOT_SSL + "breakpoint_document.html";
|
|
const REMOTE_IFRAME_URL =
|
|
"https://example.org/document-builder.sjs?html=" +
|
|
encodeURIComponent("<script>debugger;</script>");
|
|
|
|
add_task(async function() {
|
|
// Check hitting the "debugger;" statement before and after calling
|
|
// watchResource(THREAD_TYPES). Both should break. First will
|
|
// be a cached resource and second will be a live one.
|
|
await checkBreakpointBeforeWatchResources();
|
|
await checkBreakpointAfterWatchResources();
|
|
|
|
// Check setting a real breakpoint on a given line
|
|
await checkRealBreakpoint();
|
|
|
|
// Check the "pause on exception" setting
|
|
await checkPauseOnException();
|
|
|
|
// Check an edge case where spamming setBreakpoints calls causes issues
|
|
await checkSetBeforeWatch();
|
|
|
|
// Check debugger statement for (remote) iframes
|
|
await checkDebuggerStatementInIframes();
|
|
});
|
|
|
|
async function checkBreakpointBeforeWatchResources() {
|
|
info(
|
|
"Check whether ResourceCommand gets existing breakpoint, being hit before calling watchResources"
|
|
);
|
|
|
|
const tab = await addTab(BREAKPOINT_TEST_URL);
|
|
|
|
const { client, resourceCommand, targetCommand } = await initResourceCommand(
|
|
tab
|
|
);
|
|
|
|
// Ensure that the target front is initialized early from TargetCommand.onTargetAvailable
|
|
// By the time `initResourceCommand` resolves, it should already be initialized.
|
|
info(
|
|
"Verify that TargetFront's initialized is resolved after having calling attachAndInitThread"
|
|
);
|
|
await targetCommand.targetFront.initialized;
|
|
|
|
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 resourceCommand.watchResources([resourceCommand.TYPES.THREAD_STATE], {
|
|
onAvailable: resources => availableResources.push(...resources),
|
|
});
|
|
|
|
is(
|
|
availableResources.length,
|
|
1,
|
|
"Got the THREAD_STATE's related to the debugger statement"
|
|
);
|
|
const threadState = availableResources.pop();
|
|
|
|
assertPausedResource(threadState, {
|
|
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,
|
|
},
|
|
},
|
|
});
|
|
|
|
const { threadFront } = targetCommand.targetFront;
|
|
await threadFront.resume();
|
|
|
|
await waitFor(
|
|
() => availableResources.length == 1,
|
|
"Wait until we receive the resumed event"
|
|
);
|
|
|
|
const resumed = availableResources.pop();
|
|
|
|
assertResumedResource(resumed);
|
|
|
|
targetCommand.destroy();
|
|
await client.close();
|
|
}
|
|
|
|
async function checkBreakpointAfterWatchResources() {
|
|
info(
|
|
"Check whether ResourceCommand gets breakpoint hit after calling watchResources"
|
|
);
|
|
|
|
const tab = await addTab(BREAKPOINT_TEST_URL);
|
|
|
|
const { client, resourceCommand, targetCommand } = await initResourceCommand(
|
|
tab
|
|
);
|
|
|
|
info("Call watchResources");
|
|
const availableResources = [];
|
|
await resourceCommand.watchResources([resourceCommand.TYPES.THREAD_STATE], {
|
|
onAvailable: resources => availableResources.push(...resources),
|
|
});
|
|
|
|
is(
|
|
availableResources.length,
|
|
0,
|
|
"Got no THREAD_STATE 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 THREAD_STATE related to the debugger statement"
|
|
);
|
|
const threadState = availableResources.pop();
|
|
|
|
assertPausedResource(threadState, {
|
|
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 } = targetCommand.targetFront;
|
|
|
|
await threadFront.resume();
|
|
|
|
await waitFor(
|
|
() => availableResources.length == 1,
|
|
"Wait until we receive the resumed event"
|
|
);
|
|
|
|
const resumed = availableResources.pop();
|
|
|
|
assertResumedResource(resumed);
|
|
|
|
targetCommand.destroy();
|
|
await client.close();
|
|
}
|
|
|
|
async function checkRealBreakpoint() {
|
|
info(
|
|
"Check whether ResourceCommand gets breakpoint set via the thread Front (instead of just debugger statements)"
|
|
);
|
|
|
|
const tab = await addTab(BREAKPOINT_TEST_URL);
|
|
|
|
const { client, resourceCommand, targetCommand } = await initResourceCommand(
|
|
tab
|
|
);
|
|
|
|
info("Call watchResources");
|
|
const availableResources = [];
|
|
await resourceCommand.watchResources([resourceCommand.TYPES.THREAD_STATE], {
|
|
onAvailable: resources => availableResources.push(...resources),
|
|
});
|
|
|
|
is(
|
|
availableResources.length,
|
|
0,
|
|
"Got no THREAD_STATE when calling watchResources"
|
|
);
|
|
|
|
// treadFront is created and attached while calling watchResources
|
|
const { threadFront } = targetCommand.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 THREAD_STATE related to the debugger statement"
|
|
);
|
|
const threadState = availableResources.pop();
|
|
|
|
assertPausedResource(threadState, {
|
|
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);
|
|
|
|
targetCommand.destroy();
|
|
await client.close();
|
|
}
|
|
|
|
async function checkPauseOnException() {
|
|
info(
|
|
"Check whether ResourceCommand gets breakpoint for exception (when explicitly requested)"
|
|
);
|
|
|
|
const tab = await addTab(
|
|
"data:text/html,<meta charset=utf8><script>a.b.c.d</script>"
|
|
);
|
|
|
|
const {
|
|
commands,
|
|
resourceCommand,
|
|
targetCommand,
|
|
} = await initResourceCommand(tab);
|
|
|
|
info("Call watchResources");
|
|
const availableResources = [];
|
|
await resourceCommand.watchResources([resourceCommand.TYPES.THREAD_STATE], {
|
|
onAvailable: resources => availableResources.push(...resources),
|
|
});
|
|
|
|
is(
|
|
availableResources.length,
|
|
0,
|
|
"Got no THREAD_STATE when calling watchResources"
|
|
);
|
|
|
|
await commands.threadConfigurationCommand.updateConfiguration({
|
|
pauseOnExceptions: true,
|
|
});
|
|
|
|
info("Reload the page, in order to trigger exception on load");
|
|
const reloaded = reloadBrowser();
|
|
|
|
await waitFor(
|
|
() => availableResources.length == 1,
|
|
"Got the THREAD_STATE related to the debugger statement"
|
|
);
|
|
const threadState = availableResources.pop();
|
|
|
|
assertPausedResource(threadState, {
|
|
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: 27,
|
|
},
|
|
},
|
|
});
|
|
|
|
const { threadFront } = targetCommand.targetFront;
|
|
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);
|
|
|
|
targetCommand.destroy();
|
|
await commands.destroy();
|
|
}
|
|
|
|
async function checkSetBeforeWatch() {
|
|
info(
|
|
"Verify bug 1683139 - D103068, where setting a breakpoint before watching for thread state, avoid receiving the paused state"
|
|
);
|
|
|
|
const tab = await addTab(BREAKPOINT_TEST_URL);
|
|
|
|
const { client, resourceCommand, targetCommand } = await initResourceCommand(
|
|
tab
|
|
);
|
|
|
|
// Instantiate the thread front in order to be able to set a breakpoint before watching for thread state
|
|
info("Attach the top level thread actor");
|
|
await targetCommand.targetFront.attachAndInitThread(targetCommand);
|
|
const { threadFront } = targetCommand.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();
|
|
|
|
// Set the breakpoint before trying to hit it
|
|
await threadFront.setBreakpoint(
|
|
{ sourceUrl: BREAKPOINT_TEST_URL, line: 14, column: 6 },
|
|
{}
|
|
);
|
|
|
|
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();
|
|
});
|
|
|
|
// bug 1683139 - D103068. Re-setting the breakpoint just before watching for thread state
|
|
// prevented to receive the paused state change.
|
|
await threadFront.setBreakpoint(
|
|
{ sourceUrl: BREAKPOINT_TEST_URL, line: 14, column: 6 },
|
|
{}
|
|
);
|
|
|
|
info("Call watchResources");
|
|
const availableResources = [];
|
|
await resourceCommand.watchResources([resourceCommand.TYPES.THREAD_STATE], {
|
|
onAvailable: resources => availableResources.push(...resources),
|
|
});
|
|
|
|
await waitFor(
|
|
() => availableResources.length == 1,
|
|
"Got the THREAD_STATE related to the debugger statement"
|
|
);
|
|
const threadState = availableResources.pop();
|
|
|
|
assertPausedResource(threadState, {
|
|
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);
|
|
|
|
targetCommand.destroy();
|
|
await client.close();
|
|
}
|
|
|
|
async function checkDebuggerStatementInIframes() {
|
|
info("Check whether ResourceCommand gets breakpoint for (remote) iframes");
|
|
|
|
const tab = await addTab(BREAKPOINT_TEST_URL);
|
|
|
|
const { client, resourceCommand, targetCommand } = await initResourceCommand(
|
|
tab
|
|
);
|
|
|
|
info("Call watchResources");
|
|
const availableResources = [];
|
|
await resourceCommand.watchResources([resourceCommand.TYPES.THREAD_STATE], {
|
|
onAvailable: resources => availableResources.push(...resources),
|
|
});
|
|
|
|
is(
|
|
availableResources.length,
|
|
0,
|
|
"Got no THREAD_STATE when calling watchResources"
|
|
);
|
|
|
|
info("Inject the iframe with an inline 'debugger' statement");
|
|
// Note that we do not wait for the resolution of spawn as it will be paused
|
|
SpecialPowers.spawn(
|
|
gBrowser.selectedBrowser,
|
|
[REMOTE_IFRAME_URL],
|
|
async function(url) {
|
|
const iframe = content.document.createElement("iframe");
|
|
iframe.src = url;
|
|
content.document.body.appendChild(iframe);
|
|
}
|
|
);
|
|
|
|
await waitFor(
|
|
() => availableResources.length == 1,
|
|
"Got the THREAD_STATE related to the iframe's debugger statement"
|
|
);
|
|
const threadState = availableResources.pop();
|
|
|
|
assertPausedResource(threadState, {
|
|
state: "paused",
|
|
why: {
|
|
type: "debuggerStatement",
|
|
},
|
|
frame: {
|
|
type: "global",
|
|
asyncCause: null,
|
|
state: "on-stack",
|
|
// this: object actor's form referring to `this` variable
|
|
displayName: "(global)",
|
|
// arguments: []
|
|
where: {
|
|
line: 1,
|
|
column: 8,
|
|
},
|
|
},
|
|
});
|
|
|
|
const iframeTarget = threadState.targetFront;
|
|
if (isFissionEnabled() || isEveryFrameTargetEnabled()) {
|
|
is(
|
|
iframeTarget.url,
|
|
REMOTE_IFRAME_URL,
|
|
"With fission/EFT, the pause is from the iframe's target"
|
|
);
|
|
} else {
|
|
is(
|
|
iframeTarget,
|
|
targetCommand.targetFront,
|
|
"Without fission/EFT, the pause is from the top level target"
|
|
);
|
|
}
|
|
const { threadFront } = iframeTarget;
|
|
|
|
await threadFront.resume();
|
|
|
|
await waitFor(
|
|
() => availableResources.length == 1,
|
|
"Wait until we receive the resumed event"
|
|
);
|
|
|
|
const resumed = availableResources.pop();
|
|
|
|
assertResumedResource(resumed);
|
|
|
|
targetCommand.destroy();
|
|
await client.close();
|
|
}
|
|
|
|
async function assertPausedResource(resource, expected) {
|
|
is(
|
|
resource.resourceType,
|
|
ResourceCommand.TYPES.THREAD_STATE,
|
|
"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,
|
|
ResourceCommand.TYPES.THREAD_STATE,
|
|
"Resource type is correct"
|
|
);
|
|
is(resource.state, "resumed", "state attribute is correct");
|
|
}
|