Bug 1433897 - Implement hasMoved method in the flexbox highlighter to update the highlighter on changes. r=pbro draft
authorGabriel Luong <gabriel.luong@gmail.com>
Sun, 22 Apr 2018 19:10:27 -0400
changeset 786350 e958a6095e3b0c3e05f58c3dbe8ea82234e1b1b2
parent 786252 6794d2faf47c71da2f28e299e596b3f9808f490f
push id107437
push userbmo:gl@mozilla.com
push dateSun, 22 Apr 2018 23:29:20 +0000
reviewerspbro
bugs1433897
milestone61.0a1
Bug 1433897 - Implement hasMoved method in the flexbox highlighter to update the highlighter on changes. r=pbro MozReview-Commit-ID: IeCt9fvG7cr
devtools/server/actors/highlighters/flexbox.js
--- a/devtools/server/actors/highlighters/flexbox.js
+++ b/devtools/server/actors/highlighters/flexbox.js
@@ -251,29 +251,67 @@ class FlexboxHighlighter extends AutoRef
     gCachedFlexboxPattern.set(devicePixelRatio, flexboxPatternMap);
 
     return pattern;
   }
 
   /**
    * The AutoRefreshHighlighter's _hasMoved method returns true only if the
    * element's quads have changed. Override it so it also returns true if the
-   * element and its flex items have changed.
+   * flex container and its flex items have changed.
    */
   _hasMoved() {
     let hasMoved = AutoRefreshHighlighter.prototype._hasMoved.call(this);
 
-    // TODO: Implement a check of old and new flex container and flex items to react
-    // to any alignment and size changes. This is blocked on Bug 1414920 that implements
-    // a platform API to retrieve the flex container and flex item information.
+    if (!this.computedStyle) {
+      this.computedStyle = getComputedStyle(this.currentNode);
+    }
+
+    let oldFlexData = this.flexData;
+    this.flexData = this.currentNode.getAsFlexContainer();
+    let hasFlexDataChanged = compareFlexData(oldFlexData, this.flexData);
+
+    let oldAlignItems = this.alignItemsValue;
+    this.alignItemsValue = this.computedStyle.alignItems;
+    let newAlignItems = this.alignItemsValue;
+
+    let oldFlexBasis = this.flexBasis;
+    this.flexBasis = this.computedStyle.flexBasis;
+    let newFlexBasis = this.flexBasis;
 
-    return hasMoved;
+    let oldFlexDirection = this.flexDirection;
+    this.flexDirection = this.computedStyle.flexDirection;
+    let newFlexDirection = this.flexDirection;
+
+    let oldFlexWrap = this.flexWrap;
+    this.flexWrap = this.computedStyle.flexWrap;
+    let newFlexWrap = this.flexWrap;
+
+    let oldJustifyContent = this.justifyContentValue;
+    this.justifyContentValue = this.computedStyle.justifyContent;
+    let newJustifyContent = this.justifyContentValue;
+
+    return hasMoved ||
+           hasFlexDataChanged ||
+           oldAlignItems !== newAlignItems ||
+           oldFlexBasis !== newFlexBasis ||
+           oldFlexDirection !== newFlexDirection ||
+           oldFlexWrap !== newFlexWrap ||
+           oldJustifyContent !== newJustifyContent;
   }
 
   _hide() {
+    this.alignItemsValue = null;
+    this.computedStyle = null;
+    this.flexBasis = null;
+    this.flexData = null;
+    this.flexDirection = null;
+    this.flexWrap = null;
+    this.justifyContentValue = null;
+
     setIgnoreLayoutChanges(true);
     this._hideFlexbox();
     setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
   }
 
   _hideFlexbox() {
     this.getElement("canvas").setAttribute("hidden", "true");
   }
@@ -319,43 +357,41 @@ class FlexboxHighlighter extends AutoRef
     this.clearCache();
 
     if (isTopLevel) {
       this.hide();
     }
   }
 
   renderAlignItemLine() {
-    if (!this.currentQuads.content || !this.currentQuads.content[0]) {
+    if (!this.flexData || !this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
     let { devicePixelRatio } = this.win;
     let lineWidth = getDisplayPixelRatio(this.win);
     let offset = (lineWidth / 2) % 1;
     let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
     let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
 
     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 = DEFAULT_COLOR;
 
     let { bounds } = this.currentQuads.content[0];
-    let flexLines = this.currentNode.getAsFlexContainer().getLines();
-    let computedStyle = getComputedStyle(this.currentNode);
-    let alignItemsType = computedStyle.getPropertyValue("align-items");
-    let isColumn = computedStyle.getPropertyValue("flex-direction").startsWith("column");
+    let flexLines = this.flexData.getLines();
+    let isColumn = this.flexDirection.startsWith("column");
     let options = { matrix: this.currentMatrix };
 
     for (let flexLine of flexLines) {
       let { crossStart, crossSize } = flexLine;
 
-      switch (alignItemsType) {
+      switch (this.alignItemsValue) {
         case "baseline":
         case "first baseline":
           let { firstBaselineOffset } = flexLine;
 
           if (firstBaselineOffset < 0) {
             break;
           }
 
@@ -482,55 +518,53 @@ class FlexboxHighlighter extends AutoRef
     this.ctx.stroke();
     this.ctx.restore();
   }
 
   /**
    * Renders the flex basis for a given flex item.
    */
   renderFlexItemBasis(flexItem, left, top, right, bottom, boundsWidth) {
-    let computedStyle = getComputedStyle(flexItem);
-
-    if (!computedStyle) {
+    if (!this.computedStyle) {
       return;
     }
 
-    let basis = computedStyle.getPropertyValue("flex-basis");
+    let basis = this.flexBasis;
 
     if (basis.endsWith("px")) {
       right = Math.round(left + parseFloat(basis));
     } else if (basis.endsWith("%")) {
       basis = parseFloat(basis) / 100 * boundsWidth;
       right = Math.round(left + basis);
     }
 
     this.ctx.fillStyle = BASIS_FILL_COLOR;
     drawRect(this.ctx, left, top, right, bottom, this.currentMatrix);
     this.ctx.fill();
   }
 
   renderFlexItems() {
-    if (!this.currentQuads.content || !this.currentQuads.content[0]) {
+    if (!this.flexData || !this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
     let { devicePixelRatio } = this.win;
     let lineWidth = getDisplayPixelRatio(this.win);
     let offset = (lineWidth / 2) % 1;
     let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
     let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
 
     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 = DEFAULT_COLOR;
 
     let { bounds } = this.currentQuads.content[0];
-    let flexLines = this.currentNode.getAsFlexContainer().getLines();
+    let flexLines = this.flexData.getLines();
 
     for (let flexLine of flexLines) {
       let flexItems = flexLine.getItems();
 
       for (let flexItem of flexItems) {
         let { node } = flexItem;
         let quads = getAdjustedQuads(this.win, node, "border");
 
@@ -552,35 +586,34 @@ class FlexboxHighlighter extends AutoRef
         this.renderFlexItemBasis(node, left, top, right, bottom, bounds.width);
       }
     }
 
     this.ctx.restore();
   }
 
   renderFlexLines() {
-    if (!this.currentQuads.content || !this.currentQuads.content[0]) {
+    if (!this.flexData || !this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
     let { devicePixelRatio } = this.win;
     let lineWidth = getDisplayPixelRatio(this.win);
     let offset = (lineWidth / 2) % 1;
     let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
     let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
 
     this.ctx.save();
     this.ctx.translate(offset - canvasX, offset - canvasY);
     this.ctx.lineWidth = lineWidth;
     this.ctx.strokeStyle = DEFAULT_COLOR;
 
     let { bounds } = this.currentQuads.content[0];
-    let flexLines = this.currentNode.getAsFlexContainer().getLines();
-    let computedStyle = getComputedStyle(this.currentNode);
-    let isColumn = computedStyle.getPropertyValue("flex-direction").startsWith("column");
+    let flexLines = this.flexData.getLines();
+    let isColumn = this.flexDirection.startsWith("column");
     let options = { matrix: this.currentMatrix };
 
     for (let flexLine of flexLines) {
       let { crossStart, crossSize } = flexLine;
 
       if (isColumn) {
         clearRect(this.ctx, crossStart, 0, crossStart + crossSize, bounds.height,
           this.currentMatrix);
@@ -615,24 +648,23 @@ class FlexboxHighlighter extends AutoRef
         }
       }
     }
 
     this.ctx.restore();
   }
 
   renderJustifyContent() {
-    if (!this.currentQuads.content || !this.currentQuads.content[0]) {
+    if (!this.flexData || !this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
     let { bounds } = this.currentQuads.content[0];
-    let flexLines = this.currentNode.getAsFlexContainer().getLines();
-    let computedStyle = getComputedStyle(this.currentNode);
-    let isColumn = computedStyle.getPropertyValue("flex-direction").startsWith("column");
+    let flexLines = this.flexData.getLines();
+    let isColumn = this.flexDirection.startsWith("column");
 
     for (let flexLine of flexLines) {
       let { crossStart, crossSize } = flexLine;
       let flexItems = flexLine.getItems();
       let mainStart = 0;
 
       for (let flexItem of flexItems) {
         let { node } = flexItem;
@@ -703,9 +735,67 @@ class FlexboxHighlighter extends AutoRef
     root.setAttribute("style",
       `position: absolute; width: ${width}px; height: ${height}px; overflow: hidden`);
 
     setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
     return true;
   }
 }
 
+/**
+ * Returns whether or not the flex data has changed.
+ *
+ * @param  {Flex} oldFlexData
+ *         The old Flex data object.
+ * @param  {Flex} newFlexData
+ *         The new Flex data object.
+ * @return {Boolean} true if the flex data has changed and false otherwise.
+ */
+function compareFlexData(oldFlexData, newFlexData) {
+  if (!oldFlexData || !newFlexData) {
+    return true;
+  }
+
+  const oldLines = oldFlexData.getLines();
+  const newLines = newFlexData.getLines();
+
+  if (oldLines.length !== newLines.length) {
+    return true;
+  }
+
+  for (let i = 0; i < oldLines.length; i++) {
+    let oldLine = oldLines[i];
+    let newLine = newLines[i];
+
+    if (oldLine.crossSize !== newLine.crossSize ||
+        oldLine.crossStart !== newLine.crossStart ||
+        oldLine.firstBaselineOffset !== newLine.firstBaselineOffset ||
+        oldLine.growthState !== newLine.growthState ||
+        oldLine.lastBaselineOffset !== newLine.lastBaselineOffset) {
+      return true;
+    }
+
+    let oldItems = oldLine.getItems();
+    let newItems = newLine.getItems();
+
+    if (oldItems.length !== newItems.length) {
+      return true;
+    }
+
+    for (let j = 0; j < oldItems.length; j++) {
+      let oldItem = oldItems[j];
+      let newItem = newItems[j];
+
+      if (oldItem.crossMaxSize !== newItem.crossMaxSize ||
+          oldItem.crossMinSize !== newItem.crossMinSize ||
+          oldItem.mainBaseSize !== newItem.mainBaseSize ||
+          oldItem.mainDeltaSize !== newItem.mainDeltaSize ||
+          oldItem.mainMaxSize !== newItem.mainMaxSize ||
+          oldItem.mainMinSize !== newItem.mainMinSize) {
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
 exports.FlexboxHighlighter = FlexboxHighlighter;