summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/new/test/mochitest
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/debugger/new/test/mochitest')
-rw-r--r--devtools/client/debugger/new/test/mochitest/.eslintrc80
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser.ini60
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-breaking-from-console.js31
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-breaking.js32
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js50
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints.js101
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-call-stack.js62
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js72
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js88
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-console.js34
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-debugger-buttons.js54
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-editor-gutter.js64
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-editor-highlight.js46
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-editor-mode.js14
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-editor-select.js54
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-iframes.js26
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-navigation.js47
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-pause-exceptions.js46
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print-paused.js22
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print.js31
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-scopes.js27
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-searching.js28
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps-bogus.js23
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js44
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-sources.js58
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg_keyboard-shortcuts.js46
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/README.md7
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/bogus-map.js8
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/bundle.js96
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/bundle.js.map1
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-debugger-statements.html27
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-exceptions.html7
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-frames.html17
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-iframes.html17
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-minified.html14
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-script-switching.html18
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-scripts.html21
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-sourcemap-bogus.html13
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-sourcemaps.html13
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-sources.html23
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/entry.js16
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/exceptions.js19
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/frames.js24
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/long.js76
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/math.min.js3
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/nested/nested-source.js3
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/opts.js3
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/output.js5
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/script-switching-01.js6
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/script-switching-02.js13
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/simple1.js31
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/simple2.js6
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/times2.js3
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/webpack.config.js8
-rw-r--r--devtools/client/debugger/new/test/mochitest/head.js684
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();
+}