1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 const { Actor } = require("resource://devtools/shared/protocol.js");
13 } = require("resource://devtools/shared/specs/layout.js");
16 getStringifiableFragments,
17 } = require("resource://devtools/server/actors/utils/css-grid-utils.js");
19 loader.lazyRequireGetter(
22 "resource://devtools/server/actors/inspector/css-logic.js",
25 loader.lazyRequireGetter(
27 "findGridParentContainerForNode",
28 "resource://devtools/server/actors/inspector/utils.js",
31 loader.lazyRequireGetter(
34 "resource://devtools/shared/inspector/css-logic.js",
37 loader.lazyRequireGetter(
40 "resource://devtools/server/actors/css-properties.js",
43 loader.lazyRequireGetter(
46 "resource://devtools/shared/css/parsing-utils.js",
49 loader.lazyRequireGetter(
52 "resource://devtools/shared/dom-node-constants.js"
56 * Set of actors the expose the CSS layout information to the devtools protocol clients.
58 * The |Layout| actor is the main entry point. It is used to get various CSS
59 * layout-related information from the document.
61 * The |Flexbox| actor provides the container node information to inspect the flexbox
62 * container. It is also used to return an array of |FlexItem| actors which provide the
63 * flex item information.
65 * The |Grid| actor provides the grid fragment information to inspect the grid container.
68 class FlexboxActor extends Actor {
70 * @param {LayoutActor} layoutActor
71 * The LayoutActor instance.
72 * @param {DOMNode} containerEl
73 * The flex container element.
75 constructor(layoutActor, containerEl) {
76 super(layoutActor.conn, flexboxSpec);
78 this.containerEl = containerEl;
79 this.walker = layoutActor.walker;
85 this.containerEl = null;
90 const styles = CssLogic.getComputedStyle(this.containerEl);
94 // The computed style properties of the flex container.
96 "align-content": styles.alignContent,
97 "align-items": styles.alignItems,
98 "flex-direction": styles.flexDirection,
99 "flex-wrap": styles.flexWrap,
100 "justify-content": styles.justifyContent,
104 // If the WalkerActor already knows the container element, then also return its
105 // ActorID so we avoid the client from doing another round trip to get it in many
107 if (this.walker.hasNode(this.containerEl)) {
108 form.containerNodeActorID = this.walker.getNode(this.containerEl).actorID;
115 * Returns an array of FlexItemActor objects for all the flex item elements contained
116 * in the flex container element.
119 * An array of FlexItemActor objects.
122 if (isNodeDead(this.containerEl)) {
126 const flex = this.containerEl.getAsFlexContainer();
131 const flexItemActors = [];
132 const { crossAxisDirection, mainAxisDirection } = flex;
134 for (const line of flex.getLines()) {
135 for (const item of line.getItems()) {
137 new FlexItemActor(this, item.node, {
140 crossMaxSize: item.crossMaxSize,
141 crossMinSize: item.crossMinSize,
142 mainBaseSize: item.mainBaseSize,
143 mainDeltaSize: item.mainDeltaSize,
144 mainMaxSize: item.mainMaxSize,
145 mainMinSize: item.mainMinSize,
146 lineGrowthState: line.growthState,
147 clampState: item.clampState,
153 return flexItemActors;
158 * The FlexItemActor provides information about a flex items' data.
160 class FlexItemActor extends Actor {
162 * @param {FlexboxActor} flexboxActor
163 * The FlexboxActor instance.
164 * @param {DOMNode} element
165 * The flex item element.
166 * @param {Object} flexItemSizing
167 * The flex item sizing data.
169 constructor(flexboxActor, element, flexItemSizing) {
170 super(flexboxActor.conn, flexItemSpec);
172 this.containerEl = flexboxActor.containerEl;
173 this.element = element;
174 this.flexItemSizing = flexItemSizing;
175 this.walker = flexboxActor.walker;
181 this.containerEl = null;
183 this.flexItemSizing = null;
188 const { mainAxisDirection } = this.flexItemSizing;
189 const dimension = mainAxisDirection.startsWith("horizontal")
193 // Find the authored sizing properties for this item.
198 [`min-${dimension}`]: "",
199 [`max-${dimension}`]: "",
203 const isElementNode = this.element.nodeType === this.element.ELEMENT_NODE;
206 for (const name in properties) {
208 const cssRules = getCSSStyleRules(this.element);
210 for (const rule of cssRules) {
211 // For each rule, go through *all* properties, because there may be several of
212 // them in the same rule and some with !important flags (which would be more
213 // important even if placed before another property with the same name)
214 const declarations = parseDeclarations(
219 for (const declaration of declarations) {
220 if (declaration.name === name && declaration.value !== "auto") {
222 value: declaration.value,
223 priority: declaration.priority,
229 // Then go through the element style because it's usually more important, but
230 // might not be if there is a prior !important property
232 this.element.style &&
233 this.element.style[name] &&
234 this.element.style[name] !== "auto"
237 value: this.element.style.getPropertyValue(name),
238 priority: this.element.style.getPropertyPriority(name),
242 // Now that we have a list of all the property's rule values, go through all the
243 // values and show the property value with the highest priority. Therefore, show
244 // the last !important value. Otherwise, show the last value stored.
245 let rulePropertyValue = "";
248 const lastValueIndex = values.length - 1;
249 rulePropertyValue = values[lastValueIndex].value;
251 for (const { priority, value } of values) {
252 if (priority === "important") {
253 rulePropertyValue = `${value} !important`;
258 properties[name] = rulePropertyValue;
262 // Also find some computed sizing properties that will be useful for this item.
263 const { flexGrow, flexShrink } = isElementNode
264 ? CssLogic.getComputedStyle(this.element)
265 : { flexGrow: null, flexShrink: null };
266 const computedStyle = { flexGrow, flexShrink };
270 // The flex item sizing data.
271 flexItemSizing: this.flexItemSizing,
272 // The authored style properties of the flex item.
274 // The computed style properties of the flex item.
278 // If the WalkerActor already knows the flex item element, then also return its
279 // ActorID so we avoid the client from doing another round trip to get it in many
281 if (this.walker.hasNode(this.element)) {
282 form.nodeActorID = this.walker.getNode(this.element).actorID;
290 * The GridActor provides information about a given grid's fragment data.
292 class GridActor extends Actor {
294 * @param {LayoutActor} layoutActor
295 * The LayoutActor instance.
296 * @param {DOMNode} containerEl
297 * The grid container element.
299 constructor(layoutActor, containerEl) {
300 super(layoutActor.conn, gridSpec);
302 this.containerEl = containerEl;
303 this.walker = layoutActor.walker;
309 this.containerEl = null;
310 this.gridFragments = null;
315 // Seralize the grid fragment data into JSON so protocol.js knows how to write
316 // and read the data.
317 const gridFragments = this.containerEl.getGridFragments();
318 this.gridFragments = getStringifiableFragments(gridFragments);
320 // Record writing mode and text direction for use by the grid outline.
321 const { direction, gridTemplateColumns, gridTemplateRows, writingMode } =
322 CssLogic.getComputedStyle(this.containerEl);
327 gridFragments: this.gridFragments,
331 // If the WalkerActor already knows the container element, then also return its
332 // ActorID so we avoid the client from doing another round trip to get it in many
334 if (this.walker.hasNode(this.containerEl)) {
335 form.containerNodeActorID = this.walker.getNode(this.containerEl).actorID;
339 gridTemplateRows.startsWith("subgrid") ||
340 gridTemplateColumns.startsWith("subgrid");
347 * The CSS layout actor provides layout information for the given document.
349 class LayoutActor extends Actor {
350 constructor(conn, targetActor, walker) {
351 super(conn, layoutSpec);
353 this.targetActor = targetActor;
354 this.walker = walker;
360 this.targetActor = null;
365 * Helper function for getAsFlexItem, getCurrentGrid and getCurrentFlexbox. Returns the
366 * grid or flex container (whichever is requested) found by iterating on the given
367 * selected node. The current node can be a grid/flex container or grid/flex item.
368 * If it is a grid/flex item, returns the parent grid/flex container. Otherwise, returns
369 * null if the current or parent node is not a grid/flex container.
371 * @param {Node|NodeActor} node
372 * The node to start iterating at.
373 * @param {String} type
374 * Can be "grid" or "flex", the display type we are searching for.
375 * @param {Boolean} onlyLookAtContainer
376 * If true, only look at given node's container and iterate from there.
377 * @return {GridActor|FlexboxActor|null}
378 * The GridActor or FlexboxActor of the grid/flex container of the given node.
379 * Otherwise, returns null.
381 getCurrentDisplay(node, type, onlyLookAtContainer) {
382 if (isNodeDead(node)) {
386 // Given node can either be a Node or a NodeActor.
391 const flexType = type === "flex";
392 const gridType = type === "grid";
393 const displayType = this.walker.getNode(node).displayType;
395 // If the node is an element, check first if it is itself a flex or a grid.
396 if (node.nodeType === node.ELEMENT_NODE) {
401 if (flexType && displayType.includes("flex")) {
402 if (!onlyLookAtContainer) {
403 return new FlexboxActor(this, node);
406 const container = node.parentFlexElement;
408 return new FlexboxActor(this, container);
412 } else if (gridType && displayType.includes("grid")) {
413 return new GridActor(this, node);
417 // Otherwise, check if this is a flex/grid item or the parent node is a flex/grid
419 // Note that text nodes that are children of flex/grid containers are wrapped in
420 // anonymous containers, so even if their displayType getter returns null we still
421 // want to walk up the chain to find their container.
422 const parentFlexElement = node.parentFlexElement;
423 if (parentFlexElement && flexType) {
424 return new FlexboxActor(this, parentFlexElement);
426 const container = findGridParentContainerForNode(node);
427 if (container && gridType) {
428 return new GridActor(this, container);
435 * Returns the grid container for a given selected node.
436 * The node itself can be a container, but if not, walk up the DOM to find its
438 * Returns null if no container can be found.
440 * @param {Node|NodeActor} node
441 * The node to start iterating at.
442 * @return {GridActor|null}
443 * The GridActor of the grid container of the given node. Otherwise, returns
446 getCurrentGrid(node) {
447 return this.getCurrentDisplay(node, "grid");
451 * Returns the flex container for a given selected node.
452 * The node itself can be a container, but if not, walk up the DOM to find its
454 * Returns null if no container can be found.
456 * @param {Node|NodeActor} node
457 * The node to start iterating at.
458 * @param {Boolean|null} onlyLookAtParents
459 * If true, skip the passed node and only start looking at its parent and up.
460 * @return {FlexboxActor|null}
461 * The FlexboxActor of the flex container of the given node. Otherwise, returns
464 getCurrentFlexbox(node, onlyLookAtParents) {
465 return this.getCurrentDisplay(node, "flex", onlyLookAtParents);
469 * Returns an array of GridActor objects for all the grid elements contained in the
472 * @param {Node|NodeActor} node
473 * The root node for grid elements
474 * @return {Array} An array of GridActor objects.
477 if (isNodeDead(node)) {
481 // Root node can either be a Node or a NodeActor.
486 // Root node can be a #document object, which does not support getElementsWithGrid.
487 if (node.nodeType === nodeConstants.DOCUMENT_NODE) {
488 node = node.documentElement;
495 const gridElements = node.getElementsWithGrid();
496 let gridActors = gridElements.map(n => new GridActor(this, n));
498 if (this.targetActor.ignoreSubFrames) {
502 const frames = node.querySelectorAll("iframe, frame");
503 for (const frame of frames) {
504 gridActors = gridActors.concat(this.getGrids(frame.contentDocument));
511 function isNodeDead(node) {
512 return !node || (node.rawNode && Cu.isDeadWrapper(node.rawNode));
515 exports.FlexboxActor = FlexboxActor;
516 exports.FlexItemActor = FlexItemActor;
517 exports.GridActor = GridActor;
518 exports.LayoutActor = LayoutActor;