Bug 1321238 - Add unit tests for the Grid List and Display setting components. r=jdescottes
☠☠ backed out by 4a50cfa3de80 ☠ ☠
authorGabriel Luong <gabriel.luong@gmail.com>
Mon, 29 May 2017 14:23:56 -0400
changeset 409316 ad742747a8d096ec7dad94bafb81567f40c6e80d
parent 409315 7cbe2db1830c37aaf82c6af533e53c36d0f11753
child 409317 0b814165b471a980ad792d2495f9589f5aab6c84
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1321238
milestone55.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 1321238 - Add unit tests for the Grid List and Display setting components. r=jdescottes
devtools/client/inspector/boxmodel/box-model.js
devtools/client/inspector/grids/components/GridDisplaySettings.js
devtools/client/inspector/grids/components/GridList.js
devtools/client/inspector/grids/grid-inspector.js
devtools/client/inspector/grids/moz.build
devtools/client/inspector/grids/reducers/grids.js
devtools/client/inspector/grids/test/.eslintrc.js
devtools/client/inspector/grids/test/browser.ini
devtools/client/inspector/grids/test/browser_grids_display-setting-extend-grid-lines.js
devtools/client/inspector/grids/test/browser_grids_display-setting-show-grid-line-numbers.js
devtools/client/inspector/grids/test/browser_grids_grid-list-color-picker-on-ESC.js
devtools/client/inspector/grids/test/browser_grids_grid-list-color-picker-on-RETURN.js
devtools/client/inspector/grids/test/browser_grids_grid-list-element-rep.js
devtools/client/inspector/grids/test/browser_grids_grid-list-no-grids.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/head.js
devtools/client/inspector/rules/test/head.js
devtools/client/inspector/shared/highlighters-overlay.js
devtools/client/inspector/test/head.js
--- a/devtools/client/inspector/boxmodel/box-model.js
+++ b/devtools/client/inspector/boxmodel/box-model.js
@@ -122,41 +122,45 @@ BoxModel.prototype = {
    */
   updateBoxModel(reason) {
     this._updateReasons = this._updateReasons || [];
     if (reason) {
       this._updateReasons.push(reason);
     }
 
     let lastRequest = Task.spawn((function* () {
-      if (!(this.isPanelVisible() &&
-          this.inspector.selection.isConnected() &&
-          this.inspector.selection.isElementNode())) {
+      if (!this.inspector ||
+          !this.isPanelVisible() ||
+          !this.inspector.selection.isConnected() ||
+          !this.inspector.selection.isElementNode()) {
         return null;
       }
 
       let node = this.inspector.selection.nodeFront;
+
       let layout = yield this.inspector.pageStyle.getLayout(node, {
         autoMargins: true,
       });
+
       let styleEntries = yield this.inspector.pageStyle.getApplied(node, {
         // We don't need styles applied to pseudo elements of the current node.
         skipPseudo: true
       });
       this.elementRules = styleEntries.map(e => e.rule);
 
       // Update the layout properties with whether or not the element's position is
       // editable with the geometry editor.
       let isPositionEditable = yield this.inspector.pageStyle.isPositionEditable(node);
+
       layout = Object.assign({}, layout, {
         isPositionEditable,
       });
 
-      const actorCanGetOffSetParent
-        = yield this.inspector.target.actorHasMethod("domwalker", "getOffsetParent");
+      const actorCanGetOffSetParent =
+        yield this.inspector.target.actorHasMethod("domwalker", "getOffsetParent");
 
       if (actorCanGetOffSetParent) {
         // Update the redux store with the latest offset parent DOM node
         let offsetParent = yield this.inspector.walker.getOffsetParent(node);
         this.store.dispatch(updateOffsetParent(offsetParent));
       }
 
       // Update the redux store with the latest layout properties and update the box
--- a/devtools/client/inspector/grids/components/GridDisplaySettings.js
+++ b/devtools/client/inspector/grids/components/GridDisplaySettings.js
@@ -58,32 +58,34 @@ module.exports = createClass({
         dom.li(
           {
             className: "grid-settings-item",
           },
           dom.label(
             {},
             dom.input(
               {
+                id: "grid-setting-extend-grid-lines",
                 type: "checkbox",
                 checked: highlighterSettings.showInfiniteLines,
                 onChange: this.onShowInfiniteLinesCheckboxClick,
               }
             ),
             getStr("layout.extendGridLinesInfinitely")
           )
         ),
         dom.li(
           {
             className: "grid-settings-item",
           },
           dom.label(
             {},
             dom.input(
               {
+                id: "grid-setting-show-grid-line-numbers",
                 type: "checkbox",
                 checked: highlighterSettings.showGridLineNumbers,
                 onChange: this.onShowGridLineNumbersCheckboxClick,
               }
             ),
             getStr("layout.displayNumbersOnLines")
           )
         )
--- a/devtools/client/inspector/grids/components/GridList.js
+++ b/devtools/client/inspector/grids/components/GridList.js
@@ -43,17 +43,19 @@ module.exports = createClass({
       {
         className: "grid-container",
       },
       dom.span(
         {},
         getStr("layout.overlayGrid")
       ),
       dom.ul(
-        {},
+        {
+          id: "grid-list",
+        },
         grids.map(grid => GridItem({
           key: grid.id,
           getSwatchColorPickerTooltip,
           grid,
           setSelectedNode,
           onHideBoxModelHighlighter,
           onSetGridOverlayColor,
           onShowBoxModelHighlighterForNode,
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -248,23 +248,37 @@ GridInspector.prototype = {
   updateGridPanel: Task.async(function* (gridFronts) {
     // Stop refreshing if the inspector or store is already destroyed.
     if (!this.inspector || !this.store) {
       return;
     }
 
     // Get all the GridFront from the server if no gridFronts were provided.
     if (!gridFronts) {
-      gridFronts = yield this.layoutInspector.getAllGrids(this.walker.rootNode);
+      try {
+        gridFronts = yield this.layoutInspector.getAllGrids(this.walker.rootNode);
+      } catch(e) {
+        // This call might fail if called asynchrously after the toolbox is finished
+        // closing.
+        return;
+      }
     }
 
     let grids = [];
     for (let i = 0; i < gridFronts.length; i++) {
       let grid = gridFronts[i];
-      let nodeFront = yield this.walker.getNodeFromActor(grid.actorID, ["containerEl"]);
+
+      let nodeFront;
+      try {
+        nodeFront = yield this.walker.getNodeFromActor(grid.actorID, ["containerEl"]);
+      } catch (e) {
+        // This call might fail if called asynchrously after the toolbox is finished
+        // closing.
+        return;
+      }
 
       let fallbackColor = GRID_COLORS[i % GRID_COLORS.length];
       let color = this.getInitialGridColor(nodeFront, fallbackColor);
 
       grids.push({
         id: i,
         color,
         gridFragments: grid.gridFragments,
@@ -295,17 +309,17 @@ GridInspector.prototype = {
    * @param  {Event} event
    *         Event that was triggered.
    * @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.
    */
-  onHighlighterChange(event, nodeFront, options) {
+  onHighlighterChange(event, nodeFront, options = {}) {
     let highlighted = event === "grid-highlighter-shown";
     let { color } = options;
 
     // Only tell the store that the highlighter changed if it did change.
     // If we're still highlighting the same node, with the same color, no need to force
     // a refresh.
     if (this.lastHighlighterState !== highlighted ||
         this.lastHighlighterNode !== nodeFront) {
--- a/devtools/client/inspector/grids/moz.build
+++ b/devtools/client/inspector/grids/moz.build
@@ -10,8 +10,10 @@ DIRS += [
     'reducers',
     'utils',
 ]
 
 DevToolsModules(
     'grid-inspector.js',
     'types.js',
 )
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/inspector/grids/reducers/grids.js
+++ b/devtools/client/inspector/grids/reducers/grids.js
@@ -12,17 +12,17 @@ const {
 
 const INITIAL_GRIDS = [];
 
 let reducers = {
 
   [UPDATE_GRID_COLOR](grids, { nodeFront, color }) {
     let newGrids = grids.map(g => {
       if (g.nodeFront == nodeFront) {
-        g.color = color;
+        g = Object.assign({}, g, { color });
       }
 
       return g;
     });
 
     return newGrids;
   },
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+  // Extend from the shared list of defined globals for mochitests.
+  "extends": "../../../../.eslintrc.mochitests.js"
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser.ini
@@ -0,0 +1,23 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+  head.js
+  !/devtools/client/commandline/test/helpers.js
+  !/devtools/client/framework/test/shared-head.js
+  !/devtools/client/inspector/test/head.js
+  !/devtools/client/inspector/test/shared-head.js
+  !/devtools/client/shared/test/test-actor.js
+  !/devtools/client/shared/test/test-actor-registry.js
+  !/devtools/client/framework/test/shared-redux-head.js
+
+[browser_grids_display-setting-extend-grid-lines.js]
+[browser_grids_display-setting-show-grid-line-numbers.js]
+[browser_grids_grid-list-color-picker-on-ESC.js]
+[browser_grids_grid-list-color-picker-on-RETURN.js]
+[browser_grids_grid-list-element-rep.js]
+[browser_grids_grid-list-no-grids.js]
+[browser_grids_grid-list-on-mutation-element-added.js]
+[browser_grids_grid-list-on-mutation-element-removed.js]
+[browser_grids_grid-list-toggle-multiple-grids.js]
+[browser_grids_grid-list-toggle-single-grid.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_display-setting-extend-grid-lines.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the 'Extend grid lines infinitely' grid highlighter setting will update
+// the redux store and pref setting.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #grid {
+      display: grid;
+    }
+  </style>
+  <div id="grid">
+    <div id="cell1">cell1</div>
+    <div id="cell2">cell2</div>
+  </div>
+`;
+
+const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines";
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, gridInspector } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+  let { store } = inspector;
+
+  yield selectNode("#grid", inspector);
+  let checkbox = doc.getElementById("grid-setting-extend-grid-lines");
+
+  ok(!Services.prefs.getBoolPref(SHOW_INFINITE_LINES_PREF),
+    "'Extend grid lines infinitely' is pref off by default.");
+
+  info("Toggling ON the 'Extend grid lines infinitely' setting.");
+  let onCheckboxChange = waitUntilState(store, state =>
+    state.highlighterSettings.showInfiniteLines);
+  checkbox.click();
+  yield onCheckboxChange;
+
+  info("Toggling OFF the 'Extend grid lines infinitely' setting.");
+  onCheckboxChange = waitUntilState(store, state =>
+    !state.highlighterSettings.showInfiniteLines);
+  checkbox.click();
+  yield onCheckboxChange;
+
+  ok(!Services.prefs.getBoolPref(SHOW_INFINITE_LINES_PREF),
+    "'Extend grid lines infinitely' is pref off.");
+
+  Services.prefs.clearUserPref(SHOW_INFINITE_LINES_PREF);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_display-setting-show-grid-line-numbers.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the 'Display numbers on lines' grid highlighter setting will update
+// the redux store and pref setting.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #grid {
+      display: grid;
+    }
+  </style>
+  <div id="grid">
+    <div id="cell1">cell1</div>
+    <div id="cell2">cell2</div>
+  </div>
+`;
+
+const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers";
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, gridInspector } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+  let { store } = inspector;
+
+  yield selectNode("#grid", inspector);
+  let checkbox = doc.getElementById("grid-setting-show-grid-line-numbers");
+
+  info("Checking the initial state of the CSS grid highlighter setting.");
+  ok(!Services.prefs.getBoolPref(SHOW_GRID_LINE_NUMBERS),
+    "'Display numbers on lines' is pref off by default.");
+
+  info("Toggling ON the 'Display numbers on lines' setting.");
+  let onCheckboxChange = waitUntilState(store, state =>
+    state.highlighterSettings.showGridLineNumbers);
+  checkbox.click();
+  yield onCheckboxChange;
+
+  ok(Services.prefs.getBoolPref(SHOW_GRID_LINE_NUMBERS),
+    "'Display numbers on lines' is pref on.");
+
+  info("Toggling OFF the 'Display numbers on lines' setting.");
+  onCheckboxChange = waitUntilState(store, state =>
+    !state.highlighterSettings.showGridLineNumbers);
+  checkbox.click();
+  yield onCheckboxChange;
+
+  ok(!Services.prefs.getBoolPref(SHOW_GRID_LINE_NUMBERS),
+    "'Display numbers on lines' is pref off.");
+
+  Services.prefs.clearUserPref(SHOW_GRID_LINE_NUMBERS);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-color-picker-on-ESC.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the grid item's color change in the colorpicker is reverted when ESCAPE is
+// pressed.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #grid {
+      display: grid;
+    }
+  </style>
+  <div id="grid">
+    <div id="cell1">cell1</div>
+    <div id="cell2">cell2</div>
+  </div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, gridInspector, toolbox } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+  let { store } = inspector;
+  let cPicker = gridInspector.getSwatchColorPickerTooltip();
+  let spectrum = cPicker.spectrum;
+  let swatch = doc.querySelector(".grid-color-swatch");
+
+  info("Checking the initial state of the Grid Inspector.");
+  is(swatch.style.backgroundColor, "rgb(75, 0, 130)",
+    "The color swatch's background is correct.");
+  is(store.getState().grids[0].color, "#4B0082", "The grid color state is correct.");
+
+  info("Scrolling into view of the #grid color swatch.");
+  swatch.scrollIntoView();
+
+  info("Opening the color picker by clicking on the #grid color swatch.");
+  let onColorPickerReady = cPicker.once("ready");
+  swatch.click();
+  yield onColorPickerReady;
+
+  yield simulateColorPickerChange(cPicker, [0, 255, 0, .5]);
+
+  is(swatch.style.backgroundColor, "rgba(0, 255, 0, 0.5)",
+    "The color swatch's background was updated.");
+
+  info("Pressing ESCAPE to close the tooltip.");
+  let onGridColorUpdate = waitUntilState(store, state =>
+    state.grids[0].color === "#4B0082");
+  let onColorPickerHidden = cPicker.tooltip.once("hidden");
+  focusAndSendKey(spectrum.element.ownerDocument.defaultView, "ESCAPE");
+  yield onColorPickerHidden;
+  yield onGridColorUpdate;
+
+  is(swatch.style.backgroundColor, "rgb(75, 0, 130)",
+    "The color swatch's background was reverted after ESCAPE.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-color-picker-on-RETURN.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the grid item's color change in the colorpicker is committed when RETURN is
+// pressed.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #grid {
+      display: grid;
+    }
+  </style>
+  <div id="grid">
+    <div id="cell1">cell1</div>
+    <div id="cell2">cell2</div>
+  </div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, gridInspector, toolbox } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+  let { store } = inspector;
+  let cPicker = gridInspector.getSwatchColorPickerTooltip();
+  let spectrum = cPicker.spectrum;
+  let swatch = doc.querySelector(".grid-color-swatch");
+
+  info("Checking the initial state of the Grid Inspector.");
+  is(swatch.style.backgroundColor, "rgb(75, 0, 130)",
+    "The color swatch's background is correct.");
+  is(store.getState().grids[0].color, "#4B0082", "The grid color state is correct.");
+
+  info("Scrolling into view of the #grid color swatch.");
+  swatch.scrollIntoView();
+
+  info("Opening the color picker by clicking on the #grid color swatch.");
+  let onColorPickerReady = cPicker.once("ready");
+  swatch.click();
+  yield onColorPickerReady;
+
+  yield simulateColorPickerChange(cPicker, [0, 255, 0, .5]);
+
+  is(swatch.style.backgroundColor, "rgba(0, 255, 0, 0.5)",
+    "The color swatch's background was updated.");
+
+  info("Pressing RETURN to commit the color change.");
+  let onGridColorUpdate = waitUntilState(store, state =>
+    state.grids[0].color === "#00FF0080");
+  let onColorPickerHidden = cPicker.tooltip.once("hidden");
+  focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
+  yield onColorPickerHidden;
+  yield onGridColorUpdate;
+
+  is(swatch.style.backgroundColor, "rgba(0, 255, 0, 0.5)",
+    "The color swatch's background was kept after RETURN.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-element-rep.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the grid item's element rep will display the box model higlighter on hover
+// and select the node on click.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #grid {
+      display: grid;
+    }
+  </style>
+  <div id="grid">
+    <div id="cell1">cell1</div>
+    <div id="cell2">cell2</div>
+  </div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, gridInspector, toolbox } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+  let { store } = inspector;
+
+  let gridList = doc.querySelector("#grid-list");
+  let 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");
+  let onHighlight = toolbox.once("node-highlight");
+  EventUtils.synthesizeMouse(elementRep, 10, 5, {type: "mouseover"}, doc.defaultView);
+  let nodeFront = yield 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.");
+
+  let onSelection = inspector.selection.once("new-node-front");
+  EventUtils.sendMouseEvent({type: "click"}, elementRep, doc.defaultView);
+  yield onSelection;
+
+  is(inspector.selection.nodeFront, store.getState().grids[0].nodeFront,
+    "The selected node is the one stored on the grid item's state.")
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-no-grids.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that no grid list items and a "no grids available" message is displayed when
+// there are no grid containers on the page.
+
+const TEST_URI = `
+  <style type='text/css'>
+  </style>
+  <div id="grid">
+    <div id="cell1">cell1</div>
+    <div id="cell2">cell2</div>
+  </div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, gridInspector } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+  let { highlighters } = inspector;
+
+  yield selectNode("#grid", inspector);
+  let noGridList = doc.querySelector(".layout-no-grids");
+  let gridList = doc.querySelector("#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],
+    "No CSS grid highlighter exists in the highlighters overlay.");
+  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-on-mutation-element-added.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the grid list updates when a new grid container is added to the page.
+
+const TEST_URI = `
+  <style type='text/css'>
+    .grid {
+      display: grid;
+    }
+  </style>
+  <div id="grid1" class="grid">
+    <div class="cell1">cell1</div>
+    <div class="cell2">cell2</div>
+  </div>
+  <div id="grid2">
+    <div class="cell1">cell1</div>
+    <div class="cell2">cell2</div>
+  </div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, gridInspector, testActor } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+  let { highlighters, store } = inspector;
+
+  yield selectNode("#grid", inspector);
+  let gridList = doc.querySelector("#grid-list");
+  let 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],
+    "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();
+  yield 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.");
+
+  info("Adding the #grid2 container in the content page.");
+  let onGridListUpdate = waitUntilState(store, state =>
+    state.grids.length == 2 &&
+    state.grids[0].highlighted &&
+    !state.grids[1].highlighted);
+  testActor.eval(`
+    content.document.getElementById("grid2").classList.add("grid");
+  `);
+  yield 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.");
+
+  let 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();
+  yield onHighlighterShown;
+  yield onCheckboxChange;
+
+  info("Checking the CSS grid highlighter is still shown.");
+  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+
+  info("Toggling OFF the CSS grid highlighter from the layout panel.");
+  let onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
+  onCheckboxChange = waitUntilState(store, state =>
+    state.grids.length == 2 &&
+    !state.grids[0].highlighted &&
+    !state.grids[1].highlighted);
+  checkbox2.click();
+  yield onHighlighterHidden;
+  yield onCheckboxChange;
+
+  info("Checking the CSS grid highlighter is not shown.");
+  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-on-mutation-element-removed.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the grid item is removed from the grid list when the grid container is
+// removed from the page.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #grid {
+      display: grid;
+    }
+  </style>
+  <div id="grid">
+    <div id="cell1">cell1</div>
+    <div id="cell2">cell2</div>
+  </div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, gridInspector, testActor } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+  let { highlighters, store } = inspector;
+
+  yield selectNode("#grid", inspector);
+  let gridList = doc.querySelector("#grid-list");
+  let 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.");
+
+  info("Toggling ON the CSS grid highlighter from the layout panel.");
+  let onHighlighterShown = highlighters.once("grid-highlighter-shown");
+  let onCheckboxChange = waitUntilState(store, state =>
+    state.grids.length == 1 && state.grids[0].highlighted);
+  checkbox.click();
+  yield onHighlighterShown;
+  yield 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.");
+
+  info("Removing the #grid container in the content page.");
+  let onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
+  onCheckboxChange = waitUntilState(store, state => state.grids.length == 0);
+  testActor.eval(`
+    content.document.getElementById("grid").remove();
+  `);
+  yield onHighlighterHidden;
+  yield onCheckboxChange;
+
+  info("Checking the CSS grid highlighter is not shown.");
+  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+  let noGridList = doc.querySelector(".layout-no-grids");
+  ok(noGridList, "The message no grid containers is displayed.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-toggle-multiple-grids.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test toggling the grid highlighter in the grid inspector panel with multiple grids in
+// the page.
+
+const TEST_URI = `
+  <style type='text/css'>
+    .grid {
+      display: grid;
+    }
+  </style>
+  <div id="grid1" class="grid">
+    <div class="cell1">cell1</div>
+    <div class="cell2">cell2</div>
+  </div>
+  <div id="grid2" class="grid">
+    <div class="cell1">cell1</div>
+    <div class="cell2">cell2</div>
+  </div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, gridInspector, testActor } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+  let { highlighters, store } = inspector;
+
+  yield selectNode("#grid1", inspector);
+  let gridList = doc.querySelector("#grid-list");
+  let checkbox1 = gridList.children[0].querySelector("input");
+  let 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.");
+
+  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();
+  yield onHighlighterShown;
+  yield 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.");
+
+  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();
+  yield onHighlighterShown;
+  yield onCheckboxChange;
+
+  info("Checking the CSS grid highlighter is still shown.");
+  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+
+  info("Toggling OFF the CSS grid highlighter from the layout panel.");
+  let onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
+  onCheckboxChange = waitUntilState(store, state =>
+    state.grids.length == 2 &&
+    !state.grids[0].highlighted &&
+    !state.grids[1].highlighted);
+  checkbox2.click();
+  yield onHighlighterHidden;
+  yield onCheckboxChange;
+
+  info("Checking the CSS grid highlighter is not shown.");
+  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-toggle-single-grid.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests toggling ON/OFF the grid highlighter from the grid ispector panel.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #grid {
+      display: grid;
+    }
+  </style>
+  <div id="grid">
+    <div id="cell1">cell1</div>
+    <div id="cell2">cell2</div>
+  </div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { gridInspector, inspector } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+  let { highlighters, store } = inspector;
+
+  yield selectNode("#grid", inspector);
+  let gridList = doc.querySelector("#grid-list");
+  let 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.");
+
+  info("Toggling ON the CSS grid highlighter from the layout panel.");
+  let onHighlighterShown = highlighters.once("grid-highlighter-shown");
+  let onCheckboxChange = waitUntilState(store, state =>
+    state.grids.length == 1 &&
+    state.grids[0].highlighted);
+  checkbox.click();
+  yield onHighlighterShown;
+  yield 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.");
+
+  info("Toggling OFF the CSS grid highlighter from the layout panel.");
+  let onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
+  onCheckboxChange = waitUntilState(store, state =>
+    state.grids.length == 1 &&
+    !state.grids[0].highlighted);
+  checkbox.click();
+  yield onHighlighterHidden;
+  yield onCheckboxChange;
+
+  info("Checking the CSS grid highlighter is not shown.");
+  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/head.js
@@ -0,0 +1,74 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from ../../../framework/test/shared-head.js */
+/* import-globals-from ../../test/head.js */
+"use strict";
+
+// Import the inspector's head.js first (which itself imports shared-head.js).
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
+  this);
+
+// Load the shared Redux helpers into this compartment.
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/framework/test/shared-redux-head.js",
+  this);
+
+Services.prefs.setBoolPref("devtools.layoutview.enabled", true);
+Services.prefs.setIntPref("devtools.toolbox.footer.height", 350);
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("devtools.layoutview.enabled");
+  Services.prefs.clearUserPref("devtools.toolbox.footer.height");
+});
+
+const HIGHLIGHTER_TYPE = "CssGridHighlighter";
+
+/**
+ * Open the toolbox, with the inspector tool visible, and the layout view
+ * sidebar tab selected to display the box model view with properties.
+ *
+ * @return {Promise} a promise that resolves when the inspector is ready and the box model
+ *         view is visible and ready.
+ */
+function openLayoutView() {
+  return openInspectorSidebarTab("layoutview").then(data => {
+    // The actual highligher show/hide methods are mocked in box model tests.
+    // The highlighter is tested in devtools/inspector/test.
+    function mockHighlighter({highlighter}) {
+      highlighter.showBoxModel = function () {
+        return promise.resolve();
+      };
+      highlighter.hideBoxModel = function () {
+        return promise.resolve();
+      };
+    }
+    mockHighlighter(data.toolbox);
+
+    return {
+      toolbox: data.toolbox,
+      inspector: data.inspector,
+      gridInspector: data.inspector.gridInspector,
+      testActor: data.testActor
+    };
+  });
+}
+
+/**
+ * Simulate a color change in a given color picker tooltip.
+ *
+ * @param  {Spectrum|ColorWidget} colorPicker
+ *         The color picker widget.
+ * @param  {Array} newRgba
+ *         Array of the new rgba values to be set in the color widget.
+ */
+var simulateColorPickerChange = Task.async(function* (colorPicker, newRgba) {
+  info("Getting the spectrum colorpicker object");
+  let spectrum = yield colorPicker.spectrum;
+  info("Setting the new color");
+  spectrum.rgb = newRgba;
+  info("Applying the change");
+  spectrum.updateUI();
+  spectrum.onChange();
+});
--- a/devtools/client/inspector/rules/test/head.js
+++ b/devtools/client/inspector/rules/test/head.js
@@ -493,26 +493,16 @@ function waitForStyleModification(inspec
  */
 function* clickSelectorIcon(icon, view) {
   let onToggled = view.once("ruleview-selectorhighlighter-toggled");
   EventUtils.synthesizeMouseAtCenter(icon, {}, view.styleWindow);
   yield onToggled;
 }
 
 /**
- * Make sure window is properly focused before sending a key event.
- * @param {Window} win
- * @param {Event} key
- */
-function focusAndSendKey(win, key) {
-  win.document.documentElement.focus();
-  EventUtils.sendKey(key, win);
-}
-
-/**
  * Toggle one of the checkboxes inside the class-panel. Resolved after the DOM mutation
  * has been recorded.
  * @param {CssRuleView} view The rule-view instance.
  * @param {String} name The class name to find the checkbox.
  */
 function* toggleClassPanelCheckBox(view, name) {
   info(`Clicking on checkbox for class ${name}`);
   const checkBox = [...view.classPanel.querySelectorAll("[type=checkbox]")].find(box => {
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -262,28 +262,36 @@ HighlightersOverlay.prototype = {
 
   /**
    * Get a highlighter front given a type. It will only be initialized once.
    *
    * @param  {String} type
    *         The highlighter type. One of this.highlighters.
    * @return {Promise} that resolves to the highlighter
    */
-  _getHighlighter: function (type) {
+  _getHighlighter: Task.async(function* (type) {
     let utils = this.highlighterUtils;
 
     if (this.highlighters[type]) {
-      return promise.resolve(this.highlighters[type]);
+      return this.highlighters[type];
     }
 
-    return utils.getHighlighterByType(type).then(highlighter => {
-      this.highlighters[type] = highlighter;
-      return highlighter;
-    });
-  },
+    let highlighter;
+
+    try {
+      highlighter = yield utils.getHighlighterByType(type);
+    } catch (e) {}
+
+    if (!highlighter) {
+      return;
+    }
+
+    this.highlighters[type] = highlighter;
+    return highlighter;
+  }),
 
   _handleRejection: function (error) {
     if (!this.destroyed) {
       console.error(error);
     }
   },
 
   /**
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -639,16 +639,29 @@ var waitForTab = Task.async(function* ()
  */
 function synthesizeKeys(input, win) {
   for (let key of input.split("")) {
     EventUtils.synthesizeKey(key, {}, win);
   }
 }
 
 /**
+ * Make sure window is properly focused before sending a key event.
+ *
+ * @param {Window} win
+ *        The window containing the panel
+ * @param {String} key
+ *        The string value to input
+ */
+function focusAndSendKey(win, key) {
+  win.document.documentElement.focus();
+  EventUtils.sendKey(key, win);
+}
+
+/**
  * Given a Tooltip instance, fake a mouse event on the `target` DOM Element
  * and assert that the `tooltip` is correctly displayed.
  *
  * @param {Tooltip} tooltip
  *        The tooltip instance
  * @param {DOMElement} target
  *        The DOM Element on which a tooltip should appear
  *