Bug 1373134 - Scroll into view of a grid cell via the grid outline. r?pbro draft
authorMicah Tigley <tigleym@gmail.com>
Sun, 27 Aug 2017 14:21:20 -0600
changeset 654655 031b2e08e871ef9f2d0cbee60ded5d97d754547b
parent 653731 241116278286e4c38fd7461da5eae52ba409ae1b
child 654656 0126417d27524468b9beb623634739f6610fe458
child 656998 1c21cc95499ec76d9dfccc476903ec321e47c630
child 656999 a1068eec45e2b500e7fb0c7ef08e7508ad6fff92
child 658749 9d7b8f9841a7d78268a44b6d08e976e0b9cce332
push id76632
push userbmo:tigleym@gmail.com
push dateTue, 29 Aug 2017 03:23:21 +0000
reviewerspbro
bugs1373134
milestone57.0a1
Bug 1373134 - Scroll into view of a grid cell via the grid outline. r?pbro MozReview-Commit-ID: 8KQrj0tG2hU
devtools/client/inspector/grids/actions/highlighter-settings.js
devtools/client/inspector/grids/actions/index.js
devtools/client/inspector/grids/actions/moz.build
devtools/client/inspector/grids/actions/outline.js
devtools/client/inspector/grids/components/Grid.js
devtools/client/inspector/grids/components/GridDisplaySettings.js
devtools/client/inspector/grids/components/GridItem.js
devtools/client/inspector/grids/components/GridOutline.js
devtools/client/inspector/grids/grid-inspector.js
devtools/client/inspector/grids/reducers/highlighter-settings.js
devtools/client/inspector/layout/layout.js
devtools/client/locales/en-US/layout.properties
devtools/client/preferences/devtools.js
devtools/server/actors/inspector.js
devtools/shared/specs/node.js
--- a/devtools/client/inspector/grids/actions/highlighter-settings.js
+++ b/devtools/client/inspector/grids/actions/highlighter-settings.js
@@ -6,17 +6,16 @@
 
 const {
   UPDATE_SHOW_GRID_AREAS,
   UPDATE_SHOW_GRID_LINE_NUMBERS,
   UPDATE_SHOW_INFINITE_LINES,
 } = require("./index");
 
 module.exports = {
-
   /**
    * Update the grid highlighter's show grid areas preference.
    *
    * @param  {Boolean} enabled
    *         Whether or not the grid highlighter should show the grid areas.
    */
   updateShowGridAreas(enabled) {
     return {
--- a/devtools/client/inspector/grids/actions/index.js
+++ b/devtools/client/inspector/grids/actions/index.js
@@ -3,16 +3,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { createEnum } = require("devtools/client/shared/enum");
 
 createEnum([
 
+  // Update whether or not to autoscroll to highlighted grid cell.
+  "UPDATE_AUTOSCROLL_GRID_CELL",
+
   // Update the color used for the overlay of a grid.
   "UPDATE_GRID_COLOR",
 
   // Update the grid highlighted state.
   "UPDATE_GRID_HIGHLIGHTED",
 
   // Update the entire grids state with the new list of grids.
   "UPDATE_GRIDS",
--- a/devtools/client/inspector/grids/actions/moz.build
+++ b/devtools/client/inspector/grids/actions/moz.build
@@ -3,9 +3,10 @@
 # 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/.
 
 DevToolsModules(
     'grids.js',
     'highlighter-settings.js',
     'index.js',
+    'outline.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/actions/outline.js
@@ -0,0 +1,27 @@
+/* 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 {
+  UPDATE_AUTOSCROLL_GRID_CELL,
+} = require("./index");
+
+// Action creators for the grid outline inside the Layout Panel
+
+module.exports = {
+  /**
+   * Update the grid inspector's autoScrollToGridCell preference.
+   *
+   * @param  {Boolean} enabled
+   *         Whether or not the grid inspector should autoscroll to the highlighted
+   *         grid cell.
+   */
+  updateAutoScrollToGridCell(enabled) {
+    return {
+      type: UPDATE_AUTOSCROLL_GRID_CELL,
+      enabled,
+    };
+  },
+};
--- a/devtools/client/inspector/grids/components/Grid.js
+++ b/devtools/client/inspector/grids/components/Grid.js
@@ -25,16 +25,17 @@ module.exports = createClass({
     setSelectedNode: PropTypes.func.isRequired,
     onHideBoxModelHighlighter: PropTypes.func.isRequired,
     onSetGridOverlayColor: PropTypes.func.isRequired,
     onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
     onShowGridAreaHighlight: PropTypes.func.isRequired,
     onShowGridCellHighlight: PropTypes.func.isRequired,
     onShowGridLineNamesHighlight: PropTypes.func.isRequired,
     onToggleGridHighlighter: PropTypes.func.isRequired,
+    onToggleScrollToGridCell: PropTypes.func.isRequired,
     onToggleShowGridAreas: PropTypes.func.isRequired,
     onToggleShowGridLineNumbers: PropTypes.func.isRequired,
     onToggleShowInfiniteLines: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   render() {
@@ -43,16 +44,17 @@ module.exports = createClass({
       grids,
       highlighterSettings,
       setSelectedNode,
       onHideBoxModelHighlighter,
       onSetGridOverlayColor,
       onShowBoxModelHighlighterForNode,
       onShowGridAreaHighlight,
       onShowGridCellHighlight,
+      onToggleScrollToGridCell,
       onToggleShowGridAreas,
       onToggleGridHighlighter,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
     } = this.props;
 
     return grids.length ?
       dom.div(
@@ -69,16 +71,17 @@ module.exports = createClass({
             setSelectedNode,
             onHideBoxModelHighlighter,
             onSetGridOverlayColor,
             onShowBoxModelHighlighterForNode,
             onToggleGridHighlighter,
           }),
           GridDisplaySettings({
             highlighterSettings,
+            onToggleScrollToGridCell,
             onToggleShowGridAreas,
             onToggleShowGridLineNumbers,
             onToggleShowInfiniteLines,
           })
         ),
         GridOutline({
           grids,
           onShowGridAreaHighlight,
--- a/devtools/client/inspector/grids/components/GridDisplaySettings.js
+++ b/devtools/client/inspector/grids/components/GridDisplaySettings.js
@@ -1,33 +1,47 @@
 /* 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 { addons, createClass, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
+const Services = require("Services");
 
 const Types = require("../types");
 const { getStr } = require("../utils/l10n");
 
+// Prefs
+const SCROLL_INTO_VIEW_OF_GRID_PREF = "devtools.gridinspector.scrollIntoViewOfGridNode";
+
 module.exports = createClass({
 
   displayName: "GridDisplaySettings",
 
   propTypes: {
     highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
+    onToggleScrollToGridCell: PropTypes.func.isRequired,
     onToggleShowGridAreas: PropTypes.func.isRequired,
     onToggleShowGridLineNumbers: PropTypes.func.isRequired,
     onToggleShowInfiniteLines: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
+  onScrollToGridCellCheckboxClick() {
+    let {
+      highlighterSettings,
+      onToggleScrollToGridCell,
+    } = this.props;
+
+    onToggleScrollToGridCell(!highlighterSettings.scrollToGridCell);
+  },
+
   onShowGridAreasCheckboxClick() {
     let {
       highlighterSettings,
       onToggleShowGridAreas,
     } = this.props;
 
     onToggleShowGridAreas(!highlighterSettings.showGridAreasOverlay);
   },
@@ -110,14 +124,33 @@ module.exports = createClass({
                 id: "grid-setting-extend-grid-lines",
                 type: "checkbox",
                 checked: highlighterSettings.showInfiniteLines,
                 onChange: this.onShowInfiniteLinesCheckboxClick,
               }
             ),
             getStr("layout.extendLinesInfinitely")
           )
+        ),
+        Services.prefs.getBoolPref(SCROLL_INTO_VIEW_OF_GRID_PREF)
+        ? dom.li(
+          {
+            className: "grid-settings-item",
+          },
+          dom.label(
+           {},
+           dom.input(
+             {
+               id: "grid-setting-scroll-to-grid-cell",
+               type: "checkbox",
+               checked: highlighterSettings.scrollToGridCell,
+               onChange: this.onScrollToGridCellCheckboxClick,
+             }
+           ),
+           getStr("layout.autoScrollToGridCells")
+          )
         )
+        : null
       )
     );
   },
 
 });
--- a/devtools/client/inspector/grids/components/GridItem.js
+++ b/devtools/client/inspector/grids/components/GridItem.js
@@ -100,17 +100,28 @@ module.exports = createClass({
       return;
     }
 
     let {
       grid,
       onToggleGridHighlighter,
     } = this.props;
 
-    onToggleGridHighlighter(grid.nodeFront);
+    let xOffset;
+    let yOffset;
+
+    // TODO: We are drawing the first fragment since only one is currently being stored.
+    // In the future we will need to iterate over all fragments of a grid.
+    let gridFragmentIndex = 0;
+    const { gridFragments } = grid;
+    const { rows, cols } = gridFragments[gridFragmentIndex];
+    xOffset = cols.lines[0].start;
+    yOffset = rows.lines[0].start;
+
+    onToggleGridHighlighter(grid.nodeFront, xOffset, yOffset);
   },
 
   render() {
     let {
       grid,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
       setSelectedNode,
--- a/devtools/client/inspector/grids/components/GridOutline.js
+++ b/devtools/client/inspector/grids/components/GridOutline.js
@@ -138,58 +138,59 @@ module.exports = createClass({
       return VIEWPORT_MAX_HEIGHT;
     } else if (height <= VIEWPORT_MIN_HEIGHT) {
       return VIEWPORT_MIN_HEIGHT;
     }
 
     return height;
   },
 
-  onHighlightCell({ target, type }) {
+  onHighlightCell({ ctrlKey, target, type }) {
     // Debounce the highlighting of cells.
     // This way we don't end up sending many requests to the server for highlighting when
     // cells get hovered in a rapid succession We only send a request if the user settles
     // on a cell for some time.
     if (this.highlightTimeout) {
       clearTimeout(this.highlightTimeout);
     }
 
     this.highlightTimeout = setTimeout(() => {
-      this.doHighlightCell(target, type === "mouseleave");
+      this.doHighlightCell(target, type === "mouseleave", ctrlKey);
       this.highlightTimeout = null;
     }, GRID_HIGHLIGHTING_DEBOUNCE);
   },
 
-  doHighlightCell(target, hide) {
+  doHighlightCell(target, hide, keyModifierState) {
     const {
       grids,
       onShowGridAreaHighlight,
       onShowGridCellHighlight,
     } = this.props;
     const name = target.dataset.gridAreaName;
     const id = target.dataset.gridId;
     const fragmentIndex = target.dataset.gridFragmentIndex;
     const color = target.closest(".grid-cell-group").dataset.gridLineColor;
     const rowNumber = target.dataset.gridRow;
     const columnNumber = target.dataset.gridColumn;
+    const { gridXOffset, gridYOffset } = target.dataset;
 
     onShowGridAreaHighlight(grids[id].nodeFront, null, color);
     onShowGridCellHighlight(grids[id].nodeFront, color);
 
     if (hide) {
       return;
     }
 
     if (name) {
       onShowGridAreaHighlight(grids[id].nodeFront, name, color);
     }
 
     if (fragmentIndex && rowNumber && columnNumber) {
       onShowGridCellHighlight(grids[id].nodeFront, color, fragmentIndex,
-        rowNumber, columnNumber);
+        rowNumber, columnNumber, keyModifierState, gridXOffset, gridYOffset);
     }
   },
 
   /**
    * Displays a message text "Cannot show outline for this grid".
    */
   renderCannotShowOutlineText() {
     return dom.div(
@@ -229,20 +230,22 @@ module.exports = createClass({
 
     // Draw the cells contained within the grid outline border.
     for (let rowNumber = 1; rowNumber <= numberOfRows; rowNumber++) {
       height = GRID_CELL_SCALE_FACTOR * (rows.tracks[rowNumber - 1].breadth / 100);
 
       for (let columnNumber = 1; columnNumber <= numberOfColumns; columnNumber++) {
         width = GRID_CELL_SCALE_FACTOR * (cols.tracks[columnNumber - 1].breadth / 100);
 
+        const { start: xOffset } = cols.lines[columnNumber - 1];
+        const { start: yOffset } = rows.lines[rowNumber - 1];
         const gridAreaName = this.getGridAreaName(columnNumber, rowNumber, areas);
         const gridCell = this.renderGridCell(id, gridFragmentIndex, x, y,
                                              rowNumber, columnNumber, color, gridAreaName,
-                                             width, height);
+                                             width, height, xOffset, yOffset);
 
         rectangles.push(gridCell);
         x += width;
       }
 
       x = 1;
       y += height;
     }
@@ -271,25 +274,31 @@ module.exports = createClass({
    * @param  {Number} columnNumber
    *         The column number of the grid cell.
    * @param  {String|null} gridAreaName
    *         The grid area name or null if the grid cell is not part of a grid area.
    * @param  {Number} width
    *         The width of grid cell.
    * @param  {Number} height
    *         The height of the grid cell.
+   * @param  {Number} xOffset
+   *         The x-offset of the grid cell highlight.
+   * @param  {Number} yOffset
+   *         The y-offset of the grid cell highlight.
    */
   renderGridCell(id, gridFragmentIndex, x, y, rowNumber, columnNumber, color,
-    gridAreaName, width, height) {
+    gridAreaName, width, height, xOffset, yOffset) {
     return dom.rect(
       {
         "key": `${id}-${rowNumber}-${columnNumber}`,
         "className": "grid-outline-cell",
         "data-grid-area-name": gridAreaName,
         "data-grid-fragment-index": gridFragmentIndex,
+        "data-grid-x-offset": xOffset,
+        "data-grid-y-offset": yOffset,
         "data-grid-id": id,
         "data-grid-row": rowNumber,
         "data-grid-column": columnNumber,
         x,
         y,
         width,
         height,
         fill: "none",
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -16,24 +16,35 @@ const {
   updateGridHighlighted,
   updateGrids,
 } = require("./actions/grids");
 const {
   updateShowGridAreas,
   updateShowGridLineNumbers,
   updateShowInfiniteLines,
 } = require("./actions/highlighter-settings");
+const {
+  updateAutoScrollToGridCell,
+} = require("./actions/outline");
 
 const CSS_GRID_COUNT_HISTOGRAM_ID = "DEVTOOLS_NUMBER_OF_CSS_GRIDS_IN_A_PAGE";
 
+const AUTOSCROLL_TO_GRID_CELLS = "devtools.gridinspector.autoScrollToGridCells";
 const SHOW_GRID_AREAS = "devtools.gridinspector.showGridAreas";
 const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers";
 const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines";
 // @remove after release 56 (See Bug 1355747)
 const PROMOTE_COUNT_PREF = "devtools.promote.layoutview";
+const SCROLL_INTO_VIEW_OF_GRID_PREF = "devtools.gridinspector.scrollIntoViewOfGridNode";
+
+// The y-offset when scrolling to a grid cell/container. We want to offset the
+// y-coordinate 100 above the the actual y-coordinate of the container/cell's origin
+// point so that the innerHeight of the window is not preventing us from seeing the entire
+// grid and its infobars when we scrollIntoView.
+const GRID_SCROLL_YOFFSET = 100;
 
 // Default grid colors.
 const GRID_COLORS = [
   "#9400FF",
   "#DF00A9",
   "#0A84FF",
   "#12BC00",
   "#EA8000",
@@ -61,16 +72,17 @@ function GridInspector(inspector, window
   this.onHighlighterChange = this.onHighlighterChange.bind(this);
   this.onReflow = throttle(this.onReflow, 500, this);
   this.onSetGridOverlayColor = this.onSetGridOverlayColor.bind(this);
   this.onShowGridAreaHighlight = this.onShowGridAreaHighlight.bind(this);
   this.onShowGridCellHighlight = this.onShowGridCellHighlight.bind(this);
   this.onShowGridLineNamesHighlight = this.onShowGridLineNamesHighlight.bind(this);
   this.onSidebarSelect = this.onSidebarSelect.bind(this);
   this.onToggleGridHighlighter = this.onToggleGridHighlighter.bind(this);
+  this.onToggleScrollToGridCell = this.onToggleScrollToGridCell.bind(this);
   this.onToggleShowGridAreas = this.onToggleShowGridAreas.bind(this);
   this.onToggleShowGridLineNumbers = this.onToggleShowGridLineNumbers.bind(this);
   this.onToggleShowInfiniteLines = this.onToggleShowInfiniteLines.bind(this);
 
   this.init();
 }
 
 GridInspector.prototype = {
@@ -127,19 +139,21 @@ GridInspector.prototype = {
     this.swatchColorPickerTooltip = null;
     this.walker = null;
   },
 
   getComponentProps() {
     return {
       getSwatchColorPickerTooltip: this.getSwatchColorPickerTooltip,
       onSetGridOverlayColor: this.onSetGridOverlayColor,
+      onScrollToGridCellHighlight: this.onScrollToGridCellHighlight,
       onShowGridAreaHighlight: this.onShowGridAreaHighlight,
       onShowGridCellHighlight: this.onShowGridCellHighlight,
       onShowGridLineNamesHighlight: this.onShowGridLineNamesHighlight,
+      onToggleScrollToGridCell: this.onToggleScrollToGridCell,
       onToggleGridHighlighter: this.onToggleGridHighlighter,
       onToggleShowGridAreas: this.onToggleShowGridAreas,
       onToggleShowGridLineNumbers: this.onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines: this.onToggleShowInfiniteLines,
     };
   },
 
   /**
@@ -226,20 +240,22 @@ GridInspector.prototype = {
    * Load the grid highligher display settings into the store from the stored preferences.
    */
   loadHighlighterSettings() {
     let { dispatch } = this.store;
 
     let showGridAreas = Services.prefs.getBoolPref(SHOW_GRID_AREAS);
     let showGridLineNumbers = Services.prefs.getBoolPref(SHOW_GRID_LINE_NUMBERS);
     let showInfinteLines = Services.prefs.getBoolPref(SHOW_INFINITE_LINES_PREF);
+    let autoScrollToGridCell = Services.prefs.getBoolPref(AUTOSCROLL_TO_GRID_CELLS);
 
     dispatch(updateShowGridAreas(showGridAreas));
     dispatch(updateShowGridLineNumbers(showGridLineNumbers));
     dispatch(updateShowInfiniteLines(showInfinteLines));
+    dispatch(updateAutoScrollToGridCell(autoScrollToGridCell));
   },
 
   showGridHighlighter(node, settings) {
     this.lastHighlighterColor = settings.color;
     this.lastHighlighterNode = node;
     this.lastHighlighterState = true;
 
     this.highlighters.showGridHighlighter(node, settings);
@@ -504,25 +520,44 @@ GridInspector.prototype = {
    *         The index of the grid fragment for which the grid highlighter
    *         is highlighted for.
    * @param  {Number|null} rowNumber
    *         The row number of the grid cell for which the grid highlighter
    *         is highlighted for.
    * @param  {Number|null} columnNumber
    *         The column number of the grid cell for which the grid highlighter
    *         is highlighted for.
+   * @param  {Boolean|null} keyModifierState
+   *         State of the key modifier to determine whether or not to scroll to
+   *         the grid cell for which the grid highlighter is highlighted for.
+   * @param  {Number||null} xOffset
+   *         The x-offset of the grid cell highlight.
+   * @param  {Number||null} yOffset
+   *         The y-offset of the grid cell highlight.
    */
-  onShowGridCellHighlight(node, color, gridFragmentIndex, rowNumber, columnNumber) {
+  onShowGridCellHighlight(node, color, gridFragmentIndex,
+    rowNumber, columnNumber, keyModifierState, xOffset, yOffset) {
     let { highlighterSettings } = this.store.getState();
 
-    highlighterSettings.showGridCell = { gridFragmentIndex, rowNumber, columnNumber };
     highlighterSettings.color = color;
 
+    highlighterSettings.showGridCell = { gridFragmentIndex, rowNumber, columnNumber };
+
     this.showGridHighlighter(node, highlighterSettings);
 
+    if (Services.prefs.getBoolPref(SCROLL_INTO_VIEW_OF_GRID_PREF)) {
+      if (keyModifierState || Services.prefs.getBoolPref(AUTOSCROLL_TO_GRID_CELLS)) {
+        if (xOffset && yOffset) {
+          const x = Number(xOffset);
+          const y = Number(yOffset) - GRID_SCROLL_YOFFSET;
+          node.scrollIntoView(x, y);
+        }
+      }
+    }
+
     this.store.dispatch(updateGridHighlighted(node, true));
   },
 
   /**
    * Highlights the grid line in the CSS Grid Highlighter for the given grid.
    *
    * @param  {NodeFront} node
    *         The NodeFront of the grid container element for which the grid
@@ -575,25 +610,55 @@ GridInspector.prototype = {
   /**
    * 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) {
+  onToggleGridHighlighter(node, xOffset, yOffset) {
     let highlighterSettings = this.getGridHighlighterSettings(node);
     this.toggleGridHighlighter(node, highlighterSettings);
 
+    if (Services.prefs.getBoolPref(SCROLL_INTO_VIEW_OF_GRID_PREF)) {
+      const x = Number(xOffset);
+      const y = Number(yOffset) - GRID_SCROLL_YOFFSET;
+
+      node.scrollIntoView(x, y);
+    }
+
     this.store.dispatch(updateGridHighlighted(node,
       node !== this.highlighters.gridHighlighterShown));
   },
 
   /**
+    * Handler for a change in the autoscroll to grid cell checkbox
+    * in the GridDisplaySettings component.
+    * Toggles on/off the option to scroll to the grid cell in the grid highlighter.
+    * Refreshes the shown grid highlighter for the grids currently highlighted.
+    *
+    * @param  {Boolean} enabled
+    *         Whether or not the grid highlighter should autoscroll to
+    *          highlighted grid cell.
+    */
+  onToggleScrollToGridCell(enabled) {
+    this.store.dispatch(updateAutoScrollToGridCell(enabled));
+    Services.prefs.setBoolPref(AUTOSCROLL_TO_GRID_CELLS, enabled);
+    let { grids } = this.store.getState();
+
+    for (let grid of grids) {
+      if (grid.highlighted) {
+        let highlighterSettings = this.getGridHighlighterSettings(grid.nodeFront);
+        this.highlighters.showGridHighlighter(grid.nodeFront, highlighterSettings);
+      }
+    }
+  },
+
+  /**
     * 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.
     *
     * @param  {Boolean} enabled
     *         Whether or not the grid highlighter should show the grid areas.
     */
   onToggleShowGridAreas(enabled) {
--- a/devtools/client/inspector/grids/reducers/highlighter-settings.js
+++ b/devtools/client/inspector/grids/reducers/highlighter-settings.js
@@ -1,28 +1,36 @@
 /* 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 {
+  UPDATE_AUTOSCROLL_GRID_CELL,
   UPDATE_SHOW_GRID_AREAS,
   UPDATE_SHOW_GRID_LINE_NUMBERS,
   UPDATE_SHOW_INFINITE_LINES
 } = require("../actions/index");
 
 const INITIAL_HIGHLIGHTER_SETTINGS = {
+  scrollToGridCell: false,
   showGridAreasOverlay: false,
   showGridLineNumbers: false,
   showInfiniteLines: false,
 };
 
 let reducers = {
 
+  [UPDATE_AUTOSCROLL_GRID_CELL](highlighterSettings, { enabled }) {
+    return Object.assign({}, highlighterSettings, {
+      scrollToGridCell: enabled,
+    });
+  },
+
   [UPDATE_SHOW_GRID_AREAS](highlighterSettings, { enabled }) {
     return Object.assign({}, highlighterSettings, {
       showGridAreasOverlay: enabled,
     });
   },
 
   [UPDATE_SHOW_GRID_LINE_NUMBERS](highlighterSettings, { enabled }) {
     return Object.assign({}, highlighterSettings, {
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -52,16 +52,17 @@ LayoutView.prototype = {
 
     let {
       getSwatchColorPickerTooltip,
       onSetGridOverlayColor,
       onShowGridAreaHighlight,
       onShowGridCellHighlight,
       onShowGridLineNamesHighlight,
       onToggleGridHighlighter,
+      onToggleScrollToGridCell,
       onToggleShowGridAreas,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
     } = this.inspector.gridInspector.getComponentProps();
 
     let {
       onPromoteLearnMoreClick,
     } = this;
@@ -80,16 +81,17 @@ LayoutView.prototype = {
       onShowBoxModelEditor,
       onShowBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
       onShowGridAreaHighlight,
       onShowGridCellHighlight,
       onShowGridLineNamesHighlight,
       onToggleGeometryEditor,
       onToggleGridHighlighter,
+      onToggleScrollToGridCell,
       onToggleShowGridAreas,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
     });
 
     let provider = createElement(Provider, {
       id: "layoutview",
       key: "layoutview",
--- a/devtools/client/locales/en-US/layout.properties
+++ b/devtools/client/locales/en-US/layout.properties
@@ -1,15 +1,19 @@
 # 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/.
 
 # LOCALIZATION NOTE This file contains the Layout Inspector strings.
 # The Layout Inspector is a panel accessible in the Inspector sidebar.
 
+# LOCALIZATION NOTE (layout.autoScrollToGridCells):
+# Setting option to automatically scroll highlighted cell.
+layout.autoScrollToGridCells=Automatically scroll to grid cell
+
 # LOCALIZATION NOTE (layout.cannotShowGridOutline, layout.cannotSHowGridOutline.title):
 # In the case where the grid outline cannot be effectively displayed.
 layout.cannotShowGridOutline=Cannot show outline for this grid
 layout.cannotShowGridOutline.title=The selected grid’s outline cannot effectively fit inside the layout panel for it to be usable.
 
 # LOCALIZATION NOTE (layout.displayAreaNames): Label of the display area names setting
 # option in the CSS Grid pane.
 layout.displayAreaNames=Display area names
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -72,18 +72,20 @@ pref("devtools.fontinspector.enabled", t
 // Counter to promote the inspector layout view.
 // @remove after release 56 (See Bug 1355747)
 pref("devtools.promote.layoutview", 1);
 // Whether or not to show the promote bar in the layout view
 // @remove after release 56 (See Bug 1355747)
 pref("devtools.promote.layoutview.showPromoteBar", true);
 
 // Grid highlighter preferences
+pref("devtools.gridinspector.autoScrollToGridCells", false);
 pref("devtools.gridinspector.gridOutlineMaxColumns", 50);
 pref("devtools.gridinspector.gridOutlineMaxRows", 50);
+pref("devtools.gridinspector.scrollIntoViewOfGridNode", false);
 pref("devtools.gridinspector.showGridAreas", false);
 pref("devtools.gridinspector.showGridLineNumbers", false);
 pref("devtools.gridinspector.showInfiniteLines", false);
 pref("devtools.gridinspector.showNegativeLineNumbers", false);
 
 // Whether or not the box model panel is opened in the computed view
 pref("devtools.computed.boxmodel.opened", true);
 // Whether or not the box model panel is opened in the layout view
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -676,19 +676,28 @@ var NodeActor = exports.NodeActor = prot
     if (Cu.isDeadWrapper(this.rawNode)) {
       return "";
     }
     return getXPath(this.rawNode);
   },
 
   /**
    * Scroll the selected node into view.
+   *
+   * @param  {Number||null} xOffset
+   *         The x-offset of the selected node.
+   * @param  {Number||null} yOffset
+   *         The y-offset of the selected node.
    */
-  scrollIntoView: function () {
+  scrollIntoView: function (xOffset, yOffset) {
     this.rawNode.scrollIntoView(true);
+
+    if (typeof xOffset === "number" && typeof yOffset === "number") {
+      this.walker.rootWin.scrollBy(xOffset, yOffset);
+    }
   },
 
   /**
    * Get the node's image data if any (for canvas and img nodes).
    * Returns an imageData object with the actual data being a LongStringActor
    * and a size json object.
    * The image data is transmitted as a base64 encoded png data-uri.
    * The method rejects if the node isn't an image or if the image is missing
--- a/devtools/shared/specs/node.js
+++ b/devtools/shared/specs/node.js
@@ -45,17 +45,17 @@ const nodeSpec = generateActorSpec({
     },
     getXPath: {
       request: {},
       response: {
         value: RetVal("string")
       }
     },
     scrollIntoView: {
-      request: {},
+      request: {xOffset: Arg(0, "nullable:number"), yOffset: Arg(1, "nullable:number")},
       response: {}
     },
     getImageData: {
       request: {maxDim: Arg(0, "nullable:number")},
       response: RetVal("imageData")
     },
     getEventListenerInfo: {
       request: {},