Bug 1578242 - Make the inspector use the TargetList. r=gl,pbro
authorAlexandre Poirot <poirot.alex@gmail.com>
Mon, 18 Nov 2019 15:06:02 +0000
changeset 502444 820aca345d9e018e23d3273ff0cb7d1b7883bff3
parent 502443 28e92798b7fba8f17c9facb00cc66d845ef73779
child 502445 7a519d43db7f8cfded9f05d388926fffcb8146a2
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl, pbro
bugs1578242
milestone72.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 1578242 - Make the inspector use the TargetList. r=gl,pbro Differential Revision: https://phabricator.services.mozilla.com/D48859
devtools/client/framework/toolbox.js
devtools/client/inspector/fonts/fonts.js
devtools/client/inspector/grids/grid-inspector.js
devtools/client/inspector/inspector-search.js
devtools/client/inspector/inspector.js
devtools/client/inspector/markup/markup.js
devtools/client/inspector/node-picker.js
devtools/client/inspector/shared/reflow-tracker.js
devtools/client/inspector/shared/style-change-tracker.js
devtools/shared/fronts/inspector.js
devtools/shared/fronts/walker.js
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -354,17 +354,17 @@ Toolbox.prototype = {
 
   _prefs: {
     LAST_TOOL: "devtools.toolbox.selectedTool",
     SIDE_ENABLED: "devtools.toolbox.sideEnabled",
   },
 
   get nodePicker() {
     if (!this._nodePicker) {
-      this._nodePicker = new NodePicker(this.target, this.selection);
+      this._nodePicker = new NodePicker(this.targetList, this.selection);
       this._nodePicker.on("picker-starting", this._onPickerStarting);
       this._nodePicker.on("picker-started", this._onPickerStarted);
       this._nodePicker.on("picker-stopped", this._onPickerStopped);
       this._nodePicker.on("picker-node-canceled", this._onPickerCanceled);
       this._nodePicker.on("picker-node-picked", this._onPickerPicked);
       this._nodePicker.on("picker-node-previewed", this._onPickerPreviewed);
     }
 
--- a/devtools/client/inspector/fonts/fonts.js
+++ b/devtools/client/inspector/fonts/fonts.js
@@ -367,17 +367,17 @@ class FontInspector {
   }
 
   async getAllFonts(options) {
     // In case we've been destroyed in the meantime
     if (!this.document) {
       return [];
     }
 
-    const inspectorFronts = await this.inspector.inspectorFront.getAllInspectorFronts();
+    const inspectorFronts = await this.inspector.getAllInspectorFronts();
 
     let allFonts = [];
     for (const { pageStyle } of inspectorFronts) {
       allFonts = allFonts.concat(await pageStyle.getAllUsedFontFaces(options));
     }
 
     return allFonts;
   }
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -148,17 +148,17 @@ class GridInspector {
   }
 
   /**
    * Get the LayoutActor fronts for all interesting targets where we have inspectors.
    *
    * @return {Array} The list of LayoutActor fronts
    */
   async getLayoutFronts() {
-    const inspectorFronts = await this.inspector.inspectorFront.getAllInspectorFronts();
+    const inspectorFronts = await this.inspector.getAllInspectorFronts();
 
     const layoutFronts = [];
     for (const { walker } of inspectorFronts) {
       const layoutFront = await walker.getLayoutInspector();
       layoutFronts.push(layoutFront);
     }
 
     return layoutFronts;
--- a/devtools/client/inspector/inspector-search.js
+++ b/devtools/client/inspector/inspector-search.js
@@ -508,17 +508,17 @@ SelectorAutocompleter.prototype = {
       query = query.slice(0, query.length - firstPart.length - 1);
     }
     // TODO: implement some caching so that over the wire request is not made
     // everytime.
     if (/[\s+>~]$/.test(query)) {
       query += "*";
     }
 
-    this._lastQuery = this.inspector.inspectorFront
+    this._lastQuery = this.inspector
       // Get all inspectors where we want suggestions from.
       .getAllInspectorFronts()
       .then(inspectors => {
         // Get all of the suggestions.
         return Promise.all(
           inspectors.map(async ({ walker }) => {
             return walker.getSuggestionsForQuery(query, firstPart, state);
           })
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -171,16 +171,18 @@ function Inspector(toolbox) {
   // Map [panel id => panel instance]
   // Stores all the instances of sidebar panels like rule view, computed view, ...
   this._panels = new Map();
 
   this._clearSearchResultsLabel = this._clearSearchResultsLabel.bind(this);
   this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(
     this
   );
+  this._onTargetAvailable = this._onTargetAvailable.bind(this);
+  this._onTargetDestroyed = this._onTargetDestroyed.bind(this);
   this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
   this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
   this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
 
   this.onDetached = this.onDetached.bind(this);
   this.onHostChanged = this.onHostChanged.bind(this);
   this.onMarkupLoaded = this.onMarkupLoaded.bind(this);
   this.onNewSelection = this.onNewSelection.bind(this);
@@ -215,49 +217,105 @@ Inspector.prototype = {
         dbg = await this._toolbox.loadTool("jsdebugger");
       }
       this._replayResumed = !dbg.isPaused();
 
       this.currentTarget.threadFront.on("paused", this.handleThreadPaused);
       this.currentTarget.threadFront.on("resumed", this.handleThreadResumed);
     }
 
-    await this.initInspectorFront();
-
-    this.currentTarget.on("will-navigate", this._onBeforeNavigate);
-
-    await Promise.all([
-      this._getCssProperties(),
-      this._getPageStyle(),
-      this._getDefaultSelection(),
-      this._getAccessibilityFront(),
-      this._getChangesFront(),
-    ]);
+    await this.toolbox.targetList.watchTargets(
+      [this.toolbox.targetList.TYPES.FRAME],
+      this._onTargetAvailable,
+      this._onTargetDestroyed
+    );
 
     // Store the URL of the target page prior to navigation in order to ensure
     // telemetry counts in the Grid Inspector are not double counted on reload.
     this.previousURL = this.currentTarget.url;
     this.reflowTracker = new ReflowTracker(this.currentTarget);
     this.styleChangeTracker = new InspectorStyleChangeTracker(this);
 
     this._markupBox = this.panelDoc.getElementById("markup-box");
 
     return this._deferredOpen();
   },
 
-  async initInspectorFront() {
-    this.inspectorFront = await this.currentTarget.getFront("inspector");
+  async _onTargetAvailable(type, targetFront, isTopLevel) {
+    // Ignore all targets but the top level one
+    if (!isTopLevel) {
+      return;
+    }
+
+    await this.initInspectorFront(targetFront);
+
+    targetFront.on("will-navigate", this._onBeforeNavigate);
+
+    await Promise.all([
+      this._getCssProperties(),
+      this._getPageStyle(),
+      this._getDefaultSelection(),
+      this._getAccessibilityFront(),
+      this._getChangesFront(),
+    ]);
+    this.reflowTracker = new ReflowTracker(this.currentTarget);
+
+    // When we navigate to another process and switch to a new
+    // target and the inspector is already ready, we want to
+    // update the markup view accordingly. So force a new-root event.
+    // For the initial panel startup, the initial top level target
+    // update the markup view from _deferredOpen.
+    // We might want to followup here in order to share the same
+    // codepath between the initial top level target and the next
+    // one we switch to. i.e. extract from deferredOpen code
+    // which has to be called only once on inspector startup.
+    // Then move the rest to onNewRoot and always call onNewRoot from here.
+    if (this.isReady) {
+      this.onNewRoot();
+    }
+  },
+
+  _onTargetDestroyed(type, targetFront, isTopLevel) {
+    // Ignore all targets but the top level one
+    if (!isTopLevel) {
+      return;
+    }
+    targetFront.off("will-navigate", this._onBeforeNavigate);
+
+    this._defaultNode = null;
+    this.selection.setNodeFront(null);
+
+    this.reflowTracker.destroy();
+    this.reflowTracker = null;
+  },
+
+  async initInspectorFront(targetFront) {
+    this.inspectorFront = await targetFront.getFront("inspector");
     this.highlighter = this.inspectorFront.highlighter;
     this.walker = this.inspectorFront.walker;
   },
 
   get toolbox() {
     return this._toolbox;
   },
 
+  /**
+   * Get the list of InspectorFront instances that correspond to all of the inspectable
+   * targets in remote frames nested within the document inspected here, as well as the
+   * current InspectorFront instance.
+   *
+   * @return {Array} The list of InspectorFront instances.
+   */
+  async getAllInspectorFronts() {
+    return this.toolbox.targetList.getAllFronts(
+      this.toolbox.targetList.TYPES.FRAME,
+      "inspector"
+    );
+  },
+
   get highlighters() {
     if (!this._highlighters) {
       this._highlighters = new HighlightersOverlay(this);
     }
 
     return this._highlighters;
   },
 
@@ -498,20 +556,20 @@ Inspector.prototype = {
           );
         }
         this._defaultNode = node;
         return node;
       });
   },
 
   /**
-   * Target getter.
+   * Top level target front getter.
    */
   get currentTarget() {
-    return this._target;
+    return this.toolbox.targetList.targetFront;
   },
 
   /**
    * Hooks the searchbar to show result and auto completion suggestions.
    */
   setupSearchBox: function() {
     this.searchBox = this.panelDoc.getElementById("inspector-searchbox");
     this.searchClearButton = this.panelDoc.getElementById(
@@ -1413,28 +1471,28 @@ Inspector.prototype = {
    */
   set selectionCssSelectors(cssSelectors = []) {
     if (this._destroyed) {
       return;
     }
 
     this._selectionCssSelectors = {
       selectors: cssSelectors,
-      url: this._target.url,
+      url: this.currentTarget.url,
     };
   },
 
   /**
    * Get the CSS selectors for the current selection if any, that is, if a node
    * is actually selected and that node has been selected while on the same url
    */
   get selectionCssSelectors() {
     if (
       this._selectionCssSelectors &&
-      this._selectionCssSelectors.url === this._target.url
+      this._selectionCssSelectors.url === this.currentTarget.url
     ) {
       return this._selectionCssSelectors.selectors;
     }
     return [];
   },
 
   /**
    * Some inspector ruleview helpers rely on the selectionCssSelector to get the
@@ -1666,20 +1724,25 @@ Inspector.prototype = {
     if (this.ruleViewSideBar) {
       this.ruleViewSideBar.destroy();
     }
     this._destroyMarkup();
 
     this.teardownToolbar();
 
     this.breadcrumbs.destroy();
-    this.reflowTracker.destroy();
     this.styleChangeTracker.destroy();
     this.searchboxShortcuts.destroy();
 
+    this.toolbox.targetList.unwatchTargets(
+      [this.toolbox.targetList.TYPES.FRAME],
+      this._onTargetAvailable,
+      this._onTargetDestroyed
+    );
+
     this._is3PaneModeChromeEnabled = null;
     this._is3PaneModeEnabled = null;
     this._markupBox = null;
     this._markupFrame = null;
     this._target = null;
     this._toolbox = null;
     this.breadcrumbs = null;
     this.panelDoc = null;
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -395,17 +395,17 @@ MarkupView.prototype = {
       this._undo.installController(this.controllerWindow);
     }
 
     return this._undo;
   },
 
   init: async function() {
     try {
-      this.inspectorFronts = await this.inspector.inspectorFront.getAllInspectorFronts();
+      this.inspectorFronts = await this.inspector.getAllInspectorFronts();
     } catch (e) {
       // This call might fail if called asynchrously after the toolbox is finished
       // closing.
       return;
     }
 
     for (const { walker } of this.inspectorFronts) {
       walker.on("display-change", this._onWalkerNodeStatesChanged);
--- a/devtools/client/inspector/node-picker.js
+++ b/devtools/client/inspector/node-picker.js
@@ -12,26 +12,26 @@ loader.lazyRequireGetter(this, "EventEmi
  */
 
 /**
  * Get the NodePicker instance for an inspector front.
  * The NodePicker wraps the highlighter so that it can interact with the
  * walkerFront and selection api. The nodeFront is stateless, with the
  * HighlighterFront managing it's own state.
  *
- * @param {Target} target
- *        The target the toolbox will debug
+ * @param {TargetList} targetList
+ *        The TargetList component referencing all the targets to be debugged
  * @param {Selection} selection
  *        The global Selection object
  */
 class NodePicker extends EventEmitter {
-  constructor(target, selection) {
+  constructor(targetList, selection) {
     super();
 
-    this.target = target;
+    this.targetList = targetList;
     this.selection = selection;
 
     // Whether or not the node picker is active.
     this.isPicking = false;
 
     // The list of inspector fronts corresponding to the frames where picking happens.
     this._currentInspectorFronts = [];
 
@@ -75,18 +75,20 @@ class NodePicker extends EventEmitter {
       return;
     }
     this.isPicking = true;
 
     this.emit("picker-starting");
 
     // Get all the inspector fronts where the picker should start, and cache them locally
     // so we can stop the picker when needed for the same list of inspector fronts.
-    const inspectorFront = await this.target.getFront("inspector");
-    this._currentInspectorFronts = await inspectorFront.getAllInspectorFronts();
+    this._currentInspectorFronts = await this.targetList.getAllFronts(
+      this.targetList.TYPES.FRAME,
+      "inspector"
+    );
 
     for (const { walker, highlighter } of this._currentInspectorFronts) {
       walker.on("picker-node-hovered", this._onHovered);
       walker.on("picker-node-picked", this._onPicked);
       walker.on("picker-node-previewed", this._onPreviewed);
       walker.on("picker-node-canceled", this._onCanceled);
 
       await highlighter.pick(doFocus);
--- a/devtools/client/inspector/shared/reflow-tracker.js
+++ b/devtools/client/inspector/shared/reflow-tracker.js
@@ -19,17 +19,18 @@ function ReflowTracker(target) {
 
   this.reflowFront = null;
 
   this.onReflow = this.onReflow.bind(this);
 }
 
 ReflowTracker.prototype = {
   destroy() {
-    if (this.reflowFront) {
+    // Ensure that the front isn't yet destroyed
+    if (this.reflowFront && this.reflowFront.actorID) {
       this.stopTracking();
       this.reflowFront.destroy();
       this.reflowFront = null;
     }
 
     this.listeners.clear();
   },
 
--- a/devtools/client/inspector/shared/style-change-tracker.js
+++ b/devtools/client/inspector/shared/style-change-tracker.js
@@ -28,17 +28,17 @@ class InspectorStyleChangeTracker {
 
     EventEmitter.decorate(this);
   }
 
   async init() {
     try {
       // TODO: Bug 1588868 - Get all the inspector fronts whenever targets changes or
       // are added or removed.
-      this.inspectorFronts = await this.inspector.inspectorFront.getAllInspectorFronts();
+      this.inspectorFronts = await this.inspector.getAllInspectorFronts();
     } catch (e) {
       // This call might fail if called asynchrously after the toolbox is finished
       // closing.
       return;
     }
 
     for (const { walker } of this.inspectorFronts) {
       walker.on("mutations", this.onMutations);
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -14,17 +14,16 @@ const { inspectorSpec } = require("devto
 loader.lazyRequireGetter(this, "flags", "devtools/shared/flags");
 
 const TELEMETRY_EYEDROPPER_OPENED = "DEVTOOLS_EYEDROPPER_OPENED_COUNT";
 const TELEMETRY_EYEDROPPER_OPENED_MENU =
   "DEVTOOLS_MENU_EYEDROPPER_OPENED_COUNT";
 const SHOW_ALL_ANONYMOUS_CONTENT_PREF =
   "devtools.inspector.showAllAnonymousContent";
 const SHOW_UA_SHADOW_ROOTS_PREF = "devtools.inspector.showUserAgentShadowRoots";
-const BROWSER_FISSION_ENABLED_PREF = "devtools.browsertoolbox.fission";
 const CONTENT_FISSION_ENABLED_PREF = "devtools.contenttoolbox.fission";
 const USE_NEW_BOX_MODEL_HIGHLIGHTER_PREF =
   "devtools.inspector.use-new-box-model-highlighter";
 
 const telemetry = new Telemetry();
 
 /**
  * Client side of the inspector actor, which is used to create
@@ -45,26 +44,16 @@ class InspectorFront extends FrontClassW
   async initialize() {
     await Promise.all([
       this._getWalker(),
       this._getHighlighter(),
       this._getPageStyle(),
     ]);
   }
 
-  get isBrowserFissionEnabled() {
-    if (this._isBrowserFissionEnabled === undefined) {
-      this._isBrowserFissionEnabled = Services.prefs.getBoolPref(
-        BROWSER_FISSION_ENABLED_PREF
-      );
-    }
-
-    return this._isBrowserFissionEnabled;
-  }
-
   get isContentFissionEnabled() {
     if (this._isContentFissionEnabled === undefined) {
       this._isContentFissionEnabled = Services.prefs.getBoolPref(
         CONTENT_FISSION_ENABLED_PREF
       );
     }
 
     return this._isContentFissionEnabled;
@@ -76,16 +65,21 @@ class InspectorFront extends FrontClassW
     );
     const showUserAgentShadowRoots = Services.prefs.getBoolPref(
       SHOW_UA_SHADOW_ROOTS_PREF
     );
     this.walker = await this.getWalker({
       showAllAnonymousContent,
       showUserAgentShadowRoots,
     });
+
+    // We need to reparent the RootNode of remote iframe Walkers
+    // so that their parent is the NodeFront of the <iframe>
+    // element, coming from another process/target/WalkerFront.
+    await this.walker.reparentRemoteFrame();
   }
 
   async _getHighlighter() {
     const autohide = !flags.testing;
     this.highlighter = await this.getHighlighter(
       autohide,
       Services.prefs.getBoolPref(USE_NEW_BOX_MODEL_HIGHLIGHTER_PREF)
     );
@@ -147,58 +141,16 @@ class InspectorFront extends FrontClassW
     if (options && options.fromMenu) {
       telemetry.getHistogramById(TELEMETRY_EYEDROPPER_OPENED_MENU).add(true);
     } else {
       telemetry.getHistogramById(TELEMETRY_EYEDROPPER_OPENED).add(true);
     }
   }
 
   /**
-   * Get the list of InspectorFront instances that correspond to all of the inspectable
-   * targets in remote frames nested within the document inspected here.
-   *
-   * Note that this only returns a non-empty array if the used from the Browser Toolbox
-   * and with the FISSION_ENABLED pref on.
-   *
-   * @return {Array} The list of InspectorFront instances.
-   */
-  async getChildInspectors() {
-    const childInspectors = [];
-    const target = this.targetFront;
-
-    // this line can be removed when we are ready for fission frames
-    if (this.isBrowserFissionEnabled && target.chrome && !target.isAddon) {
-      const { frames } = await target.listRemoteFrames();
-      // attempt to get targets and filter by targets that could connect
-      for (const descriptor of frames) {
-        const remoteTarget = await descriptor.getTarget();
-        if (remoteTarget) {
-          // get inspector
-          const remoteInspectorFront = await remoteTarget.getFront("inspector");
-          await remoteInspectorFront.walker.reparentRemoteFrame();
-          childInspectors.push(remoteInspectorFront);
-        }
-      }
-    }
-    return childInspectors;
-  }
-
-  /**
-   * Get the list of InspectorFront instances that correspond to all of the inspectable
-   * targets in remote frames nested within the document inspected here, as well as the
-   * current InspectorFront instance.
-   *
-   * @return {Array} The list of InspectorFront instances.
-   */
-  async getAllInspectorFronts() {
-    const remoteInspectors = await this.getChildInspectors();
-    return [this, ...remoteInspectors];
-  }
-
-  /**
    * Given a node grip, return a NodeFront on the right context.
    *
    * @param {Object} grip: The node grip.
    * @returns {Promise<NodeFront|null>} A promise that resolves with  a NodeFront or null
    *                                    if the NodeFront couldn't be created/retrieved.
    */
   async getNodeFrontFromNodeGrip(grip) {
     const gripHasContentDomReference = "contentDomReference" in grip;
--- a/devtools/shared/fronts/walker.js
+++ b/devtools/shared/fronts/walker.js
@@ -449,20 +449,43 @@ class WalkerFront extends FrontClassWith
     // And return the same kind of response `walker.children` returns
     return {
       nodes: [documentNode],
       hasFirst: true,
       hasLast: true,
     };
   }
 
+  /**
+   * Ensure that the RootNode of this Walker has the right parent NodeFront.
+   *
+   * This method does nothing if we are on the top level target's WalkerFront,
+   * as the RootNode won't have any parent.
+   *
+   * Otherwise, if we are in an iframe's WalkerFront, we would expect the parent
+   * of the RootNode (i.e. the NodeFront for the document loaded within the iframe)
+   * to be the <iframe>'s NodeFront. Because of fission, the two NodeFront may refer
+   * to DOM Element running in distinct processes and so the NodeFront comes from
+   * two distinct Targets and two distinct WalkerFront.
+   * This is why we need this manual "reparent" code to do the glue between the
+   * two documents.
+   */
   async reparentRemoteFrame() {
     // Get the parent target, which most likely runs in another process
     const descriptorFront = this.targetFront.descriptorFront;
+    // If we are on the top target, descriptorFront will be the RootFront
+    // and won't have the getParentTarget method.
+    if (!descriptorFront.getParentTarget) {
+      return;
+    }
     const parentTarget = await descriptorFront.getParentTarget();
+    // Don't reparent if we are on the top target
+    if (parentTarget == this.targetFront) {
+      return;
+    }
     // Get the NodeFront for the embedder element
     // i.e. the <iframe> element which is hosting the document that
     const parentWalker = (await parentTarget.getFront("inspector")).walker;
     // As this <iframe> most likely runs in another process, we have to get it through the parent
     // target's WalkerFront.
     const parentNode = (await parentWalker.getEmbedderElement(
       descriptorFront.id
     )).node;