diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/shared/commands/resource/tests/browser_resources_thread_states.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/shared/commands/resource/tests/browser_resources_thread_states.js')
-rw-r--r-- | devtools/shared/commands/resource/tests/browser_resources_thread_states.js | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/devtools/shared/commands/resource/tests/browser_resources_thread_states.js b/devtools/shared/commands/resource/tests/browser_resources_thread_states.js new file mode 100644 index 0000000000..f915bb14d0 --- /dev/null +++ b/devtools/shared/commands/resource/tests/browser_resources_thread_states.js @@ -0,0 +1,557 @@ +/* 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"); +} |