Bug 1387511 - Part 2: Format css-grid.js r=pbro draft
authorGabriel Luong <gabriel.luong@gmail.com>
Tue, 14 Nov 2017 00:58:26 -0500
changeset 697500 b4133c9f6ac355c57f593aa4d43e469cfe667160
parent 697499 bd657dd656d4aac96d010c3240440998766e213f
child 740141 ed7f25bd69badf5f062aa8dc2a9583df1da63312
push id89026
push userbmo:gl@mozilla.com
push dateTue, 14 Nov 2017 05:58:59 +0000
reviewerspbro
bugs1387511
milestone59.0a1
Bug 1387511 - Part 2: Format css-grid.js r=pbro MozReview-Commit-ID: LGY4RIi0bFC
devtools/server/actors/highlighters/css-grid.js
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -164,17 +164,17 @@ class CssGridHighlighter extends AutoRef
     this.onPageHide = this.onPageHide.bind(this);
     this.onWillNavigate = this.onWillNavigate.bind(this);
 
     this.highlighterEnv.on("will-navigate", this.onWillNavigate);
 
     let { pageListenerTarget } = highlighterEnv;
     pageListenerTarget.addEventListener("pagehide", this.onPageHide);
 
-    // Initialize the <canvas> position to the top left corner of the page
+    // Initialize the <canvas> position to the top left corner of the page.
     this._canvasPosition = {
       x: 0,
       y: 0
     };
 
     // Calling `updateCanvasPosition` anyway since the highlighter could be initialized
     // on a page that has scrolled already.
     updateCanvasPosition(this._canvasPosition, this._scroll, this.win,
@@ -208,17 +208,17 @@ class CssGridHighlighter extends AutoRef
         "class": "canvas",
         "hidden": "true",
         "width": CANVAS_SIZE,
         "height": CANVAS_SIZE
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
-    // Build the SVG element
+    // Build the SVG element.
     let svg = createSVGNode(this.win, {
       nodeType: "svg",
       parent: root,
       attributes: {
         "id": "elements",
         "width": "100%",
         "height": "100%",
         "hidden": "true"
@@ -250,17 +250,17 @@ class CssGridHighlighter extends AutoRef
       parent: regions,
       attributes: {
         "class": "cells",
         "id": "cells"
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
-    // Building the grid area infobar markup
+    // Build the grid area infobar markup.
     let areaInfobarContainer = createNode(this.win, {
       parent: container,
       attributes: {
         "class": "area-infobar-container",
         "id": "area-infobar-container",
         "position": "top",
         "hidden": "true"
       },
@@ -296,17 +296,17 @@ class CssGridHighlighter extends AutoRef
       parent: areaTextbox,
       attributes: {
         "class": "area-infobar-dimensions",
         "id": "area-infobar-dimensions"
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
-    // Building the grid cell infobar markup
+    // Build 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"
       },
@@ -342,17 +342,17 @@ class CssGridHighlighter extends AutoRef
       parent: cellTextbox,
       attributes: {
         "class": "cell-infobar-dimensions",
         "id": "cell-infobar-dimensions"
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
-    // Building the grid line infobar markup
+    // Build the grid line infobar markup.
     let lineInfobarContainer = createNode(this.win, {
       parent: container,
       attributes: {
         "class": "line-infobar-container",
         "id": "line-infobar-container",
         "position": "top",
         "hidden": "true"
       },
@@ -479,19 +479,21 @@ class CssGridHighlighter extends AutoRef
     let pattern = ctx.createPattern(canvas, "repeat");
 
     gridPatternMap.set(dimension, pattern);
     gCachedGridPattern.set(devicePixelRatio, gridPatternMap);
 
     return pattern;
   }
 
+  /**
+   * If a page hide event is triggered for current window's highlighter, hide the
+   * highlighter.
+   */
   onPageHide({ target }) {
-    // If a page hide event is triggered for current window's highlighter, hide the
-    // highlighter.
     if (target.defaultView === this.win) {
       this.hide();
     }
   }
 
   /**
    * Called when the page will-navigate. Used to hide the grid highlighter and clear
    * the cached gap patterns and avoid using DeadWrapper obejcts as gap patterns the
@@ -594,17 +596,17 @@ class CssGridHighlighter extends AutoRef
     return this.currentNode.getGridFragments().length > 0;
   }
 
   /**
    * Is a given grid fragment valid? i.e. does it actually have tracks? In some cases, we
    * may have a fragment that defines column tracks but doesn't have any rows (or vice
    * versa). In which case we do not want to draw anything for that fragment.
    *
-   * @param {Object} fragment
+   * @param  {Object} fragment
    * @return {Boolean}
    */
   isValidFragment(fragment) {
     return fragment.cols.tracks.length && fragment.rows.tracks.length;
   }
 
   /**
    * The AutoRefreshHighlighter's _hasMoved method returns true only if the
@@ -619,18 +621,18 @@ class CssGridHighlighter extends AutoRef
     this.gridData = this.currentNode.getGridFragments();
     let newGridData = stringifyGridFragments(this.gridData);
 
     return hasMoved || oldGridData !== newGridData;
   }
 
   /**
    * Update the highlighter on the current highlighted node (the one that was
-   * passed as an argument to show(node)).
-   * Should be called whenever node's geometry or grid changes.
+   * passed as an argument to show(node)). Should be called whenever node's geometry
+   * or grid changes.
    */
   _update() {
     setIgnoreLayoutChanges(true);
 
     let root = this.getElement("root");
     let cells = this.getElement("cells");
     let areas = this.getElement("areas");
 
@@ -648,17 +650,17 @@ class CssGridHighlighter extends AutoRef
     // Updates the <canvas> element's position and size.
     // It also clear the <canvas>'s drawing context.
     updateCanvasElement(this.canvas, this._canvasPosition, this.win.devicePixelRatio);
 
     // Clear the grid area highlights.
     this.clearGridAreas();
     this.clearGridCell();
 
-    // Update the current matrix used in our canvas' rendering
+    // Update the current matrix used in our canvas' rendering.
     let { currentMatrix, hasNodeTransformations } = getCurrentMatrix(this.currentNode,
       this.win);
     this.currentMatrix = currentMatrix;
     this.hasNodeTransformations = hasNodeTransformations;
 
     // Start drawing the grid fragments.
     for (let i = 0; i < this.gridData.length; i++) {
       this.renderFragment(this.gridData[i]);
@@ -725,18 +727,18 @@ class CssGridHighlighter extends AutoRef
    * @param  {Object} bounds
    *         A DOMRect-like object represent the grid cell rectangle.
    */
   _updateGridCellInfobar(rowNumber, columnNumber, bounds) {
     let { width, height } = bounds;
     let dim = parseFloat(width.toPrecision(6)) +
               " \u00D7 " +
               parseFloat(height.toPrecision(6));
-    let position = LAYOUT_L10N.getFormatStr("layout.rowColumnPositions",
-                   rowNumber, columnNumber);
+    let position = LAYOUT_L10N.getFormatStr("layout.rowColumnPositions", rowNumber,
+      columnNumber);
 
     this.getElement("cell-infobar-position").setTextContent(position);
     this.getElement("cell-infobar-dimensions").setTextContent(dim);
 
     let container = this.getElement("cell-infobar-container");
     moveInfobar(container, bounds, this.win, {
       position: "top",
       hideIfOffscreen: true
@@ -812,166 +814,99 @@ class CssGridHighlighter extends AutoRef
     return trackIndex + 1;
   }
 
   renderFragment(fragment) {
     if (!this.isValidFragment(fragment)) {
       return;
     }
 
-    this.renderLines(fragment.cols, COLUMNS, "left", "top", "height",
-                     this.getFirstRowLinePos(fragment),
-                     this.getLastRowLinePos(fragment));
-    this.renderLines(fragment.rows, ROWS, "top", "left", "width",
-                     this.getFirstColLinePos(fragment),
-                     this.getLastColLinePos(fragment));
+    this.renderLines(fragment.cols, COLUMNS, this.getFirstRowLinePos(fragment),
+      this.getLastRowLinePos(fragment));
+    this.renderLines(fragment.rows, ROWS, this.getFirstColLinePos(fragment),
+      this.getLastColLinePos(fragment));
 
     if (this.options.showGridAreasOverlay) {
       this.renderGridAreaOverlay();
     }
 
     // Line numbers are rendered in a 2nd step to avoid overlapping with existing lines.
     if (this.options.showGridLineNumbers) {
-      this.renderLineNumbers(fragment.cols, COLUMNS, "left", "top",
-                       this.getFirstRowLinePos(fragment));
-      this.renderLineNumbers(fragment.rows, ROWS, "top", "left",
-                       this.getFirstColLinePos(fragment));
+      this.renderLineNumbers(fragment.cols, COLUMNS, this.getFirstRowLinePos(fragment));
+      this.renderLineNumbers(fragment.rows, ROWS, this.getFirstColLinePos(fragment));
 
       if (Services.prefs.getBoolPref(NEGATIVE_LINE_NUMBERS_PREF)) {
-        this.renderNegativeLineNumbers(fragment.cols, COLUMNS, "left", "top",
-                          this.getLastRowLinePos(fragment));
-        this.renderNegativeLineNumbers(fragment.rows, ROWS, "top", "left",
-                          this.getLastColLinePos(fragment));
+        this.renderNegativeLineNumbers(fragment.cols, COLUMNS,
+          this.getLastRowLinePos(fragment));
+        this.renderNegativeLineNumbers(fragment.rows, ROWS,
+          this.getLastColLinePos(fragment));
       }
     }
   }
 
   /**
-   * Render the negative grid lines given the grid dimension information of the
-   * column or row lines.
-   *
-   * See @param for renderLines.
-   */
-  renderNegativeLineNumbers(gridDimension, dimensionType, mainSide, crossSide,
-            startPos) {
-    let lineStartPos = startPos;
-
-    // Keep track of the number of collapsed lines per line position
-    let stackedLines = [];
-
-    const { lines } = gridDimension;
-
-    for (let i = 0, line; (line = lines[i++]);) {
-      let linePos = line.start;
-      let negativeLineNumber = line.negativeNumber;
-
-      // Don't render any negative line number greater than -1.
-      if (negativeLineNumber == 0) {
-        break;
-      }
-
-      // Check for overlapping lines. We render a second box beneath the last overlapping
-      // line number to indicate there are lines beneath it.
-      const gridLine = gridDimension.tracks[line.number - 1];
-
-      if (gridLine) {
-        const { breadth }  = gridLine;
-
-        if (breadth === 0) {
-          stackedLines.push(negativeLineNumber);
-
-          if (stackedLines.length > 0) {
-            this.renderGridLineNumber(negativeLineNumber, linePos, lineStartPos,
-              line.breadth, dimensionType, 1);
-          }
-
-          continue;
-        }
-      }
-
-      // For negative line numbers, we want to display the smallest
-      // value at the front of the stack.
-      if (stackedLines.length) {
-        negativeLineNumber = stackedLines[0];
-        stackedLines = [];
-      }
-
-      this.renderGridLineNumber(negativeLineNumber, linePos, lineStartPos, line.breadth,
-        dimensionType);
-    }
-  }
-
-  /**
    * Renders the grid area overlay on the css grid highlighter canvas.
    */
   renderGridAreaOverlay() {
     let padding = 1;
 
     for (let i = 0; i < this.gridData.length; i++) {
       let fragment = this.gridData[i];
 
       for (let area of fragment.areas) {
         let { rowStart, rowEnd, columnStart, columnEnd, type } = area;
 
         if (type === "implicit") {
           continue;
         }
 
-        // Draw the line edges for the grid area
+        // Draw the line edges for the grid area.
         const areaColStart = fragment.cols.lines[columnStart - 1];
         const areaColEnd = fragment.cols.lines[columnEnd - 1];
 
         const areaRowStart = fragment.rows.lines[rowStart - 1];
         const areaRowEnd = fragment.rows.lines[rowEnd - 1];
 
         const areaColStartLinePos = areaColStart.start + areaColStart.breadth;
         const areaRowStartLinePos = areaRowStart.start + areaRowStart.breadth;
 
-        this.renderLine(areaColStartLinePos + padding,
-                        areaRowStartLinePos, areaRowEnd.start,
-                        COLUMNS, "areaEdge");
-        this.renderLine(areaColEnd.start - padding,
-                        areaRowStartLinePos, areaRowEnd.start,
-                        COLUMNS, "areaEdge");
+        this.renderLine(areaColStartLinePos + padding, areaRowStartLinePos,
+          areaRowEnd.start, COLUMNS, "areaEdge");
+        this.renderLine(areaColEnd.start - padding, areaRowStartLinePos,
+          areaRowEnd.start, COLUMNS, "areaEdge");
 
-        this.renderLine(areaRowStartLinePos + padding,
-                        areaColStartLinePos, areaColEnd.start,
-                        ROWS, "areaEdge");
-        this.renderLine(areaRowEnd.start - padding,
-                        areaColStartLinePos, areaColEnd.start,
-                        ROWS, "areaEdge");
+        this.renderLine(areaRowStartLinePos + padding, areaColStartLinePos,
+          areaColEnd.start, ROWS, "areaEdge");
+        this.renderLine(areaRowEnd.start - padding, areaColStartLinePos, areaColEnd.start,
+          ROWS, "areaEdge");
 
         this.renderGridAreaName(fragment, area);
       }
     }
-
-    this.ctx.restore();
   }
 
   /**
    * Render grid area name on the containing grid area cell.
    *
    * @param  {Object} fragment
    *         The grid fragment of the grid container.
    * @param  {Object} area
    *         The area overlay to render on the CSS highlighter canvas.
    */
   renderGridAreaName(fragment, area) {
     let { rowStart, rowEnd, columnStart, columnEnd } = area;
     let { devicePixelRatio } = this.win;
     let displayPixelRatio = getDisplayPixelRatio(this.win);
     let offset = (displayPixelRatio / 2) % 1;
-    let fontSize = (GRID_AREA_NAME_FONT_SIZE * displayPixelRatio);
+    let fontSize = GRID_AREA_NAME_FONT_SIZE * displayPixelRatio;
+    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
+    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
 
     this.ctx.save();
-
-    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
-    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
     this.ctx.translate(offset - canvasX, offset - canvasY);
-
     this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY;
     this.ctx.strokeStyle = this.color;
     this.ctx.textAlign = "center";
     this.ctx.textBaseline = "middle";
 
     // Draw the text for the grid area name.
     for (let rowNumber = rowStart; rowNumber < rowEnd; rowNumber++) {
       for (let columnNumber = columnStart; columnNumber < columnEnd; columnNumber++) {
@@ -981,36 +916,34 @@ class CssGridHighlighter extends AutoRef
         // Check if the font size is exceeds the bounds of the containing grid cell.
         if (fontSize > (column.breadth * displayPixelRatio) ||
             fontSize > (row.breadth * displayPixelRatio)) {
           fontSize = (column.breadth + row.breadth) / 2;
           this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY;
         }
 
         let textWidth = this.ctx.measureText(area.name).width;
-
         // The width of the character 'm' approximates the height of the text.
         let textHeight = this.ctx.measureText("m").width;
-
         // Padding in pixels for the line number text inside of the line number container.
         let padding = 3 * displayPixelRatio;
 
         let boxWidth = textWidth + 2 * padding;
         let boxHeight = textHeight + 2 * padding;
 
         let x = column.start + column.breadth / 2;
         let y = row.start + row.breadth / 2;
 
         [x, y] = apply(this.currentMatrix, [x, y]);
 
         let rectXPos = x - boxWidth / 2;
         let rectYPos = y - boxHeight / 2;
 
         // Draw a rounded rectangle with a border width of 1 pixel,
-        // a border color matching the grid color, and a white background
+        // a border color matching the grid color, and a white background.
         this.ctx.lineWidth = 1 * displayPixelRatio;
         this.ctx.strokeStyle = this.color;
         this.ctx.fillStyle = "white";
         let radius = 2 * displayPixelRatio;
         drawRoundedRect(this.ctx, rectXPos, rectYPos, boxWidth, boxHeight, radius);
 
         this.ctx.fillStyle = this.color;
         this.ctx.fillText(area.name, x, y + padding);
@@ -1025,113 +958,47 @@ class CssGridHighlighter extends AutoRef
    * column or row lines.
    *
    * @param  {GridDimension} gridDimension
    *         Column or row grid dimension object.
    * @param  {Object} quad.bounds
    *         The content bounds of the box model region quads.
    * @param  {String} dimensionType
    *         The grid dimension type which is either the constant COLUMNS or ROWS.
-   * @param  {String} mainSide
-   *         The main side of the given grid dimension - "top" for rows and
-   *         "left" for columns.
-   * @param  {String} crossSide
-   *         The cross side of the given grid dimension - "left" for rows and
-   *         "top" for columns.
-   * @param  {String} mainSize
-   *         The main size of the given grid dimension - "width" for rows and
-   *         "height" for columns.
    * @param  {Number} startPos
-   *         The start position of the cross side of the grid dimension.
+   *         The start position of the cross side ("left" for ROWS and "top" for COLUMNS)
+   *         of the grid dimension.
    * @param  {Number} endPos
-   *         The end position of the cross side of the grid dimension.
+   *         The end position of the cross side ("left" for ROWS and "top" for COLUMNS)
+   *         of the grid dimension.
    */
-  renderLines(gridDimension, dimensionType, mainSide, crossSide,
-              mainSize, startPos, endPos) {
-    let lineStartPos = startPos;
-    let lineEndPos = endPos;
+  renderLines(gridDimension, dimensionType, startPos, endPos) {
+    const { lines, tracks } = gridDimension;
+    const lastEdgeLineIndex = this.getLastEdgeLineIndex(tracks);
 
-    let lastEdgeLineIndex = this.getLastEdgeLineIndex(gridDimension.tracks);
-
-    for (let i = 0; i < gridDimension.lines.length; i++) {
-      let line = gridDimension.lines[i];
+    for (let i = 0; i < lines.length; i++) {
+      let line = lines[i];
       let linePos = line.start;
 
       if (i == 0 || i == lastEdgeLineIndex) {
-        this.renderLine(linePos, lineStartPos, lineEndPos, dimensionType, "edge");
+        this.renderLine(linePos, startPos, endPos, dimensionType, "edge");
       } else {
-        this.renderLine(linePos, lineStartPos, lineEndPos, dimensionType,
-                        gridDimension.tracks[i - 1].type);
+        this.renderLine(linePos, startPos, endPos, dimensionType, tracks[i - 1].type);
       }
 
       // Render a second line to illustrate the gutter for non-zero breadth.
       if (line.breadth > 0) {
-        this.renderGridGap(linePos, lineStartPos, lineEndPos, line.breadth,
-                           dimensionType);
-        this.renderLine(linePos + line.breadth, lineStartPos, lineEndPos, dimensionType,
-                        gridDimension.tracks[i].type);
+        this.renderGridGap(linePos, startPos, endPos, line.breadth, dimensionType);
+        this.renderLine(linePos + line.breadth, startPos, endPos, dimensionType,
+          tracks[i].type);
       }
     }
   }
 
   /**
-   * Render the grid lines given the grid dimension information of the
-   * column or row lines.
-   *
-   * see @param for renderLines.
-   */
-  renderLineNumbers(gridDimension, dimensionType, mainSide, crossSide,
-              startPos) {
-    let lineStartPos = startPos;
-
-    // Keep track of the number of collapsed lines per line position
-    let stackedLines = [];
-
-    const { lines } = gridDimension;
-
-    for (let i = 0, line; (line = lines[i++]);) {
-      let linePos = line.start;
-
-      // If you place something using negative numbers, you can trigger some implicit grid
-      // creation above and to the left of the explicit grid (assuming a horizontal-tb
-      // writing mode).
-      // The first explicit grid line gets the number of 1; any implicit grid lines
-      // before 1 get negative numbers, but do not get any positivity numbers.
-      // Since here we're rendering only the positive line numbers, we have to skip any
-      // implicit grid lines before the first tha is explicit.
-      // For such lines the API returns always 0 as line's number.
-      if (line.number === 0) {
-        continue;
-      }
-
-      // Check for overlapping lines. We render a second box beneath the last overlapping
-      // line number to indicate there are lines beneath it.
-      const gridLine = gridDimension.tracks[line.number - 1];
-
-      if (gridLine) {
-        const { breadth }  = gridLine;
-
-        if (breadth === 0) {
-          stackedLines.push(gridDimension.lines[i].number);
-
-          if (stackedLines.length > 0) {
-            this.renderGridLineNumber(line.number, linePos, lineStartPos, line.breadth,
-              dimensionType, 1);
-          }
-
-          continue;
-        }
-      }
-
-      this.renderGridLineNumber(line.number, linePos, lineStartPos, line.breadth,
-        dimensionType);
-    }
-  }
-
-  /**
    * Render the grid line on the css grid highlighter canvas.
    *
    * @param  {Number} linePos
    *         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
@@ -1140,35 +1007,35 @@ class CssGridHighlighter extends AutoRef
    *         The grid dimension type which is either the constant COLUMNS or ROWS.
    * @param  {String} lineType
    *         The grid line type - "edge", "explicit", or "implicit".
    */
   renderLine(linePos, startPos, endPos, dimensionType, lineType) {
     let { devicePixelRatio } = this.win;
     let lineWidth = getDisplayPixelRatio(this.win);
     let offset = (lineWidth / 2) % 1;
-
-    let x = Math.round(this._canvasPosition.x * devicePixelRatio);
-    let y = Math.round(this._canvasPosition.y * devicePixelRatio);
+    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
+    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
 
     linePos = Math.round(linePos);
     startPos = Math.round(startPos);
     endPos = Math.round(endPos);
 
     this.ctx.save();
     this.ctx.setLineDash(GRID_LINES_PROPERTIES[lineType].lineDash);
     this.ctx.beginPath();
-    this.ctx.translate(offset - x, offset - y);
+    this.ctx.translate(offset - canvasX, offset - canvasY);
 
     let lineOptions = {
       matrix: this.currentMatrix
     };
 
     if (this.options.showInfiniteLines) {
-      lineOptions.extendToBoundaries = [x, y, x + CANVAS_SIZE, y + CANVAS_SIZE];
+      lineOptions.extendToBoundaries = [canvasX, canvasY, canvasX + CANVAS_SIZE,
+                                        canvasY + CANVAS_SIZE];
     }
 
     if (dimensionType === COLUMNS) {
       drawLine(this.ctx, linePos, startPos, linePos, endPos, lineOptions);
     } else {
       drawLine(this.ctx, startPos, linePos, endPos, linePos, lineOptions);
     }
 
@@ -1181,16 +1048,128 @@ class CssGridHighlighter extends AutoRef
       this.ctx.lineWidth = lineWidth;
     }
 
     this.ctx.stroke();
     this.ctx.restore();
   }
 
   /**
+   * Render the grid lines given the grid dimension information of the
+   * column or row lines.
+   *
+   * @param  {GridDimension} gridDimension
+   *         Column or row grid dimension object.
+   * @param  {String} dimensionType
+   *         The grid dimension type which is either the constant COLUMNS or ROWS.
+   * @param  {Number} startPos
+   *         The start position of the cross side ("left" for ROWS and "top" for COLUMNS)
+   *         of the grid dimension.
+   */
+  renderLineNumbers(gridDimension, dimensionType, startPos) {
+    const { lines, tracks } = gridDimension;
+    // Keep track of the number of collapsed lines per line position.
+    let stackedLines = [];
+
+    for (let i = 0, line; (line = lines[i++]);) {
+      // If you place something using negative numbers, you can trigger some implicit
+      // grid creation above and to the left of the explicit grid (assuming a
+      // horizontal-tb writing mode).
+      //
+      // The first explicit grid line gets the number of 1, and any implicit grid lines
+      // before 1 get negative numbers. Since here we're rendering only the positive line
+      // numbers, we have to skip any implicit grid lines before the first one that is
+      // explicit. The API returns a 0 as the line's number for these implicit lines that
+      // occurs before the first explicit line.
+      if (line.number === 0) {
+        continue;
+      }
+
+      // Check for overlapping lines. We render a second box beneath the last overlapping
+      // line number to indicate there are lines beneath it.
+      const gridLine = tracks[line.number - 1];
+
+      if (gridLine) {
+        const { breadth }  = gridLine;
+
+        if (breadth === 0) {
+          stackedLines.push(lines[i].number);
+
+          if (stackedLines.length > 0) {
+            this.renderGridLineNumber(line.number, line.start, startPos, line.breadth,
+              dimensionType, 1);
+          }
+
+          continue;
+        }
+      }
+
+      this.renderGridLineNumber(line.number, line.start, startPos, line.breadth,
+        dimensionType);
+    }
+  }
+
+  /**
+   * Render the negative grid lines given the grid dimension information of the
+   * column or row lines.
+   *
+   * @param  {GridDimension} gridDimension
+   *         Column or row grid dimension object.
+   * @param  {String} dimensionType
+   *         The grid dimension type which is either the constant COLUMNS or ROWS.
+   * @param  {Number} startPos
+   *         The start position of the cross side ("left" for ROWS and "top" for COLUMNS)
+   *         of the grid dimension.
+   */
+  renderNegativeLineNumbers(gridDimension, dimensionType, startPos) {
+    const { lines, tracks } = gridDimension;
+    // Keep track of the number of collapsed lines per line position.
+    let stackedLines = [];
+
+    for (let i = 0, line; (line = lines[i++]);) {
+      let linePos = line.start;
+      let negativeLineNumber = line.negativeNumber;
+
+      // Don't render any negative line number greater than -1.
+      if (negativeLineNumber == 0) {
+        break;
+      }
+
+      // Check for overlapping lines. We render a second box beneath the last overlapping
+      // line number to indicate there are lines beneath it.
+      const gridLine = tracks[line.number - 1];
+
+      if (gridLine) {
+        const { breadth }  = gridLine;
+
+        if (breadth === 0) {
+          stackedLines.push(negativeLineNumber);
+
+          if (stackedLines.length > 0) {
+            this.renderGridLineNumber(negativeLineNumber, linePos, startPos,
+              line.breadth, dimensionType, 1);
+          }
+
+          continue;
+        }
+      }
+
+      // For negative line numbers, we want to display the smallest
+      // value at the front of the stack.
+      if (stackedLines.length) {
+        negativeLineNumber = stackedLines[0];
+        stackedLines = [];
+      }
+
+      this.renderGridLineNumber(negativeLineNumber, linePos, startPos, line.breadth,
+        dimensionType);
+    }
+  }
+
+  /**
    * Render the grid line number on the css grid highlighter canvas.
    *
    * @param  {Number} lineNumber
    *         The grid line number.
    * @param  {Number} linePos
    *         The line position along the x-axis for a column grid line and
    *         y-axis for a row grid line.
    * @param  {Number} startPos
@@ -1202,50 +1181,49 @@ class CssGridHighlighter extends AutoRef
    * @param  {Number||undefined} stackedLineIndex
    *         The line index position of the stacked line.
    */
   renderGridLineNumber(lineNumber, linePos, startPos, breadth, dimensionType,
     stackedLineIndex) {
     let displayPixelRatio = getDisplayPixelRatio(this.win);
     let { devicePixelRatio } = this.win;
     let offset = (displayPixelRatio / 2) % 1;
+    let fontSize = GRID_FONT_SIZE * displayPixelRatio;
+    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
+    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
 
     linePos = Math.round(linePos);
     startPos = Math.round(startPos);
     breadth = Math.round(breadth);
 
     if (linePos + breadth < 0) {
-      // The line is not visible on screen, don't render the line number
+      // Don't render the line number since the line is not visible on screen.
       return;
     }
 
     this.ctx.save();
-    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
-    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
     this.ctx.translate(offset - canvasX, offset - canvasY);
-
-    let fontSize = (GRID_FONT_SIZE * displayPixelRatio);
     this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY;
 
     // For a general grid box, the height of the character "m" will be its minimum width
-    // and height. If line number's text width is greater then grid box's text width
-    // will use that instead.
+    // and height. If line number's text width is greater, then use the grid box's text
+    // width instead.
     let textHeight = this.ctx.measureText("m").width;
     let textWidth = Math.max(textHeight, this.ctx.measureText(lineNumber).width);
 
     // Padding in pixels for the line number text inside of the line number container.
     let padding = 3 * displayPixelRatio;
     let offsetFromEdge = 2 * displayPixelRatio;
 
     let boxWidth = textWidth + 2 * padding;
     let boxHeight = textHeight + 2 * padding;
 
-     // Calculate the x & y coordinates for the line number container, so that its arrow
-     // tip is centered on the line (or the gap if there is one), and is offset by the
-     // calculated padding value from the grid container edge.
+    // Calculate the x & y coordinates for the line number container, so that its arrow
+    // tip is centered on the line (or the gap if there is one), and is offset by the
+    // calculated padding value from the grid container edge.
     let x, y;
 
     if (dimensionType === COLUMNS) {
       x = linePos + breadth / 2;
       y = startPos;
 
       if (lineNumber > 0) {
         y -= offsetFromEdge;
@@ -1261,17 +1239,17 @@ class CssGridHighlighter extends AutoRef
       } else {
         x += offsetFromEdge;
       }
     }
 
     [x, y] = apply(this.currentMatrix, [x, y]);
 
     if (stackedLineIndex) {
-      // Offset the stacked line number by half of the box's width/height
+      // Offset the stacked line number by half of the box's width/height.
       const xOffset = boxWidth / 4;
       const yOffset = boxHeight / 4;
 
       if (lineNumber > 0) {
         x -= xOffset;
         y -= yOffset;
       } else {
         x += xOffset;
@@ -1286,17 +1264,17 @@ class CssGridHighlighter extends AutoRef
 
     // Draw a bubble rectanglular arrow with a border width of 2 pixels, a border color
     // matching the grid color and a white background (the line number will be written in
     // black).
     this.ctx.lineWidth = 2 * displayPixelRatio;
     this.ctx.strokeStyle = this.color;
     this.ctx.fillStyle = "white";
 
-    // See param definitions of drawBubbleRect
+    // See param definitions of drawBubbleRect.
     let radius = 2 * displayPixelRatio;
     let margin = 2 * displayPixelRatio;
     let arrowSize = 8 * displayPixelRatio;
 
     let minBoxSize = arrowSize * 2 + padding;
     boxWidth = Math.max(boxWidth, minBoxSize);
     boxHeight = Math.max(boxHeight, minBoxSize);
 
@@ -1325,17 +1303,16 @@ class CssGridHighlighter extends AutoRef
     }
 
     // Write the line number inside of the rectangle.
     this.ctx.textAlign = "center";
     this.ctx.textBaseline = "middle";
     this.ctx.fillStyle = "black";
     const numberText = stackedLineIndex ? "" : lineNumber;
     this.ctx.fillText(numberText, x, y);
-
     this.ctx.restore();
   }
 
   /**
    * Render the grid gap area on the css grid highlighter canvas.
    *
    * @param  {Number} linePos
    *         The line position along the x-axis for a column grid line and
@@ -1348,17 +1325,16 @@ class CssGridHighlighter extends AutoRef
    *         The grid line breadth value.
    * @param  {String} dimensionType
    *         The grid dimension type which is either the constant COLUMNS or ROWS.
    */
   renderGridGap(linePos, startPos, endPos, breadth, dimensionType) {
     let { devicePixelRatio } = this.win;
     let displayPixelRatio = getDisplayPixelRatio(this.win);
     let offset = (displayPixelRatio / 2) % 1;
-
     let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
     let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
 
     linePos = Math.round(linePos);
     startPos = Math.round(startPos);
     breadth = Math.round(breadth);
 
     this.ctx.save();
@@ -1379,31 +1355,32 @@ class CssGridHighlighter extends AutoRef
         endPos = Math.round(endPos);
       } else {
         endPos = this._winDimensions.width;
         startPos = -endPos;
       }
       drawRect(this.ctx, startPos, linePos, endPos, linePos + breadth,
         this.currentMatrix);
     }
+
     this.ctx.fill();
     this.ctx.restore();
   }
 
   /**
    * Render the grid area highlight for the given area name or for all the grid areas.
    *
    * @param  {String} areaName
    *         Name of the grid area to be highlighted. If no area name is provided, all
    *         the grid areas should be highlighted.
    */
   renderGridArea(areaName) {
-    let paths = [];
     let { devicePixelRatio } = this.win;
     let displayPixelRatio = getDisplayPixelRatio(this.win);
+    let paths = [];
 
     for (let i = 0; i < this.gridData.length; i++) {
       let fragment = this.gridData[i];
 
       for (let area of fragment.areas) {
         if (areaName && areaName != area.name) {
           continue;
         }
@@ -1475,17 +1452,16 @@ class CssGridHighlighter extends AutoRef
 
     let x1 = column.start;
     let y1 = row.start;
     let x2 = column.start + column.breadth;
     let y2 = row.start + row.breadth;
 
     let { devicePixelRatio } = this.win;
     let displayPixelRatio = getDisplayPixelRatio(this.win);
-
     let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix);
 
     // Scale down by `devicePixelRatio` since SVG element already take them into account.
     let svgPoints = points.map(point => ({
       x: Math.round(point.x / devicePixelRatio),
       y: Math.round(point.y / devicePixelRatio)
     }));