Bug 1477614 - Part 1: Implement a Flex Item Highlighter that highlights a flex item's flex-basis, flex-grow and flex-shrink. r=pbro
MozReview-Commit-ID: KghusKBXysg
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -211,16 +211,49 @@
:-moz-native-anonymous [class$=infobar-dimensions] {
color: hsl(210, 30%, 85%);
border-inline-start: 1px solid #5a6169;
margin-inline-start: 6px;
padding-inline-start: 6px;
}
+/* Flex Item Highlighter */
+
+:-moz-native-anonymous .flex-item-basis {
+ fill: transparent;
+ stroke: #FF1AD9;
+ stroke-dasharray: 2 1;
+ stroke-opacity: 0.7;
+ stroke-width: 2;
+ shape-rendering: crispEdges;
+}
+
+:-moz-native-anonymous .flex-item-sizing {
+ background-repeat: round;
+ position: absolute;
+ transform-origin: 0 0;
+}
+
+:-moz-native-anonymous .flex-item-sizing.top {
+ background-image: url('data:image/svg+xml;utf8,<svg width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="rgb(148, 0, 255)" fill-opacity=".8" fill-rule="evenodd" clip-rule="evenodd" d="M12.707 7.293a1 1 0 0 0-1.414 0l-7 7a1 1 0 0 0 1.414 1.414L12 9.414l6.293 6.293a1 1 0 0 0 1.414-1.414l-7-7z"></path></svg>');
+}
+
+:-moz-native-anonymous .flex-item-sizing.left {
+ background-image: url('data:image/svg+xml;utf8,<svg width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="rgb(148, 0, 255)" fill-opacity=".8" fill-rule="evenodd" clip-rule="evenodd" d="M15.707 4.293a1 1 0 0 1 0 1.414L9.414 12l6.293 6.293a1 1 0 1 1-1.414 1.414l-7-7a1 1 0 0 1 0-1.414l7-7a1 1 0 0 1 1.414 0z"></path></svg>');
+}
+
+:-moz-native-anonymous .flex-item-sizing.bottom {
+ background-image: url('data:image/svg+xml;utf8,<svg width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="rgb(148, 0, 255)" fill-opacity=".8" fill-rule="evenodd" clip-rule="evenodd" d="M4.293 8.293a1 1 0 0 1 1.414 0L12 14.586l6.293-6.293a1 1 0 1 1 1.414 1.414l-7 7a1 1 0 0 1-1.414 0l-7-7a1 1 0 0 1 0-1.414z"></path></svg>');
+}
+
+:-moz-native-anonymous .flex-item-sizing.right {
+ background-image: url('data:image/svg+xml;utf8,<svg width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="rgb(148, 0, 255)" fill-opacity=".8" fill-rule="evenodd" clip-rule="evenodd" d="M8.293 4.293a1 1 0 0 1 1.414 0l7 7a1 1 0 0 1 0 1.414l-7 7a1 1 0 0 1-1.414-1.414L14.586 12 8.293 5.707a1 1 0 0 1 0-1.414z"></path></svg>');
+}
+
/* CSS Grid Highlighter */
:-moz-native-anonymous .css-grid-canvas {
position: absolute;
pointer-events: none;
top: 0;
left: 0;
image-rendering: -moz-crisp-edges;
--- a/devtools/server/actors/highlighters.js
+++ b/devtools/server/actors/highlighters.js
@@ -704,15 +704,16 @@ HighlighterEnvironment.prototype = {
}
};
register("BoxModelHighlighter", "box-model");
register("CssGridHighlighter", "css-grid");
register("CssTransformHighlighter", "css-transform");
register("EyeDropper", "eye-dropper");
register("FlexboxHighlighter", "flexbox");
+register("FlexItemHighlighter", "flex-item");
register("FontsHighlighter", "fonts");
register("GeometryEditorHighlighter", "geometry-editor");
register("MeasuringToolHighlighter", "measuring-tool");
register("PausedDebuggerOverlay", "paused-debugger");
register("RulersHighlighter", "rulers");
register("SelectorHighlighter", "selector");
register("ShapesHighlighter", "shapes");
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/highlighters/flex-item.js
@@ -0,0 +1,365 @@
+ /* 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 {
+ CanvasFrameAnonymousContentHelper,
+ createNode,
+ createSVGNode,
+ isNodeValid,
+} = require("./utils/markup");
+const { setIgnoreLayoutChanges } = require("devtools/shared/layout/utils");
+const nodeConstants = require("devtools/shared/dom-node-constants");
+const { getFlexItemData } = require("devtools/server/actors/utils/flex-utils");
+
+/**
+ * The FlexItemHighlighter draws the flex basis and sizing regions of a flex item.
+ *
+ * Structure:
+ * <div class="highlighter-container" role="presentation">
+ * <div id="flex-item-root" class="flex-item-root">
+ * <svg id="flex-item-elements" class"flex-item-elements" hidden="true">
+ * <g class="flex-item-group">
+ * <path id="flex-item-basis" class="flex-item-basis" d="..." />
+ * </g>
+ * </svg>
+ * <div id="flex-item-sizing" class="flex-item-sizing" hidden="true"></div>
+ * </div>
+ * </div>
+ */
+class FlexItemHighlighter extends AutoRefreshHighlighter {
+ constructor(highlighterEnv) {
+ super(highlighterEnv);
+
+ this.ID_CLASS_PREFIX = "flex-item-";
+
+ 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);
+
+ const { pageListenerTarget } = highlighterEnv;
+ pageListenerTarget.addEventListener("pagehide", this.onPageHide);
+ }
+
+ _buildMarkup() {
+ const container = createNode(this.win, {
+ attributes: {
+ class: "highlighter-container",
+ role: "presentation"
+ }
+ });
+
+ // Build the root wrapper, used to adapt to the page zoom.
+ const root = createNode(this.win, {
+ parent: container,
+ attributes: {
+ id: "root",
+ class: "root",
+ role: "presentation"
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ // Build the SVG element with its path elements to render the flex basis and
+ // sizing of the flex item.
+
+ const svg = createSVGNode(this.win, {
+ nodeType: "svg",
+ parent: root,
+ attributes: {
+ id: "elements",
+ hidden: "true",
+ width: "100%",
+ height: "100%",
+ role: "presentation"
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ const group = createSVGNode(this.win, {
+ nodeType: "g",
+ parent: svg,
+ attributes: {
+ class: "group",
+ role: "presentation"
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ createSVGNode(this.win, {
+ nodeType: "path",
+ parent: group,
+ attributes: {
+ id: "basis",
+ class: "basis",
+ role: "presentation"
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ // Build the flex item sizing element that will be used to visualize flex shrink and
+ // grow.
+ createNode(this.win, {
+ parent: root,
+ attributes: {
+ id: "sizing",
+ class: "sizing",
+ hidden: "true"
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ return container;
+ }
+
+ destroy() {
+ this.highlighterEnv.off("will-navigate", this.onWillNavigate);
+
+ const { pageListenerTarget } = this.highlighterEnv;
+ if (pageListenerTarget) {
+ pageListenerTarget.removeEventListener("pagehide", this.onPageHide);
+ }
+
+ this.markup.destroy();
+
+ AutoRefreshHighlighter.prototype.destroy.call(this);
+ }
+
+ getElement(id) {
+ return this.markup.getElement(this.ID_CLASS_PREFIX + id);
+ }
+
+ _getPathCoordinates(p1, p2, p3, p4) {
+ return "M" + p1.x + "," + p1.y + " " +
+ "L" + p2.x + "," + p2.y + " " +
+ "L" + p3.x + "," + p3.y + " " +
+ "L" + p4.x + "," + p4.y + " " +
+ "L" + p4.x + "," + p1.y;
+ }
+
+ _hide() {
+ setIgnoreLayoutChanges(true);
+ this._hideFlexItem();
+ this._hideFlexSizing();
+ setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
+ }
+
+ _hideFlexItem() {
+ this.getElement("elements").setAttribute("hidden", "true");
+ }
+
+ _hideFlexSizing() {
+ this.getElement("sizing").setAttribute("hidden", "true");
+ }
+
+ /**
+ * Overrides the AutoRefreshHighlighter's _isNodeValid method to also return true for
+ * text nodes since these can also be highlighted.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ */
+ _isNodeValid(node) {
+ return node && (isNodeValid(node) || isNodeValid(node, nodeConstants.TEXT_NODE));
+ }
+
+ /**
+ * Returns whether or not the current node can be highlighted (eg, Does it have quads?).
+ *
+ * @return {Boolean} true if the current node can be highlighted and false otherwise.
+ */
+ _nodeNeedsHighlighting() {
+ return this.currentQuads.margin.length ||
+ this.currentQuads.border.length ||
+ this.currentQuads.padding.length ||
+ this.currentQuads.content.length;
+ }
+
+ /**
+ * If a page hide event is triggered for the 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 flex item highlighter.
+ */
+ onWillNavigate({ isTopLevel }) {
+ if (isTopLevel) {
+ this.hide();
+ }
+ }
+
+ _show() {
+ return this._update();
+ }
+
+ _showFlexItem() {
+ this.getElement("elements").removeAttribute("hidden");
+ }
+
+ _showFlexSizing() {
+ this.getElement("sizing").removeAttribute("hidden");
+ }
+
+ /**
+ * Updates the highlighter on the current highlighted flex item node.
+ */
+ _update() {
+ let shown = false;
+ setIgnoreLayoutChanges(true);
+
+ if (this._updateFlexItem()) {
+ this._showFlexItem();
+ shown = true;
+ } else {
+ // Nothing to highlight (not a flex item or has no quads).
+ this._hide();
+ }
+
+ setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
+
+ return shown;
+ }
+
+ /**
+ * Updates the flex item with the current node.
+ *
+ * @return {Boolean} true if the current node has a flex item to be highlighted.
+ */
+ _updateFlexItem() {
+ if (!this._nodeNeedsHighlighting()) {
+ return false;
+ }
+
+ const flexItemData = getFlexItemData(this.options.flexData, this.currentNode);
+ if (!flexItemData) {
+ return false;
+ }
+
+ this._updateFlexBasis(flexItemData);
+ this._updateFlexSizing(flexItemData);
+
+ // Un-zoom the root wrapper if the page was zoomed.
+ const rootId = this.ID_CLASS_PREFIX + "elements";
+ this.markup.scaleRootElement(this.currentNode, rootId);
+
+ return true;
+ }
+
+ /**
+ * Updates the flex basis path to display the flex basis of the current flex item.
+ *
+ * @param {Object} flexItemData
+ * Object representation of the Flex item data object.
+ */
+ _updateFlexBasis(flexItemData) {
+ const basis = this.getElement("basis");
+ basis.setAttribute("d", "");
+
+ const { mainBaseSize } = flexItemData;
+ const { p1, p2, p3, p4 } = this.currentQuads.content[0];
+ const isColumn = this.options.flexDirection.startsWith("column");
+
+ const flexBasisPath = isColumn ?
+ this._getPathCoordinates(
+ p1,
+ p2,
+ { x: p3.x, y: p2.y + mainBaseSize },
+ { x: p4.x, y: p1.y + mainBaseSize }
+ )
+ :
+ this._getPathCoordinates(
+ p1,
+ { x: p1.x + mainBaseSize, y: p2.y },
+ { x: p1.x + mainBaseSize, y: p3.y },
+ p4
+ );
+
+ this.getElement("basis").setAttribute("d", flexBasisPath);
+ }
+
+ /**
+ * Updates the flex sizing node to display the flex grow or shrink sizing of the
+ * current flex item.
+ *
+ * @param {Object} flexItemData
+ * Object representation of the Flex item data object.
+ */
+ _updateFlexSizing(flexItemData) {
+ const sizing = this.getElement("sizing");
+ sizing.setAttribute("style", "");
+ sizing.classList.remove("top");
+ sizing.classList.remove("left");
+ sizing.classList.remove("bottom");
+ sizing.classList.remove("right");
+
+ const { mainBaseSize, mainDeltaSize } = flexItemData;
+
+ if (mainDeltaSize === 0) {
+ // No flex sizing information to display.
+ return;
+ }
+
+ const { bounds } = this.currentQuads.content[0];
+ const isColumn = this.options.flexDirection.startsWith("column");
+
+ let x, y, width, height;
+ if (mainDeltaSize < 0) {
+ // Get the correct node location and sizing to show flex shrink.
+ if (isColumn) {
+ sizing.classList.add("top");
+
+ x = bounds.left;
+ y = bounds.bottom;
+ width = bounds.width;
+ height = Math.abs(mainDeltaSize);
+ } else {
+ sizing.classList.add("left");
+
+ x = bounds.right;
+ y = bounds.top;
+ width = Math.abs(mainDeltaSize);
+ height = bounds.height;
+ }
+ } else if (mainDeltaSize > 0) {
+ // Get the correct node location and sizing to show flex grow.
+ if (isColumn) {
+ sizing.classList.add("bottom");
+
+ x = bounds.left;
+ y = bounds.top + mainBaseSize;
+ width = bounds.width;
+ height = mainDeltaSize;
+ } else {
+ sizing.classList.add("right");
+
+ x = bounds.left + mainBaseSize;
+ y = bounds.top;
+ width = mainDeltaSize;
+ height = bounds.height;
+ }
+ }
+
+ sizing.setAttribute("style", `
+ width: ${width}px;
+ height: ${height}px;
+ transform: translate(${x}px, ${y}px);
+ `);
+
+ this._showFlexSizing();
+ }
+}
+
+exports.FlexItemHighlighter = FlexItemHighlighter;
--- a/devtools/server/actors/highlighters/flexbox.js
+++ b/devtools/server/actors/highlighters/flexbox.js
@@ -17,21 +17,24 @@ const {
updateCanvasPosition,
} = require("./utils/canvas");
const {
CanvasFrameAnonymousContentHelper,
createNode,
getComputedStyle,
} = require("./utils/markup");
const {
- getAdjustedQuads,
getDisplayPixelRatio,
getWindowDimensions,
setIgnoreLayoutChanges,
} = require("devtools/shared/layout/utils");
+const {
+ compareFlexData,
+ getFlexData,
+} = require("devtools/server/actors/utils/flex-utils");
const FLEXBOX_LINES_PROPERTIES = {
"edge": {
lineDash: [12, 10]
},
"item": {
lineDash: [0, 0]
},
@@ -714,124 +717,9 @@ class FlexboxHighlighter extends AutoRef
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 box quads of the flex items.
- *
- * @param {Flex} flex
- * The Flex data object.
- * @param {Window} win
- * The Window object.
- * @return {Object} representation of the Flex data object.
- */
-function getFlexData(flex, win) {
- 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,
- quads: getAdjustedQuads(win, item.node),
- };
- }),
- };
- })
- };
-}
-
-/**
- * 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.
- */
-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 oldItemQuads = oldItem.quads;
- const newItemQuads = newItem.quads;
-
- if (oldItemQuads.length !== newItemQuads.length) {
- return true;
- }
-
- const { bounds: oldItemBounds } = oldItemQuads[0];
- const { bounds: newItemBounds } = newItemQuads[0];
-
- if (oldItemBounds.bottom !== newItemBounds.bottom ||
- oldItemBounds.height !== newItemBounds.height ||
- oldItemBounds.left !== newItemBounds.left ||
- oldItemBounds.right !== newItemBounds.right ||
- oldItemBounds.top !== newItemBounds.top ||
- oldItemBounds.width !== newItemBounds.width ||
- oldItemBounds.x !== newItemBounds.x ||
- oldItemBounds.y !== newItemBounds.y) {
- return true;
- }
- }
- }
-
- return false;
-}
-
exports.FlexboxHighlighter = FlexboxHighlighter;
--- a/devtools/server/actors/highlighters/moz.build
+++ b/devtools/server/actors/highlighters/moz.build
@@ -10,16 +10,17 @@ DIRS += [
DevToolsModules(
'accessible.js',
'auto-refresh.js',
'box-model.js',
'css-grid.js',
'css-transform.js',
'eye-dropper.js',
+ 'flex-item.js',
'flexbox.js',
'fonts.js',
'geometry-editor.js',
'measuring-tool.js',
'paused-debugger.js',
'rulers.js',
'selector.js',
'shapes.js',
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/utils/flex-utils.js
@@ -0,0 +1,157 @@
+/* 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 { getAdjustedQuads } = require("devtools/shared/layout/utils");
+
+/**
+ * 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.
+ */
+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 oldItemQuads = oldItem.quads;
+ const newItemQuads = newItem.quads;
+
+ if (oldItemQuads.length !== newItemQuads.length) {
+ return true;
+ }
+
+ const { bounds: oldItemBounds } = oldItemQuads[0];
+ const { bounds: newItemBounds } = newItemQuads[0];
+
+ if (oldItemBounds.bottom !== newItemBounds.bottom ||
+ oldItemBounds.height !== newItemBounds.height ||
+ oldItemBounds.left !== newItemBounds.left ||
+ oldItemBounds.right !== newItemBounds.right ||
+ oldItemBounds.top !== newItemBounds.top ||
+ oldItemBounds.width !== newItemBounds.width ||
+ oldItemBounds.x !== newItemBounds.x ||
+ oldItemBounds.y !== newItemBounds.y) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Returns an object representation of the Flex data object and its array of FlexLine
+ * and FlexItem objects along with the box quads of the flex items.
+ *
+ * @param {Flex} flex
+ * The Flex data object.
+ * @param {Window} win
+ * The Window object.
+ * @return {Object|null} representation of the Flex data object.
+ */
+function getFlexData(flex, win) {
+ 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,
+ quads: getAdjustedQuads(win, item.node),
+ };
+ }),
+ };
+ })
+ };
+}
+
+/**
+ * Returns an object representation of the Flex item data for a given a node from the
+ * Flex data object.
+ *
+ * @param {Object} flexData
+ * Object representation of the Flex data object.
+ * @param {DOMNode} node
+ * The flex item node that we want the flex data for.
+ * @return {Object|null} representation of the Flex item data. See getFlexData() above to
+ * see the properties of the Flex item data object.
+ */
+function getFlexItemData(flexData, node) {
+ if (!flexData) {
+ return null;
+ }
+
+ for (const flexLine of flexData.lines) {
+ for (const flexItem of flexLine.items) {
+ if (flexItem.node === node) {
+ return flexItem;
+ }
+ }
+ }
+
+ return null;
+}
+
+exports.compareFlexData = compareFlexData;
+exports.getFlexData = getFlexData;
+exports.getFlexItemData = getFlexItemData;
--- a/devtools/server/actors/utils/moz.build
+++ b/devtools/server/actors/utils/moz.build
@@ -6,16 +6,17 @@
DevToolsModules(
'actor-registry-utils.js',
'audionodes.json',
'automation-timeline.js',
'breakpoint-actor-map.js',
'css-grid-utils.js',
'event-loop.js',
+ 'flex-utils.js',
'make-debugger.js',
'map-uri-to-addon-id.js',
'shapes-utils.js',
'source-actor-store.js',
'stack.js',
'TabSources.js',
'walker-search.js',
)