Bug 1458767 - Lazy require the HighlightersOverlay in the Inspector. r=pbro draft
authorGabriel Luong <gabriel.luong@gmail.com>
Wed, 23 May 2018 18:53:18 -0400
changeset 799131 01eca14ea03cd6cffba04d5e8274977d300849e1
parent 798933 def02132a4cff5e03c4a95a3b3b8b1bf2d71a615
push id110930
push userbmo:gl@mozilla.com
push dateWed, 23 May 2018 22:53:41 +0000
reviewerspbro
bugs1458767
milestone62.0a1
Bug 1458767 - Lazy require the HighlightersOverlay in the Inspector. r=pbro MozReview-Commit-ID: Ca8Mi4BEorA
devtools/client/inspector/boxmodel/box-model.js
devtools/client/inspector/computed/computed.js
devtools/client/inspector/flexbox/flexbox.js
devtools/client/inspector/grids/grid-inspector.js
devtools/client/inspector/inspector.js
devtools/client/inspector/rules/rules.js
devtools/client/inspector/rules/views/rule-editor.js
devtools/client/inspector/shared/highlighters-overlay.js
devtools/client/inspector/test/shared-head.js
--- a/devtools/client/inspector/boxmodel/box-model.js
+++ b/devtools/client/inspector/boxmodel/box-model.js
@@ -21,17 +21,16 @@ const NUMERIC = /^-?[\d\.]+$/;
  *
  * @param  {Inspector} inspector
  *         An instance of the Inspector currently loaded in the toolbox.
  * @param  {Window} window
  *         The document window of the toolbox.
  */
 function BoxModel(inspector, window) {
   this.document = window.document;
-  this.highlighters = inspector.highlighters;
   this.inspector = inspector;
   this.store = inspector.store;
 
   this.updateBoxModel = this.updateBoxModel.bind(this);
 
   this.onHideBoxModelHighlighter = this.onHideBoxModelHighlighter.bind(this);
   this.onHideGeometryEditor = this.onHideGeometryEditor.bind(this);
   this.onMarkupViewLeave = this.onMarkupViewLeave.bind(this);
@@ -53,22 +52,29 @@ BoxModel.prototype = {
    * and cleans up references.
    */
   destroy() {
     this.inspector.selection.off("new-node-front", this.onNewSelection);
     this.inspector.sidebar.off("select", this.onSidebarSelect);
 
     this.untrackReflows();
 
+    this._highlighters = null;
     this.document = null;
-    this.highlighters = null;
     this.inspector = null;
     this.walker = null;
   },
 
+  get highlighters() {
+    if (!this._highlighters) {
+      // highlighters is a lazy getter in the inspector.
+      this._highlighters = this.inspector.highlighters;
+    }
+  },
+
   /**
    * Returns an object containing the box model's handler functions used in the box
    * model's React component props.
    */
   getComponentProps() {
     return {
       onHideBoxModelHighlighter: this.onHideBoxModelHighlighter,
       onShowBoxModelEditor: this.onShowBoxModelEditor,
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -145,49 +145,51 @@ UpdateProcess.prototype = {
  * @param {Document} document
  *        The document that will contain the computed view.
  * @param {PageStyleFront} pageStyle
  *        Front for the page style actor that will be providing
  *        the style information.
  */
 function CssComputedView(inspector, document, pageStyle) {
   this.inspector = inspector;
-  this.highlighters = inspector.highlighters;
   this.styleDocument = document;
   this.styleWindow = this.styleDocument.defaultView;
   this.pageStyle = pageStyle;
 
   this.propertyViews = [];
 
   let cssProperties = getCssProperties(inspector.toolbox);
   this._outputParser = new OutputParser(document, cssProperties);
 
   // Create bound methods.
   this.focusWindow = this.focusWindow.bind(this);
+  this._onClearSearch = this._onClearSearch.bind(this);
+  this._onClick = this._onClick.bind(this);
   this._onContextMenu = this._onContextMenu.bind(this);
-  this._onClick = this._onClick.bind(this);
   this._onCopy = this._onCopy.bind(this);
   this._onFilterStyles = this._onFilterStyles.bind(this);
-  this._onClearSearch = this._onClearSearch.bind(this);
   this._onIncludeBrowserStyles = this._onIncludeBrowserStyles.bind(this);
 
   let doc = this.styleDocument;
   this.element = doc.getElementById("computed-property-container");
   this.searchField = doc.getElementById("computed-searchbox");
   this.searchClearButton = doc.getElementById("computed-searchinput-clear");
   this.includeBrowserStylesCheckbox = doc.getElementById("browser-style-checkbox");
 
   this.shortcuts = new KeyShortcuts({ window: this.styleWindow });
   this._onShortcut = this._onShortcut.bind(this);
   this.shortcuts.on("CmdOrCtrl+F", event => this._onShortcut("CmdOrCtrl+F", event));
   this.shortcuts.on("Escape", event => this._onShortcut("Escape", event));
   this.styleDocument.addEventListener("copy", this._onCopy);
   this.styleDocument.addEventListener("mousedown", this.focusWindow);
   this.element.addEventListener("click", this._onClick);
   this.element.addEventListener("contextmenu", this._onContextMenu);
+  this.element.addEventListener("mousemove", () => {
+    this.addHighlightersToView();
+  }, { once: true });
   this.searchField.addEventListener("input", this._onFilterStyles);
   this.searchClearButton.addEventListener("click", this._onClearSearch);
   this.includeBrowserStylesCheckbox.addEventListener("input",
     this._onIncludeBrowserStyles);
 
   this.searchClearButton.hidden = true;
 
   // No results text.
@@ -201,18 +203,16 @@ function CssComputedView(inspector, docu
 
   // The element that we're inspecting, and the document that it comes from.
   this._viewedElement = null;
 
   this.createStyleViews();
 
   // Add the tooltips and highlightersoverlay
   this.tooltips = new TooltipsOverlay(this);
-
-  this.highlighters.addToView(this);
 }
 
 /**
  * Lookup a l10n string in the shared styleinspector string bundle.
  *
  * @param {String} name
  *        The key to lookup.
  * @returns {String} localized version of the given key.
@@ -245,16 +245,26 @@ CssComputedView.prototype = {
   get contextMenu() {
     if (!this._contextMenu) {
       this._contextMenu = new StyleInspectorMenu(this, { isRuleView: false });
     }
 
     return this._contextMenu;
   },
 
+  // Get the highlighters overlay from the Inspector.
+  get highlighters() {
+    if (!this._highlighters) {
+      // highlighters is a lazy getter in the inspector.
+      this._highlighters = this.inspector.highlighters;
+    }
+
+    return this._highlighters;
+  },
+
   setPageStyle: function(pageStyle) {
     this.pageStyle = pageStyle;
   },
 
   get includeBrowserStyles() {
     return this.includeBrowserStylesCheckbox.checked;
   },
 
@@ -718,16 +728,24 @@ CssComputedView.prototype = {
 
       clipboardHelper.copyString(text);
     } catch (e) {
       console.error(e);
     }
   },
 
   /**
+   * Adds the highlighters overlay to the computed view. This is called by the "mousemove"
+   * event handler and in shared-head.js when opening and selecting the computed view.
+   */
+  addHighlightersToView() {
+    this.highlighters.addToView(this);
+  },
+
+  /**
    * Destructor for CssComputedView.
    */
   destroy: function() {
     this._viewedElement = null;
     this._outputParser = null;
 
     this._prefObserver.off("devtools.defaultColorUnit", this._handlePrefChange);
     this._prefObserver.destroy();
@@ -740,43 +758,46 @@ CssComputedView.prototype = {
       this._refreshProcess.cancel();
     }
 
     if (this._contextMenu) {
       this._contextMenu.destroy();
       this._contextMenu = null;
     }
 
+    if (this._highlighters) {
+      this._highlighters.removeFromView(this);
+      this._highlighters = null;
+    }
+
     this.tooltips.destroy();
-    this.highlighters.removeFromView(this);
 
     // Remove bound listeners
-    this.styleDocument.removeEventListener("mousedown", this.focusWindow);
     this.element.removeEventListener("click", this._onClick);
-    this.styleDocument.removeEventListener("copy", this._onCopy);
     this.element.removeEventListener("contextmenu", this._onContextMenu);
     this.searchField.removeEventListener("input", this._onFilterStyles);
     this.searchClearButton.removeEventListener("click", this._onClearSearch);
+    this.styleDocument.removeEventListener("copy", this._onCopy);
+    this.styleDocument.removeEventListener("mousedown", this.focusWindow);
     this.includeBrowserStylesCheckbox.removeEventListener("input",
       this._onIncludeBrowserStyles);
 
     // Nodes used in templating
     this.element = null;
     this.searchField = null;
     this.searchClearButton = null;
     this.includeBrowserStylesCheckbox = null;
 
     // Property views
     for (let propView of this.propertyViews) {
       propView.destroy();
     }
     this.propertyViews = null;
 
     this.inspector = null;
-    this.highlighters = null;
     this.styleDocument = null;
     this.styleWindow = null;
 
     this._isDestroyed = true;
   }
 };
 
 function PropertyInfo(tree, name) {
--- a/devtools/client/inspector/flexbox/flexbox.js
+++ b/devtools/client/inspector/flexbox/flexbox.js
@@ -10,64 +10,79 @@ const {
   clearFlexbox,
   updateFlexbox,
   updateFlexboxHighlighted,
 } = require("./actions/flexbox");
 
 class FlexboxInspector {
   constructor(inspector, window) {
     this.document = window.document;
-    this.highlighters = inspector.highlighters;
     this.inspector = inspector;
     this.store = inspector.store;
     this.walker = inspector.walker;
 
     this.onHighlighterShown = this.onHighlighterShown.bind(this);
     this.onHighlighterHidden = this.onHighlighterHidden.bind(this);
     this.onReflow = throttle(this.onReflow, 500, this);
     this.onSidebarSelect = this.onSidebarSelect.bind(this);
     this.onToggleFlexboxHighlighter = this.onToggleFlexboxHighlighter.bind(this);
     this.onUpdatePanel = this.onUpdatePanel.bind(this);
 
     this.init();
   }
 
+  // Get the highlighters overlay from the Inspector.
+  get highlighters() {
+    if (!this._highlighters) {
+      // highlighters is a lazy getter in the inspector.
+      this._highlighters = this.inspector.highlighters;
+    }
+
+    return this._highlighters;
+  }
+
   async init() {
     if (!this.inspector) {
       return;
     }
     try {
       this.hasGetCurrentFlexbox = await this.inspector.target.actorHasMethod("layout",
         "getCurrentFlexbox");
       this.layoutInspector = await this.walker.getLayoutInspector();
     } catch (e) {
       // These calls might fail if called asynchrously after the toolbox is finished
       // closing.
       return;
     }
 
-    this.highlighters.on("flexbox-highlighter-hidden", this.onHighlighterHidden);
-    this.highlighters.on("flexbox-highlighter-shown", this.onHighlighterShown);
+    this.document.addEventListener("mousemove", () => {
+      this.highlighters.on("flexbox-highlighter-hidden", this.onHighlighterHidden);
+      this.highlighters.on("flexbox-highlighter-shown", this.onHighlighterShown);
+    }, { once: true });
+
     this.inspector.sidebar.on("select", this.onSidebarSelect);
 
     this.onSidebarSelect();
   }
 
   destroy() {
-    this.highlighters.off("flexbox-highlighter-hidden", this.onHighlighterHidden);
-    this.highlighters.off("flexbox-highlighter-shown", this.onHighlighterShown);
+    if (this._highlighters) {
+      this.highlighters.off("flexbox-highlighter-hidden", this.onHighlighterHidden);
+      this.highlighters.off("flexbox-highlighter-shown", this.onHighlighterShown);
+    }
+
     this.inspector.selection.off("new-node-front", this.onUpdatePanel);
     this.inspector.sidebar.off("select", this.onSidebarSelect);
     this.inspector.off("new-root", this.onUpdatePanel);
 
     this.inspector.reflowTracker.untrackReflows(this, this.onReflow);
 
+    this._highlighters = null;
     this.document = null;
     this.hasGetCurrentFlexbox = null;
-    this.highlighters = null;
     this.inspector = null;
     this.layoutInspector = null;
     this.store = null;
     this.walker = null;
   }
 
   getComponentProps() {
     return {
@@ -269,17 +284,20 @@ class FlexboxInspector {
           ["containerEl"]);
       } catch (e) {
         // This call might fail if called asynchrously after the toolbox is finished
         // closing.
         return;
       }
     }
 
+    let highlighted = this._highlighters &&
+      nodeFront == this.highlighters.flexboxHighlighterShown;
+
     this.store.dispatch(updateFlexbox({
       actorID: flexboxFront.actorID,
-      highlighted: nodeFront == this.highlighters.flexboxHighlighterShown,
+      highlighted,
       nodeFront,
     }));
   }
 }
 
 module.exports = FlexboxInspector;
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -47,17 +47,16 @@ const GRID_COLORS = [
   "#058B00",
   "#A47F00",
   "#005A71"
 ];
 
 class GridInspector {
   constructor(inspector, window) {
     this.document = window.document;
-    this.highlighters = inspector.highlighters;
     this.inspector = inspector;
     this.store = inspector.store;
     this.telemetry = inspector.telemetry;
     this.walker = this.inspector.walker;
 
     this.getSwatchColorPickerTooltip = this.getSwatchColorPickerTooltip.bind(this);
     this.updateGridPanel = this.updateGridPanel.bind(this);
 
@@ -71,16 +70,24 @@ class GridInspector {
     this.onToggleGridHighlighter = this.onToggleGridHighlighter.bind(this);
     this.onToggleShowGridAreas = this.onToggleShowGridAreas.bind(this);
     this.onToggleShowGridLineNumbers = this.onToggleShowGridLineNumbers.bind(this);
     this.onToggleShowInfiniteLines = this.onToggleShowInfiniteLines.bind(this);
 
     this.init();
   }
 
+  get highlighters() {
+    if (!this._highlighters) {
+      this._highlighters = this.inspector.highlighters;
+    }
+
+    return this._highlighters;
+  }
+
   /**
    * Initializes the grid inspector by fetching the LayoutFront from the walker, loading
    * the highlighter settings and initalizing the SwatchColorPicker instance.
    */
   async init() {
     if (!this.inspector) {
       return;
     }
@@ -97,44 +104,50 @@ class GridInspector {
     this.swatchColorPickerTooltip = new SwatchColorPickerTooltip(
       this.inspector.toolbox.doc,
       this.inspector,
       {
         supportsCssColor4ColorFunction: () => false
       }
     );
 
-    this.highlighters.on("grid-highlighter-hidden", this.onHighlighterHidden);
-    this.highlighters.on("grid-highlighter-shown", this.onHighlighterShown);
+    this.document.addEventListener("mousemove", () => {
+      this.highlighters.on("grid-highlighter-hidden", this.onHighlighterHidden);
+      this.highlighters.on("grid-highlighter-shown", this.onHighlighterShown);
+    }, { once: true });
+
     this.inspector.sidebar.on("select", this.onSidebarSelect);
     this.inspector.on("new-root", this.onNavigate);
 
     this.onSidebarSelect();
   }
 
   /**
    * Destruction function called when the inspector is destroyed. Removes event listeners
    * and cleans up references.
    */
   destroy() {
-    this.highlighters.off("grid-highlighter-hidden", this.onHighlighterHidden);
-    this.highlighters.off("grid-highlighter-shown", this.onHighlighterShown);
+    if (this._highlighters) {
+      this.highlighters.off("grid-highlighter-hidden", this.onHighlighterHidden);
+      this.highlighters.off("grid-highlighter-shown", this.onHighlighterShown);
+    }
+
     this.inspector.sidebar.off("select", this.onSidebarSelect);
     this.inspector.off("new-root", this.onNavigate);
 
     this.inspector.reflowTracker.untrackReflows(this, this.onReflow);
 
     // The color picker may not be ready as `init` function is async,
     // and we do not wait for its completion before calling destroy in tests
     if (this.swatchColorPickerTooltip) {
       this.swatchColorPickerTooltip.destroy();
     }
 
+    this._highlighters = null;
     this.document = null;
-    this.highlighters = null;
     this.inspector = null;
     this.layoutInspector = null;
     this.store = null;
     this.swatchColorPickerTooltip = null;
     this.walker = null;
   }
 
   getComponentProps() {
@@ -158,17 +171,18 @@ class GridInspector {
    * @param  {String} customColor
    *         The color fetched from the custom palette, if it exists.
    * @param  {String} fallbackColor
    *         The color to use if no color could be found for the node front.
    * @return {String} color
    *         The color to use.
    */
   getInitialGridColor(nodeFront, customColor, fallbackColor) {
-    let highlighted = nodeFront == this.highlighters.gridHighlighterShown;
+    let highlighted = this._highlighters &&
+      nodeFront == this.highlighters.gridHighlighterShown;
 
     let color;
     if (customColor) {
       color = customColor;
     } else if (highlighted && this.highlighters.state.grid.options) {
       // If the node front is currently highlighted, use the color from the highlighter
       // options.
       color = this.highlighters.state.grid.options.color;
@@ -290,24 +304,26 @@ class GridInspector {
           // closing.
           return;
         }
       }
 
       let colorForHost = customColors[hostname] ? customColors[hostname][i] : undefined;
       let fallbackColor = GRID_COLORS[i % GRID_COLORS.length];
       let color = this.getInitialGridColor(nodeFront, colorForHost, fallbackColor);
+      let highlighted = this._highlighters &&
+        nodeFront == this.highlighters.gridHighlighterShown;
 
       grids.push({
         id: i,
         actorID: grid.actorID,
         color,
         direction: grid.direction,
         gridFragments: grid.gridFragments,
-        highlighted: nodeFront == this.highlighters.gridHighlighterShown,
+        highlighted,
         nodeFront,
         writingMode: grid.writingMode,
       });
     }
 
     this.store.dispatch(updateGrids(grids));
     this.inspector.emit("grid-panel-updated");
   }
@@ -422,17 +438,18 @@ class GridInspector {
 
     // Otherwise, continue comparing with the new grids.
     const newNodeFronts = newGridFronts.filter(grid => grid.containerNodeFront)
                                        .map(grid => grid.containerNodeFront.actorID);
     if (grids.length === newGridFronts.length &&
         oldNodeFronts.sort().join(",") == newNodeFronts.sort().join(",")) {
       // Same list of containers, but let's check if the geometry of the current grid has
       // changed, if it hasn't we can safely abort.
-      if (!this.highlighters.gridHighlighterShown ||
+      if (!this._highlighters ||
+          !this.highlighters.gridHighlighterShown ||
           (this.highlighters.gridHighlighterShown &&
            !this.haveCurrentFragmentsChanged(newGridFronts))) {
         return;
       }
     }
 
     // Either the list of containers or the current fragments have changed, do update.
     this.updateGridPanel(newGridFronts);
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -8,32 +8,32 @@
 
 "use strict";
 
 const Services = require("Services");
 const promise = require("promise");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {executeSoon} = require("devtools/shared/DevToolsUtils");
 const {Toolbox} = require("devtools/client/framework/toolbox");
-const HighlightersOverlay = require("devtools/client/inspector/shared/highlighters-overlay");
 const ReflowTracker = require("devtools/client/inspector/shared/reflow-tracker");
 const Store = require("devtools/client/inspector/store");
 const InspectorStyleChangeTracker = require("devtools/client/inspector/shared/style-change-tracker");
 
 // Use privileged promise in panel documents to prevent having them to freeze
 // during toolbox destruction. See bug 1402779.
 const Promise = require("Promise");
 
 loader.lazyRequireGetter(this, "initCssProperties", "devtools/shared/fronts/css-properties", true);
 loader.lazyRequireGetter(this, "HTMLBreadcrumbs", "devtools/client/inspector/breadcrumbs", true);
 loader.lazyRequireGetter(this, "ThreePaneOnboardingTooltip", "devtools/client/inspector/shared/three-pane-onboarding-tooltip");
 loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts");
 loader.lazyRequireGetter(this, "InspectorSearch", "devtools/client/inspector/inspector-search", true);
 loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/inspector/toolsidebar", true);
 loader.lazyRequireGetter(this, "MarkupView", "devtools/client/inspector/markup/markup");
+loader.lazyRequireGetter(this, "HighlightersOverlay", "devtools/client/inspector/shared/highlighters-overlay");
 loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-constants");
 loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
 loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
 loader.lazyRequireGetter(this, "ExtensionSidebar", "devtools/client/inspector/extensions/extension-sidebar");
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
 
 const {LocalizationHelper, localizeMarkup} = require("devtools/shared/l10n");
@@ -107,17 +107,16 @@ function Inspector(toolbox) {
   this.telemetry = toolbox.telemetry;
 
   this.store = Store();
 
   // Map [panel id => panel instance]
   // Stores all the instances of sidebar panels like rule view, computed view, ...
   this._panels = new Map();
 
-  this.highlighters = new HighlightersOverlay(this);
   this.reflowTracker = new ReflowTracker(this._target);
   this.styleChangeTracker = new InspectorStyleChangeTracker(this);
 
   // 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.target.url;
 
   this.is3PaneModeEnabled = Services.prefs.getBoolPref(THREE_PANE_ENABLED_PREF);
@@ -184,16 +183,24 @@ Inspector.prototype = {
   get selection() {
     return this.toolbox.selection;
   },
 
   get highlighter() {
     return this.toolbox.highlighter;
   },
 
+  get highlighters() {
+    if (!this._highlighters) {
+      this._highlighters = new HighlightersOverlay(this);
+    }
+
+    return this._highlighters;
+  },
+
   // Added in 53.
   get canGetCssPath() {
     return this._target.client.traits.getCssPath;
   },
 
   // Added in 56.
   get canGetXPath() {
     return this._target.client.traits.getXPath;
@@ -1085,20 +1092,22 @@ Inspector.prototype = {
   async onMarkupLoaded() {
     if (!this.markup) {
       return;
     }
 
     let onExpand = this.markup.expandNode(this.selection.nodeFront);
 
     // Restore the highlighter states prior to emitting "new-root".
-    await Promise.all([
-      this.highlighters.restoreFlexboxState(),
-      this.highlighters.restoreGridState()
-    ]);
+    if (this._highlighters) {
+      await Promise.all([
+        this.highlighters.restoreFlexboxState(),
+        this.highlighters.restoreGridState()
+      ]);
+    }
 
     this.emit("new-root");
 
     // Wait for full expand of the selected node in order to ensure
     // the markup view is fully emitted before firing 'reloaded'.
     // 'reloaded' is used to know when the panel is fully updated
     // after a page reload.
     await onExpand;
@@ -1309,51 +1318,53 @@ Inspector.prototype = {
     if (this.animationinspector) {
       this.animationinspector.destroy();
     }
 
     if (this.threePaneTooltip) {
       this.threePaneTooltip.destroy();
     }
 
+    if (this._highlighters) {
+      this._highlighters.destroy();
+      this._highlighters = null;
+    }
+
     let cssPropertiesDestroyer = this._cssProperties.front.destroy();
     let sidebarDestroyer = this.sidebar.destroy();
     let ruleViewSideBarDestroyer = this.ruleViewSideBar ?
       this.ruleViewSideBar.destroy() : null;
     let markupDestroyer = this._destroyMarkup();
-    let highlighterDestroyer = this.highlighters.destroy();
 
     this.teardownSplitter();
     this.teardownToolbar();
 
     this.breadcrumbs.destroy();
     this.reflowTracker.destroy();
     this.styleChangeTracker.destroy();
     this.search.destroy();
 
     this._notificationBox = null;
     this._target = null;
     this._toolbox = null;
     this.breadcrumbs = null;
-    this.highlighters = null;
     this.is3PaneModeEnabled = null;
     this.panelDoc = null;
     this.panelWin.inspector = null;
     this.panelWin = null;
     this.resultsLength = null;
     this.search = null;
     this.searchBox = null;
     this.show3PaneTooltip = null;
     this.sidebar = null;
     this.store = null;
     this.telemetry = null;
     this.threePaneTooltip = null;
 
     this._panelDestroyer = promise.all([
-      highlighterDestroyer,
       cssPropertiesDestroyer,
       markupDestroyer,
       sidebarDestroyer,
       ruleViewSideBarDestroyer
     ]);
 
     return this._panelDestroyer;
   },
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -99,17 +99,16 @@ const INSET_POINT_TYPES = ["top", "right
  *        set of disabled properties.
  * @param {PageStyleFront} pageStyle
  *        The PageStyleFront for communicating with the remote server.
  */
 function CssRuleView(inspector, document, store, pageStyle) {
   EventEmitter.decorate(this);
 
   this.inspector = inspector;
-  this.highlighters = inspector.highlighters;
   this.styleDocument = document;
   this.styleWindow = this.styleDocument.defaultView;
   this.store = store || {};
   // References to rules marked by various editors where they intend to write changes.
   // @see selectRule(), unselectRule()
   this.selectedRules = new Map();
   this.pageStyle = pageStyle;
 
@@ -147,16 +146,19 @@ function CssRuleView(inspector, document
   this.shortcuts = new KeyShortcuts({ window: this.styleWindow });
   this._onShortcut = this._onShortcut.bind(this);
   this.shortcuts.on("Escape", event => this._onShortcut("Escape", event));
   this.shortcuts.on("Return", event => this._onShortcut("Return", event));
   this.shortcuts.on("Space", event => this._onShortcut("Space", event));
   this.shortcuts.on("CmdOrCtrl+F", event => this._onShortcut("CmdOrCtrl+F", event));
   this.element.addEventListener("copy", this._onCopy);
   this.element.addEventListener("contextmenu", this._onContextMenu);
+  this.element.addEventListener("mousemove", () => {
+    this.addHighlightersToView();
+  }, { once: true });
   this.addRuleButton.addEventListener("click", this._onAddRule);
   this.searchField.addEventListener("input", this._onFilterStyles);
   this.searchClearButton.addEventListener("click", this._onClearSearch);
   this.pseudoClassToggle.addEventListener("click", this._onTogglePseudoClassPanel);
   this.classToggle.addEventListener("click", this._onToggleClassPanel);
   this.hoverCheckbox.addEventListener("click", this._onTogglePseudoClass);
   this.activeCheckbox.addEventListener("click", this._onTogglePseudoClass);
   this.focusCheckbox.addEventListener("click", this._onTogglePseudoClass);
@@ -171,18 +173,16 @@ function CssRuleView(inspector, document
   this._prefObserver.on(PREF_DEFAULT_COLOR_UNIT, this._handleDefaultColorUnitPrefChange);
 
   this.showUserAgentStyles = Services.prefs.getBoolPref(PREF_UA_STYLES);
 
   this._showEmpty();
 
   // Add the tooltips and highlighters to the view
   this.tooltips = new TooltipsOverlay(this);
-
-  this.highlighters.addToView(this);
 }
 
 CssRuleView.prototype = {
   // The element that we're inspecting.
   _viewedElement: null,
 
   // Used for cancelling timeouts in the style filter.
   _filterChangedTimeout: null,
@@ -219,16 +219,26 @@ CssRuleView.prototype = {
     return this._contextMenu;
   },
 
   // Get the dummy elemenet.
   get dummyElement() {
     return this._dummyElement;
   },
 
+  // Get the highlighters overlay from the Inspector.
+  get highlighters() {
+    if (!this._highlighters) {
+      // highlighters is a lazy getter in the inspector.
+      this._highlighters = this.inspector.highlighters;
+    }
+
+    return this._highlighters;
+  },
+
   // Get the filter search value.
   get searchValue() {
     return this.searchField.value.toLowerCase();
   },
 
   get rules() {
     return this._elementStyle ? this._elementStyle.rules : [];
   },
@@ -740,18 +750,23 @@ CssRuleView.prototype = {
       this._classListPreviewer = null;
     }
 
     if (this._contextMenu) {
       this._contextMenu.destroy();
       this._contextMenu = null;
     }
 
+    if (this._highlighters) {
+      this._highlighters.removeFromView(this);
+      this._highlighters = null;
+    }
+
     this.tooltips.destroy();
-    this.highlighters.removeFromView(this);
+
     this.unselectAllRules();
 
     // Remove bound listeners
     this.shortcuts.destroy();
     this.element.removeEventListener("copy", this._onCopy);
     this.element.removeEventListener("contextmenu", this._onContextMenu);
     this.addRuleButton.removeEventListener("click", this._onAddRule);
     this.searchField.removeEventListener("input", this._onFilterStyles);
@@ -768,17 +783,16 @@ CssRuleView.prototype = {
     this.pseudoClassToggle = null;
     this.classPanel = null;
     this.classToggle = null;
     this.hoverCheckbox = null;
     this.activeCheckbox = null;
     this.focusCheckbox = null;
 
     this.inspector = null;
-    this.highlighters = null;
     this.styleDocument = null;
     this.styleWindow = null;
 
     if (this.element.parentNode) {
       this.element.remove();
     }
 
     if (this._elementStyle) {
@@ -1618,18 +1632,25 @@ CssRuleView.prototype = {
     } else if (name === "Escape" &&
                event.target === this.searchField &&
                this._onClearSearch()) {
       // Handle the search box's keypress event. If the escape key is pressed,
       // clear the search box field.
       event.preventDefault();
       event.stopPropagation();
     }
-  }
+  },
 
+  /**
+   * Adds the highlighters overlay to the rule view. This is called by the "mousemove"
+   * event handler and in shared-head.js when opening and selecting the rule view.
+   */
+  addHighlightersToView() {
+    this.highlighters.addToView(this);
+  },
 };
 
 /**
  * Helper functions
  */
 
 /**
  * Walk up the DOM from a given node until a parent property holder is found.
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -169,20 +169,21 @@ RuleEditor.prototype = {
           // This is an inline style from an inherited rule. Need to resolve the unique
           // selector from the node which rule this is inherited from.
           selector = await this.rule.inherited.getUniqueSelector();
         } else {
           // This is an inline style from the current node.
           selector = this.ruleView.inspector.selectionCssSelector;
         }
 
+        let isHighlighted = this.ruleView._highlighters &&
+          this.ruleView.highlighters.selectorHighlighterShown === selector;
         let selectorHighlighter = createChild(header, "span", {
           class: "ruleview-selectorhighlighter" +
-                 (this.ruleView.highlighters.selectorHighlighterShown === selector ?
-                  " highlighted" : ""),
+                 (isHighlighted ? " highlighted" : ""),
           title: l10n("rule.selectorHighlighter.tooltip")
         });
         selectorHighlighter.addEventListener("click", () => {
           this.ruleView.toggleSelectorHighlighter(selectorHighlighter, selector);
         });
 
         this.uniqueSelector = selector;
         this.emit("selector-icon-created");
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -967,17 +967,17 @@ class HighlightersOverlay {
 
     this.highlighters = null;
   }
 
   /**
    * Destroy this overlay instance, removing it from the view and destroying
    * all initialized highlighters.
    */
-  async destroy() {
+  destroy() {
     this.destroyHighlighters();
     this.destroyEditors();
 
     // Remove inspector events.
     this.inspector.off("markupmutation", this.onMarkupMutation);
     this.inspector.target.off("will-navigate", this.onWillNavigate);
 
     this._lastHovered = null;
--- a/devtools/client/inspector/test/shared-head.js
+++ b/devtools/client/inspector/test/shared-head.js
@@ -74,43 +74,52 @@ var openInspectorSidebarTab = async func
  * Open the toolbox, with the inspector tool visible, and the rule-view
  * sidebar tab selected.
  *
  * @return a promise that resolves when the inspector is ready and the rule view
  * is visible and ready
  */
 function openRuleView() {
   return openInspector().then(data => {
+    let view = data.inspector.getPanel("ruleview").view;
+
     // Replace the view to use a custom debounce function that can be triggered manually
     // through an additional ".flush()" property.
-    data.inspector.getPanel("ruleview").view.debounce = manualDebounce();
+    view.debounce = manualDebounce();
+
+    // Adds the highlighters overlay in the rule view.
+    view.addHighlightersToView();
 
     return {
       toolbox: data.toolbox,
       inspector: data.inspector,
       testActor: data.testActor,
-      view: data.inspector.getPanel("ruleview").view
+      view,
     };
   });
 }
 
 /**
  * Open the toolbox, with the inspector tool visible, and the computed-view
  * sidebar tab selected.
  *
  * @return a promise that resolves when the inspector is ready and the computed
  * view is visible and ready
  */
 function openComputedView() {
   return openInspectorSidebarTab("computedview").then(data => {
+    let view = data.inspector.getPanel("computedview").computedView;
+    // Adds the highlighters overlay in the computed view.
+    view.addHighlightersToView();
+
     return {
       toolbox: data.toolbox,
       inspector: data.inspector,
       testActor: data.testActor,
-      view: data.inspector.getPanel("computedview").computedView
+      view,
     };
   });
 }
 
 /**
  * Open the toolbox, with the inspector tool visible, and the layout view
  * sidebar tab selected to display the box model view with properties.
  *
@@ -144,29 +153,33 @@ function openLayoutView() {
 /**
  * Select the rule view sidebar tab on an already opened inspector panel.
  *
  * @param {InspectorPanel} inspector
  *        The opened inspector panel
  * @return {CssRuleView} the rule view
  */
 function selectRuleView(inspector) {
-  return inspector.getPanel("ruleview").view;
+  let view = inspector.getPanel("ruleview").view;
+  view.addHighlightersToView();
+  return view;
 }
 
 /**
  * Select the computed view sidebar tab on an already opened inspector panel.
  *
  * @param {InspectorPanel} inspector
  *        The opened inspector panel
  * @return {CssComputedView} the computed view
  */
 function selectComputedView(inspector) {
   inspector.sidebar.select("computedview");
-  return inspector.getPanel("computedview").computedView;
+  let view = inspector.getPanel("computedview").computedView;
+  view.addHighlightersToView();
+  return view;
 }
 
 /**
  * Select the layout view sidebar tab on an already opened inspector panel.
  *
  * @param  {InspectorPanel} inspector
  * @return {BoxModel} the box model
  */