Bug 1511426 - Flexbox highlighter fails to scroll when flexbox container is scrollable r=gl
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Thu, 17 Jan 2019 15:52:08 +0000
changeset 454291 ed843bb514f9e7c6c52914663d62d663f5658f19
parent 454290 560290f263515bb7c7bb834cde8f7d64d5bdde54
child 454292 c3d4685b58a5eabb34d6a04fa1cd4e7d5e53c7c9
push id35392
push userncsoregi@mozilla.com
push dateThu, 17 Jan 2019 21:53:28 +0000
treeherdermozilla-central@24982570fc83 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl
bugs1511426
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1511426 - Flexbox highlighter fails to scroll when flexbox container is scrollable r=gl Differential Revision: https://phabricator.services.mozilla.com/D13848
devtools/server/actors/highlighters/flexbox.js
--- a/devtools/server/actors/highlighters/flexbox.js
+++ b/devtools/server/actors/highlighters/flexbox.js
@@ -416,28 +416,20 @@ class FlexboxHighlighter extends AutoRef
   }
 
   renderAlignItemLine() {
     if (!this.options.showAlignment || !this.flexData ||
         !this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
-    const { devicePixelRatio } = this.win;
-    const lineWidth = getDisplayPixelRatio(this.win);
-    const offset = (lineWidth / 2) % 1;
-    const zoom = getCurrentZoom(this.win);
-    const canvasX = Math.round(this._canvasPosition.x * devicePixelRatio * zoom);
-    const canvasY = Math.round(this._canvasPosition.y * devicePixelRatio * zoom);
-
-    this.ctx.save();
-    this.ctx.translate(offset - canvasX, offset - canvasY);
-    this.ctx.setLineDash(FLEXBOX_LINES_PROPERTIES.alignItems.lineDash);
-    this.ctx.lineWidth = lineWidth * 3;
-    this.ctx.strokeStyle = this.color;
+    this.setupCanvas({
+      lineDash: FLEXBOX_LINES_PROPERTIES.alignItems.lineDash,
+      lineWidthMultiplier: 3,
+    });
 
     const { bounds } = this.currentQuads.content[0];
     const isColumn = this.flexDirection.startsWith("column");
     const options = { matrix: this.currentMatrix };
 
     for (const flexLine of this.flexData.lines) {
       const { crossStart, crossSize } = flexLine;
 
@@ -518,31 +510,25 @@ class FlexboxHighlighter extends AutoRef
   }
 
   renderFlexContainer() {
     if (!this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
     const { devicePixelRatio } = this.win;
-    const lineWidth = getDisplayPixelRatio(this.win);
-    const offset = (lineWidth / 2) % 1;
-    const zoom = getCurrentZoom(this.win);
-    const canvasX = Math.round(this._canvasPosition.x * devicePixelRatio * zoom);
-    const canvasY = Math.round(this._canvasPosition.y * devicePixelRatio * zoom);
     const containerQuad = getUntransformedQuad(this.currentNode, "content");
     const { width, height } = containerQuad.getBounds();
 
-    this.ctx.save();
-    this.ctx.translate(offset - canvasX, offset - canvasY);
-    this.ctx.setLineDash(FLEXBOX_LINES_PROPERTIES.edge.lineDash);
-    this.ctx.lineWidth = lineWidth * 2;
-    this.ctx.strokeStyle = this.color;
+    this.setupCanvas({
+      lineDash: FLEXBOX_LINES_PROPERTIES.alignItems.lineDash,
+      lineWidthMultiplier: 2,
+    });
+
     this.ctx.fillStyle = this.getFlexContainerPattern(devicePixelRatio);
-
     drawRect(this.ctx, 0, 0, width, height, this.currentMatrix);
 
     // Find current angle of outer flex element by measuring the angle of two arbitrary
     // points, then rotate canvas, so the hash pattern stays 45deg to the boundary.
     const p1 = apply(this.currentMatrix, [0, 0]);
     const p2 = apply(this.currentMatrix, [1, 0]);
     const angleRad = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]);
     this.ctx.rotate(angleRad);
@@ -552,28 +538,19 @@ class FlexboxHighlighter extends AutoRef
     this.ctx.restore();
   }
 
   renderFlexItems() {
     if (!this.flexData || !this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
-    const { devicePixelRatio } = this.win;
-    const lineWidth = getDisplayPixelRatio(this.win);
-    const offset = (lineWidth / 2) % 1;
-    const zoom = getCurrentZoom(this.win);
-    const canvasX = Math.round(this._canvasPosition.x * devicePixelRatio * zoom);
-    const canvasY = Math.round(this._canvasPosition.y * devicePixelRatio * zoom);
-
-    this.ctx.save();
-    this.ctx.translate(offset - canvasX, offset - canvasY);
-    this.ctx.setLineDash(FLEXBOX_LINES_PROPERTIES.item.lineDash);
-    this.ctx.lineWidth = lineWidth;
-    this.ctx.strokeStyle = this.color;
+    this.setupCanvas({
+      lineDash: FLEXBOX_LINES_PROPERTIES.item.lineDash,
+    });
 
     for (const flexLine of this.flexData.lines) {
       for (const flexItem of flexLine.items) {
         const { left, top, right, bottom } = flexItem.rect;
 
         clearRect(this.ctx, left, top, right, bottom, this.currentMatrix);
         drawRect(this.ctx, left, top, right, bottom, this.currentMatrix);
         this.ctx.stroke();
@@ -583,89 +560,83 @@ class FlexboxHighlighter extends AutoRef
     this.ctx.restore();
   }
 
   renderFlexLines() {
     if (!this.flexData || !this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
-    const { devicePixelRatio } = this.win;
     const lineWidth = getDisplayPixelRatio(this.win);
-    const offset = (lineWidth / 2) % 1;
-    const zoom = getCurrentZoom(this.win);
-    const canvasX = Math.round(this._canvasPosition.x * devicePixelRatio * zoom);
-    const canvasY = Math.round(this._canvasPosition.y * devicePixelRatio * zoom);
-    const containerQuad = getUntransformedQuad(this.currentNode, "content");
-    const { width, height } = containerQuad.getBounds();
     const options = { matrix: this.currentMatrix };
+    const { width: containerWidth, height: containerHeight } =
+      getUntransformedQuad(this.currentNode, "content").getBounds();
 
-    this.ctx.save();
-    this.ctx.translate(offset - canvasX, offset - canvasY);
-    this.ctx.lineWidth = lineWidth;
-    this.ctx.strokeStyle = this.color;
+    this.setupCanvas({
+      useScrollOffsets: true,
+    });
 
     for (const flexLine of this.flexData.lines) {
       const { crossStart, crossSize } = flexLine;
 
       switch (this.axes) {
         case "horizontal-lr vertical-tb":
         case "horizontal-lr vertical-bt":
         case "horizontal-rl vertical-tb":
         case "horizontal-rl vertical-bt":
-          clearRect(this.ctx, 0, crossStart, width, crossStart + crossSize,
+          clearRect(this.ctx, 0, crossStart, containerWidth, crossStart + crossSize,
             this.currentMatrix);
 
           // Avoid drawing the start flex line when they overlap with the flex container.
           if (crossStart != 0) {
-            drawLine(this.ctx, 0, crossStart, width, crossStart, options);
+            drawLine(this.ctx, 0, crossStart, containerWidth, crossStart, options);
             this.ctx.stroke();
           }
 
           // Avoid drawing the end flex line when they overlap with the flex container.
-          if (crossStart + crossSize < height - lineWidth * 2) {
-            drawLine(this.ctx, 0, crossStart + crossSize, width,
+          if (crossStart + crossSize < containerHeight - lineWidth * 2) {
+            drawLine(this.ctx, 0, crossStart + crossSize, containerWidth,
               crossStart + crossSize, options);
             this.ctx.stroke();
           }
           break;
         case "vertical-tb horizontal-lr":
         case "vertical-bt horizontal-rl":
-          clearRect(this.ctx, crossStart, 0, crossStart + crossSize, height,
+          clearRect(this.ctx, crossStart, 0, crossStart + crossSize, containerHeight,
             this.currentMatrix);
 
           // Avoid drawing the start flex line when they overlap with the flex container.
           if (crossStart != 0) {
-            drawLine(this.ctx, crossStart, 0, crossStart, height, options);
+            drawLine(this.ctx, crossStart, 0, crossStart, containerHeight, options);
             this.ctx.stroke();
           }
 
           // Avoid drawing the end flex line when they overlap with the flex container.
-          if (crossStart + crossSize < width - lineWidth * 2) {
+          if (crossStart + crossSize < containerWidth - lineWidth * 2) {
             drawLine(this.ctx, crossStart + crossSize, 0, crossStart + crossSize,
-              height, options);
+              containerHeight, options);
             this.ctx.stroke();
           }
           break;
         case "vertical-bt horizontal-lr":
         case "vertical-tb horizontal-rl":
-          clearRect(this.ctx, width - crossStart, 0, width - crossStart - crossSize,
-            height, this.currentMatrix);
+          clearRect(this.ctx, containerWidth - crossStart, 0,
+            containerWidth - crossStart - crossSize, containerHeight, this.currentMatrix);
 
           // Avoid drawing the start flex line when they overlap with the flex container.
           if (crossStart != 0) {
-            drawLine(this.ctx, width - crossStart, 0, width - crossStart, height,
-              options);
+            drawLine(this.ctx, containerWidth - crossStart, 0,
+              containerWidth - crossStart, containerHeight, options);
             this.ctx.stroke();
           }
 
           // Avoid drawing the end flex line when they overlap with the flex container.
-          if (crossStart + crossSize < width - lineWidth * 2) {
-            drawLine(this.ctx, width - crossStart - crossSize, 0,
-              width - crossStart - crossSize, height, options);
+          if (crossStart + crossSize < containerWidth - lineWidth * 2) {
+            drawLine(this.ctx, containerWidth - crossStart - crossSize, 0,
+              containerWidth - crossStart - crossSize, containerHeight, options);
             this.ctx.stroke();
           }
           break;
       }
     }
 
     this.ctx.restore();
   }
@@ -673,27 +644,25 @@ class FlexboxHighlighter extends AutoRef
   /**
    * Clear the whole alignment container along the main axis for each flex item.
    */
   renderJustifyContent() {
     if (!this.flexData || !this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
-    const containerQuad = getUntransformedQuad(this.currentNode, "content");
-    const containerBounds = containerQuad.getBounds();
-    const { width: containerWidth, height: containerHeight } = containerBounds;
+    const { width: containerWidth, height: containerHeight } =
+      getUntransformedQuad(this.currentNode, "content").getBounds();
 
-    const offset = (getDisplayPixelRatio(this.win) / 2) % 1;
-    const zoom = getCurrentZoom(this.win);
-    const canvasX = Math.round(this._canvasPosition.x * this.win.devicePixelRatio * zoom);
-    const canvasY = Math.round(this._canvasPosition.y * this.win.devicePixelRatio * zoom);
-
-    this.ctx.save();
-    this.ctx.translate(offset - canvasX, offset - canvasY);
+    this.setupCanvas({
+      lineDash: FLEXBOX_LINES_PROPERTIES.alignItems.lineDash,
+      offset: (getDisplayPixelRatio(this.win) / 2) % 1,
+      skipLineAndStroke: true,
+      useScrollOffsets: true,
+    });
 
     for (const flexLine of this.flexData.lines) {
       const { crossStart, crossSize } = flexLine;
       let mainStart = 0;
 
       // In these two situations mainStart goes from right to left so set it's
       // value as appropriate.
       if (this.axes === "horizontal-lr vertical-bt" ||
@@ -751,16 +720,76 @@ class FlexboxHighlighter extends AutoRef
             containerWidth - crossStart, containerHeight);
           break;
       }
     }
 
     this.ctx.restore();
   }
 
+  /**
+   * Set up the canvas with the given options prior to drawing.
+   *
+   * @param {String} [options.lineDash = null]
+   *        An Array of numbers that specify distances to alternately draw a
+   *        line and a gap (in coordinate space units). If the number of
+   *        elements in the array is odd, the elements of the array get copied
+   *        and concatenated. For example, [5, 15, 25] will become
+   *        [5, 15, 25, 5, 15, 25]. If the array is empty, the line dash list is
+   *        cleared and line strokes return to being solid.
+   *
+   *        We use the following constants here:
+   *          FLEXBOX_LINES_PROPERTIES.edge.lineDash,
+   *          FLEXBOX_LINES_PROPERTIES.item.lineDash
+   *          FLEXBOX_LINES_PROPERTIES.alignItems.lineDash
+   * @param {Number} [options.lineWidthMultiplier = 1]
+   *        The width of the line.
+   * @param {Number} [options.offset = `(displayPixelRatio / 2) % 1`]
+   *        The single line width used to obtain a crisp line.
+   * @param {Boolean} [options.skipLineAndStroke = false]
+   *        Skip the setting of lineWidth and strokeStyle.
+   * @param {Boolean} [options.useScrollOffsets = false]
+   *        Take scroll and zoom offsets into account. This is often needed
+   *        when working purely with mainStart, mainSize, crossStart and
+   *        crossSize because they do not take the scroll position into account.
+   */
+  setupCanvas({
+      lineDash = null,
+      lineWidthMultiplier = 1,
+      offset = (getDisplayPixelRatio(this.win) / 2) % 1,
+      skipLineAndStroke = false,
+      useScrollOffsets = false }) {
+    const { devicePixelRatio } = this.win;
+    const lineWidth = getDisplayPixelRatio(this.win);
+    const zoom = getCurrentZoom(this.win);
+
+    let offsetX = this._canvasPosition.x;
+    let offsetY = this._canvasPosition.y;
+
+    if (useScrollOffsets) {
+      offsetX += this.currentNode.scrollLeft / zoom;
+      offsetY += this.currentNode.scrollTop / zoom;
+    }
+
+    const canvasX = Math.round(offsetX * devicePixelRatio * zoom);
+    const canvasY = Math.round(offsetY * devicePixelRatio * zoom);
+
+    this.ctx.save();
+    this.ctx.translate(offset - canvasX, offset - canvasY);
+
+    if (lineDash) {
+      this.ctx.setLineDash(lineDash);
+    }
+
+    if (!skipLineAndStroke) {
+      this.ctx.lineWidth = lineWidth * lineWidthMultiplier;
+      this.ctx.strokeStyle = this.color;
+    }
+  }
+
   _update() {
     setIgnoreLayoutChanges(true);
 
     const root = this.getElement("root");
 
     // Hide the root element and force the reflow in order to get the proper window's
     // dimensions without increasing them.
     root.setAttribute("style", "display: none");
@@ -850,19 +879,26 @@ function getRectFromFlexItemValues(item,
   const rect = item.frameRect;
   const domRect = new DOMRect(rect.x, rect.y, rect.width, rect.height);
   const win = container.ownerGlobal;
   const style = win.getComputedStyle(container);
   const borderLeftWidth = parseInt(style.borderLeftWidth, 10) || 0;
   const borderTopWidth = parseInt(style.borderTopWidth, 10) || 0;
   const paddingLeft = parseInt(style.paddingLeft, 10) || 0;
   const paddingTop = parseInt(style.paddingTop, 10) || 0;
+  const scrollX = container.scrollLeft || 0;
+  const scrollY = container.scrollTop || 0;
 
-  domRect.x -= borderLeftWidth + paddingLeft;
-  domRect.y -= borderTopWidth + paddingTop;
+  domRect.x -= paddingLeft + scrollX;
+  domRect.y -= paddingTop + scrollY;
+
+  if (style.overflow === "visible" || style.overflow === "-moz-hidden-unscrollable") {
+    domRect.x -= borderLeftWidth;
+    domRect.y -= borderTopWidth;
+  }
 
   return domRect;
 }
 
 /**
  * Returns whether or not the flex data has changed.
  *
  * @param  {Flex} oldFlexData