diff options
Diffstat (limited to 'devtools/client/debugger/new/test')
55 files changed, 2422 insertions, 0 deletions
diff --git a/devtools/client/debugger/new/test/mochitest/.eslintrc b/devtools/client/debugger/new/test/mochitest/.eslintrc new file mode 100644 index 000000000..017b921f8 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/.eslintrc @@ -0,0 +1,80 @@ +{ + "globals": { + "add_task": false, + "Assert": false, + "BrowserTestUtils": false, + "content": false, + "ContentTask": false, + "ContentTaskUtils": false, + "EventUtils": false, + "executeSoon": false, + "expectUncaughtException": false, + "export_assertions": false, + "extractJarToTmp": false, + "finish": false, + "getJar": false, + "getRootDirectory": false, + "getTestFilePath": false, + "gBrowser": false, + "gTestPath": false, + "info": false, + "is": false, + "isnot": false, + "ok": false, + "registerCleanupFunction": false, + "requestLongerTimeout": false, + "SimpleTest": false, + "SpecialPowers": false, + "TestUtils": false, + "thisTestLeaksUncaughtRejectionsAndShouldBeFixed": false, + "todo": false, + "todo_is": false, + "todo_isnot": false, + "waitForClipboard": false, + "waitForExplicitFinish": false, + "waitForFocus": false, + + // Globals introduced in debugger-specific head.js + "promise": false, + "BrowserToolboxProcess": false, + "OS": false, + "waitForNextDispatch": false, + "waitForDispatch": false, + "waitForThreadEvents": false, + "waitForState": false, + "waitForElement": false, + "waitForPaused": false, + "waitForSources": false, + "isPaused": false, + "assertPausedLocation": false, + "assertHighlightLocation": false, + "createDebuggerContext": false, + "initDebugger": false, + "invokeInTab": false, + "findSource": false, + "findElement": false, + "findElementWithSelector": false, + "findAllElements": false, + "openNewTabAndToolbox": false, + "selectSource": false, + "stepOver": false, + "stepIn": false, + "stepOut": false, + "resume": false, + "reload": false, + "navigate": false, + "removeBreakpoint": false, + "addBreakpoint": false, + "toggleCallStack": false, + "toggleScopes": false, + "isVisibleWithin": false, + "clickElement": false, + "rightClickElement": false, + "selectMenuItem": false, + "togglePauseOnExceptions": false, + "type": false, + "pressKey": false, + "EXAMPLE_URL": false, + "waitUntil": false + } +} diff --git a/devtools/client/debugger/new/test/mochitest/browser.ini b/devtools/client/debugger/new/test/mochitest/browser.ini new file mode 100644 index 000000000..d0e40a4a7 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser.ini @@ -0,0 +1,60 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +skip-if = (os == 'linux' && debug && bits == 32) +support-files = + head.js + !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js + examples/bundle.js + examples/bundle.js.map + examples/doc-scripts.html + examples/doc-script-switching.html + examples/doc-exceptions.html + examples/doc-iframes.html + examples/doc-frames.html + examples/doc-debugger-statements.html + examples/doc-minified.html + examples/doc-sourcemaps.html + examples/doc-sourcemap-bogus.html + examples/doc-sources.html + examples/bogus-map.js + examples/entry.js + examples/exceptions.js + examples/long.js + examples/math.min.js + examples/nested/nested-source.js + examples/opts.js + examples/output.js + examples/simple1.js + examples/simple2.js + examples/frames.js + examples/script-switching-02.js + examples/script-switching-01.js + examples/times2.js + +[browser_dbg-breaking.js] +[browser_dbg-breaking-from-console.js] +[browser_dbg-breakpoints.js] +[browser_dbg-breakpoints-cond.js] +[browser_dbg-call-stack.js] +[browser_dbg-scopes.js] +[browser_dbg-chrome-create.js] +[browser_dbg-chrome-debugging.js] +[browser_dbg-console.js] +[browser_dbg-debugger-buttons.js] +[browser_dbg-editor-gutter.js] +[browser_dbg-editor-mode.js] +[browser_dbg-editor-select.js] +[browser_dbg-editor-highlight.js] +[browser_dbg-iframes.js] +[browser_dbg_keyboard-shortcuts.js] +[browser_dbg-pause-exceptions.js] +[browser_dbg-navigation.js] +[browser_dbg-pretty-print.js] +[browser_dbg-pretty-print-paused.js] +[browser_dbg-searching.js] +skip-if = true +[browser_dbg-sourcemaps.js] +[browser_dbg-sourcemaps-bogus.js] +[browser_dbg-sources.js]
\ No newline at end of file diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-breaking-from-console.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-breaking-from-console.js new file mode 100644 index 000000000..8005b518d --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breaking-from-console.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that `debugger` statements are hit before the debugger even +// initializes and it properly highlights the right location in the +// debugger. + +add_task(function* () { + const url = EXAMPLE_URL + "doc-script-switching.html"; + const toolbox = yield openNewTabAndToolbox(url, "webconsole"); + + // Type "debugger" into console + let jsterm = toolbox.getPanel("webconsole").hud.jsterm; + jsterm.execute("debugger"); + + // Wait for the debugger to be selected and make sure it's paused + yield new Promise((resolve) => { + toolbox.on("jsdebugger-selected", resolve); + }); + is(toolbox.threadClient.state, "paused"); + + // Create a dbg context + const dbg = createDebuggerContext(toolbox); + const { selectors: { getSelectedSource }, getState } = dbg; + + // Make sure the thread is paused in the right source and location + yield waitForDispatch(dbg, "LOAD_SOURCE_TEXT"); + is(dbg.win.cm.getValue(), "debugger"); + const source = getSelectedSource(getState()).toJS(); + assertPausedLocation(dbg, source, 1); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-breaking.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-breaking.js new file mode 100644 index 000000000..8994897c4 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breaking.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests the breakpoints are hit in various situations. + +add_task(function* () { + const dbg = yield initDebugger("doc-scripts.html"); + const { selectors: { getSelectedSource }, getState } = dbg; + + // Make sure we can set a top-level breakpoint and it will be hit on + // reload. + yield addBreakpoint(dbg, "scripts.html", 18); + reload(dbg); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "scripts.html", 18); + yield resume(dbg); + + const paused = waitForPaused(dbg); + + // Create an eval script that pauses itself. + invokeInTab("doEval"); + + yield paused; + yield resume(dbg); + const source = getSelectedSource(getState()).toJS(); + ok(!source.url, "It is an eval source"); + + yield addBreakpoint(dbg, source, 5); + invokeInTab("evaledFunc"); + yield waitForPaused(dbg); + assertPausedLocation(dbg, source, 5); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js new file mode 100644 index 000000000..b6f7fb021 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function findBreakpoint(dbg, url, line) { + const { selectors: { getBreakpoint }, getState } = dbg; + const source = findSource(dbg, url); + return getBreakpoint(getState(), { sourceId: source.id, line }); +} + +function setConditionalBreakpoint(dbg, index, condition) { + return Task.spawn(function* () { + rightClickElement(dbg, "gutter", index); + selectMenuItem(dbg, 2); + yield waitForElement(dbg, ".conditional-breakpoint-panel input"); + findElementWithSelector(dbg, ".conditional-breakpoint-panel input").focus(); + type(dbg, condition); + pressKey(dbg, "Enter"); + }); +} + +add_task(function* () { + const dbg = yield initDebugger("doc-scripts.html"); + yield selectSource(dbg, "simple2"); + + // Adding a conditional Breakpoint + yield setConditionalBreakpoint(dbg, 5, "1"); + yield waitForDispatch(dbg, "ADD_BREAKPOINT"); + let bp = findBreakpoint(dbg, "simple2", 5); + is(bp.condition, "1", "breakpoint is created with the condition"); + + // Editing a conditional Breakpoint + yield setConditionalBreakpoint(dbg, 5, "2"); + yield waitForDispatch(dbg, "SET_BREAKPOINT_CONDITION"); + bp = findBreakpoint(dbg, "simple2", 5); + is(bp.condition, "21", "breakpoint is created with the condition"); + + // Removing a conditional breakpoint + clickElement(dbg, "gutter", 5); + yield waitForDispatch(dbg, "REMOVE_BREAKPOINT"); + bp = findBreakpoint(dbg, "simple2", 5); + is(bp, null, "breakpoint was removed"); + + // Adding a condition to a breakpoint + clickElement(dbg, "gutter", 5); + yield waitForDispatch(dbg, "ADD_BREAKPOINT"); + yield setConditionalBreakpoint(dbg, 5, "1"); + bp = findBreakpoint(dbg, "simple2", 5); + is(bp.condition, "1", "breakpoint is created with the condition"); +}); + diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints.js new file mode 100644 index 000000000..10bf44957 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints.js @@ -0,0 +1,101 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function toggleBreakpoint(dbg, index) { + const bp = findElement(dbg, "breakpointItem", index); + const input = bp.querySelector("input"); + input.click(); +} + +function removeBreakpoint(dbg, index) { + return Task.spawn(function* () { + const bp = findElement(dbg, "breakpointItem", index); + bp.querySelector(".close-btn").click(); + yield waitForDispatch(dbg, "REMOVE_BREAKPOINT"); + }); +} + +function disableBreakpoint(dbg, index) { + return Task.spawn(function* () { + toggleBreakpoint(dbg, index); + yield waitForDispatch(dbg, "REMOVE_BREAKPOINT"); + }); +} + +function enableBreakpoint(dbg, index) { + return Task.spawn(function* () { + toggleBreakpoint(dbg, index); + yield waitForDispatch(dbg, "ADD_BREAKPOINT"); + }); +} + +function toggleBreakpoints(dbg) { + return Task.spawn(function* () { + const btn = findElement(dbg, "toggleBreakpoints"); + btn.click(); + yield waitForDispatch(dbg, "TOGGLE_BREAKPOINTS"); + }); +} + +function findBreakpoint(dbg, url, line) { + const { selectors: { getBreakpoint }, getState } = dbg; + const source = findSource(dbg, url); + return getBreakpoint(getState(), { sourceId: source.id, line }); +} + +function findBreakpoints(dbg) { + const { selectors: { getBreakpoints }, getState } = dbg; + return getBreakpoints(getState()); +} + +add_task(function* () { + const dbg = yield initDebugger("doc-scripts.html"); + + // Create two breakpoints + yield selectSource(dbg, "simple2"); + yield addBreakpoint(dbg, "simple2", 3); + yield addBreakpoint(dbg, "simple2", 5); + + // Disable the first one + yield disableBreakpoint(dbg, 1); + let bp1 = findBreakpoint(dbg, "simple2", 3); + let bp2 = findBreakpoint(dbg, "simple2", 5); + is(bp1.disabled, true, "first breakpoint is disabled"); + is(bp2.disabled, false, "second breakpoint is enabled"); + + // Disable and Re-Enable the second one + yield disableBreakpoint(dbg, 2); + yield enableBreakpoint(dbg, 2); + bp2 = findBreakpoint(dbg, "simple2", 5); + is(bp2.disabled, false, "second breakpoint is enabled"); +}); + +// toggle all +add_task(function* () { + const dbg = yield initDebugger("doc-scripts.html"); + + // Create two breakpoints + yield selectSource(dbg, "simple2"); + yield addBreakpoint(dbg, "simple2", 3); + yield addBreakpoint(dbg, "simple2", 5); + + // Disable all of the breakpoints + yield toggleBreakpoints(dbg); + let bp1 = findBreakpoint(dbg, "simple2", 3); + let bp2 = findBreakpoint(dbg, "simple2", 5); + is(bp1.disabled, true, "first breakpoint is disabled"); + is(bp2.disabled, true, "second breakpoint is disabled"); + + // Enable all of the breakpoints + yield toggleBreakpoints(dbg); + bp1 = findBreakpoint(dbg, "simple2", 3); + bp2 = findBreakpoint(dbg, "simple2", 5); + is(bp1.disabled, false, "first breakpoint is enabled"); + is(bp2.disabled, false, "second breakpoint is enabled"); + + // Remove the breakpoints + yield removeBreakpoint(dbg, 1); + yield removeBreakpoint(dbg, 1); + const bps = findBreakpoints(dbg); + is(bps.size, 0, "breakpoints are removed"); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-call-stack.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-call-stack.js new file mode 100644 index 000000000..54a401eeb --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-call-stack.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// checks to see if the frame is selected and the title is correct +function isFrameSelected(dbg, index, title) { + const $frame = findElement(dbg, "frame", index); + const frame = dbg.selectors.getSelectedFrame(dbg.getState()); + + const elSelected = $frame.classList.contains("selected"); + const titleSelected = frame.displayName == title; + + return elSelected && titleSelected; +} + +function toggleButton(dbg) { + const callStackBody = findElement(dbg, "callStackBody"); + return callStackBody.querySelector(".show-more"); +} + +add_task(function* () { + const dbg = yield initDebugger("doc-script-switching.html"); + + toggleCallStack(dbg); + + const notPaused = findElement(dbg, "callStackBody").innerText; + is(notPaused, "Not Paused", "Not paused message is shown"); + + invokeInTab("firstCall"); + yield waitForPaused(dbg); + + ok(isFrameSelected(dbg, 1, "secondCall"), "the first frame is selected"); + + clickElement(dbg, "frame", 2); + ok(isFrameSelected(dbg, 2, "firstCall"), "the second frame is selected"); + + let button = toggleButton(dbg); + ok(!button, "toggle button shouldn't be there"); +}); + +add_task(function* () { + const dbg = yield initDebugger("doc-frames.html"); + + toggleCallStack(dbg); + + invokeInTab("startRecursion"); + yield waitForPaused(dbg); + + ok(isFrameSelected(dbg, 1, "recurseA"), "the first frame is selected"); + + // check to make sure that the toggle button isn't there + let button = toggleButton(dbg); + let frames = findAllElements(dbg, "frames"); + is(button.innerText, "Expand Rows", "toggle button should be expand"); + is(frames.length, 7, "There should be at most seven frames"); + + button.click(); + + button = toggleButton(dbg); + frames = findAllElements(dbg, "frames"); + is(button.innerText, "Collapse Rows", "toggle button should be collapse"); + is(frames.length, 22, "All of the frames should be shown"); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js new file mode 100644 index 000000000..a2d88c064 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js @@ -0,0 +1,72 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that a chrome debugger can be created in a new process. + */ + +const { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {}); +let gProcess = undefined; + +function initChromeDebugger() { + info("Initializing a chrome debugger process."); + return new Promise(resolve => { + BrowserToolboxProcess.init(onClose, (event, _process) => { + info("Browser toolbox process started successfully."); + resolve(_process); + }); + }); +} + +function onClose() { + ok(!gProcess._dbgProcess.isRunning, + "The remote debugger process isn't closed as it should be!"); + is(gProcess._dbgProcess.exitValue, (Services.appinfo.OS == "WINNT" ? 0 : 256), + "The remote debugger process didn't die cleanly."); + + info("process exit value: " + gProcess._dbgProcess.exitValue); + + info("profile path: " + gProcess._dbgProfilePath); + + finish(); +} + +registerCleanupFunction(function() { + Services.prefs.clearUserPref("devtools.debugger.remote-enabled"); + gProcess = null; +}); + +add_task(function* () { + // Windows XP and 8.1 test slaves are terribly slow at this test. + requestLongerTimeout(5); + Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true); + + gProcess = yield initChromeDebugger(); + + ok(gProcess._dbgProcess, + "The remote debugger process wasn't created properly!"); + ok(gProcess._dbgProcess.isRunning, + "The remote debugger process isn't running!"); + is(typeof gProcess._dbgProcess.pid, "number", + "The remote debugger process doesn't have a pid (?!)"); + + info("process location: " + gProcess._dbgProcess.location); + info("process pid: " + gProcess._dbgProcess.pid); + info("process name: " + gProcess._dbgProcess.processName); + info("process sig: " + gProcess._dbgProcess.processSignature); + + ok(gProcess._dbgProfilePath, + "The remote debugger profile wasn't created properly!"); + + is( + gProcess._dbgProfilePath, + OS.Path.join(OS.Constants.Path.profileDir, "chrome_debugger_profile"), + "The remote debugger profile isn't where we expect it!" + ); + + info("profile path: " + gProcess._dbgProfilePath); + + gProcess.close(); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js new file mode 100644 index 000000000..3933c919b --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js @@ -0,0 +1,88 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that chrome debugging works. + */ + +var gClient, gThreadClient; +var gNewGlobal = promise.defer(); +var gNewChromeSource = promise.defer(); + +var { DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {}); +var customLoader = new DevToolsLoader(); +customLoader.invisibleToDebugger = true; +var { DebuggerServer } = customLoader.require("devtools/server/main"); +var { DebuggerClient } = require("devtools/shared/client/main"); + +function initDebuggerClient() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + DebuggerServer.allowChromeProcess = true; + + let transport = DebuggerServer.connectPipe(); + return new DebuggerClient(transport); +} + +function attachThread(client, actor) { + return new Promise(resolve => { + client.attachTab(actor, (response, tabClient) => { + tabClient.attachThread(null, (r, threadClient) => { + resolve(threadClient); + }); + }); + }); +} + +function onNewGlobal() { + ok(true, "Received a new chrome global."); + gClient.removeListener("newGlobal", onNewGlobal); + gNewGlobal.resolve(); +} + +function onNewSource(event, packet) { + if (packet.source.url.startsWith("chrome:")) { + ok(true, "Received a new chrome source: " + packet.source.url); + gThreadClient.removeListener("newSource", onNewSource); + gNewChromeSource.resolve(); + } +} + +function resumeAndCloseConnection() { + return new Promise(resolve => { + gThreadClient.resume(() => resolve(gClient.close())); + }); +} + +registerCleanupFunction(function() { + gClient = null; + gThreadClient = null; + gNewGlobal = null; + gNewChromeSource = null; + + customLoader = null; + DebuggerServer = null; +}); + +add_task(function* () { + gClient = initDebuggerClient(); + + const [type] = yield gClient.connect(); + is(type, "browser", "Root actor should identify itself as a browser."); + + const response = yield gClient.getProcess(); + let actor = response.form.actor; + gThreadClient = yield attachThread(gClient, actor); + gBrowser.selectedTab = gBrowser.addTab("about:mozilla"); + + // listen for a new source and global + gThreadClient.addListener("newSource", onNewSource); + gClient.addListener("newGlobal", onNewGlobal); + yield promise.all([ gNewGlobal.promise, gNewChromeSource.promise ]); + + yield resumeAndCloseConnection(); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-console.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-console.js new file mode 100644 index 000000000..c57103663 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-console.js @@ -0,0 +1,34 @@ +// Return a promise with a reference to jsterm, opening the split +// console if necessary. This cleans up the split console pref so +// it won't pollute other tests. +function getSplitConsole(dbg) { + const { toolbox, win } = dbg; + + registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled"); + }); + + if (!win) { + win = toolbox.win; + } + + if (!toolbox.splitConsole) { + EventUtils.synthesizeKey("VK_ESCAPE", {}, win); + } + + return new Promise(resolve => { + toolbox.getPanelWhenReady("webconsole").then(() => { + ok(toolbox.splitConsole, "Split console is shown."); + let jsterm = toolbox.getPanel("webconsole").hud.jsterm; + resolve(jsterm); + }); + }); +} + +add_task(function* () { + Services.prefs.setBoolPref("devtools.toolbox.splitconsoleEnabled", true); + const dbg = yield initDebugger("doc-script-switching.html"); + + yield getSplitConsole(dbg); + ok(dbg.toolbox.splitConsole, "Split console is shown."); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-debugger-buttons.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-debugger-buttons.js new file mode 100644 index 000000000..0094650bc --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-debugger-buttons.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function clickStepOver(dbg) { + clickElement(dbg, "stepOver"); + return waitForPaused(dbg); +} + +function clickStepIn(dbg) { + clickElement(dbg, "stepIn"); + return waitForPaused(dbg); +} + +function clickStepOut(dbg) { + clickElement(dbg, "stepOut"); + return waitForPaused(dbg); +} + +/** + * Test debugger buttons + * 1. resume + * 2. stepOver + * 3. stepIn + * 4. stepOver to the end of a function + * 5. stepUp at the end of a function + */ +add_task(function* () { + const dbg = yield initDebugger("doc-debugger-statements.html"); + + yield reload(dbg); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 8); + + // resume + clickElement(dbg, "resume"); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 12); + + // step over + yield clickStepOver(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 13); + + // step into + yield clickStepIn(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 18); + + // step over + yield clickStepOver(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 20); + + // step out + yield clickStepOut(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 20); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-gutter.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-gutter.js new file mode 100644 index 000000000..12a771c31 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-gutter.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests the breakpoint gutter and making sure breakpoint icons exist +// correctly + +// Utilities for interacting with the editor +function clickGutter(dbg, line) { + clickElement(dbg, "gutter", line); +} + +function getLineEl(dbg, line) { + const lines = dbg.win.document.querySelectorAll(".CodeMirror-code > div"); + return lines[line - 1]; +} + +function assertEditorBreakpoint(dbg, line, shouldExist) { + const exists = !!getLineEl(dbg, line).querySelector(".new-breakpoint"); + ok(exists === shouldExist, + "Breakpoint " + (shouldExist ? "exists" : "does not exist") + + " on line " + line); +} + +add_task(function* () { + const dbg = yield initDebugger("doc-scripts.html"); + const { selectors: { getBreakpoints, getBreakpoint }, getState } = dbg; + const source = findSource(dbg, "simple1.js"); + + yield selectSource(dbg, source.url); + + // Make sure that clicking the gutter creates a breakpoint icon. + clickGutter(dbg, 4); + yield waitForDispatch(dbg, "ADD_BREAKPOINT"); + is(getBreakpoints(getState()).size, 1, "One breakpoint exists"); + assertEditorBreakpoint(dbg, 4, true); + + // Make sure clicking at the same place removes the icon. + clickGutter(dbg, 4); + yield waitForDispatch(dbg, "REMOVE_BREAKPOINT"); + is(getBreakpoints(getState()).size, 0, "No breakpoints exist"); + assertEditorBreakpoint(dbg, 4, false); + + // Test that a breakpoint icon slides down to the correct line. + clickGutter(dbg, 2); + yield waitForDispatch(dbg, "ADD_BREAKPOINT"); + is(getBreakpoints(getState()).size, 1, "One breakpoint exists"); + ok(getBreakpoint(getState(), { sourceId: source.id, line: 4 }), + "Breakpoint has correct line"); + assertEditorBreakpoint(dbg, 2, false); + assertEditorBreakpoint(dbg, 4, true); + + // Do the same sliding and make sure it works if there's already a + // breakpoint. + clickGutter(dbg, 2); + yield waitForDispatch(dbg, "ADD_BREAKPOINT"); + is(getBreakpoints(getState()).size, 1, "One breakpoint exists"); + assertEditorBreakpoint(dbg, 2, false); + assertEditorBreakpoint(dbg, 4, true); + + clickGutter(dbg, 4); + yield waitForDispatch(dbg, "REMOVE_BREAKPOINT"); + is(getBreakpoints(getState()).size, 0, "No breakpoints exist"); + assertEditorBreakpoint(dbg, 4, false); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-highlight.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-highlight.js new file mode 100644 index 000000000..d7892e629 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-highlight.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the editor will always highight the right line, no +// matter if the source text doesn't exist yet or even if the source +// doesn't exist. + +add_task(function* () { + const dbg = yield initDebugger("doc-scripts.html"); + const { selectors: { getSourceText }, getState } = dbg; + const sourceUrl = EXAMPLE_URL + "long.js"; + + // The source itself doesn't even exist yet, and using + // `selectSourceURL` will set a pending request to load this source + // and highlight a specific line. + dbg.actions.selectSourceURL(sourceUrl, { line: 66 }); + + // Wait for the source text to load and make sure we're in the right + // place. + yield waitForDispatch(dbg, "LOAD_SOURCE_TEXT"); + assertHighlightLocation(dbg, "long.js", 66); + + // Jump to line 16 and make sure the editor scrolled. + yield selectSource(dbg, "long.js", 16); + assertHighlightLocation(dbg, "long.js", 16); + + // Make sure only one line is ever highlighted and the flash + // animation is cancelled on old lines. + yield selectSource(dbg, "long.js", 17); + yield selectSource(dbg, "long.js", 18); + assertHighlightLocation(dbg, "long.js", 18); + is(findAllElements(dbg, "highlightLine").length, 1, + "Only 1 line is highlighted"); + + // Test jumping to a line in a source that exists but hasn't been + // loaded yet. + selectSource(dbg, "simple1.js", 6); + + // Make sure the source is in the loading state, wait for it to be + // fully loaded, and check the highlighted line. + const simple1 = findSource(dbg, "simple1.js"); + ok(getSourceText(getState(), simple1.id).get("loading")); + yield waitForDispatch(dbg, "LOAD_SOURCE_TEXT"); + ok(getSourceText(getState(), simple1.id).get("text")); + assertHighlightLocation(dbg, "simple1.js", 6); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-mode.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-mode.js new file mode 100644 index 000000000..2a23aa09f --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-mode.js @@ -0,0 +1,14 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the editor sets the correct mode for different file +// types +add_task(function* () { + const dbg = yield initDebugger("doc-scripts.html"); + + yield selectSource(dbg, "simple1.js"); + is(dbg.win.cm.getOption("mode").name, "javascript", "Mode is correct"); + + yield selectSource(dbg, "doc-scripts.html"); + is(dbg.win.cm.getOption("mode").name, "htmlmixed", "Mode is correct"); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-select.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-select.js new file mode 100644 index 000000000..8b954f899 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-select.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the editor highlights the correct location when the +// debugger pauses + +// checks to see if the first breakpoint is visible +function isElementVisible(dbg, elementName) { + const bpLine = findElement(dbg, elementName); + const cm = findElement(dbg, "codeMirror"); + return bpLine && isVisibleWithin(cm, bpLine); +} + +add_task(function* () { + // This test runs too slowly on linux debug. I'd like to figure out + // which is the slowest part of this and make it run faster, but to + // fix a frequent failure allow a longer timeout. + requestLongerTimeout(2); + + const dbg = yield initDebugger("doc-scripts.html"); + const { selectors: { getSelectedSource }, getState } = dbg; + const simple1 = findSource(dbg, "simple1.js"); + const simple2 = findSource(dbg, "simple2.js"); + + // Set the initial breakpoint. + yield addBreakpoint(dbg, simple1, 4); + ok(!getSelectedSource(getState()), "No selected source"); + + // Call the function that we set a breakpoint in. + invokeInTab("main"); + yield waitForPaused(dbg); + assertPausedLocation(dbg, simple1, 4); + + // Step through to another file and make sure it's paused in the + // right place. + yield stepIn(dbg); + assertPausedLocation(dbg, simple2, 2); + + // Step back out to the initial file. + yield stepOut(dbg); + yield stepOut(dbg); + assertPausedLocation(dbg, simple1, 5); + yield resume(dbg); + + // Make sure that we can set a breakpoint on a line out of the + // viewport, and that pausing there scrolls the editor to it. + let longSrc = findSource(dbg, "long.js"); + yield addBreakpoint(dbg, longSrc, 66); + + invokeInTab("testModel"); + yield waitForPaused(dbg); + assertPausedLocation(dbg, longSrc, 66); + ok(isElementVisible(dbg, "breakpoint"), "Breakpoint is visible"); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-iframes.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-iframes.js new file mode 100644 index 000000000..9039da1be --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-iframes.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test debugging a page with iframes + * 1. pause in the main thread + * 2. pause in the iframe + */ +add_task(function* () { + const dbg = yield initDebugger("doc-iframes.html"); + + // test pausing in the main thread + yield reload(dbg); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "iframes.html", 8); + + // test pausing in the iframe + yield resume(dbg); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 8); + + // test pausing in the iframe + yield resume(dbg); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 12); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-navigation.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-navigation.js new file mode 100644 index 000000000..381b6b7fd --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-navigation.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function countSources(dbg) { + const sources = dbg.selectors.getSources(dbg.getState()); + return sources.size; +} + +/** + * Test navigating + * navigating while paused will reset the pause state and sources + */ +add_task(function* () { + const dbg = yield initDebugger("doc-script-switching.html"); + const { selectors: { getSelectedSource, getPause }, getState } = dbg; + + invokeInTab("firstCall"); + yield waitForPaused(dbg); + + yield navigate(dbg, "doc-scripts.html", "simple1.js"); + yield addBreakpoint(dbg, "simple1.js", 4); + invokeInTab("main"); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "simple1.js", 4); + is(countSources(dbg), 4, "4 sources are loaded."); + + yield navigate(dbg, "about:blank"); + yield waitForDispatch(dbg, "NAVIGATE"); + is(countSources(dbg), 0, "0 sources are loaded."); + ok(!getPause(getState()), "No pause state exists"); + + yield navigate(dbg, + "doc-scripts.html", + "simple1.js", + "simple2.js", + "long.js", + "scripts.html" + ); + + is(countSources(dbg), 4, "4 sources are loaded."); + + // Test that the current select source persists across reloads + yield selectSource(dbg, "long.js"); + yield reload(dbg, "long.js"); + ok(getSelectedSource(getState()).get("url").includes("long.js"), + "Selected source is long.js"); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-pause-exceptions.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-pause-exceptions.js new file mode 100644 index 000000000..133316b54 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-pause-exceptions.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function uncaughtException() { + return invokeInTab("uncaughtException").catch(() => {}); +} + +function caughtException() { + return invokeInTab("caughtException"); +} + +/* + Tests Pausing on exception + 1. skip an uncaught exception + 2. pause on an uncaught exception + 3. pause on a caught error + 4. skip a caught error +*/ +add_task(function* () { + const dbg = yield initDebugger("doc-exceptions.html"); + + // test skipping an uncaught exception + yield togglePauseOnExceptions(dbg, false, false); + yield uncaughtException(); + ok(!isPaused(dbg)); + + // Test pausing on an uncaught exception + yield togglePauseOnExceptions(dbg, true, false); + uncaughtException(); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "exceptions.js", 2); + yield resume(dbg); + + // Test pausing on a caught Error + caughtException(); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "exceptions.js", 15); + yield resume(dbg); + + // Test skipping a caught error + yield togglePauseOnExceptions(dbg, true, true); + caughtException(); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "exceptions.js", 17); + yield resume(dbg); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print-paused.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print-paused.js new file mode 100644 index 000000000..73919e65e --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print-paused.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests pretty-printing a source that is currently paused. + +add_task(function* () { + const dbg = yield initDebugger("doc-minified.html"); + + yield selectSource(dbg, "math.min.js"); + yield addBreakpoint(dbg, "math.min.js", 2); + + invokeInTab("arithmetic"); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "math.min.js", 2); + + clickElement(dbg, "prettyPrintButton"); + yield waitForDispatch(dbg, "TOGGLE_PRETTY_PRINT"); + + assertPausedLocation(dbg, "math.min.js:formatted", 18); + + yield resume(dbg); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print.js new file mode 100644 index 000000000..260bfef38 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests basic pretty-printing functionality. + +add_task(function* () { + const dbg = yield initDebugger("doc-minified.html"); + + yield selectSource(dbg, "math.min.js"); + clickElement(dbg, "prettyPrintButton"); + yield waitForDispatch(dbg, "TOGGLE_PRETTY_PRINT"); + + const ppSrc = findSource(dbg, "math.min.js:formatted"); + ok(ppSrc, "Pretty-printed source exists"); + + yield addBreakpoint(dbg, ppSrc, 18); + + invokeInTab("arithmetic"); + yield waitForPaused(dbg); + assertPausedLocation(dbg, ppSrc, 18); + yield stepOver(dbg); + assertPausedLocation(dbg, ppSrc, 27); + yield resume(dbg); + + // The pretty-print button should go away in the pretty-printed + // source. + ok(!findElement(dbg, "sourceFooter"), "Footer is hidden"); + + yield selectSource(dbg, "math.min.js"); + ok(findElement(dbg, "sourceFooter"), "Footer is hidden"); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes.js new file mode 100644 index 000000000..adb99be84 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function toggleNode(dbg, index) { + clickElement(dbg, "scopeNode", index); +} + +function getLabel(dbg, index) { + return findElement(dbg, "scopeNode", index).innerText; +} + +add_task(function* () { + const dbg = yield initDebugger("doc-script-switching.html"); + + toggleScopes(dbg); + + invokeInTab("firstCall"); + yield waitForPaused(dbg); + + toggleNode(dbg, 1); + toggleNode(dbg, 2); + + yield waitForDispatch(dbg, "LOAD_OBJECT_PROPERTIES"); + + is(getLabel(dbg, 1), "secondCall"); + is(getLabel(dbg, 2), "<this>"); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-searching.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-searching.js new file mode 100644 index 000000000..dd25e2b54 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-searching.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Testing source search +add_task(function* () { + const dbg = yield initDebugger("doc-script-switching.html"); + + pressKey(dbg, "sourceSearch"); + yield waitForElement(dbg, "input"); + findElementWithSelector(dbg, "input").focus(); + type(dbg, "sw"); + pressKey(dbg, "Enter"); + + yield waitForDispatch(dbg, "LOAD_SOURCE_TEXT"); + let source = dbg.selectors.getSelectedSource(dbg.getState()); + ok(source.get("url").match(/switching-01/), "first source is selected"); + + // 2. arrow keys and check to see if source is selected + pressKey(dbg, "sourceSearch"); + findElementWithSelector(dbg, "input").focus(); + type(dbg, "sw"); + pressKey(dbg, "Down"); + pressKey(dbg, "Enter"); + + yield waitForDispatch(dbg, "LOAD_SOURCE_TEXT"); + source = dbg.selectors.getSelectedSource(dbg.getState()); + ok(source.get("url").match(/switching-02/), "second source is selected"); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps-bogus.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps-bogus.js new file mode 100644 index 000000000..e8c6070fc --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps-bogus.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that an error while loading a sourcemap does not break +// debugging. + +add_task(function* () { + const dbg = yield initDebugger("doc-sourcemap-bogus.html"); + const { selectors: { getSources }, getState } = dbg; + + yield selectSource(dbg, "bogus-map.js"); + + // We should still be able to set breakpoints and pause in the + // generated source. + yield addBreakpoint(dbg, "bogus-map.js", 4); + invokeInTab("runCode"); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "bogus-map.js", 4); + + // Make sure that only the single generated source exists. The + // sourcemap failed to download. + is(getSources(getState()).size, 1, "Only 1 source exists"); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js new file mode 100644 index 000000000..30fd7b70c --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js @@ -0,0 +1,44 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests loading sourcemapped sources, setting breakpoints, and +// stepping in them. + +add_task(function* () { + const dbg = yield initDebugger("doc-sourcemaps.html"); + const { selectors: { getBreakpoint, getBreakpoints }, getState } = dbg; + + yield waitForSources(dbg, "entry.js", "output.js", "times2.js", "opts.js"); + ok(true, "Original sources exist"); + const entrySrc = findSource(dbg, "entry.js"); + + yield selectSource(dbg, entrySrc); + ok(dbg.win.cm.getValue().includes("window.keepMeAlive"), + "Original source text loaded correctly"); + + // Test that breakpoint sliding is not attempted. The breakpoint + // should not move anywhere. + yield addBreakpoint(dbg, entrySrc, 13); + is(getBreakpoints(getState()).size, 1, "One breakpoint exists"); + ok(getBreakpoint(getState(), { sourceId: entrySrc.id, line: 13 }), + "Breakpoint has correct line"); + + // Test breaking on a breakpoint + yield addBreakpoint(dbg, "entry.js", 15); + is(getBreakpoints(getState()).size, 2, "Two breakpoints exist"); + ok(getBreakpoint(getState(), { sourceId: entrySrc.id, line: 15 }), + "Breakpoint has correct line"); + + invokeInTab("keepMeAlive"); + yield waitForPaused(dbg); + assertPausedLocation(dbg, entrySrc, 15); + + yield stepIn(dbg); + assertPausedLocation(dbg, "times2.js", 2); + yield stepOver(dbg); + assertPausedLocation(dbg, "times2.js", 3); + + yield stepOut(dbg); + yield stepOut(dbg); + assertPausedLocation(dbg, "entry.js", 16); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-sources.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-sources.js new file mode 100644 index 000000000..64b7f56ae --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sources.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the source tree works. + +function* waitForSourceCount(dbg, i) { + // We are forced to wait until the DOM nodes appear because the + // source tree batches its rendering. + yield waitUntil(() => { + return findAllElements(dbg, "sourceNodes").length === i; + }); +} + +add_task(function* () { + const dbg = yield initDebugger("doc-sources.html"); + const { selectors: { getSelectedSource }, getState } = dbg; + + // Expand nodes and make sure more sources appear. + is(findAllElements(dbg, "sourceNodes").length, 2); + + clickElement(dbg, "sourceArrow", 2); + is(findAllElements(dbg, "sourceNodes").length, 7); + + clickElement(dbg, "sourceArrow", 3); + is(findAllElements(dbg, "sourceNodes").length, 8); + + // Select a source. + ok(!findElementWithSelector(dbg, ".sources-list .focused"), + "Source is not focused"); + const selected = waitForDispatch(dbg, "SELECT_SOURCE"); + clickElement(dbg, "sourceNode", 4); + yield selected; + ok(findElementWithSelector(dbg, ".sources-list .focused"), + "Source is focused"); + ok(getSelectedSource(getState()).get("url").includes("nested-source.js"), + "The right source is selected"); + + // Make sure new sources appear in the list. + ContentTask.spawn(gBrowser.selectedBrowser, null, function() { + const script = content.document.createElement("script"); + script.src = "math.min.js"; + content.document.body.appendChild(script); + }); + + yield waitForSourceCount(dbg, 9); + is(findElement(dbg, "sourceNode", 7).querySelector("span").innerText, + "math.min.js", + "The dynamic script exists"); + + // Make sure named eval sources appear in the list. + ContentTask.spawn(gBrowser.selectedBrowser, null, function() { + content.eval("window.evaledFunc = function() {} //# sourceURL=evaled.js"); + }); + yield waitForSourceCount(dbg, 11); + is(findElement(dbg, "sourceNode", 2).querySelector("span").innerText, + "evaled.js", + "The eval script exists"); +}); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg_keyboard-shortcuts.js b/devtools/client/debugger/new/test/mochitest/browser_dbg_keyboard-shortcuts.js new file mode 100644 index 000000000..0d7e572ef --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_keyboard-shortcuts.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test keyboard shortcuts. + */ + +function pressResume(dbg) { + pressKey(dbg, "resumeKey"); + return waitForPaused(dbg); +} + +function pressStepOver(dbg) { + pressKey(dbg, "stepOverKey"); + return waitForPaused(dbg); +} + +function pressStepIn(dbg) { + pressKey(dbg, "stepInKey"); + return waitForPaused(dbg); +} + +function pressStepOut(dbg) { + pressKey(dbg, "stepOutKey"); + return waitForPaused(dbg); +} + +add_task(function*() { + const dbg = yield initDebugger("doc-debugger-statements.html"); + + yield reload(dbg); + yield waitForPaused(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 8); + + yield pressResume(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 12); + + yield pressStepIn(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 13); + + yield pressStepOut(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 14); + + yield pressStepOver(dbg); + assertPausedLocation(dbg, "debugger-statements.html", 9); +}); diff --git a/devtools/client/debugger/new/test/mochitest/examples/README.md b/devtools/client/debugger/new/test/mochitest/examples/README.md new file mode 100644 index 000000000..1be42619d --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/README.md @@ -0,0 +1,7 @@ +### Test Examples + +##### Pages +* **doc_script-switching-01** - includes two scripts that reference each other. The second function has a debugger. +* **doc-scripts** - includes three sources, a long source and two sources that reference each other. +* **doc-iframes** - includes an iframe with the debugger statements source. +* **debugger-statements** - inline script with functions for testing stepping. diff --git a/devtools/client/debugger/new/test/mochitest/examples/bogus-map.js b/devtools/client/debugger/new/test/mochitest/examples/bogus-map.js new file mode 100644 index 000000000..20b5bbf7e --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/bogus-map.js @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +function runCode(){ + var a=1; + a=a*2; + return a; +} +//# sourceMappingURL=bogus.map diff --git a/devtools/client/debugger/new/test/mochitest/examples/bundle.js b/devtools/client/debugger/new/test/mochitest/examples/bundle.js new file mode 100644 index 000000000..a03ace934 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/bundle.js @@ -0,0 +1,96 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; +/******/ +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.loaded = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + const times2 = __webpack_require__(1); + const { output } = __webpack_require__(2); + const opts = __webpack_require__(3); + + output(times2(1)); + output(times2(2)); + + if(opts.extra) { + output(times2(3)); + } + + window.keepMeAlive = function() { + // This function exists to make sure this script is never garbage + // collected. It is also callable from tests. + return times2(4); + } + + +/***/ }, +/* 1 */ +/***/ function(module, exports) { + + module.exports = function(x) { + return x * 2; + } + + +/***/ }, +/* 2 */ +/***/ function(module, exports) { + + function output(str) { + console.log(str); + } + + module.exports = { output }; + + +/***/ }, +/* 3 */ +/***/ function(module, exports) { + + module.exports = { + extra: true + }; + + +/***/ } +/******/ ]); +//# sourceMappingURL=bundle.js.map
\ No newline at end of file diff --git a/devtools/client/debugger/new/test/mochitest/examples/bundle.js.map b/devtools/client/debugger/new/test/mochitest/examples/bundle.js.map new file mode 100644 index 000000000..ed7336ad1 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/bundle.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///webpack/bootstrap 4ef8c7ec7c1df790781e","webpack:///./entry.js","webpack:///./times2.js","webpack:///./output.js","webpack:///./opts.js"],"names":[],"mappings":";AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,uBAAe;AACf;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;;;;;;ACtCA;AACA,QAAO,SAAS;AAChB;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;;;;;;ACfA;AACA;AACA;;;;;;;ACFA;AACA;AACA;;AAEA,mBAAkB;;;;;;;ACJlB;AACA;AACA","file":"bundle.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 4ef8c7ec7c1df790781e","const times2 = require(\"./times2\");\nconst { output } = require(\"./output\");\nconst opts = require(\"./opts\");\n\noutput(times2(1));\noutput(times2(2));\n\nif(opts.extra) {\n output(times2(3));\n}\n\nwindow.keepMeAlive = function() {\n // This function exists to make sure this script is never garbage\n // collected. It is also callable from tests.\n return times2(4);\n}\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./entry.js\n// module id = 0\n// module chunks = 0","module.exports = function(x) {\n return x * 2;\n}\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./times2.js\n// module id = 1\n// module chunks = 0","function output(str) {\n console.log(str);\n}\n\nmodule.exports = { output };\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./output.js\n// module id = 2\n// module chunks = 0","module.exports = {\n extra: true\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./opts.js\n// module id = 3\n// module chunks = 0"],"sourceRoot":""}
\ No newline at end of file diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-debugger-statements.html b/devtools/client/debugger/new/test/mochitest/examples/doc-debugger-statements.html new file mode 100644 index 000000000..967619d31 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-debugger-statements.html @@ -0,0 +1,27 @@ +<html> + <head> + <title>Debugger Statements</title> + </head> + + <body> + <script> + debugger; + test(); + + function test() { + debugger; + stepIntoMe(); + } + + function stepIntoMe() { + // step in + stepOverMe(); + // step out + } + + function stepOverMe() { + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-exceptions.html b/devtools/client/debugger/new/test/mochitest/examples/doc-exceptions.html new file mode 100644 index 000000000..5ca65b755 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-exceptions.html @@ -0,0 +1,7 @@ +<html> + <head> + <title>Debugger test page</title> + <script type="text/javascript" src="exceptions.js"></script> + </head> + <body></body> +</html> diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-frames.html b/devtools/client/debugger/new/test/mochitest/examples/doc-frames.html new file mode 100644 index 000000000..408c55b28 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-frames.html @@ -0,0 +1,17 @@ +<html> + <head> + <title>Frames</title> + </head> + + <body> + <script> + debugger; + // This inline script allows this HTML page to show up as a + // source. It also needs to introduce a new global variable so + // it's not immediately garbage collected. + function inline_script() { var x = 5; } + </script> + <script type="text/javascript" src="frames.js"></script> + </body> + +</html> diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-iframes.html b/devtools/client/debugger/new/test/mochitest/examples/doc-iframes.html new file mode 100644 index 000000000..26446eaa1 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-iframes.html @@ -0,0 +1,17 @@ +<html> + <head> + <title>Iframe</title> + </head> + + <body> + <script> + debugger; + // This inline script allows this HTML page to show up as a + // source. It also needs to introduce a new global variable so + // it's not immediately garbage collected. + function inline_script() { var x = 5; } + </script> + <iframe src="doc-debugger-statements.html"></iframe> + </body> + +</html> diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-minified.html b/devtools/client/debugger/new/test/mochitest/examples/doc-minified.html new file mode 100644 index 000000000..4c95a9b4a --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-minified.html @@ -0,0 +1,14 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script src="math.min.js"></script> + </body> +</html> diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-script-switching.html b/devtools/client/debugger/new/test/mochitest/examples/doc-script-switching.html new file mode 100644 index 000000000..3c71497c2 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-script-switching.html @@ -0,0 +1,18 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button onclick="firstCall()">Click me!</button> + + <script type="text/javascript" src="script-switching-01.js"></script> + <script type="text/javascript" src="script-switching-02.js"></script> + </body> + +</html> diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-scripts.html b/devtools/client/debugger/new/test/mochitest/examples/doc-scripts.html new file mode 100644 index 000000000..212b4802f --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-scripts.html @@ -0,0 +1,21 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script src="simple1.js"></script> + <script src="simple2.js"></script> + <script src="long.js"></script> + <script> + // This inline script allows this HTML page to show up as a + // source. It also needs to introduce a new global variable so + // it's not immediately garbage collected. + function inline_script() { var x = 5; } + </script> + </body> +</html> diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemap-bogus.html b/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemap-bogus.html new file mode 100644 index 000000000..da448a2cd --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemap-bogus.html @@ -0,0 +1,13 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script src="bogus-map.js"></script> + </body> +</html> diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemaps.html b/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemaps.html new file mode 100644 index 000000000..10f5da047 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemaps.html @@ -0,0 +1,13 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script src="bundle.js"></script> + </body> +</html> diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-sources.html b/devtools/client/debugger/new/test/mochitest/examples/doc-sources.html new file mode 100644 index 000000000..14cc86701 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-sources.html @@ -0,0 +1,23 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script src="simple1.js"></script> + <script src="simple2.js"></script> + <script src="long.js"></script> + <script> + // This inline script allows this HTML page to show up as a + // source. It also needs to introduce a new global variable so + // it's not immediately garbage collected. + function inline_script() { var x = 5; } + </script> + <script src="nested/nested-source.js"></script> + <script src="nested/deeper/deeper-source.js"></script> + </body> +</html> diff --git a/devtools/client/debugger/new/test/mochitest/examples/entry.js b/devtools/client/debugger/new/test/mochitest/examples/entry.js new file mode 100644 index 000000000..d397a966b --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/entry.js @@ -0,0 +1,16 @@ +const times2 = require("./times2"); +const { output } = require("./output"); +const opts = require("./opts"); + +output(times2(1)); +output(times2(2)); + +if(opts.extra) { + output(times2(3)); +} + +window.keepMeAlive = function() { + // This function exists to make sure this script is never garbage + // collected. It is also callable from tests. + return times2(4); +} diff --git a/devtools/client/debugger/new/test/mochitest/examples/exceptions.js b/devtools/client/debugger/new/test/mochitest/examples/exceptions.js new file mode 100644 index 000000000..9523f00ca --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/exceptions.js @@ -0,0 +1,19 @@ +function uncaughtException() { + throw "unreachable" +} + +function caughtError() { + try { + throw new Error("error"); + } catch (e) { + debugger; + } +} + +function caughtException() { + try { + throw "reachable"; + } catch (e) { + debugger; + } +} diff --git a/devtools/client/debugger/new/test/mochitest/examples/frames.js b/devtools/client/debugger/new/test/mochitest/examples/frames.js new file mode 100644 index 000000000..0f031582e --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/frames.js @@ -0,0 +1,24 @@ +function recurseA(i) { + if (i == 20) { + debugger; + return; + } + + // down into the rabbit hole we go + return (i % 2) ? recurseA(++i) : recurseB(++i); +} + +function recurseB(i) { + if (i == 20) { + debugger; + return; + } + + // down into the rabbit hole we go + return (i % 2) ? recurseA(++i) : recurseB(++i); +} + + +window.startRecursion = function() { + return recurseA(0); +} diff --git a/devtools/client/debugger/new/test/mochitest/examples/long.js b/devtools/client/debugger/new/test/mochitest/examples/long.js new file mode 100644 index 000000000..58d605b36 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/long.js @@ -0,0 +1,76 @@ +var app = {}; + +// Generic "model" object. You can use whatever +// framework you want. For this application it +// may not even be worth separating this logic +// out, but we do this to demonstrate one way to +// separate out parts of your application. +app.TodoModel = function (key) { + this.key = key; + this.todos = []; + this.onChanges = []; +}; + +app.TodoModel.prototype.addTodo = function (title) { + this.todos = this.todos.concat([{ + id: Utils.uuid(), + title: title, + completed: false + }]); +}; + +app.TodoModel.prototype.inform = function() { + // Something changed, but we do nothing + return null; +}; + +app.TodoModel.prototype.toggleAll = function (checked) { + // Note: it's usually better to use immutable data structures since they're + // easier to reason about and React works very well with them. That's why + // we use map() and filter() everywhere instead of mutating the array or + // todo items themselves. + this.todos = this.todos.map(function (todo) { + return Object.assign({}, todo, {completed: checked}); + }); + + this.inform(); +}; + +app.TodoModel.prototype.toggle = function (todoToToggle) { + this.todos = this.todos.map(function (todo) { + return todo !== todoToToggle ? + todo : + Object.assign({}, todo, {completed: !todo.completed}); + }); + + this.inform(); +}; + +app.TodoModel.prototype.destroy = function (todo) { + this.todos = this.todos.filter(function (candidate) { + return candidate !== todo; + }); + + this.inform(); +}; + +app.TodoModel.prototype.save = function (todoToSave, text) { + this.todos = this.todos.map(function (todo) { + return todo !== todoToSave ? todo : Object.assign({}, todo, {title: text}); + }); + + this.inform(); +}; + +app.TodoModel.prototype.clearCompleted = function () { + this.todos = this.todos.filter(function (todo) { + return !todo.completed; + }); + + this.inform(); +}; + +function testModel() { + const model = new app.TodoModel(); + model.clearCompleted(); +} diff --git a/devtools/client/debugger/new/test/mochitest/examples/math.min.js b/devtools/client/debugger/new/test/mochitest/examples/math.min.js new file mode 100644 index 000000000..5a8593345 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/math.min.js @@ -0,0 +1,3 @@ +function add(a,b,k){var result=a+b;return k(result)}function sub(a,b,k){var result=a-b;return k(result)}function mul(a,b,k){var result=a*b;return k(result)}function div(a,b,k){var result=a/b;return k(result)}function arithmetic(){ + add(4,4,function(a){ + sub(a,2,function(b){mul(b,3,function(c){div(c,2,function(d){console.log(d)})})})})}; diff --git a/devtools/client/debugger/new/test/mochitest/examples/nested/nested-source.js b/devtools/client/debugger/new/test/mochitest/examples/nested/nested-source.js new file mode 100644 index 000000000..a7b20f015 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/nested/nested-source.js @@ -0,0 +1,3 @@ +function computeSomething() { + return 1; +} diff --git a/devtools/client/debugger/new/test/mochitest/examples/opts.js b/devtools/client/debugger/new/test/mochitest/examples/opts.js new file mode 100644 index 000000000..20988fa4a --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/opts.js @@ -0,0 +1,3 @@ +module.exports = { + extra: true +}; diff --git a/devtools/client/debugger/new/test/mochitest/examples/output.js b/devtools/client/debugger/new/test/mochitest/examples/output.js new file mode 100644 index 000000000..14281fdbf --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/output.js @@ -0,0 +1,5 @@ +function output(str) { + console.log(str); +} + +module.exports = { output }; diff --git a/devtools/client/debugger/new/test/mochitest/examples/script-switching-01.js b/devtools/client/debugger/new/test/mochitest/examples/script-switching-01.js new file mode 100644 index 000000000..4ba2772de --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/script-switching-01.js @@ -0,0 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function firstCall() { + secondCall(); +} diff --git a/devtools/client/debugger/new/test/mochitest/examples/script-switching-02.js b/devtools/client/debugger/new/test/mochitest/examples/script-switching-02.js new file mode 100644 index 000000000..feb74315f --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/script-switching-02.js @@ -0,0 +1,13 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function secondCall() { + // This comment is useful: ☺ + debugger; + function foo() {} + if (x) { + foo(); + } +} + +var x = true; diff --git a/devtools/client/debugger/new/test/mochitest/examples/simple1.js b/devtools/client/debugger/new/test/mochitest/examples/simple1.js new file mode 100644 index 000000000..87cc50f44 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/simple1.js @@ -0,0 +1,31 @@ +function main() { + // A comment so we can test that breakpoint sliding works across + // multiple lines + const func = foo(1, 2); + const result = func(); + return result; +} + +function doEval() { + eval("(" + function() { + debugger; + + window.evaledFunc = function() { + var foo = 1; + var bar = 2; + return foo + bar; + }; + }.toString() + ")()"); +} + +function doNamedEval() { + eval("(" + function() { + debugger; + + window.evaledFunc = function() { + var foo = 1; + var bar = 2; + return foo + bar; + }; + }.toString() + ")();\n //# sourceURL=evaled.js"); +} diff --git a/devtools/client/debugger/new/test/mochitest/examples/simple2.js b/devtools/client/debugger/new/test/mochitest/examples/simple2.js new file mode 100644 index 000000000..40c280edf --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/simple2.js @@ -0,0 +1,6 @@ +function foo(x, y) { + function bar() { + return x + y; + } + return bar; +} diff --git a/devtools/client/debugger/new/test/mochitest/examples/times2.js b/devtools/client/debugger/new/test/mochitest/examples/times2.js new file mode 100644 index 000000000..2d51ed87a --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/times2.js @@ -0,0 +1,3 @@ +module.exports = function(x) { + return x * 2; +} diff --git a/devtools/client/debugger/new/test/mochitest/examples/webpack.config.js b/devtools/client/debugger/new/test/mochitest/examples/webpack.config.js new file mode 100644 index 000000000..ff22342ce --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/webpack.config.js @@ -0,0 +1,8 @@ + +module.exports = { + entry: "./entry.js", + output: { + filename: "bundle.js" + }, + devtool: "sourcemap" +} diff --git a/devtools/client/debugger/new/test/mochitest/head.js b/devtools/client/debugger/new/test/mochitest/head.js new file mode 100644 index 000000000..b0964d890 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/head.js @@ -0,0 +1,684 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * The Mochitest API documentation + * @module mochitest + */ + +/** + * The mochitest API to wait for certain events. + * @module mochitest/waits + * @parent mochitest + */ + +/** + * The mochitest API predefined asserts. + * @module mochitest/asserts + * @parent mochitest + */ + +/** + * The mochitest API for interacting with the debugger. + * @module mochitest/actions + * @parent mochitest + */ + +/** + * Helper methods for the mochitest API. + * @module mochitest/helpers + * @parent mochitest + */ + +// shared-head.js handles imports, constants, and utility functions +Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this); +var { Toolbox } = require("devtools/client/framework/toolbox"); +const EXAMPLE_URL = "http://example.com/browser/devtools/client/debugger/new/test/mochitest/examples/"; + +Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend"); + delete window.resumeTest; +}); + +// Wait until an action of `type` is dispatched. This is different +// then `_afterDispatchDone` because it doesn't wait for async actions +// to be done/errored. Use this if you want to listen for the "start" +// action of an async operation (somewhat rare). +function waitForNextDispatch(store, type) { + return new Promise(resolve => { + store.dispatch({ + // Normally we would use `services.WAIT_UNTIL`, but use the + // internal name here so tests aren't forced to always pass it + // in + type: "@@service/waitUntil", + predicate: action => action.type === type, + run: (dispatch, getState, action) => { + resolve(action); + } + }); + }); +} + +// Wait until an action of `type` is dispatched. If it's part of an +// async operation, wait until the `status` field is "done" or "error" +function _afterDispatchDone(store, type) { + return new Promise(resolve => { + store.dispatch({ + // Normally we would use `services.WAIT_UNTIL`, but use the + // internal name here so tests aren't forced to always pass it + // in + type: "@@service/waitUntil", + predicate: action => { + if (action.type === type) { + return action.status ? + (action.status === "done" || action.status === "error") : + true; + } + }, + run: (dispatch, getState, action) => { + resolve(action); + } + }); + }); +} + +/** + * Wait for a specific action type to be dispatch. + * If an async action, will wait for it to be done. + * + * @memberof mochitest/waits + * @param {Object} dbg + * @param {String} type + * @param {Number} eventRepeat + * @return {Promise} + * @static + */ +function waitForDispatch(dbg, type, eventRepeat = 1) { + let count = 0; + + return Task.spawn(function* () { + info("Waiting for " + type + " to dispatch " + eventRepeat + " time(s)"); + while (count < eventRepeat) { + yield _afterDispatchDone(dbg.store, type); + count++; + info(type + " dispatched " + count + " time(s)"); + } + }); +} + +/** + * Waits for specific thread events. + * + * @memberof mochitest/waits + * @param {Object} dbg + * @param {String} eventName + * @return {Promise} + * @static + */ +function waitForThreadEvents(dbg, eventName) { + info("Waiting for thread event '" + eventName + "' to fire."); + const thread = dbg.toolbox.threadClient; + + return new Promise(function(resolve, reject) { + thread.addListener(eventName, function onEvent(eventName, ...args) { + info("Thread event '" + eventName + "' fired."); + thread.removeListener(eventName, onEvent); + resolve.apply(resolve, args); + }); + }); +} + +/** + * Waits for `predicate(state)` to be true. `state` is the redux app state. + * + * @memberof mochitest/waits + * @param {Object} dbg + * @param {Function} predicate + * @return {Promise} + * @static + */ +function waitForState(dbg, predicate) { + return new Promise(resolve => { + const unsubscribe = dbg.store.subscribe(() => { + if (predicate(dbg.store.getState())) { + unsubscribe(); + resolve(); + } + }); + }); +} + +/** + * Waits for sources to be loaded. + * + * @memberof mochitest/waits + * @param {Object} dbg + * @param {Array} sources + * @return {Promise} + * @static + */ +function waitForSources(dbg, ...sources) { + if (sources.length === 0) { + return Promise.resolve(); + } + + info("Waiting on sources: " + sources.join(", ")); + const { selectors: { getSources }, store } = dbg; + return Promise.all(sources.map(url => { + function sourceExists(state) { + return getSources(state).some(s => { + return s.get("url").includes(url); + }); + } + + if (!sourceExists(store.getState())) { + return waitForState(dbg, sourceExists); + } + })); +} + +function waitForElement(dbg, selector) { + return waitUntil(() => findElementWithSelector(dbg, selector)) +} + +/** + * Assert that the debugger is paused at the correct location. + * + * @memberof mochitest/asserts + * @param {Object} dbg + * @param {String} source + * @param {Number} line + * @static + */ +function assertPausedLocation(dbg, source, line) { + const { selectors: { getSelectedSource, getPause }, getState } = dbg; + source = findSource(dbg, source); + + // Check the selected source + is(getSelectedSource(getState()).get("id"), source.id); + + // Check the pause location + const location = getPause(getState()).getIn(["frame", "location"]); + is(location.get("sourceId"), source.id); + is(location.get("line"), line); + + // Check the debug line + ok(dbg.win.cm.lineInfo(line - 1).wrapClass.includes("debug-line"), + "Line is highlighted as paused"); +} + +/** + * Assert that the debugger is highlighting the correct location. + * + * @memberof mochitest/asserts + * @param {Object} dbg + * @param {String} source + * @param {Number} line + * @static + */ +function assertHighlightLocation(dbg, source, line) { + const { selectors: { getSelectedSource, getPause }, getState } = dbg; + source = findSource(dbg, source); + + // Check the selected source + is(getSelectedSource(getState()).get("url"), source.url); + + // Check the highlight line + const lineEl = findElement(dbg, "highlightLine"); + ok(lineEl, "Line is highlighted"); + ok(isVisibleWithin(findElement(dbg, "codeMirror"), lineEl), + "Highlighted line is visible"); + ok(dbg.win.cm.lineInfo(line - 1).wrapClass.includes("highlight-line"), + "Line is highlighted"); +} + +/** + * Returns boolean for whether the debugger is paused. + * + * @memberof mochitest/asserts + * @param {Object} dbg + * @static + */ +function isPaused(dbg) { + const { selectors: { getPause }, getState } = dbg; + return !!getPause(getState()); +} + +/** + * Waits for the debugger to be fully paused. + * + * @memberof mochitest/waits + * @param {Object} dbg + * @static + */ +function waitForPaused(dbg) { + return Task.spawn(function* () { + // We want to make sure that we get both a real paused event and + // that the state is fully populated. The client may do some more + // work (call other client methods) before populating the state. + yield waitForThreadEvents(dbg, "paused"), + yield waitForState(dbg, state => { + const pause = dbg.selectors.getPause(state); + // Make sure we have the paused state. + if (!pause) { + return false; + } + // Make sure the source text is completely loaded for the + // source we are paused in. + const sourceId = pause.getIn(["frame", "location", "sourceId"]); + const sourceText = dbg.selectors.getSourceText(dbg.getState(), sourceId); + return sourceText && !sourceText.get("loading"); + }); + }); +} + +function createDebuggerContext(toolbox) { + const win = toolbox.getPanel("jsdebugger").panelWin; + const store = win.Debugger.store; + + return { + actions: win.Debugger.actions, + selectors: win.Debugger.selectors, + getState: store.getState, + store: store, + client: win.Debugger.client, + toolbox: toolbox, + win: win + }; +} + +/** + * Intilializes the debugger. + * + * @memberof mochitest + * @param {String} url + * @param {Array} sources + * @return {Promise} dbg + * @static + */ +function initDebugger(url, ...sources) { + return Task.spawn(function* () { + const toolbox = yield openNewTabAndToolbox(EXAMPLE_URL + url, "jsdebugger"); + return createDebuggerContext(toolbox); + }); +} + +window.resumeTest = undefined; +/** + * Pause the test and let you interact with the debugger. + * The test can be resumed by invoking `resumeTest` in the console. + * + * @memberof mochitest + * @static + */ +function pauseTest() { + info("Test paused. Invoke resumeTest to continue."); + return new Promise(resolve => resumeTest = resolve); +} + +// Actions +/** + * Returns a source that matches the URL. + * + * @memberof mochitest/actions + * @param {Object} dbg + * @param {String} url + * @return {Object} source + * @static + */ +function findSource(dbg, url) { + if (typeof url !== "string") { + // Support passing in a source object itelf all APIs that use this + // function support both styles + const source = url; + return source; + } + + const sources = dbg.selectors.getSources(dbg.getState()); + const source = sources.find(s => s.get("url").includes(url)); + + if (!source) { + throw new Error("Unable to find source: " + url); + } + + return source.toJS(); +} + +/** + * Selects the source. + * + * @memberof mochitest/actions + * @param {Object} dbg + * @param {String} url + * @param {Number} line + * @return {Promise} + * @static + */ +function selectSource(dbg, url, line) { + info("Selecting source: " + url); + const source = findSource(dbg, url); + const hasText = !!dbg.selectors.getSourceText(dbg.getState(), source.id); + dbg.actions.selectSource(source.id, { line }); + + if (!hasText) { + return waitForDispatch(dbg, "LOAD_SOURCE_TEXT"); + } +} + +/** + * Steps over. + * + * @memberof mochitest/actions + * @param {Object} dbg + * @return {Promise} + * @static + */ +function stepOver(dbg) { + info("Stepping over"); + dbg.actions.stepOver(); + return waitForPaused(dbg); +} + +/** + * Steps in. + * + * @memberof mochitest/actions + * @param {Object} dbg + * @return {Promise} + * @static + */ +function stepIn(dbg) { + info("Stepping in"); + dbg.actions.stepIn(); + return waitForPaused(dbg); +} + +/** + * Steps out. + * + * @memberof mochitest/actions + * @param {Object} dbg + * @return {Promise} + * @static + */ +function stepOut(dbg) { + info("Stepping out"); + dbg.actions.stepOut(); + return waitForPaused(dbg); +} + +/** + * Resumes. + * + * @memberof mochitest/actions + * @param {Object} dbg + * @return {Promise} + * @static + */ +function resume(dbg) { + info("Resuming"); + dbg.actions.resume(); + return waitForThreadEvents(dbg, "resumed"); +} + +/** + * Reloads the debuggee. + * + * @memberof mochitest/actions + * @param {Object} dbg + * @param {Array} sources + * @return {Promise} + * @static + */ +function reload(dbg, ...sources) { + return dbg.client.reload().then(() => waitForSources(...sources)); +} + +/** + * Navigates the debuggee to another url. + * + * @memberof mochitest/actions + * @param {Object} dbg + * @param {String} url + * @param {Array} sources + * @return {Promise} + * @static + */ +function navigate(dbg, url, ...sources) { + dbg.client.navigate(url); + return waitForSources(dbg, ...sources); +} + +/** + * Adds a breakpoint to a source at line/col. + * + * @memberof mochitest/actions + * @param {Object} dbg + * @param {String} source + * @param {Number} line + * @param {Number} col + * @return {Promise} + * @static + */ +function addBreakpoint(dbg, source, line, col) { + source = findSource(dbg, source); + const sourceId = source.id; + dbg.actions.addBreakpoint({ sourceId, line, col }); + return waitForDispatch(dbg, "ADD_BREAKPOINT"); +} + +/** + * Removes a breakpoint from a source at line/col. + * + * @memberof mochitest/actions + * @param {Object} dbg + * @param {String} source + * @param {Number} line + * @param {Number} col + * @return {Promise} + * @static + */ +function removeBreakpoint(dbg, sourceId, line, col) { + return dbg.actions.removeBreakpoint({ sourceId, line, col }); +} + +/** + * Toggles the Pause on exceptions feature in the debugger. + * + * @memberof mochitest/actions + * @param {Object} dbg + * @param {Boolean} pauseOnExceptions + * @param {Boolean} ignoreCaughtExceptions + * @return {Promise} + * @static + */ +function togglePauseOnExceptions(dbg, + pauseOnExceptions, ignoreCaughtExceptions) { + const command = dbg.actions.pauseOnExceptions( + pauseOnExceptions, + ignoreCaughtExceptions + ); + + if (!isPaused(dbg)) { + return waitForThreadEvents(dbg, "resumed"); + } + + return command; +} + +// Helpers + +/** + * Invokes a global function in the debuggee tab. + * + * @memberof mochitest/helpers + * @param {String} fnc + * @return {Promise} + * @static + */ +function invokeInTab(fnc) { + info(`Invoking function ${fnc} in tab`); + return ContentTask.spawn(gBrowser.selectedBrowser, fnc, function* (fnc) { + content.wrappedJSObject[fnc](); // eslint-disable-line mozilla/no-cpows-in-tests, max-len + }); +} + +const isLinux = Services.appinfo.OS === "Linux"; +const cmdOrCtrl = isLinux ? { ctrlKey: true } : { metaKey: true }; +const keyMappings = { + sourceSearch: { code: "p", modifiers: cmdOrCtrl}, + fileSearch: { code: "f", modifiers: cmdOrCtrl}, + "Enter": { code: "VK_RETURN" }, + "Up": { code: "VK_UP" }, + "Down": { code: "VK_DOWN" }, + pauseKey: { code: "VK_F8" }, + resumeKey: { code: "VK_F8" }, + stepOverKey: { code: "VK_F10" }, + stepInKey: { code: "VK_F11", modifiers: { ctrlKey: isLinux }}, + stepOutKey: { code: "VK_F11", modifiers: { ctrlKey: isLinux, shiftKey: true }} +}; + +/** + * Simulates a key press in the debugger window. + * + * @memberof mochitest/helpers + * @param {Object} dbg + * @param {String} keyName + * @return {Promise} + * @static + */ +function pressKey(dbg, keyName) { + let keyEvent = keyMappings[keyName]; + + const { code, modifiers } = keyEvent; + return EventUtils.synthesizeKey( + code, + modifiers || {}, + dbg.win + ); +} + +function type(dbg, string) { + string.split("").forEach(char => { + EventUtils.synthesizeKey(char, {}, dbg.win); + }); +} + +function isVisibleWithin(outerEl, innerEl) { + const innerRect = innerEl.getBoundingClientRect(); + const outerRect = outerEl.getBoundingClientRect(); + return innerRect.top > outerRect.top && + innerRect.bottom < outerRect.bottom; +} + +const selectors = { + callStackHeader: ".call-stack-pane ._header", + callStackBody: ".call-stack-pane .pane", + scopesHeader: ".scopes-pane ._header", + breakpointItem: i => `.breakpoints-list .breakpoint:nth-child(${i})`, + scopeNode: i => `.scopes-list .tree-node:nth-child(${i}) .object-label`, + frame: i => `.frames ul li:nth-child(${i})`, + frames: ".frames ul li", + gutter: i => `.CodeMirror-code *:nth-child(${i}) .CodeMirror-linenumber`, + menuitem: i => `menupopup menuitem:nth-child(${i})`, + pauseOnExceptions: ".pause-exceptions", + breakpoint: ".CodeMirror-code > .new-breakpoint", + highlightLine: ".CodeMirror-code > .highlight-line", + codeMirror: ".CodeMirror", + resume: ".resume.active", + stepOver: ".stepOver.active", + stepOut: ".stepOut.active", + stepIn: ".stepIn.active", + toggleBreakpoints: ".toggleBreakpoints", + prettyPrintButton: ".prettyPrint", + sourceFooter: ".source-footer", + sourceNode: i => `.sources-list .tree-node:nth-child(${i})`, + sourceNodes: ".sources-list .tree-node", + sourceArrow: i => `.sources-list .tree-node:nth-child(${i}) .arrow`, +}; + +function getSelector(elementName, ...args) { + let selector = selectors[elementName]; + if (!selector) { + throw new Error(`The selector ${elementName} is not defined`); + } + + if (typeof selector == "function") { + selector = selector(...args); + } + + return selector; +} + +function findElement(dbg, elementName, ...args) { + const selector = getSelector(elementName, ...args); + return findElementWithSelector(dbg, selector); +} + +function findElementWithSelector(dbg, selector) { + return dbg.win.document.querySelector(selector); +} + +function findAllElements(dbg, elementName, ...args) { + const selector = getSelector(elementName, ...args); + return dbg.win.document.querySelectorAll(selector); +} + +/** + * Simulates a mouse click in the debugger DOM. + * + * @memberof mochitest/helpers + * @param {Object} dbg + * @param {String} elementName + * @param {Array} args + * @return {Promise} + * @static + */ +function clickElement(dbg, elementName, ...args) { + const selector = getSelector(elementName, ...args); + return EventUtils.synthesizeMouseAtCenter( + findElementWithSelector(dbg, selector), + {}, + dbg.win + ); +} + +function rightClickElement(dbg, elementName, ...args) { + const selector = getSelector(elementName, ...args); + const doc = dbg.win.document; + return EventUtils.synthesizeMouseAtCenter( + doc.querySelector(selector), + {type: "contextmenu"}, + dbg.win + ); +} + +function selectMenuItem(dbg, index) { + // the context menu is in the toolbox window + const doc = dbg.toolbox.win.document; + + // there are several context menus, we want the one with the menu-api + const popup = doc.querySelector("menupopup[menu-api=\"true\"]"); + + const item = popup.querySelector(`menuitem:nth-child(${index})`); + return EventUtils.synthesizeMouseAtCenter(item, {}, dbg.toolbox.win ); +} + +/** + * Toggles the debugger call stack accordian. + * + * @memberof mochitest/actions + * @param {Object} dbg + * @return {Promise} + * @static + */ +function toggleCallStack(dbg) { + return findElement(dbg, "callStackHeader").click(); +} + +function toggleScopes(dbg) { + return findElement(dbg, "scopesHeader").click(); +} |