Bug 1578408 - Attach content process thread actor from the toolbox. r=jlast,jdescottes,yulia
☠☠ backed out by f3f6f1a4ad6a ☠ ☠
authorAlexandre Poirot <poirot.alex@gmail.com>
Tue, 24 Sep 2019 14:46:36 +0000
changeset 494796 e2ddce1acc62b1e1bf367a226b0cb64a8cec7e78
parent 494795 01dd32f2378e608d646e8581c16bee490188f029
child 494797 ab7bb18bcca2fab4480af1a9d230c231b50d230e
push id114131
push userdluca@mozilla.com
push dateThu, 26 Sep 2019 09:47:34 +0000
treeherdermozilla-inbound@1dc1a755079a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlast, jdescottes, yulia
bugs1578408
milestone71.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 1578408 - Attach content process thread actor from the toolbox. r=jlast,jdescottes,yulia We need to attach the thread actor as soon as the toolbox start. To: * know when the thread is paused, even if the debugger isn't opened * report paused state by changing the debugger icon * switch to the debugger on pause This will also fix some other code, like in the console, which expect the Thread actor to be attached and already inspecting the debugged target's globals. Differential Revision: https://phabricator.services.mozilla.com/D44638
devtools/client/debugger/src/client/firefox.js
devtools/client/debugger/src/client/firefox/commands.js
devtools/client/debugger/src/client/firefox/targets.js
devtools/client/framework/toolbox.js
--- a/devtools/client/debugger/src/client/firefox.js
+++ b/devtools/client/debugger/src/client/firefox.js
@@ -50,37 +50,31 @@ export async function onConnect(connecti
 
   // Retrieve possible event listener breakpoints
   actions.getEventListenerBreakpointTypes().catch(e => console.error(e));
 
   // Initialize the event breakpoints on the thread up front so that
   // they are active once attached.
   actions.addEventListenerBreakpoints([]).catch(e => console.error(e));
 
-  // In Firefox, we need to initially request all of the sources. This
-  // usually fires off individual `newSource` notifications as the
-  // debugger finds them, but there may be existing sources already in
-  // the debugger (if it's paused already, or if loading the page from
-  // bfcache) so explicity fire `newSource` events for all returned
-  // sources.
   const traits = tabTarget.traits;
   await actions.connect(
     tabTarget.url,
     threadFront.actor,
     traits && traits.canRewind,
     tabTarget.isWebExtension
   );
 
-  const fetched = clientCommands
-    .fetchSources()
-    .then(sources => actions.newGeneratedSources(sources));
+  // Fetch the sources for all the targets
+  //
+  // In Firefox, we need to initially request all of the sources. This
+  // usually fires off individual `newSource` notifications as the
+  // debugger finds them, but there may be existing sources already in
+  // the debugger (if it's paused already, or if loading the page from
+  // bfcache) so explicity fire `newSource` events for all returned
+  // sources.
+  const sources = await clientCommands.fetchSources();
+  await actions.newGeneratedSources(sources);
 
-  // If the threadFront is already paused, make sure to show a
-  // paused state.
-  const pausedPacket = threadFront.getLastPausePacket();
-  if (pausedPacket) {
-    clientEvents.paused(threadFront, pausedPacket);
-  }
-
-  return fetched;
+  await clientCommands.checkIfAlreadyPaused();
 }
 
 export { createObjectClient, clientCommands, clientEvents };
--- a/devtools/client/debugger/src/client/firefox/commands.js
+++ b/devtools/client/debugger/src/client/firefox/commands.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 
 import { prepareSourcePayload, createThread } from "./create";
 import { updateTargets } from "./targets";
+import { clientEvents } from "./events";
 
 import Reps from "devtools-reps";
 import type { Node } from "devtools-reps";
 
 import type {
   ActorId,
   BreakpointLocation,
   BreakpointOptions,
@@ -420,18 +421,45 @@ async function getSources(
 }
 
 async function toggleEventLogging(logEventBreakpoints: boolean) {
   return forEachThread(thread =>
     thread.toggleEventLogging(logEventBreakpoints)
   );
 }
 
+function getAllThreadFronts() {
+  const fronts = [currentThreadFront];
+  for (const targetsForType of Object.values(targets)) {
+    for (const { threadFront } of Object.values(targetsForType)) {
+      fronts.push(threadFront);
+    }
+  }
+  return fronts;
+}
+
+// Fetch the sources for all the targets
 async function fetchSources(): Promise<Array<GeneratedSourceData>> {
-  return getSources(currentThreadFront);
+  let sources = [];
+  for (const threadFront of getAllThreadFronts()) {
+    sources = sources.concat(await getSources(threadFront));
+  }
+  return sources;
+}
+
+// Check if any of the targets were paused before we opened
+// the debugger. If one is paused. Fake a `pause` RDP event
+// by directly calling the client event listener.
+async function checkIfAlreadyPaused() {
+  for (const threadFront of getAllThreadFronts()) {
+    const pausedPacket = threadFront.getLastPausePacket();
+    if (pausedPacket) {
+      clientEvents.paused(threadFront, pausedPacket);
+    }
+  }
 }
 
 function getSourceForActor(actor: ActorId) {
   if (!sourceActors[actor]) {
     throw new Error(`Unknown source actor: ${actor}`);
   }
   return sourceActors[actor];
 }
@@ -547,16 +575,17 @@ const clientCommands = {
   evaluateExpressions,
   navigate,
   reload,
   getProperties,
   getFrameScopes,
   pauseOnExceptions,
   toggleEventLogging,
   fetchSources,
+  checkIfAlreadyPaused,
   registerSourceActor,
   fetchThreads,
   getMainThread,
   sendPacket,
   setSkipPausing,
   setEventListenerBreakpoints,
   getEventListenerBreakpointTypes,
   detachWorkers,
--- a/devtools/client/debugger/src/client/firefox/targets.js
+++ b/devtools/client/debugger/src/client/firefox/targets.js
@@ -22,24 +22,32 @@ async function attachTargets(type, targe
 
   for (const targetFront of targetLists) {
     try {
       await targetFront.attach();
       const threadActorID = targetFront.targetForm.threadActor;
       if (targets[threadActorID]) {
         newTargets[threadActorID] = targets[threadActorID];
       } else {
-        const [, threadFront] = await targetFront.attachThread(args.options);
-        // NOTE: resume is not necessary for ProcessDescriptors and can be removed
-        // once we switch to WorkerDescriptors
-        threadFront.resume();
+        // Content process targets have already been attached by the toolbox.
+        // And the thread front has been initialized from there.
+        // So we only need to retrieve it here.
+        let threadFront = targetFront.threadFront;
+
+        // But workers targets are still only managed by the debugger codebase
+        // and so we have to attach their thread actor
+        if (!threadFront) {
+          [, threadFront] = await targetFront.attachThread(args.options);
+          // NOTE: resume is not necessary for ProcessDescriptors and can be removed
+          // once we switch to WorkerDescriptors
+          threadFront.resume();
+        }
 
         addThreadEventListeners(threadFront);
 
-        await targetFront.attachConsole();
         newTargets[threadFront.actor] = targetFront;
       }
     } catch (e) {
       // If any of the workers have terminated since the list command initiated
       // then we will get errors. Ignore these.
     }
   }
 
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -566,28 +566,85 @@ Toolbox.prototype = {
       this.selectTool("jsdebugger", packet.why.type);
     }
   },
 
   _onResumedState: function() {
     this.unhighlightTool("jsdebugger");
   },
 
-  _startThreadFrontListeners: function() {
-    this.threadFront.on("paused", this._onPausedState);
-    this.threadFront.on("resumed", this._onResumedState);
+  /**
+   * Attach to a new top-level target.
+   * This method will attach to the top-level target, as well as any potential
+   * additional targets we may care about.
+   */
+  async _attachTargets(target) {
+    this._threadFront = await this._attachTarget(target);
+
+    const fissionSupport = Services.prefs.getBoolPref(
+      "devtools.browsertoolbox.fission"
+    );
+
+    if (fissionSupport && target.isParentProcess && !target.isAddon) {
+      const { mainRoot } = target.client;
+      const { processes } = await mainRoot.listProcesses();
+
+      for (const processDescriptor of processes) {
+        const targetFront = await processDescriptor.getTarget();
+
+        // Ignore the parent process target, which is the current target
+        if (targetFront === target) {
+          continue;
+        }
+
+        if (!targetFront) {
+          console.warn(
+            "Can't retrieve the target front for process",
+            processDescriptor
+          );
+          continue;
+        }
+        await this._attachTarget(targetFront);
+      }
+    }
+  },
+
+  /**
+   * This method focuses on attaching to one particular target.
+   * It ensure that the target actor is fully initialized and is watching for
+   * resources. We do that by calling its `attach` method.
+   * And we listen for thread actor events in order to update toolbox UI when
+   * we hit a breakpoint.
+   */
+  async _attachTarget(target) {
+    await target.attach();
+
+    // Start tracking network activity on toolbox open for targets such as tabs.
+    // (Workers and potentially others don't manage the console client in the target.)
+    if (target.activeConsole) {
+      await target.activeConsole.startListeners(["NetworkActivity"]);
+    }
+
+    const threadFront = await this._attachAndResumeThread(target);
+    this._startThreadFrontListeners(threadFront);
+    return threadFront;
+  },
+
+  _startThreadFrontListeners: function(threadFront) {
+    threadFront.on("paused", this._onPausedState);
+    threadFront.on("resumed", this._onResumedState);
   },
 
   _stopThreadFrontListeners: function() {
     this.threadFront.off("paused", this._onPausedState);
     this.threadFront.off("resumed", this._onResumedState);
   },
 
-  _attachAndResumeThread: async function() {
-    const [, threadFront] = await this._target.attachThread({
+  _attachAndResumeThread: async function(target) {
+    const [, threadFront] = await target.attachThread({
       autoBlackBox: false,
       ignoreFrameEnvironment: true,
       pauseOnExceptions: Services.prefs.getBoolPref(
         "devtools.debugger.pause-on-exceptions"
       ),
       ignoreCaughtExceptions: Services.prefs.getBoolPref(
         "devtools.debugger.ignore-caught-exceptions"
       ),
@@ -645,27 +702,17 @@ Toolbox.prototype = {
         domHelper.onceDOMReady(() => {
           resolve();
         }, this._URL);
       });
 
       // Optimization: fire up a few other things before waiting on
       // the iframe being ready (makes startup faster)
 
-      // Load the toolbox-level actor fronts and utilities now
-      await this._target.attach();
-
-      // Start tracking network activity on toolbox open for targets such as tabs.
-      // (Workers and potentially others don't manage the console client in the target.)
-      if (this._target.activeConsole) {
-        await this._target.activeConsole.startListeners(["NetworkActivity"]);
-      }
-
-      this._threadFront = await this._attachAndResumeThread();
-      this._startThreadFrontListeners();
+      await this._attachTargets(this.target);
 
       await domReady;
 
       this.browserRequire = BrowserLoader({
         window: this.win,
         useOnlyShared: true,
       }).require;