@quick-game/cli
Version:
Command line interface for rapid qg development
859 lines • 40.1 kB
JavaScript
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as TextUtils from '../../models/text_utils/text_utils.js';
import { cssMetadata, VariableRegex } from './CSSMetadata.js';
import { CSSKeyframesRule, CSSPositionFallbackRule, CSSPropertyRule, CSSStyleRule } from './CSSRule.js';
import { CSSStyleDeclaration, Type } from './CSSStyleDeclaration.js';
export function parseCSSVariableNameAndFallback(cssVariableValue) {
const match = cssVariableValue.match(/var\(\s*(--(?:[\s\w\P{ASCII}-]|\\.)+),?\s*(.*)\s*\)/u);
return { variableName: match && match[1].trim(), fallback: match && match[2] };
}
export class CSSRegisteredProperty {
#registration;
#cssModel;
#style;
constructor(cssModel, registration) {
this.#cssModel = cssModel;
this.#registration = registration;
}
isAtProperty() {
return this.#registration instanceof CSSPropertyRule;
}
propertyName() {
return this.#registration instanceof CSSPropertyRule ? this.#registration.propertyName().text :
this.#registration.propertyName;
}
initialValue() {
return this.#registration instanceof CSSPropertyRule ? this.#registration.initialValue() :
this.#registration.initialValue?.text ?? null;
}
inherits() {
return this.#registration instanceof CSSPropertyRule ? this.#registration.inherits() : this.#registration.inherits;
}
syntax() {
return this.#registration instanceof CSSPropertyRule ? this.#registration.syntax() :
`"${this.#registration.syntax}"`;
}
#asCSSProperties() {
if (this.#registration instanceof CSSPropertyRule) {
return [];
}
const { inherits, initialValue, syntax } = this.#registration;
const properties = [
{ name: 'inherits', value: `${inherits}` },
{ name: 'syntax', value: `"${syntax}"` },
];
if (initialValue !== undefined) {
properties.push({ name: 'initial-value', value: initialValue.text });
}
return properties;
}
style() {
if (!this.#style) {
this.#style = this.#registration instanceof CSSPropertyRule ?
this.#registration.style :
new CSSStyleDeclaration(this.#cssModel, null, { cssProperties: this.#asCSSProperties(), shorthandEntries: [] }, Type.Pseudo);
}
return this.#style;
}
}
export class CSSMatchedStyles {
#cssModelInternal;
#nodeInternal;
#addedStyles;
#matchingSelectors;
#keyframesInternal;
#registeredProperties;
#registeredPropertyMap = new Map();
#nodeForStyleInternal;
#inheritedStyles;
#mainDOMCascade;
#pseudoDOMCascades;
#customHighlightPseudoDOMCascades;
#styleToDOMCascade;
#parentLayoutNodeId;
#positionFallbackRules;
constructor({ cssModel, node, inlinePayload, attributesPayload, matchedPayload, pseudoPayload, inheritedPayload, inheritedPseudoPayload, animationsPayload, parentLayoutNodeId, positionFallbackRules, propertyRules, cssPropertyRegistrations, }) {
this.#cssModelInternal = cssModel;
this.#nodeInternal = node;
this.#addedStyles = new Map();
this.#matchingSelectors = new Map();
this.#registeredProperties = [
...propertyRules.map(rule => new CSSPropertyRule(cssModel, rule)),
...cssPropertyRegistrations,
].map(r => new CSSRegisteredProperty(cssModel, r));
this.#keyframesInternal = [];
if (animationsPayload) {
this.#keyframesInternal = animationsPayload.map(rule => new CSSKeyframesRule(cssModel, rule));
}
this.#positionFallbackRules = positionFallbackRules.map(rule => new CSSPositionFallbackRule(cssModel, rule));
this.#parentLayoutNodeId = parentLayoutNodeId;
this.#nodeForStyleInternal = new Map();
this.#inheritedStyles = new Set();
matchedPayload = cleanUserAgentPayload(matchedPayload);
for (const inheritedResult of inheritedPayload) {
inheritedResult.matchedCSSRules = cleanUserAgentPayload(inheritedResult.matchedCSSRules);
}
this.#mainDOMCascade = this.buildMainCascade(inlinePayload, attributesPayload, matchedPayload, inheritedPayload);
[this.#pseudoDOMCascades, this.#customHighlightPseudoDOMCascades] =
this.buildPseudoCascades(pseudoPayload, inheritedPseudoPayload);
this.#styleToDOMCascade = new Map();
for (const domCascade of Array.from(this.#customHighlightPseudoDOMCascades.values())
.concat(Array.from(this.#pseudoDOMCascades.values()))
.concat(this.#mainDOMCascade)) {
for (const style of domCascade.styles()) {
this.#styleToDOMCascade.set(style, domCascade);
}
}
for (const prop of this.#registeredProperties) {
this.#registeredPropertyMap.set(prop.propertyName(), prop);
}
function cleanUserAgentPayload(payload) {
for (const ruleMatch of payload) {
cleanUserAgentSelectors(ruleMatch);
}
// Merge UA rules that are sequential and have similar selector/media.
const cleanMatchedPayload = [];
for (const ruleMatch of payload) {
const lastMatch = cleanMatchedPayload[cleanMatchedPayload.length - 1];
if (!lastMatch || ruleMatch.rule.origin !== 'user-agent' || lastMatch.rule.origin !== 'user-agent' ||
ruleMatch.rule.selectorList.text !== lastMatch.rule.selectorList.text ||
mediaText(ruleMatch) !== mediaText(lastMatch)) {
cleanMatchedPayload.push(ruleMatch);
continue;
}
mergeRule(ruleMatch, lastMatch);
}
return cleanMatchedPayload;
function mergeRule(from, to) {
const shorthands = new Map();
const properties = new Map();
for (const entry of to.rule.style.shorthandEntries) {
shorthands.set(entry.name, entry.value);
}
for (const entry of to.rule.style.cssProperties) {
properties.set(entry.name, entry.value);
}
for (const entry of from.rule.style.shorthandEntries) {
shorthands.set(entry.name, entry.value);
}
for (const entry of from.rule.style.cssProperties) {
properties.set(entry.name, entry.value);
}
to.rule.style.shorthandEntries = [...shorthands.entries()].map(([name, value]) => ({ name, value }));
to.rule.style.cssProperties = [...properties.entries()].map(([name, value]) => ({ name, value }));
}
function mediaText(ruleMatch) {
if (!ruleMatch.rule.media) {
return null;
}
return ruleMatch.rule.media.map(media => media.text).join(', ');
}
function cleanUserAgentSelectors(ruleMatch) {
const { matchingSelectors, rule } = ruleMatch;
if (rule.origin !== 'user-agent' || !matchingSelectors.length) {
return;
}
rule.selectorList.selectors = rule.selectorList.selectors.filter((item, i) => matchingSelectors.includes(i));
rule.selectorList.text = rule.selectorList.selectors.map(item => item.text).join(', ');
ruleMatch.matchingSelectors = matchingSelectors.map((item, i) => i);
}
}
}
buildMainCascade(inlinePayload, attributesPayload, matchedPayload, inheritedPayload) {
const nodeCascades = [];
const nodeStyles = [];
function addAttributesStyle() {
if (!attributesPayload) {
return;
}
const style = new CSSStyleDeclaration(this.#cssModelInternal, null, attributesPayload, Type.Attributes);
this.#nodeForStyleInternal.set(style, this.#nodeInternal);
nodeStyles.push(style);
}
// Inline style has the greatest specificity.
if (inlinePayload && this.#nodeInternal.nodeType() === Node.ELEMENT_NODE) {
const style = new CSSStyleDeclaration(this.#cssModelInternal, null, inlinePayload, Type.Inline);
this.#nodeForStyleInternal.set(style, this.#nodeInternal);
nodeStyles.push(style);
}
// Add rules in reverse order to match the cascade order.
let addedAttributesStyle;
for (let i = matchedPayload.length - 1; i >= 0; --i) {
const rule = new CSSStyleRule(this.#cssModelInternal, matchedPayload[i].rule);
if ((rule.isInjected() || rule.isUserAgent()) && !addedAttributesStyle) {
// Show element's Style Attributes after all author rules.
addedAttributesStyle = true;
addAttributesStyle.call(this);
}
this.#nodeForStyleInternal.set(rule.style, this.#nodeInternal);
nodeStyles.push(rule.style);
this.addMatchingSelectors(this.#nodeInternal, rule, matchedPayload[i].matchingSelectors);
}
if (!addedAttributesStyle) {
addAttributesStyle.call(this);
}
nodeCascades.push(new NodeCascade(this, nodeStyles, false /* #isInherited */));
// Walk the node structure and identify styles with inherited properties.
let parentNode = this.#nodeInternal.parentNode;
for (let i = 0; parentNode && inheritedPayload && i < inheritedPayload.length; ++i) {
const inheritedStyles = [];
const entryPayload = inheritedPayload[i];
const inheritedInlineStyle = entryPayload.inlineStyle ?
new CSSStyleDeclaration(this.#cssModelInternal, null, entryPayload.inlineStyle, Type.Inline) :
null;
if (inheritedInlineStyle && this.containsInherited(inheritedInlineStyle)) {
this.#nodeForStyleInternal.set(inheritedInlineStyle, parentNode);
inheritedStyles.push(inheritedInlineStyle);
this.#inheritedStyles.add(inheritedInlineStyle);
}
const inheritedMatchedCSSRules = entryPayload.matchedCSSRules || [];
for (let j = inheritedMatchedCSSRules.length - 1; j >= 0; --j) {
const inheritedRule = new CSSStyleRule(this.#cssModelInternal, inheritedMatchedCSSRules[j].rule);
this.addMatchingSelectors(parentNode, inheritedRule, inheritedMatchedCSSRules[j].matchingSelectors);
if (!this.containsInherited(inheritedRule.style)) {
continue;
}
if (containsStyle(nodeStyles, inheritedRule.style) ||
containsStyle(this.#inheritedStyles, inheritedRule.style)) {
continue;
}
this.#nodeForStyleInternal.set(inheritedRule.style, parentNode);
inheritedStyles.push(inheritedRule.style);
this.#inheritedStyles.add(inheritedRule.style);
}
parentNode = parentNode.parentNode;
nodeCascades.push(new NodeCascade(this, inheritedStyles, true /* #isInherited */));
}
return new DOMInheritanceCascade(nodeCascades, this.#registeredProperties);
function containsStyle(styles, query) {
if (!query.styleSheetId || !query.range) {
return false;
}
for (const style of styles) {
if (query.styleSheetId === style.styleSheetId && style.range && query.range.equal(style.range)) {
return true;
}
}
return false;
}
}
/**
* Pseudo rule matches received via the inspector protocol are grouped by pseudo type.
* For custom highlight pseudos, we need to instead group the rule matches by highlight
* name in order to produce separate cascades for each highlight name. This is necessary
* so that styles of ::highlight(foo) are not shown as overriding styles of ::highlight(bar).
*
* This helper function takes a list of rule matches and generates separate NodeCascades
* for each custom highlight name that was matched.
*/
buildSplitCustomHighlightCascades(rules, node, isInherited, pseudoCascades) {
const splitHighlightRules = new Map();
for (let j = rules.length - 1; j >= 0; --j) {
const highlightNamesToMatchingSelectorIndices = this.customHighlightNamesToMatchingSelectorIndices(rules[j]);
for (const [highlightName, matchingSelectors] of highlightNamesToMatchingSelectorIndices) {
const pseudoRule = new CSSStyleRule(this.#cssModelInternal, rules[j].rule);
this.#nodeForStyleInternal.set(pseudoRule.style, node);
if (isInherited) {
this.#inheritedStyles.add(pseudoRule.style);
}
this.addMatchingSelectors(node, pseudoRule, matchingSelectors);
const ruleListForHighlightName = splitHighlightRules.get(highlightName);
if (ruleListForHighlightName) {
ruleListForHighlightName.push(pseudoRule.style);
}
else {
splitHighlightRules.set(highlightName, [pseudoRule.style]);
}
}
}
for (const [highlightName, highlightStyles] of splitHighlightRules) {
const nodeCascade = new NodeCascade(this, highlightStyles, isInherited, true /* #isHighlightPseudoCascade*/);
const cascadeListForHighlightName = pseudoCascades.get(highlightName);
if (cascadeListForHighlightName) {
cascadeListForHighlightName.push(nodeCascade);
}
else {
pseudoCascades.set(highlightName, [nodeCascade]);
}
}
}
/**
* Return a mapping of the highlight names in the specified RuleMatch to
* the indices of selectors in that selector list with that highlight name.
*
* For example, consider the following ruleset:
* span::highlight(foo), div, #mySpan::highlight(bar), .highlighted::highlight(foo) {
* color: blue;
* }
*
* For a <span id="mySpan" class="highlighted"></span>, a RuleMatch for that span
* would have matchingSelectors [0, 2, 3] indicating that the span
* matches all of the highlight selectors.
*
* For that RuleMatch, this function would produce the following map:
* {
* "foo": [0, 3],
* "bar": [2]
* }
*
* @param ruleMatch
* @returns A mapping of highlight names to lists of indices into the selector
* list associated with ruleMatch. The indices correspond to the selectors in the rule
* associated with the key's highlight name.
*/
customHighlightNamesToMatchingSelectorIndices(ruleMatch) {
const highlightNamesToMatchingSelectors = new Map();
for (let i = 0; i < ruleMatch.matchingSelectors.length; i++) {
const matchingSelectorIndex = ruleMatch.matchingSelectors[i];
const selectorText = ruleMatch.rule.selectorList.selectors[matchingSelectorIndex].text;
const highlightNameMatch = selectorText.match(/::highlight\((.*)\)/);
if (highlightNameMatch) {
const highlightName = highlightNameMatch[1];
const selectorsForName = highlightNamesToMatchingSelectors.get(highlightName);
if (selectorsForName) {
selectorsForName.push(matchingSelectorIndex);
}
else {
highlightNamesToMatchingSelectors.set(highlightName, [matchingSelectorIndex]);
}
}
}
return highlightNamesToMatchingSelectors;
}
buildPseudoCascades(pseudoPayload, inheritedPseudoPayload) {
const pseudoInheritanceCascades = new Map();
const customHighlightPseudoInheritanceCascades = new Map();
if (!pseudoPayload) {
return [pseudoInheritanceCascades, customHighlightPseudoInheritanceCascades];
}
const pseudoCascades = new Map();
const customHighlightPseudoCascades = new Map();
for (let i = 0; i < pseudoPayload.length; ++i) {
const entryPayload = pseudoPayload[i];
// PseudoElement nodes are not created unless "content" css property is set.
const pseudoElement = this.#nodeInternal.pseudoElements().get(entryPayload.pseudoType)?.at(-1) || null;
const pseudoStyles = [];
const rules = entryPayload.matches || [];
if (entryPayload.pseudoType === "highlight" /* Protocol.DOM.PseudoType.Highlight */) {
this.buildSplitCustomHighlightCascades(rules, this.#nodeInternal, false /* #isInherited */, customHighlightPseudoCascades);
}
else {
for (let j = rules.length - 1; j >= 0; --j) {
const pseudoRule = new CSSStyleRule(this.#cssModelInternal, rules[j].rule);
pseudoStyles.push(pseudoRule.style);
const nodeForStyle = cssMetadata().isHighlightPseudoType(entryPayload.pseudoType) ? this.#nodeInternal : pseudoElement;
this.#nodeForStyleInternal.set(pseudoRule.style, nodeForStyle);
if (nodeForStyle) {
this.addMatchingSelectors(nodeForStyle, pseudoRule, rules[j].matchingSelectors);
}
}
const isHighlightPseudoCascade = cssMetadata().isHighlightPseudoType(entryPayload.pseudoType);
const nodeCascade = new NodeCascade(this, pseudoStyles, false /* #isInherited */, isHighlightPseudoCascade /* #isHighlightPseudoCascade*/);
pseudoCascades.set(entryPayload.pseudoType, [nodeCascade]);
}
}
if (inheritedPseudoPayload) {
let parentNode = this.#nodeInternal.parentNode;
for (let i = 0; parentNode && i < inheritedPseudoPayload.length; ++i) {
const inheritedPseudoMatches = inheritedPseudoPayload[i].pseudoElements;
for (let j = 0; j < inheritedPseudoMatches.length; ++j) {
const inheritedEntryPayload = inheritedPseudoMatches[j];
const rules = inheritedEntryPayload.matches || [];
if (inheritedEntryPayload.pseudoType === "highlight" /* Protocol.DOM.PseudoType.Highlight */) {
this.buildSplitCustomHighlightCascades(rules, parentNode, true /* #isInherited */, customHighlightPseudoCascades);
}
else {
const pseudoStyles = [];
for (let k = rules.length - 1; k >= 0; --k) {
const pseudoRule = new CSSStyleRule(this.#cssModelInternal, rules[k].rule);
pseudoStyles.push(pseudoRule.style);
this.#nodeForStyleInternal.set(pseudoRule.style, parentNode);
this.#inheritedStyles.add(pseudoRule.style);
this.addMatchingSelectors(parentNode, pseudoRule, rules[k].matchingSelectors);
}
const isHighlightPseudoCascade = cssMetadata().isHighlightPseudoType(inheritedEntryPayload.pseudoType);
const nodeCascade = new NodeCascade(this, pseudoStyles, true /* #isInherited */, isHighlightPseudoCascade /* #isHighlightPseudoCascade*/);
const cascadeListForPseudoType = pseudoCascades.get(inheritedEntryPayload.pseudoType);
if (cascadeListForPseudoType) {
cascadeListForPseudoType.push(nodeCascade);
}
else {
pseudoCascades.set(inheritedEntryPayload.pseudoType, [nodeCascade]);
}
}
}
parentNode = parentNode.parentNode;
}
}
// Now that we've built the arrays of NodeCascades for each pseudo type, convert them into
// DOMInheritanceCascades.
for (const [pseudoType, nodeCascade] of pseudoCascades.entries()) {
pseudoInheritanceCascades.set(pseudoType, new DOMInheritanceCascade(nodeCascade, this.#registeredProperties));
}
for (const [highlightName, nodeCascade] of customHighlightPseudoCascades.entries()) {
customHighlightPseudoInheritanceCascades.set(highlightName, new DOMInheritanceCascade(nodeCascade, this.#registeredProperties));
}
return [pseudoInheritanceCascades, customHighlightPseudoInheritanceCascades];
}
addMatchingSelectors(node, rule, matchingSelectorIndices) {
for (const matchingSelectorIndex of matchingSelectorIndices) {
const selector = rule.selectors[matchingSelectorIndex];
selector && this.setSelectorMatches(node, selector.text, true);
}
}
node() {
return this.#nodeInternal;
}
cssModel() {
return this.#cssModelInternal;
}
hasMatchingSelectors(rule) {
const matchingSelectors = this.getMatchingSelectors(rule);
return matchingSelectors.length > 0 && this.queryMatches(rule.style);
}
getParentLayoutNodeId() {
return this.#parentLayoutNodeId;
}
getMatchingSelectors(rule) {
const node = this.nodeForStyle(rule.style);
if (!node || typeof node.id !== 'number') {
return [];
}
const map = this.#matchingSelectors.get(node.id);
if (!map) {
return [];
}
const result = [];
for (let i = 0; i < rule.selectors.length; ++i) {
if (map.get(rule.selectors[i].text)) {
result.push(i);
}
}
return result;
}
async recomputeMatchingSelectors(rule) {
const node = this.nodeForStyle(rule.style);
if (!node) {
return;
}
const promises = [];
for (const selector of rule.selectors) {
promises.push(querySelector.call(this, node, selector.text));
}
await Promise.all(promises);
async function querySelector(node, selectorText) {
const ownerDocument = node.ownerDocument;
if (!ownerDocument) {
return;
}
// We assume that "matching" property does not ever change during the
// MatchedStyleResult's lifetime.
if (typeof node.id === 'number') {
const map = this.#matchingSelectors.get(node.id);
if (map && map.has(selectorText)) {
return;
}
}
if (typeof ownerDocument.id !== 'number') {
return;
}
const matchingNodeIds = await this.#nodeInternal.domModel().querySelectorAll(ownerDocument.id, selectorText);
if (matchingNodeIds) {
if (typeof node.id === 'number') {
this.setSelectorMatches(node, selectorText, matchingNodeIds.indexOf(node.id) !== -1);
}
else {
this.setSelectorMatches(node, selectorText, false);
}
}
}
}
addNewRule(rule, node) {
this.#addedStyles.set(rule.style, node);
return this.recomputeMatchingSelectors(rule);
}
setSelectorMatches(node, selectorText, value) {
if (typeof node.id !== 'number') {
return;
}
let map = this.#matchingSelectors.get(node.id);
if (!map) {
map = new Map();
this.#matchingSelectors.set(node.id, map);
}
map.set(selectorText, value);
}
queryMatches(style) {
if (!style.parentRule) {
return true;
}
const parentRule = style.parentRule;
const queries = [...parentRule.media, ...parentRule.containerQueries, ...parentRule.supports, ...parentRule.scopes];
for (const query of queries) {
if (!query.active()) {
return false;
}
}
return true;
}
nodeStyles() {
return this.#mainDOMCascade.styles();
}
registeredProperties() {
return this.#registeredProperties;
}
getRegisteredProperty(name) {
return this.#registeredPropertyMap.get(name);
}
keyframes() {
return this.#keyframesInternal;
}
positionFallbackRules() {
return this.#positionFallbackRules;
}
pseudoStyles(pseudoType) {
const domCascade = this.#pseudoDOMCascades.get(pseudoType);
return domCascade ? domCascade.styles() : [];
}
pseudoTypes() {
return new Set(this.#pseudoDOMCascades.keys());
}
customHighlightPseudoStyles(highlightName) {
const domCascade = this.#customHighlightPseudoDOMCascades.get(highlightName);
return domCascade ? domCascade.styles() : [];
}
customHighlightPseudoNames() {
return new Set(this.#customHighlightPseudoDOMCascades.keys());
}
containsInherited(style) {
const properties = style.allProperties();
for (let i = 0; i < properties.length; ++i) {
const property = properties[i];
// Does this style contain non-overridden inherited property?
if (property.activeInStyle() && cssMetadata().isPropertyInherited(property.name)) {
return true;
}
}
return false;
}
nodeForStyle(style) {
return this.#addedStyles.get(style) || this.#nodeForStyleInternal.get(style) || null;
}
availableCSSVariables(style) {
const domCascade = this.#styleToDOMCascade.get(style) || null;
return domCascade ? domCascade.findAvailableCSSVariables(style) : [];
}
computeCSSVariable(style, variableName) {
const domCascade = this.#styleToDOMCascade.get(style) || null;
return domCascade ? domCascade.computeCSSVariable(style, variableName) : null;
}
computeValue(style, value) {
const domCascade = this.#styleToDOMCascade.get(style) || null;
return domCascade ? domCascade.computeValue(style, value) : null;
}
/**
* Same as computeValue, but to be used for `var(--#name [,...])` values only
*/
computeSingleVariableValue(style, cssVariableValue) {
const domCascade = this.#styleToDOMCascade.get(style) || null;
return domCascade ? domCascade.computeSingleVariableValue(style, cssVariableValue) : null;
}
isInherited(style) {
return this.#inheritedStyles.has(style);
}
propertyState(property) {
const domCascade = this.#styleToDOMCascade.get(property.ownerStyle);
return domCascade ? domCascade.propertyState(property) : null;
}
resetActiveProperties() {
this.#mainDOMCascade.reset();
for (const domCascade of this.#pseudoDOMCascades.values()) {
domCascade.reset();
}
for (const domCascade of this.#customHighlightPseudoDOMCascades.values()) {
domCascade.reset();
}
}
}
class NodeCascade {
#matchedStyles;
styles;
#isInherited;
#isHighlightPseudoCascade;
propertiesState;
activeProperties;
constructor(matchedStyles, styles, isInherited, isHighlightPseudoCascade = false) {
this.#matchedStyles = matchedStyles;
this.styles = styles;
this.#isInherited = isInherited;
this.#isHighlightPseudoCascade = isHighlightPseudoCascade;
this.propertiesState = new Map();
this.activeProperties = new Map();
}
computeActiveProperties() {
this.propertiesState.clear();
this.activeProperties.clear();
for (let i = this.styles.length - 1; i >= 0; i--) {
const style = this.styles[i];
const rule = style.parentRule;
// Compute cascade for CSSStyleRules only.
if (rule && !(rule instanceof CSSStyleRule)) {
continue;
}
if (rule && !this.#matchedStyles.hasMatchingSelectors(rule)) {
continue;
}
for (const property of style.allProperties()) {
// Do not pick non-inherited properties from inherited styles.
const metadata = cssMetadata();
// All properties are inherited for highlight pseudos.
if (this.#isInherited && !this.#isHighlightPseudoCascade && !metadata.isPropertyInherited(property.name)) {
continue;
}
// When a property does not have a range in an otherwise ranged CSSStyleDeclaration,
// we consider it as a non-leading property (see computeLeadingProperties()), and most
// of them are computed longhands. We exclude these from activeProperties calculation,
// and use parsed longhands instead (see below).
if (style.range && !property.range) {
continue;
}
if (!property.activeInStyle()) {
this.propertiesState.set(property, PropertyState.Overloaded);
continue;
}
const canonicalName = metadata.canonicalPropertyName(property.name);
this.updatePropertyState(property, canonicalName);
for (const longhand of property.getLonghandProperties()) {
if (metadata.isCSSPropertyName(longhand.name)) {
this.updatePropertyState(longhand, longhand.name);
}
}
}
}
}
updatePropertyState(propertyWithHigherSpecificity, canonicalName) {
const activeProperty = this.activeProperties.get(canonicalName);
if (activeProperty?.important && !propertyWithHigherSpecificity.important) {
this.propertiesState.set(propertyWithHigherSpecificity, PropertyState.Overloaded);
return;
}
if (activeProperty) {
this.propertiesState.set(activeProperty, PropertyState.Overloaded);
}
this.propertiesState.set(propertyWithHigherSpecificity, PropertyState.Active);
this.activeProperties.set(canonicalName, propertyWithHigherSpecificity);
}
}
class DOMInheritanceCascade {
#nodeCascades;
#propertiesState;
#availableCSSVariables;
#computedCSSVariables;
#initialized;
#styleToNodeCascade;
#registeredProperties;
constructor(nodeCascades, registeredProperties) {
this.#nodeCascades = nodeCascades;
this.#propertiesState = new Map();
this.#availableCSSVariables = new Map();
this.#computedCSSVariables = new Map();
this.#initialized = false;
this.#registeredProperties = registeredProperties;
this.#styleToNodeCascade = new Map();
for (const nodeCascade of nodeCascades) {
for (const style of nodeCascade.styles) {
this.#styleToNodeCascade.set(style, nodeCascade);
}
}
}
findAvailableCSSVariables(style) {
const nodeCascade = this.#styleToNodeCascade.get(style);
if (!nodeCascade) {
return [];
}
this.ensureInitialized();
const availableCSSVariables = this.#availableCSSVariables.get(nodeCascade);
if (!availableCSSVariables) {
return [];
}
return Array.from(availableCSSVariables.keys());
}
computeCSSVariable(style, variableName) {
const nodeCascade = this.#styleToNodeCascade.get(style);
if (!nodeCascade) {
return null;
}
this.ensureInitialized();
const availableCSSVariables = this.#availableCSSVariables.get(nodeCascade);
const computedCSSVariables = this.#computedCSSVariables.get(nodeCascade);
if (!availableCSSVariables || !computedCSSVariables) {
return null;
}
return this.innerComputeCSSVariable(availableCSSVariables, computedCSSVariables, variableName);
}
computeValue(style, value) {
const nodeCascade = this.#styleToNodeCascade.get(style);
if (!nodeCascade) {
return null;
}
this.ensureInitialized();
const availableCSSVariables = this.#availableCSSVariables.get(nodeCascade);
const computedCSSVariables = this.#computedCSSVariables.get(nodeCascade);
if (!availableCSSVariables || !computedCSSVariables) {
return null;
}
return this.innerComputeValue(availableCSSVariables, computedCSSVariables, value);
}
computeSingleVariableValue(style, cssVariableValue) {
const nodeCascade = this.#styleToNodeCascade.get(style);
if (!nodeCascade) {
return null;
}
this.ensureInitialized();
const availableCSSVariables = this.#availableCSSVariables.get(nodeCascade);
const computedCSSVariables = this.#computedCSSVariables.get(nodeCascade);
if (!availableCSSVariables || !computedCSSVariables) {
return null;
}
const computedValue = this.innerComputeValue(availableCSSVariables, computedCSSVariables, cssVariableValue);
const { variableName } = parseCSSVariableNameAndFallback(cssVariableValue);
return { computedValue, fromFallback: variableName !== null && !availableCSSVariables.has(variableName) };
}
innerComputeCSSVariable(availableCSSVariables, computedCSSVariables, variableName) {
if (!availableCSSVariables.has(variableName)) {
return null;
}
if (computedCSSVariables.has(variableName)) {
return computedCSSVariables.get(variableName) || null;
}
// Set dummy value to avoid infinite recursion.
computedCSSVariables.set(variableName, null);
const definedValue = availableCSSVariables.get(variableName);
if (definedValue === undefined || definedValue === null) {
return null;
}
const computedValue = this.innerComputeValue(availableCSSVariables, computedCSSVariables, definedValue);
computedCSSVariables.set(variableName, computedValue);
return computedValue;
}
innerComputeValue(availableCSSVariables, computedCSSVariables, value) {
const results = TextUtils.TextUtils.Utils.splitStringByRegexes(value, [VariableRegex]);
const tokens = [];
for (const result of results) {
if (result.regexIndex === -1) {
tokens.push(result.value);
continue;
}
// process var() function
const { variableName, fallback } = parseCSSVariableNameAndFallback(result.value);
if (!variableName) {
return null;
}
const computedValue = this.innerComputeCSSVariable(availableCSSVariables, computedCSSVariables, variableName);
if (computedValue === null && !fallback) {
return null;
}
if (computedValue === null) {
tokens.push(fallback);
}
else {
tokens.push(computedValue);
}
}
return tokens.map(token => token ? token.trim() : '').join(' ');
}
styles() {
return Array.from(this.#styleToNodeCascade.keys());
}
propertyState(property) {
this.ensureInitialized();
return this.#propertiesState.get(property) || null;
}
reset() {
this.#initialized = false;
this.#propertiesState.clear();
this.#availableCSSVariables.clear();
this.#computedCSSVariables.clear();
}
ensureInitialized() {
if (this.#initialized) {
return;
}
this.#initialized = true;
const activeProperties = new Map();
for (const nodeCascade of this.#nodeCascades) {
nodeCascade.computeActiveProperties();
for (const [property, state] of nodeCascade.propertiesState) {
if (state === PropertyState.Overloaded) {
this.#propertiesState.set(property, PropertyState.Overloaded);
continue;
}
const canonicalName = cssMetadata().canonicalPropertyName(property.name);
if (activeProperties.has(canonicalName)) {
this.#propertiesState.set(property, PropertyState.Overloaded);
continue;
}
activeProperties.set(canonicalName, property);
this.#propertiesState.set(property, PropertyState.Active);
}
}
// If every longhand of the shorthand is not active, then the shorthand is not active too.
for (const [canonicalName, shorthandProperty] of activeProperties) {
const shorthandStyle = shorthandProperty.ownerStyle;
const longhands = shorthandProperty.getLonghandProperties();
if (!longhands.length) {
continue;
}
let hasActiveLonghands = false;
for (const longhand of longhands) {
const longhandCanonicalName = cssMetadata().canonicalPropertyName(longhand.name);
const longhandActiveProperty = activeProperties.get(longhandCanonicalName);
if (!longhandActiveProperty) {
continue;
}
if (longhandActiveProperty.ownerStyle === shorthandStyle) {
hasActiveLonghands = true;
break;
}
}
if (hasActiveLonghands) {
continue;
}
activeProperties.delete(canonicalName);
this.#propertiesState.set(shorthandProperty, PropertyState.Overloaded);
}
// Work inheritance chain backwards to compute visible CSS Variables.
const accumulatedCSSVariables = new Map();
this.#registeredProperties.forEach(rule => accumulatedCSSVariables.set(rule.propertyName(), rule.initialValue()));
for (let i = this.#nodeCascades.length - 1; i >= 0; --i) {
const nodeCascade = this.#nodeCascades[i];
const variableNames = [];
for (const entry of nodeCascade.activeProperties.entries()) {
const propertyName = entry[0];
const property = entry[1];
if (propertyName.startsWith('--')) {
accumulatedCSSVariables.set(propertyName, property.value);
variableNames.push(propertyName);
}
}
const availableCSSVariablesMap = new Map(accumulatedCSSVariables);
const computedVariablesMap = new Map();
this.#availableCSSVariables.set(nodeCascade, availableCSSVariablesMap);
this.#computedCSSVariables.set(nodeCascade, computedVariablesMap);
for (const variableName of variableNames) {
accumulatedCSSVariables.delete(variableName);
accumulatedCSSVariables.set(variableName, this.innerComputeCSSVariable(availableCSSVariablesMap, computedVariablesMap, variableName));
}
}
}
}
// TODO(crbug.com/1167717): Make this a const enum again
// eslint-disable-next-line rulesdir/const_enum
export var PropertyState;
(function (PropertyState) {
PropertyState["Active"] = "Active";
PropertyState["Overloaded"] = "Overloaded";
})(PropertyState || (PropertyState = {}));
//# sourceMappingURL=CSSMatchedStyles.js.map