Bug 1348919 - forced grid cell infobar and area infobar position; r=gl draft
authorMatteo Ferretti <mferretti@mozilla.com>
Mon, 15 May 2017 13:54:42 +0200
changeset 577794 2b28f163010d30f562fe39a3a6bc9f0c586ff68a
parent 577793 54af3ab740d0723620f6e2b01d5c98e8f3a17b12
child 628591 4ee6db1be3cf4ea57536377201920c9f81f51474
push id58789
push userbmo:zer0@mozilla.com
push dateMon, 15 May 2017 11:56:57 +0000
reviewersgl
bugs1348919
milestone55.0a1
Bug 1348919 - forced grid cell infobar and area infobar position; r=gl The infobars needs to be not hidden when the `moveInfobar` function is called, otherwise the computed style won't work as expected; therefore I switched the functions' call related to that. I added an optional `options` argument to the `moveInfobar` function in order to support forced position and make the infobar hide if offscreen. MozReview-Commit-ID: 81dxkMGt7vT
devtools/server/actors/highlighters/css-grid.js
devtools/server/actors/highlighters/utils/markup.js
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -802,32 +802,38 @@ CssGridHighlighter.prototype = extend(Au
     let dim = parseFloat(width.toPrecision(6)) +
               " \u00D7 " +
               parseFloat(height.toPrecision(6));
 
     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(container, x1, x2, y1, y2, {
+      position: "bottom",
+      hideIfOffscreen: true
+    });
   },
 
   _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);
+    this._moveInfobar(container, x1, x2, y1, y2, {
+      position: "top",
+      hideIfOffscreen: true
+    });
   },
 
   /**
    * Update the grid information displayed in the grid line info bar.
    *
    * @param  {String} gridLineNames
    *         Comma-separated string of names for the grid line.
    * @param  {Number} gridLineNumber
@@ -852,29 +858,29 @@ CssGridHighlighter.prototype = extend(Au
    *         The first x-coordinate of the grid rectangle.
    * @param  {Number} x2
    *         The second x-coordinate of the grid rectangle.
    * @param  {Number} y1
    *         The first y-coordinate of the grid rectangle.
    * @param  {Number} y2
    *         The second y-coordinate of the grid rectangle.
    */
-  _moveInfobar(container, x1, x2, y1, y2) {
+  _moveInfobar(container, x1, x2, y1, y2, options) {
     let bounds = {
       bottom: y2,
       height: y2 - y1,
       left: x1,
       right: x2,
       top: y1,
       width: x2 - x1,
       x: x1,
       y: y1,
     };
 
-    moveInfobar(container, bounds, this.win);
+    moveInfobar(container, bounds, this.win, options);
   },
 
   /**
    * The <canvas>'s position needs to be updated if the page scrolls too much, in order
    * to give the illusion that it always covers the viewport.
    */
   _scrollUpdate() {
     let hasPositionChanged = this.calculateCanvasPosition();
@@ -1375,18 +1381,18 @@ 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._showGridAreaInfoBar();
           this._updateGridAreaInfobar(area, x1, x2, y1, y2);
-          this._showGridAreaInfoBar();
         }
       }
     }
 
     let areas = this.getElement("areas");
     areas.setAttribute("d", paths.join(" "));
   },
 
@@ -1425,18 +1431,18 @@ CssGridHighlighter.prototype = extend(Au
 
     let path = "M" + x1 + "," + y1 + " " +
                "L" + x2 + "," + y1 + " " +
                "L" + x2 + "," + y2 + " " +
                "L" + x1 + "," + y2;
     let cells = this.getElement("cells");
     cells.setAttribute("d", path);
 
+    this._showGridCellInfoBar();
     this._updateGridCellInfobar(rowNumber, columnNumber, x1, x2, y1, y2);
-    this._showGridCellInfoBar();
   },
 
   /**
    * Render the grid line name highlight for the given grid fragment index, lineNumber,
    * and dimensionType.
    *
    * @param  {Number} gridFragmentIndex
    *         Index of the grid fragment to render the grid line highlight.
@@ -1474,18 +1480,18 @@ CssGridHighlighter.prototype = extend(Au
     let x = dimensionType === COLUMNS
       ? linePos.start + (bounds.left / currentZoom)
       : colXPosition.start + (bounds.left / currentZoom);
 
     let y = dimensionType === ROWS
       ? linePos.start + (bounds.top / currentZoom)
       : rowYPosition.start + (bounds.top / currentZoom);
 
+    this._showGridLineInfoBar();
     this._updateGridLineInfobar(names.join(", "), lineNumber, x, y);
-    this._showGridLineInfoBar();
   },
 
   /**
    * Hide the highlighter, the canvas and the infobars.
    */
   _hide() {
     setIgnoreLayoutChanges(true);
     this._hideGrid();
--- a/devtools/server/actors/highlighters/utils/markup.js
+++ b/devtools/server/actors/highlighters/utils/markup.js
@@ -558,63 +558,72 @@ exports.CanvasFrameAnonymousContentHelpe
  * to horizontally center align with the container element if possible.
  *
  * @param  {DOMNode} container
  *         The container element which will be used to position the infobar.
  * @param  {Object} bounds
  *         The content bounds of the container element.
  * @param  {Window} win
  *         The window object.
+ * @param  {Object} [options={}]
+ *         Advanced options for the infobar.
+ * @param  {String} options.position
+ *         Force the infobar to be displayed either on "top" or "bottom". Any other value
+ *         will be ingnored.
+ * @param  {Boolean} options.hideIfOffscreen
+ *         If set to `true`, hides the infobar if it's offscreen, instead of automatically
+ *         reposition it.
  */
-function moveInfobar(container, bounds, win) {
+function moveInfobar(container, bounds, win, options = {}) {
   let zoom = getCurrentZoom(win);
   let viewport = getViewportDimensions(win);
 
   let { computedStyle } = container;
 
-  // To simplify, we use the same arrow's size value as margin's value for all four sides.
-  let margin = parseFloat(computedStyle
+  let margin = 2;
+  let arrowSize = parseFloat(computedStyle
                               .getPropertyValue("--highlighter-bubble-arrow-size"));
   let containerHeight = parseFloat(computedStyle.getPropertyValue("height"));
   let containerWidth = parseFloat(computedStyle.getPropertyValue("width"));
   let containerHalfWidth = containerWidth / 2;
 
   let viewportWidth = viewport.width * zoom;
   let viewportHeight = viewport.height * zoom;
   let { pageXOffset, pageYOffset } = win;
 
   pageYOffset *= zoom;
   pageXOffset *= zoom;
-  containerHeight += margin;
 
   // Defines the boundaries for the infobar.
   let topBoundary = margin;
-  let bottomBoundary = viewportHeight - containerHeight;
+  let bottomBoundary = viewportHeight - containerHeight - margin - 1;
   let leftBoundary = containerHalfWidth + margin;
   let rightBoundary = viewportWidth - containerHalfWidth - margin;
 
   // Set the default values.
-  let top = bounds.y - containerHeight;
-  let bottom = bounds.bottom + margin;
+  let top = bounds.y - containerHeight - arrowSize;
+  let bottom = bounds.bottom + margin + arrowSize;
   let left = bounds.x + bounds.width / 2;
   let isOverlapTheNode = false;
   let positionAttribute = "top";
   let position = "absolute";
 
   // Here we start the math.
   // We basically want to position absolutely the infobar, except when is pointing to a
   // node that is offscreen or partially offscreen, in a way that the infobar can't
   // be placed neither on top nor on bottom.
   // In such cases, the infobar will overlap the node, and to limit the latency given
   // by APZ (See Bug 1312103) it will be positioned as "fixed".
   // It's a sort of "position: sticky" (but positioned as absolute instead of relative).
   let canBePlacedOnTop = top >= pageYOffset;
   let canBePlacedOnBottom = bottomBoundary + pageYOffset - bottom > 0;
+  let forcedOnTop = options.position === "top";
+  let forcedOnBottom = options.position === "bottom";
 
-  if (!canBePlacedOnTop && canBePlacedOnBottom) {
+  if ((!canBePlacedOnTop && canBePlacedOnBottom && !forcedOnTop) || forcedOnBottom) {
     top = bottom;
     positionAttribute = "bottom";
   }
 
   let isOffscreenOnTop = top < topBoundary + pageYOffset;
   let isOffscreenOnBottom = top > bottomBoundary + pageYOffset;
   let isOffscreenOnLeft = left < leftBoundary + pageXOffset;
   let isOffscreenOnRight = left > rightBoundary + pageXOffset;
@@ -625,17 +634,20 @@ function moveInfobar(container, bounds, 
   } else if (isOffscreenOnBottom) {
     top = bottomBoundary;
     isOverlapTheNode = true;
   } else if (isOffscreenOnLeft || isOffscreenOnRight) {
     isOverlapTheNode = true;
     top -= pageYOffset;
   }
 
-  if (isOverlapTheNode) {
+  if (isOverlapTheNode && options.hideIfOffscreen) {
+    container.setAttribute("hidden", "true");
+    return;
+  } else if (isOverlapTheNode) {
     left = Math.min(Math.max(leftBoundary, left - pageXOffset), rightBoundary);
 
     position = "fixed";
     container.setAttribute("hide-arrow", "true");
   } else {
     position = "absolute";
     container.removeAttribute("hide-arrow");
   }