Bug 1302197 - Implement the option to show grid cell highlights in the CSS Grid Highlighter. r?pbro draft
authorGabriel Luong <gabriel.luong@gmail.com>
Wed, 01 Mar 2017 02:46:50 -0500
changeset 490723 dfef8cbfd944ed42bff87c643f588391ec806ee8
parent 490722 417a8c548d3944bd91eb26322f2092525f727ff4
child 547359 f05cf4a7dc533b9ebc6b57ecb4c245385b45d653
push id47207
push userbmo:gl@mozilla.com
push dateWed, 01 Mar 2017 07:47:28 +0000
reviewerspbro
bugs1302197
milestone54.0a1
Bug 1302197 - Implement the option to show grid cell highlights in the CSS Grid Highlighter. r?pbro MozReview-Commit-ID: BSs9tPksPgA
devtools/server/actors/highlighters.css
devtools/server/actors/highlighters/css-grid.js
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -225,23 +225,25 @@
   left: 0;
   image-rendering: -moz-crisp-edges;
 }
 
 :-moz-native-anonymous .css-grid-regions {
   opacity: 0.6;
 }
 
-:-moz-native-anonymous .css-grid-areas {
+:-moz-native-anonymous .css-grid-areas,
+:-moz-native-anonymous .css-grid-cells {
   fill: #CEC0ED;
   stroke: none;
 }
 
-:-moz-native-anonymous .css-grid-infobar-areaname {
-  color: hsl(285,100%, 75%);
+:-moz-native-anonymous .css-grid-area-infobar-name,
+:-moz-native-anonymous .css-grid-cell-infobar-position {
+  color: hsl(285, 100%, 75%);
 }
 
 /* CSS Transform Highlighter */
 
 :-moz-native-anonymous .css-transform-transformed {
   fill: var(--highlighter-content-color);
   opacity: 0.8;
 }
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -16,19 +16,21 @@ const {
 const {
   getCurrentZoom,
   setIgnoreLayoutChanges,
   getWindowDimensions
 } = require("devtools/shared/layout/utils");
 const { stringifyGridFragments } = require("devtools/server/actors/utils/css-grid-utils");
 
 const CSS_GRID_ENABLED_PREF = "layout.css.grid.enabled";
+
 const DEFAULT_GRID_COLOR = "#4B0082";
+
+const COLUMNS = "cols";
 const ROWS = "rows";
-const COLUMNS = "cols";
 
 const GRID_LINES_PROPERTIES = {
   "edge": {
     lineDash: [0, 0],
     alpha: 1,
   },
   "explicit": {
     lineDash: [5, 3],
@@ -64,41 +66,56 @@ const COLUMN_KEY = {};
  * h.show(node, options);
  * h.hide();
  * h.destroy();
  *
  * Available Options:
  * - color(colorValue)
  *     @param  {String} colorValue
  *     The color that should be used to draw the highlighter for this grid.
+ * - showAllGridAreas(isShown)
+ *     @param  {Boolean} isShown
+ *     Shows all the grid area highlights for the current grid if isShown is true.
  * - showGridArea(areaName)
  *     @param  {String} areaName
  *     Shows the grid area highlight for the given area name.
- * - showAllGridAreas
- *     Shows all the grid area highlights for the current grid.
+ * - showGridCell({ gridFragmentIndex: Number, rowNumber: Number, columnNumber: Number })
+ *     @param  {Object} { gridFragmentIndex: Number, rowNumber: Number,
+ *                        columnNumber: Number }
+ *     An object containing the grid fragment index, row and column numbers to the
+ *     corresponding grid cell to highlight for the current grid.
  * - showGridLineNumbers(isShown)
- *     @param  {Boolean}
+ *     @param  {Boolean} isShown
  *     Displays the grid line numbers on the grid lines if isShown is true.
  * - showInfiniteLines(isShown)
  *     @param  {Boolean} isShown
  *     Displays an infinite line to represent the grid lines if isShown is true.
  *
  * Structure:
  * <div class="highlighter-container">
  *   <canvas id="css-grid-canvas" class="css-grid-canvas">
  *   <svg class="css-grid-elements" hidden="true">
  *     <g class="css-grid-regions">
  *       <path class="css-grid-areas" points="..." />
+ *       <path class="css-grid-cells" points="..." />
  *     </g>
  *   </svg>
- *   <div class="css-grid-infobar-container">
+ *   <div class="css-grid-area-infobar-container">
  *     <div class="css-grid-infobar">
  *       <div class="css-grid-infobar-text">
- *         <span class="css-grid-infobar-areaname">Grid Area Name</span>
- *         <span class="css-grid-infobar-dimensions"Grid Area Dimensions></span>
+ *         <span class="css-grid-area-infobar-name">Grid Area Name</span>
+ *         <span class="css-grid-area-infobar-dimensions"Grid Area Dimensions></span>
+ *       </div>
+ *     </div>
+ *   </div>
+ *   <div class="css-grid-cell-infobar-container">
+ *     <div class="css-grid-infobar">
+ *       <div class="css-grid-infobar-text">
+ *         <span class="css-grid-cell-infobar-position">Grid Cell Position</span>
+ *         <span class="css-grid-cell-infobar-dimensions"Grid Cell Dimensions></span>
  *       </div>
  *     </div>
  *   </div>
  * </div>
  */
 function CssGridHighlighter(highlighterEnv) {
   AutoRefreshHighlighter.call(this, highlighterEnv);
 
@@ -178,58 +195,114 @@ CssGridHighlighter.prototype = extend(Au
       parent: regions,
       attributes: {
         "class": "areas",
         "id": "areas"
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
-    // Building the grid infobar markup
-    let infobarContainer = createNode(this.win, {
+    createSVGNode(this.win, {
+      nodeType: "path",
+      parent: regions,
+      attributes: {
+        "class": "cells",
+        "id": "cells"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    // Building the grid area infobar markup
+    let areaInfobarContainer = createNode(this.win, {
       parent: container,
       attributes: {
-        "class": "infobar-container",
-        "id": "infobar-container",
+        "class": "area-infobar-container",
+        "id": "area-infobar-container",
         "position": "top",
         "hidden": "true"
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
-    let infobar = createNode(this.win, {
-      parent: infobarContainer,
+    let areaInfobar = createNode(this.win, {
+      parent: areaInfobarContainer,
       attributes: {
         "class": "infobar"
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
-    let textbox = createNode(this.win, {
-      parent: infobar,
+    let areaTextbox = createNode(this.win, {
+      parent: areaInfobar,
       attributes: {
         "class": "infobar-text"
       },
       prefix: this.ID_CLASS_PREFIX
     });
     createNode(this.win, {
       nodeType: "span",
-      parent: textbox,
+      parent: areaTextbox,
       attributes: {
-        "class": "infobar-areaname",
-        "id": "infobar-areaname"
+        "class": "area-infobar-name",
+        "id": "area-infobar-name"
       },
       prefix: this.ID_CLASS_PREFIX
     });
     createNode(this.win, {
       nodeType: "span",
-      parent: textbox,
+      parent: areaTextbox,
+      attributes: {
+        "class": "area-infobar-dimensions",
+        "id": "area-infobar-dimensions"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    // Building the grid cell infobar markup
+    let cellInfobarContainer = createNode(this.win, {
+      parent: container,
+      attributes: {
+        "class": "cell-infobar-container",
+        "id": "cell-infobar-container",
+        "position": "top",
+        "hidden": "true"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    let cellInfobar = createNode(this.win, {
+      parent: cellInfobarContainer,
       attributes: {
-        "class": "infobar-dimensions",
-        "id": "infobar-dimensions"
+        "class": "infobar"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    let cellTextbox = createNode(this.win, {
+      parent: cellInfobar,
+      attributes: {
+        "class": "infobar-text"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+    createNode(this.win, {
+      nodeType: "span",
+      parent: cellTextbox,
+      attributes: {
+        "class": "cell-infobar-position",
+        "id": "cell-infobar-position"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+    createNode(this.win, {
+      nodeType: "span",
+      parent: cellTextbox,
+      attributes: {
+        "class": "cell-infobar-dimensions",
+        "id": "cell-infobar-dimensions"
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
     return container;
   },
 
   destroy() {
@@ -339,33 +412,53 @@ CssGridHighlighter.prototype = extend(Au
   /**
    * Shows the grid area highlight for the given area name.
    *
    * @param  {String} areaName
    *         Grid area name.
    */
   showGridArea(areaName) {
     this.renderGridArea(areaName);
-    this._showGridArea();
   },
 
   /**
    * Shows all the grid area highlights for the current grid.
    */
   showAllGridAreas() {
     this.renderGridArea();
-    this._showGridArea();
   },
 
   /**
    * Clear the grid area highlights.
    */
   clearGridAreas() {
-    let box = this.getElement("areas");
-    box.setAttribute("d", "");
+    let areas = this.getElement("areas");
+    areas.setAttribute("d", "");
+  },
+
+  /**
+   * Shows the grid cell highlight for the given grid cell options.
+   *
+   * @param  {Number} options.gridFragmentIndex
+   *         Index of the grid fragment to render the grid cell highlight.
+   * @param  {Number} options.rowNumber
+   *         Row number of the grid cell to highlight.
+   * @param  {Number} options.columnNumber
+   *         Column number of the grid cell to highlight.
+   */
+  showGridCell({ gridFragmentIndex, rowNumber, columnNumber }) {
+    this.renderGridCell(gridFragmentIndex, rowNumber, columnNumber);
+  },
+
+  /**
+   * Clear the grid cell highlights.
+   */
+  clearGridCell() {
+    let cells = this.getElement("cells");
+    cells.setAttribute("d", "");
   },
 
   /**
    * Checks if the current node has a CSS Grid layout.
    *
    * @return  {Boolean} true if the current node has a CSS grid layout, false otherwise.
    */
   isGrid() {
@@ -417,76 +510,97 @@ CssGridHighlighter.prototype = extend(Au
 
     // Display the grid area highlights if needed.
     if (this.options.showAllGridAreas) {
       this.showAllGridAreas();
     } else if (this.options.showGridArea) {
       this.showGridArea(this.options.showGridArea);
     }
 
+    // Display the grid cell highlights if needed.
+    if (this.options.showGridCell) {
+      this.showGridCell(this.options.showGridCell);
+    }
+
     this._showGrid();
+    this._showGridElements();
 
     root.setAttribute("style",
       `position:absolute; width:${width}px;height:${height}px; overflow:hidden`);
 
     setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
     return true;
   },
 
   /**
-   * Update the grid information displayed in the grid info bar.
+   * Update the grid information displayed in the grid area info bar.
    *
    * @param  {GridArea} area
    *         The grid area object.
    * @param  {Number} x1
    *         The first x-coordinate of the grid area rectangle.
    * @param  {Number} x2
    *         The second x-coordinate of the grid area rectangle.
    * @param  {Number} y1
    *         The first y-coordinate of the grid area rectangle.
    * @param  {Number} y2
    *         The second y-coordinate of the grid area rectangle.
    */
-  _updateInfobar(area, x1, x2, y1, y2) {
+  _updateGridAreaInfobar(area, x1, x2, y1, y2) {
     let width = x2 - x1;
     let height = y2 - y1;
     let dim = parseFloat(width.toPrecision(6)) +
               " \u00D7 " +
               parseFloat(height.toPrecision(6));
 
-    this.getElement("infobar-areaname").setTextContent(area.name);
-    this.getElement("infobar-dimensions").setTextContent(dim);
+    this.getElement("area-infobar-name").setTextContent(area.name);
+    this.getElement("area-infobar-dimensions").setTextContent(dim);
+
+    let container = this.getElement("area-infobar-container");
+    this._moveInfobar(container, x1, x2, y1, y2);
+  },
 
-    this._moveInfobar(x1, x2, y1, y2);
+  _updateGridCellInfobar(rowNumber, columnNumber, x1, x2, y1, y2) {
+    let width = x2 - x1;
+    let height = y2 - y1;
+    let dim = parseFloat(width.toPrecision(6)) +
+              " \u00D7 " +
+              parseFloat(height.toPrecision(6));
+    let position = `${rowNumber}\/${columnNumber}`;
+
+    this.getElement("cell-infobar-position").setTextContent(position);
+    this.getElement("cell-infobar-dimensions").setTextContent(dim);
+
+    let container = this.getElement("cell-infobar-container");
+    this._moveInfobar(container, x1, x2, y1, y2);
   },
 
   /**
-   * Move the grid infobar to the right place in the highlighter.
+   * Move the given grid infobar to the right place in the highlighter.
    *
    * @param  {Number} x1
-   *         The first x-coordinate of the grid area rectangle.
+   *         The first x-coordinate of the grid rectangle.
    * @param  {Number} x2
-   *         The second x-coordinate of the grid area rectangle.
+   *         The second x-coordinate of the grid rectangle.
    * @param  {Number} y1
-   *         The first y-coordinate of the grid area rectangle.
+   *         The first y-coordinate of the grid rectangle.
    * @param  {Number} y2
-   *         The second y-coordinate of the grid area rectangle.
+   *         The second y-coordinate of the grid rectangle.
    */
-  _moveInfobar(x1, x2, y1, y2) {
+  _moveInfobar(container, x1, x2, y1, y2) {
     let bounds = {
       bottom: y2,
       height: y2 - y1,
       left: x1,
       right: x2,
       top: y1,
       width: x2 - x1,
       x: x1,
       y: y1,
     };
-    let container = this.getElement("infobar-container");
 
     moveInfobar(container, bounds, this.win);
   },
 
   clearCanvas(width, height) {
     let ratio = parseFloat((this.win.devicePixelRatio || 1).toFixed(2));
 
     // Resize the canvas taking the dpr into account so as to have crisp lines.
@@ -610,17 +724,17 @@ CssGridHighlighter.prototype = extend(Au
    *         The line position along the x-axis for a column grid line and
    *         y-axis for a row grid line.
    * @param  {Number} startPos
    *         The start position of the cross side of the grid line.
    * @param  {Number} endPos
    *         The end position of the cross side of the grid line.
    * @param  {String} dimensionType
    *         The grid dimension type which is either the constant COLUMNS or ROWS.
-   * @param  {[type]} lineType
+   * @param  {String} lineType
    *         The grid line type - "edge", "explicit", or "implicit".
    */
   renderLine(linePos, startPos, endPos, dimensionType, lineType) {
     this.ctx.save();
     this.ctx.setLineDash(GRID_LINES_PROPERTIES[lineType].lineDash);
     this.ctx.beginPath();
     this.ctx.translate(.5, .5);
 
@@ -734,56 +848,108 @@ CssGridHighlighter.prototype = extend(Au
         let path = "M" + x1 + "," + y1 + " " +
                    "L" + x2 + "," + y1 + " " +
                    "L" + x2 + "," + y2 + " " +
                    "L" + x1 + "," + y2;
         paths.push(path);
 
         // Update and show the info bar when only displaying a single grid area.
         if (areaName) {
-          this._updateInfobar(area, x1, x2, y1, y2);
-          this._showInfoBar();
+          this._updateGridAreaInfobar(area, x1, x2, y1, y2);
+          this._showGridAreaInfoBar();
         }
       }
     }
 
-    let box = this.getElement("areas");
-    box.setAttribute("d", paths.join(" "));
+    let areas = this.getElement("areas");
+    areas.setAttribute("d", paths.join(" "));
   },
 
   /**
-   * Hide the highlighter, the canvas and the infobar.
+   * Render the grid cell highlight for the given grid fragment index, row and column
+   * number.
+   *
+   * @param  {Number} gridFragmentIndex
+   *         Index of the grid fragment to render the grid cell highlight.
+   * @param  {Number} rowNumber
+   *         Row number of the grid cell to highlight.
+   * @param  {Number} columnNumber
+   *         Column number of the grid cell to highlight.
+   */
+  renderGridCell(gridFragmentIndex, rowNumber, columnNumber) {
+    let fragment = this.gridData[gridFragmentIndex];
+    if (!fragment) {
+      return;
+    }
+
+    let row = fragment.rows.tracks[rowNumber - 1];
+    let column = fragment.cols.tracks[columnNumber - 1];
+
+    if (!row || !column) {
+      return;
+    }
+
+    let currentZoom = getCurrentZoom(this.win);
+    let {bounds} = this.currentQuads.content[gridFragmentIndex];
+
+    let x1 = column.start + (bounds.left / currentZoom);
+    let x2 = column.start + column.breadth + (bounds.left / currentZoom);
+    let y1 = row.start + (bounds.top / currentZoom);
+    let y2 = row.start + row.breadth + (bounds.top / currentZoom);
+
+    let path = "M" + x1 + "," + y1 + " " +
+               "L" + x2 + "," + y1 + " " +
+               "L" + x2 + "," + y2 + " " +
+               "L" + x1 + "," + y2;
+    let cells = this.getElement("cells");
+    cells.setAttribute("d", path);
+
+    this._updateGridCellInfobar(rowNumber, columnNumber, x1, x2, y1, y2);
+    this._showGridCellInfoBar();
+  },
+
+  /**
+   * Hide the highlighter, the canvas and the infobars.
    */
   _hide() {
     setIgnoreLayoutChanges(true);
     this._hideGrid();
-    this._hideGridArea();
-    this._hideInfoBar();
+    this._hideGridElements();
+    this._hideGridAreaInfoBar();
+    this._hideGridCellInfoBar();
     setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
   },
 
   _hideGrid() {
     this.getElement("canvas").setAttribute("hidden", "true");
   },
 
   _showGrid() {
     this.getElement("canvas").removeAttribute("hidden");
   },
 
-  _hideGridArea() {
+  _hideGridElements() {
     this.getElement("elements").setAttribute("hidden", "true");
   },
 
-  _showGridArea() {
+  _showGridElements() {
     this.getElement("elements").removeAttribute("hidden");
   },
 
-  _hideInfoBar() {
-    this.getElement("infobar-container").setAttribute("hidden", "true");
+  _hideGridAreaInfoBar() {
+    this.getElement("area-infobar-container").setAttribute("hidden", "true");
   },
 
-  _showInfoBar() {
-    this.getElement("infobar-container").removeAttribute("hidden");
+  _showGridAreaInfoBar() {
+    this.getElement("area-infobar-container").removeAttribute("hidden");
+  },
+
+  _hideGridCellInfoBar() {
+    this.getElement("cell-infobar-container").setAttribute("hidden", "true");
+  },
+
+  _showGridCellInfoBar() {
+    this.getElement("cell-infobar-container").removeAttribute("hidden");
   },
 
 });
 
 exports.CssGridHighlighter = CssGridHighlighter;