CSSModel.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. export default class CSSModel extends SDK.SDKModel{constructor(target){super(target);this._domModel=(target.model(SDK.DOMModel));this._sourceMapManager=new SDK.SourceMapManager(target);this._agent=target.cssAgent();this._styleLoader=new ComputedStyleLoader(this);this._resourceTreeModel=target.model(SDK.ResourceTreeModel);if(this._resourceTreeModel){this._resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.MainFrameNavigated,this._resetStyleSheets,this);}
  2. target.registerCSSDispatcher(new CSSDispatcher(this));if(!target.suspended()){this._enable();}
  3. this._styleSheetIdToHeader=new Map();this._styleSheetIdsForURL=new Map();this._originalStyleSheetText=new Map();this._isRuleUsageTrackingEnabled=false;this._sourceMapManager.setEnabled(Common.moduleSetting('cssSourceMapsEnabled').get());Common.moduleSetting('cssSourceMapsEnabled').addChangeListener(event=>this._sourceMapManager.setEnabled((event.data)));}
  4. headersForSourceURL(sourceURL){const headers=[];for(const headerId of this.styleSheetIdsForURL(sourceURL)){const header=this.styleSheetHeaderForId(headerId);if(header){headers.push(header);}}
  5. return headers;}
  6. createRawLocationsByURL(sourceURL,lineNumber,columnNumber){const headers=this.headersForSourceURL(sourceURL);headers.sort(stylesheetComparator);const compareToArgLocation=(_,header)=>lineNumber-header.startLine||columnNumber-header.startColumn;const endIndex=headers.upperBound(undefined,compareToArgLocation);if(!endIndex){return[];}
  7. const locations=[];const last=headers[endIndex-1];for(let index=endIndex-1;index>=0&&headers[index].startLine===last.startLine&&headers[index].startColumn===last.startColumn;--index){if(headers[index].containsLocation(lineNumber,columnNumber)){locations.push(new SDK.CSSLocation(headers[index],lineNumber,columnNumber));}}
  8. return locations;function stylesheetComparator(a,b){return a.startLine-b.startLine||a.startColumn-b.startColumn||a.id.localeCompare(b.id);}}
  9. sourceMapManager(){return this._sourceMapManager;}
  10. static trimSourceURL(text){let sourceURLIndex=text.lastIndexOf('/*# sourceURL=');if(sourceURLIndex===-1){sourceURLIndex=text.lastIndexOf('/*@ sourceURL=');if(sourceURLIndex===-1){return text;}}
  11. const sourceURLLineIndex=text.lastIndexOf('\n',sourceURLIndex);if(sourceURLLineIndex===-1){return text;}
  12. const sourceURLLine=text.substr(sourceURLLineIndex+1).split('\n',1)[0];const sourceURLRegex=/[\040\t]*\/\*[#@] sourceURL=[\040\t]*([^\s]*)[\040\t]*\*\/[\040\t]*$/;if(sourceURLLine.search(sourceURLRegex)===-1){return text;}
  13. return text.substr(0,sourceURLLineIndex)+text.substr(sourceURLLineIndex+sourceURLLine.length+1);}
  14. domModel(){return this._domModel;}
  15. async setStyleText(styleSheetId,range,text,majorChange){try{await this._ensureOriginalStyleSheetText(styleSheetId);const stylePayloads=await this._agent.setStyleTexts([{styleSheetId:styleSheetId,range:range.serializeToObject(),text:text}]);if(!stylePayloads||stylePayloads.length!==1){return false;}
  16. this._domModel.markUndoableState(!majorChange);const edit=new Edit(styleSheetId,range,text,stylePayloads[0]);this._fireStyleSheetChanged(styleSheetId,edit);return true;}catch(e){return false;}}
  17. async setSelectorText(styleSheetId,range,text){Host.userMetrics.actionTaken(Host.UserMetrics.Action.StyleRuleEdited);try{await this._ensureOriginalStyleSheetText(styleSheetId);const selectorPayload=await this._agent.setRuleSelector(styleSheetId,range,text);if(!selectorPayload){return false;}
  18. this._domModel.markUndoableState();const edit=new Edit(styleSheetId,range,text,selectorPayload);this._fireStyleSheetChanged(styleSheetId,edit);return true;}catch(e){return false;}}
  19. async setKeyframeKey(styleSheetId,range,text){Host.userMetrics.actionTaken(Host.UserMetrics.Action.StyleRuleEdited);try{await this._ensureOriginalStyleSheetText(styleSheetId);const payload=await this._agent.setKeyframeKey(styleSheetId,range,text);if(!payload){return false;}
  20. this._domModel.markUndoableState();const edit=new Edit(styleSheetId,range,text,payload);this._fireStyleSheetChanged(styleSheetId,edit);return true;}catch(e){return false;}}
  21. startCoverage(){this._isRuleUsageTrackingEnabled=true;return this._agent.startRuleUsageTracking();}
  22. takeCoverageDelta(){return this._agent.takeCoverageDelta().then(ruleUsage=>ruleUsage||[]);}
  23. stopCoverage(){this._isRuleUsageTrackingEnabled=false;return this._agent.stopRuleUsageTracking();}
  24. async mediaQueriesPromise(){const payload=await this._agent.getMediaQueries();return payload?SDK.CSSMedia.parseMediaArrayPayload(this,payload):[];}
  25. isEnabled(){return this._isEnabled;}
  26. async _enable(){await this._agent.enable();this._isEnabled=true;if(this._isRuleUsageTrackingEnabled){await this.startCoverage();}
  27. this.dispatchEventToListeners(Events.ModelWasEnabled);}
  28. async matchedStylesPromise(nodeId){const response=await this._agent.invoke_getMatchedStylesForNode({nodeId});if(response[Protocol.Error]){return null;}
  29. const node=this._domModel.nodeForId(nodeId);if(!node){return null;}
  30. return new SDK.CSSMatchedStyles(this,(node),response.inlineStyle||null,response.attributesStyle||null,response.matchedCSSRules||[],response.pseudoElements||[],response.inherited||[],response.cssKeyframesRules||[]);}
  31. classNamesPromise(styleSheetId){return this._agent.collectClassNames(styleSheetId).then(classNames=>classNames||[]);}
  32. computedStylePromise(nodeId){return this._styleLoader.computedStylePromise(nodeId);}
  33. async backgroundColorsPromise(nodeId){const response=this._agent.invoke_getBackgroundColors({nodeId});if(response[Protocol.Error]){return null;}
  34. return response;}
  35. platformFontsPromise(nodeId){return this._agent.getPlatformFontsForNode(nodeId);}
  36. allStyleSheets(){const values=this._styleSheetIdToHeader.valuesArray();function styleSheetComparator(a,b){if(a.sourceURL<b.sourceURL){return-1;}else if(a.sourceURL>b.sourceURL){return 1;}
  37. return a.startLine-b.startLine||a.startColumn-b.startColumn;}
  38. values.sort(styleSheetComparator);return values;}
  39. async inlineStylesPromise(nodeId){const response=await this._agent.invoke_getInlineStylesForNode({nodeId});if(response[Protocol.Error]||!response.inlineStyle){return null;}
  40. const inlineStyle=new SDK.CSSStyleDeclaration(this,null,response.inlineStyle,SDK.CSSStyleDeclaration.Type.Inline);const attributesStyle=response.attributesStyle?new SDK.CSSStyleDeclaration(this,null,response.attributesStyle,SDK.CSSStyleDeclaration.Type.Attributes):null;return new InlineStyleResult(inlineStyle,attributesStyle);}
  41. forcePseudoState(node,pseudoClass,enable){const pseudoClasses=node.marker(PseudoStateMarker)||[];if(enable){if(pseudoClasses.indexOf(pseudoClass)>=0){return false;}
  42. pseudoClasses.push(pseudoClass);node.setMarker(PseudoStateMarker,pseudoClasses);}else{if(pseudoClasses.indexOf(pseudoClass)<0){return false;}
  43. pseudoClasses.remove(pseudoClass);if(pseudoClasses.length){node.setMarker(PseudoStateMarker,pseudoClasses);}else{node.setMarker(PseudoStateMarker,null);}}
  44. this._agent.forcePseudoState(node.id,pseudoClasses);this.dispatchEventToListeners(Events.PseudoStateForced,{node:node,pseudoClass:pseudoClass,enable:enable});return true;}
  45. pseudoState(node){return node.marker(PseudoStateMarker)||[];}
  46. async setMediaText(styleSheetId,range,newMediaText){Host.userMetrics.actionTaken(Host.UserMetrics.Action.StyleRuleEdited);try{await this._ensureOriginalStyleSheetText(styleSheetId);const mediaPayload=await this._agent.setMediaText(styleSheetId,range,newMediaText);if(!mediaPayload){return false;}
  47. this._domModel.markUndoableState();const edit=new Edit(styleSheetId,range,newMediaText,mediaPayload);this._fireStyleSheetChanged(styleSheetId,edit);return true;}catch(e){return false;}}
  48. async addRule(styleSheetId,ruleText,ruleLocation){try{await this._ensureOriginalStyleSheetText(styleSheetId);const rulePayload=await this._agent.addRule(styleSheetId,ruleText,ruleLocation);if(!rulePayload){return null;}
  49. this._domModel.markUndoableState();const edit=new Edit(styleSheetId,ruleLocation,ruleText,rulePayload);this._fireStyleSheetChanged(styleSheetId,edit);return new SDK.CSSStyleRule(this,rulePayload);}catch(e){return null;}}
  50. async requestViaInspectorStylesheet(node){const frameId=node.frameId()||(this._resourceTreeModel?this._resourceTreeModel.mainFrame.id:'');const headers=this._styleSheetIdToHeader.valuesArray();const styleSheetHeader=headers.find(header=>header.frameId===frameId&&header.isViaInspector());if(styleSheetHeader){return styleSheetHeader;}
  51. try{const styleSheetId=await this._agent.createStyleSheet(frameId);return styleSheetId&&this._styleSheetIdToHeader.get(styleSheetId)||null;}catch(e){return null;}}
  52. mediaQueryResultChanged(){this.dispatchEventToListeners(Events.MediaQueryResultChanged);}
  53. fontsUpdated(){this.dispatchEventToListeners(Events.FontsUpdated);}
  54. styleSheetHeaderForId(id){return this._styleSheetIdToHeader.get(id)||null;}
  55. styleSheetHeaders(){return this._styleSheetIdToHeader.valuesArray();}
  56. _fireStyleSheetChanged(styleSheetId,edit){this.dispatchEventToListeners(Events.StyleSheetChanged,{styleSheetId:styleSheetId,edit:edit});}
  57. _ensureOriginalStyleSheetText(styleSheetId){const header=this.styleSheetHeaderForId(styleSheetId);if(!header){return Promise.resolve((null));}
  58. let promise=this._originalStyleSheetText.get(header);if(!promise){promise=this.getStyleSheetText(header.id);this._originalStyleSheetText.set(header,promise);this._originalContentRequestedForTest(header);}
  59. return promise;}
  60. _originalContentRequestedForTest(header){}
  61. originalStyleSheetText(header){return this._ensureOriginalStyleSheetText(header.id);}
  62. getAllStyleSheetHeaders(){return this._styleSheetIdToHeader.values();}
  63. _styleSheetAdded(header){console.assert(!this._styleSheetIdToHeader.get(header.styleSheetId));const styleSheetHeader=new SDK.CSSStyleSheetHeader(this,header);this._styleSheetIdToHeader.set(header.styleSheetId,styleSheetHeader);const url=styleSheetHeader.resourceURL();if(!this._styleSheetIdsForURL.get(url)){this._styleSheetIdsForURL.set(url,{});}
  64. const frameIdToStyleSheetIds=this._styleSheetIdsForURL.get(url);let styleSheetIds=frameIdToStyleSheetIds[styleSheetHeader.frameId];if(!styleSheetIds){styleSheetIds=[];frameIdToStyleSheetIds[styleSheetHeader.frameId]=styleSheetIds;}
  65. styleSheetIds.push(styleSheetHeader.id);this._sourceMapManager.attachSourceMap(styleSheetHeader,styleSheetHeader.sourceURL,styleSheetHeader.sourceMapURL);this.dispatchEventToListeners(Events.StyleSheetAdded,styleSheetHeader);}
  66. _styleSheetRemoved(id){const header=this._styleSheetIdToHeader.get(id);console.assert(header);if(!header){return;}
  67. this._styleSheetIdToHeader.remove(id);const url=header.resourceURL();const frameIdToStyleSheetIds=(this._styleSheetIdsForURL.get(url));console.assert(frameIdToStyleSheetIds,'No frameId to styleSheetId map is available for given style sheet URL.');frameIdToStyleSheetIds[header.frameId].remove(id);if(!frameIdToStyleSheetIds[header.frameId].length){delete frameIdToStyleSheetIds[header.frameId];if(!Object.keys(frameIdToStyleSheetIds).length){this._styleSheetIdsForURL.remove(url);}}
  68. this._originalStyleSheetText.remove(header);this._sourceMapManager.detachSourceMap(header);this.dispatchEventToListeners(Events.StyleSheetRemoved,header);}
  69. styleSheetIdsForURL(url){const frameIdToStyleSheetIds=this._styleSheetIdsForURL.get(url);if(!frameIdToStyleSheetIds){return[];}
  70. let result=[];for(const frameId in frameIdToStyleSheetIds){result=result.concat(frameIdToStyleSheetIds[frameId]);}
  71. return result;}
  72. async setStyleSheetText(styleSheetId,newText,majorChange){const header=(this._styleSheetIdToHeader.get(styleSheetId));console.assert(header);newText=CSSModel.trimSourceURL(newText);if(header.hasSourceURL){newText+='\n/*# sourceURL='+header.sourceURL+' */';}
  73. await this._ensureOriginalStyleSheetText(styleSheetId);const response=await this._agent.invoke_setStyleSheetText({styleSheetId:header.id,text:newText});const sourceMapURL=response.sourceMapURL;this._sourceMapManager.detachSourceMap(header);header.setSourceMapURL(sourceMapURL);this._sourceMapManager.attachSourceMap(header,header.sourceURL,header.sourceMapURL);if(sourceMapURL===null){return'Error in CSS.setStyleSheetText';}
  74. this._domModel.markUndoableState(!majorChange);this._fireStyleSheetChanged(styleSheetId);return null;}
  75. async getStyleSheetText(styleSheetId){try{const text=await this._agent.getStyleSheetText(styleSheetId);return text&&CSSModel.trimSourceURL(text);}catch(e){return null;}}
  76. _resetStyleSheets(){const headers=this._styleSheetIdToHeader.valuesArray();this._styleSheetIdsForURL.clear();this._styleSheetIdToHeader.clear();for(let i=0;i<headers.length;++i){this._sourceMapManager.detachSourceMap(headers[i]);this.dispatchEventToListeners(Events.StyleSheetRemoved,headers[i]);}}
  77. suspendModel(){this._isEnabled=false;return this._agent.disable().then(this._resetStyleSheets.bind(this));}
  78. async resumeModel(){return this._enable();}
  79. setEffectivePropertyValueForNode(nodeId,name,value){this._agent.setEffectivePropertyValueForNode(nodeId,name,value);}
  80. cachedMatchedCascadeForNode(node){if(this._cachedMatchedCascadeNode!==node){this.discardCachedMatchedCascade();}
  81. this._cachedMatchedCascadeNode=node;if(!this._cachedMatchedCascadePromise){this._cachedMatchedCascadePromise=this.matchedStylesPromise(node.id);}
  82. return this._cachedMatchedCascadePromise;}
  83. discardCachedMatchedCascade(){delete this._cachedMatchedCascadeNode;delete this._cachedMatchedCascadePromise;}
  84. dispose(){super.dispose();this._sourceMapManager.dispose();}}
  85. export const Events={FontsUpdated:Symbol('FontsUpdated'),MediaQueryResultChanged:Symbol('MediaQueryResultChanged'),ModelWasEnabled:Symbol('ModelWasEnabled'),PseudoStateForced:Symbol('PseudoStateForced'),StyleSheetAdded:Symbol('StyleSheetAdded'),StyleSheetChanged:Symbol('StyleSheetChanged'),StyleSheetRemoved:Symbol('StyleSheetRemoved')};const PseudoStateMarker='pseudo-state-marker';export class Edit{constructor(styleSheetId,oldRange,newText,payload){this.styleSheetId=styleSheetId;this.oldRange=oldRange;this.newRange=TextUtils.TextRange.fromEdit(oldRange,newText);this.newText=newText;this.payload=payload;}}
  86. export class CSSLocation{constructor(header,lineNumber,columnNumber){this._cssModel=header.cssModel();this.styleSheetId=header.id;this.url=header.resourceURL();this.lineNumber=lineNumber;this.columnNumber=columnNumber||0;}
  87. cssModel(){return this._cssModel;}
  88. header(){return this._cssModel.styleSheetHeaderForId(this.styleSheetId);}}
  89. class CSSDispatcher{constructor(cssModel){this._cssModel=cssModel;}
  90. mediaQueryResultChanged(){this._cssModel.mediaQueryResultChanged();}
  91. fontsUpdated(){this._cssModel.fontsUpdated();}
  92. styleSheetChanged(styleSheetId){this._cssModel._fireStyleSheetChanged(styleSheetId);}
  93. styleSheetAdded(header){this._cssModel._styleSheetAdded(header);}
  94. styleSheetRemoved(id){this._cssModel._styleSheetRemoved(id);}}
  95. class ComputedStyleLoader{constructor(cssModel){this._cssModel=cssModel;this._nodeIdToPromise=new Map();}
  96. computedStylePromise(nodeId){let promise=this._nodeIdToPromise.get(nodeId);if(promise){return promise;}
  97. promise=this._cssModel._agent.getComputedStyleForNode(nodeId).then(parsePayload.bind(this));this._nodeIdToPromise.set(nodeId,promise);return promise;function parsePayload(computedPayload){this._nodeIdToPromise.delete(nodeId);if(!computedPayload||!computedPayload.length){return null;}
  98. const result=new Map();for(const property of computedPayload){result.set(property.name,property.value);}
  99. return result;}}}
  100. export class InlineStyleResult{constructor(inlineStyle,attributesStyle){this.inlineStyle=inlineStyle;this.attributesStyle=attributesStyle;}}
  101. self.SDK=self.SDK||{};SDK=SDK||{};SDK.CSSModel=CSSModel;SDK.CSSModel.Events=Events;SDK.CSSModel.Edit=Edit;SDK.CSSModel.InlineStyleResult=InlineStyleResult;SDK.CSSLocation=CSSLocation;SDK.SDKModel.register(SDK.CSSModel,SDK.Target.Capability.DOM,true);SDK.CSSModel.RuleUsage;SDK.CSSModel.ContrastInfo;