tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

layout.js (15470B)


      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/. */
      4 
      5 "use strict";
      6 
      7 const { Actor } = require("resource://devtools/shared/protocol.js");
      8 const {
      9  flexboxSpec,
     10  flexItemSpec,
     11  gridSpec,
     12  layoutSpec,
     13 } = require("resource://devtools/shared/specs/layout.js");
     14 
     15 const {
     16  getStringifiableFragments,
     17 } = require("resource://devtools/server/actors/utils/css-grid-utils.js");
     18 
     19 loader.lazyRequireGetter(
     20  this,
     21  "CssLogic",
     22  "resource://devtools/server/actors/inspector/css-logic.js",
     23  true
     24 );
     25 loader.lazyRequireGetter(
     26  this,
     27  "findGridParentContainerForNode",
     28  "resource://devtools/server/actors/inspector/utils.js",
     29  true
     30 );
     31 loader.lazyRequireGetter(
     32  this,
     33  "getMatchingCSSRules",
     34  "resource://devtools/shared/inspector/css-logic.js",
     35  true
     36 );
     37 loader.lazyRequireGetter(
     38  this,
     39  "isCssPropertyKnown",
     40  "resource://devtools/server/actors/css-properties.js",
     41  true
     42 );
     43 loader.lazyRequireGetter(
     44  this,
     45  "parseDeclarations",
     46  "resource://devtools/shared/css/parsing-utils.js",
     47  true
     48 );
     49 loader.lazyRequireGetter(
     50  this,
     51  "nodeConstants",
     52  "resource://devtools/shared/dom-node-constants.js"
     53 );
     54 
     55 /**
     56 * Set of actors the expose the CSS layout information to the devtools protocol clients.
     57 *
     58 * The |Layout| actor is the main entry point. It is used to get various CSS
     59 * layout-related information from the document.
     60 *
     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.
     64 *
     65 * The |Grid| actor provides the grid fragment information to inspect the grid container.
     66 */
     67 
     68 class FlexboxActor extends Actor {
     69  /**
     70   * @param  {LayoutActor} layoutActor
     71   *         The LayoutActor instance.
     72   * @param  {DOMNode} containerEl
     73   *         The flex container element.
     74   */
     75  constructor(layoutActor, containerEl) {
     76    super(layoutActor.conn, flexboxSpec);
     77 
     78    this.containerEl = containerEl;
     79    this.walker = layoutActor.walker;
     80  }
     81 
     82  destroy() {
     83    super.destroy();
     84 
     85    this.containerEl = null;
     86    this.walker = null;
     87  }
     88 
     89  form() {
     90    const styles = CssLogic.getComputedStyle(this.containerEl);
     91 
     92    const form = {
     93      actor: this.actorID,
     94      // The computed style properties of the flex container.
     95      properties: {
     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,
    101      },
    102    };
    103 
    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
    106    // cases.
    107    if (this.walker.hasNode(this.containerEl)) {
    108      form.containerNodeActorID = this.walker.getNode(this.containerEl).actorID;
    109    }
    110 
    111    return form;
    112  }
    113 
    114  /**
    115   * Returns an array of FlexItemActor objects for all the flex item elements contained
    116   * in the flex container element.
    117   *
    118   * @return {Array}
    119   *         An array of FlexItemActor objects.
    120   */
    121  getFlexItems() {
    122    if (isNodeDead(this.containerEl)) {
    123      return [];
    124    }
    125 
    126    const flex = this.containerEl.getAsFlexContainer();
    127    if (!flex) {
    128      return [];
    129    }
    130 
    131    const flexItemActors = [];
    132    const { crossAxisDirection, mainAxisDirection } = flex;
    133 
    134    for (const line of flex.getLines()) {
    135      for (const item of line.getItems()) {
    136        flexItemActors.push(
    137          new FlexItemActor(this, item.node, {
    138            crossAxisDirection,
    139            mainAxisDirection,
    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,
    148          })
    149        );
    150      }
    151    }
    152 
    153    return flexItemActors;
    154  }
    155 }
    156 
    157 /**
    158 * The FlexItemActor provides information about a flex items' data.
    159 */
    160 class FlexItemActor extends Actor {
    161  /**
    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.
    168   */
    169  constructor(flexboxActor, element, flexItemSizing) {
    170    super(flexboxActor.conn, flexItemSpec);
    171 
    172    this.containerEl = flexboxActor.containerEl;
    173    this.element = element;
    174    this.flexItemSizing = flexItemSizing;
    175    this.walker = flexboxActor.walker;
    176  }
    177 
    178  destroy() {
    179    super.destroy();
    180 
    181    this.containerEl = null;
    182    this.element = null;
    183    this.flexItemSizing = null;
    184    this.walker = null;
    185  }
    186 
    187  form() {
    188    const { mainAxisDirection } = this.flexItemSizing;
    189    const dimension = mainAxisDirection.startsWith("horizontal")
    190      ? "width"
    191      : "height";
    192 
    193    // Find the authored sizing properties for this item.
    194    const properties = {
    195      "flex-basis": "",
    196      "flex-grow": "",
    197      "flex-shrink": "",
    198      [`min-${dimension}`]: "",
    199      [`max-${dimension}`]: "",
    200      [dimension]: "",
    201    };
    202 
    203    const isElementNode = this.element.nodeType === this.element.ELEMENT_NODE;
    204 
    205    if (isElementNode) {
    206      for (const name in properties) {
    207        const values = [];
    208        const cssRules = getMatchingCSSRules(this.element);
    209 
    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(
    215            isCssPropertyKnown,
    216            rule.style.cssText
    217          );
    218 
    219          for (const declaration of declarations) {
    220            if (declaration.name === name && declaration.value !== "auto") {
    221              values.push({
    222                value: declaration.value,
    223                priority: declaration.priority,
    224              });
    225            }
    226          }
    227        }
    228 
    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
    231        if (
    232          this.element.style &&
    233          this.element.style[name] &&
    234          this.element.style[name] !== "auto"
    235        ) {
    236          values.push({
    237            value: this.element.style.getPropertyValue(name),
    238            priority: this.element.style.getPropertyPriority(name),
    239          });
    240        }
    241 
    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 = "";
    246 
    247        if (values.length) {
    248          const lastValueIndex = values.length - 1;
    249          rulePropertyValue = values[lastValueIndex].value;
    250 
    251          for (const { priority, value } of values) {
    252            if (priority === "important") {
    253              rulePropertyValue = `${value} !important`;
    254            }
    255          }
    256        }
    257 
    258        properties[name] = rulePropertyValue;
    259      }
    260    }
    261 
    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 };
    267 
    268    const form = {
    269      actor: this.actorID,
    270      // The flex item sizing data.
    271      flexItemSizing: this.flexItemSizing,
    272      // The authored style properties of the flex item.
    273      properties,
    274      // The computed style properties of the flex item.
    275      computedStyle,
    276    };
    277 
    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
    280    // cases.
    281    if (this.walker.hasNode(this.element)) {
    282      form.nodeActorID = this.walker.getNode(this.element).actorID;
    283    }
    284 
    285    return form;
    286  }
    287 }
    288 
    289 /**
    290 * The GridActor provides information about a given grid's fragment data.
    291 */
    292 class GridActor extends Actor {
    293  /**
    294   * @param  {LayoutActor} layoutActor
    295   *         The LayoutActor instance.
    296   * @param  {DOMNode} containerEl
    297   *         The grid container element.
    298   */
    299  constructor(layoutActor, containerEl) {
    300    super(layoutActor.conn, gridSpec);
    301 
    302    this.containerEl = containerEl;
    303    this.walker = layoutActor.walker;
    304  }
    305 
    306  destroy() {
    307    super.destroy();
    308 
    309    this.containerEl = null;
    310    this.gridFragments = null;
    311    this.walker = null;
    312  }
    313 
    314  form() {
    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);
    319 
    320    // Record writing mode and text direction for use by the grid outline.
    321    const { direction, writingMode } = CssLogic.getComputedStyle(
    322      this.containerEl
    323    );
    324 
    325    const form = {
    326      actor: this.actorID,
    327      direction,
    328      gridFragments: this.gridFragments,
    329      writingMode,
    330    };
    331 
    332    // If the WalkerActor already knows the container element, then also return its
    333    // ActorID so we avoid the client from doing another round trip to get it in many
    334    // cases.
    335    if (this.walker.hasNode(this.containerEl)) {
    336      form.containerNodeActorID = this.walker.getNode(this.containerEl).actorID;
    337    }
    338 
    339    const gridContainerType = InspectorUtils.getGridContainerType(
    340      this.containerEl
    341    );
    342    form.isSubgrid =
    343      gridContainerType &
    344      (InspectorUtils.GRID_SUBGRID_COL | InspectorUtils.GRID_SUBGRID_ROW);
    345 
    346    return form;
    347  }
    348 }
    349 
    350 /**
    351 * The CSS layout actor provides layout information for the given document.
    352 */
    353 class LayoutActor extends Actor {
    354  constructor(conn, targetActor, walker) {
    355    super(conn, layoutSpec);
    356 
    357    this.targetActor = targetActor;
    358    this.walker = walker;
    359  }
    360 
    361  destroy() {
    362    super.destroy();
    363 
    364    this.targetActor = null;
    365    this.walker = null;
    366  }
    367 
    368  /**
    369   * Helper function for getAsFlexItem, getCurrentGrid and getCurrentFlexbox. Returns the
    370   * grid or flex container (whichever is requested) found by iterating on the given
    371   * selected node. The current node can be a grid/flex container or grid/flex item.
    372   * If it is a grid/flex item, returns the parent grid/flex container. Otherwise, returns
    373   * null if the current or parent node is not a grid/flex container.
    374   *
    375   * @param  {Node|NodeActor} node
    376   *         The node to start iterating at.
    377   * @param  {string} type
    378   *         Can be "grid" or "flex", the display type we are searching for.
    379   * @param  {boolean} onlyLookAtContainer
    380   *         If true, only look at given node's container and iterate from there.
    381   * @return {GridActor|FlexboxActor|null}
    382   *         The GridActor or FlexboxActor of the grid/flex container of the given node.
    383   *         Otherwise, returns null.
    384   */
    385  getCurrentDisplay(node, type, onlyLookAtContainer) {
    386    if (isNodeDead(node)) {
    387      return null;
    388    }
    389 
    390    // Given node can either be a Node or a NodeActor.
    391    if (node.rawNode) {
    392      node = node.rawNode;
    393    }
    394 
    395    const flexType = type === "flex";
    396    const gridType = type === "grid";
    397    const displayType = this.walker.getNode(node).displayType;
    398 
    399    // If the node is an element, check first if it is itself a flex or a grid.
    400    if (node.nodeType === node.ELEMENT_NODE) {
    401      if (!displayType) {
    402        return null;
    403      }
    404 
    405      if (flexType && displayType.includes("flex")) {
    406        if (!onlyLookAtContainer) {
    407          return new FlexboxActor(this, node);
    408        }
    409 
    410        const container = node.parentFlexElement;
    411        if (container) {
    412          return new FlexboxActor(this, container);
    413        }
    414 
    415        return null;
    416      } else if (gridType && displayType.includes("grid")) {
    417        return new GridActor(this, node);
    418      }
    419    }
    420 
    421    // Otherwise, check if this is a flex/grid item or the parent node is a flex/grid
    422    // container.
    423    // Note that text nodes that are children of flex/grid containers are wrapped in
    424    // anonymous containers, so even if their displayType getter returns null we still
    425    // want to walk up the chain to find their container.
    426    const parentFlexElement = node.parentFlexElement;
    427    if (parentFlexElement && flexType) {
    428      return new FlexboxActor(this, parentFlexElement);
    429    }
    430    const container = findGridParentContainerForNode(node);
    431    if (container && gridType) {
    432      return new GridActor(this, container);
    433    }
    434 
    435    return null;
    436  }
    437 
    438  /**
    439   * Returns the grid container for a given selected node.
    440   * The node itself can be a container, but if not, walk up the DOM to find its
    441   * container.
    442   * Returns null if no container can be found.
    443   *
    444   * @param  {Node|NodeActor} node
    445   *         The node to start iterating at.
    446   * @return {GridActor|null}
    447   *         The GridActor of the grid container of the given node. Otherwise, returns
    448   *         null.
    449   */
    450  getCurrentGrid(node) {
    451    return this.getCurrentDisplay(node, "grid");
    452  }
    453 
    454  /**
    455   * Returns the flex container for a given selected node.
    456   * The node itself can be a container, but if not, walk up the DOM to find its
    457   * container.
    458   * Returns null if no container can be found.
    459   *
    460   * @param  {Node|NodeActor} node
    461   *         The node to start iterating at.
    462   * @param  {boolean | null} onlyLookAtParents
    463   *         If true, skip the passed node and only start looking at its parent and up.
    464   * @return {FlexboxActor|null}
    465   *         The FlexboxActor of the flex container of the given node. Otherwise, returns
    466   *         null.
    467   */
    468  getCurrentFlexbox(node, onlyLookAtParents) {
    469    return this.getCurrentDisplay(node, "flex", onlyLookAtParents);
    470  }
    471 
    472  /**
    473   * Returns an array of GridActor objects for all the grid elements contained in the
    474   * given root node.
    475   *
    476   * @param  {Node|NodeActor} node
    477   *         The root node for grid elements
    478   * @return {Array} An array of GridActor objects.
    479   */
    480  getGrids(node) {
    481    if (isNodeDead(node)) {
    482      return [];
    483    }
    484 
    485    // Root node can either be a Node or a NodeActor.
    486    if (node.rawNode) {
    487      node = node.rawNode;
    488    }
    489 
    490    // Root node can be a #document object, which does not support getElementsWithGrid.
    491    if (node.nodeType === nodeConstants.DOCUMENT_NODE) {
    492      node = node.documentElement;
    493    }
    494 
    495    if (!node) {
    496      return [];
    497    }
    498 
    499    const gridElements = node.getElementsWithGrid();
    500    let gridActors = gridElements.map(n => new GridActor(this, n));
    501 
    502    if (this.targetActor.ignoreSubFrames) {
    503      return gridActors;
    504    }
    505 
    506    const frames = node.querySelectorAll("iframe, frame");
    507    for (const frame of frames) {
    508      gridActors = gridActors.concat(this.getGrids(frame.contentDocument));
    509    }
    510 
    511    return gridActors;
    512  }
    513 }
    514 
    515 function isNodeDead(node) {
    516  return !node || (node.rawNode && Cu.isDeadWrapper(node.rawNode));
    517 }
    518 
    519 exports.FlexboxActor = FlexboxActor;
    520 exports.FlexItemActor = FlexItemActor;
    521 exports.GridActor = GridActor;
    522 exports.LayoutActor = LayoutActor;