Bug 1409970 - Part 2: Adds an initial FlexboxHighlighter boilerplate. r=pbro
MozReview-Commit-ID: GM5y5PiISjm
--- 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'
)