Bug 1508655 - BoxModel highlighter move nodeHighlighter to highlighterFront; r=ochameau
authoryulia <ystartsev@mozilla.com>
Fri, 23 Nov 2018 10:50:21 +0000
changeset 447803 5f6b602fb590ccbaab9a33474d72a0aacdbd5529
parent 447802 f67b6fdd714f01fab6ae4d3984a6c298b52c0f71
child 447804 5edb94ce0260d72ab9fd67a88842ab63811d5f73
push id35090
push userbtara@mozilla.com
push dateFri, 23 Nov 2018 21:37:23 +0000
treeherdermozilla-central@2317749c5abf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1508655
milestone65.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 1508655 - BoxModel highlighter move nodeHighlighter to highlighterFront; r=ochameau Depends on D12319 Differential Revision: https://phabricator.services.mozilla.com/D12320
devtools/client/accessibility/components/Accessible.js
devtools/client/framework/components/ToolboxToolbar.js
devtools/client/framework/toolbox-highlighter-utils.js
devtools/client/framework/toolbox.js
devtools/client/inspector/animation/test/browser_animation_animation-target_highlight.js
devtools/client/inspector/animation/test/head.js
devtools/client/inspector/boxmodel/box-model.js
devtools/client/inspector/boxmodel/test/browser_boxmodel_guides.js
devtools/client/inspector/breadcrumbs.js
devtools/client/inspector/extensions/extension-sidebar.js
devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
devtools/client/inspector/flexbox/test/browser_flexbox_container_element_rep.js
devtools/client/inspector/grids/test/browser_grids_grid-list-element-rep.js
devtools/client/inspector/inspector.js
devtools/client/inspector/markup/markup.js
devtools/client/inspector/markup/test/browser_markup_events_click_to_close.js
devtools/client/inspector/markup/test/browser_markup_keybindings_04.js
devtools/client/inspector/test/browser_inspector_breadcrumbs_highlight_hover.js
devtools/client/inspector/test/browser_inspector_breadcrumbs_keyboard_trap.js
devtools/client/inspector/test/browser_inspector_highlighter-comments.js
devtools/client/inspector/test/browser_inspector_highlighter-zoom.js
devtools/client/inspector/test/head.js
devtools/client/shared/widgets/VariablesView.jsm
devtools/client/webconsole/test/mochitest/browser_webconsole_nodes_highlight.js
devtools/client/webconsole/webconsole-output-wrapper.js
devtools/shared/fronts/highlighters.js
--- a/devtools/client/accessibility/components/Accessible.js
+++ b/devtools/client/accessibility/components/Accessible.js
@@ -155,25 +155,25 @@ class Accessible extends Component {
     this.setState({ expanded });
   }
 
   showHighlighter(nodeFront) {
     if (!gToolbox) {
       return;
     }
 
-    gToolbox.highlighterUtils.highlightNodeFront(nodeFront);
+    gToolbox.highlighter.highlight(nodeFront);
   }
 
   hideHighlighter() {
     if (!gToolbox) {
       return;
     }
 
-    gToolbox.highlighterUtils.unhighlight();
+    gToolbox.highlighter.unhighlight();
   }
 
   showAccessibleHighlighter(accessible) {
     const { walker, dispatch } = this.props;
     dispatch(unhighlight());
 
     if (!accessible || !walker) {
       return;
--- a/devtools/client/framework/components/ToolboxToolbar.js
+++ b/devtools/client/framework/components/ToolboxToolbar.js
@@ -252,17 +252,20 @@ class ToolboxToolbar extends Component {
       {
         id,
         disabled,
         menuId: id + "-panel",
         doc: toolbox.doc,
         className: `devtools-button command-button ${isChecked ? "checked" : ""}`,
         ref: "frameMenuButton",
         title: description,
-        onCloseButton: toolbox.highlighterUtils.unhighlight,
+        onCloseButton: async () => {
+          await toolbox.initInspector();
+          toolbox.highlighter.unhighlight();
+        },
       },
       this.createFrameList
     );
   }
 
   clickFrameButton(event) {
     const { toolbox } = this.props;
     toolbox.onSelectFrame(event.target.id);
--- a/devtools/client/framework/toolbox-highlighter-utils.js
+++ b/devtools/client/framework/toolbox-highlighter-utils.js
@@ -1,16 +1,15 @@
 /* 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/. */
 
 "use strict";
 
 const promise = require("promise");
-const flags = require("devtools/shared/flags");
 
 /**
  * Client-side highlighter shared module.
  * To be used by toolbox panels that need to highlight DOM elements.
  *
  * Highlighting and selecting elements is common enough that it needs to be at
  * toolbox level, accessible by any panel that needs it.
  * That's why the toolbox is the one that initializes the inspector and
@@ -33,20 +32,16 @@ exports.getHighlighterUtils = function(t
   }
 
   // Exported API properties will go here
   const exported = {};
 
   // Is the highlighter currently in pick mode
   let isPicking = false;
 
-  // Is the box model already displayed, used to prevent dispatching
-  // unnecessary requests, especially during toolbox shutdown
-  let isNodeFrontHighlighted = false;
-
   /**
    * Release this utils, nullifying the references to the toolbox
    */
   exported.release = function() {
     toolbox = null;
   };
 
   /**
@@ -174,60 +169,16 @@ exports.getHighlighterUtils = function(t
    * gets the focus.
    */
   function onPickerNodeCanceled() {
     cancelPicker();
     toolbox.win.focus();
   }
 
   /**
-   * Show the box model highlighter on a node in the content page.
-   * The node needs to be a NodeFront, as defined by the inspector actor
-   * @see devtools/server/actors/inspector/inspector.js
-   * @param {NodeFront} nodeFront The node to highlight
-   * @param {Object} options
-   * @return A promise that resolves when the node has been highlighted
-   */
-  exported.highlightNodeFront = requireInspector(
-  async function(nodeFront, options = {}) {
-    if (!nodeFront) {
-      return;
-    }
-
-    isNodeFrontHighlighted = true;
-    await toolbox.highlighter.showBoxModel(nodeFront, options);
-
-    toolbox.emit("node-highlight", nodeFront);
-  });
-
-  /**
-   * Hide the highlighter.
-   * @param {Boolean} forceHide Only really matters in test mode (when
-   * flags.testing is true). In test mode, hovering over several nodes
-   * in the markup view doesn't hide/show the highlighter to ease testing. The
-   * highlighter stays visible at all times, except when the mouse leaves the
-   * markup view, which is when this param is passed to true
-   * @return a promise that resolves when the highlighter is hidden
-   */
-  exported.unhighlight = async function(forceHide = false) {
-    forceHide = forceHide || !flags.testing;
-
-    if (isNodeFrontHighlighted && forceHide && toolbox.highlighter) {
-      isNodeFrontHighlighted = false;
-      await toolbox.highlighter.hideBoxModel();
-    }
-
-    // unhighlight is called when destroying the toolbox, which means that by
-    // now, the toolbox reference might have been nullified already.
-    if (toolbox) {
-      toolbox.emit("node-unhighlight");
-    }
-  };
-
-  /**
    * If the main, box-model, highlighter isn't enough, or if multiple highlighters
    * are needed in parallel, this method can be used to return a new instance of a
    * highlighter actor, given a type.
    * The type of the highlighter passed must be known by the server.
    * The highlighter actor returned will have the show(nodeFront) and hide()
    * methods and needs to be released by the consumer when not needed anymore.
    * @return Promise a promise that resolves to the highlighter
    */
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -2374,17 +2374,17 @@ Toolbox.prototype = {
   onHighlightFrame: async function(frameId) {
     // Need to initInspector to check presence of getNodeActorFromWindowID
     // and use the highlighter later
     await this.initInspector();
 
     // Only enable frame highlighting when the top level document is targeted
     if (this.rootFrameSelected) {
       const frameActor = await this.walker.getNodeActorFromWindowID(frameId);
-      this.highlighterUtils.highlightNodeFront(frameActor);
+      this.highlighter.highlight(frameActor);
     }
   },
 
   /**
    * A handler for 'frameUpdate' packets received from the backend.
    * Following properties might be set on the packet:
    *
    * destroyAll {Boolean}: All frames have been destroyed.
--- a/devtools/client/inspector/animation/test/browser_animation_animation-target_highlight.js
+++ b/devtools/client/inspector/animation/test/browser_animation_animation-target_highlight.js
@@ -20,23 +20,23 @@ add_task(async function() {
   const {
     animationInspector,
     inspector,
     panel,
     toolbox,
   } = await openAnimationInspector();
 
   info("Check highlighting when mouse over on a target node");
-  const onHighlight = toolbox.once("node-highlight");
+  const onHighlight = toolbox.highlighter.once("node-highlight");
   mouseOverOnTargetNode(animationInspector, panel, 0);
   let nodeFront = await onHighlight;
   assertNodeFront(nodeFront, "DIV", "ball animated");
 
   info("Check unhighlighting when mouse out on a target node");
-  const onUnhighlight = toolbox.once("node-unhighlight");
+  const onUnhighlight = toolbox.highlighter.once("node-unhighlight");
   mouseOutOnTargetNode(animationInspector, panel, 0);
   await onUnhighlight;
   ok(true, "Unhighlighted the targe node");
 
   info("Check node is highlighted when the inspect icon is clicked");
   let onHighlighterShown = inspector.highlighters.once("box-model-highlighter-shown");
   await clickOnInspectIcon(animationInspector, panel, 0);
   nodeFront = await onHighlighterShown;
@@ -50,21 +50,21 @@ add_task(async function() {
   ok(panel.querySelectorAll(".animation-target")[0].classList.contains("highlighting"),
     "The highlighted element still should have 'highlighting' class");
 
   info("Check no highlight event occur by mouse over locked target");
   let highlightEventCount = 0;
   const highlightEventCounter = () => {
     highlightEventCount += 1;
   };
-  toolbox.on("node-highlight", highlightEventCounter);
+  toolbox.highlighter.on("node-highlight", highlightEventCounter);
   mouseOverOnTargetNode(animationInspector, panel, 0);
   await wait(500);
   is(highlightEventCount, 0, "Highlight event should not occur");
-  toolbox.off("node-highlight", highlightEventCounter);
+  toolbox.highlighter.off("node-highlight", highlightEventCounter);
 
   info("Highlighting another animation target");
   onHighlighterShown = inspector.highlighters.once("box-model-highlighter-shown");
   await clickOnInspectIcon(animationInspector, panel, 1);
   nodeFront = await onHighlighterShown;
   assertNodeFront(nodeFront, "DIV", "ball multi");
 
   info("Check the highlighted state of the animation targets");
--- a/devtools/client/inspector/animation/test/head.js
+++ b/devtools/client/inspector/animation/test/head.js
@@ -267,17 +267,17 @@ const clickOnSummaryGraph = async functi
  *        #animation-container element.
  * @param {Number} index
  *        The index of the AnimationTargetComponent to click on.
  */
 const clickOnTargetNode = async function(animationInspector, panel, index) {
   info(`Click on a target node in animation target component[${ index }]`);
   const targetEl = panel.querySelectorAll(".animation-target .objectBox")[index];
   targetEl.scrollIntoView(false);
-  const onHighlight = animationInspector.inspector.toolbox.once("node-highlight");
+  const onHighlight = animationInspector.inspector.highlighter.once("node-highlight");
   const onAnimationTargetUpdated = animationInspector.once("animation-target-rendered");
   EventUtils.synthesizeMouseAtCenter(targetEl, {}, targetEl.ownerGlobal);
   await onAnimationTargetUpdated;
   await waitForSummaryAndDetail(animationInspector);
   await onHighlight;
 };
 
 /**
--- a/devtools/client/inspector/boxmodel/box-model.js
+++ b/devtools/client/inspector/boxmodel/box-model.js
@@ -194,18 +194,17 @@ BoxModel.prototype = {
   /**
    * Hides the box-model highlighter on the currently selected element.
    */
   onHideBoxModelHighlighter() {
     if (!this.inspector) {
       return;
     }
 
-    const toolbox = this.inspector.toolbox;
-    toolbox.highlighterUtils.unhighlight();
+    this.inspector.highlighter.unhighlight();
   },
 
   /**
    * Hides the geometry editor and updates the box moodel store with the new
    * geometry editor enabled state.
    */
   onHideGeometryEditor() {
     const { markup, selection, toolbox } = this.inspector;
@@ -333,20 +332,18 @@ BoxModel.prototype = {
    * @param  {Object} options
    *         Options passed to the highlighter actor.
    */
   onShowBoxModelHighlighter(options = {}) {
     if (!this.inspector) {
       return;
     }
 
-    const toolbox = this.inspector.toolbox;
     const nodeFront = this.inspector.selection.nodeFront;
-
-    toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
+    this.inspector.highlighter.highlight(nodeFront, options);
   },
 
   /**
    * Handler for the inspector sidebar select event. Starts tracking reflows if the
    * layout panel is visible. Otherwise, stop tracking reflows. Finally, refresh the box
    * model view if it is visible.
    */
   onSidebarSelect() {
--- a/devtools/client/inspector/boxmodel/test/browser_boxmodel_guides.js
+++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_guides.js
@@ -42,17 +42,17 @@ add_task(async function() {
 });
 
 async function testGuideOnLayoutHover(elt, expectedRegion, inspector) {
   info("Synthesizing mouseover on the boxmodel-view");
   EventUtils.synthesizeMouse(elt, 50, 2, {type: "mouseover"},
     elt.ownerDocument.defaultView);
 
   info("Waiting for the node-highlight event from the toolbox");
-  await inspector.toolbox.once("node-highlight");
+  await inspector.highlighter.once("node-highlight");
 
   // Wait for the next event tick to make sure the remaining part of the
   // test is executed after finishing synthesizing mouse event.
   await new Promise(executeSoon);
 
   is(highlightedNodeFront, inspector.selection.nodeFront,
     "The right nodeFront was highlighted");
   is(highlighterOptions.region, expectedRegion,
--- a/devtools/client/inspector/breadcrumbs.js
+++ b/devtools/client/inspector/breadcrumbs.js
@@ -573,17 +573,17 @@ HTMLBreadcrumbs.prototype = {
     }
   },
 
   /**
    * On mouse out, make sure to unhighlight.
    * @param {DOMEvent} event.
    */
   handleMouseOut: function(event) {
-    this.inspector.toolbox.highlighterUtils.unhighlight();
+    this.inspector.highlighter.unhighlight();
   },
 
   /**
    * Handle a keyboard shortcut supported by the breadcrumbs widget.
    *
    * @param {String} name
    *        Name of the keyboard shortcut received.
    * @param {DOMEvent} event
@@ -718,17 +718,17 @@ HTMLBreadcrumbs.prototype = {
       button.focus();
     };
 
     button.onBreadcrumbsClick = () => {
       this.selection.setNodeFront(node, { reason: "breadcrumbs" });
     };
 
     button.onBreadcrumbsHover = () => {
-      this.inspector.toolbox.highlighterUtils.highlightNodeFront(node);
+      this.inspector.highlighter.highlight(node);
     };
 
     return button;
   },
 
   /**
    * Connecting the end of the breadcrumbs to a node.
    * @param {NodeFront} node The node to reach.
--- a/devtools/client/inspector/extensions/extension-sidebar.js
+++ b/devtools/client/inspector/extensions/extension-sidebar.js
@@ -68,33 +68,23 @@ class ExtensionSidebar {
           },
           releaseActor: (actor) => {
             if (!actor) {
               return;
             }
             this.inspector.toolbox.target.client.release(actor);
           },
           highlightDomElement: async (grip, options = {}) => {
-            const { highlighterUtils } = this.inspector.toolbox;
-
-            if (!highlighterUtils) {
-              return null;
-            }
-
+            const { highlighter } = this.inspector;
             const nodeFront = await this.inspector.walker.gripToNodeFront(grip);
-            return highlighterUtils.highlightNodeFront(nodeFront, options);
+            return highlighter.highlight(nodeFront, options);
           },
           unHighlightDomElement: (forceHide = false) => {
-            const { highlighterUtils } = this.inspector.toolbox;
-
-            if (!highlighterUtils) {
-              return null;
-            }
-
-            return highlighterUtils.unhighlight(forceHide);
+            const { highlighter } = this.inspector;
+            return highlighter.unhighlight(forceHide);
           },
           openNodeInInspector: async (grip) => {
             const { walker } = this.inspector;
             const front = await walker.gripToNodeFront(grip);
             const onInspectorUpdated = this.inspector.once("inspector-updated");
             const onNodeFrontSet = this.inspector.toolbox.selection.setNodeFront(front, {
               reason: "inspector-extension-sidebar",
             });
--- a/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
+++ b/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
@@ -178,26 +178,26 @@ add_task(async function testSidebarDOMNo
   assertObjectInspector(sidebarPanelContent, {
     expectedDOMNodes: 1,
     expectedOpenInspectors: 1,
   });
 
   // Test highlight DOMNode on mouseover.
   info("Highlight the node by moving the cursor on it");
 
-  const onNodeHighlight = toolbox.once("node-highlight");
+  const onNodeHighlight = toolbox.highlighter.once("node-highlight");
 
   moveMouseOnObjectInspectorDOMNode(sidebarPanelContent);
 
   const nodeFront = await onNodeHighlight;
   is(nodeFront.displayName, "body", "The correct node was highlighted");
 
   // Test unhighlight DOMNode on mousemove.
   info("Unhighlight the node by moving away from the node");
-  const onNodeUnhighlight = toolbox.once("node-unhighlight");
+  const onNodeUnhighlight = toolbox.highlighter.once("node-unhighlight");
 
   moveMouseOnPanelCenter(sidebarPanelContent);
 
   await onNodeUnhighlight;
   info("node-unhighlight event was fired when moving away from the node");
 
   inspectedWindowFront.destroy();
 });
@@ -211,18 +211,18 @@ add_task(async function testSidebarDOMNo
   inspector.selection.setNodeFront(null);
   let nodeFront = await onceNewNodeFront;
   is(nodeFront, undefined, "The inspector selection should have been unselected");
 
   info("Select the ObjectInspector DOMNode in the inspector panel by clicking on it");
 
   // Once we click the open-inspector icon we expect a new node front to be selected
   // and the node to have been highlighted and unhighlighted.
-  const onNodeHighlight = toolbox.once("node-highlight");
-  const onNodeUnhighlight = toolbox.once("node-unhighlight");
+  const onNodeHighlight = toolbox.highlighter.once("node-highlight");
+  const onNodeUnhighlight = toolbox.highlighter.once("node-unhighlight");
   onceNewNodeFront = inspector.selection.once("new-node-front");
 
   clickOpenInspectorIcon(sidebarPanelContent);
 
   nodeFront = await onceNewNodeFront;
   is(nodeFront.displayName, "body", "The correct node has been selected");
   nodeFront = await onNodeHighlight;
   is(nodeFront.displayName, "body", "The correct node was highlighted");
--- a/devtools/client/inspector/flexbox/test/browser_flexbox_container_element_rep.js
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_container_element_rep.js
@@ -15,17 +15,17 @@ add_task(async function() {
 
   const onFlexContainerRepRendered = waitForDOM(doc, ".flex-header-content .objectBox");
   await selectNode("#container", inspector);
   const [flexContainerRep] = await onFlexContainerRepRendered;
 
   ok(flexContainerRep, "The flex container element rep is rendered.");
 
   info("Listen to node-highlight event and mouse over the rep");
-  const onHighlight = toolbox.once("node-highlight");
+  const onHighlight = toolbox.highlighter.once("node-highlight");
   EventUtils.synthesizeMouse(flexContainerRep, 10, 5, {type: "mouseover"},
     doc.defaultView);
   const nodeFront = await onHighlight;
 
   ok(nodeFront, "nodeFront was returned from highlighting the node.");
   is(nodeFront.tagName, "DIV", "The highlighted node has the correct tagName.");
   is(nodeFront.attributes[0].name, "id",
     "The highlighted node has the correct attributes.");
--- a/devtools/client/inspector/grids/test/browser_grids_grid-list-element-rep.js
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-element-rep.js
@@ -25,17 +25,17 @@ add_task(async function() {
   const { store } = inspector;
 
   const gridList = doc.querySelector("#grid-list");
   const elementRep = gridList.children[0].querySelector(".open-inspector");
   info("Scrolling into the view the #grid element node rep.");
   elementRep.scrollIntoView();
 
   info("Listen to node-highlight event and mouse over the widget");
-  const onHighlight = toolbox.once("node-highlight");
+  const onHighlight = toolbox.highlighter.once("node-highlight");
   EventUtils.synthesizeMouse(elementRep, 10, 5, {type: "mouseover"}, doc.defaultView);
   const nodeFront = await onHighlight;
 
   ok(nodeFront, "nodeFront was returned from highlighting the node.");
   is(nodeFront.tagName, "DIV", "The highlighted node has the correct tagName.");
   is(nodeFront.attributes[0].name, "id",
     "The highlighted node has the correct attributes.");
   is(nodeFront.attributes[0].value, "grid", "The highlighted node has the correct id.");
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -2478,17 +2478,17 @@ Inspector.prototype = {
    *
    * @param  {NodeFront} nodeFront
    *         The node to highlight.
    * @param  {Object} options
    *         Options passed to the highlighter actor.
    */
   onShowBoxModelHighlighterForNode(nodeFront, options) {
     const toolbox = this.toolbox;
-    toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
+    toolbox.highlighter.highlight(nodeFront, options);
   },
 
   /**
    * Returns a value indicating whether a node can be deleted.
    *
    * @param {NodeFront} nodeFront
    *        The node to test for deletion
    */
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -450,31 +450,31 @@ MarkupView.prototype = {
    *
    * @param  {NodeFront} nodeFront
    *         The node to show the highlighter for
    * @return {Promise} Resolves when the highlighter for this nodeFront is
    *         shown, taking into account that there could already be highlighter
    *         requests queued up
    */
   _showBoxModel: function(nodeFront) {
-    return this.toolbox.highlighterUtils.highlightNodeFront(nodeFront)
+    return this.toolbox.highlighter.highlight(nodeFront)
       .catch(this._handleRejectionIfNotDestroyed);
   },
 
   /**
    * Hide the box model highlighter on a given node front
    *
    * @param  {Boolean} forceHide
-   *         See toolbox-highlighter-utils/unhighlight
+   *         See highlighterFront method `unhighlight`
    * @return {Promise} Resolves when the highlighter for this nodeFront is
    *         hidden, taking into account that there could already be highlighter
    *         requests queued up
    */
   _hideBoxModel: function(forceHide) {
-    return this.toolbox.highlighterUtils.unhighlight(forceHide)
+    return this.toolbox.highlighter.unhighlight(forceHide)
       .catch(this._handleRejectionIfNotDestroyed);
   },
 
   _briefBoxModelTimer: null,
 
   _clearBriefBoxModelTimer: function() {
     if (this._briefBoxModelTimer) {
       clearTimeout(this._briefBoxModelTimer);
--- a/devtools/client/inspector/markup/test/browser_markup_events_click_to_close.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_click_to_close.js
@@ -64,17 +64,17 @@ add_task(async function() {
     "The tooltip is still hidden after waiting for one second");
 
   info("Open the tooltip on evHolder2 again");
   onShown = tooltip.once("shown");
   EventUtils.synthesizeMouseAtCenter(evHolder2, {}, inspector.markup.win);
   await onShown;
 
   info("Click on the computed view tab");
-  const onHighlighterHidden = toolbox.once("node-unhighlight");
+  const onHighlighterHidden = toolbox.highlighter.once("node-unhighlight");
   const onTabComputedViewSelected = inspector.sidebar.once("computedview-selected");
   const computedViewTab = inspector.panelDoc.querySelector("#computedview-tab");
   EventUtils.synthesizeMouseAtCenter(computedViewTab, {},
     inspector.panelDoc.defaultView);
 
   await onTabComputedViewSelected;
   info("computed view was selected");
 
--- a/devtools/client/inspector/markup/test/browser_markup_keybindings_04.js
+++ b/devtools/client/inspector/markup/test/browser_markup_keybindings_04.js
@@ -35,17 +35,17 @@ add_task(async function() {
 });
 
 function assertNodeSelected(inspector, tagName) {
   is(inspector.selection.nodeFront.tagName.toLowerCase(), tagName,
     `The <${tagName}> node is selected`);
 }
 
 function selectPreviousNodeWithArrowUp(inspector) {
-  const onNodeHighlighted = inspector.toolbox.once("node-highlight");
+  const onNodeHighlighted = inspector.highlighter.once("node-highlight");
   const onUpdated = inspector.once("inspector-updated");
   EventUtils.synthesizeKey("KEY_ArrowUp");
   return Promise.all([onUpdated, onNodeHighlighted]);
 }
 
 async function selectWithElementPicker(inspector, testActor) {
   await startPicker(inspector.toolbox);
 
--- a/devtools/client/inspector/test/browser_inspector_breadcrumbs_highlight_hover.js
+++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_highlight_hover.js
@@ -9,36 +9,36 @@
 add_task(async function() {
   info("Loading the test document and opening the inspector");
   const {toolbox, inspector, testActor} = await openInspectorForURL(
     "data:text/html;charset=utf-8,<h1>foo</h1><span>bar</span>");
   info("Selecting the test node");
   await selectNode("span", inspector);
   const bcButtons = inspector.breadcrumbs.container;
 
-  let onNodeHighlighted = toolbox.once("node-highlight");
+  let onNodeHighlighted = toolbox.highlighter.once("node-highlight");
   let button = bcButtons.childNodes[1];
   EventUtils.synthesizeMouseAtCenter(button, {type: "mousemove"},
     button.ownerDocument.defaultView);
   await onNodeHighlighted;
 
   let isVisible = await testActor.isHighlighting();
   ok(isVisible, "The highlighter is shown on a markup container hover");
 
   ok((await testActor.assertHighlightedNode("body")),
      "The highlighter highlights the right node");
 
-  const onNodeUnhighlighted = toolbox.once("node-unhighlight");
+  const onNodeUnhighlighted = toolbox.highlighter.once("node-unhighlight");
   // move outside of the breadcrumb trail to trigger unhighlight
   EventUtils.synthesizeMouseAtCenter(inspector.addNodeButton,
     {type: "mousemove"},
     inspector.addNodeButton.ownerDocument.defaultView);
   await onNodeUnhighlighted;
 
-  onNodeHighlighted = toolbox.once("node-highlight");
+  onNodeHighlighted = toolbox.highlighter.once("node-highlight");
   button = bcButtons.childNodes[2];
   EventUtils.synthesizeMouseAtCenter(button, {type: "mousemove"},
     button.ownerDocument.defaultView);
   await onNodeHighlighted;
 
   isVisible = await testActor.isHighlighting();
   ok(isVisible, "The highlighter is shown on a markup container hover");
 
--- a/devtools/client/inspector/test/browser_inspector_breadcrumbs_keyboard_trap.js
+++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_keyboard_trap.js
@@ -51,17 +51,17 @@ add_task(async function() {
   const {breadcrumbs} = inspector;
 
   await selectNode("#i2", inspector);
 
   info("Clicking on the corresponding breadcrumbs node to focus it");
   const container = doc.getElementById("inspector-breadcrumbs");
 
   const button = container.querySelector("button[checked]");
-  const onHighlight = toolbox.once("node-highlight");
+  const onHighlight = toolbox.highlighter.once("node-highlight");
   button.click();
   await onHighlight;
 
   // Ensure a breadcrumb is focused.
   is(doc.activeElement, container, "Focus is on selected breadcrumb");
   is(container.getAttribute("aria-activedescendant"), button.id,
     "aria-activedescendant is set correctly");
 
--- a/devtools/client/inspector/test/browser_inspector_highlighter-comments.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-comments.js
@@ -44,17 +44,17 @@ add_task(async function() {
   await hoverElement("#id4");
   await assertHighlighterHidden();
 
   info("Hovering over a text node and waiting for highlighter to appear.");
   await hoverTextNode("Visible text node");
   await assertHighlighterShownOnTextNode("body", 14);
 
   function hoverContainer(container) {
-    const promise = inspector.toolbox.once("node-highlight");
+    const promise = inspector.highlighter.once("node-highlight");
 
     container.tagLine.scrollIntoView();
     EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
         markupView.doc.defaultView);
 
     return promise;
   }
 
--- a/devtools/client/inspector/test/browser_inspector_highlighter-zoom.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-zoom.js
@@ -16,41 +16,41 @@ const TEST_LEVELS = [2, 1, .5];
 // node, for the values given.
 const expectedStyle = (w, h, z) =>
         (z !== 1 ? `transform-origin:top left; transform:scale(${1 / z}); ` : "") +
         `position:absolute; width:${w * z}px;height:${h * z}px; ` +
         "overflow:hidden";
 
 add_task(async function() {
   const {inspector, testActor} = await openInspectorForURL(TEST_URL);
-  const highlighterUtils = inspector.toolbox.highlighterUtils;
+  const highlighter = inspector.highlighter;
 
   const div = await getNodeFront("div", inspector);
 
   for (const level of TEST_LEVELS) {
     info(`Zoom to level ${level}`);
     await testActor.zoomPageTo(level, false);
 
     info("Highlight the test node");
-    await highlighterUtils.highlightNodeFront(div);
+    await highlighter.highlight(div);
 
     const isVisible = await testActor.isHighlighting();
     ok(isVisible, `The highlighter is visible at zoom level ${level}`);
 
     await testActor.isNodeCorrectlyHighlighted("div", is);
 
     info("Check that the highlighter root wrapper node was scaled down");
 
     const style = await getElementsNodeStyle(testActor);
     const { width, height } = await testActor.getWindowDimensions();
     is(style, expectedStyle(width, height, level),
       "The style attribute of the root element is correct");
 
     info("Unhighlight the node");
-    await highlighterUtils.unhighlight();
+    await highlighter.unhighlight();
   }
 });
 
 async function getElementsNodeStyle(testActor) {
   const value =
     await testActor.getHighlighterNodeAttribute("box-model-elements", "style");
   return value;
 }
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -323,17 +323,17 @@ async function(selector, inspector, expe
  * is shown on the corresponding node
  */
 var hoverContainer = async function(selector, inspector) {
   info("Hovering over the markup-container for node " + selector);
 
   const nodeFront = await getNodeFront(selector, inspector);
   const container = getContainerForNodeFront(nodeFront, inspector);
 
-  const highlit = inspector.toolbox.once("node-highlight");
+  const highlit = inspector.highlighter.once("node-highlight");
   EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousemove"},
     inspector.markup.doc.defaultView);
   return highlit;
 };
 
 /**
  * Simulate a click on the markup-container (a line in the markup-view)
  * that corresponds to the selector passed.
--- a/devtools/client/shared/widgets/VariablesView.jsm
+++ b/devtools/client/shared/widgets/VariablesView.jsm
@@ -2776,30 +2776,31 @@ Variable.prototype = extend(Scope.protot
   },
 
   /**
    * In case this variable is a DOMNode and part of a variablesview that has been
    * linked to the toolbox's inspector, then highlight the corresponding node
    */
   highlightDomNode: async function() {
     if (this.toolbox) {
+      await this.toolbox.initInspector();
       if (!this._nodeFront) {
         this.nodeFront = await this.toolbox.walker.gripToNodeFront(this._valueGrip);
       }
-      await this.toolbox.highlighterUtils.highlightNodeFront(this._nodeFront);
+      await this.toolbox.highlighter.highlight(this._nodeFront);
     }
   },
 
   /**
    * Unhighlight a previously highlit node
    * @see highlightDomNode
    */
   unhighlightDomNode: function() {
     if (this.toolbox) {
-      this.toolbox.highlighterUtils.unhighlight();
+      this.toolbox.highlighter.unhighlight();
     }
   },
 
   /**
    * Sets a variable's configurable, enumerable and writable attributes,
    * and specifies if it's a 'this', '<exception>', '<return>' or '__proto__'
    * reference.
    */
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_nodes_highlight.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_nodes_highlight.js
@@ -19,33 +19,34 @@ const HTML = `
     </script>
   </html>
 `;
 const TEST_URI = "data:text/html;charset=utf-8," + encodeURI(HTML);
 
 add_task(async function() {
   const hud = await openNewTabAndConsole(TEST_URI);
   const toolbox = gDevTools.getToolbox(hud.target);
+  await toolbox.initInspector();
 
   await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
     content.wrappedJSObject.logNode("h1");
   });
 
   const msg = await waitFor(() => findMessage(hud, "<h1>"));
   const node = msg.querySelector(".objectBox-node");
   ok(node !== null, "Node was logged as expected");
   const view = node.ownerDocument.defaultView;
 
   info("Highlight the node by moving the cursor on it");
-  const onNodeHighlight = toolbox.once("node-highlight");
+  const onNodeHighlight = toolbox.highlighter.once("node-highlight");
   EventUtils.synthesizeMouseAtCenter(node, {type: "mousemove"}, view);
 
   const nodeFront = await onNodeHighlight;
   is(nodeFront.displayName, "h1", "The correct node was highlighted");
 
   info("Unhighlight the node by moving away from the node");
-  const onNodeUnhighlight = toolbox.once("node-unhighlight");
+  const onNodeUnhighlight = toolbox.highlighter.once("node-unhighlight");
   const btn = toolbox.doc.getElementById("toolbox-meatball-menu-button");
   EventUtils.synthesizeMouseAtCenter(btn, {type: "mousemove"}, view);
 
   await onNodeUnhighlight;
   ok(true, "node-unhighlight event was fired when moving away from the node");
 });
--- a/devtools/client/webconsole/webconsole-output-wrapper.js
+++ b/devtools/client/webconsole/webconsole-output-wrapper.js
@@ -208,22 +208,23 @@ WebConsoleOutputWrapper.prototype = {
               return panel.panelWin.Netmonitor.inspectRequest(requestId);
             });
           },
           sourceMapService: this.toolbox ? this.toolbox.sourceMapURLService : null,
           highlightDomElement: async (grip, options = {}) => {
             if (!this.toolbox.highlighter) {
               return null;
             }
+            await this.toolbox.initInspector();
             const nodeFront = await this.toolbox.walker.gripToNodeFront(grip);
-            return this.toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
+            return this.toolbox.highlighter.highlight(nodeFront, options);
           },
           unHighlightDomElement: (forceHide = false) => {
-            return this.toolbox.highlighterUtils
-              ? this.toolbox.highlighterUtils.unhighlight(forceHide)
+            return this.toolbox.highlighter
+              ? this.toolbox.highlighter.unhighlight(forceHide)
               : null;
           },
           openNodeInInspector: async (grip) => {
             const onSelectInspector = this.toolbox.selectTool("inspector", "inspect_dom");
             const onGripNodeToFront = this.toolbox.walker.gripToNodeFront(grip);
             const [
               front,
               inspector,
--- a/devtools/shared/fronts/highlighters.js
+++ b/devtools/shared/fronts/highlighters.js
@@ -1,35 +1,75 @@
 /* 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/. */
 "use strict";
 
 const { FrontClassWithSpec, custom } = require("devtools/shared/protocol");
+const flags = require("devtools/shared/flags");
 const {
   customHighlighterSpec,
   highlighterSpec,
 } = require("devtools/shared/specs/highlighters");
 
 const HighlighterFront = FrontClassWithSpec(highlighterSpec, {
+  isNodeFrontHighlighted: false,
   // Update the object given a form representation off the wire.
   form: function(json) {
     this.actorID = json.actor;
     // FF42+ HighlighterActors starts exposing custom form, with traits object
     this.traits = json.traits || {};
   },
 
   pick: custom(function(doFocus) {
     if (doFocus && this.pickAndFocus) {
       return this.pickAndFocus();
     }
     return this._pick();
   }, {
     impl: "_pick",
   }),
+
+  /**
+   * Show the box model highlighter on a node in the content page.
+   * The node needs to be a NodeFront, as defined by the inspector actor
+   * @see devtools/server/actors/inspector/inspector.js
+   * @param {NodeFront} nodeFront The node to highlight
+   * @param {Object} options
+   * @return A promise that resolves when the node has been highlighted
+   */
+  highlight: async function(nodeFront, options = {}) {
+    if (!nodeFront) {
+      return;
+    }
+
+    this.isNodeFrontHighlighted = true;
+    await this.showBoxModel(nodeFront, options);
+    this.emit("node-highlight", nodeFront);
+  },
+
+  /**
+   * Hide the highlighter.
+   * @param {Boolean} forceHide Only really matters in test mode (when
+   * flags.testing is true). In test mode, hovering over several nodes
+   * in the markup view doesn't hide/show the highlighter to ease testing. The
+   * highlighter stays visible at all times, except when the mouse leaves the
+   * markup view, which is when this param is passed to true
+   * @return a promise that resolves when the highlighter is hidden
+   */
+  unhighlight: async function(forceHide = false) {
+    forceHide = forceHide || !flags.testing;
+
+    if (this.isNodeFrontHighlighted && forceHide) {
+      this.isNodeFrontHighlighted = false;
+      await this.hideBoxModel();
+    }
+
+    this.emit("node-unhighlight");
+  },
 });
 
 exports.HighlighterFront = HighlighterFront;
 
 const CustomHighlighterFront = FrontClassWithSpec(customHighlighterSpec, {
   _isShown: false,
 
   show: custom(function(...args) {