| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 | 'use strict';/** * @typedef {import('css-tree').Rule} CsstreeRule * @typedef {import('./types').Specificity} Specificity * @typedef {import('./types').Stylesheet} Stylesheet * @typedef {import('./types').StylesheetRule} StylesheetRule * @typedef {import('./types').StylesheetDeclaration} StylesheetDeclaration * @typedef {import('./types').ComputedStyles} ComputedStyles * @typedef {import('./types').XastRoot} XastRoot * @typedef {import('./types').XastElement} XastElement * @typedef {import('./types').XastParent} XastParent * @typedef {import('./types').XastChild} XastChild */const stable = require('stable');const csstree = require('css-tree');// @ts-ignore not defined in @types/cssoconst specificity = require('csso/lib/restructure/prepare/specificity');const { visit, matches } = require('./xast.js');const {  attrsGroups,  inheritableAttrs,  presentationNonInheritableGroupAttrs,} = require('../plugins/_collections.js');// @ts-ignore not defined in @types/csstreeconst csstreeWalkSkip = csstree.walk.skip;/** * @type {(ruleNode: CsstreeRule, dynamic: boolean) => StylesheetRule} */const parseRule = (ruleNode, dynamic) => {  let selectors;  let selectorsSpecificity;  /**   * @type {Array<StylesheetDeclaration>}   */  const declarations = [];  csstree.walk(ruleNode, (cssNode) => {    if (cssNode.type === 'SelectorList') {      // compute specificity from original node to consider pseudo classes      selectorsSpecificity = specificity(cssNode);      const newSelectorsNode = csstree.clone(cssNode);      csstree.walk(newSelectorsNode, (pseudoClassNode, item, list) => {        if (pseudoClassNode.type === 'PseudoClassSelector') {          dynamic = true;          list.remove(item);        }      });      selectors = csstree.generate(newSelectorsNode);      return csstreeWalkSkip;    }    if (cssNode.type === 'Declaration') {      declarations.push({        name: cssNode.property,        value: csstree.generate(cssNode.value),        important: cssNode.important === true,      });      return csstreeWalkSkip;    }  });  if (selectors == null || selectorsSpecificity == null) {    throw Error('assert');  }  return {    dynamic,    selectors,    specificity: selectorsSpecificity,    declarations,  };};/** * @type {(css: string, dynamic: boolean) => Array<StylesheetRule>} */const parseStylesheet = (css, dynamic) => {  /**   * @type {Array<StylesheetRule>}   */  const rules = [];  const ast = csstree.parse(css, {    parseValue: false,    parseAtrulePrelude: false,  });  csstree.walk(ast, (cssNode) => {    if (cssNode.type === 'Rule') {      rules.push(parseRule(cssNode, dynamic || false));      return csstreeWalkSkip;    }    if (cssNode.type === 'Atrule') {      if (cssNode.name === 'keyframes') {        return csstreeWalkSkip;      }      csstree.walk(cssNode, (ruleNode) => {        if (ruleNode.type === 'Rule') {          rules.push(parseRule(ruleNode, dynamic || true));          return csstreeWalkSkip;        }      });      return csstreeWalkSkip;    }  });  return rules;};/** * @type {(css: string) => Array<StylesheetDeclaration>} */const parseStyleDeclarations = (css) => {  /**   * @type {Array<StylesheetDeclaration>}   */  const declarations = [];  const ast = csstree.parse(css, {    context: 'declarationList',    parseValue: false,  });  csstree.walk(ast, (cssNode) => {    if (cssNode.type === 'Declaration') {      declarations.push({        name: cssNode.property,        value: csstree.generate(cssNode.value),        important: cssNode.important === true,      });    }  });  return declarations;};/** * @type {(stylesheet: Stylesheet, node: XastElement) => ComputedStyles} */const computeOwnStyle = (stylesheet, node) => {  /**   * @type {ComputedStyles}   */  const computedStyle = {};  const importantStyles = new Map();  // collect attributes  for (const [name, value] of Object.entries(node.attributes)) {    if (attrsGroups.presentation.includes(name)) {      computedStyle[name] = { type: 'static', inherited: false, value };      importantStyles.set(name, false);    }  }  // collect matching rules  for (const { selectors, declarations, dynamic } of stylesheet.rules) {    if (matches(node, selectors)) {      for (const { name, value, important } of declarations) {        const computed = computedStyle[name];        if (computed && computed.type === 'dynamic') {          continue;        }        if (dynamic) {          computedStyle[name] = { type: 'dynamic', inherited: false };          continue;        }        if (          computed == null ||          important === true ||          importantStyles.get(name) === false        ) {          computedStyle[name] = { type: 'static', inherited: false, value };          importantStyles.set(name, important);        }      }    }  }  // collect inline styles  const styleDeclarations =    node.attributes.style == null      ? []      : parseStyleDeclarations(node.attributes.style);  for (const { name, value, important } of styleDeclarations) {    const computed = computedStyle[name];    if (computed && computed.type === 'dynamic') {      continue;    }    if (      computed == null ||      important === true ||      importantStyles.get(name) === false    ) {      computedStyle[name] = { type: 'static', inherited: false, value };      importantStyles.set(name, important);    }  }  return computedStyle;};/** * Compares two selector specificities. * extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211 * * @type {(a: Specificity, b: Specificity) => number} */const compareSpecificity = (a, b) => {  for (var i = 0; i < 4; i += 1) {    if (a[i] < b[i]) {      return -1;    } else if (a[i] > b[i]) {      return 1;    }  }  return 0;};/** * @type {(root: XastRoot) => Stylesheet} */const collectStylesheet = (root) => {  /**   * @type {Array<StylesheetRule>}   */  const rules = [];  /**   * @type {Map<XastElement, XastParent>}   */  const parents = new Map();  visit(root, {    element: {      enter: (node, parentNode) => {        // store parents        parents.set(node, parentNode);        // find and parse all styles        if (node.name === 'style') {          const dynamic =            node.attributes.media != null && node.attributes.media !== 'all';          if (            node.attributes.type == null ||            node.attributes.type === '' ||            node.attributes.type === 'text/css'          ) {            const children = node.children;            for (const child of children) {              if (child.type === 'text' || child.type === 'cdata') {                rules.push(...parseStylesheet(child.value, dynamic));              }            }          }        }      },    },  });  // sort by selectors specificity  stable.inplace(rules, (a, b) =>    compareSpecificity(a.specificity, b.specificity)  );  return { rules, parents };};exports.collectStylesheet = collectStylesheet;/** * @type {(stylesheet: Stylesheet, node: XastElement) => ComputedStyles} */const computeStyle = (stylesheet, node) => {  const { parents } = stylesheet;  // collect inherited styles  const computedStyles = computeOwnStyle(stylesheet, node);  let parent = parents.get(node);  while (parent != null && parent.type !== 'root') {    const inheritedStyles = computeOwnStyle(stylesheet, parent);    for (const [name, computed] of Object.entries(inheritedStyles)) {      if (        computedStyles[name] == null &&        // ignore not inheritable styles        inheritableAttrs.includes(name) === true &&        presentationNonInheritableGroupAttrs.includes(name) === false      ) {        computedStyles[name] = { ...computed, inherited: true };      }    }    parent = parents.get(parent);  }  return computedStyles;};exports.computeStyle = computeStyle;
 |