Bug 1317102 - Display multiple grid containers in the CSS Grid Inspector. r=pbro draft
authorGabriel Luong <gabriel.luong@gmail.com>
Wed, 27 Jun 2018 19:05:03 -0400
changeset 811701 a08cdf4e549d851fdfa2ccb3e57247d77e260e63
parent 811683 b8a8cac376ca7d7447f59f9e6c3df3b45f8a10c2
push id114394
push userbmo:gl@mozilla.com
push dateThu, 28 Jun 2018 01:06:32 +0000
reviewerspbro
bugs1317102
milestone63.0a1
Bug 1317102 - Display multiple grid containers in the CSS Grid Inspector. r=pbro MozReview-Commit-ID: FZDOJA5AUP2
devtools/client/inspector/grids/grid-inspector.js
devtools/client/inspector/grids/reducers/grids.js
devtools/client/inspector/grids/test/browser_grids_grid-list-no-grids.js
devtools/client/inspector/grids/test/browser_grids_grid-list-on-iframe-reloaded.js
devtools/client/inspector/grids/test/browser_grids_grid-list-on-mutation-element-added.js
devtools/client/inspector/grids/test/browser_grids_grid-list-on-mutation-element-removed.js
devtools/client/inspector/grids/test/browser_grids_grid-list-toggle-multiple-grids.js
devtools/client/inspector/grids/test/browser_grids_grid-list-toggle-single-grid.js
devtools/client/inspector/grids/test/browser_grids_restored-after-reload.js
devtools/client/inspector/grids/test/head.js
devtools/client/inspector/markup/views/element-container.js
devtools/client/inspector/markup/views/element-editor.js
devtools/client/inspector/rules/test/browser_rules_edit-display-grid-property.js
devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-mutation.js
devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-navigate.js
devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-reload.js
devtools/client/inspector/rules/test/browser_rules_grid-highlighter-restored-after-reload.js
devtools/client/inspector/rules/test/browser_rules_grid-toggle_01.js
devtools/client/inspector/rules/test/browser_rules_grid-toggle_01b.js
devtools/client/inspector/rules/test/browser_rules_grid-toggle_02.js
devtools/client/inspector/rules/test/browser_rules_grid-toggle_03.js
devtools/client/inspector/rules/test/browser_rules_grid-toggle_04.js
devtools/client/inspector/rules/views/text-property-editor.js
devtools/client/inspector/shared/highlighters-overlay.js
devtools/client/preferences/devtools-client.js
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
 const { throttle } = require("devtools/client/inspector/shared/utils");
+const flags = require("devtools/shared/flags");
 
 const {
   updateGridColor,
   updateGridHighlighted,
   updateGrids,
 } = require("./actions/grids");
 const {
   updateShowGridAreas,
@@ -112,16 +113,21 @@ class GridInspector {
       return;
     }
 
     this.document.addEventListener("mousemove", () => {
       this.highlighters.on("grid-highlighter-hidden", this.onHighlighterHidden);
       this.highlighters.on("grid-highlighter-shown", this.onHighlighterShown);
     }, { once: true });
 
+    if (flags.testing) {
+      this.highlighters.on("grid-highlighter-hidden", this.onHighlighterHidden);
+      this.highlighters.on("grid-highlighter-shown", this.onHighlighterShown);
+    }
+
     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
@@ -174,25 +180,25 @@ 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) {
-    const highlighted = nodeFront == this.highlighters.gridHighlighterShown;
+    const highlighted = this.highlighters.gridHighlighters.has(nodeFront);
 
     let color;
     if (customColor) {
       color = customColor;
-    } else if (highlighted && this.highlighters.state.grid.options) {
+    } else if (highlighted && this.highlighters.state.grids.has(nodeFront.actorID)) {
       // If the node front is currently highlighted, use the color from the highlighter
       // options.
-      color = this.highlighters.state.grid.options.color;
+      color = this.highlighters.state.grids.get(nodeFront.actorID).options.color;
     } else {
       // Otherwise use the color defined in the store for this node front.
       color = this.getGridColorForNodeFront(nodeFront);
     }
 
     return color || fallbackColor;
   }
 
@@ -218,39 +224,50 @@ class GridInspector {
   /**
    * Retrieve the shared SwatchColorPicker instance.
    */
   getSwatchColorPickerTooltip() {
     return this.swatchColorPickerTooltip;
   }
 
   /**
-   * Given a list of new grid fronts, and if we have a currently highlighted grid, check
+   * Given a list of new grid fronts, and if there are highlighted grids, check
    * if its fragments have changed.
    *
    * @param  {Array} newGridFronts
    *         A list of GridFront objects.
    * @return {Boolean}
    */
   haveCurrentFragmentsChanged(newGridFronts) {
-    const currentNode = this.highlighters.gridHighlighterShown;
-    if (!currentNode) {
+    const gridHighlighters = this.highlighters.gridHighlighters;
+
+    if (!gridHighlighters.size) {
       return false;
     }
 
-    const newGridFront = newGridFronts.find(g => g.containerNodeFront === currentNode);
-    if (!newGridFront) {
+    const gridFronts = newGridFronts.filter(g =>
+      gridHighlighters.has(g.containerNodeFront));
+    if (!gridFronts.length) {
       return false;
     }
 
     const { grids } = this.store.getState();
-    const oldFragments = grids.find(g => g.nodeFront === currentNode).gridFragments;
-    const newFragments = newGridFront.gridFragments;
+
+    for (const node of gridHighlighters.keys()) {
+      const oldFragments = grids
+        .find(g => g.nodeFront === node).gridFragments;
+      const newFragments = newGridFronts
+        .find(g => g.containerNodeFront === node).gridFragments;
 
-    return !compareFragmentsGeometry(oldFragments, newFragments);
+      if (!compareFragmentsGeometry(oldFragments, newFragments)) {
+        return true;
+      }
+    }
+
+    return false;
   }
 
   /**
    * Returns true if the layout panel is visible, and false otherwise.
    */
   isPanelVisible() {
     return this.inspector && this.inspector.toolbox && this.inspector.sidebar &&
            this.inspector.toolbox.currentToolId === "inspector" &&
@@ -312,17 +329,17 @@ class GridInspector {
           // closing.
           return;
         }
       }
 
       const colorForHost = customColors[hostname] ? customColors[hostname][i] : null;
       const fallbackColor = GRID_COLORS[i % GRID_COLORS.length];
       const color = this.getInitialGridColor(nodeFront, colorForHost, fallbackColor);
-      const highlighted = nodeFront == this.highlighters.gridHighlighterShown;
+      const highlighted = this.highlighters.gridHighlighters.has(nodeFront);
 
       grids.push({
         id: i,
         actorID: grid.actorID,
         color,
         direction: grid.direction,
         gridFragments: grid.gridFragments,
         highlighted,
@@ -337,51 +354,45 @@ class GridInspector {
   /**
    * Handler for "grid-highlighter-shown" events emitted from the
    * HighlightersOverlay. Passes nodefront and event name to handleHighlighterChange.
    * Required since on and off events need the same reference object.
    *
    * @param  {NodeFront} nodeFront
    *         The NodeFront of the grid container element for which the grid
    *         highlighter is shown for.
-   * @param  {Object} options
-   *         The highlighter options used for the highlighter being shown/hidden.
    */
-  onHighlighterShown(nodeFront, options) {
-    this.onHighlighterChange(nodeFront, true, options);
+  onHighlighterShown(nodeFront) {
+    this.onHighlighterChange(nodeFront, true);
   }
 
   /**
    * Handler for "grid-highlighter-hidden" events emitted from the
    * HighlightersOverlay. Passes nodefront and event name to handleHighlighterChange.
    * Required since on and off events need the same reference object.
    *
    * @param  {NodeFront} nodeFront
    *         The NodeFront of the grid container element for which the grid highlighter
    *         is hidden for.
-   * @param  {Object} options
-   *         The highlighter options used for the highlighter being shown/hidden.
    */
-  onHighlighterHidden(nodeFront, options) {
-    this.onHighlighterChange(nodeFront, false, options);
+  onHighlighterHidden(nodeFront) {
+    this.onHighlighterChange(nodeFront, false);
   }
 
   /**
    * Handler for "grid-highlighter-shown" and "grid-highlighter-hidden" events emitted
    * from the HighlightersOverlay. Updates the NodeFront's grid highlighted state.
    *
    * @param  {NodeFront} nodeFront
    *         The NodeFront of the grid container element for which the grid highlighter
    *         is shown for.
    * @param  {Boolean} highlighted
    *         If the grid should be updated to highlight or hide.
-   * @param  {Object} options
-   *         The highlighter options used for the highlighter being shown/hidden.
    */
-  onHighlighterChange(nodeFront, highlighted, options = {}) {
+  onHighlighterChange(nodeFront, highlighted) {
     if (!this.isPanelVisible()) {
       return;
     }
 
     const { grids } = this.store.getState();
     const grid = grids.find(g => g.nodeFront === nodeFront);
 
     if (!grid || grid.highlighted === highlighted) {
@@ -427,39 +438,35 @@ class GridInspector {
     try {
       newGridFronts = await this.layoutInspector.getGrids(this.walker.rootNode);
     } catch (e) {
       // This call might fail if called asynchrously after the toolbox is finished
       // closing.
       return;
     }
 
-    // Get the node front(s) from the current grid(s) so we can compare them to them to
-    // node(s) of the new grids.
-    const oldNodeFronts = grids.map(grid => grid.nodeFront.actorID);
-
     // In some cases, the nodes for current grids may have been removed from the DOM in
     // which case we need to update.
     if (grids.length && grids.some(grid => !grid.nodeFront.actorID)) {
       this.updateGridPanel(newGridFronts);
       return;
     }
 
-    // Otherwise, continue comparing with the new grids.
-    const newNodeFronts = newGridFronts.filter(grid => grid.containerNodeFront)
+    // Get the node front(s) from the current grid(s) so we can compare them to them to
+    // node(s) of the new grids.
+    const oldNodeFronts = grids.map(grid => grid.nodeFront.actorID);
+    const newNodeFronts = newGridFronts.filter(grid => grid.containerNode)
                                        .map(grid => grid.containerNodeFront.actorID);
+
     if (grids.length === newGridFronts.length &&
-        oldNodeFronts.sort().join(",") == newNodeFronts.sort().join(",")) {
+        oldNodeFronts.sort().join(",") == newNodeFronts.sort().join(",") &&
+        !this.haveCurrentFragmentsChanged(newGridFronts)) {
       // 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 ||
-          (this.highlighters.gridHighlighterShown &&
-           !this.haveCurrentFragmentsChanged(newGridFronts))) {
-        return;
-      }
+      return;
     }
 
     // Either the list of containers or the current fragments have changed, do update.
     this.updateGridPanel(newGridFronts);
   }
 
   /**
    * Handler for a change in the grid overlay color picker for a grid container.
@@ -541,19 +548,16 @@ class GridInspector {
    * Handler for a change in the input checkboxes in the GridList component.
    * Toggles on/off the grid highlighter for the provided grid container element.
    *
    * @param  {NodeFront} node
    *         The NodeFront of the grid container element for which the grid
    *         highlighter is toggled on/off for.
    */
   onToggleGridHighlighter(node) {
-    const { grids } = this.store.getState();
-    const grid = grids.find(g => g.nodeFront === node);
-    this.store.dispatch(updateGridHighlighted(node, !grid.highlighted));
     this.highlighters.toggleGridHighlighter(node, "grid");
   }
 
   /**
     * Handler for a change in the show grid areas checkbox in the GridDisplaySettings
     * component. Toggles on/off the option to show the grid areas in the grid highlighter.
     * Refreshes the shown grid highlighter for the grids currently highlighted.
     *
--- a/devtools/client/inspector/grids/reducers/grids.js
+++ b/devtools/client/inspector/grids/reducers/grids.js
@@ -1,20 +1,26 @@
 /* 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 Services = require("Services");
+
 const {
   UPDATE_GRID_COLOR,
   UPDATE_GRID_HIGHLIGHTED,
   UPDATE_GRIDS,
 } = require("../actions/index");
 
+// Maximum number of grid highlighters that can be displayed.
+const MAX_GRID_HIGHLIGHTERS =
+  Services.prefs.getIntPref("devtools.gridinspector.maxHighlighters");
+
 const INITIAL_GRIDS = [];
 
 const reducers = {
 
   [UPDATE_GRID_COLOR](grids, { nodeFront, color }) {
     const newGrids = grids.map(g => {
       if (g.nodeFront == nodeFront) {
         g = Object.assign({}, g, { color });
@@ -23,21 +29,31 @@ const reducers = {
       return g;
     });
 
     return newGrids;
   },
 
   [UPDATE_GRID_HIGHLIGHTED](grids, { nodeFront, highlighted }) {
     return grids.map(g => {
-      const isUpdatedNode = g.nodeFront === nodeFront;
+      if (MAX_GRID_HIGHLIGHTERS === 1) {
+        const isUpdatedNode = g.nodeFront === nodeFront;
+
+        return Object.assign({}, g, {
+          highlighted: isUpdatedNode && highlighted,
+        });
+      }
 
-      return Object.assign({}, g, {
-        highlighted: isUpdatedNode && highlighted
-      });
+      if (g.nodeFront == nodeFront) {
+        g = Object.assign({}, g, {
+          highlighted: !g.highlighted,
+        });
+      }
+
+      return g;
     });
   },
 
   [UPDATE_GRIDS](_, { grids }) {
     return grids;
   },
 
 };
--- a/devtools/client/inspector/grids/test/browser_grids_grid-list-no-grids.js
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-no-grids.js
@@ -23,12 +23,11 @@ add_task(async function() {
 
   await selectNode("#grid", inspector);
   const noGridList = doc.querySelector(".grid-pane .devtools-sidepanel-no-result");
   const gridList = doc.getElementById("grid-list");
 
   info("Checking the initial state of the Grid Inspector.");
   ok(noGridList, "The message no grid containers is displayed.");
   ok(!gridList, "No grid containers are listed.");
-  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
+  ok(!highlighters.gridHighlighters.size,
     "No CSS grid highlighter exists in the highlighters overlay.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
 });
--- a/devtools/client/inspector/grids/test/browser_grids_grid-list-on-iframe-reloaded.js
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-on-iframe-reloaded.js
@@ -18,17 +18,17 @@ add_task(async function() {
   const gridList = doc.querySelector("#grid-list");
 
   info("Clicking on the first checkbox to highlight the grid");
   await enableTheFirstGrid(doc, inspector);
 
   is(gridList.childNodes.length, 1, "There's one grid in the list");
   const checkbox = gridList.querySelector("input");
   ok(checkbox.checked, "The checkbox is checked");
-  ok(highlighters.gridHighlighterShown, "There's a highlighter shown");
+  is(highlighters.gridHighlighters.size, 1, "There's a highlighter shown");
 
   info("Reload the iframe in content and expect the grid list to update");
   const oldGrid = store.getState().grids[0];
   const onNewListUnchecked = waitUntilState(store, state =>
     state.grids.length == 1 &&
     state.grids[0].actorID !== oldGrid.actorID &&
     !state.grids[0].highlighted);
   const onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
@@ -37,17 +37,17 @@ add_task(async function() {
   await onHighlighterHidden;
 
   is(gridList.childNodes.length, 1, "There's still one grid in the list");
 
   info("Highlight the first grid again to make sure this still works");
   await enableTheFirstGrid(doc, inspector);
 
   is(gridList.childNodes.length, 1, "There's again one grid in the list");
-  ok(highlighters.gridHighlighterShown, "There's a highlighter shown");
+  is(highlighters.gridHighlighters.size, 1, "There's a highlighter shown");
 });
 
 async function enableTheFirstGrid(doc, { highlighters, store }) {
   const checkbox = doc.querySelector("#grid-list input");
 
   const onHighlighterShown = highlighters.once("grid-highlighter-shown");
   const onCheckboxChange = waitUntilState(store, state =>
     state.grids.length == 1 && state.grids[0].highlighted);
--- a/devtools/client/inspector/grids/test/browser_grids_grid-list-on-mutation-element-added.js
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-on-mutation-element-added.js
@@ -28,66 +28,61 @@ add_task(async function() {
   const { highlighters, store } = inspector;
 
   await selectNode("#grid", inspector);
   const gridList = doc.getElementById("grid-list");
   const checkbox1 = gridList.children[0].querySelector("input");
 
   info("Checking the initial state of the Grid Inspector.");
   is(gridList.childNodes.length, 1, "One grid container is listed.");
-  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
+  ok(!highlighters.gridHighlighters.size,
     "No CSS grid highlighter exists in the highlighters overlay.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter from the layout panel.");
   let onHighlighterShown = highlighters.once("grid-highlighter-shown");
   checkbox1.click();
   await onHighlighterShown;
 
   info("Checking the CSS grid highlighter is created.");
-  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "CSS grid highlighter is created in the highlighters overlay.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Adding the #grid2 container in the content page.");
   const onGridListUpdate = waitUntilState(store, state =>
     state.grids.length == 2 &&
     state.grids[0].highlighted &&
     !state.grids[1].highlighted);
   testActor.eval(`
     document.getElementById("grid2").classList.add("grid");
   `);
   await onGridListUpdate;
 
   info("Checking the new Grid Inspector state.");
   is(gridList.childNodes.length, 2, "Two grid containers are listed.");
-  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "CSS grid highlighter is created in the highlighters overlay.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   const checkbox2 = gridList.children[1].querySelector("input");
 
   info("Toggling ON the CSS grid highlighter for #grid2.");
   onHighlighterShown = highlighters.once("grid-highlighter-shown");
   let onCheckboxChange = waitUntilState(store, state =>
     state.grids.length == 2 &&
     !state.grids[0].highlighted &&
     state.grids[1].highlighted);
   checkbox2.click();
   await onHighlighterShown;
   await onCheckboxChange;
 
   info("Checking the CSS grid highlighter is still shown.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Toggling OFF the CSS grid highlighter from the layout panel.");
   const onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
   onCheckboxChange = waitUntilState(store, state =>
     state.grids.length == 2 &&
     !state.grids[0].highlighted &&
     !state.grids[1].highlighted);
   checkbox2.click();
   await onHighlighterHidden;
   await onCheckboxChange;
 
   info("Checking the CSS grid highlighter is not shown.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 });
--- a/devtools/client/inspector/grids/test/browser_grids_grid-list-on-mutation-element-removed.js
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-on-mutation-element-removed.js
@@ -25,39 +25,35 @@ add_task(async function() {
   const { highlighters, store } = inspector;
 
   await selectNode("#grid", inspector);
   const gridList = doc.getElementById("grid-list");
   const checkbox = gridList.children[0].querySelector("input");
 
   info("Checking the initial state of the Grid Inspector.");
   is(gridList.childNodes.length, 1, "One grid container is listed.");
-  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "No CSS grid highlighter exists in the highlighters overlay.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter from the layout panel.");
   const onHighlighterShown = highlighters.once("grid-highlighter-shown");
   let onCheckboxChange = waitUntilState(store, state =>
     state.grids.length == 1 && state.grids[0].highlighted);
   checkbox.click();
   await onHighlighterShown;
   await onCheckboxChange;
 
   info("Checking the CSS grid highlighter is created.");
-  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "CSS grid highlighter is created in the highlighters overlay.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Removing the #grid container in the content page.");
   const onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
   onCheckboxChange = waitUntilState(store, state => state.grids.length == 0);
   testActor.eval(`
     document.getElementById("grid").remove();
   `);
   await onHighlighterHidden;
   await onCheckboxChange;
 
   info("Checking the CSS grid highlighter is not shown.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
   const noGridList = doc.querySelector(".grid-pane .devtools-sidepanel-no-result");
   ok(noGridList, "The message no grid containers is displayed.");
 });
--- a/devtools/client/inspector/grids/test/browser_grids_grid-list-toggle-multiple-grids.js
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-toggle-multiple-grids.js
@@ -32,53 +32,49 @@ add_task(async function() {
   const gridList = doc.getElementById("grid-list");
   const checkbox1 = gridList.children[0].querySelector("input");
   const checkbox2 = gridList.children[1].querySelector("input");
 
   info("Checking the initial state of the Grid Inspector.");
   is(gridList.childNodes.length, 2, "2 grid containers are listed.");
   ok(!checkbox1.checked, `Grid item ${checkbox1.value} is unchecked in the grid list.`);
   ok(!checkbox2.checked, `Grid item ${checkbox2.value} is unchecked in the grid list.`);
-  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "No CSS grid highlighter exists in the highlighters overlay.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter for #grid1.");
   let onHighlighterShown = highlighters.once("grid-highlighter-shown");
   let onCheckboxChange = waitUntilState(store, state =>
     state.grids.length == 2 &&
     state.grids[0].highlighted &&
     !state.grids[1].highlighted);
   checkbox1.click();
   await onHighlighterShown;
   await onCheckboxChange;
 
   info("Checking the CSS grid highlighter is created.");
-  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "CSS grid highlighter is created in the highlighters overlay.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter for #grid2.");
   onHighlighterShown = highlighters.once("grid-highlighter-shown");
   onCheckboxChange = waitUntilState(store, state =>
     state.grids.length == 2 &&
     !state.grids[0].highlighted &&
     state.grids[1].highlighted);
   checkbox2.click();
   await onHighlighterShown;
   await onCheckboxChange;
 
   info("Checking the CSS grid highlighter is still shown.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Toggling OFF the CSS grid highlighter from the layout panel.");
   const onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
   onCheckboxChange = waitUntilState(store, state =>
     state.grids.length == 2 &&
     !state.grids[0].highlighted &&
     !state.grids[1].highlighted);
   checkbox2.click();
   await onHighlighterHidden;
   await onCheckboxChange;
 
   info("Checking the CSS grid highlighter is not shown.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 });
--- a/devtools/client/inspector/grids/test/browser_grids_grid-list-toggle-single-grid.js
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-toggle-single-grid.js
@@ -25,38 +25,34 @@ add_task(async function() {
 
   await selectNode("#grid", inspector);
   const gridList = doc.getElementById("grid-list");
   const checkbox = gridList.children[0].querySelector("input");
 
   info("Checking the initial state of the Grid Inspector.");
   is(gridList.childNodes.length, 1, "One grid container is listed.");
   ok(!checkbox.checked, `Grid item ${checkbox.value} is unchecked in the grid list.`);
-  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "No CSS grid highlighter exists in the highlighters overlay.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter from the layout panel.");
   const onHighlighterShown = highlighters.once("grid-highlighter-shown");
   let onCheckboxChange = waitUntilState(store, state =>
     state.grids.length == 1 &&
     state.grids[0].highlighted);
   checkbox.click();
   await onHighlighterShown;
   await onCheckboxChange;
 
   info("Checking the CSS grid highlighter is created.");
-  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "CSS grid highlighter is created in the highlighters overlay.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Toggling OFF the CSS grid highlighter from the layout panel.");
   const onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
   onCheckboxChange = waitUntilState(store, state =>
     state.grids.length == 1 &&
     !state.grids[0].highlighted);
   checkbox.click();
   await onHighlighterHidden;
   await onCheckboxChange;
 
   info("Checking the CSS grid highlighter is not shown.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 });
--- a/devtools/client/inspector/grids/test/browser_grids_restored-after-reload.js
+++ b/devtools/client/inspector/grids/test/browser_grids_restored-after-reload.js
@@ -47,41 +47,39 @@ add_task(async function() {
   const onCheckboxChange = waitUntilState(store, state =>
     state.grids.length == 1 &&
     state.grids[0].highlighted);
   checkbox.click();
   await onHighlighterShown;
   await onCheckboxChange;
 
   info("Checking the CSS grid highlighter is created.");
-  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "CSS grid highlighter is created in the highlighters overlay.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Reload the page, expect the highlighter to be displayed once again and " +
     "grid is checked");
   let onStateRestored = highlighters.once("grid-state-restored");
   let onGridListRestored = waitUntilState(store, state =>
     state.grids.length == 1 &&
     state.grids[0].highlighted);
   await refreshTab();
   let { restored } = await onStateRestored;
   await onGridListRestored;
 
   info("Check that the grid highlighter can be displayed after reloading the page");
   ok(restored, "The highlighter state was restored");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Navigate to another URL, and check that the highlighter is hidden and " +
     "grid is unchecked");
   const otherUri = "data:text/html;charset=utf-8," + encodeURIComponent(OTHER_URI);
   onStateRestored = highlighters.once("grid-state-restored");
   onGridListRestored = waitUntilState(store, state =>
     state.grids.length == 1 &&
     !state.grids[0].highlighted);
   await navigateTo(inspector, otherUri);
   ({ restored } = await onStateRestored);
   await onGridListRestored;
 
   info("Check that the grid highlighter is hidden after navigating to a different page");
   ok(!restored, "The highlighter state was not restored");
-  ok(!highlighters.gridHighlighterShown, "CSS grid highlighter is hidden.");
+  ok(!highlighters.gridHighlighters.size, "CSS grid highlighter is hidden.");
 });
--- a/devtools/client/inspector/grids/test/head.js
+++ b/devtools/client/inspector/grids/test/head.js
@@ -18,17 +18,16 @@ Services.scriptloader.loadSubScript(
   this);
 
 Services.prefs.setIntPref("devtools.toolbox.footer.height", 350);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.toolbox.footer.height");
 });
 
 const asyncStorage = require("devtools/shared/async-storage");
-const HIGHLIGHTER_TYPE = "CssGridHighlighter";
 
 /**
  * Simulate a color change in a given color picker tooltip.
  *
  * @param  {Spectrum} colorPicker
  *         The color picker widget.
  * @param  {Array} newRgba
  *         Array of the new rgba values to be set in the color widget.
--- a/devtools/client/inspector/markup/views/element-container.js
+++ b/devtools/client/inspector/markup/views/element-container.js
@@ -66,17 +66,17 @@ MarkupElementContainer.prototype = exten
 
   /**
    * Handler for "grid-highlighter-hidden" and "grid-highlighter-shown" event emitted from
    * the HighlightersOverlay. Toggles the active state of the display badge if it matches
    * the highlighted grid node.
    */
   onGridHighlighterChange: function() {
     this.editor.displayBadge.classList.toggle("active",
-      this.markup.highlighters.gridHighlighterShown === this.node);
+      this.markup.highlighters.gridHighlighters.has(this.node));
   },
 
   async _buildEventTooltipContent(target) {
     const tooltip = this.markup.eventDetailsTooltip;
 
     await tooltip.hide();
 
     const listenerInfo = await this.node.getEventListenerInfo();
--- a/devtools/client/inspector/markup/views/element-editor.js
+++ b/devtools/client/inspector/markup/views/element-editor.js
@@ -290,17 +290,17 @@ ElementEditor.prototype = {
   updateDisplayBadge: function() {
     const showDisplayBadge = this.node.displayType in DISPLAY_TYPES;
     this.displayBadge.textContent = this.node.displayType;
     this.displayBadge.dataset.display = showDisplayBadge ? this.node.displayType : "";
     this.displayBadge.style.display = showDisplayBadge ? "inline-block" : "none";
     this.displayBadge.title = showDisplayBadge ?
       DISPLAY_TYPES[this.node.displayType] : "";
     this.displayBadge.classList.toggle("active",
-      this.highlighters.gridHighlighterShown === this.node);
+      this.highlighters.gridHighlighters.has(this.node));
   },
 
   /**
    * Update the inline text editor in case of a single text child node.
    */
   updateTextEditor: function() {
     const node = this.node.inlineTextChild;
 
--- a/devtools/client/inspector/rules/test/browser_rules_edit-display-grid-property.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-display-grid-property.js
@@ -40,10 +40,10 @@ add_task(async function() {
   editor.input.value = "block;";
   EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
   await onHighlighterHidden;
   await onDone;
 
   info("Check the grid highlighter and grid toggle button are hidden.");
   gridToggle = container.querySelector(".ruleview-grid");
   ok(!gridToggle, "Grid highlighter toggle is not visible.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-mutation.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-mutation.js
@@ -27,18 +27,18 @@ add_task(async function() {
   await selectNode("#grid", inspector);
   const container = getRuleViewProperty(view, "#grid", "display").valueSpan;
   const gridToggle = container.querySelector(".ruleview-grid");
 
   info("Toggling ON the CSS grid highlighter from the rule-view.");
   const onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   await onHighlighterShown;
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   const onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
   info("Remove the #grid container in the content page");
   testActor.eval(`
     document.querySelector("#grid").remove();
   `);
   await onHighlighterHidden;
-  ok(!highlighters.gridHighlighterShown, "CSS grid highlighter is hidden.");
+  ok(!highlighters.gridHighlighters.size, "CSS grid highlighter is hidden.");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-navigate.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-navigate.js
@@ -29,13 +29,13 @@ add_task(async function() {
   const container = getRuleViewProperty(view, "#grid", "display").valueSpan;
   const gridToggle = container.querySelector(".ruleview-grid");
 
   info("Toggling ON the CSS grid highlighter from the rule-view.");
   const onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   await onHighlighterShown;
 
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   await navigateTo(inspector, TEST_URI_2);
-  ok(!highlighters.gridHighlighterShown, "CSS grid highlighter is hidden.");
+  ok(!highlighters.gridHighlighters.size, "CSS grid highlighter is hidden.");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-reload.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-reload.js
@@ -44,10 +44,10 @@ async function checkGridHighlighter() {
   const container = getRuleViewProperty(view, "#grid", "display").valueSpan;
   const gridToggle = container.querySelector(".ruleview-grid");
 
   info("Toggling ON the CSS grid highlighter from the rule-view.");
   const onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   await onHighlighterShown;
 
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 }
--- a/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-restored-after-reload.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-restored-after-reload.js
@@ -42,27 +42,27 @@ add_task(async function() {
   const container = getRuleViewProperty(view, "#grid", "display").valueSpan;
   const gridToggle = container.querySelector(".ruleview-grid");
 
   info("Toggling ON the CSS grid highlighter from the rule-view.");
   const onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   await onHighlighterShown;
 
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Reload the page, expect the highlighter to be displayed once again");
   let onStateRestored = highlighters.once("grid-state-restored");
   await refreshTab();
   let { restored } = await onStateRestored;
   ok(restored, "The highlighter state was restored");
 
   info("Check that the grid highlighter can be displayed after reloading the page");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Navigate to another URL, and check that the highlighter is hidden");
   const otherUri = "data:text/html;charset=utf-8," + encodeURIComponent(OTHER_URI);
   onStateRestored = highlighters.once("grid-state-restored");
   await navigateTo(inspector, otherUri);
   ({ restored } = await onStateRestored);
   ok(!restored, "The highlighter state was not restored");
-  ok(!highlighters.gridHighlighterShown, "CSS grid highlighter is hidden.");
+  ok(!highlighters.gridHighlighters.size, "CSS grid highlighter is hidden.");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_01.js
@@ -14,51 +14,45 @@ const TEST_URI = `
     }
   </style>
   <div id="grid">
     <div id="cell1">cell1</div>
     <div id="cell2">cell2</div>
   </div>
 `;
 
-const HIGHLIGHTER_TYPE = "CssGridHighlighter";
-
 add_task(async function() {
   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   const {inspector, view} = await openRuleView();
   const highlighters = view.highlighters;
 
   await selectNode("#grid", inspector);
   const container = getRuleViewProperty(view, "#grid", "display").valueSpan;
   const gridToggle = container.querySelector(".ruleview-grid");
 
   info("Checking the initial state of the CSS grid toggle in the rule-view.");
   ok(gridToggle, "Grid highlighter toggle is visible.");
   ok(!gridToggle.classList.contains("active"),
     "Grid highlighter toggle button is not active.");
-  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "No CSS grid highlighter exists in the rule-view.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter from the rule-view.");
   const onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   await onHighlighterShown;
 
   info("Checking the CSS grid highlighter is created and toggle button is active in " +
     "the rule-view.");
   ok(gridToggle.classList.contains("active"),
     "Grid highlighter toggle is active.");
-  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "CSS grid highlighter created in the rule-view.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Toggling OFF the CSS grid highlighter from the rule-view.");
   const onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
   gridToggle.click();
   await onHighlighterHidden;
 
   info("Checking the CSS grid highlighter is not shown and toggle button is not active " +
     "in the rule-view.");
   ok(!gridToggle.classList.contains("active"),
     "Grid highlighter toggle button is not active.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_01b.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_01b.js
@@ -14,51 +14,45 @@ const TEST_URI = `
     }
   </style>
   <div id="grid">
     <div id="cell1">cell1</div>
     <div id="cell2">cell2</div>
   </div>
 `;
 
-const HIGHLIGHTER_TYPE = "CssGridHighlighter";
-
 add_task(async function() {
   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   const {inspector, view} = await openRuleView();
   const highlighters = view.highlighters;
 
   await selectNode("#grid", inspector);
   const container = getRuleViewProperty(view, "#grid", "display").valueSpan;
   const gridToggle = container.querySelector(".ruleview-grid");
 
   info("Checking the initial state of the CSS grid toggle in the rule-view.");
   ok(gridToggle, "Grid highlighter toggle is visible.");
   ok(!gridToggle.classList.contains("active"),
     "Grid highlighter toggle button is not active.");
-  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "No CSS grid highlighter exists in the rule-view.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter from the rule-view.");
   const onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   await onHighlighterShown;
 
   info("Checking the CSS grid highlighter is created and toggle button is active in " +
     "the rule-view.");
   ok(gridToggle.classList.contains("active"),
     "Grid highlighter toggle is active.");
-  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "CSS grid highlighter created in the rule-view.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Toggling OFF the CSS grid highlighter from the rule-view.");
   const onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
   gridToggle.click();
   await onHighlighterHidden;
 
   info("Checking the CSS grid highlighter is not shown and toggle button is not active " +
     "in the rule-view.");
   ok(!gridToggle.classList.contains("active"),
     "Grid highlighter toggle button is not active.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_02.js
@@ -17,57 +17,51 @@ const TEST_URI = `
     }
   </style>
   <ul id="grid">
     <li id="cell1">cell1</li>
     <li id="cell2">cell2</li>
   </ul>
 `;
 
-const HIGHLIGHTER_TYPE = "CssGridHighlighter";
-
 add_task(async function() {
   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   const {inspector, view} = await openRuleView();
   const highlighters = view.highlighters;
 
   await selectNode("#grid", inspector);
   const container = getRuleViewProperty(view, "#grid", "display").valueSpan;
   const gridToggle = container.querySelector(".ruleview-grid");
   const overriddenContainer = getRuleViewProperty(view, "div, ul", "display").valueSpan;
   const overriddenGridToggle = overriddenContainer.querySelector(".ruleview-grid");
 
   info("Checking the initial state of the CSS grid toggle in the rule-view.");
   ok(gridToggle && overriddenGridToggle, "Grid highlighter toggles are visible.");
   ok(!gridToggle.classList.contains("active") &&
     !overriddenGridToggle.classList.contains("active"),
     "Grid highlighter toggle buttons are not active.");
-  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "No CSS grid highlighter exists in the rule-view.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter from the overridden rule in the rule-view.");
   const onHighlighterShown = highlighters.once("grid-highlighter-shown");
   overriddenGridToggle.click();
   await onHighlighterShown;
 
   info("Checking the CSS grid highlighter is created and toggle buttons are active in " +
     "the rule-view.");
   ok(gridToggle.classList.contains("active") &&
     overriddenGridToggle.classList.contains("active"),
     "Grid highlighter toggle is active.");
-  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "CSS grid highlighter created in the rule-view.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Toggling off the CSS grid highlighter from the normal grid declaration in the " +
     "rule-view.");
   const onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
   gridToggle.click();
   await onHighlighterHidden;
 
   info("Checking the CSS grid highlighter is not shown and toggle buttons are not " +
     "active in the rule-view.");
   ok(!gridToggle.classList.contains("active") &&
     !overriddenGridToggle.classList.contains("active"),
     "Grid highlighter toggle buttons are not active.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_03.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_03.js
@@ -17,75 +17,69 @@ const TEST_URI = `
     <div class="cell2">cell2</div>
   </div>
   <div id="grid2" class="grid">
     <div class="cell1">cell1</div>
     <div class="cell2">cell2</div>
   </div>
 `;
 
-const HIGHLIGHTER_TYPE = "CssGridHighlighter";
-
 add_task(async function() {
   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   const {inspector, view} = await openRuleView();
   const highlighters = view.highlighters;
 
   info("Selecting the first grid container.");
   await selectNode("#grid1", inspector);
   let container = getRuleViewProperty(view, ".grid", "display").valueSpan;
   let gridToggle = container.querySelector(".ruleview-grid");
 
   info("Checking the state of the CSS grid toggle for the first grid container in the " +
     "rule-view.");
   ok(gridToggle, "Grid highlighter toggle is visible.");
   ok(!gridToggle.classList.contains("active"),
     "Grid highlighter toggle button is not active.");
-  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "No CSS grid highlighter exists in the rule-view.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter for the first grid container from the " +
     "rule-view.");
   let onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   await onHighlighterShown;
 
   info("Checking the CSS grid highlighter is created and toggle button is active in " +
     "the rule-view.");
   ok(gridToggle.classList.contains("active"),
     "Grid highlighter toggle is active.");
-  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "CSS grid highlighter created in the rule-view.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Selecting the second grid container.");
   await selectNode("#grid2", inspector);
-  const firstGridHighterShown = highlighters.gridHighlighterShown;
+  const firstGridHighterShown = highlighters.gridHighlighters.keys().next().value;
   container = getRuleViewProperty(view, ".grid", "display").valueSpan;
   gridToggle = container.querySelector(".ruleview-grid");
 
   info("Checking the state of the CSS grid toggle for the second grid container in the " +
     "rule-view.");
   ok(gridToggle, "Grid highlighter toggle is visible.");
   ok(!gridToggle.classList.contains("active"),
     "Grid highlighter toggle button is not active.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is still shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is still shown.");
 
   info("Toggling ON the CSS grid highlighter for the second grid container from the " +
     "rule-view.");
   onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   await onHighlighterShown;
 
   info("Checking the CSS grid highlighter is created for the second grid container and " +
     "toggle button is active in the rule-view.");
   ok(gridToggle.classList.contains("active"),
     "Grid highlighter toggle is active.");
-  ok(highlighters.gridHighlighterShown != firstGridHighterShown,
+  ok(highlighters.gridHighlighters.keys().next().value != firstGridHighterShown,
     "Grid highlighter for the second grid container is shown.");
 
   info("Selecting the first grid container.");
   await selectNode("#grid1", inspector);
   container = getRuleViewProperty(view, ".grid", "display").valueSpan;
   gridToggle = container.querySelector(".ruleview-grid");
 
   info("Checking the state of the CSS grid toggle for the first grid container in the " +
--- a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_04.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_04.js
@@ -14,51 +14,45 @@ const TEST_URI = `
     }
   </style>
   <div id="grid">
     <div id="cell1">cell1</div>
     <div id="cell2">cell2</div>
   </div>
 `;
 
-const HIGHLIGHTER_TYPE = "CssGridHighlighter";
-
 add_task(async function() {
   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   const {inspector, view} = await openRuleView();
   const highlighters = view.highlighters;
 
   await selectNode("#grid", inspector);
   const container = getRuleViewProperty(view, "#grid", "display").valueSpan;
   const gridToggle = container.querySelector(".ruleview-grid");
 
   info("Checking the initial state of the CSS grid toggle in the rule-view.");
   ok(gridToggle, "Grid highlighter toggle is visible.");
   ok(!gridToggle.classList.contains("active"),
     "Grid highlighter toggle button is not active.");
-  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "No CSS grid highlighter exists in the rule-view.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter from the rule-view.");
   const onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   await onHighlighterShown;
 
   info("Checking the CSS grid highlighter is created and toggle button is active in " +
     "the rule-view.");
   ok(gridToggle.classList.contains("active"),
     "Grid highlighter toggle is active.");
-  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
-    "CSS grid highlighter created in the rule-view.");
-  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+  is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
 
   info("Toggling OFF the CSS grid highlighter from the rule-view.");
   const onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
   gridToggle.click();
   await onHighlighterHidden;
 
   info("Checking the CSS grid highlighter is not shown and toggle button is not active " +
     "in the rule-view.");
   ok(!gridToggle.classList.contains("active"),
     "Grid highlighter toggle button is not active.");
-  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
 });
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -527,18 +527,18 @@ TextPropertyEditor.prototype = {
           this.ruleView.inspector.selection.nodeFront) {
         flexToggle.classList.add("active");
       }
     }
 
     const gridToggle = this.valueSpan.querySelector(".ruleview-grid");
     if (gridToggle) {
       gridToggle.setAttribute("title", l10n("rule.gridToggle.tooltip"));
-      if (this.ruleView.highlighters.gridHighlighterShown ===
-          this.ruleView.inspector.selection.nodeFront) {
+      if (this.ruleView.highlighters.gridHighlighters.has(
+            this.ruleView.inspector.selection.nodeFront)) {
         gridToggle.classList.add("active");
       }
     }
 
     const shapeToggle = this.valueSpan.querySelector(".ruleview-shapeswatch");
     if (shapeToggle) {
       const mode = "css" + name.split("-").map(s => {
         return s[0].toUpperCase() + s.slice(1);
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -1,65 +1,72 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* 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 Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {
   VIEW_NODE_VALUE_TYPE,
   VIEW_NODE_SHAPE_POINT_TYPE
 } = require("devtools/client/inspector/shared/node-types");
 
+// Maximum number of grid highlighters that can be displayed.
+const MAX_GRID_HIGHLIGHTERS =
+  Services.prefs.getIntPref("devtools.gridinspector.maxHighlighters");
+
 const DEFAULT_GRID_COLOR = "#4B0082";
 
 /**
  * Highlighters overlay is a singleton managing all highlighters in the Inspector.
  */
 class HighlightersOverlay {
   /**
    * @param  {Inspector} inspector
    *         Inspector toolbox panel.
    */
   constructor(inspector) {
-    /*
-    * Collection of instantiated highlighter actors like FlexboxHighlighter,
-    * CssGridHighlighter, ShapesHighlighter and GeometryEditorHighlighter.
-    */
+    /**
+     * Collection of instantiated highlighter actors like FlexboxHighlighter,
+     * ShapesHighlighter and GeometryEditorHighlighter.
+     */
     this.highlighters = {};
-    /*
-    * Collection of instantiated in-context editors, like ShapesInContextEditor, which
-    * behave like highlighters but with added editing capabilities that need to map value
-    * changes to properties in the Rule view.
-    */
+    // Collection of instantiated grid highlighter actors.
+    this.gridHighlighters = new Map();
+    /**
+     * Collection of instantiated in-context editors, like ShapesInContextEditor, which
+     * behave like highlighters but with added editing capabilities that need to map value
+     * changes to properties in the Rule view.
+     */
     this.editors = {};
     this.inspector = inspector;
     this.highlighterUtils = this.inspector.toolbox.highlighterUtils;
     this.store = this.inspector.store;
     this.telemetry = inspector.telemetry;
 
     // NodeFront of the flexbox container that is highlighted.
     this.flexboxHighlighterShown = null;
     // NodeFront of element that is highlighted by the geometry editor.
     this.geometryEditorHighlighterShown = null;
-    // NodeFront of the grid container that is highlighted.
-    this.gridHighlighterShown = null;
     // Name of the highlighter shown on mouse hover.
     this.hoveredHighlighterShown = null;
     // Name of the selector highlighter shown.
     this.selectorHighlighterShown = null;
     // NodeFront of the shape that is highlighted
     this.shapesHighlighterShown = null;
+
     // Saved state to be restore on page navigation.
     this.state = {
       flexbox: {},
-      grid: {},
+      // Map of grid NodeFront actorID to the their stored grid options
+      grids: new Map(),
       shapes: {},
     };
 
     this.onClick = this.onClick.bind(this);
     this.onMarkupMutation = this.onMarkupMutation.bind(this);
     this.onMouseMove = this.onMouseMove.bind(this);
     this.onMouseOut = this.onMouseOut.bind(this);
     this.onWillNavigate = this.onWillNavigate.bind(this);
@@ -117,17 +124,17 @@ class HighlightersOverlay {
     const el = view.element;
     el.removeEventListener("click", this.onClick, true);
     el.removeEventListener("mousemove", this.onMouseMove);
     el.removeEventListener("mouseout", this.onMouseOut);
   }
 
   /**
    * Toggle the shapes highlighter for the given node.
-
+   *
    * @param  {NodeFront} node
    *         The NodeFront of the element with a shape to highlight.
    * @param  {Object} options
    *         Object used for passing options to the shapes highlighter.
    * @param {TextProperty} textProperty
    *        TextProperty where to write changes.
    */
   async toggleShapesHighlighter(node, options, textProperty) {
@@ -311,17 +318,17 @@ class HighlightersOverlay {
    * @param  {NodeFront} node
    *         The NodeFront of the grid container element to highlight.
    * @param. {String|null} trigger
    *         String name matching "grid" or "rule" to indicate where the
    *         grid highlighter was toggled on from. "grid" represents the grid view
    *         "rule" represents the rule view.
    */
   async toggleGridHighlighter(node, trigger) {
-    if (node == this.gridHighlighterShown) {
+    if (this.gridHighlighters.has(node)) {
       await this.hideGridHighlighter(node);
       return;
     }
 
     await this.showGridHighlighter(node, {}, trigger);
   }
 
   /**
@@ -332,17 +339,28 @@ class HighlightersOverlay {
    * @param  {Object} options
    *         Object used for passing options to the grid highlighter.
    * @param. {String|null} trigger
    *         String name matching "grid" or "rule" to indicate where the
    *         grid highlighter was toggled on from. "grid" represents the grid view
    *         "rule" represents the rule view.
    */
   async showGridHighlighter(node, options, trigger) {
-    const highlighter = await this._getHighlighter("CssGridHighlighter");
+    if (!this.gridHighlighters.has(node) && MAX_GRID_HIGHLIGHTERS === 1) {
+      for (const nodeFront of this.gridHighlighters.keys()) {
+        await this.hideGridHighlighter(nodeFront);
+      }
+    }
+
+    if (!this.gridHighlighters.has(node) &&
+        this.gridHighlighters.size === MAX_GRID_HIGHLIGHTERS) {
+      return;
+    }
+
+    const highlighter = await this._getGridHighlighter(node);
     if (!highlighter) {
       return;
     }
 
     options = Object.assign({}, options, this.getGridHighlighterSettings(node));
 
     const isShown = await highlighter.show(node, options);
     if (!isShown) {
@@ -356,49 +374,49 @@ class HighlightersOverlay {
     } else if (trigger == "rule") {
       this.telemetry.scalarAdd("devtools.rules.gridinspector.opened", 1);
     }
 
     try {
       // Save grid highlighter state.
       const { url } = this.inspector.target;
       const selector = await node.getUniqueSelector();
-      this.state.grid = { selector, options, url };
-      this.gridHighlighterShown = node;
+      this.state.grids.set(node.actorID, { selector, options, url });
+
       // Emit the NodeFront of the grid container element that the grid highlighter was
-      // shown for.
+      // shown for, and its options for testing the highlighter setting options.
       this.emit("grid-highlighter-shown", node, options);
     } catch (e) {
       this._handleRejection(e);
     }
   }
 
   /**
    * Hide the grid highlighter for the given grid container element.
    *
    * @param  {NodeFront} node
    *         The NodeFront of the grid container element to unhighlight.
    */
   async hideGridHighlighter(node) {
-    if (!this.gridHighlighterShown || !this.highlighters.CssGridHighlighter) {
+    if (!this.gridHighlighters.has(node)) {
       return;
     }
 
     this._toggleRuleViewIcon(node, false, ".ruleview-grid");
 
-    await this.highlighters.CssGridHighlighter.hide();
+    const highlighter = this.gridHighlighters.get(node);
+    await highlighter.finalize();
+
+    this.gridHighlighters.delete(node);
 
     // Emit the NodeFront of the grid container element that the grid highlighter was
     // hidden for.
-    const nodeFront = this.gridHighlighterShown;
-    this.gridHighlighterShown = null;
-    this.emit("grid-highlighter-hidden", nodeFront, this.state.grid.options);
+    this.emit("grid-highlighter-hidden", node);
 
-    // Erase grid highlighter state.
-    this.state.grid = {};
+    this.state.grids.delete(node.actorID);
   }
 
   /**
    * Show the box model highlighter for the given node.
    *
    * @param  {NodeFront} node
    *         The NodeFront of the element to highlight.
    * @param  {Object} options
@@ -495,17 +513,19 @@ class HighlightersOverlay {
     }
   }
 
   /**
    * Restores the saved grid highlighter state.
    */
   async restoreGridState() {
     try {
-      await this.restoreState("grid", this.state.grid, this.showGridHighlighter);
+      for (const state of this.state.grids.values()) {
+        await this.restoreState("grid", state, this.showGridHighlighter);
+      }
     } catch (e) {
       this._handleRejection(e);
     }
   }
 
   /**
    * Helper function called by restoreFlexboxState, restoreGridState.
    * Restores the saved highlighter state for the given highlighter
@@ -541,28 +561,27 @@ class HighlightersOverlay {
       await showFunction(nodeFront, options);
       this.emit(`${name}-state-restored`, { restored: true });
     }
 
     this.emit(`${name}-state-restored`, { restored: false });
   }
 
   /**
-  * Get an instance of an in-context editor for the given type.
-  *
-  * In-context editors behave like highlighters but with added editing capabilities which
-  * need to write value changes back to something, like to properties in the Rule view.
-  * They typically exist in the context of the page, like the ShapesInContextEditor.
-  *
-  * @param  {String} type
-  *         Type of in-context editor. Currently supported: "shapesEditor"
-  *
-  * @return {Object|null}
-  *         Reference to instance for given type of in-context editor or null.
-  */
+   * Get an instance of an in-context editor for the given type.
+   *
+   * In-context editors behave like highlighters but with added editing capabilities which
+   * need to write value changes back to something, like to properties in the Rule view.
+   * They typically exist in the context of the page, like the ShapesInContextEditor.
+   *
+   * @param  {String} type
+   *         Type of in-context editor. Currently supported: "shapesEditor"
+   * @return {Object|null}
+   *         Reference to instance for given type of in-context editor or null.
+   */
   async getInContextEditor(type) {
     if (this.editors[type]) {
       return this.editors[type];
     }
 
     let editor;
 
     switch (type) {
@@ -608,16 +627,48 @@ class HighlightersOverlay {
       // Ignore any error
     }
 
     if (!highlighter) {
       return null;
     }
 
     this.highlighters[type] = highlighter;
+
+    return highlighter;
+  }
+
+  /**
+   * Get a grid highlighter front for a given node. It will initialize a new grid
+   * highlighter for every unique node.
+   *
+   * @param  {NodeFront} node
+   *         The NodeFront of the grid container element to highlight.
+   * @return {Promise} that resolves to the grid highlighter front.
+   */
+  async _getGridHighlighter(node) {
+    if (this.gridHighlighters.has(node)) {
+      return this.gridHighlighters.get(node);
+    }
+
+    const utils = this.highlighterUtils;
+    let highlighter;
+
+    try {
+      highlighter = await utils.getHighlighterByType("CssGridHighlighter");
+    } catch (e) {
+      // Ignore any error
+    }
+
+    if (!highlighter) {
+      return null;
+    }
+
+    this.gridHighlighters.set(node, highlighter);
+
     return highlighter;
   }
 
   _handleRejection(error) {
     if (!this.destroyed) {
       console.error(error);
     }
   }
@@ -890,52 +941,66 @@ class HighlightersOverlay {
   async onMarkupMutation(mutations) {
     const hasInterestingMutation = mutations.some(mut => mut.type === "childList");
     if (!hasInterestingMutation) {
       // Bail out if the mutations did not remove nodes, or if no grid highlighter is
       // displayed.
       return;
     }
 
+    for (const node of this.gridHighlighters.keys()) {
+      this._hideHighlighterIfDeadNode(node, this.hideGridHighlighter);
+    }
+
     this._hideHighlighterIfDeadNode(this.flexboxHighlighterShown,
       this.hideFlexboxHighlighter);
-    this._hideHighlighterIfDeadNode(this.gridHighlighterShown,
-      this.hideGridHighlighter);
     this._hideHighlighterIfDeadNode(this.shapesHighlighterShown,
       this.hideShapesHighlighter);
   }
 
   /**
    * Clear saved highlighter shown properties on will-navigate.
    */
   onWillNavigate() {
+    this.destroyEditors();
+    this.destroyGridHighlighters();
+
     this.boxModelHighlighterShown = null;
     this.flexboxHighlighterShown = null;
     this.geometryEditorHighlighterShown = null;
-    this.gridHighlighterShown = null;
     this.hoveredHighlighterShown = null;
     this.selectorHighlighterShown = null;
     this.shapesHighlighterShown = null;
-    this.destroyEditors();
   }
 
   /**
-  * Destroy and clean-up all instances of in-context editors.
-  */
+   * Destroy and clean-up all instances of in-context editors.
+   */
   destroyEditors() {
     for (const type in this.editors) {
       this.editors[type].off("show");
       this.editors[type].off("hide");
       this.editors[type].destroy();
     }
 
     this.editors = {};
   }
 
   /**
+   * Destroy all instances of the grid highlighter and clear the Map of grid highlighters.
+   */
+  destroyGridHighlighters() {
+    for (const highlighter of this.gridHighlighters.values()) {
+      highlighter.finalize();
+    }
+
+    this.gridHighlighters.clear();
+  }
+
+  /**
   * Destroy and clean-up all instances of highlighters.
   */
   destroyHighlighters() {
     for (const type in this.highlighters) {
       if (this.highlighters[type]) {
         this.highlighters[type].finalize();
         this.highlighters[type] = null;
       }
@@ -944,34 +1009,33 @@ class HighlightersOverlay {
     this.highlighters = null;
   }
 
   /**
    * Destroy this overlay instance, removing it from the view and destroying
    * all initialized highlighters.
    */
   destroy() {
-    this.destroyHighlighters();
-    this.destroyEditors();
-
-    // Remove inspector events.
     this.inspector.off("markupmutation", this.onMarkupMutation);
     this.inspector.target.off("will-navigate", this.onWillNavigate);
 
+    this.destroyEditors();
+    this.destroyGridHighlighters();
+    this.destroyHighlighters();
+
     this._lastHovered = null;
 
     this.inspector = null;
     this.highlighterUtils = null;
     this.state = null;
     this.store = null;
 
     this.boxModelHighlighterShown = null;
     this.flexboxHighlighterShown = null;
     this.geometryEditorHighlighterShown = null;
-    this.gridHighlighterShown = null;
     this.hoveredHighlighterShown = null;
     this.selectorHighlighterShown = null;
     this.shapesHighlighterShown = null;
 
     this.destroyed = true;
   }
 }
 
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -69,16 +69,18 @@ pref("devtools.inspector.fonteditor.enab
 pref("devtools.inspector.fonthighlighter.enabled", false);
 
 // Grid highlighter preferences
 pref("devtools.gridinspector.gridOutlineMaxColumns", 50);
 pref("devtools.gridinspector.gridOutlineMaxRows", 50);
 pref("devtools.gridinspector.showGridAreas", false);
 pref("devtools.gridinspector.showGridLineNumbers", false);
 pref("devtools.gridinspector.showInfiniteLines", false);
+// Max number of grid highlighters that can be displayed
+pref("devtools.gridinspector.maxHighlighters", 1);
 
 // Whether or not the box model panel is opened in the layout view
 pref("devtools.layout.boxmodel.opened", true);
 // Whether or not the flexbox panel is opened in the layout view
 pref("devtools.layout.flexbox.opened", true);
 // Whether or not the grid inspector panel is opened in the layout view
 pref("devtools.layout.grid.opened", true);