Bug 1501226 - Support for CSS transforms in the Flexbox highlighter r=gl

In many places we were using bounds to calculate an objects dimensions but that obviously doesn't work when an object is rotated e.g.

{F588303}

Also, we were using `getBoxQuads()`, which gives the co-ordinates of the translated object. We were then applying the transform matrix to the canvas even though the coordinates came from the object **after** it was already transformed.

Anyhow, now we get the dimensions of objects as if they are not transformed and then apply the transformation matrix, which gives a great result every time.

Differential Revision: https://phabricator.services.mozilla.com/D9805

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Michael Ratcliffe 2018-11-07 10:12:56 +00:00
Родитель 1b50b597e6
Коммит af575ff9cd
1 изменённых файлов: 108 добавлений и 50 удалений

Просмотреть файл

@ -164,10 +164,11 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
// Clear the pattern cache to avoid dead object exceptions (Bug 1342051).
this.clearCache();
this.axes = null;
this.crossAxisDirection = null;
this.flexData = null;
this.mainAxisDirection = null;
this.crossAxisDirection = null;
this.axes = null;
this.transform = null;
AutoRefreshHighlighter.prototype.destroy.call(this);
}
@ -337,6 +338,10 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
this.justifyContentValue = this.computedStyle.justifyContent;
const newJustifyContent = this.justifyContentValue;
const oldTransform = this.transformValue;
this.transformValue = this.computedStyle.transform;
const newTransform = this.transformValue;
return hasMoved ||
hasFlexDataChanged ||
oldAlignItems !== newAlignItems ||
@ -344,7 +349,8 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
oldFlexWrap !== newFlexWrap ||
oldJustifyContent !== newJustifyContent ||
oldCrossAxisDirection !== newCrossAxisDirection ||
oldMainAxisDirection !== newMainAxisDirection;
oldMainAxisDirection !== newMainAxisDirection ||
oldTransform !== newTransform;
}
_hide() {
@ -530,8 +536,8 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.getFlexContainerPattern(devicePixelRatio);
const { bounds } = this.currentQuads.content[0];
drawRect(this.ctx, 0, 0, bounds.width, bounds.height, this.currentMatrix);
const { clientWidth, clientHeight } = this.currentNode;
drawRect(this.ctx, 0, 0, clientWidth, clientHeight, this.currentMatrix);
// Find current angle of outer flex element by measuring the angle of two arbitrary
// points, then rotate canvas, so the hash pattern stays 45deg to the boundary.
@ -556,6 +562,7 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
const zoom = getCurrentZoom(this.win);
const canvasX = Math.round(this._canvasPosition.x * devicePixelRatio * zoom);
const canvasY = Math.round(this._canvasPosition.y * devicePixelRatio * zoom);
const containerOffsets = getNodeRect(this.currentNode);
this.ctx.save();
this.ctx.translate(offset - canvasX, offset - canvasY);
@ -563,21 +570,18 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
this.ctx.lineWidth = lineWidth;
this.ctx.strokeStyle = this.color;
const { bounds } = this.currentQuads.content[0];
for (const flexLine of this.flexData.lines) {
for (const flexItem of flexLine.items) {
const quads = flexItem.quads;
if (!quads.length) {
const offsets = getNodeRect(flexItem.node);
if (!offsets) {
continue;
}
// Adjust the flex item bounds relative to the current quads.
const { bounds: flexItemBounds } = quads[0];
const left = Math.round(flexItemBounds.left / zoom - bounds.left);
const top = Math.round(flexItemBounds.top / zoom - bounds.top);
const right = Math.round(flexItemBounds.right / zoom - bounds.left);
const bottom = Math.round(flexItemBounds.bottom / zoom - bounds.top);
const left = offsets.left - containerOffsets.left;
const top = offsets.top - containerOffsets.top;
const right = offsets.right - containerOffsets.left;
const bottom = offsets.bottom - containerOffsets.top;
clearRect(this.ctx, left, top, right, bottom, this.currentMatrix);
drawRect(this.ctx, left, top, right, bottom, this.currentMatrix);
@ -599,15 +603,14 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
const zoom = getCurrentZoom(this.win);
const canvasX = Math.round(this._canvasPosition.x * devicePixelRatio * zoom);
const canvasY = Math.round(this._canvasPosition.y * devicePixelRatio * zoom);
const { clientWidth, clientHeight } = this.currentNode;
const options = { matrix: this.currentMatrix };
this.ctx.save();
this.ctx.translate(offset - canvasX, offset - canvasY);
this.ctx.lineWidth = lineWidth;
this.ctx.strokeStyle = this.color;
const { bounds } = this.currentQuads.content[0];
const options = { matrix: this.currentMatrix };
for (const flexLine of this.flexData.lines) {
const { crossStart, crossSize } = flexLine;
@ -616,56 +619,56 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
case "horizontal-lr vertical-bt":
case "horizontal-rl vertical-tb":
case "horizontal-rl vertical-bt":
clearRect(this.ctx, 0, crossStart, bounds.width, crossStart + crossSize,
clearRect(this.ctx, 0, crossStart, clientWidth, crossStart + crossSize,
this.currentMatrix);
// Avoid drawing the start flex line when they overlap with the flex container.
if (crossStart != 0) {
drawLine(this.ctx, 0, crossStart, bounds.width, crossStart, options);
drawLine(this.ctx, 0, crossStart, clientWidth, crossStart, options);
this.ctx.stroke();
}
// Avoid drawing the end flex line when they overlap with the flex container.
if (bounds.height - crossStart - crossSize >= lineWidth) {
drawLine(this.ctx, 0, crossStart + crossSize, bounds.width,
if (clientHeight - crossStart - crossSize >= lineWidth) {
drawLine(this.ctx, 0, crossStart + crossSize, clientWidth,
crossStart + crossSize, options);
this.ctx.stroke();
}
break;
case "vertical-tb horizontal-lr":
case "vertical-bt horizontal-rl":
clearRect(this.ctx, crossStart, 0, crossStart + crossSize, bounds.height,
clearRect(this.ctx, crossStart, 0, crossStart + crossSize, clientHeight,
this.currentMatrix);
// Avoid drawing the start flex line when they overlap with the flex container.
if (crossStart != 0) {
drawLine(this.ctx, crossStart, 0, crossStart, bounds.height, options);
drawLine(this.ctx, crossStart, 0, crossStart, clientHeight, options);
this.ctx.stroke();
}
// Avoid drawing the end flex line when they overlap with the flex container.
if (bounds.width - crossStart - crossSize >= lineWidth) {
if (clientWidth - crossStart - crossSize >= lineWidth) {
drawLine(this.ctx, crossStart + crossSize, 0, crossStart + crossSize,
bounds.height, options);
clientHeight, options);
this.ctx.stroke();
}
break;
case "vertical-bt horizontal-lr":
case "vertical-tb horizontal-rl":
clearRect(this.ctx, bounds.width - crossStart, 0,
bounds.width - crossStart - crossSize, bounds.height, this.currentMatrix);
clearRect(this.ctx, clientWidth - crossStart, 0,
clientWidth - crossStart - crossSize, clientHeight, this.currentMatrix);
// Avoid drawing the start flex line when they overlap with the flex container.
if (crossStart != 0) {
drawLine(this.ctx, bounds.width - crossStart, 0, bounds.width - crossStart,
bounds.height, options);
drawLine(this.ctx, clientWidth - crossStart, 0, clientWidth - crossStart,
clientHeight, options);
this.ctx.stroke();
}
// Avoid drawing the end flex line when they overlap with the flex container.
if (bounds.width - crossStart - crossSize >= lineWidth) {
drawLine(this.ctx, bounds.width - crossStart - crossSize, 0,
bounds.width - crossStart - crossSize, bounds.height, options);
if (clientWidth - crossStart - crossSize >= lineWidth) {
drawLine(this.ctx, clientWidth - crossStart - crossSize, 0,
clientWidth - crossStart - crossSize, clientHeight, options);
this.ctx.stroke();
}
break;
@ -683,27 +686,27 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
return;
}
const zoom = getCurrentZoom(this.win);
const { bounds } = this.currentQuads.content[0];
const lineWidth = getDisplayPixelRatio(this.win);
const { clientWidth, clientHeight } = this.currentNode;
const containerOffsets = getNodeRect(this.currentNode);
// Draw a justify content pattern over the whole flex container.
this.drawJustifyContent(0, 0, bounds.width, bounds.height, this.currentMatrix);
this.drawJustifyContent(0, 0, clientWidth, clientHeight);
for (const flexLine of this.flexData.lines) {
const { crossStart, crossSize } = flexLine;
for (const flexItem of flexLine.items) {
const quads = flexItem.quads;
if (!quads.length) {
const offsets = getNodeRect(flexItem.node);
if (!offsets) {
continue;
}
// Adjust the flex item bounds relative to the current quads.
const { bounds: flexItemBounds } = quads[0];
const left = Math.round(flexItemBounds.left / zoom - bounds.left);
const top = Math.round(flexItemBounds.top / zoom - bounds.top);
const right = Math.round(flexItemBounds.right / zoom - bounds.left);
const bottom = Math.round(flexItemBounds.bottom / zoom - bounds.top);
const left = offsets.left - containerOffsets.left;
const top = offsets.top - containerOffsets.top;
const right = offsets.right - containerOffsets.left;
const bottom = offsets.bottom - containerOffsets.top;
// Clear a rectangular are covering the alignment container.
switch (this.axes) {
@ -711,18 +714,22 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
case "horizontal-lr vertical-bt":
case "horizontal-rl vertical-tb":
case "horizontal-rl vertical-bt":
clearRect(this.ctx, left, Math.round(crossStart) + 2, right,
Math.round(crossStart + crossSize) - 2, this.currentMatrix);
clearRect(this.ctx,
left, Math.round(crossStart) + 2 * lineWidth, right,
Math.round(crossStart + crossSize) - 2 * lineWidth, this.currentMatrix);
break;
case "vertical-tb horizontal-lr":
case "vertical-bt horizontal-rl":
clearRect(this.ctx, Math.round(crossStart) + 1, top,
Math.round(crossStart + crossSize), bottom, this.currentMatrix);
clearRect(this.ctx,
Math.round(crossStart) + lineWidth * 2, top,
Math.round(crossStart + crossSize) - lineWidth, bottom, this.currentMatrix);
break;
case "vertical-bt horizontal-lr":
case "vertical-tb horizontal-rl":
clearRect(this.ctx, Math.round(bounds.width - crossStart - crossSize) + 1,
top, Math.round(bounds.width - crossStart), bottom, this.currentMatrix);
clearRect(this.ctx,
Math.round(clientWidth - crossStart - crossSize) + lineWidth * 2, top,
Math.round(clientWidth - crossStart) - lineWidth, bottom,
this.currentMatrix);
break;
}
}
@ -888,4 +895,55 @@ function compareFlexData(oldFlexData, newFlexData) {
return false;
}
/**
* Get the untransformed coordinates for a node.
*
* @param {DOMNode} node
* The node for which the coordinates are to be returned.
*
* @returns {Object}
* {
* left: left, // The absolute left coordinates of the node.
* top: top, // The absolute top coordinates of the node.
* right: right, // The absolute right coordinates of the node.
* bottom: bottom, // The absolute left coordinates of the node.
* width: width, // The width of the node.
* height, // The Height of the node.
* }
*/
function getNodeRect(node) {
if (node.nodeType === node.TEXT_NODE) {
// For now ignore text node flex items because we cannot get the
// untransformed position and dimensions of a text node
// (see https://bugzil.la/1505079).
return null;
}
const win = node.ownerGlobal;
const style = win.getComputedStyle(node);
const borderLeft = parseInt(style.borderLeftWidth, 10) || 0;
const borderTop = parseInt(style.borderTopWidth, 10) || 0;
const width = node.offsetWidth;
const height = node.offsetHeight;
let left = 0;
let top = 0;
while (node) {
left += node.offsetLeft - node.scrollLeft + node.clientLeft;
top += node.offsetTop - node.scrollTop + node.clientTop;
node = node.offsetParent;
}
return {
left: left - borderLeft,
top: top - borderTop,
right: left + width - borderLeft,
bottom: top + height - borderTop,
width: width,
height: height,
};
}
exports.FlexboxHighlighter = FlexboxHighlighter;