Bug 1450145 - Rewrite console expressions through the debugger panel when available; r=nchevobbe draft
authorLogan Smyth <loganfsmyth@gmail.com>
Thu, 29 Mar 2018 10:55:47 -0700
changeset 776828 e2f093ad8e53fe9c2a39b7ab8aebd42357522d61
parent 776018 c44f60c43432d468639b5fe078420e60c13fd3de
push id105012
push userbmo:loganfsmyth@gmail.com
push dateTue, 03 Apr 2018 20:36:18 +0000
reviewersnchevobbe
bugs1450145
milestone61.0a1
Bug 1450145 - Rewrite console expressions through the debugger panel when available; r=nchevobbe MozReview-Commit-ID: FyB01tz1Ynr
devtools/client/debugger/new/test/mochitest/browser.ini
devtools/client/debugger/new/test/mochitest/browser_dbg-babel-breakpoint-console.js
devtools/client/debugger/panel.js
devtools/client/webconsole/hudservice.js
devtools/client/webconsole/jsterm.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_add_edited_input_to_history.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_ctrl_key_nav.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_history_nav.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_history_persist.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_multiline.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_history_arrow_keys.js
devtools/client/webconsole/test/browser_console_history_persist.js
devtools/client/webconsole/test/browser_console_optimized_out_vars.js
devtools/client/webconsole/test/browser_webconsole_bug_594497_history_arrow_keys.js
devtools/client/webconsole/test/browser_webconsole_bug_804845_ctrl_key_nav.js
devtools/client/webconsole/test/browser_webconsole_bug_817834_add_edited_input_to_history.js
devtools/client/webconsole/test/browser_webconsole_multiline_input.js
devtools/client/webconsole/test/head.js
--- a/devtools/client/debugger/new/test/mochitest/browser.ini
+++ b/devtools/client/debugger/new/test/mochitest/browser.ini
@@ -125,16 +125,17 @@ support-files =
   examples/pause-points.js
   examples/script-mutate.js
   examples/script-switching-02.js
   examples/script-switching-01.js
   examples/times2.js
 
 [browser_dbg-asm.js]
 [browser_dbg-async-stepping.js]
+[browser_dbg-babel-breakpoint-console.js]
 [browser_dbg-babel-scopes.js]
 [browser_dbg-babel-stepping.js]
 [browser_dbg-babel-preview.js]
 [browser_dbg-breaking.js]
 [browser_dbg-breaking-from-console.js]
 [browser_dbg-breakpoints.js]
 [browser_dbg-breakpoints-toggle.js]
 [browser_dbg-breakpoints-reloading.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-babel-breakpoint-console.js
@@ -0,0 +1,95 @@
+
+async function evalInConsoleAtPoint(dbg, fixture, { line, column }, statements) {
+  const { selectors: { getBreakpoint, getBreakpoints }, getState } = dbg;
+
+  const filename = `fixtures/${fixture}/input.js`;
+  await waitForSources(dbg, filename);
+
+  ok(true, "Original sources exist");
+  const source = findSource(dbg, filename);
+
+  await selectSource(dbg, source);
+
+  // Test that breakpoint is not off by a line.
+  await addBreakpoint(dbg, source, line);
+
+  is(getBreakpoints(getState()).size, 1, "One breakpoint exists");
+  ok(
+    getBreakpoint(getState(), { sourceId: source.id, line, column }),
+    "Breakpoint has correct line"
+  );
+
+  const fnName = fixture.replace(/-([a-z])/g, (s, c) => c.toUpperCase());
+
+  const invokeResult = invokeInTab(fnName);
+
+  let invokeFailed = await Promise.race([
+    waitForPaused(dbg),
+    invokeResult.then(() => new Promise(() => {}), () => true)
+  ]);
+
+  if (invokeFailed) {
+    return invokeResult;
+  }
+
+  assertPausedLocation(dbg);
+
+  await assertConsoleEval(dbg, statements);
+
+  await removeBreakpoint(dbg, source.id, line, column);
+
+  is(getBreakpoints(getState()).size, 0, "Breakpoint reverted");
+
+  await resume(dbg);
+
+  // If the invoke errored later somehow, capture here so the error is reported nicely.
+  await invokeResult;
+
+  ok(true, `Ran tests for ${fixture} at line ${line} column ${column}`);
+}
+
+async function assertConsoleEval(dbg, statements) {
+  const jsterm = (await dbg.toolbox.selectTool("webconsole")).hud.jsterm;
+
+  for (const [index, statement] of statements.entries()) {
+    await dbg.client.evaluate(`
+      window.TEST_RESULT = false;
+    `);
+    await jsterm.execute(`
+      TEST_RESULT = ${statement};
+    `);
+
+    const result = await dbg.client.evaluate(`window.TEST_RESULT`);
+    is(result.result, true, `'${statement}' evaluates to true`);
+  }
+}
+
+add_task(async function() {
+  await pushPref("devtools.debugger.features.map-scopes", true);
+
+  const dbg = await initDebugger("doc-babel.html");
+
+  await evalInConsoleAtPoint(dbg, "eval-source-maps", { line: 14, column: 4 }, [
+    "one === 1",
+    "two === 4",
+    "three === 5",
+  ]);
+
+  await evalInConsoleAtPoint(dbg, "imported-bindings", { line: 20, column: 2 }, [
+    `aDefault === "a-default"`,
+    `anAliased === "an-original"`,
+    `aNamed === "a-named"`,
+    `aDefault2 === "a-default2"`,
+    `anAliased2 === "an-original2"`,
+    `aNamed2 === "a-named2"`,
+    `aDefault3 === "a-default3"`,
+    `anAliased3 === "an-original3"`,
+    `aNamed3 === "a-named3"`,
+  ]);
+
+  await evalInConsoleAtPoint(dbg, "shadowed-vars", { line: 18, column: 6 }, [
+    `aVar === "var3"`,
+    `aLet === "let3"`,
+    `aConst === "const3"`,
+  ]);
+});
--- a/devtools/client/debugger/panel.js
+++ b/devtools/client/debugger/panel.js
@@ -126,16 +126,22 @@ DebuggerPanel.prototype = {
 
     return this._destroyer = this._controller.shutdownDebugger().then(() => {
       this.emit("destroyed");
     });
   },
 
   // DebuggerPanel API
 
+  getMappedExpression(expression) {
+    // No-op implementation since this feature doesn't exist in the older
+    // debugger implementation.
+    return expression;
+  },
+
   isPaused() {
     let framesController = this.panelWin.DebuggerController.StackFrames;
     let thread = framesController.activeThread;
     return thread && thread.paused;
   },
 
   getFrames() {
     let framesController = this.panelWin.DebuggerController.StackFrames;
--- a/devtools/client/webconsole/hudservice.js
+++ b/devtools/client/webconsole/hudservice.js
@@ -477,16 +477,30 @@ WebConsole.prototype = {
 
     if (!panel) {
       return null;
     }
 
     return panel.getFrames();
   },
 
+  async getMappedExpression(expression) {
+    let toolbox = gDevTools.getToolbox(this.target);
+    if (!toolbox) {
+      return expression;
+    }
+    let panel = toolbox.getPanel("jsdebugger");
+
+    if (!panel) {
+      return expression;
+    }
+
+    return panel.getMappedExpression(expression);
+  },
+
   /**
    * Retrieves the current selection from the Inspector, if such a selection
    * exists. This is used to pass the ID of the selected actor to the Web
    * Console server for the $0 helper.
    *
    * @return object|null
    *         A Selection referring to the currently selected node in the
    *         Inspector.
--- a/devtools/client/webconsole/jsterm.js
+++ b/devtools/client/webconsole/jsterm.js
@@ -431,17 +431,17 @@ JSTerm.prototype = {
    *        The string you want to execute. If this is not provided, the current
    *        user input is used - taken from |this.getInputValue()|.
    * @param function [callback]
    *        Optional function to invoke when the result is displayed.
    *        This is deprecated - please use the promise return value instead.
    * @returns Promise
    *          Resolves with the message once the result is displayed.
    */
-  execute: function(executeString, callback) {
+  execute: async function(executeString, callback) {
     let deferred = defer();
     let resultCallback;
     if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
       resultCallback = (msg) => deferred.resolve(msg);
     } else {
       resultCallback = (msg) => {
         deferred.resolve(msg);
         if (callback) {
@@ -451,16 +451,31 @@ JSTerm.prototype = {
     }
 
     // attempt to execute the content of the inputNode
     executeString = executeString || this.getInputValue();
     if (!executeString) {
       return null;
     }
 
+    // Append a new value in the history of executed code, or overwrite the most
+    // recent entry. The most recent entry may contain the last edited input
+    // value that was not evaluated yet.
+    this.history[this.historyIndex++] = executeString;
+    this.historyPlaceHolder = this.history.length;
+
+    if (this.history.length > this.inputHistoryCount) {
+      this.history.splice(0, this.history.length - this.inputHistoryCount);
+      this.historyIndex = this.historyPlaceHolder = this.history.length;
+    }
+    this.storeHistory();
+    WebConsoleUtils.usageCount++;
+    this.setInputValue("");
+    this.clearCompletion();
+
     let selectedNodeActor = null;
     let inspectorSelection = this.hud.owner.getInspectorSelection();
     if (inspectorSelection && inspectorSelection.nodeFront) {
       selectedNodeActor = inspectorSelection.nodeFront.actorID;
     }
 
     if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
       const { ConsoleCommand } = require("devtools/client/webconsole/new-console-output/types");
@@ -477,32 +492,19 @@ JSTerm.prototype = {
     }
     let onResult = this._executeResultCallback.bind(this, resultCallback);
 
     let options = {
       frame: this.SELECTED_FRAME,
       selectedNodeActor: selectedNodeActor,
     };
 
-    this.requestEvaluation(executeString, options).then(onResult, onResult);
-
-    // Append a new value in the history of executed code, or overwrite the most
-    // recent entry. The most recent entry may contain the last edited input
-    // value that was not evaluated yet.
-    this.history[this.historyIndex++] = executeString;
-    this.historyPlaceHolder = this.history.length;
+    const mappedString = await this.hud.owner.getMappedExpression(executeString);
+    this.requestEvaluation(mappedString, options).then(onResult, onResult);
 
-    if (this.history.length > this.inputHistoryCount) {
-      this.history.splice(0, this.history.length - this.inputHistoryCount);
-      this.historyIndex = this.historyPlaceHolder = this.history.length;
-    }
-    this.storeHistory();
-    WebConsoleUtils.usageCount++;
-    this.setInputValue("");
-    this.clearCompletion();
     return deferred.promise;
   },
 
   /**
    * Request a JavaScript string evaluation from the server.
    *
    * @param string str
    *        String to execute.
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_add_edited_input_to_history.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_add_edited_input_to_history.js
@@ -8,45 +8,45 @@
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=817834
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 817834";
 
 add_task(async function() {
   let hud = await openNewTabAndConsole(TEST_URI);
-  testEditedInputHistory(hud);
+  await testEditedInputHistory(hud);
 });
 
-function testEditedInputHistory(hud) {
+async function testEditedInputHistory(hud) {
   let jsterm = hud.jsterm;
   let inputNode = jsterm.inputNode;
 
   ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
   is(inputNode.selectionStart, 0);
   is(inputNode.selectionEnd, 0);
 
   jsterm.setInputValue('"first item"');
   EventUtils.synthesizeKey("KEY_ArrowUp");
   is(jsterm.getInputValue(), '"first item"', "null test history up");
   EventUtils.synthesizeKey("KEY_ArrowDown");
   is(jsterm.getInputValue(), '"first item"', "null test history down");
 
-  jsterm.execute();
+  await jsterm.execute();
   is(jsterm.getInputValue(), "", "cleared input line after submit");
 
   jsterm.setInputValue('"editing input 1"');
   EventUtils.synthesizeKey("KEY_ArrowUp");
   is(jsterm.getInputValue(), '"first item"', "test history up");
   EventUtils.synthesizeKey("KEY_ArrowDown");
   is(jsterm.getInputValue(), '"editing input 1"',
     "test history down restores in-progress input");
 
   jsterm.setInputValue('"second item"');
-  jsterm.execute();
+  await jsterm.execute();
   jsterm.setInputValue('"editing input 2"');
   EventUtils.synthesizeKey("KEY_ArrowUp");
   is(jsterm.getInputValue(), '"second item"', "test history up");
   EventUtils.synthesizeKey("KEY_ArrowUp");
   is(jsterm.getInputValue(), '"first item"', "test history up");
   EventUtils.synthesizeKey("KEY_ArrowDown");
   is(jsterm.getInputValue(), '"second item"', "test history down");
   EventUtils.synthesizeKey("KEY_ArrowDown");
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_ctrl_key_nav.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_ctrl_key_nav.js
@@ -18,17 +18,17 @@ add_task(async function() {
   const {jsterm} = await openNewTabAndConsole(TEST_URI);
 
   ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
   is(jsterm.inputNode.selectionStart, 0);
   is(jsterm.inputNode.selectionEnd, 0);
 
   testSingleLineInputNavNoHistory(jsterm);
   testMultiLineInputNavNoHistory(jsterm);
-  testNavWithHistory(jsterm);
+  await testNavWithHistory(jsterm);
 });
 
 function testSingleLineInputNavNoHistory(jsterm) {
   let inputNode = jsterm.inputNode;
   // Single char input
   EventUtils.sendString("1");
   is(inputNode.selectionStart, 1, "caret location after single char input");
 
@@ -146,31 +146,31 @@ function testMultiLineInputNavNoHistory(
     synthesizeLineEndKey();
     caretPos = inputNode.selectionStart;
     expectedStringBeforeCarat += lineValues[i];
     is(jsterm.getInputValue().slice(0, caretPos), expectedStringBeforeCarat,
        "ctrl-e to end of line " + (i + 1) + "in multiline input");
   }
 }
 
-function testNavWithHistory(jsterm) {
+async function testNavWithHistory(jsterm) {
   let inputNode = jsterm.inputNode;
 
   // NOTE: Tests does NOT currently define behaviour for ctrl-p/ctrl-n with
   // caret placed _within_ single line input
   let values = [
     '"single line input"',
     '"a longer single-line input to check caret repositioning"',
     '"multi-line"\n"input"\n"here!"',
   ];
 
   // submit to history
   for (let i = 0; i < values.length; i++) {
     jsterm.setInputValue(values[i]);
-    jsterm.execute();
+    await jsterm.execute();
   }
   is(inputNode.selectionStart, 0, "caret location at start of empty line");
 
   synthesizeLineUpKey();
   is(inputNode.selectionStart, values[values.length - 1].length,
      "caret location correct at end of last history input");
 
   // Navigate backwards history with ctrl-p
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_history_nav.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_history_nav.js
@@ -15,32 +15,35 @@ add_task(async function() {
   let { jsterm } = await openNewTabAndConsole(TEST_URI);
   let popup = jsterm.autocompletePopup;
 
   // The autocomplete popup should never be displayed during the test.
   let onShown = function() {
     ok(false, "popup shown");
   };
 
-  jsterm.execute(`window.foobarBug660806 = {
+  await jsterm.execute(`window.foobarBug660806 = {
     'location': 'value0',
     'locationbar': 'value1'
   }`);
 
   popup.on("popup-opened", onShown);
 
   ok(!popup.isOpen, "popup is not open");
 
   ok(!jsterm.lastInputValue, "no lastInputValue");
   jsterm.setInputValue("window.foobarBug660806.location");
   is(jsterm.lastInputValue, "window.foobarBug660806.location",
      "lastInputValue is correct");
 
   EventUtils.synthesizeKey("KEY_Enter");
 
+  // Wait for the execution to complete and clear the value.
+  await waitFor(() => !jsterm.lastInputValue);
+
   let onSetInputValue = jsterm.once("set-input-value");
   EventUtils.synthesizeKey("KEY_ArrowUp");
   await onSetInputValue;
 
   // We don't have an explicit event to wait for here, so we just wait for the next tick
   // before checking the popup status.
   await new Promise(executeSoon);
 
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_history_persist.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_history_persist.js
@@ -44,17 +44,17 @@ add_task(async function() {
   let hud3 = await openNewTabAndConsole(TEST_URI, false);
   is(JSON.stringify(hud3.jsterm.history),
      '["0","1","2","3","4","5","6","7","8","9"]',
      "Third tab has populated history");
 
   // Set input value separately from execute so UP arrow accurately navigates
   // history.
   hud3.jsterm.setInputValue('"hello from third tab"');
-  hud3.jsterm.execute();
+  await hud3.jsterm.execute();
 
   is(JSON.stringify(hud1.jsterm.history),
      '["0","1","2","3","4","5","6","7","8","9"]',
      "First tab history hasn't changed due to command in third tab");
   is(JSON.stringify(hud2.jsterm.history),
      '["0","1","2","3","4","5","6","7","8","9",""]',
      "Second tab history hasn't changed due to command in third tab");
   is(JSON.stringify(hud3.jsterm.history),
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_multiline.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_multiline.js
@@ -56,14 +56,16 @@ add_task(async function() {
     let inputWithNewline = input + "\n";
     is(inputValue, inputWithNewline, "Input value is correct");
   }
 
   for (let {input, shiftKey} of SHOULD_EXECUTE) {
     hud.jsterm.setInputValue(input);
     EventUtils.synthesizeKey("VK_RETURN", { shiftKey });
 
+    await waitFor(() => !hud.jsterm.getInputValue());
+
     let inputValue = hud.jsterm.getInputValue();
     is(inputNode.selectionStart, 0, "selection starts/ends at 0");
     is(inputNode.selectionEnd, 0, "selection starts/ends at 0");
     is(inputValue, "", "Input value is cleared");
   }
 });
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_history_arrow_keys.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_history_arrow_keys.js
@@ -23,17 +23,17 @@ add_task(async function() {
   let { jsterm } = hud;
 
   jsterm.focus();
   ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
 
   info("Execute each test value in the console");
   for (let value of TEST_VALUES) {
     jsterm.setInputValue(value);
-    jsterm.execute();
+    await jsterm.execute();
   }
 
   performTests(jsterm);
 });
 
 function performTests(jsterm) {
   let { inputNode } = jsterm;
   let values = TEST_VALUES;
--- a/devtools/client/webconsole/test/browser_console_history_persist.js
+++ b/devtools/client/webconsole/test/browser_console_history_persist.js
@@ -48,17 +48,17 @@ add_task(function* () {
   let hud3 = yield openConsole();
   is(JSON.stringify(hud3.jsterm.history),
      '["0","1","2","3","4","5","6","7","8","9"]',
      "Third tab has populated history");
 
   // Set input value separately from execute so UP arrow accurately navigates
   // history.
   hud3.jsterm.setInputValue('"hello from third tab"');
-  hud3.jsterm.execute();
+  yield hud3.jsterm.execute();
 
   is(JSON.stringify(hud1.jsterm.history),
      '["0","1","2","3","4","5","6","7","8","9"]',
      "First tab history hasn't changed due to command in third tab");
   is(JSON.stringify(hud2.jsterm.history),
      '["0","1","2","3","4","5","6","7","8","9",""]',
      "Second tab history hasn't changed due to command in third tab");
   is(JSON.stringify(hud3.jsterm.history),
@@ -93,17 +93,17 @@ add_task(function* () {
  */
 function* populateInputHistory(hud) {
   let jsterm = hud.jsterm;
 
   for (let i = 0; i < INPUT_HISTORY_COUNT; i++) {
     // Set input value separately from execute so UP arrow accurately navigates
     // history.
     jsterm.setInputValue(i);
-    jsterm.execute();
+    yield jsterm.execute();
   }
 }
 
 /**
  * Check pressing up results in history traversal like:
  *  [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
  */
 function* testNaviatingHistoryInUI(hud) {
--- a/devtools/client/webconsole/test/browser_console_optimized_out_vars.js
+++ b/devtools/client/webconsole/test/browser_console_optimized_out_vars.js
@@ -35,17 +35,17 @@ function test() {
     });
 
     yield fetchedScopes;
     ok(true, "Scopes were fetched");
 
     yield toolbox.selectTool("webconsole");
 
     // This is the meat of the test: evaluate the optimized out variable.
-    hud.jsterm.execute("upvar");
+    yield hud.jsterm.execute("upvar");
     yield waitForMessages({
       webconsole: hud,
       messages: [{
         text: "optimized out",
         category: CATEGORY_OUTPUT,
       }]
     });
 
--- a/devtools/client/webconsole/test/browser_webconsole_bug_594497_history_arrow_keys.js
+++ b/devtools/client/webconsole/test/browser_webconsole_bug_594497_history_arrow_keys.js
@@ -10,37 +10,37 @@ var jsterm, inputNode, values;
 var TEST_URI = "data:text/html;charset=utf-8,Web Console test for " +
                "bug 594497 and bug 619598";
 
 add_task(function* () {
   yield loadTab(TEST_URI);
 
   let hud = yield openConsole();
 
-  setup(hud);
+  yield setup(hud);
   performTests();
 
   jsterm = inputNode = values = null;
 });
 
-function setup(HUD) {
+function* setup(HUD) {
   jsterm = HUD.jsterm;
   inputNode = jsterm.inputNode;
 
   jsterm.focus();
 
   ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
 
   values = ["document", "window", "document.body"];
   values.push(values.join(";\n"), "document.location");
 
   // Execute each of the values;
   for (let i = 0; i < values.length; i++) {
     jsterm.setInputValue(values[i]);
-    jsterm.execute();
+    yield jsterm.execute();
   }
 }
 
 function performTests() {
   EventUtils.synthesizeKey("KEY_ArrowUp");
 
 
   is(jsterm.getInputValue(), values[4],
--- a/devtools/client/webconsole/test/browser_webconsole_bug_804845_ctrl_key_nav.js
+++ b/devtools/client/webconsole/test/browser_webconsole_bug_804845_ctrl_key_nav.js
@@ -12,31 +12,31 @@ const TEST_URI = "data:text/html;charset
 
 var jsterm, inputNode;
 
 add_task(function* () {
   yield loadTab(TEST_URI);
 
   let hud = yield openConsole();
 
-  doTests(hud);
+  yield doTests(hud);
 
   jsterm = inputNode = null;
 });
 
-function doTests(HUD) {
+function* doTests(HUD) {
   jsterm = HUD.jsterm;
   inputNode = jsterm.inputNode;
   ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
   is(jsterm.inputNode.selectionStart, 0);
   is(jsterm.inputNode.selectionEnd, 0);
 
   testSingleLineInputNavNoHistory();
   testMultiLineInputNavNoHistory();
-  testNavWithHistory();
+  yield testNavWithHistory();
 }
 
 function testSingleLineInputNavNoHistory() {
   // Single char input
   EventUtils.sendString("1");
   is(inputNode.selectionStart, 1, "caret location after single char input");
 
   // nav to start/end with ctrl-a and ctrl-e;
@@ -152,27 +152,27 @@ function testMultiLineInputNavNoHistory(
     EventUtils.synthesizeKey("e", {ctrlKey: true});
     caretPos = inputNode.selectionStart;
     expectedStringBeforeCarat += lineValues[i];
     is(jsterm.getInputValue().slice(0, caretPos), expectedStringBeforeCarat,
        "ctrl-e to end of line " + (i + 1) + "in multiline input");
   }
 }
 
-function testNavWithHistory() {
+function* testNavWithHistory() {
   // NOTE: Tests does NOT currently define behaviour for ctrl-p/ctrl-n with
   // caret placed _within_ single line input
   let values = ['"single line input"',
                 '"a longer single-line input to check caret repositioning"',
                 ['"multi-line"', '"input"', '"here!"'].join("\n"),
                ];
   // submit to history
   for (let i = 0; i < values.length; i++) {
     jsterm.setInputValue(values[i]);
-    jsterm.execute();
+    yield jsterm.execute();
   }
   is(inputNode.selectionStart, 0, "caret location at start of empty line");
 
   EventUtils.synthesizeKey("p", {ctrlKey: true});
   is(inputNode.selectionStart, values[values.length - 1].length,
      "caret location correct at end of last history input");
 
   // Navigate backwards history with ctrl-p
--- a/devtools/client/webconsole/test/browser_webconsole_bug_817834_add_edited_input_to_history.js
+++ b/devtools/client/webconsole/test/browser_webconsole_bug_817834_add_edited_input_to_history.js
@@ -6,49 +6,49 @@
 // Test that user input that is not submitted in the command line input is not
 // lost after navigating in history.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=817834
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 817834";
 
-add_task(function* () {
-  yield loadTab(TEST_URI);
+add_task(async function () {
+  await loadTab(TEST_URI);
 
-  let hud = yield openConsole();
+  let hud = await openConsole();
 
-  testEditedInputHistory(hud);
+  await testEditedInputHistory(hud);
 });
 
-function testEditedInputHistory(HUD) {
+async function testEditedInputHistory(HUD) {
   let jsterm = HUD.jsterm;
   let inputNode = jsterm.inputNode;
   ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
   is(inputNode.selectionStart, 0);
   is(inputNode.selectionEnd, 0);
 
   jsterm.setInputValue('"first item"');
   EventUtils.synthesizeKey("KEY_ArrowUp");
   is(jsterm.getInputValue(), '"first item"', "null test history up");
   EventUtils.synthesizeKey("KEY_ArrowDown");
   is(jsterm.getInputValue(), '"first item"', "null test history down");
 
-  jsterm.execute();
+  await jsterm.execute();
   is(jsterm.getInputValue(), "", "cleared input line after submit");
 
   jsterm.setInputValue('"editing input 1"');
   EventUtils.synthesizeKey("KEY_ArrowUp");
   is(jsterm.getInputValue(), '"first item"', "test history up");
   EventUtils.synthesizeKey("KEY_ArrowDown");
   is(jsterm.getInputValue(), '"editing input 1"',
     "test history down restores in-progress input");
 
   jsterm.setInputValue('"second item"');
-  jsterm.execute();
+  await jsterm.execute();
   jsterm.setInputValue('"editing input 2"');
   EventUtils.synthesizeKey("KEY_ArrowUp");
   is(jsterm.getInputValue(), '"second item"', "test history up");
   EventUtils.synthesizeKey("KEY_ArrowUp");
   is(jsterm.getInputValue(), '"first item"', "test history up");
   EventUtils.synthesizeKey("KEY_ArrowDown");
   is(jsterm.getInputValue(), '"second item"', "test history down");
   EventUtils.synthesizeKey("KEY_ArrowDown");
--- a/devtools/client/webconsole/test/browser_webconsole_multiline_input.js
+++ b/devtools/client/webconsole/test/browser_webconsole_multiline_input.js
@@ -56,15 +56,18 @@ add_task(function* () {
        "caret at end of multiline input");
     let inputWithNewline = test.input + "\n";
     is(inputValue, inputWithNewline, "Input value is correct");
   }
 
   for (let test of SHOULD_EXECUTE) {
     hud.jsterm.setInputValue(test.input);
     EventUtils.synthesizeKey("VK_RETURN", { shiftKey: test.shiftKey });
+
+    yield waitFor(() => !hud.jsterm.getInputValue());
+
     let inputValue = hud.jsterm.getInputValue();
     is(inputNode.selectionStart, 0, "selection starts/ends at 0");
     is(inputNode.selectionEnd, 0, "selection starts/ends at 0");
     is(inputValue, "", "Input value is cleared");
   }
 
 });
--- a/devtools/client/webconsole/test/head.js
+++ b/devtools/client/webconsole/test/head.js
@@ -125,16 +125,35 @@ function afterAllTabsLoaded(callback, wi
   }
 
   if (!stillToLoad) {
     callback();
   }
 }
 
 /**
+ * Wait for a predicate to return a result.
+ *
+ * @param function condition
+ *        Invoked once in a while until it returns a truthy value. This should be an
+ *        idempotent function, since we have to run it a second time after it returns
+ *        true in order to return the value.
+ * @param string message [optional]
+ *        A message to output if the condition fails.
+ * @param number interval [optional]
+ *        How often the predicate is invoked, in milliseconds.
+ * @return object
+ *         A promise that is resolved with the result of the condition.
+ */
+async function waitFor(condition, message = "waitFor", interval = 10, maxTries = 500) {
+  await BrowserTestUtils.waitForCondition(condition, message, interval, maxTries);
+  return condition();
+}
+
+/**
  * Check if a log entry exists in the HUD output node.
  *
  * @param {Element} outputNode
  *        the HUD output node.
  * @param {string} matchString
  *        the string you want to check if it exists in the output node.
  * @param {string} msg
  *        the message describing the test