CSSMatchedStyles.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. export default class CSSMatchedStyles{constructor(cssModel,node,inlinePayload,attributesPayload,matchedPayload,pseudoPayload,inheritedPayload,animationsPayload){this._cssModel=cssModel;this._node=node;this._addedStyles=new Map();this._matchingSelectors=new Map();this._keyframes=[];if(animationsPayload){this._keyframes=animationsPayload.map(rule=>new SDK.CSSKeyframesRule(cssModel,rule));}
  2. this._nodeForStyle=new Map();this._inheritedStyles=new Set();matchedPayload=cleanUserAgentPayload(matchedPayload);for(const inheritedResult of inheritedPayload){inheritedResult.matchedCSSRules=cleanUserAgentPayload(inheritedResult.matchedCSSRules);}
  3. this._mainDOMCascade=this._buildMainCascade(inlinePayload,attributesPayload,matchedPayload,inheritedPayload);this._pseudoDOMCascades=this._buildPseudoCascades(pseudoPayload);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);}}
  4. function cleanUserAgentPayload(payload){for(const ruleMatch of payload){cleanUserAgentSelectors(ruleMatch);}
  5. 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;}
  6. mergeRule(ruleMatch,lastMatch);}
  7. 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);}
  8. for(const entry of to.rule.style.cssProperties){properties.set(entry.name,entry.value);}
  9. for(const entry of from.rule.style.shorthandEntries){shorthands.set(entry.name,entry.value);}
  10. for(const entry of from.rule.style.cssProperties){properties.set(entry.name,entry.value);}
  11. 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)}));}
  12. function mediaText(ruleMatch){if(!ruleMatch.rule.media){return null;}
  13. return ruleMatch.rule.media.map(media=>media.text).join(', ');}
  14. function cleanUserAgentSelectors(ruleMatch){const{matchingSelectors,rule}=ruleMatch;if(rule.origin!=='user-agent'||!matchingSelectors.length){return;}
  15. 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);}}}
  16. _buildMainCascade(inlinePayload,attributesPayload,matchedPayload,inheritedPayload){const nodeCascades=[];const nodeStyles=[];function addAttributesStyle(){if(!attributesPayload){return;}
  17. const style=new SDK.CSSStyleDeclaration(this._cssModel,null,attributesPayload,SDK.CSSStyleDeclaration.Type.Attributes);this._nodeForStyle.set(style,this._node);nodeStyles.push(style);}
  18. 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);}
  19. 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){addedAttributesStyle=true;addAttributesStyle.call(this);}
  20. this._nodeForStyle.set(rule.style,this._node);nodeStyles.push(rule.style);this._addMatchingSelectors(this._node,rule,matchedPayload[i].matchingSelectors);}
  21. if(!addedAttributesStyle){addAttributesStyle.call(this);}
  22. nodeCascades.push(new NodeCascade(this,nodeStyles,false));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);}
  23. 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;}
  24. if(containsStyle(nodeStyles,inheritedRule.style)||containsStyle(this._inheritedStyles,inheritedRule.style)){continue;}
  25. this._nodeForStyle.set(inheritedRule.style,parentNode);inheritedStyles.push(inheritedRule.style);this._inheritedStyles.add(inheritedRule.style);}
  26. parentNode=parentNode.parentNode;nodeCascades.push(new NodeCascade(this,inheritedStyles,true));}
  27. return new DOMInheritanceCascade(nodeCascades);function containsStyle(styles,query){if(!query.styleSheetId||!query.range){return false;}
  28. for(const style of styles){if(query.styleSheetId===style.styleSheetId&&style.range&&query.range.equal(style.range)){return true;}}
  29. return false;}}
  30. _buildPseudoCascades(pseudoPayload){const pseudoCascades=new Map();if(!pseudoPayload){return pseudoCascades;}
  31. for(let i=0;i<pseudoPayload.length;++i){const entryPayload=pseudoPayload[i];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);}}
  32. const nodeCascade=new NodeCascade(this,pseudoStyles,false);pseudoCascades.set(entryPayload.pseudoType,new DOMInheritanceCascade([nodeCascade]));}
  33. return pseudoCascades;}
  34. _addMatchingSelectors(node,rule,matchingSelectorIndices){for(const matchingSelectorIndex of matchingSelectorIndices){const selector=rule.selectors[matchingSelectorIndex];this._setSelectorMatches(node,selector.text,true);}}
  35. node(){return this._node;}
  36. cssModel(){return this._cssModel;}
  37. hasMatchingSelectors(rule){const matchingSelectors=this.matchingSelectors(rule);return matchingSelectors.length>0&&this.mediaMatches(rule.style);}
  38. matchingSelectors(rule){const node=this.nodeForStyle(rule.style);if(!node){return[];}
  39. const map=this._matchingSelectors.get(node.id);if(!map){return[];}
  40. const result=[];for(let i=0;i<rule.selectors.length;++i){if(map.get(rule.selectors[i].text)){result.push(i);}}
  41. return result;}
  42. recomputeMatchingSelectors(rule){const node=this.nodeForStyle(rule.style);if(!node){return Promise.resolve();}
  43. const promises=[];for(const selector of rule.selectors){promises.push(querySelector.call(this,node,selector.text));}
  44. return Promise.all(promises);async function querySelector(node,selectorText){const ownerDocument=node.ownerDocument||null;const map=this._matchingSelectors.get(node.id);if((map&&map.has(selectorText))||!ownerDocument){return;}
  45. const matchingNodeIds=await this._node.domModel().querySelectorAll(ownerDocument.id,selectorText);if(matchingNodeIds){this._setSelectorMatches(node,selectorText,matchingNodeIds.indexOf(node.id)!==-1);}}}
  46. addNewRule(rule,node){this._addedStyles.set(rule.style,node);return this.recomputeMatchingSelectors(rule);}
  47. _setSelectorMatches(node,selectorText,value){let map=this._matchingSelectors.get(node.id);if(!map){map=new Map();this._matchingSelectors.set(node.id,map);}
  48. map.set(selectorText,value);}
  49. mediaMatches(style){const media=style.parentRule?style.parentRule.media:[];for(let i=0;media&&i<media.length;++i){if(!media[i].active()){return false;}}
  50. return true;}
  51. nodeStyles(){return this._mainDOMCascade.styles();}
  52. keyframes(){return this._keyframes;}
  53. pseudoStyles(pseudoType){const domCascade=this._pseudoDOMCascades.get(pseudoType);return domCascade?domCascade.styles():[];}
  54. pseudoTypes(){return new Set(this._pseudoDOMCascades.keys());}
  55. _containsInherited(style){const properties=style.allProperties();for(let i=0;i<properties.length;++i){const property=properties[i];if(property.activeInStyle()&&SDK.cssMetadata().isPropertyInherited(property.name)){return true;}}
  56. return false;}
  57. nodeForStyle(style){return this._addedStyles.get(style)||this._nodeForStyle.get(style)||null;}
  58. availableCSSVariables(style){const domCascade=this._styleToDOMCascade.get(style)||null;return domCascade?domCascade.availableCSSVariables(style):[];}
  59. computeCSSVariable(style,variableName){const domCascade=this._styleToDOMCascade.get(style)||null;return domCascade?domCascade.computeCSSVariable(style,variableName):null;}
  60. computeValue(style,value){const domCascade=this._styleToDOMCascade.get(style)||null;return domCascade?domCascade.computeValue(style,value):null;}
  61. isInherited(style){return this._inheritedStyles.has(style);}
  62. propertyState(property){const domCascade=this._styleToDOMCascade.get(property.ownerStyle);return domCascade?domCascade.propertyState(property):null;}
  63. resetActiveProperties(){this._mainDOMCascade.reset();for(const domCascade of this._pseudoDOMCascades.values()){domCascade.reset();}}}
  64. class NodeCascade{constructor(matchedStyles,styles,isInherited){this._matchedStyles=matchedStyles;this._styles=styles;this._isInherited=isInherited;this._propertiesState=new Map();this._activeProperties=new Map();}
  65. _computeActiveProperties(){this._propertiesState.clear();this._activeProperties.clear();for(const style of this._styles){const rule=style.parentRule;if(rule&&!(rule instanceof SDK.CSSStyleRule)){continue;}
  66. if(rule&&!this._matchedStyles.hasMatchingSelectors(rule)){continue;}
  67. for(const property of style.allProperties()){if(this._isInherited&&!SDK.cssMetadata().isPropertyInherited(property.name)){continue;}
  68. if(!property.activeInStyle()){this._propertiesState.set(property,PropertyState.Overloaded);continue;}
  69. 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;}
  70. if(activeProperty){this._propertiesState.set(activeProperty,PropertyState.Overloaded);}
  71. this._propertiesState.set(property,PropertyState.Active);this._activeProperties.set(canonicalName,property);}}}}
  72. class DOMInheritanceCascade{constructor(nodeCascades){this._nodeCascades=nodeCascades;this._propertiesState=new Map();this._availableCSSVariables=new Map();this._computedCSSVariables=new Map();this._initialized=false;this._styleToNodeCascade=new Map();for(const nodeCascade of nodeCascades){for(const style of nodeCascade._styles){this._styleToNodeCascade.set(style,nodeCascade);}}}
  73. availableCSSVariables(style){const nodeCascade=this._styleToNodeCascade.get(style);if(!nodeCascade){return[];}
  74. this._ensureInitialized();return Array.from(this._availableCSSVariables.get(nodeCascade).keys());}
  75. computeCSSVariable(style,variableName){const nodeCascade=this._styleToNodeCascade.get(style);if(!nodeCascade){return null;}
  76. this._ensureInitialized();const availableCSSVariables=this._availableCSSVariables.get(nodeCascade);const computedCSSVariables=this._computedCSSVariables.get(nodeCascade);return this._innerComputeCSSVariable(availableCSSVariables,computedCSSVariables,variableName);}
  77. computeValue(style,value){const nodeCascade=this._styleToNodeCascade.get(style);if(!nodeCascade){return null;}
  78. this._ensureInitialized();const availableCSSVariables=this._availableCSSVariables.get(nodeCascade);const computedCSSVariables=this._computedCSSVariables.get(nodeCascade);return this._innerComputeValue(availableCSSVariables,computedCSSVariables,value);}
  79. _innerComputeCSSVariable(availableCSSVariables,computedCSSVariables,variableName){if(!availableCSSVariables.has(variableName)){return null;}
  80. if(computedCSSVariables.has(variableName)){return computedCSSVariables.get(variableName);}
  81. computedCSSVariables.set(variableName,null);const definedValue=availableCSSVariables.get(variableName);const computedValue=this._innerComputeValue(availableCSSVariables,computedCSSVariables,definedValue);computedCSSVariables.set(variableName,computedValue);return computedValue;}
  82. _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;}
  83. const regexMatch=result.value.match(/^var\((--[a-zA-Z0-9-_]+)[,]?\s*(.*)\)$/);if(!regexMatch){return null;}
  84. const cssVariable=regexMatch[1];const computedValue=this._innerComputeCSSVariable(availableCSSVariables,computedCSSVariables,cssVariable);if(computedValue===null&&!regexMatch[2]){return null;}
  85. if(computedValue===null){tokens.push(regexMatch[2]);}else{tokens.push(computedValue);}}
  86. return tokens.map(token=>token.trim()).join(' ');}
  87. styles(){return Array.from(this._styleToNodeCascade.keys());}
  88. propertyState(property){this._ensureInitialized();return this._propertiesState.get(property)||null;}
  89. reset(){this._initialized=false;this._propertiesState.clear();this._availableCSSVariables.clear();this._computedCSSVariables.clear();}
  90. _ensureInitialized(){if(this._initialized){return;}
  91. this._initialized=true;const activeProperties=new Map();for(const nodeCascade of this._nodeCascades){nodeCascade._computeActiveProperties();for(const entry of nodeCascade._propertiesState.entries()){const property=(entry[0]);const state=(entry[1]);if(state===PropertyState.Overloaded){this._propertiesState.set(property,PropertyState.Overloaded);continue;}
  92. const canonicalName=SDK.cssMetadata().canonicalPropertyName(property.name);if(activeProperties.has(canonicalName)){this._propertiesState.set(property,PropertyState.Overloaded);continue;}
  93. activeProperties.set(canonicalName,property);this._propertiesState.set(property,PropertyState.Active);}}
  94. for(const entry of activeProperties.entries()){const canonicalName=(entry[0]);const shorthandProperty=(entry[1]);const shorthandStyle=shorthandProperty.ownerStyle;const longhands=shorthandStyle.longhandProperties(shorthandProperty.name);if(!longhands.length){continue;}
  95. let hasActiveLonghands=false;for(const longhand of longhands){const longhandCanonicalName=SDK.cssMetadata().canonicalPropertyName(longhand.name);const longhandActiveProperty=activeProperties.get(longhandCanonicalName);if(!longhandActiveProperty){continue;}
  96. if(longhandActiveProperty.ownerStyle===shorthandStyle){hasActiveLonghands=true;break;}}
  97. if(hasActiveLonghands){continue;}
  98. activeProperties.delete(canonicalName);this._propertiesState.set(shorthandProperty,PropertyState.Overloaded);}
  99. 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=(entry[0]);const property=(entry[1]);if(propertyName.startsWith('--')){accumulatedCSSVariables.set(propertyName,property.value);}}
  100. this._availableCSSVariables.set(nodeCascade,new Map(accumulatedCSSVariables));this._computedCSSVariables.set(nodeCascade,new Map());}}}
  101. export const PropertyState={Active:'Active',Overloaded:'Overloaded'};self.SDK=self.SDK||{};SDK=SDK||{};SDK.CSSMatchedStyles=CSSMatchedStyles;SDK.CSSMatchedStyles.PropertyState=PropertyState;