Bug 1409970 - Part 2: Adds an initial FlexboxHighlighter boilerplate. r=pbro draft
authorGabriel Luong <gabriel.luong@gmail.com>
Tue, 24 Oct 2017 00:57:58 -0400
changeset 685215 ced7b52216bfd78b00300a72e4785d124c43d637
parent 685214 424deb603d57a795519cddfda81330f60c1abc34
child 737075 27174b78130a3e8c10b82061655e607294b17af3
push id85848
push userbmo:gl@mozilla.com
push dateTue, 24 Oct 2017 04:58:56 +0000
reviewerspbro
bugs1409970
milestone58.0a1
Bug 1409970 - Part 2: Adds an initial FlexboxHighlighter boilerplate. r=pbro MozReview-Commit-ID: GM5y5PiISjm
devtools/server/actors/highlighters.js
devtools/server/actors/highlighters/flexbox.js
devtools/server/actors/highlighters/moz.build
--- a/devtools/server/actors/highlighters.js
+++ b/devtools/server/actors/highlighters.js
@@ -702,15 +702,16 @@ HighlighterEnvironment.prototype = {
     this._tabActor = null;
     this._win = null;
   }
 };
 
 register("BoxModelHighlighter", "box-model");
 register("CssGridHighlighter", "css-grid");
 register("CssTransformHighlighter", "css-transform");
-register("SelectorHighlighter", "selector");
+register("EyeDropper", "eye-dropper");
+register("FlexboxHighlighter", "flexbox");
 register("GeometryEditorHighlighter", "geometry-editor");
-register("RulersHighlighter", "rulers");
 register("MeasuringToolHighlighter", "measuring-tool");
-register("EyeDropper", "eye-dropper");
 register("PausedDebuggerOverlay", "paused-debugger");
+register("RulersHighlighter", "rulers");
+register("SelectorHighlighter", "selector");
 register("ShapesHighlighter", "shapes");
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/highlighters/flexbox.js
@@ -0,0 +1,215 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { AutoRefreshHighlighter } = require("./auto-refresh");
+const {
+  getCanvasPosition,
+  getCurrentMatrix,
+  updateCanvasElement,
+} = require("./utils/canvas");
+const {
+  CanvasFrameAnonymousContentHelper,
+  createNode,
+} = require("./utils/markup");
+const {
+  setIgnoreLayoutChanges,
+} = require("devtools/shared/layout/utils");
+
+// We create a <canvas> element that has always 4096x4096 physical pixels, to displays
+// our flexbox's overlay.
+// Then, we move the element around when needed, to give the perception that it always
+// covers the screen (See bug 1345434).
+//
+// This canvas size value is the safest we can use because most GPUs can handle it.
+// It's also far from the maximum canvas memory allocation limit (4096x4096x4 is
+// 67.108.864 bytes, where the limit is 500.000.000 bytes, see:
+// http://searchfox.org/mozilla-central/source/gfx/thebes/gfxPrefs.h#401).
+//
+// Note:
+// Once bug 1232491 lands, we could try to refactor this code to use the values from
+// the displayport API instead.
+//
+// Using a fixed value should also solve bug 1348293.
+const CANVAS_SIZE = 4096;
+
+class FlexboxHighlighter extends AutoRefreshHighlighter {
+  constructor(highlighterEnv) {
+    super(highlighterEnv);
+
+    this.ID_CLASS_PREFIX = "flexbox-";
+
+    this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv,
+      this._buildMarkup.bind(this));
+
+    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
+    this._canvasPosition = {
+      x: 0,
+      y: 0
+    };
+
+    // Calling `getCanvasPosition` anyway since the highlighter could be initialized
+    // on a page that has scrolled already.
+    let { canvasX, canvasY } = getCanvasPosition(this._canvasPosition, this._scroll,
+      this.win, this._winDimensions);
+    this._canvasPosition.x = canvasX;
+    this._canvasPosition.y = canvasY;
+  }
+
+  _buildMarkup() {
+    let container = createNode(this.win, {
+      attributes: {
+        "class": "highlighter-container"
+      }
+    });
+
+    let root = createNode(this.win, {
+      parent: container,
+      attributes: {
+        "id": "root",
+        "class": "root"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    // We use a <canvas> element so that we can draw an arbitrary number of lines
+    // which wouldn't be possible with HTML or SVG without having to insert and remove
+    // the whole markup on every update.
+    createNode(this.win, {
+      parent: root,
+      nodeType: "canvas",
+      attributes: {
+        "id": "canvas",
+        "class": "canvas",
+        "hidden": "true",
+        "width": CANVAS_SIZE,
+        "height": CANVAS_SIZE
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    return container;
+  }
+
+  destroy() {
+    let { highlighterEnv } = this;
+    highlighterEnv.off("will-navigate", this.onWillNavigate);
+
+    let { pageListenerTarget } = highlighterEnv;
+    if (pageListenerTarget) {
+      pageListenerTarget.removeEventListener("pagehide", this.onPageHide);
+    }
+
+    this.markup.destroy();
+
+    AutoRefreshHighlighter.prototype.destroy.call(this);
+  }
+
+  get canvas() {
+    return this.getElement("canvas");
+  }
+
+  get ctx() {
+    return this.canvas.getCanvasContext("2d");
+  }
+
+  getElement(id) {
+    return this.markup.getElement(this.ID_CLASS_PREFIX + id);
+  }
+
+  _hide() {
+    setIgnoreLayoutChanges(true);
+    this._hideFlexbox();
+    setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
+  }
+
+  _hideFlexbox() {
+    this.getElement("canvas").setAttribute("hidden", "true");
+  }
+
+  /**
+   * 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 { hasUpdated } = getCanvasPosition(this._canvasPosition, this._scroll, this.win,
+      this._winDimensions);
+
+    if (hasUpdated) {
+      this._update();
+    }
+  }
+
+  _show() {
+    this._hide();
+    return this._update();
+  }
+
+  _showFlexbox() {
+    this.getElement("canvas").removeAttribute("hidden");
+  }
+
+  /**
+   * If a page hide event is triggered for current window's highlighter, hide the
+   * highlighter.
+   */
+  onPageHide({ target }) {
+    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
+   * next time.
+   */
+  onWillNavigate({ isTopLevel }) {
+    if (isTopLevel) {
+      this.hide();
+    }
+  }
+
+  _update() {
+    setIgnoreLayoutChanges(true);
+
+    let 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");
+    this.win.document.documentElement.offsetWidth;
+
+    let { width, height } = this._winDimensions;
+
+    // Updates the <canvas> element's position and size.
+    // It also clear the <canvas>'s drawing context.
+    updateCanvasElement(this.canvas, this._canvasPosition, this.win.devicePixelRatio);
+
+    // Update the current matrix used in our canvas' rendering
+    let { currentMatrix, hasNodeTransformations } = getCurrentMatrix(this.currentNode,
+      this.win);
+    this.currentMatrix = currentMatrix;
+    this.hasNodeTransformations = hasNodeTransformations;
+
+    this._showFlexbox();
+
+    root.setAttribute("style",
+      `position:absolute; width:${width}px;height:${height}px; overflow:hidden`);
+
+    setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
+    return true;
+  }
+}
+
+exports.FlexboxHighlighter = FlexboxHighlighter;
--- a/devtools/server/actors/highlighters/moz.build
+++ b/devtools/server/actors/highlighters/moz.build
@@ -9,16 +9,17 @@ DIRS += [
 ]
 
 DevToolsModules(
     'auto-refresh.js',
     'box-model.js',
     'css-grid.js',
     'css-transform.js',
     'eye-dropper.js',
+    'flexbox.js',
     'geometry-editor.js',
     'measuring-tool.js',
     'paused-debugger.js',
     'rulers.js',
     'selector.js',
     'shapes.js',
     'simple-outline.js'
 )