/* 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("resource://devtools/server/actors/highlighters/auto-refresh.js"); const { apply } = require("resource://devtools/shared/layout/dom-matrix-2d.js"); const { CANVAS_SIZE, DEFAULT_COLOR, clearRect, drawLine, drawRect, getCurrentMatrix, updateCanvasElement, updateCanvasPosition, } = require("resource://devtools/server/actors/highlighters/utils/canvas.js"); const { CanvasFrameAnonymousContentHelper, getComputedStyle, } = require("resource://devtools/server/actors/highlighters/utils/markup.js"); const { getAbsoluteScrollOffsetsForNode, getCurrentZoom, getDisplayPixelRatio, getUntransformedQuad, getWindowDimensions, setIgnoreLayoutChanges, } = require("resource://devtools/shared/layout/utils.js"); const FLEXBOX_LINES_PROPERTIES = { edge: { lineDash: [5, 3], }, item: { lineDash: [0, 0], }, alignItems: { lineDash: [0, 0], }, }; const FLEXBOX_CONTAINER_PATTERN_LINE_DASH = [5, 3]; // px const FLEXBOX_CONTAINER_PATTERN_WIDTH = 14; // px const FLEXBOX_CONTAINER_PATTERN_HEIGHT = 14; // px const FLEXBOX_JUSTIFY_CONTENT_PATTERN_WIDTH = 7; // px const FLEXBOX_JUSTIFY_CONTENT_PATTERN_HEIGHT = 7; // px /** * Cached used by `FlexboxHighlighter.getFlexContainerPattern`. */ const gCachedFlexboxPattern = new Map(); const FLEXBOX = "flexbox"; const JUSTIFY_CONTENT = "justify-content"; /** * The FlexboxHighlighter is the class that overlays a visual canvas on top of * display: [inline-]flex elements. * * @param {String} options.color * The color that should be used to draw the highlighter for this flexbox. * Structure: *
*
* *
*
*/ class FlexboxHighlighter extends AutoRefreshHighlighter { constructor(highlighterEnv) { super(highlighterEnv); this.ID_CLASS_PREFIX = "flexbox-"; this.markup = new CanvasFrameAnonymousContentHelper( this.highlighterEnv, this._buildMarkup.bind(this) ); this.isReady = this.markup.initialize(); this.onPageHide = this.onPageHide.bind(this); this.onWillNavigate = this.onWillNavigate.bind(this); this.highlighterEnv.on("will-navigate", this.onWillNavigate); const { pageListenerTarget } = highlighterEnv; pageListenerTarget.addEventListener("pagehide", this.onPageHide); // Initialize the position to the top left corner of the page this._canvasPosition = { x: 0, y: 0, }; this._ignoreZoom = true; // Calling `updateCanvasPosition` anyway since the highlighter could be initialized // on a page that has scrolled already. updateCanvasPosition( this._canvasPosition, this._scroll, this.win, this._winDimensions ); } _buildMarkup() { const container = this.markup.createNode({ attributes: { class: "highlighter-container", }, }); const root = this.markup.createNode({ parent: container, attributes: { id: "root", class: "root", }, prefix: this.ID_CLASS_PREFIX, }); // We use a element because there is an arbitrary number of items and texts // to draw which wouldn't be possible with HTML or SVG without having to insert and // remove the whole markup on every update. this.markup.createNode({ parent: root, nodeType: "canvas", attributes: { id: "canvas", class: "canvas", hidden: "true", width: CANVAS_SIZE, height: CANVAS_SIZE, }, prefix: this.ID_CLASS_PREFIX, }); return container; } clearCache() { gCachedFlexboxPattern.clear(); } destroy() { const { highlighterEnv } = this; highlighterEnv.off("will-navigate", this.onWillNavigate); const { pageListenerTarget } = highlighterEnv; if (pageListenerTarget) { pageListenerTarget.removeEventListener("pagehide", this.onPageHide); } this.markup.destroy(); // 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.transform = null; AutoRefreshHighlighter.prototype.destroy.call(this); } /** * Draw the justify content for a given flex item (left, top, right, bottom) position. */ drawJustifyContent(left, top, right, bottom) { const { devicePixelRatio } = this.win; this.ctx.fillStyle = this.getJustifyContentPattern(devicePixelRatio); drawRect(this.ctx, left, top, right, bottom, this.currentMatrix); this.ctx.fill(); } get canvas() { return this.getElement("canvas"); } get color() { return this.options.color || DEFAULT_COLOR; } get container() { return this.currentNode; } get ctx() { return this.canvas.getCanvasContext("2d"); } getElement(id) { return this.markup.getElement(this.ID_CLASS_PREFIX + id); } /** * Gets the flexbox container pattern used to render the container regions. * * @param {Number} devicePixelRatio * The device pixel ratio we want the pattern for. * @return {CanvasPattern} flex container pattern. */ getFlexContainerPattern(devicePixelRatio) { let flexboxPatternMap = null; if (gCachedFlexboxPattern.has(devicePixelRatio)) { flexboxPatternMap = gCachedFlexboxPattern.get(devicePixelRatio); } else { flexboxPatternMap = new Map(); } if (gCachedFlexboxPattern.has(FLEXBOX)) { return gCachedFlexboxPattern.get(FLEXBOX); } // Create the diagonal lines pattern for the rendering the flexbox gaps. const canvas = this.markup.createNode({ nodeType: "canvas" }); const width = (canvas.width = FLEXBOX_CONTAINER_PATTERN_WIDTH * devicePixelRatio); const height = (canvas.height = FLEXBOX_CONTAINER_PATTERN_HEIGHT * devicePixelRatio); const ctx = canvas.getContext("2d"); ctx.save(); ctx.setLineDash(FLEXBOX_CONTAINER_PATTERN_LINE_DASH); ctx.beginPath(); ctx.translate(0.5, 0.5); ctx.moveTo(0, 0); ctx.lineTo(width, height); ctx.strokeStyle = this.color; ctx.stroke(); ctx.restore(); const pattern = ctx.createPattern(canvas, "repeat"); flexboxPatternMap.set(FLEXBOX, pattern); gCachedFlexboxPattern.set(devicePixelRatio, flexboxPatternMap); return pattern; } /** * Gets the flexbox justify content pattern used to render the justify content regions. * * @param {Number} devicePixelRatio * The device pixel ratio we want the pattern for. * @return {CanvasPattern} flex justify content pattern. */ getJustifyContentPattern(devicePixelRatio) { let flexboxPatternMap = null; if (gCachedFlexboxPattern.has(devicePixelRatio)) { flexboxPatternMap = gCachedFlexboxPattern.get(devicePixelRatio); } else { flexboxPatternMap = new Map(); } if (flexboxPatternMap.has(JUSTIFY_CONTENT)) { return flexboxPatternMap.get(JUSTIFY_CONTENT); } // Create the inversed diagonal lines pattern // for the rendering the justify content gaps. const canvas = this.markup.createNode({ nodeType: "canvas" }); const zoom = getCurrentZoom(this.win); const width = (canvas.width = FLEXBOX_JUSTIFY_CONTENT_PATTERN_WIDTH * devicePixelRatio * zoom); const height = (canvas.height = FLEXBOX_JUSTIFY_CONTENT_PATTERN_HEIGHT * devicePixelRatio * zoom); const ctx = canvas.getContext("2d"); ctx.save(); ctx.setLineDash(FLEXBOX_CONTAINER_PATTERN_LINE_DASH); ctx.beginPath(); ctx.translate(0.5, 0.5); ctx.moveTo(0, height); ctx.lineTo(width, 0); ctx.strokeStyle = this.color; ctx.stroke(); ctx.restore(); const pattern = ctx.createPattern(canvas, "repeat"); flexboxPatternMap.set(JUSTIFY_CONTENT, pattern); gCachedFlexboxPattern.set(devicePixelRatio, flexboxPatternMap); return pattern; } /** * The AutoRefreshHighlighter's _hasMoved method returns true only if the * element's quads have changed. Override it so it also returns true if the * flex container and its flex items have changed. */ _hasMoved() { const hasMoved = AutoRefreshHighlighter.prototype._hasMoved.call(this); if (!this.computedStyle) { this.computedStyle = getComputedStyle(this.container); } const flex = this.container.getAsFlexContainer(); const oldCrossAxisDirection = this.crossAxisDirection; this.crossAxisDirection = flex ? flex.crossAxisDirection : null; const newCrossAxisDirection = this.crossAxisDirection; const oldMainAxisDirection = this.mainAxisDirection; this.mainAxisDirection = flex ? flex.mainAxisDirection : null; const newMainAxisDirection = this.mainAxisDirection; // Concatenate the axes to simplify conditionals. this.axes = `${this.mainAxisDirection} ${this.crossAxisDirection}`; const oldFlexData = this.flexData; this.flexData = getFlexData(this.container); const hasFlexDataChanged = compareFlexData(oldFlexData, this.flexData); const oldAlignItems = this.alignItemsValue; this.alignItemsValue = this.computedStyle.alignItems; const newAlignItems = this.alignItemsValue; const oldFlexDirection = this.flexDirection; this.flexDirection = this.computedStyle.flexDirection; const newFlexDirection = this.flexDirection; const oldFlexWrap = this.flexWrap; this.flexWrap = this.computedStyle.flexWrap; const newFlexWrap = this.flexWrap; const oldJustifyContent = this.justifyContentValue; 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 || oldFlexDirection !== newFlexDirection || oldFlexWrap !== newFlexWrap || oldJustifyContent !== newJustifyContent || oldCrossAxisDirection !== newCrossAxisDirection || oldMainAxisDirection !== newMainAxisDirection || oldTransform !== newTransform ); } _hide() { this.alignItemsValue = null; this.computedStyle = null; this.flexData = null; this.flexDirection = null; this.flexWrap = null; this.justifyContentValue = null; setIgnoreLayoutChanges(true); this._hideFlexbox(); setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement); } _hideFlexbox() { this.getElement("canvas").setAttribute("hidden", "true"); } /** * The '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() { const hasUpdated = updateCanvasPosition( 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 flexbox highlighter and clear * the cached gap patterns and avoid using DeadWrapper obejcts as gap patterns the * next time. */ onWillNavigate({ isTopLevel }) { this.clearCache(); if (isTopLevel) { this.hide(); } } renderFlexContainer() { if (!this.currentQuads.content || !this.currentQuads.content[0]) { return; } const { devicePixelRatio } = this.win; const containerQuad = getUntransformedQuad(this.container, "content"); const { width, height } = containerQuad.getBounds(); this.setupCanvas({ lineDash: FLEXBOX_LINES_PROPERTIES.alignItems.lineDash, lineWidthMultiplier: 2, }); this.ctx.fillStyle = this.getFlexContainerPattern(devicePixelRatio); drawRect(this.ctx, 0, 0, width, height, 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. const p1 = apply(this.currentMatrix, [0, 0]); const p2 = apply(this.currentMatrix, [1, 0]); const angleRad = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]); this.ctx.rotate(angleRad); this.ctx.fill(); this.ctx.stroke(); this.ctx.restore(); } renderFlexItems() { if ( !this.flexData || !this.currentQuads.content || !this.currentQuads.content[0] ) { return; } this.setupCanvas({ lineDash: FLEXBOX_LINES_PROPERTIES.item.lineDash, }); for (const flexLine of this.flexData.lines) { for (const flexItem of flexLine.items) { const { left, top, right, bottom } = flexItem.rect; clearRect(this.ctx, left, top, right, bottom, this.currentMatrix); drawRect(this.ctx, left, top, right, bottom, this.currentMatrix); this.ctx.stroke(); } } this.ctx.restore(); } renderFlexLines() { if ( !this.flexData || !this.currentQuads.content || !this.currentQuads.content[0] ) { return; } const lineWidth = getDisplayPixelRatio(this.win); const options = { matrix: this.currentMatrix }; const { width: containerWidth, height: containerHeight } = getUntransformedQuad(this.container, "content").getBounds(); this.setupCanvas({ useContainerScrollOffsets: true, }); for (const flexLine of this.flexData.lines) { const { crossStart, crossSize } = flexLine; switch (this.axes) { case "horizontal-lr vertical-tb": case "horizontal-lr vertical-bt": case "horizontal-rl vertical-tb": case "horizontal-rl vertical-bt": clearRect( this.ctx, 0, crossStart, containerWidth, 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, containerWidth, crossStart, options ); this.ctx.stroke(); } // Avoid drawing the end flex line when they overlap with the flex container. if (crossStart + crossSize < containerHeight - lineWidth * 2) { drawLine( this.ctx, 0, crossStart + crossSize, containerWidth, crossStart + crossSize, options ); this.ctx.stroke(); } break; case "vertical-tb horizontal-lr": case "vertical-bt horizontal-rl": clearRect( this.ctx, crossStart, 0, crossStart + crossSize, containerHeight, this.currentMatrix ); // Avoid drawing the start flex line when they overlap with the flex container. if (crossStart != 0) { drawLine( this.ctx, crossStart, 0, crossStart, containerHeight, options ); this.ctx.stroke(); } // Avoid drawing the end flex line when they overlap with the flex container. if (crossStart + crossSize < containerWidth - lineWidth * 2) { drawLine( this.ctx, crossStart + crossSize, 0, crossStart + crossSize, containerHeight, options ); this.ctx.stroke(); } break; case "vertical-bt horizontal-lr": case "vertical-tb horizontal-rl": clearRect( this.ctx, containerWidth - crossStart, 0, containerWidth - crossStart - crossSize, containerHeight, this.currentMatrix ); // Avoid drawing the start flex line when they overlap with the flex container. if (crossStart != 0) { drawLine( this.ctx, containerWidth - crossStart, 0, containerWidth - crossStart, containerHeight, options ); this.ctx.stroke(); } // Avoid drawing the end flex line when they overlap with the flex container. if (crossStart + crossSize < containerWidth - lineWidth * 2) { drawLine( this.ctx, containerWidth - crossStart - crossSize, 0, containerWidth - crossStart - crossSize, containerHeight, options ); this.ctx.stroke(); } break; } } this.ctx.restore(); } /** * Clear the whole alignment container along the main axis for each flex item. */ // eslint-disable-next-line complexity renderJustifyContent() { if ( !this.flexData || !this.currentQuads.content || !this.currentQuads.content[0] ) { return; } const { width: containerWidth, height: containerHeight } = getUntransformedQuad(this.container, "content").getBounds(); this.setupCanvas({ lineDash: FLEXBOX_LINES_PROPERTIES.alignItems.lineDash, offset: (getDisplayPixelRatio(this.win) / 2) % 1, skipLineAndStroke: true, useContainerScrollOffsets: true, }); for (const flexLine of this.flexData.lines) { const { crossStart, crossSize } = flexLine; let mainStart = 0; // In these two situations mainStart goes from right to left so set it's // value as appropriate. if ( this.axes === "horizontal-lr vertical-bt" || this.axes === "horizontal-rl vertical-tb" ) { mainStart = containerWidth; } for (const flexItem of flexLine.items) { const { left, top, right, bottom } = flexItem.rect; switch (this.axes) { case "horizontal-lr vertical-tb": case "horizontal-rl vertical-bt": this.drawJustifyContent( mainStart, crossStart, left, crossStart + crossSize ); mainStart = right; break; case "horizontal-lr vertical-bt": case "horizontal-rl vertical-tb": this.drawJustifyContent( right, crossStart, mainStart, crossStart + crossSize ); mainStart = left; break; case "vertical-tb horizontal-lr": case "vertical-bt horizontal-rl": this.drawJustifyContent( crossStart, mainStart, crossStart + crossSize, top ); mainStart = bottom; break; case "vertical-bt horizontal-lr": case "vertical-tb horizontal-rl": this.drawJustifyContent( containerWidth - crossStart - crossSize, mainStart, containerWidth - crossStart, top ); mainStart = bottom; break; } } // Draw the last justify-content area after the last flex item. switch (this.axes) { case "horizontal-lr vertical-tb": case "horizontal-rl vertical-bt": this.drawJustifyContent( mainStart, crossStart, containerWidth, crossStart + crossSize ); break; case "horizontal-lr vertical-bt": case "horizontal-rl vertical-tb": this.drawJustifyContent( 0, crossStart, mainStart, crossStart + crossSize ); break; case "vertical-tb horizontal-lr": case "vertical-bt horizontal-rl": this.drawJustifyContent( crossStart, mainStart, crossStart + crossSize, containerHeight ); break; case "vertical-bt horizontal-lr": case "vertical-tb horizontal-rl": this.drawJustifyContent( containerWidth - crossStart - crossSize, mainStart, containerWidth - crossStart, containerHeight ); break; } } this.ctx.restore(); } /** * Set up the canvas with the given options prior to drawing. * * @param {String} [options.lineDash = null] * An Array of numbers that specify distances to alternately draw a * line and a gap (in coordinate space units). If the number of * elements in the array is odd, the elements of the array get copied * and concatenated. For example, [5, 15, 25] will become * [5, 15, 25, 5, 15, 25]. If the array is empty, the line dash list is * cleared and line strokes return to being solid. * * We use the following constants here: * FLEXBOX_LINES_PROPERTIES.edge.lineDash, * FLEXBOX_LINES_PROPERTIES.item.lineDash * FLEXBOX_LINES_PROPERTIES.alignItems.lineDash * @param {Number} [options.lineWidthMultiplier = 1] * The width of the line. * @param {Number} [options.offset = `(displayPixelRatio / 2) % 1`] * The single line width used to obtain a crisp line. * @param {Boolean} [options.skipLineAndStroke = false] * Skip the setting of lineWidth and strokeStyle. * @param {Boolean} [options.useContainerScrollOffsets = false] * Take the flexbox container's scroll and zoom offsets into account. * This is needed for drawing flex lines and justify content when the * flexbox container itself is display:scroll. */ setupCanvas({ lineDash = null, lineWidthMultiplier = 1, offset = (getDisplayPixelRatio(this.win) / 2) % 1, skipLineAndStroke = false, useContainerScrollOffsets = false, }) { const { devicePixelRatio } = this.win; const lineWidth = getDisplayPixelRatio(this.win); const zoom = getCurrentZoom(this.win); const style = getComputedStyle(this.container); const position = style.position; let offsetX = this._canvasPosition.x; let offsetY = this._canvasPosition.y; if (useContainerScrollOffsets) { offsetX += this.container.scrollLeft / zoom; offsetY += this.container.scrollTop / zoom; } // If the flexbox container is position:fixed we need to subtract the scroll // positions of all ancestral elements. if (position === "fixed") { const { scrollLeft, scrollTop } = getAbsoluteScrollOffsetsForNode( this.container ); offsetX -= scrollLeft / zoom; offsetY -= scrollTop / zoom; } const canvasX = Math.round(offsetX * devicePixelRatio * zoom); const canvasY = Math.round(offsetY * devicePixelRatio * zoom); this.ctx.save(); this.ctx.translate(offset - canvasX, offset - canvasY); if (lineDash) { this.ctx.setLineDash(lineDash); } if (!skipLineAndStroke) { this.ctx.lineWidth = lineWidth * lineWidthMultiplier; this.ctx.strokeStyle = this.color; } } _update() { setIgnoreLayoutChanges(true); const 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; this._winDimensions = getWindowDimensions(this.win); const { width, height } = this._winDimensions; // Updates the element's position and size. // It also clear the 's drawing context. updateCanvasElement( this.canvas, this._canvasPosition, this.win.devicePixelRatio, { zoomWindow: this.win, } ); // Update the current matrix used in our canvas' rendering const { currentMatrix, hasNodeTransformations } = getCurrentMatrix( this.container, this.win, { ignoreWritingModeAndTextDirection: true, } ); this.currentMatrix = currentMatrix; this.hasNodeTransformations = hasNodeTransformations; if (this.prevColor != this.color) { this.clearCache(); } this.renderFlexContainer(); this.renderFlexLines(); this.renderJustifyContent(); this.renderFlexItems(); this._showFlexbox(); this.prevColor = this.color; root.setAttribute( "style", `position: absolute; width: ${width}px; height: ${height}px; overflow: hidden` ); setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement); return true; } } /** * Returns an object representation of the Flex data object and its array of FlexLine * and FlexItem objects along with the DOMRects of the flex items. * * @param {DOMNode} container * The flex container. * @return {Object|null} representation of the Flex data object. */ function getFlexData(container) { const flex = container.getAsFlexContainer(); if (!flex) { return null; } return { lines: flex.getLines().map(line => { return { crossSize: line.crossSize, crossStart: line.crossStart, firstBaselineOffset: line.firstBaselineOffset, growthState: line.growthState, lastBaselineOffset: line.lastBaselineOffset, items: line.getItems().map(item => { return { crossMaxSize: item.crossMaxSize, crossMinSize: item.crossMinSize, mainBaseSize: item.mainBaseSize, mainDeltaSize: item.mainDeltaSize, mainMaxSize: item.mainMaxSize, mainMinSize: item.mainMinSize, node: item.node, rect: getRectFromFlexItemValues(item, container), }; }), }; }), }; } /** * Given a FlexItemValues, return a DOMRect representing the flex item taking * into account its flex container's border and padding. * * @param {FlexItemValues} item * The FlexItemValues for which we need the DOMRect. * @param {DOMNode} * Flex container containing the flex item. * @return {DOMRect} representing the flex item. */ function getRectFromFlexItemValues(item, container) { const rect = item.frameRect; const domRect = new DOMRect(rect.x, rect.y, rect.width, rect.height); const win = container.ownerGlobal; const style = win.getComputedStyle(container); const borderLeftWidth = parseInt(style.borderLeftWidth, 10) || 0; const borderTopWidth = parseInt(style.borderTopWidth, 10) || 0; const paddingLeft = parseInt(style.paddingLeft, 10) || 0; const paddingTop = parseInt(style.paddingTop, 10) || 0; const scrollX = container.scrollLeft || 0; const scrollY = container.scrollTop || 0; domRect.x -= paddingLeft + scrollX; domRect.y -= paddingTop + scrollY; if (style.overflow === "visible" || style.overflow === "clip") { domRect.x -= borderLeftWidth; domRect.y -= borderTopWidth; } return domRect; } /** * Returns whether or not the flex data has changed. * * @param {Flex} oldFlexData * The old Flex data object. * @param {Flex} newFlexData * The new Flex data object. * @return {Boolean} true if the flex data has changed and false otherwise. */ // eslint-disable-next-line complexity function compareFlexData(oldFlexData, newFlexData) { if (!oldFlexData || !newFlexData) { return true; } const oldLines = oldFlexData.lines; const newLines = newFlexData.lines; if (oldLines.length !== newLines.length) { return true; } for (let i = 0; i < oldLines.length; i++) { const oldLine = oldLines[i]; const newLine = newLines[i]; if ( oldLine.crossSize !== newLine.crossSize || oldLine.crossStart !== newLine.crossStart || oldLine.firstBaselineOffset !== newLine.firstBaselineOffset || oldLine.growthState !== newLine.growthState || oldLine.lastBaselineOffset !== newLine.lastBaselineOffset ) { return true; } const oldItems = oldLine.items; const newItems = newLine.items; if (oldItems.length !== newItems.length) { return true; } for (let j = 0; j < oldItems.length; j++) { const oldItem = oldItems[j]; const newItem = newItems[j]; if ( oldItem.crossMaxSize !== newItem.crossMaxSize || oldItem.crossMinSize !== newItem.crossMinSize || oldItem.mainBaseSize !== newItem.mainBaseSize || oldItem.mainDeltaSize !== newItem.mainDeltaSize || oldItem.mainMaxSize !== newItem.mainMaxSize || oldItem.mainMinSize !== newItem.mainMinSize ) { return true; } const oldItemRect = oldItem.rect; const newItemRect = newItem.rect; // We are using DOMRects so we only need to compare x, y, width and // height (left, top, right and bottom are calculated from these values). if ( oldItemRect.x !== newItemRect.x || oldItemRect.y !== newItemRect.y || oldItemRect.width !== newItemRect.width || oldItemRect.height !== newItemRect.height ) { return true; } } } return false; } exports.FlexboxHighlighter = FlexboxHighlighter;