tor-browser

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

utils.js (11691B)


      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 {
      8  VIEW_NODE_CSS_QUERY_CONTAINER,
      9  VIEW_NODE_CSS_SELECTOR_WARNINGS,
     10  VIEW_NODE_FONT_TYPE,
     11  VIEW_NODE_IMAGE_URL_TYPE,
     12  VIEW_NODE_INACTIVE_CSS,
     13  VIEW_NODE_LOCATION_TYPE,
     14  VIEW_NODE_PROPERTY_TYPE,
     15  VIEW_NODE_SELECTOR_TYPE,
     16  VIEW_NODE_SHAPE_POINT_TYPE,
     17  VIEW_NODE_SHAPE_SWATCH,
     18  VIEW_NODE_VALUE_TYPE,
     19  VIEW_NODE_VARIABLE_TYPE,
     20 } = require("resource://devtools/client/inspector/shared/node-types.js");
     21 const INSET_POINT_TYPES = ["top", "right", "bottom", "left"];
     22 
     23 /**
     24 * Returns the [Rule] object associated with the given node.
     25 *
     26 * @param {DOMNode} node
     27 *        The node which we want to find the [Rule] object for
     28 * @param {ElementStyle} elementStyle
     29 *        The [ElementStyle] associated with the selected element
     30 * @return {Rule|null} associated with the given node
     31 */
     32 function getRuleFromNode(node, elementStyle) {
     33  const ruleEl = node.closest(".ruleview-rule[data-rule-id]");
     34  const ruleId = ruleEl ? ruleEl.dataset.ruleId : null;
     35  return ruleId ? elementStyle.getRule(ruleId) : null;
     36 }
     37 
     38 /**
     39 * Returns the [TextProperty] object associated with the given node.
     40 *
     41 * @param {DOMNode} node
     42 *        The node which we want to find the [TextProperty] object for
     43 * @param {Rule|null} rule
     44 *        The [Rule] associated with the given node
     45 * @return {TextProperty|null} associated with the given node
     46 */
     47 function getDeclarationFromNode(node, rule) {
     48  if (!rule) {
     49    return null;
     50  }
     51 
     52  const declarationEl = node.closest(".ruleview-property[data-declaration-id]");
     53  const declarationId = declarationEl
     54    ? declarationEl.dataset.declarationId
     55    : null;
     56  return rule ? rule.getDeclaration(declarationId) : null;
     57 }
     58 
     59 /**
     60 * Get the type of a given node in the Rules view.
     61 *
     62 * @param {DOMNode} node
     63 *        The node which we want information about
     64 * @param {ElementStyle} elementStyle
     65 *        The ElementStyle to which this rule belongs
     66 * @return {object | null} containing the following props:
     67 * - rule {Rule} The Rule object.
     68 * - type {String} One of the VIEW_NODE_XXX_TYPE const in
     69 *   client/inspector/shared/node-types.
     70 * - value {Object} Depends on the type of the node.
     71 * - view {String} Always "rule" to indicate the rule view.
     72 * Otherwise, returns null if the node isn't anything we care about.
     73 */
     74 // eslint-disable-next-line complexity
     75 function getNodeInfo(node, elementStyle) {
     76  if (!node) {
     77    return null;
     78  }
     79 
     80  const rule = getRuleFromNode(node, elementStyle);
     81  const declaration = getDeclarationFromNode(node, rule);
     82  const classList = node.classList;
     83 
     84  let type, value;
     85 
     86  if (declaration && classList.contains("ruleview-propertyname")) {
     87    type = VIEW_NODE_PROPERTY_TYPE;
     88    value = {
     89      property: node.textContent,
     90      value: getPropertyNameAndValue(node).value,
     91      enabled: declaration.enabled,
     92      overridden: declaration.overridden,
     93      pseudoElement: rule.pseudoElement,
     94      sheetHref: rule.domRule.href,
     95      textProperty: declaration,
     96    };
     97  } else if (declaration && classList.contains("ruleview-propertyvalue")) {
     98    type = VIEW_NODE_VALUE_TYPE;
     99    value = {
    100      property: getPropertyNameAndValue(node).name,
    101      value: node.textContent,
    102      enabled: declaration.enabled,
    103      overridden: declaration.overridden,
    104      pseudoElement: rule.pseudoElement,
    105      sheetHref: rule.domRule.href,
    106      textProperty: declaration,
    107    };
    108  } else if (declaration && classList.contains("ruleview-font-family")) {
    109    const { name: propertyName, value: propertyValue } =
    110      getPropertyNameAndValue(node);
    111    type = VIEW_NODE_FONT_TYPE;
    112    value = {
    113      property: propertyName,
    114      value: propertyValue,
    115      enabled: declaration.enabled,
    116      overridden: declaration.overridden,
    117      pseudoElement: rule.pseudoElement,
    118      sheetHref: rule.domRule.href,
    119      textProperty: declaration,
    120    };
    121  } else if (declaration && classList.contains("inspector-shape-point")) {
    122    type = VIEW_NODE_SHAPE_POINT_TYPE;
    123    value = {
    124      property: getPropertyNameAndValue(node).name,
    125      value: node.textContent,
    126      enabled: declaration.enabled,
    127      overridden: declaration.overridden,
    128      pseudoElement: rule.pseudoElement,
    129      sheetHref: rule.domRule.href,
    130      textProperty: declaration,
    131      toggleActive: getShapeToggleActive(node),
    132      point: getShapePoint(node),
    133    };
    134  } else if (
    135    declaration &&
    136    classList.contains("ruleview-inactive-css-warning")
    137  ) {
    138    type = VIEW_NODE_INACTIVE_CSS;
    139    value = declaration.getInactiveCssData();
    140  } else if (node.closest(".container-query-declaration")) {
    141    type = VIEW_NODE_CSS_QUERY_CONTAINER;
    142    const containerQueryEl = node.closest(".container-query");
    143    value = {
    144      ancestorIndex: containerQueryEl.getAttribute("data-ancestor-index"),
    145      rule,
    146    };
    147  } else if (node.classList.contains("ruleview-selector-warnings")) {
    148    type = VIEW_NODE_CSS_SELECTOR_WARNINGS;
    149    value = node.getAttribute("data-selector-warning-kind").split(",");
    150  } else if (declaration && classList.contains("inspector-shapeswatch")) {
    151    type = VIEW_NODE_SHAPE_SWATCH;
    152    value = {
    153      enabled: declaration.enabled,
    154      overridden: declaration.overridden,
    155      textProperty: declaration,
    156    };
    157  } else if (
    158    declaration &&
    159    (classList.contains("inspector-variable") ||
    160      classList.contains("inspector-unmatched"))
    161  ) {
    162    type = VIEW_NODE_VARIABLE_TYPE;
    163    value = {
    164      property: getPropertyNameAndValue(node).name,
    165      value: node.textContent.trim(),
    166      enabled: declaration.enabled,
    167      overridden: declaration.overridden,
    168      pseudoElement: rule.pseudoElement,
    169      sheetHref: rule.domRule.href,
    170      textProperty: declaration,
    171      variable: node.dataset.variable,
    172      variableComputed: node.dataset.variableComputed,
    173      startingStyleVariable: node.dataset.startingStyleVariable,
    174      registeredProperty: {
    175        initialValue: node.dataset.registeredPropertyInitialValue,
    176        syntax: node.dataset.registeredPropertySyntax,
    177        inherits: node.dataset.registeredPropertyInherits,
    178      },
    179      outputParserOptions: declaration.editor.outputParserOptions,
    180      cssProperties: declaration.editor.ruleView.cssProperties,
    181    };
    182  } else if (
    183    declaration &&
    184    classList.contains("theme-link") &&
    185    !classList.contains("ruleview-rule-source")
    186  ) {
    187    type = VIEW_NODE_IMAGE_URL_TYPE;
    188    value = {
    189      property: getPropertyNameAndValue(node).name,
    190      value: node.parentNode.textContent,
    191      url: node.href,
    192      enabled: declaration.enabled,
    193      overridden: declaration.overridden,
    194      pseudoElement: rule.pseudoElement,
    195      sheetHref: rule.domRule.href,
    196      textProperty: declaration,
    197    };
    198  } else if (
    199    classList.contains("ruleview-selectors-container") ||
    200    classList.contains("ruleview-selector") ||
    201    classList.contains("ruleview-selector-element") ||
    202    classList.contains("ruleview-selector-attribute") ||
    203    classList.contains("ruleview-selector-pseudo-class") ||
    204    classList.contains("ruleview-selector-pseudo-class-lock")
    205  ) {
    206    type = VIEW_NODE_SELECTOR_TYPE;
    207    value = rule.selectorText;
    208  } else if (
    209    classList.contains("ruleview-rule-source") ||
    210    classList.contains("ruleview-rule-source-label")
    211  ) {
    212    type = VIEW_NODE_LOCATION_TYPE;
    213    const sourceLabelEl = classList.contains("ruleview-rule-source-label")
    214      ? node
    215      : node.querySelector(".ruleview-rule-source-label");
    216    value =
    217      sourceLabelEl.getAttribute("href") || rule.sheet?.href || rule.title;
    218  } else {
    219    return null;
    220  }
    221 
    222  return {
    223    rule,
    224    type,
    225    value,
    226    view: "rule",
    227  };
    228 }
    229 
    230 /**
    231 * Walk up the DOM from a given node until a parent property holder is found,
    232 * and return the textContent for the name and value nodes.
    233 * Stops at the first property found, so if node is inside the computed property
    234 * list, the computed property will be returned
    235 *
    236 * @param {DOMNode} node
    237 *        The node to start from
    238 * @return {object} {name, value}
    239 */
    240 function getPropertyNameAndValue(node) {
    241  while (node?.classList) {
    242    // Check first for ruleview-computed since it's the deepest
    243    if (
    244      node.classList.contains("ruleview-computed") ||
    245      node.classList.contains("ruleview-property")
    246    ) {
    247      return {
    248        name: node.querySelector(".ruleview-propertyname").textContent,
    249        value: node.querySelector(".ruleview-propertyvalue").textContent,
    250      };
    251    }
    252 
    253    node = node.parentNode;
    254  }
    255 
    256  return null;
    257 }
    258 
    259 /**
    260 * Walk up the DOM from a given node until a parent property holder is found,
    261 * and return an active shape toggle if one exists.
    262 *
    263 * @param {DOMNode} node
    264 *        The node to start from
    265 * @returns {DOMNode} The active shape toggle node, if one exists.
    266 */
    267 function getShapeToggleActive(node) {
    268  while (node?.classList) {
    269    // Check first for ruleview-computed since it's the deepest
    270    if (
    271      node.classList.contains("ruleview-computed") ||
    272      node.classList.contains("ruleview-property")
    273    ) {
    274      return node.querySelector(`.inspector-shapeswatch[aria-pressed="true"]`);
    275    }
    276 
    277    node = node.parentNode;
    278  }
    279 
    280  return null;
    281 }
    282 
    283 /**
    284 * Get the point associated with a shape point node.
    285 *
    286 * @param {DOMNode} node
    287 *        A shape point node
    288 * @returns {string} The point associated with the given node.
    289 */
    290 function getShapePoint(node) {
    291  const classList = node.classList;
    292  let point = node.dataset.point;
    293  // Inset points use classes instead of data because a single span can represent
    294  // multiple points.
    295  const insetClasses = [];
    296  classList.forEach(className => {
    297    if (INSET_POINT_TYPES.includes(className)) {
    298      insetClasses.push(className);
    299    }
    300  });
    301  if (insetClasses.length) {
    302    point = insetClasses.join(",");
    303  }
    304  return point;
    305 }
    306 
    307 /**
    308 * Returns an array of CSS variables used in a CSS property value.
    309 * If no CSS variables are used, returns an empty array.
    310 *
    311 * @param {string} propertyValue
    312 *        CSS property value (e.g. "1px solid var(--color, blue)")
    313 * @return {Array}
    314 *         List of variable names (e.g. ["--color"])
    315 */
    316 function getCSSVariables(propertyValue = "") {
    317  const variables = [];
    318  const parts = propertyValue.split(/var\(\s*--/);
    319 
    320  if (parts.length) {
    321    // Skip first part. It is the substring before the first occurence of "var(--"
    322    for (let i = 1; i < parts.length; i++) {
    323      // Split the part by any of the following characters expected after a variable name:
    324      // comma, closing parenthesis or whitespace.
    325      // Take just the first match. Anything else is either:
    326      // - the fallback value, ex: ", blue" from "var(--color, blue)"
    327      // - the closing parenthesis, ex: ")" from "var(--color)"
    328      const variable = parts[i].split(/[,)\s+]/).shift();
    329 
    330      if (variable) {
    331        // Add back the double-dash. The initial string was split by "var(--"
    332        variables.push(`--${variable}`);
    333      }
    334    }
    335  }
    336 
    337  return variables;
    338 }
    339 
    340 /**
    341 * Get the CSS compatibility issue information for a given node.
    342 *
    343 * @param {DOMNode} node
    344 *        The node which we want compatibility information about
    345 * @param {ElementStyle} elementStyle
    346 *        The ElementStyle to which this rule belongs
    347 */
    348 async function getNodeCompatibilityInfo(node, elementStyle) {
    349  const rule = getRuleFromNode(node, elementStyle);
    350  const declaration = getDeclarationFromNode(node, rule);
    351  const issue = await declaration.isCompatible();
    352 
    353  return issue;
    354 }
    355 
    356 module.exports = {
    357  getCSSVariables,
    358  getNodeInfo,
    359  getRuleFromNode,
    360  getNodeCompatibilityInfo,
    361 };