--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -91,16 +91,87 @@ const CANVAS_SIZE = 4096;
// This constant is used as value to draw infinite lines on canvas; since we cannot use
// the canvas boundaries as coordinates to draw the lines, and then applying
// transformations on top of them (the resulting coordinates might ending before reaching
// the viewport's edges, therefore the lines won't looks as "infinite").
const CANVAS_INFINITY = CANVAS_SIZE << 8;
/**
+ * Returns an array containing the four coordinates of a rectangle, given its diagonal
+ * as input; optionally applying a matrix, and a function to each of the coordinates'
+ * value.
+ *
+ * @param {Number} x1
+ * The x-axis coordinate of the rectangle's diagonal start point.
+ * @param {Number} y1
+ * The y-axis coordinate of the rectangle's diagonal start point.
+ * @param {Number} x2
+ * The x-axis coordinate of the rectangle's diagonal end point.
+ * @param {Number} y2
+ * The y-axis coordinate of the rectangle's diagonal end point.
+ * @param {Array} [matrix=identity()]
+ * A transformation matrix to apply.
+ * @return {Array}
+ * The rect four corners' points transformed by the matrix given.
+ */
+function getPointsFromDiagonal(x1, y1, x2, y2, matrix = identity()) {
+ return [
+ [x1, y1],
+ [x2, y1],
+ [x2, y2],
+ [x1, y2]
+ ].map(point => {
+ let transformedPoint = apply(matrix, point);
+
+ return {x: transformedPoint[0], y: transformedPoint[1]};
+ });
+}
+
+/**
+ * Takes an array of four points and returns a DOMRect-like object, represent the
+ * boundaries defined by the points given.
+ *
+ * @param {Array} points
+ * The four points.
+ * @return {Object}
+ * A DOMRect-like object.
+ */
+function getBoundsFromPoints(points) {
+ let bounds = {};
+
+ bounds.left = Math.min(points[0].x, points[1].x, points[2].x, points[3].x);
+ bounds.right = Math.max(points[0].x, points[1].x, points[2].x, points[3].x);
+ bounds.top = Math.min(points[0].y, points[1].y, points[2].y, points[3].y);
+ bounds.bottom = Math.max(points[0].y, points[1].y, points[2].y, points[3].y);
+
+ bounds.x = bounds.left;
+ bounds.y = bounds.top;
+ bounds.width = bounds.right - bounds.left;
+ bounds.height = bounds.bottom - bounds.top;
+
+ return bounds;
+}
+
+/**
+ * Takes an array of four points and returns a string represent a path description.
+ *
+ * @param {Array} points
+ * The four points.
+ * @return {String}
+ * A Path Description that can be used in svg's <path> element.
+ */
+function getPathDescriptionFromPoints(points) {
+ return "M" + points[0].x + "," + points[0].y + " " +
+ "L" + points[1].x + "," + points[1].y + " " +
+ "L" + points[2].x + "," + points[2].y + " " +
+ "L" + points[3].x + "," + points[3].y;
+}
+
+/**
* Draws a line to the context given, applying a transformation matrix if passed.
*
* @param {CanvasRenderingContext2D} ctx
* The 2d canvas context.
* @param {Number} x1
* The x-axis of the coordinate for the begin of the line.
* @param {Number} y1
* The y-axis of the coordinate for the begin of the line.
@@ -132,28 +203,23 @@ function drawLine(ctx, x1, y1, x2, y2, m
* @param {Number} x2
* The x-axis coordinate of the rectangle's diagonal end point.
* @param {Number} y2
* The y-axis coordinate of the rectangle's diagonal end point.
* @param {Array} [matrix=identity()]
* The transformation matrix to apply.
*/
function drawRect(ctx, x1, y1, x2, y2, matrix = identity()) {
- let p = [
- [x1, y1],
- [x2, y1],
- [x2, y2],
- [x1, y2]
- ].map(point => apply(matrix, point).map(Math.round));
+ let p = getPointsFromDiagonal(x1, y1, x2, y2, matrix);
ctx.beginPath();
- ctx.moveTo(p[0][0], p[0][1]);
- ctx.lineTo(p[1][0], p[1][1]);
- ctx.lineTo(p[2][0], p[2][1]);
- ctx.lineTo(p[3][0], p[3][1]);
+ ctx.moveTo(Math.round(p[0].x), Math.round(p[0].y));
+ ctx.lineTo(Math.round(p[1].x), Math.round(p[1].y));
+ ctx.lineTo(Math.round(p[2].x), Math.round(p[2].y));
+ ctx.lineTo(Math.round(p[3].x), Math.round(p[3].y));
ctx.closePath();
}
/**
* Utility method to draw a rounded rectangle in the provided canvas context.
*
* @param {CanvasRenderingContext2D} ctx
* The 2d canvas context.
@@ -792,55 +858,57 @@ CssGridHighlighter.prototype = extend(Au
return true;
},
/**
* Update the grid information displayed in the grid area info bar.
*
* @param {GridArea} area
* The grid area object.
- * @param {Number} x1
- * The first x-coordinate of the grid area rectangle.
- * @param {Number} x2
- * The second x-coordinate of the grid area rectangle.
- * @param {Number} y1
- * The first y-coordinate of the grid area rectangle.
- * @param {Number} y2
- * The second y-coordinate of the grid area rectangle.
+ * @param {Object} bounds
+ * A DOMRect-like object represent the grid area rectangle.
*/
- _updateGridAreaInfobar(area, x1, x2, y1, y2) {
- let width = x2 - x1;
- let height = y2 - y1;
+ _updateGridAreaInfobar(area, bounds) {
+ let { width, height } = bounds;
let dim = parseFloat(width.toPrecision(6)) +
" \u00D7 " +
parseFloat(height.toPrecision(6));
this.getElement("area-infobar-name").setTextContent(area.name);
this.getElement("area-infobar-dimensions").setTextContent(dim);
let container = this.getElement("area-infobar-container");
- this._moveInfobar(container, x1, x2, y1, y2, {
+ moveInfobar(container, bounds, this.win, {
position: "bottom",
hideIfOffscreen: true
});
},
- _updateGridCellInfobar(rowNumber, columnNumber, x1, x2, y1, y2) {
- let width = x2 - x1;
- let height = y2 - y1;
+ /**
+ * Update the grid information displayed in the grid cell info bar.
+ *
+ * @param {Number} rowNumber
+ * The grid cell's row number.
+ * @param {Number} columnNumber
+ * The grid cell's column number.
+ * @param {Object} bounds
+ * A DOMRect-like object represent the grid cell rectangle.
+ */
+ _updateGridCellInfobar(rowNumber, columnNumber, bounds) {
+ let { width, height } = bounds;
let dim = parseFloat(width.toPrecision(6)) +
" \u00D7 " +
parseFloat(height.toPrecision(6));
let position = `${rowNumber}\/${columnNumber}`;
this.getElement("cell-infobar-position").setTextContent(position);
this.getElement("cell-infobar-dimensions").setTextContent(dim);
let container = this.getElement("cell-infobar-container");
- this._moveInfobar(container, x1, x2, y1, y2, {
+ moveInfobar(container, bounds, this.win, {
position: "top",
hideIfOffscreen: true
});
},
/**
* Update the grid information displayed in the grid line info bar.
*
@@ -853,44 +921,18 @@ CssGridHighlighter.prototype = extend(Au
* @param {Number} y
* The y-coordinate of the grid line.
*/
_updateGridLineInfobar(gridLineNames, gridLineNumber, x, y) {
this.getElement("line-infobar-number").setTextContent(gridLineNumber);
this.getElement("line-infobar-names").setTextContent(gridLineNames);
let container = this.getElement("line-infobar-container");
- this._moveInfobar(container, x, x, y, y);
- },
-
- /**
- * Move the given grid infobar to the right place in the highlighter.
- *
- * @param {Number} x1
- * The first x-coordinate of the grid rectangle.
- * @param {Number} x2
- * The second x-coordinate of the grid rectangle.
- * @param {Number} y1
- * The first y-coordinate of the grid rectangle.
- * @param {Number} y2
- * The second y-coordinate of the grid rectangle.
- */
- _moveInfobar(container, x1, x2, y1, y2, options) {
- let bounds = {
- bottom: y2,
- height: y2 - y1,
- left: x1,
- right: x2,
- top: y1,
- width: x2 - x1,
- x: x1,
- y: y1,
- };
-
- moveInfobar(container, bounds, this.win, options);
+ moveInfobar(container,
+ getBoundsFromPoints([{x, y}, {x, y}, {x, y}, {x, y}]), this.win);
},
/**
* 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 hasPositionChanged = this.calculateCanvasPosition();
@@ -974,18 +1016,18 @@ CssGridHighlighter.prototype = extend(Au
// translating it to give the perception that it always covers the viewport.
this.canvas.setAttribute("style",
`width:${size}px;height:${size}px; transform: translate(${x}px, ${y}px);`);
this.ctx.clearRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);
},
/**
- * Updates the current matrix taking in account the following transformations, in this
- * order:
+ * Updates the current matrices for both canvas drawing and SVG, taking in account the
+ * following transformations, in this order:
* 1. The scale given by the display pixel ratio.
* 2. The translation to the top left corner of the element.
* 3. The scale given by the current zoom.
* 4. The translation given by the top and left padding of the element.
* 5. Any CSS transformation applied directly to the element (only 2D
* transformation; the 3D transformation are flattened, see `dom-matrix-2d` module
* for further details.)
*
@@ -1464,49 +1506,59 @@ CssGridHighlighter.prototype = extend(Au
* Render the grid area highlight for the given area name or for all the grid areas.
*
* @param {String} areaName
* Name of the grid area to be highlighted. If no area name is provided, all
* the grid areas should be highlighted.
*/
renderGridArea(areaName) {
let paths = [];
- let currentZoom = getCurrentZoom(this.win);
+ let { devicePixelRatio } = this.win;
+ let displayPixelRatio = getDisplayPixelRatio(this.win);
for (let i = 0; i < this.gridData.length; i++) {
let fragment = this.gridData[i];
- let {bounds} = this.currentQuads.content[i];
for (let area of fragment.areas) {
if (areaName && areaName != area.name) {
continue;
}
let rowStart = fragment.rows.lines[area.rowStart - 1];
let rowEnd = fragment.rows.lines[area.rowEnd - 1];
let columnStart = fragment.cols.lines[area.columnStart - 1];
let columnEnd = fragment.cols.lines[area.columnEnd - 1];
- let x1 = columnStart.start + columnStart.breadth +
- (bounds.left / currentZoom);
- let x2 = columnEnd.start + (bounds.left / currentZoom);
- let y1 = rowStart.start + rowStart.breadth +
- (bounds.top / currentZoom);
- let y2 = rowEnd.start + (bounds.top / currentZoom);
+ let x1 = columnStart.start + columnStart.breadth;
+ let y1 = rowStart.start + rowStart.breadth;
+ let x2 = columnEnd.start;
+ let y2 = rowEnd.start;
+
+ let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix);
- let path = "M" + x1 + "," + y1 + " " +
- "L" + x2 + "," + y1 + " " +
- "L" + x2 + "," + y2 + " " +
- "L" + x1 + "," + y2;
- paths.push(path);
+ // Scale down by `devicePixelRatio` since SVG element already take them into
+ // account.
+ let svgPoints = points.map(point => ({
+ x: Math.round(point.x / devicePixelRatio),
+ y: Math.round(point.y / devicePixelRatio)
+ }));
+
+ // Scale down by `displayPixelRatio` since infobar's HTML elements already take it
+ // into account; and the zoom scaling is handled by `moveInfobar`.
+ let bounds = getBoundsFromPoints(points.map(point => ({
+ x: Math.round(point.x / displayPixelRatio),
+ y: Math.round(point.y / displayPixelRatio)
+ })));
+
+ paths.push(getPathDescriptionFromPoints(svgPoints));
// Update and show the info bar when only displaying a single grid area.
if (areaName) {
this._showGridAreaInfoBar();
- this._updateGridAreaInfobar(area, x1, x2, y1, y2);
+ this._updateGridAreaInfobar(area, bounds);
}
}
}
let areas = this.getElement("areas");
areas.setAttribute("d", paths.join(" "));
},
@@ -1530,33 +1582,44 @@ CssGridHighlighter.prototype = extend(Au
let row = fragment.rows.tracks[rowNumber - 1];
let column = fragment.cols.tracks[columnNumber - 1];
if (!row || !column) {
return;
}
- let currentZoom = getCurrentZoom(this.win);
- let {bounds} = this.currentQuads.content[gridFragmentIndex];
+ let x1 = column.start;
+ let y1 = row.start;
+ let x2 = column.start + column.breadth;
+ let y2 = row.start + row.breadth;
+
+ let { devicePixelRatio } = this.win;
+ let displayPixelRatio = getDisplayPixelRatio(this.win);
+
+ let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix);
- let x1 = column.start + (bounds.left / currentZoom);
- let x2 = column.start + column.breadth + (bounds.left / currentZoom);
- let y1 = row.start + (bounds.top / currentZoom);
- let y2 = row.start + row.breadth + (bounds.top / currentZoom);
+ // Scale down by `devicePixelRatio` since SVG element already take them into account.
+ let svgPoints = points.map(point => ({
+ x: Math.round(point.x / devicePixelRatio),
+ y: Math.round(point.y / devicePixelRatio)
+ }));
- let path = "M" + x1 + "," + y1 + " " +
- "L" + x2 + "," + y1 + " " +
- "L" + x2 + "," + y2 + " " +
- "L" + x1 + "," + y2;
+ // Scale down by `displayPixelRatio` since infobar's HTML elements already take it
+ // into account, and the zoom scaling is handled by `moveInfobar`.
+ let bounds = getBoundsFromPoints(points.map(point => ({
+ x: Math.round(point.x / displayPixelRatio),
+ y: Math.round(point.y / displayPixelRatio)
+ })));
+
let cells = this.getElement("cells");
- cells.setAttribute("d", path);
+ cells.setAttribute("d", getPathDescriptionFromPoints(svgPoints));
this._showGridCellInfoBar();
- this._updateGridCellInfobar(rowNumber, columnNumber, x1, x2, y1, y2);
+ this._updateGridCellInfobar(rowNumber, columnNumber, bounds);
},
/**
* Render the grid line name highlight for the given grid fragment index, lineNumber,
* and dimensionType.
*
* @param {Number} gridFragmentIndex
* Index of the grid fragment to render the grid line highlight.