| // 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. |
| /** |
| * @unrestricted |
| */ |
| export default class CSSMatchedStyles { |
| /** |
| * @param {!SDK.CSSModel} cssModel |
| * @param {!SDK.DOMNode} node |
| * @param {?Protocol.CSS.CSSStyle} inlinePayload |
| * @param {?Protocol.CSS.CSSStyle} attributesPayload |
| * @param {!Array.<!Protocol.CSS.RuleMatch>} matchedPayload |
| * @param {!Array.<!Protocol.CSS.PseudoElementMatches>} pseudoPayload |
| * @param {!Array.<!Protocol.CSS.InheritedStyleEntry>} inheritedPayload |
| * @param {!Array.<!Protocol.CSS.CSSKeyframesRule>} animationsPayload |
| */ |
| constructor( |
| cssModel, |
| node, |
| inlinePayload, |
| attributesPayload, |
| matchedPayload, |
| pseudoPayload, |
| inheritedPayload, |
| animationsPayload) { |
| this._cssModel = cssModel; |
| this._node = node; |
| /** @type {!Map<!SDK.CSSStyleDeclaration, !SDK.DOMNode>} */ |
| this._addedStyles = new Map(); |
| /** @type {!Map<!Protocol.DOM.NodeId, !Map<string, boolean>>} */ |
| this._matchingSelectors = new Map(); |
| this._keyframes = []; |
| if (animationsPayload) { |
| this._keyframes = animationsPayload.map(rule => new SDK.CSSKeyframesRule(cssModel, rule)); |
| } |
| |
| /** @type {!Map<!SDK.CSSStyleDeclaration, ?SDK.DOMNode>} */ |
| this._nodeForStyle = new Map(); |
| /** @type {!Set<!SDK.CSSStyleDeclaration>} */ |
| 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._buildPseudoCascades(pseudoPayload); |
| |
| /** @type {!Map<!SDK.CSSStyleDeclaration, !DOMInheritanceCascade>} */ |
| this._styleToDOMCascade = new Map(); |
| for (const domCascade of Array.from(this._pseudoDOMCascades.values()).concat(this._mainDOMCascade)) { |
| for (const style of domCascade.styles()) { |
| this._styleToDOMCascade.set(style, domCascade); |
| } |
| } |
| |
| /** |
| * @param {!Array<!Protocol.CSS.RuleMatch>} payload |
| * @return {!Array<!Protocol.CSS.RuleMatch>} |
| */ |
| 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.peekLast(); |
| 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; |
| |
| /** |
| * @param {!Protocol.CSS.RuleMatch} from |
| * @param {!Protocol.CSS.RuleMatch} to |
| */ |
| function mergeRule(from, to) { |
| const shorthands = /** @type {!Map<string, string>} */ (new Map()); |
| const properties = /** @type {!Map<string, string>} */ (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.keysArray().map(name => ({name, value: shorthands.get(name)})); |
| to.rule.style.cssProperties = properties.keysArray().map(name => ({name, value: properties.get(name)})); |
| } |
| |
| /** |
| * @param {!Protocol.CSS.RuleMatch} ruleMatch |
| * @return {?string} |
| */ |
| function mediaText(ruleMatch) { |
| if (!ruleMatch.rule.media) { |
| return null; |
| } |
| return ruleMatch.rule.media.map(media => media.text).join(', '); |
| } |
| |
| /** |
| * @param {!Protocol.CSS.RuleMatch} ruleMatch |
| */ |
| 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); |
| } |
| } |
| } |
| |
| /** |
| * @param {?Protocol.CSS.CSSStyle} inlinePayload |
| * @param {?Protocol.CSS.CSSStyle} attributesPayload |
| * @param {!Array.<!Protocol.CSS.RuleMatch>} matchedPayload |
| * @param {!Array.<!Protocol.CSS.InheritedStyleEntry>} inheritedPayload |
| * @return {!DOMInheritanceCascade} |
| */ |
| _buildMainCascade(inlinePayload, attributesPayload, matchedPayload, inheritedPayload) { |
| /** @type {!Array<!NodeCascade>} */ |
| const nodeCascades = []; |
| |
| /** @type {!Array<!SDK.CSSStyleDeclaration>} */ |
| const nodeStyles = []; |
| |
| /** |
| * @this {CSSMatchedStyles} |
| */ |
| function addAttributesStyle() { |
| if (!attributesPayload) { |
| return; |
| } |
| const style = |
| new SDK.CSSStyleDeclaration(this._cssModel, null, attributesPayload, SDK.CSSStyleDeclaration.Type.Attributes); |
| this._nodeForStyle.set(style, this._node); |
| nodeStyles.push(style); |
| } |
| |
| // Inline style has the greatest specificity. |
| if (inlinePayload && this._node.nodeType() === Node.ELEMENT_NODE) { |
| const style = |
| new SDK.CSSStyleDeclaration(this._cssModel, null, inlinePayload, SDK.CSSStyleDeclaration.Type.Inline); |
| this._nodeForStyle.set(style, this._node); |
| 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 SDK.CSSStyleRule(this._cssModel, matchedPayload[i].rule); |
| if ((rule.isInjected() || rule.isUserAgent()) && !addedAttributesStyle) { |
| // Show element's Style Attributes after all author rules. |
| addedAttributesStyle = true; |
| addAttributesStyle.call(this); |
| } |
| this._nodeForStyle.set(rule.style, this._node); |
| nodeStyles.push(rule.style); |
| this._addMatchingSelectors(this._node, 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._node.parentNode; |
| for (let i = 0; parentNode && inheritedPayload && i < inheritedPayload.length; ++i) { |
| const inheritedStyles = []; |
| const entryPayload = inheritedPayload[i]; |
| const inheritedInlineStyle = entryPayload.inlineStyle ? |
| new SDK.CSSStyleDeclaration( |
| this._cssModel, null, entryPayload.inlineStyle, SDK.CSSStyleDeclaration.Type.Inline) : |
| null; |
| if (inheritedInlineStyle && this._containsInherited(inheritedInlineStyle)) { |
| this._nodeForStyle.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 SDK.CSSStyleRule(this._cssModel, 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._nodeForStyle.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); |
| |
| /** |
| * @param {!Array<!SDK.CSSStyleDeclaration>|!Set<!SDK.CSSStyleDeclaration>} styles |
| * @param {!SDK.CSSStyleDeclaration} query |
| * @return {boolean} |
| */ |
| 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; |
| } |
| } |
| |
| /** |
| * @param {!Array.<!Protocol.CSS.PseudoElementMatches>} pseudoPayload |
| * @return {!Map<!Protocol.DOM.PseudoType, !DOMInheritanceCascade>} |
| */ |
| _buildPseudoCascades(pseudoPayload) { |
| /** @type {!Map<!Protocol.DOM.PseudoType, !DOMInheritanceCascade>} */ |
| const pseudoCascades = new Map(); |
| if (!pseudoPayload) { |
| return pseudoCascades; |
| } |
| 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._node.pseudoElements().get(entryPayload.pseudoType) || null; |
| const pseudoStyles = []; |
| const rules = entryPayload.matches || []; |
| for (let j = rules.length - 1; j >= 0; --j) { |
| const pseudoRule = new SDK.CSSStyleRule(this._cssModel, rules[j].rule); |
| pseudoStyles.push(pseudoRule.style); |
| this._nodeForStyle.set(pseudoRule.style, pseudoElement); |
| if (pseudoElement) { |
| this._addMatchingSelectors(pseudoElement, pseudoRule, rules[j].matchingSelectors); |
| } |
| } |
| const nodeCascade = new NodeCascade(this, pseudoStyles, false /* isInherited */); |
| pseudoCascades.set(entryPayload.pseudoType, new DOMInheritanceCascade([nodeCascade])); |
| } |
| return pseudoCascades; |
| } |
| |
| /** |
| * @param {!SDK.DOMNode} node |
| * @param {!SDK.CSSStyleRule} rule |
| * @param {!Array<number>} matchingSelectorIndices |
| * @this {CSSMatchedStyles} |
| */ |
| _addMatchingSelectors(node, rule, matchingSelectorIndices) { |
| for (const matchingSelectorIndex of matchingSelectorIndices) { |
| const selector = rule.selectors[matchingSelectorIndex]; |
| this._setSelectorMatches(node, selector.text, true); |
| } |
| } |
| |
| /** |
| * @return {!SDK.DOMNode} |
| */ |
| node() { |
| return this._node; |
| } |
| |
| /** |
| * @return {!SDK.CSSModel} |
| */ |
| cssModel() { |
| return this._cssModel; |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleRule} rule |
| * @return {boolean} |
| */ |
| hasMatchingSelectors(rule) { |
| const matchingSelectors = this.matchingSelectors(rule); |
| return matchingSelectors.length > 0 && this.mediaMatches(rule.style); |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleRule} rule |
| * @return {!Array<number>} |
| */ |
| matchingSelectors(rule) { |
| const node = this.nodeForStyle(rule.style); |
| if (!node) { |
| 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; |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleRule} rule |
| * @return {!Promise} |
| */ |
| recomputeMatchingSelectors(rule) { |
| const node = this.nodeForStyle(rule.style); |
| if (!node) { |
| return Promise.resolve(); |
| } |
| const promises = []; |
| for (const selector of rule.selectors) { |
| promises.push(querySelector.call(this, node, selector.text)); |
| } |
| return Promise.all(promises); |
| |
| /** |
| * @param {!SDK.DOMNode} node |
| * @param {string} selectorText |
| * @this {CSSMatchedStyles} |
| */ |
| async function querySelector(node, selectorText) { |
| const ownerDocument = node.ownerDocument || null; |
| // We assume that "matching" property does not ever change during the |
| // MatchedStyleResult's lifetime. |
| const map = this._matchingSelectors.get(node.id); |
| if ((map && map.has(selectorText)) || !ownerDocument) { |
| return; |
| } |
| |
| const matchingNodeIds = await this._node.domModel().querySelectorAll(ownerDocument.id, selectorText); |
| |
| if (matchingNodeIds) { |
| this._setSelectorMatches(node, selectorText, matchingNodeIds.indexOf(node.id) !== -1); |
| } |
| } |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleRule} rule |
| * @param {!SDK.DOMNode} node |
| * @return {!Promise} |
| */ |
| addNewRule(rule, node) { |
| this._addedStyles.set(rule.style, node); |
| return this.recomputeMatchingSelectors(rule); |
| } |
| |
| /** |
| * @param {!SDK.DOMNode} node |
| * @param {string} selectorText |
| * @param {boolean} value |
| */ |
| _setSelectorMatches(node, selectorText, value) { |
| let map = this._matchingSelectors.get(node.id); |
| if (!map) { |
| map = new Map(); |
| this._matchingSelectors.set(node.id, map); |
| } |
| map.set(selectorText, value); |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleDeclaration} style |
| * @return {boolean} |
| */ |
| mediaMatches(style) { |
| const media = style.parentRule ? style.parentRule.media : []; |
| for (let i = 0; media && i < media.length; ++i) { |
| if (!media[i].active()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * @return {!Array<!SDK.CSSStyleDeclaration>} |
| */ |
| nodeStyles() { |
| return this._mainDOMCascade.styles(); |
| } |
| |
| /** |
| * @return {!Array.<!SDK.CSSKeyframesRule>} |
| */ |
| keyframes() { |
| return this._keyframes; |
| } |
| |
| /** |
| * @param {!Protocol.DOM.PseudoType} pseudoType |
| * @return {!Array<!SDK.CSSStyleDeclaration>} |
| */ |
| pseudoStyles(pseudoType) { |
| const domCascade = this._pseudoDOMCascades.get(pseudoType); |
| return domCascade ? domCascade.styles() : []; |
| } |
| |
| /** |
| * @return {!Set<!Protocol.DOM.PseudoType>} |
| */ |
| pseudoTypes() { |
| return new Set(this._pseudoDOMCascades.keys()); |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleDeclaration} style |
| * @return {boolean} |
| */ |
| _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() && SDK.cssMetadata().isPropertyInherited(property.name)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleDeclaration} style |
| * @return {?SDK.DOMNode} |
| */ |
| nodeForStyle(style) { |
| return this._addedStyles.get(style) || this._nodeForStyle.get(style) || null; |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleDeclaration} style |
| * @return {!Array<string>} |
| */ |
| availableCSSVariables(style) { |
| const domCascade = this._styleToDOMCascade.get(style) || null; |
| return domCascade ? domCascade.availableCSSVariables(style) : []; |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleDeclaration} style |
| * @param {string} variableName |
| * @return {?string} |
| */ |
| computeCSSVariable(style, variableName) { |
| const domCascade = this._styleToDOMCascade.get(style) || null; |
| return domCascade ? domCascade.computeCSSVariable(style, variableName) : null; |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleDeclaration} style |
| * @param {string} value |
| * @return {?string} |
| */ |
| computeValue(style, value) { |
| const domCascade = this._styleToDOMCascade.get(style) || null; |
| return domCascade ? domCascade.computeValue(style, value) : null; |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleDeclaration} style |
| * @return {boolean} |
| */ |
| isInherited(style) { |
| return this._inheritedStyles.has(style); |
| } |
| |
| /** |
| * @param {!SDK.CSSProperty} property |
| * @return {?PropertyState} |
| */ |
| 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(); |
| } |
| } |
| } |
| |
| class NodeCascade { |
| /** |
| * @param {!CSSMatchedStyles} matchedStyles |
| * @param {!Array<!SDK.CSSStyleDeclaration>} styles |
| * @param {boolean} isInherited |
| */ |
| constructor(matchedStyles, styles, isInherited) { |
| this._matchedStyles = matchedStyles; |
| this._styles = styles; |
| this._isInherited = isInherited; |
| /** @type {!Map<!SDK.CSSProperty, !PropertyState>} */ |
| this._propertiesState = new Map(); |
| /** @type {!Map.<string, !SDK.CSSProperty>} */ |
| this._activeProperties = new Map(); |
| } |
| |
| _computeActiveProperties() { |
| this._propertiesState.clear(); |
| this._activeProperties.clear(); |
| |
| for (const style of this._styles) { |
| const rule = style.parentRule; |
| // Compute cascade for CSSStyleRules only. |
| if (rule && !(rule instanceof SDK.CSSStyleRule)) { |
| continue; |
| } |
| if (rule && !this._matchedStyles.hasMatchingSelectors(rule)) { |
| continue; |
| } |
| |
| for (const property of style.allProperties()) { |
| // Do not pick non-inherited properties from inherited styles. |
| if (this._isInherited && !SDK.cssMetadata().isPropertyInherited(property.name)) { |
| continue; |
| } |
| |
| if (!property.activeInStyle()) { |
| this._propertiesState.set(property, PropertyState.Overloaded); |
| continue; |
| } |
| |
| const canonicalName = SDK.cssMetadata().canonicalPropertyName(property.name); |
| const activeProperty = this._activeProperties.get(canonicalName); |
| if (activeProperty && (activeProperty.important || !property.important)) { |
| this._propertiesState.set(property, PropertyState.Overloaded); |
| continue; |
| } |
| |
| if (activeProperty) { |
| this._propertiesState.set(activeProperty, PropertyState.Overloaded); |
| } |
| this._propertiesState.set(property, PropertyState.Active); |
| this._activeProperties.set(canonicalName, property); |
| } |
| } |
| } |
| } |
| |
| class DOMInheritanceCascade { |
| /** |
| * @param {!Array<!NodeCascade>} nodeCascades |
| */ |
| constructor(nodeCascades) { |
| this._nodeCascades = nodeCascades; |
| /** @type {!Map<!SDK.CSSProperty, !PropertyState>} */ |
| this._propertiesState = new Map(); |
| /** @type {!Map<!NodeCascade, !Map<string, string>>} */ |
| this._availableCSSVariables = new Map(); |
| /** @type {!Map<!NodeCascade, !Map<string, ?string>>} */ |
| this._computedCSSVariables = new Map(); |
| this._initialized = false; |
| |
| /** @type {!Map<!SDK.CSSStyleDeclaration, !NodeCascade>} */ |
| this._styleToNodeCascade = new Map(); |
| for (const nodeCascade of nodeCascades) { |
| for (const style of nodeCascade._styles) { |
| this._styleToNodeCascade.set(style, nodeCascade); |
| } |
| } |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleDeclaration} style |
| * @return {!Array<string>} |
| */ |
| availableCSSVariables(style) { |
| const nodeCascade = this._styleToNodeCascade.get(style); |
| if (!nodeCascade) { |
| return []; |
| } |
| this._ensureInitialized(); |
| return Array.from(this._availableCSSVariables.get(nodeCascade).keys()); |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleDeclaration} style |
| * @param {string} variableName |
| * @return {?string} |
| */ |
| 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); |
| return this._innerComputeCSSVariable(availableCSSVariables, computedCSSVariables, variableName); |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleDeclaration} style |
| * @param {string} value |
| * @return {?string} |
| */ |
| 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); |
| return this._innerComputeValue(availableCSSVariables, computedCSSVariables, value); |
| } |
| |
| /** |
| * @param {!Map<string, string>} availableCSSVariables |
| * @param {!Map<string, ?string>} computedCSSVariables |
| * @param {string} variableName |
| * @return {?string} |
| */ |
| _innerComputeCSSVariable(availableCSSVariables, computedCSSVariables, variableName) { |
| if (!availableCSSVariables.has(variableName)) { |
| return null; |
| } |
| if (computedCSSVariables.has(variableName)) { |
| return computedCSSVariables.get(variableName); |
| } |
| // Set dummy value to avoid infinite recursion. |
| computedCSSVariables.set(variableName, null); |
| const definedValue = availableCSSVariables.get(variableName); |
| const computedValue = this._innerComputeValue(availableCSSVariables, computedCSSVariables, definedValue); |
| computedCSSVariables.set(variableName, computedValue); |
| return computedValue; |
| } |
| |
| /** |
| * @param {!Map<string, string>} availableCSSVariables |
| * @param {!Map<string, ?string>} computedCSSVariables |
| * @param {string} value |
| * @return {?string} |
| */ |
| _innerComputeValue(availableCSSVariables, computedCSSVariables, value) { |
| const results = TextUtils.TextUtils.splitStringByRegexes(value, [SDK.CSSMetadata.VariableRegex]); |
| const tokens = []; |
| for (const result of results) { |
| if (result.regexIndex === -1) { |
| tokens.push(result.value); |
| continue; |
| } |
| // process var() function |
| const regexMatch = result.value.match(/^var\((--[a-zA-Z0-9-_]+)[,]?\s*(.*)\)$/); |
| if (!regexMatch) { |
| return null; |
| } |
| const cssVariable = regexMatch[1]; |
| const computedValue = this._innerComputeCSSVariable(availableCSSVariables, computedCSSVariables, cssVariable); |
| if (computedValue === null && !regexMatch[2]) { |
| return null; |
| } |
| if (computedValue === null) { |
| tokens.push(regexMatch[2]); |
| } else { |
| tokens.push(computedValue); |
| } |
| } |
| return tokens.map(token => token.trim()).join(' '); |
| } |
| |
| /** |
| * @return {!Array<!SDK.CSSStyleDeclaration>} |
| */ |
| styles() { |
| return Array.from(this._styleToNodeCascade.keys()); |
| } |
| |
| /** |
| * @param {!SDK.CSSProperty} property |
| * @return {?PropertyState} |
| */ |
| 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 entry of nodeCascade._propertiesState.entries()) { |
| const property = /** @type {!SDK.CSSProperty} */ (entry[0]); |
| const state = /** @type {!PropertyState} */ (entry[1]); |
| if (state === PropertyState.Overloaded) { |
| this._propertiesState.set(property, PropertyState.Overloaded); |
| continue; |
| } |
| const canonicalName = SDK.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 entry of activeProperties.entries()) { |
| const canonicalName = /** @type {string} */ (entry[0]); |
| const shorthandProperty = /** @type {!SDK.CSSProperty} */ (entry[1]); |
| const shorthandStyle = shorthandProperty.ownerStyle; |
| const longhands = shorthandStyle.longhandProperties(shorthandProperty.name); |
| if (!longhands.length) { |
| continue; |
| } |
| let hasActiveLonghands = false; |
| for (const longhand of longhands) { |
| const longhandCanonicalName = SDK.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(); |
| for (let i = this._nodeCascades.length - 1; i >= 0; --i) { |
| const nodeCascade = this._nodeCascades[i]; |
| for (const entry of nodeCascade._activeProperties.entries()) { |
| const propertyName = /** @type {string} */ (entry[0]); |
| const property = /** @type {!SDK.CSSProperty} */ (entry[1]); |
| if (propertyName.startsWith('--')) { |
| accumulatedCSSVariables.set(propertyName, property.value); |
| } |
| } |
| this._availableCSSVariables.set(nodeCascade, new Map(accumulatedCSSVariables)); |
| this._computedCSSVariables.set(nodeCascade, new Map()); |
| } |
| } |
| } |
| |
| /** @enum {string} */ |
| export const PropertyState = { |
| Active: 'Active', |
| Overloaded: 'Overloaded' |
| }; |
| |
| /* Legacy exported object */ |
| self.SDK = self.SDK || {}; |
| |
| /* Legacy exported object */ |
| SDK = SDK || {}; |
| |
| /** @constructor */ |
| SDK.CSSMatchedStyles = CSSMatchedStyles; |
| |
| /** @enum {string} */ |
| SDK.CSSMatchedStyles.PropertyState = PropertyState; |