Bug 1498300 - Add top-level await support to the Browser Console; r=bgrins.
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Tue, 16 Oct 2018 13:57:32 +0000
changeset 489848 21f2d2802b18cf831154d454535d27998a6c5176
parent 489803 f2e35ed6a6928b35e085462dc4268fd86f00fdb9
child 489849 327775843762d0268c7d48b88771d884eec7b800
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersbgrins
bugs1498300
milestone64.0a1
Bug 1498300 - Add top-level await support to the Browser Console; r=bgrins. This patch moves the parserService from the toolbox, which isn't accessible in the case of the Browser Console, to the console itself. A lightweight test is added to ensure top-level await is supported in the browser console. Differential Revision: https://phabricator.services.mozilla.com/D8816
devtools/client/framework/toolbox.js
devtools/client/webconsole/test/mochitest/browser.ini
devtools/client/webconsole/test/mochitest/browser_console_jsterm_await.js
devtools/client/webconsole/webconsole.js
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -681,31 +681,16 @@ Toolbox.prototype = {
   get sourceMapService() {
     if (!Services.prefs.getBoolPref("devtools.source-map.client-service.enabled")) {
       return null;
     }
     return this._createSourceMapService();
   },
 
   /**
-   * A common access point for the client-side parser service that any panel can use.
-   */
-  get parserService() {
-    if (this._parserService) {
-      return this._parserService;
-    }
-
-    this._parserService =
-      this.browserRequire("devtools/client/debugger/new/src/workers/parser/index");
-    this._parserService
-      .start("resource://devtools/client/debugger/new/dist/parser-worker.js", this.win);
-    return this._parserService;
-  },
-
-  /**
    * Clients wishing to use source maps but that want the toolbox to
    * track the source and style sheet actor mapping can use this
    * source map service.  This is a higher-level service than the one
    * returned by |sourceMapService|, in that it automatically tracks
    * source and style sheet actor IDs.
    */
   get sourceMapURLService() {
     if (this._sourceMapURLService) {
@@ -2870,21 +2855,16 @@ Toolbox.prototype = {
       this._sourceMapURLService.destroy();
       this._sourceMapURLService = null;
     }
     if (this._sourceMapService) {
       this._sourceMapService.stopSourceMapWorker();
       this._sourceMapService = null;
     }
 
-    if (this._parserService) {
-      this._parserService.stop();
-      this._parserService = null;
-    }
-
     if (this.webconsolePanel) {
       this._saveSplitConsoleHeight();
       this.webconsolePanel.removeEventListener("resize",
         this._saveSplitConsoleHeight);
       this.webconsolePanel = null;
     }
     if (this.textBoxContextMenuPopup) {
       this.textBoxContextMenuPopup.removeEventListener("popupshowing",
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -168,16 +168,17 @@ support-files =
 skip-if = true # Bug 1437843
 [browser_console_consolejsm_output.js]
 [browser_console_context_menu_entries.js]
 skip-if = (os == "linux" && (debug || ccov)) # Bug 1440059
 [browser_console_dead_objects.js]
 [browser_console_devtools_loader_exception.js]
 [browser_console_error_source_click.js]
 [browser_console_filters.js]
+[browser_console_jsterm_await.js]
 [browser_console_nsiconsolemessage.js]
 [browser_console_open_or_focus.js]
 skip-if = (verify && debug && (os == 'mac' || os == 'linux'))
 [browser_console_restore.js]
 skip-if = verify
 [browser_console_webconsole_console_api_calls.js]
 [browser_console_webconsole_ctrlw_close_tab.js]
 [browser_console_webconsole_iframe_messages.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_console_jsterm_await.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This is a lightweight version of browser_jsterm_await.js to only ensure top-level await
+// support in the Browser Console.
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,Top-level await Browser Console test";
+
+add_task(async function() {
+  // Enable await mapping.
+  await pushPref("devtools.debugger.features.map-await-expression", true);
+
+  // Run test with legacy JsTerm
+  await pushPref("devtools.webconsole.jsterm.codeMirror", false);
+  await performTests();
+  // And then run it with the CodeMirror-powered one.
+  await pushPref("devtools.webconsole.jsterm.codeMirror", true);
+  await performTests();
+});
+
+async function performTests() {
+  await addTab(TEST_URI);
+  const hud = await HUDService.toggleBrowserConsole();
+
+  const executeAndWaitForResultMessage = (input, expectedOutput) =>
+    executeAndWaitForMessage(hud, input, expectedOutput, ".result");
+
+  info("Evaluate a top-level await expression");
+  const simpleAwait = `await new Promise(r => setTimeout(() => r(["await1"]), 500))`;
+  await executeAndWaitForResultMessage(
+    simpleAwait,
+    `Array [ "await1" ]`,
+  );
+
+  // Check that the resulting promise of the async iife is not displayed.
+  const messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
+  const messagesText = Array.from(messages).map(n => n.textContent).join(" - ");
+  is(messagesText.includes("Promise {"), false, "The output does not contain a Promise");
+  ok(messagesText.includes(simpleAwait) && messagesText.includes(`Array [ "await1" ]`),
+    "The output contains the the expected messages");
+
+  info("Close the Browser console");
+  await HUDService.toggleBrowserConsole();
+}
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -238,33 +238,50 @@ WebConsole.prototype = {
    *            - {String} expression: The mapped expression
    *            - {Object} mapped: An object containing the different mapping that could
    *                               be done and if they were applied on the input.
    *                               At the moment, contains `await`, `bindings` and
    *                               `originalExpression`.
    */
   getMappedExpression(expression) {
     const toolbox = gDevTools.getToolbox(this.target);
-    if (!toolbox) {
-      return null;
-    }
 
-    const panel = toolbox.getPanel("jsdebugger");
+    // We need to check if the debugger is open, since it may perform a variable name
+    // substitution for sourcemapped script (i.e. evaluated `myVar.trim()` might need to
+    // be transformed into `a.trim()`).
+    const panel = toolbox && toolbox.getPanel("jsdebugger");
     if (panel) {
       return panel.getMappedExpression(expression);
     }
 
-    if (toolbox.parserService && expression.includes("await ")) {
-      return toolbox.parserService.mapExpression(expression);
+    if (this.parserService && expression.includes("await ")) {
+      return this.parserService.mapExpression(expression);
     }
 
     return null;
   },
 
   /**
+   * A common access point for the client-side parser service that any panel can use.
+   */
+  get parserService() {
+    if (this._parserService) {
+      return this._parserService;
+    }
+
+    this._parserService =
+      require("devtools/client/debugger/new/src/workers/parser/index");
+
+    this._parserService.start(
+      "resource://devtools/client/debugger/new/dist/parser-worker.js",
+      this.browserWindow);
+    return this._parserService;
+  },
+
+  /**
    * 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.
    *         If the inspector was never opened, or no node was ever selected,
@@ -304,16 +321,21 @@ WebConsole.prototype = {
       if (!this._browserConsole) {
         try {
           await this.target.activeTab.focus();
         } catch (ex) {
           // Tab focus can fail if the tab or target is closed.
         }
       }
 
+      if (this._parserService) {
+        this._parserService.stop();
+        this._parserService = null;
+      }
+
       const id = Utils.supportsString(this.hudId);
       Services.obs.notifyObservers(id, "web-console-destroyed");
     })();
 
     return this._destroyer;
   },
 };