Bug 1573315 - The debugger should attach to content processes in-addition to workers. r=yulia,ochameau
authorJason Laster <jlaster@mozilla.com>
Thu, 15 Aug 2019 18:09:52 +0000
changeset 488322 956ab93a93c7ab130819d6d7c9a3fb05ec6093fa
parent 488321 c5f74c51b12fd2fda0a6e0e2fd4098ce72a74228
child 488323 195166db95ccaae18f182af7ef11e576b29d13e4
push id36440
push userncsoregi@mozilla.com
push dateFri, 16 Aug 2019 03:57:48 +0000
treeherdermozilla-central@a58b7dc85887 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyulia, ochameau
bugs1573315
milestone70.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 1573315 - The debugger should attach to content processes in-addition to workers. r=yulia,ochameau Differential Revision: https://phabricator.services.mozilla.com/D41650
devtools/client/debugger/src/client/firefox/commands.js
devtools/client/debugger/src/client/firefox/create.js
devtools/client/debugger/src/client/firefox/events.js
devtools/client/debugger/src/client/firefox/moz.build
devtools/client/debugger/src/client/firefox/targets.js
devtools/client/debugger/src/client/firefox/types.js
devtools/client/debugger/src/client/firefox/workers.js
devtools/client/debugger/src/utils/prefs.js
--- a/devtools/client/debugger/src/client/firefox/commands.js
+++ b/devtools/client/debugger/src/client/firefox/commands.js
@@ -1,17 +1,16 @@
 /* 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, createWorker } from "./create";
-import { supportsWorkers, updateWorkerTargets } from "./workers";
-import { features } from "../../utils/prefs";
+import { prepareSourcePayload, createTarget } from "./create";
+import { updateTargets } from "./targets";
 
 import Reps from "devtools-reps";
 import type { Node } from "devtools-reps";
 
 import type {
   ActorId,
   BreakpointLocation,
   BreakpointOptions,
@@ -22,51 +21,51 @@ import type {
   Script,
   SourceId,
   SourceActor,
   Worker,
   Range,
 } from "../../types";
 
 import type {
-  TabTarget,
+  Target,
   DebuggerClient,
   Grip,
   ThreadFront,
   ObjectClient,
   SourcesPacket,
 } from "./types";
 
 import type {
   EventListenerCategoryList,
   EventListenerActiveList,
 } from "../../actions/types";
 
-let workerTargets: Object;
-let threadFront: ThreadFront;
-let tabTarget: TabTarget;
+let targets: { [string]: Target };
+let currentThreadFront: ThreadFront;
+let currentTarget: Target;
 let debuggerClient: DebuggerClient;
 let sourceActors: { [ActorId]: SourceId };
 let breakpoints: { [string]: Object };
 let eventBreakpoints: ?EventListenerActiveList;
 let supportsWasm: boolean;
 
 type Dependencies = {
   threadFront: ThreadFront,
-  tabTarget: TabTarget,
+  tabTarget: Target,
   debuggerClient: DebuggerClient,
   supportsWasm: boolean,
 };
 
 function setupCommands(dependencies: Dependencies) {
-  threadFront = dependencies.threadFront;
-  tabTarget = dependencies.tabTarget;
+  currentThreadFront = dependencies.threadFront;
+  currentTarget = dependencies.tabTarget;
   debuggerClient = dependencies.debuggerClient;
   supportsWasm = dependencies.supportsWasm;
-  workerTargets = {};
+  targets = {};
   sourceActors = {};
   breakpoints = {};
 }
 
 function hasWasmSupport() {
   return supportsWasm;
 }
 
@@ -94,42 +93,44 @@ function releaseActor(actor: String) {
   return debuggerClient.release(actor);
 }
 
 function sendPacket(packet: Object) {
   return debuggerClient.request(packet);
 }
 
 function lookupTarget(thread: string) {
-  if (thread == threadFront.actor) {
-    return tabTarget;
+  if (thread == currentThreadFront.actor) {
+    return currentTarget;
   }
-  if (!workerTargets[thread]) {
+
+  if (!targets[thread]) {
     throw new Error(`Unknown thread front: ${thread}`);
   }
-  return workerTargets[thread];
+
+  return targets[thread];
 }
 
 function lookupThreadFront(thread: string) {
   const target = lookupTarget(thread);
   return target.threadFront;
 }
 
-function listWorkerThreadFronts() {
-  return (Object.values(workerTargets): any).map(target => target.threadFront);
+function listThreadFronts() {
+  return (Object.values(targets): any).map(target => target.threadFront);
 }
 
 function forEachThread(iteratee) {
   // We have to be careful here to atomically initiate the operation on every
   // thread, with no intervening await. Otherwise, other code could run and
   // trigger additional thread operations. Requests on server threads will
   // resolve in FIFO order, and this could result in client and server state
   // going out of sync.
 
-  const promises = [threadFront, ...listWorkerThreadFronts()].map(
+  const promises = [currentThreadFront, ...listThreadFronts()].map(
     // If a thread shuts down while sending the message then it will
     // throw. Ignore these exceptions.
     t => iteratee(t).catch(e => console.log(e))
   );
 
   return Promise.all(promises);
 }
 
@@ -167,49 +168,53 @@ async function sourceContents({
 }: SourceActor): Promise<{| source: any, contentType: ?string |}> {
   const sourceThreadFront = lookupThreadFront(thread);
   const sourceFront = sourceThreadFront.source({ actor });
   const { source, contentType } = await sourceFront.source();
   return { source, contentType };
 }
 
 function setXHRBreakpoint(path: string, method: string) {
-  return threadFront.setXHRBreakpoint(path, method);
+  return currentThreadFront.setXHRBreakpoint(path, method);
 }
 
 function removeXHRBreakpoint(path: string, method: string) {
-  return threadFront.removeXHRBreakpoint(path, method);
+  return currentThreadFront.removeXHRBreakpoint(path, method);
 }
 
 // Get the string key to use for a breakpoint location.
 // See also duplicate code in breakpoint-actor-map.js :(
 function locationKey(location: BreakpointLocation) {
   const { sourceUrl, line, column } = location;
   const sourceId = location.sourceId || "";
   // $FlowIgnore
   return `${sourceUrl}:${sourceId}:${line}:${column}`;
 }
 
 function detachWorkers() {
-  for (const thread of listWorkerThreadFronts()) {
+  for (const thread of listThreadFronts()) {
     thread.detach();
   }
 }
 
 function maybeGenerateLogGroupId(options) {
-  if (options.logValue && tabTarget.traits && tabTarget.traits.canRewind) {
+  if (
+    options.logValue &&
+    currentTarget.traits &&
+    currentTarget.traits.canRewind
+  ) {
     return { ...options, logGroupId: `logGroup-${Math.random()}` };
   }
   return options;
 }
 
 function maybeClearLogpoint(location: BreakpointLocation) {
   const bp = breakpoints[locationKey(location)];
-  if (bp && bp.options.logGroupId && tabTarget.activeConsole) {
-    tabTarget.activeConsole.emit(
+  if (bp && bp.options.logGroupId && currentTarget.activeConsole) {
+    currentTarget.activeConsole.emit(
       "clearLogpointMessages",
       bp.options.logGroupId
     );
   }
 }
 
 function hasBreakpoint(location: BreakpointLocation) {
   return !!breakpoints[locationKey(location)];
@@ -243,53 +248,53 @@ async function evaluateExpressions(scrip
 
 type EvaluateParam = { thread: string, frameId: ?FrameId };
 
 function evaluate(
   script: ?Script,
   { thread, frameId }: EvaluateParam = {}
 ): Promise<{ result: Grip | null }> {
   const params = { thread, frameActor: frameId };
-  if (!tabTarget || !script) {
+  if (!currentTarget || !script) {
     return Promise.resolve({ result: null });
   }
 
-  const target = thread ? lookupTarget(thread) : tabTarget;
+  const target = thread ? lookupTarget(thread) : currentTarget;
   const console = target.activeConsole;
   if (!console) {
     return Promise.resolve({ result: null });
   }
 
   return console.evaluateJSAsync(script, params);
 }
 
 function autocomplete(
   input: string,
   cursor: number,
   frameId: ?string
 ): Promise<mixed> {
-  if (!tabTarget || !tabTarget.activeConsole || !input) {
+  if (!currentTarget || !currentTarget.activeConsole || !input) {
     return Promise.resolve({});
   }
   return new Promise(resolve => {
-    tabTarget.activeConsole.autocomplete(
+    currentTarget.activeConsole.autocomplete(
       input,
       cursor,
       result => resolve(result),
       frameId
     );
   });
 }
 
 function navigate(url: string): Promise<*> {
-  return tabTarget.navigateTo({ url });
+  return currentTarget.navigateTo({ url });
 }
 
 function reload(): Promise<*> {
-  return tabTarget.reload();
+  return currentTarget.reload();
 }
 
 function getProperties(thread: string, grip: Grip): Promise<*> {
   const objClient = lookupThreadFront(thread).pauseGrip(grip);
 
   return objClient.getPrototypeAndProperties().then(resp => {
     const { ownProperties, safeGetterValues } = resp;
     for (const name in safeGetterValues) {
@@ -323,17 +328,17 @@ function pauseOnExceptions(
   );
 }
 
 async function blackBox(
   sourceActor: SourceActor,
   isBlackBoxed: boolean,
   range?: Range
 ): Promise<*> {
-  const sourceFront = threadFront.source({ actor: sourceActor.actor });
+  const sourceFront = currentThreadFront.source({ actor: sourceActor.actor });
   if (isBlackBoxed) {
     await sourceFront.unblackBox(range);
   } else {
     await sourceFront.blackBox(range);
   }
 }
 
 function setSkipPausing(shouldSkip: boolean) {
@@ -351,17 +356,17 @@ function setEventListenerBreakpoints(ids
 }
 
 // eslint-disable-next-line
 async function getEventListenerBreakpointTypes(): Promise<
   EventListenerCategoryList
 > {
   let categories;
   try {
-    categories = await threadFront.getAvailableEventBreakpoints();
+    categories = await currentThreadFront.getAvailableEventBreakpoints();
 
     if (!Array.isArray(categories)) {
       // When connecting to older browser that had our placeholder
       // implementation of the 'getAvailableEventBreakpoints' endpoint, we
       // actually get back an object with a 'value' property containing
       // the categories. Since that endpoint wasn't actually backed with a
       // functional implementation, we just bail here instead of storing the
       // 'value' property into the categories.
@@ -385,73 +390,57 @@ async function getSources(
   client: ThreadFront
 ): Promise<Array<GeneratedSourceData>> {
   const { sources }: SourcesPacket = await client.getSources();
 
   return sources.map(source => prepareSourcePayload(client, source));
 }
 
 async function fetchSources(): Promise<Array<GeneratedSourceData>> {
-  return getSources(threadFront);
+  return getSources(currentThreadFront);
 }
 
 function getSourceForActor(actor: ActorId) {
   if (!sourceActors[actor]) {
     throw new Error(`Unknown source actor: ${actor}`);
   }
   return sourceActors[actor];
 }
 
 async function fetchWorkers(): Promise<Worker[]> {
-  if (features.windowlessWorkers) {
-    const options = {
-      breakpoints,
-      eventBreakpoints,
-      observeAsmJS: true,
-    };
-
-    const newWorkerTargets = await updateWorkerTargets({
-      tabTarget,
-      debuggerClient,
-      threadFront,
-      workerTargets,
-      options,
-    });
+  const options = {
+    breakpoints,
+    eventBreakpoints,
+    observeAsmJS: true,
+  };
 
-    // Fetch the sources and install breakpoints on any new workers.
-    const workerNames = Object.getOwnPropertyNames(newWorkerTargets);
-    for (const actor of workerNames) {
-      if (!workerTargets[actor]) {
-        const front = newWorkerTargets[actor].threadFront;
+  const newTargets = await updateTargets({
+    currentTarget,
+    debuggerClient,
+    targets,
+    options,
+  });
 
-        // This runs in the background and populates some data, but we also
-        // want to allow it to fail quietly. For instance, it is pretty easy
-        // for source clients to throw during the fetch if their thread
-        // shuts down, and this would otherwise cause test failures.
-        getSources(front).catch(e => console.error(e));
-      }
+  // Fetch the sources and install breakpoints on any new workers.
+  // NOTE: This runs in the background and fails quitely because it is
+  // pretty easy for sources to throw during the fetch if their thread
+  // shuts down, which would cause test failures.
+  for (const actor in newTargets) {
+    if (!targets[actor]) {
+      const { threadFront } = newTargets[actor];
+      getSources(threadFront).catch(e => console.error(e));
     }
-
-    workerTargets = newWorkerTargets;
-
-    return workerNames.map(actor =>
-      createWorker(actor, workerTargets[actor].url)
-    );
   }
 
-  if (!supportsWorkers(tabTarget)) {
-    return Promise.resolve([]);
-  }
-
-  const { workers } = await tabTarget.listWorkers();
-  return workers;
+  targets = newTargets;
+  return Object.keys(targets).map(id => createTarget(id, targets[id]));
 }
 
 function getMainThread() {
-  return threadFront.actor;
+  return currentThreadFront.actor;
 }
 
 async function getSourceActorBreakpointPositions(
   { thread, actor }: SourceActor,
   range: Range
 ): Promise<{ [number]: number[] }> {
   const sourceThreadFront = lookupThreadFront(thread);
   const sourceFront = sourceThreadFront.source({ actor });
--- a/devtools/client/debugger/src/client/firefox/create.js
+++ b/devtools/client/debugger/src/client/firefox/create.js
@@ -1,22 +1,23 @@
 /* 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
 // This module converts Firefox specific types to the generic types
 
-import type { Frame, ThreadId, GeneratedSourceData } from "../../types";
+import type { Frame, ThreadId, GeneratedSourceData, Worker } from "../../types";
 import type {
   PausedPacket,
   FramesResponse,
   FramePacket,
   SourcePayload,
   ThreadFront,
+  Target,
 } from "./types";
 
 import { clientCommands } from "./commands";
 
 export function prepareSourcePayload(
   client: ThreadFront,
   source: SourcePayload
 ): GeneratedSourceData {
@@ -67,17 +68,17 @@ export function createPause(
   return {
     ...packet,
     thread,
     frame: createFrame(thread, frame),
     frames: response.frames.map(createFrame.bind(null, thread)),
   };
 }
 
-export function createWorker(actor: string, url: string) {
+export function createTarget(actor: string, target: Target): Worker {
   return {
     actor,
-    url,
+    url: target.url || "",
     // Ci.nsIWorkerDebugger.TYPE_DEDICATED
-    type: 0,
+    type: actor.includes("process") ? 1 : 0,
     name: "",
   };
 }
--- a/devtools/client/debugger/src/client/firefox/events.js
+++ b/devtools/client/debugger/src/client/firefox/events.js
@@ -4,27 +4,27 @@
 
 // @flow
 
 import type {
   SourcePacket,
   PausedPacket,
   ThreadFront,
   Actions,
-  TabTarget,
+  Target,
 } from "./types";
 
 import { createPause, prepareSourcePayload } from "./create";
 import sourceQueue from "../../utils/source-queue";
 
 const CALL_STACK_PAGE_SIZE = 1000;
 
 type Dependencies = {
   threadFront: ThreadFront,
-  tabTarget: TabTarget,
+  tabTarget: Target,
   actions: Actions,
 };
 
 let actions: Actions;
 let isInterrupted: boolean;
 
 function addThreadEventListeners(thread: ThreadFront) {
   Object.keys(clientEvents).forEach(eventName => {
--- a/devtools/client/debugger/src/client/firefox/moz.build
+++ b/devtools/client/debugger/src/client/firefox/moz.build
@@ -6,10 +6,10 @@
 DIRS += [
 
 ]
 
 CompiledModules(
     'commands.js',
     'create.js',
     'events.js',
-    'workers.js',
+    'targets.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/src/client/firefox/targets.js
@@ -0,0 +1,77 @@
+/* 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 { addThreadEventListeners } from "./events";
+import { prefs } from "../../utils/prefs";
+import type { DebuggerClient, Target } from "./types";
+
+type Args = {
+  currentTarget: Target,
+  debuggerClient: DebuggerClient,
+  targets: { [string]: Target },
+  options: Object,
+};
+
+async function attachTargets(targetLists, { options, targets }: Args) {
+  const newTargets = {};
+
+  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(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.
+    }
+  }
+
+  return newTargets;
+}
+
+export async function updateWorkerTargets(args: Args) {
+  const { currentTarget } = args;
+  if (!currentTarget.isBrowsingContext || currentTarget.isContentProcess) {
+    return {};
+  }
+
+  const { workers } = await currentTarget.listWorkers();
+  return attachTargets(workers, args);
+}
+
+export async function updateProcessTargets(args: Args): Promise<*> {
+  const { currentTarget, debuggerClient } = args;
+  if (!prefs.fission || !currentTarget.chrome || currentTarget.isAddon) {
+    return Promise.resolve({});
+  }
+
+  const { processes } = await debuggerClient.mainRoot.listProcesses();
+  const targets = await Promise.all(
+    processes
+      .filter(descriptor => !descriptor.isParent)
+      .map(descriptor => descriptor.getTarget())
+  );
+
+  return attachTargets(targets, args);
+}
+
+export async function updateTargets(args: Args) {
+  const workers = await updateWorkerTargets(args);
+  const processes = await updateProcessTargets(args);
+  return { ...workers, ...processes };
+}
--- a/devtools/client/debugger/src/client/firefox/types.js
+++ b/devtools/client/debugger/src/client/firefox/types.js
@@ -184,53 +184,59 @@ export type TabPayload = {
 export type Actions = {
   paused: Pause => void,
   resumed: ActorId => void,
   newQueuedSources: (QueuedSourceData[]) => void,
   fetchEventListeners: () => void,
   updateWorkers: () => void,
 };
 
+type ConsoleClient = {
+  evaluateJS: (
+    script: Script,
+    func: Function,
+    params?: { frameActor: ?FrameId }
+  ) => void,
+  evaluateJSAsync: (
+    script: Script,
+    func: Function,
+    params?: { frameActor: ?FrameId }
+  ) => Promise<{ result: Grip | null }>,
+  autocomplete: (
+    input: string,
+    cursor: number,
+    func: Function,
+    frameId: ?string
+  ) => void,
+  emit: (string, any) => void,
+};
+
 /**
  * Tab Target gives access to the browser tabs
  * @memberof firefox
  * @static
  */
-export type TabTarget = {
+export type Target = {
   on: (string, Function) => void,
   emit: (string, any) => void,
-  threadFront: ThreadFront,
-  activeConsole: {
-    evaluateJS: (
-      script: Script,
-      func: Function,
-      params?: { frameActor: ?FrameId }
-    ) => void,
-    evaluateJSAsync: (
-      script: Script,
-      func: Function,
-      params?: { frameActor: ?FrameId }
-    ) => Promise<{ result: Grip | null }>,
-    autocomplete: (
-      input: string,
-      cursor: number,
-      func: Function,
-      frameId: ?string
-    ) => void,
-    emit: (string, any) => void,
-  },
   form: { consoleActor: any },
   root: any,
   navigateTo: ({ url: string }) => Promise<*>,
   listWorkers: () => Promise<*>,
   reload: () => Promise<*>,
   destroy: () => void,
+  threadFront: ThreadFront,
+  activeConsole: ConsoleClient,
+
   isBrowsingContext: boolean,
   isContentProcess: boolean,
   traits: Object,
+  chrome: Boolean,
+  url: string,
+  isAddon: Boolean,
 };
 
 /**
  * Clients for accessing the Firefox debug server and browser
  * @memberof firefox/clients
  * @static
  */
 
@@ -242,29 +248,26 @@ export type TabTarget = {
 export type DebuggerClient = {
   _activeRequests: {
     get: any => any,
     delete: any => void,
   },
   mainRoot: {
     traits: any,
     getFront: string => Promise<*>,
+    listProcesses: () => Promise<{ processes: ProcessDescriptor }>,
   },
   connect: () => Promise<*>,
   request: (packet: Object) => Promise<*>,
   attachConsole: (actor: String, listeners: Array<*>) => Promise<*>,
   createObjectClient: (grip: Grip) => {},
   release: (actor: String) => {},
 };
 
-export type TabClient = {
-  listWorkers: () => Promise<*>,
-  addListener: (string, Function) => void,
-  on: (string, Function) => void,
-};
+type ProcessDescriptor = Object;
 
 /**
  * A grip is a JSON value that refers to a specific JavaScript value in the
  * debuggee. Grips appear anywhere an arbitrary value from the debuggee needs
  * to be conveyed to the client: stack frames, object property lists, lexical
  * environments, paused packets, and so on.
  *
  * For mutable values like objects and arrays, grips do not merely convey the
@@ -359,24 +362,25 @@ export type ThreadFront = {
   interrupt: () => Promise<*>,
   eventListeners: () => Promise<*>,
   getFrames: (number, number) => FramesResponse,
   getEnvironment: (frame: Frame) => Promise<*>,
   on: (string, Function) => void,
   getSources: () => Promise<SourcesPacket>,
   reconfigure: ({ observeAsmJS: boolean }) => Promise<*>,
   getLastPausePacket: () => ?PausedPacket,
-  _parent: TabClient,
+  _parent: Target,
   actor: ActorId,
   actorID: ActorId,
   request: (payload: Object) => Promise<*>,
   url: string,
-  setActiveEventBreakpoints: (string[]) => void,
+  setActiveEventBreakpoints: (string[]) => Promise<void>,
   getAvailableEventBreakpoints: () => Promise<EventListenerCategoryList>,
   skipBreakpoints: boolean => Promise<{| skip: boolean |}>,
+  detach: () => Promise<void>,
 };
 
 export type Panel = {|
   emit: (eventName: string) => void,
   openLink: (url: string) => void,
   openWorkerToolbox: (worker: Worker) => void,
   openElementInInspector: (grip: Object) => void,
   openConsoleAndEvaluate: (input: string) => void,
deleted file mode 100644
--- a/devtools/client/debugger/src/client/firefox/workers.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/* 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 { addThreadEventListeners } from "./events";
-import type { TabTarget } from "./types";
-
-export function supportsWorkers(tabTarget: TabTarget) {
-  return tabTarget.isBrowsingContext || tabTarget.isContentProcess;
-}
-
-export async function updateWorkerTargets({
-  tabTarget,
-  debuggerClient,
-  threadFront,
-  workerTargets,
-  options,
-}: Object) {
-  if (!supportsWorkers(tabTarget)) {
-    return {};
-  }
-
-  const newWorkerTargets = {};
-
-  const { workers } = await tabTarget.listWorkers();
-  for (const workerTargetFront of workers) {
-    try {
-      await workerTargetFront.attach();
-      const threadActorID = workerTargetFront._threadActor;
-      if (workerTargets[threadActorID]) {
-        newWorkerTargets[threadActorID] = workerTargets[threadActorID];
-      } else {
-        const [, workerThread] = await workerTargetFront.attachThread(options);
-        workerThread.resume();
-
-        addThreadEventListeners(workerThread);
-
-        const consoleFront = await workerTargetFront.getFront("console");
-        await consoleFront.startListeners([]);
-
-        newWorkerTargets[workerThread.actor] = workerTargetFront;
-      }
-    } catch (e) {
-      // If any of the workers have terminated since the list command initiated
-      // then we will get errors. Ignore these.
-    }
-  }
-
-  return newWorkerTargets;
-}
--- a/devtools/client/debugger/src/utils/prefs.js
+++ b/devtools/client/debugger/src/utils/prefs.js
@@ -9,16 +9,17 @@ import { isDevelopment } from "devtools-
 import Services from "devtools-services";
 
 // Schema version to bump when the async store format has changed incompatibly
 // and old stores should be cleared.
 const prefsSchemaVersion = 11;
 const pref = Services.pref;
 
 if (isDevelopment()) {
+  pref("devtools.browsertoolbox.fission", false);
   pref("devtools.debugger.logging", false);
   pref("devtools.debugger.alphabetize-outline", false);
   pref("devtools.debugger.auto-pretty-print", false);
   pref("devtools.source-map.client-service.enabled", true);
   pref("devtools.chrome.enabled", false);
   pref("devtools.debugger.pause-on-exceptions", false);
   pref("devtools.debugger.pause-on-caught-exceptions", false);
   pref("devtools.debugger.ignore-caught-exceptions", true);
@@ -69,16 +70,17 @@ if (isDevelopment()) {
   pref("devtools.debugger.features.dom-mutation-breakpoints", true);
   pref("devtools.debugger.features.log-points", true);
   pref("devtools.debugger.features.inline-preview", true);
   pref("devtools.debugger.log-actions", true);
   pref("devtools.debugger.features.overlay-step-buttons", false);
 }
 
 export const prefs = new PrefsHelper("devtools", {
+  fission: ["Bool", "browsertoolbox.fission"],
   logging: ["Bool", "debugger.logging"],
   editorWrapping: ["Bool", "debugger.ui.editor-wrapping"],
   alphabetizeOutline: ["Bool", "debugger.alphabetize-outline"],
   autoPrettyPrint: ["Bool", "debugger.auto-pretty-print"],
   clientSourceMapsEnabled: ["Bool", "source-map.client-service.enabled"],
   chromeAndExtenstionsEnabled: ["Bool", "chrome.enabled"],
   pauseOnExceptions: ["Bool", "debugger.pause-on-exceptions"],
   pauseOnCaughtExceptions: ["Bool", "debugger.pause-on-caught-exceptions"],