/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ // This test asserts that the new debugger works from the browser toolbox process // There are shutdown issues for which multiple rejections are left uncaught. // See bug 1018184 for resolving these issues. const { PromiseTestUtils } = ChromeUtils.importESModule( "resource://testing-common/PromiseTestUtils.sys.mjs" ); PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); // On debug test runner, it takes about 50s to run the test. requestLongerTimeout(4); /* eslint-disable mozilla/no-arbitrary-setTimeout */ const { fetch } = require("resource://devtools/shared/DevToolsUtils.js"); const debuggerHeadURL = CHROME_URL_ROOT + "../../../debugger/test/mochitest/shared-head.js"; add_task(async function runTest() { let { content: debuggerHead } = await fetch(debuggerHeadURL); // We remove its import of shared-head, which isn't available in browser toolbox process // And isn't needed thanks to testHead's symbols debuggerHead = debuggerHead.replace( /Services.scriptloader.loadSubScript[^\)]*\);/g, "" ); await pushPref("devtools.browsertoolbox.scope", "everything"); const ToolboxTask = await initBrowserToolboxTask(); await ToolboxTask.importFunctions({ // head.js uses this method registerCleanupFunction: () => {}, waitForDispatch, waitUntil, }); await ToolboxTask.importScript(debuggerHead); info("### First test breakpoint in the parent process script"); const s = Cu.Sandbox("http://mozilla.org"); // Use a unique id for the fake script name in order to be able to run // this test more than once. That's because the Sandbox is not immediately // destroyed and so the debugger would display only one file but not necessarily // connected to the latest sandbox. const id = new Date().getTime(); // Pass a fake URL to evalInSandbox. If we just pass a filename, // Debugger is going to fail and only display root folder (`/`) listing. // But it won't try to fetch this url and use sandbox content as expected. const testUrl = `http://mozilla.org/browser-toolbox-test-${id}.js`; Cu.evalInSandbox( `this.plop = function plop() { const foo = 1; return foo; };`, s, "1.8", testUrl, 0 ); // Execute the function every second in order to trigger the breakpoint const interval = setInterval(s.plop, 1000); await ToolboxTask.spawn(testUrl, async _testUrl => { /* global gToolbox, createDebuggerContext, waitForSources, waitForPaused, addBreakpoint, assertPausedAtSourceAndLine, stepIn, findSource, removeBreakpoint, resume, selectSource, assertNotPaused, assertBreakpoint, assertTextContentOnLine, waitForResumed */ Services.prefs.clearUserPref("devtools.debugger.tabs"); Services.prefs.clearUserPref("devtools.debugger.pending-selected-location"); info("Waiting for debugger load"); await gToolbox.selectTool("jsdebugger"); const dbg = createDebuggerContext(gToolbox); await waitForSources(dbg, _testUrl); info("Loaded, selecting the test script to debug"); const fileName = _testUrl.match(/browser-toolbox-test.*\.js/)[0]; await selectSource(dbg, fileName); info("Add a breakpoint and wait to be paused"); const onPaused = waitForPaused(dbg); await addBreakpoint(dbg, fileName, 2); await onPaused; const source = findSource(dbg, fileName); assertPausedAtSourceAndLine(dbg, source.id, 2); assertTextContentOnLine(dbg, 2, "const foo = 1;"); is( dbg.selectors.getBreakpointCount(), 1, "There is exactly one breakpoint" ); await stepIn(dbg); assertPausedAtSourceAndLine(dbg, source.id, 3); assertTextContentOnLine(dbg, 3, "return foo;"); is( dbg.selectors.getBreakpointCount(), 1, "We still have only one breakpoint after step-in" ); // Remove the breakpoint before resuming in order to prevent hitting the breakpoint // again during test closing. await removeBreakpoint(dbg, source.id, 2); await resume(dbg); // Let a change for the interval to re-execute await new Promise(r => setTimeout(r, 1000)); is(dbg.selectors.getBreakpointCount(), 0, "There is no more breakpoints"); assertNotPaused(dbg); }); clearInterval(interval); info("### Now test breakpoint in a privileged content process script"); const testUrl2 = `http://mozilla.org/content-process-test-${id}.js`; await SpecialPowers.spawn(gBrowser.selectedBrowser, [testUrl2], testUrl => { // Use a sandbox in order to have a URL to set a breakpoint const s = Cu.Sandbox("http://mozilla.org"); Cu.evalInSandbox( `this.foo = function foo() { const plop = 1; return plop; };`, s, "1.8", testUrl, 0 ); content.interval = content.setInterval(s.foo, 1000); }); await ToolboxTask.spawn(testUrl2, async _testUrl => { const dbg = createDebuggerContext(gToolbox); const fileName = _testUrl.match(/content-process-test.*\.js/)[0]; await waitForSources(dbg, _testUrl); await selectSource(dbg, fileName); const onPaused = waitForPaused(dbg); await addBreakpoint(dbg, fileName, 2); await onPaused; const source = findSource(dbg, fileName); assertPausedAtSourceAndLine(dbg, source.id, 2); assertTextContentOnLine(dbg, 2, "const plop = 1;"); await assertBreakpoint(dbg, 2); is(dbg.selectors.getBreakpointCount(), 1, "We have exactly one breakpoint"); await stepIn(dbg); assertPausedAtSourceAndLine(dbg, source.id, 3); assertTextContentOnLine(dbg, 3, "return plop;"); is( dbg.selectors.getBreakpointCount(), 1, "We still have only one breakpoint after step-in" ); // Remove the breakpoint before resuming in order to prevent hitting the breakpoint // again during test closing. await removeBreakpoint(dbg, source.id, 2); await resume(dbg); // Let a change for the interval to re-execute await new Promise(r => setTimeout(r, 1000)); is(dbg.selectors.getBreakpointCount(), 0, "There is no more breakpoints"); assertNotPaused(dbg); }); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { content.clearInterval(content.interval); }); info("Trying pausing in a content process that crashes"); const crashingUrl = "data:text/html,"; const crashingTab = await addTab(crashingUrl); await ToolboxTask.spawn(crashingUrl, async url => { const dbg = createDebuggerContext(gToolbox); await waitForPaused(dbg); const source = findSource(dbg, url); assertPausedAtSourceAndLine(dbg, source.id, 1); const thread = dbg.selectors.getThread(dbg.selectors.getCurrentThread()); is(thread.isTopLevel, false, "The current thread is not the top level one"); is(thread.targetType, "process", "The current thread is the tab one"); }); info( "Crash the tab and ensure the debugger resumes and switch to the main thread" ); await BrowserTestUtils.crashFrame(crashingTab.linkedBrowser); await ToolboxTask.spawn(null, async () => { const dbg = createDebuggerContext(gToolbox); await waitForResumed(dbg); const thread = dbg.selectors.getThread(dbg.selectors.getCurrentThread()); is(thread.isTopLevel, true, "The current thread is the top level one"); }); await removeTab(crashingTab); await ToolboxTask.destroy(); });