browser_browser_toolbox_debugger.js (7545B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 // This test asserts that the new debugger works from the browser toolbox process 5 6 // There are shutdown issues for which multiple rejections are left uncaught. 7 // See bug 1018184 for resolving these issues. 8 const { PromiseTestUtils } = ChromeUtils.importESModule( 9 "resource://testing-common/PromiseTestUtils.sys.mjs" 10 ); 11 PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); 12 13 // On debug test runner, it takes about 50s to run the test. 14 requestLongerTimeout(4); 15 16 /* eslint-disable mozilla/no-arbitrary-setTimeout */ 17 18 const { fetch } = require("resource://devtools/shared/DevToolsUtils.js"); 19 20 const debuggerHeadURL = 21 CHROME_URL_ROOT + "../../../debugger/test/mochitest/shared-head.js"; 22 23 add_task(async function runTest() { 24 let { content: debuggerHead } = await fetch(debuggerHeadURL); 25 26 // We remove its import of shared-head, which isn't available in browser toolbox process 27 // And isn't needed thanks to testHead's symbols 28 debuggerHead = debuggerHead.replace( 29 /Services.scriptloader.loadSubScript[^\)]*\);/g, 30 "" 31 ); 32 33 await pushPref("devtools.browsertoolbox.scope", "everything"); 34 35 const ToolboxTask = await initBrowserToolboxTask(); 36 await ToolboxTask.importFunctions({ 37 // head.js uses this method 38 registerCleanupFunction: () => {}, 39 waitForDispatch, 40 waitUntil, 41 }); 42 await ToolboxTask.importScript(debuggerHead); 43 44 info("### First test breakpoint in the parent process script"); 45 const s = Cu.Sandbox("http://mozilla.org"); 46 47 // Use a unique id for the fake script name in order to be able to run 48 // this test more than once. That's because the Sandbox is not immediately 49 // destroyed and so the debugger would display only one file but not necessarily 50 // connected to the latest sandbox. 51 const id = new Date().getTime(); 52 53 // Pass a fake URL to evalInSandbox. If we just pass a filename, 54 // Debugger is going to fail and only display root folder (`/`) listing. 55 // But it won't try to fetch this url and use sandbox content as expected. 56 const testUrl = `http://mozilla.org/browser-toolbox-test-${id}.js`; 57 Cu.evalInSandbox( 58 `this.plop = function plop() { 59 const foo = 1; 60 return foo; 61 };`, 62 s, 63 "1.8", 64 testUrl, 65 0 66 ); 67 68 // Execute the function every second in order to trigger the breakpoint 69 const interval = setInterval(s.plop, 1000); 70 71 await ToolboxTask.spawn(testUrl, async _testUrl => { 72 /* global gToolbox, createDebuggerContext, waitForSources, waitForPaused, 73 addBreakpoint, assertPausedAtSourceAndLine, stepIn, findSource, 74 removeBreakpoint, resume, selectSource, assertNotPaused, assertBreakpoint, 75 assertTextContentOnLine, waitForResumed */ 76 Services.prefs.clearUserPref("devtools.debugger.tabs"); 77 Services.prefs.clearUserPref("devtools.debugger.pending-selected-location"); 78 79 info("Waiting for debugger load"); 80 await gToolbox.selectTool("jsdebugger"); 81 const dbg = createDebuggerContext(gToolbox); 82 83 await waitForSources(dbg, _testUrl); 84 85 info("Loaded, selecting the test script to debug"); 86 const fileName = _testUrl.match(/browser-toolbox-test.*\.js/)[0]; 87 await selectSource(dbg, fileName); 88 89 info("Add a breakpoint and wait to be paused"); 90 const onPaused = waitForPaused(dbg); 91 await addBreakpoint(dbg, fileName, 2); 92 await onPaused; 93 94 const source = findSource(dbg, fileName); 95 await assertPausedAtSourceAndLine(dbg, source.id, 2); 96 assertTextContentOnLine(dbg, 2, "const foo = 1;"); 97 is( 98 dbg.selectors.getBreakpointCount(), 99 1, 100 "There is exactly one breakpoint" 101 ); 102 103 await stepIn(dbg); 104 105 await assertPausedAtSourceAndLine(dbg, source.id, 3); 106 assertTextContentOnLine(dbg, 3, "return foo;"); 107 is( 108 dbg.selectors.getBreakpointCount(), 109 1, 110 "We still have only one breakpoint after step-in" 111 ); 112 113 // Remove the breakpoint before resuming in order to prevent hitting the breakpoint 114 // again during test closing. 115 await removeBreakpoint(dbg, source.id, 2); 116 117 await resume(dbg); 118 119 // Let a change for the interval to re-execute 120 await new Promise(r => setTimeout(r, 1000)); 121 122 is(dbg.selectors.getBreakpointCount(), 0, "There is no more breakpoints"); 123 124 assertNotPaused(dbg); 125 }); 126 127 clearInterval(interval); 128 129 info("### Now test breakpoint in a privileged content process script"); 130 const testUrl2 = `http://mozilla.org/content-process-test-${id}.js`; 131 await SpecialPowers.spawn(gBrowser.selectedBrowser, [testUrl2], testUrl => { 132 // Use a sandbox in order to have a URL to set a breakpoint 133 const s = Cu.Sandbox("http://mozilla.org"); 134 Cu.evalInSandbox( 135 `this.foo = function foo() { 136 const plop = 1; 137 return plop; 138 };`, 139 s, 140 "1.8", 141 testUrl, 142 0 143 ); 144 content.interval = content.setInterval(s.foo, 1000); 145 }); 146 await ToolboxTask.spawn(testUrl2, async _testUrl => { 147 const dbg = createDebuggerContext(gToolbox); 148 149 const fileName = _testUrl.match(/content-process-test.*\.js/)[0]; 150 await waitForSources(dbg, _testUrl); 151 152 await selectSource(dbg, fileName); 153 154 const onPaused = waitForPaused(dbg); 155 await addBreakpoint(dbg, fileName, 2); 156 await onPaused; 157 158 const source = findSource(dbg, fileName); 159 await assertPausedAtSourceAndLine(dbg, source.id, 2); 160 assertTextContentOnLine(dbg, 2, "const plop = 1;"); 161 await assertBreakpoint(dbg, 2); 162 is(dbg.selectors.getBreakpointCount(), 1, "We have exactly one breakpoint"); 163 164 await stepIn(dbg); 165 166 await assertPausedAtSourceAndLine(dbg, source.id, 3); 167 assertTextContentOnLine(dbg, 3, "return plop;"); 168 is( 169 dbg.selectors.getBreakpointCount(), 170 1, 171 "We still have only one breakpoint after step-in" 172 ); 173 174 // Remove the breakpoint before resuming in order to prevent hitting the breakpoint 175 // again during test closing. 176 await removeBreakpoint(dbg, source.id, 2); 177 178 await resume(dbg); 179 180 // Let a change for the interval to re-execute 181 await new Promise(r => setTimeout(r, 1000)); 182 183 is(dbg.selectors.getBreakpointCount(), 0, "There is no more breakpoints"); 184 185 assertNotPaused(dbg); 186 }); 187 188 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 189 content.clearInterval(content.interval); 190 }); 191 192 info("Trying pausing in a content process that crashes"); 193 194 const crashingUrl = 195 "data:text/html,<script>setTimeout(()=>{debugger;})</script>"; 196 const crashingTab = await addTab(crashingUrl); 197 await ToolboxTask.spawn(crashingUrl, async url => { 198 const dbg = createDebuggerContext(gToolbox); 199 await waitForPaused(dbg); 200 const source = findSource(dbg, url); 201 await assertPausedAtSourceAndLine(dbg, source.id, 1); 202 const thread = dbg.selectors.getThread(dbg.selectors.getCurrentThread()); 203 is(thread.isTopLevel, false, "The current thread is not the top level one"); 204 is(thread.targetType, "process", "The current thread is the tab one"); 205 }); 206 207 info( 208 "Crash the tab and ensure the debugger resumes and switch to the main thread" 209 ); 210 await BrowserTestUtils.crashFrame(crashingTab.linkedBrowser); 211 212 await ToolboxTask.spawn(null, async () => { 213 const dbg = createDebuggerContext(gToolbox); 214 await waitForResumed(dbg); 215 const thread = dbg.selectors.getThread(dbg.selectors.getCurrentThread()); 216 is(thread.isTopLevel, true, "The current thread is the top level one"); 217 }); 218 219 await removeTab(crashingTab); 220 221 await ToolboxTask.destroy(); 222 });