--- a/devtools/server/actors/highlighters/flexbox.js
+++ b/devtools/server/actors/highlighters/flexbox.js
@@ -3,25 +3,28 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { AutoRefreshHighlighter } = require("./auto-refresh");
const {
CANVAS_SIZE,
DEFAULT_COLOR,
+ DEFAULT_FONT,
clearRect,
drawRect,
+ drawText,
getCurrentMatrix,
updateCanvasElement,
updateCanvasPosition,
} = require("./utils/canvas");
const {
CanvasFrameAnonymousContentHelper,
createNode,
+ getComputedStyle,
} = require("./utils/markup");
const {
getAdjustedQuads,
getDisplayPixelRatio,
setIgnoreLayoutChanges,
} = require("devtools/shared/layout/utils");
const FLEXBOX_LINES_PROPERTIES = {
@@ -34,16 +37,18 @@ const FLEXBOX_LINES_PROPERTIES = {
alpha: 1,
},
};
const FLEXBOX_CONTAINER_PATTERN_WIDTH = 14; // px
const FLEXBOX_CONTAINER_PATTERN_HEIGHT = 14; // px
const FLEXBOX_CONTAINER_PATTERN_LINE_DISH = [5, 3]; // px
+const FLEXBOX_TEXT_PADDING = 5;
+
/**
* Cached used by `FlexboxHighlighter.getFlexContainerPattern`.
*/
const gCachedFlexboxPattern = new Map();
const FLEXBOX = "flexbox";
class FlexboxHighlighter extends AutoRefreshHighlighter {
@@ -180,16 +185,27 @@ class FlexboxHighlighter extends AutoRef
ctx.stroke();
ctx.restore();
let pattern = ctx.createPattern(canvas, "repeat");
flexboxPatternMap.set(FLEXBOX, pattern);
gCachedFlexboxPattern.set(devicePixelRatio, flexboxPatternMap);
return pattern;
+}
+
+ /**
+ * Checks if the current node is a flexbox.
+ *
+ * @return {Boolean} true if the current node is a flexbox, false otherwise.
+ */
+ isFlexbox() {
+ let computedStyle = getComputedStyle(this.currentNode);
+ let display = computedStyle.getPropertyValue("display");
+ return display === "flex";
}
/**
* 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.
*/
_hasMoved() {
@@ -221,16 +237,21 @@ class FlexboxHighlighter extends AutoRef
this._winDimensions);
if (hasUpdated) {
this._update();
}
}
_show() {
+ if (!this.isFlexbox()) {
+ this.hide();
+ return false;
+ }
+
this._hide();
return this._update();
}
_showFlexbox() {
this.getElement("canvas").removeAttribute("hidden");
}
@@ -252,16 +273,117 @@ class FlexboxHighlighter extends AutoRef
onWillNavigate({ isTopLevel }) {
this.clearCache();
if (isTopLevel) {
this.hide();
}
}
+ renderFlexDirection() {
+ if (!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.globalAlpha = FLEXBOX_LINES_PROPERTIES.edge.alpha;
+ this.ctx.font = DEFAULT_FONT;
+ this.ctx.fillStyle = DEFAULT_COLOR;
+ this.ctx.strokeStyle = DEFAULT_COLOR;
+
+ let { bounds } = this.currentQuads.content[0];
+
+ let computedStyle = getComputedStyle(this.currentNode);
+ let direction = computedStyle.getPropertyValue("flex-direction");
+
+ if (direction === "row") {
+ this.renderFlexDirectionRow(bounds, offset);
+ } else if (direction === "column"){
+ this.renderFlexDirectionColumn(bounds, offset);
+ } else if (direction === "row-reverse") {
+ this.renderFlexDirectionRowReverse(bounds, offset);
+ } else {
+ this.renderFlexDirectionColumnReverse(bounds, offset);
+ }
+
+ this.ctx.restore();
+ }
+
+ renderFlexDirectionColumn(bounds, offset) {
+ this.ctx.save();
+
+ this.ctx.textAlign = "end";
+ drawText(this.ctx, "🡐 COLUMN", -FLEXBOX_TEXT_PADDING, 0, "vertical",
+ this.currentMatrix);
+ this.ctx.textAlign = "center";
+ drawText(this.ctx, "START", bounds.width / 2, -FLEXBOX_TEXT_PADDING,
+ "horizontal", this.currentMatrix);
+ this.ctx.textBaseline = "top";
+ drawText(this.ctx, "END", bounds.width / 2,
+ bounds.height + FLEXBOX_TEXT_PADDING + offset, "horizontal",
+ this.currentMatrix);
+
+ this.ctx.restore();
+ }
+
+ renderFlexDirectionColumnReverse(bounds, offset) {
+ this.ctx.save();
+
+ this.ctx.textAlign = "end";
+ drawText(this.ctx, "COLUMN 🡒", -FLEXBOX_TEXT_PADDING, 0, "vertical",
+ this.currentMatrix);
+ this.ctx.textAlign = "center";
+ drawText(this.ctx, "END", bounds.width / 2, -FLEXBOX_TEXT_PADDING,
+ "horizontal", this.currentMatrix);
+ this.ctx.textBaseline = "top";
+ drawText(this.ctx, "START", bounds.width / 2,
+ bounds.height + FLEXBOX_TEXT_PADDING + offset, "horizontal",
+ this.currentMatrix);
+
+ this.ctx.restore();
+ }
+
+ renderFlexDirectionRow(bounds, offset) {
+ this.ctx.save();
+
+ drawText(this.ctx, "ROW 🡒", 0, -FLEXBOX_TEXT_PADDING, "horizontal",
+ this.currentMatrix);
+ this.ctx.textAlign = "center";
+ drawText(this.ctx, "START", -FLEXBOX_TEXT_PADDING, bounds.height / 2,
+ "vertical", this.currentMatrix);
+ this.ctx.textBaseline = "top";
+ drawText(this.ctx, "END", bounds.width + FLEXBOX_TEXT_PADDING + offset,
+ bounds.height / 2, "vertical", this.currentMatrix);
+
+ this.ctx.restore();
+ }
+
+ renderFlexDirectionRowReverse(bounds, offset) {
+ this.ctx.save();
+
+ drawText(this.ctx, "🡐 ROW", 0, -FLEXBOX_TEXT_PADDING, "horizontal",
+ this.currentMatrix);
+ this.ctx.textAlign = "center";
+ drawText(this.ctx, "END", -FLEXBOX_TEXT_PADDING, bounds.height / 2,
+ "vertical", this.currentMatrix);
+ this.ctx.textBaseline = "top";
+ drawText(this.ctx, "START", bounds.width + FLEXBOX_TEXT_PADDING + offset,
+ bounds.height / 2, "vertical", this.currentMatrix);
+
+ this.ctx.restore();
+ }
+
renderFlexContainer() {
if (!this.currentQuads.content || !this.currentQuads.content[0]) {
return;
}
let { devicePixelRatio } = this.win;
let lineWidth = getDisplayPixelRatio(this.win);
let offset = (lineWidth / 2) % 1;
@@ -348,16 +470,17 @@ class FlexboxHighlighter extends AutoRef
// Update the current matrix used in our canvas' rendering
let { currentMatrix, hasNodeTransformations } = getCurrentMatrix(this.currentNode,
this.win);
this.currentMatrix = currentMatrix;
this.hasNodeTransformations = hasNodeTransformations;
this.renderFlexContainer();
+ this.renderFlexDirection();
this.renderFlexItems();
this._showFlexbox();
root.setAttribute("style",
`position: absolute; width: ${width}px; height: ${height}px; overflow: hidden`);
setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
--- a/devtools/server/actors/highlighters/utils/canvas.js
+++ b/devtools/server/actors/highlighters/utils/canvas.js
@@ -33,16 +33,19 @@ const { getViewportDimensions } = requir
// the displayport API instead.
//
// Using a fixed value should also solve bug 1348293.
const CANVAS_SIZE = 4096;
// The default color used for the canvas' font, fill and stroke colors.
const DEFAULT_COLOR = "#9400FF";
+// The default font used for the canvas' font.
+const DEFAULT_FONT = "10px sans-serif";
+
/**
* Draws a rect to the context given and applies a transformation matrix if passed.
* The coordinates are the start and end points of the rectangle's diagonal.
*
* @param {CanvasRenderingContext2D} ctx
* The 2D canvas context.
* @param {Number} x1
* The x-axis coordinate of the rectangle's diagonal start point.
@@ -225,16 +228,47 @@ function drawRoundedRect(ctx, x, y, widt
ctx.arcTo(x + width, y, x + width - radius, y, radius);
ctx.lineTo(x + radius, y);
ctx.arcTo(x, y, x, y + radius, radius);
ctx.stroke();
ctx.fill();
}
/**
+ * Draws text in the provided canvas context.
+ *
+ * @param {CanvasRenderingContext2D} ctx
+ * The 2D canvas context.
+ * @param {String} content
+ * The text to print on the canvas.
+ * @param {Number} x
+ * The x-axis origin of the text.
+ * @param {Number} y
+ * The y-axis origin of the text.
+ * @param {String} direction
+ * The direction that the text should be printed in.
+ * @param {Array} [matrix=identity()]
+ * The transformation matrix to apply.
+ */
+function drawText(ctx, content, x, y, direction, matrix = identity()) {
+ let p = apply(matrix, [x, y]);
+
+ ctx.save();
+
+ ctx.translate(p[0], p[1]);
+ if (direction === "vertical") {
+ ctx.rotate(Math.PI * 1.5);
+ }
+
+ ctx.fillText(content, 0, 0);
+
+ ctx.restore();
+}
+
+/**
* Given an array of four points and returns a DOMRect-like object representing the
* boundaries defined by the four points.
*
* @param {Array} points
* An array with 4 pointer objects {x, y} representing the box quads.
* @return {Object} DOMRect-like object of the 4 points.
*/
function getBoundsFromPoints(points) {
@@ -447,19 +481,21 @@ function updateCanvasPosition(canvasPosi
canvasPosition.x = canvasX;
canvasPosition.y = canvasY;
return hasUpdated;
}
exports.CANVAS_SIZE = CANVAS_SIZE;
exports.DEFAULT_COLOR = DEFAULT_COLOR;
+exports.DEFAULT_FONT = DEFAULT_FONT;
exports.clearRect = clearRect;
exports.drawBubbleRect = drawBubbleRect;
exports.drawLine = drawLine;
exports.drawRect = drawRect;
exports.drawRoundedRect = drawRoundedRect;
+exports.drawText = drawText;
exports.getBoundsFromPoints = getBoundsFromPoints;
exports.getCurrentMatrix = getCurrentMatrix;
exports.getPathDescriptionFromPoints = getPathDescriptionFromPoints;
exports.getPointsFromDiagonal = getPointsFromDiagonal;
exports.updateCanvasElement = updateCanvasElement;
exports.updateCanvasPosition = updateCanvasPosition;