Bug 1538781 - Use the right thread for console evaluations when paused in a worker thread's frame, r=nchevobbe.
authorBrian Hackett <bhackett1024@gmail.com>
Mon, 15 Apr 2019 13:27:53 -1000
changeset 470284 0a223da4c1e482cff28ae415e52ef50853b85ff3
parent 470283 8c09cf1ee1e5321cb042554c088bef563b328d12
child 470285 b330e84e1458ac4e11343638f622329a48bb952b
push id35892
push userrgurzau@mozilla.com
push dateSat, 20 Apr 2019 09:55:32 +0000
treeherdermozilla-central@a092972b53f0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe
bugs1538781
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1538781 - Use the right thread for console evaluations when paused in a worker thread's frame, r=nchevobbe. Differential Revision: https://phabricator.services.mozilla.com/D27625
devtools/client/debugger/panel.js
devtools/client/debugger/src/client/firefox/commands.js
devtools/client/webconsole/actions/autocomplete.js
devtools/client/webconsole/components/JSTerm.js
devtools/client/webconsole/test/mochitest/browser.ini
devtools/client/webconsole/test/mochitest/browser_webconsole_worker_evaluate.js
devtools/client/webconsole/test/mochitest/test-evaluate-worker.html
devtools/client/webconsole/test/mochitest/test-evaluate-worker.js
devtools/client/webconsole/webconsole-wrapper.js
devtools/client/webconsole/webconsole.js
--- a/devtools/client/debugger/panel.js
+++ b/devtools/client/debugger/panel.js
@@ -134,16 +134,20 @@ DebuggerPanel.prototype = {
 
     frames.forEach(frame => {
       frame.actor = frame.id;
     });
 
     return { frames, selected };
   },
 
+  lookupConsoleClient: function(thread) {
+    return this._client.lookupConsoleClient(thread);
+  },
+
   getMappedExpression(expression) {
     return this._actions.getMappedExpression(expression);
   },
 
   isPaused() {
     const thread = this._selectors.getCurrentThread(this._getState());
     return this._selectors.getIsPaused(this._getState(), thread);
   },
--- a/devtools/client/debugger/src/client/firefox/commands.js
+++ b/devtools/client/debugger/src/client/firefox/commands.js
@@ -507,12 +507,13 @@ const clientCommands = {
   registerSourceActor,
   fetchWorkers,
   getMainThread,
   sendPacket,
   setSkipPausing,
   setEventListenerBreakpoints,
   waitForWorkers,
   detachWorkers,
-  hasWasmSupport
+  hasWasmSupport,
+  lookupConsoleClient
 };
 
 export { setupCommands, clientCommands };
--- a/devtools/client/webconsole/actions/autocomplete.js
+++ b/devtools/client/webconsole/actions/autocomplete.js
@@ -21,17 +21,17 @@ const {
  */
 function autocompleteUpdate(force, getterPath) {
   return ({dispatch, getState, services}) => {
     if (services.inputHasSelection()) {
       return dispatch(autocompleteClear());
     }
 
     const inputValue = services.getInputValue();
-    const frameActorId = services.getFrameActor();
+    const { frameActor: frameActorId, client } = services.getFrameActor();
     const cursor = services.getInputCursor();
 
     const state = getState().autocomplete;
     const { cache } = state;
     if (!force && (
       !inputValue ||
       /^[a-zA-Z0-9_$]/.test(inputValue.substring(cursor))
     )) {
@@ -68,17 +68,17 @@ function autocompleteUpdate(force, gette
       } else {
         authorizedEvaluations = [getterPath];
       }
     }
 
     return dispatch(autocompleteDataFetch({
       input,
       frameActorId,
-      client: services.getWebConsoleClient(),
+      client,
       authorizedEvaluations,
       force,
     }));
   };
 }
 
 /**
  * Called when the autocompletion data should be cleared.
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -669,18 +669,21 @@ class JSTerm extends Component {
    */
   requestEvaluation(str, options = {}) {
     // Send telemetry event. If we are in the browser toolbox we send -1 as the
     // toolbox session id.
     this.props.serviceContainer.recordTelemetryEvent("execute_js", {
       "lines": str.split(/\n/).length,
     });
 
-    return this.webConsoleClient.evaluateJSAsync(str, {
-      frameActor: this.props.serviceContainer.getFrameActor(options.frame),
+    const { frameActor, client } =
+      this.props.serviceContainer.getFrameActor(options.frame);
+
+    return client.evaluateJSAsync(str, {
+      frameActor,
       ...options,
     });
   }
 
   /**
    * Copy the object/variable by invoking the server
    * which invokes the `copy(variable)` command and makes it
    * available in the clipboard
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -45,16 +45,18 @@ support-files =
   test-dynamic-import.html
   test-dynamic-import.js
   test-error.html
   test-error-worker.html
   test-error-worker.js
   test-error-worker2.js
   test-eval-in-stackframe.html
   test-eval-sources.html
+  test-evaluate-worker.html
+  test-evaluate-worker.js
   test-external-script-errors.html
   test-external-script-errors.js
   test-iframe-insecure-form-action.html
   test-iframe1.html
   test-iframe2.html
   test-iframe3.html
   test-iframe-wrong-hud-iframe.html
   test-iframe-wrong-hud.html
@@ -407,8 +409,9 @@ tags = trackingprotection
 [browser_webconsole_view_source.js]
 [browser_webconsole_visibility_messages.js]
 [browser_webconsole_warn_about_replaced_api.js]
 [browser_webconsole_warning_group_content_blocking.js]
 [browser_webconsole_warning_groups_outside_console_group.js]
 [browser_webconsole_warning_groups.js]
 [browser_webconsole_websocket.js]
 [browser_webconsole_worker_error.js]
+[browser_webconsole_worker_evaluate.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_worker_evaluate.js
@@ -0,0 +1,32 @@
+/* -*- 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/ */
+
+// When the debugger is paused in a worker thread, console evaluations should
+// be performed in that worker's selected frame.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
+                 "test/mochitest/test-evaluate-worker.html";
+
+add_task(async function() {
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const {jsterm} = hud;
+
+  await openDebugger();
+  const toolbox = gDevTools.getToolbox(hud.target);
+  const dbg = createDebuggerContext(toolbox);
+
+  jsterm.execute("pauseInWorker(42)");
+
+  await waitForPaused(dbg);
+  await openConsole();
+
+  const onMessage = waitForMessage(hud, "42");
+  jsterm.execute("data");
+  await onMessage;
+
+  ok(true, "Evaluated console message in worker thread");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/test-evaluate-worker.html
@@ -0,0 +1,9 @@
+<script>
+"use strict";
+var w = new Worker("test-evaluate-worker.js");
+
+// eslint-disable-next-line no-unused-vars
+function pauseInWorker(value) {
+  w.postMessage(value);
+}
+</script>
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/test-evaluate-worker.js
@@ -0,0 +1,8 @@
+"use strict";
+
+self.addEventListener("message", ({ data }) => foo(data));
+
+function foo(data) {
+  // eslint-disable-next-line no-debugger
+  debugger;
+}
--- a/devtools/client/webconsole/webconsole-wrapper.js
+++ b/devtools/client/webconsole/webconsole-wrapper.js
@@ -126,29 +126,40 @@ class WebConsoleWrapper {
           return webConsoleUI.webConsoleClient;
         },
 
         /**
          * Retrieve the FrameActor ID given a frame depth, or the selected one if no
          * frame depth given.
          *
          * @param {Number} frame: optional frame depth.
-         * @return {String|null}: The FrameActor ID for the given frame depth (or the
-         *                        selected frame if it exists).
+         * @return { frameActor: String|null, client: Object }:
+         *         frameActor is the FrameActor ID for the given frame depth
+         *         (or the selected frame if it exists), null if no frame was found.
+         *         client is the WebConsole client for the thread the frame is
+         *         associated with.
          */
         getFrameActor: (frame = null) => {
           const state = this.hud.getDebuggerFrames();
           if (!state) {
-            return null;
+            return { frameActor: null, client: webConsoleUI.webConsoleClient };
           }
 
           const grip = Number.isInteger(frame)
             ? state.frames[frame]
             : state.frames[state.selected];
-          return grip ? grip.actor : null;
+
+          if (!grip) {
+            return { frameActor: null, client: webConsoleUI.webConsoleClient };
+          }
+
+          return {
+            frameActor: grip.actor,
+            client: this.hud.lookupConsoleClient(grip.thread),
+          };
         },
 
         inputHasSelection: () => {
           const {editor, inputNode} = webConsoleUI.jsterm || {};
           return editor
             ? !!editor.getSelection()
             : (inputNode && inputNode.selectionStart !== inputNode.selectionEnd);
         },
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -248,16 +248,28 @@ class WebConsole {
     if (!panel) {
       return null;
     }
 
     return panel.getFrames();
   }
 
   /**
+   * Return the console client to use when interacting with a thread.
+   *
+   * @param {String} thread: The ID of the target thread.
+   * @returns {Object} The console client associated with the thread.
+   */
+  lookupConsoleClient(thread) {
+    const toolbox = gDevTools.getToolbox(this.target);
+    const panel = toolbox.getPanel("jsdebugger");
+    return panel.lookupConsoleClient(thread);
+  }
+
+  /**
    * Given an expression, returns an object containing a new expression, mapped by the
    * parser worker to provide additional feature for the user (top-level await,
    * original languages mapping, …).
    *
    * @param {String} expression: The input to maybe map.
    * @returns {Object|null}
    *          Returns null if the input can't be mapped.
    *          If it can, returns an object containing the following: