| /* |
| * Copyright (C) 2010 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * @unrestricted |
| */ |
| export default class CSSModel extends SDK.SDKModel { |
| /** |
| * @param {!SDK.Target} target |
| */ |
| constructor(target) { |
| super(target); |
| this._domModel = /** @type {!SDK.DOMModel} */ (target.model(SDK.DOMModel)); |
| /** @type {!SDK.SourceMapManager<!SDK.CSSStyleSheetHeader>} */ |
| 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); |
| } |
| target.registerCSSDispatcher(new CSSDispatcher(this)); |
| if (!target.suspended()) { |
| this._enable(); |
| } |
| /** @type {!Map.<string, !SDK.CSSStyleSheetHeader>} */ |
| this._styleSheetIdToHeader = new Map(); |
| /** @type {!Map.<string, !Object.<!Protocol.Page.FrameId, !Array.<!Protocol.CSS.StyleSheetId>>>} */ |
| this._styleSheetIdsForURL = new Map(); |
| |
| /** @type {!Map.<!SDK.CSSStyleSheetHeader, !Promise<?string>>} */ |
| this._originalStyleSheetText = new Map(); |
| |
| /** @type {boolean} */ |
| this._isRuleUsageTrackingEnabled = false; |
| |
| this._sourceMapManager.setEnabled(Common.moduleSetting('cssSourceMapsEnabled').get()); |
| Common.moduleSetting('cssSourceMapsEnabled') |
| .addChangeListener(event => this._sourceMapManager.setEnabled(/** @type {boolean} */ (event.data))); |
| } |
| |
| /** |
| * @param {string} sourceURL |
| * @return {!Array<!SDK.CSSStyleSheetHeader>} |
| */ |
| headersForSourceURL(sourceURL) { |
| const headers = []; |
| for (const headerId of this.styleSheetIdsForURL(sourceURL)) { |
| const header = this.styleSheetHeaderForId(headerId); |
| if (header) { |
| headers.push(header); |
| } |
| } |
| return headers; |
| } |
| |
| /** |
| * @param {string} sourceURL |
| * @param {number} lineNumber |
| * @param {number} columnNumber |
| * @return {!Array<!CSSLocation>} |
| */ |
| 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 []; |
| } |
| 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)); |
| } |
| } |
| |
| |
| return locations; |
| /** |
| * @param {!SDK.CSSStyleSheetHeader} a |
| * @param {!SDK.CSSStyleSheetHeader} b |
| * @return {number} |
| */ |
| function stylesheetComparator(a, b) { |
| return a.startLine - b.startLine || a.startColumn - b.startColumn || a.id.localeCompare(b.id); |
| } |
| } |
| |
| /** |
| * @return {!SDK.SourceMapManager<!SDK.CSSStyleSheetHeader>} |
| */ |
| sourceMapManager() { |
| return this._sourceMapManager; |
| } |
| |
| /** |
| * @param {string} text |
| * @return {string} |
| */ |
| static trimSourceURL(text) { |
| let sourceURLIndex = text.lastIndexOf('/*# sourceURL='); |
| if (sourceURLIndex === -1) { |
| sourceURLIndex = text.lastIndexOf('/*@ sourceURL='); |
| if (sourceURLIndex === -1) { |
| return text; |
| } |
| } |
| const sourceURLLineIndex = text.lastIndexOf('\n', sourceURLIndex); |
| if (sourceURLLineIndex === -1) { |
| return text; |
| } |
| 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; |
| } |
| return text.substr(0, sourceURLLineIndex) + text.substr(sourceURLLineIndex + sourceURLLine.length + 1); |
| } |
| |
| /** |
| * @return {!SDK.DOMModel} |
| */ |
| domModel() { |
| return this._domModel; |
| } |
| |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| * @param {!TextUtils.TextRange} range |
| * @param {string} text |
| * @param {boolean} majorChange |
| * @return {!Promise<boolean>} |
| */ |
| 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; |
| } |
| |
| this._domModel.markUndoableState(!majorChange); |
| const edit = new Edit(styleSheetId, range, text, stylePayloads[0]); |
| this._fireStyleSheetChanged(styleSheetId, edit); |
| return true; |
| } catch (e) { |
| return false; |
| } |
| } |
| |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| * @param {!TextUtils.TextRange} range |
| * @param {string} text |
| * @return {!Promise<boolean>} |
| */ |
| 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; |
| } |
| this._domModel.markUndoableState(); |
| const edit = new Edit(styleSheetId, range, text, selectorPayload); |
| this._fireStyleSheetChanged(styleSheetId, edit); |
| return true; |
| } catch (e) { |
| return false; |
| } |
| } |
| |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| * @param {!TextUtils.TextRange} range |
| * @param {string} text |
| * @return {!Promise<boolean>} |
| */ |
| 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; |
| } |
| this._domModel.markUndoableState(); |
| const edit = new Edit(styleSheetId, range, text, payload); |
| this._fireStyleSheetChanged(styleSheetId, edit); |
| return true; |
| } catch (e) { |
| return false; |
| } |
| } |
| |
| startCoverage() { |
| this._isRuleUsageTrackingEnabled = true; |
| return this._agent.startRuleUsageTracking(); |
| } |
| |
| /** |
| * @return {!Promise<!Array<!Protocol.CSS.RuleUsage>>} |
| */ |
| takeCoverageDelta() { |
| return this._agent.takeCoverageDelta().then(ruleUsage => ruleUsage || []); |
| } |
| |
| /** |
| * @return {!Promise} |
| */ |
| stopCoverage() { |
| this._isRuleUsageTrackingEnabled = false; |
| return this._agent.stopRuleUsageTracking(); |
| } |
| |
| /** |
| * @return {!Promise<!Array<!SDK.CSSMedia>>} |
| */ |
| async mediaQueriesPromise() { |
| const payload = await this._agent.getMediaQueries(); |
| return payload ? SDK.CSSMedia.parseMediaArrayPayload(this, payload) : []; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isEnabled() { |
| return this._isEnabled; |
| } |
| |
| /** |
| * @return {!Promise} |
| */ |
| async _enable() { |
| await this._agent.enable(); |
| this._isEnabled = true; |
| if (this._isRuleUsageTrackingEnabled) { |
| await this.startCoverage(); |
| } |
| this.dispatchEventToListeners(Events.ModelWasEnabled); |
| } |
| |
| /** |
| * @param {!Protocol.DOM.NodeId} nodeId |
| * @return {!Promise<?SDK.CSSMatchedStyles>} |
| */ |
| async matchedStylesPromise(nodeId) { |
| const response = await this._agent.invoke_getMatchedStylesForNode({nodeId}); |
| |
| if (response[Protocol.Error]) { |
| return null; |
| } |
| |
| const node = this._domModel.nodeForId(nodeId); |
| if (!node) { |
| return null; |
| } |
| |
| return new SDK.CSSMatchedStyles( |
| this, /** @type {!SDK.DOMNode} */ (node), response.inlineStyle || null, response.attributesStyle || null, |
| response.matchedCSSRules || [], response.pseudoElements || [], response.inherited || [], |
| response.cssKeyframesRules || []); |
| } |
| |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| * @return {!Promise<!Array<string>>} |
| */ |
| classNamesPromise(styleSheetId) { |
| return this._agent.collectClassNames(styleSheetId).then(classNames => classNames || []); |
| } |
| |
| /** |
| * @param {!Protocol.DOM.NodeId} nodeId |
| * @return {!Promise<?Map<string, string>>} |
| */ |
| computedStylePromise(nodeId) { |
| return this._styleLoader.computedStylePromise(nodeId); |
| } |
| |
| /** |
| * @param {number} nodeId |
| * @return {!Promise<?CSSModel.ContrastInfo>} |
| */ |
| async backgroundColorsPromise(nodeId) { |
| const response = this._agent.invoke_getBackgroundColors({nodeId}); |
| if (response[Protocol.Error]) { |
| return null; |
| } |
| |
| return response; |
| } |
| |
| /** |
| * @param {number} nodeId |
| * @return {!Promise<?Array<!Protocol.CSS.PlatformFontUsage>>} |
| */ |
| platformFontsPromise(nodeId) { |
| return this._agent.getPlatformFontsForNode(nodeId); |
| } |
| |
| /** |
| * @return {!Array.<!SDK.CSSStyleSheetHeader>} |
| */ |
| allStyleSheets() { |
| const values = this._styleSheetIdToHeader.valuesArray(); |
| /** |
| * @param {!SDK.CSSStyleSheetHeader} a |
| * @param {!SDK.CSSStyleSheetHeader} b |
| * @return {number} |
| */ |
| function styleSheetComparator(a, b) { |
| if (a.sourceURL < b.sourceURL) { |
| return -1; |
| } else if (a.sourceURL > b.sourceURL) { |
| return 1; |
| } |
| return a.startLine - b.startLine || a.startColumn - b.startColumn; |
| } |
| values.sort(styleSheetComparator); |
| |
| return values; |
| } |
| |
| /** |
| * @param {!Protocol.DOM.NodeId} nodeId |
| * @return {!Promise<?InlineStyleResult>} |
| */ |
| async inlineStylesPromise(nodeId) { |
| const response = await this._agent.invoke_getInlineStylesForNode({nodeId}); |
| |
| if (response[Protocol.Error] || !response.inlineStyle) { |
| return null; |
| } |
| 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); |
| } |
| |
| /** |
| * @param {!SDK.DOMNode} node |
| * @param {string} pseudoClass |
| * @param {boolean} enable |
| * @return {boolean} |
| */ |
| forcePseudoState(node, pseudoClass, enable) { |
| const pseudoClasses = node.marker(PseudoStateMarker) || []; |
| if (enable) { |
| if (pseudoClasses.indexOf(pseudoClass) >= 0) { |
| return false; |
| } |
| pseudoClasses.push(pseudoClass); |
| node.setMarker(PseudoStateMarker, pseudoClasses); |
| } else { |
| if (pseudoClasses.indexOf(pseudoClass) < 0) { |
| return false; |
| } |
| pseudoClasses.remove(pseudoClass); |
| if (pseudoClasses.length) { |
| node.setMarker(PseudoStateMarker, pseudoClasses); |
| } else { |
| node.setMarker(PseudoStateMarker, null); |
| } |
| } |
| |
| this._agent.forcePseudoState(node.id, pseudoClasses); |
| this.dispatchEventToListeners(Events.PseudoStateForced, {node: node, pseudoClass: pseudoClass, enable: enable}); |
| return true; |
| } |
| |
| /** |
| * @param {!SDK.DOMNode} node |
| * @return {?Array<string>} state |
| */ |
| pseudoState(node) { |
| return node.marker(PseudoStateMarker) || []; |
| } |
| |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| * @param {!TextUtils.TextRange} range |
| * @param {string} newMediaText |
| * @return {!Promise<boolean>} |
| */ |
| 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; |
| } |
| this._domModel.markUndoableState(); |
| const edit = new Edit(styleSheetId, range, newMediaText, mediaPayload); |
| this._fireStyleSheetChanged(styleSheetId, edit); |
| return true; |
| } catch (e) { |
| return false; |
| } |
| } |
| |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| * @param {string} ruleText |
| * @param {!TextUtils.TextRange} ruleLocation |
| * @return {!Promise<?SDK.CSSStyleRule>} |
| */ |
| async addRule(styleSheetId, ruleText, ruleLocation) { |
| try { |
| await this._ensureOriginalStyleSheetText(styleSheetId); |
| const rulePayload = await this._agent.addRule(styleSheetId, ruleText, ruleLocation); |
| |
| if (!rulePayload) { |
| return null; |
| } |
| 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; |
| } |
| } |
| |
| /** |
| * @param {!SDK.DOMNode} node |
| * @return {!Promise<?SDK.CSSStyleSheetHeader>} |
| */ |
| 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; |
| } |
| |
| try { |
| const styleSheetId = await this._agent.createStyleSheet(frameId); |
| return styleSheetId && this._styleSheetIdToHeader.get(styleSheetId) || null; |
| } catch (e) { |
| return null; |
| } |
| } |
| |
| mediaQueryResultChanged() { |
| this.dispatchEventToListeners(Events.MediaQueryResultChanged); |
| } |
| |
| fontsUpdated() { |
| this.dispatchEventToListeners(Events.FontsUpdated); |
| } |
| |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} id |
| * @return {?SDK.CSSStyleSheetHeader} |
| */ |
| styleSheetHeaderForId(id) { |
| return this._styleSheetIdToHeader.get(id) || null; |
| } |
| |
| /** |
| * @return {!Array.<!SDK.CSSStyleSheetHeader>} |
| */ |
| styleSheetHeaders() { |
| return this._styleSheetIdToHeader.valuesArray(); |
| } |
| |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| * @param {!Edit=} edit |
| */ |
| _fireStyleSheetChanged(styleSheetId, edit) { |
| this.dispatchEventToListeners(Events.StyleSheetChanged, {styleSheetId: styleSheetId, edit: edit}); |
| } |
| |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| * @return {!Promise<?string>} |
| */ |
| _ensureOriginalStyleSheetText(styleSheetId) { |
| const header = this.styleSheetHeaderForId(styleSheetId); |
| if (!header) { |
| return Promise.resolve(/** @type {?string} */ (null)); |
| } |
| let promise = this._originalStyleSheetText.get(header); |
| if (!promise) { |
| promise = this.getStyleSheetText(header.id); |
| this._originalStyleSheetText.set(header, promise); |
| this._originalContentRequestedForTest(header); |
| } |
| return promise; |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleSheetHeader} header |
| */ |
| _originalContentRequestedForTest(header) { |
| } |
| |
| /** |
| * @param {!SDK.CSSStyleSheetHeader} header |
| * @return {!Promise<?string>} |
| */ |
| originalStyleSheetText(header) { |
| return this._ensureOriginalStyleSheetText(header.id); |
| } |
| |
| /** |
| * |
| * @return {!Iterable<!SDK.CSSStyleSheetHeader>} |
| */ |
| getAllStyleSheetHeaders() { |
| return this._styleSheetIdToHeader.values(); |
| } |
| |
| /** |
| * @param {!Protocol.CSS.CSSStyleSheetHeader} header |
| */ |
| _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, {}); |
| } |
| const frameIdToStyleSheetIds = this._styleSheetIdsForURL.get(url); |
| let styleSheetIds = frameIdToStyleSheetIds[styleSheetHeader.frameId]; |
| if (!styleSheetIds) { |
| styleSheetIds = []; |
| frameIdToStyleSheetIds[styleSheetHeader.frameId] = styleSheetIds; |
| } |
| styleSheetIds.push(styleSheetHeader.id); |
| this._sourceMapManager.attachSourceMap(styleSheetHeader, styleSheetHeader.sourceURL, styleSheetHeader.sourceMapURL); |
| this.dispatchEventToListeners(Events.StyleSheetAdded, styleSheetHeader); |
| } |
| |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} id |
| */ |
| _styleSheetRemoved(id) { |
| const header = this._styleSheetIdToHeader.get(id); |
| console.assert(header); |
| if (!header) { |
| return; |
| } |
| this._styleSheetIdToHeader.remove(id); |
| const url = header.resourceURL(); |
| const frameIdToStyleSheetIds = |
| /** @type {!Object.<!Protocol.Page.FrameId, !Array.<!Protocol.CSS.StyleSheetId>>} */ ( |
| 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); |
| } |
| } |
| this._originalStyleSheetText.remove(header); |
| this._sourceMapManager.detachSourceMap(header); |
| this.dispatchEventToListeners(Events.StyleSheetRemoved, header); |
| } |
| |
| /** |
| * @param {string} url |
| * @return {!Array.<!Protocol.CSS.StyleSheetId>} |
| */ |
| styleSheetIdsForURL(url) { |
| const frameIdToStyleSheetIds = this._styleSheetIdsForURL.get(url); |
| if (!frameIdToStyleSheetIds) { |
| return []; |
| } |
| |
| let result = []; |
| for (const frameId in frameIdToStyleSheetIds) { |
| result = result.concat(frameIdToStyleSheetIds[frameId]); |
| } |
| return result; |
| } |
| |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| * @param {string} newText |
| * @param {boolean} majorChange |
| * @return {!Promise<?string>} |
| */ |
| async setStyleSheetText(styleSheetId, newText, majorChange) { |
| const header = /** @type {!SDK.CSSStyleSheetHeader} */ (this._styleSheetIdToHeader.get(styleSheetId)); |
| console.assert(header); |
| newText = CSSModel.trimSourceURL(newText); |
| if (header.hasSourceURL) { |
| newText += '\n/*# sourceURL=' + header.sourceURL + ' */'; |
| } |
| |
| 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'; |
| } |
| this._domModel.markUndoableState(!majorChange); |
| this._fireStyleSheetChanged(styleSheetId); |
| return null; |
| } |
| |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| * @return {!Promise<?string>} |
| */ |
| async getStyleSheetText(styleSheetId) { |
| try { |
| const text = await this._agent.getStyleSheetText(styleSheetId); |
| return text && CSSModel.trimSourceURL(text); |
| } catch (e) { |
| return null; |
| } |
| } |
| |
| _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]); |
| } |
| } |
| |
| /** |
| * @override |
| * @return {!Promise} |
| */ |
| suspendModel() { |
| this._isEnabled = false; |
| return this._agent.disable().then(this._resetStyleSheets.bind(this)); |
| } |
| |
| /** |
| * @override |
| * @return {!Promise} |
| */ |
| async resumeModel() { |
| return this._enable(); |
| } |
| |
| /** |
| * @param {number} nodeId |
| * @param {string} name |
| * @param {string} value |
| */ |
| setEffectivePropertyValueForNode(nodeId, name, value) { |
| this._agent.setEffectivePropertyValueForNode(nodeId, name, value); |
| } |
| |
| /** |
| * @param {!SDK.DOMNode} node |
| * @return {!Promise.<?SDK.CSSMatchedStyles>} |
| */ |
| cachedMatchedCascadeForNode(node) { |
| if (this._cachedMatchedCascadeNode !== node) { |
| this.discardCachedMatchedCascade(); |
| } |
| this._cachedMatchedCascadeNode = node; |
| if (!this._cachedMatchedCascadePromise) { |
| this._cachedMatchedCascadePromise = this.matchedStylesPromise(node.id); |
| } |
| return this._cachedMatchedCascadePromise; |
| } |
| |
| discardCachedMatchedCascade() { |
| delete this._cachedMatchedCascadeNode; |
| delete this._cachedMatchedCascadePromise; |
| } |
| |
| /** |
| * @override |
| */ |
| dispose() { |
| super.dispose(); |
| this._sourceMapManager.dispose(); |
| } |
| } |
| |
| /** @enum {symbol} */ |
| 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'; |
| |
| /** |
| * @unrestricted |
| */ |
| export class Edit { |
| /** |
| * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| * @param {!TextUtils.TextRange} oldRange |
| * @param {string} newText |
| * @param {?Object} payload |
| */ |
| constructor(styleSheetId, oldRange, newText, payload) { |
| this.styleSheetId = styleSheetId; |
| this.oldRange = oldRange; |
| this.newRange = TextUtils.TextRange.fromEdit(oldRange, newText); |
| this.newText = newText; |
| this.payload = payload; |
| } |
| } |
| |
| export class CSSLocation { |
| /** |
| * @param {!SDK.CSSStyleSheetHeader} header |
| * @param {number} lineNumber |
| * @param {number=} columnNumber |
| */ |
| constructor(header, lineNumber, columnNumber) { |
| this._cssModel = header.cssModel(); |
| this.styleSheetId = header.id; |
| this.url = header.resourceURL(); |
| this.lineNumber = lineNumber; |
| this.columnNumber = columnNumber || 0; |
| } |
| |
| /** |
| * @return {!CSSModel} |
| */ |
| cssModel() { |
| return this._cssModel; |
| } |
| |
| /** |
| * @return {?SDK.CSSStyleSheetHeader} |
| */ |
| header() { |
| return this._cssModel.styleSheetHeaderForId(this.styleSheetId); |
| } |
| } |
| |
| /** |
| * @implements {Protocol.CSSDispatcher} |
| * @unrestricted |
| */ |
| class CSSDispatcher { |
| /** |
| * @param {!CSSModel} cssModel |
| */ |
| constructor(cssModel) { |
| this._cssModel = cssModel; |
| } |
| |
| /** |
| * @override |
| */ |
| mediaQueryResultChanged() { |
| this._cssModel.mediaQueryResultChanged(); |
| } |
| |
| /** |
| * @override |
| */ |
| fontsUpdated() { |
| this._cssModel.fontsUpdated(); |
| } |
| |
| /** |
| * @override |
| * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| */ |
| styleSheetChanged(styleSheetId) { |
| this._cssModel._fireStyleSheetChanged(styleSheetId); |
| } |
| |
| /** |
| * @override |
| * @param {!Protocol.CSS.CSSStyleSheetHeader} header |
| */ |
| styleSheetAdded(header) { |
| this._cssModel._styleSheetAdded(header); |
| } |
| |
| /** |
| * @override |
| * @param {!Protocol.CSS.StyleSheetId} id |
| */ |
| styleSheetRemoved(id) { |
| this._cssModel._styleSheetRemoved(id); |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| class ComputedStyleLoader { |
| /** |
| * @param {!CSSModel} cssModel |
| */ |
| constructor(cssModel) { |
| this._cssModel = cssModel; |
| /** @type {!Map<!Protocol.DOM.NodeId, !Promise<?Map<string, string>>>} */ |
| this._nodeIdToPromise = new Map(); |
| } |
| |
| /** |
| * @param {!Protocol.DOM.NodeId} nodeId |
| * @return {!Promise<?Map<string, string>>} |
| */ |
| computedStylePromise(nodeId) { |
| let promise = this._nodeIdToPromise.get(nodeId); |
| if (promise) { |
| return promise; |
| } |
| promise = this._cssModel._agent.getComputedStyleForNode(nodeId).then(parsePayload.bind(this)); |
| this._nodeIdToPromise.set(nodeId, promise); |
| return promise; |
| |
| /** |
| * @param {?Array<!Protocol.CSS.CSSComputedStyleProperty>} computedPayload |
| * @return {?Map<string, string>} |
| * @this {ComputedStyleLoader} |
| */ |
| function parsePayload(computedPayload) { |
| this._nodeIdToPromise.delete(nodeId); |
| if (!computedPayload || !computedPayload.length) { |
| return null; |
| } |
| const result = new Map(); |
| for (const property of computedPayload) { |
| result.set(property.name, property.value); |
| } |
| return result; |
| } |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class InlineStyleResult { |
| /** |
| * @param {?SDK.CSSStyleDeclaration} inlineStyle |
| * @param {?SDK.CSSStyleDeclaration} attributesStyle |
| */ |
| constructor(inlineStyle, attributesStyle) { |
| this.inlineStyle = inlineStyle; |
| this.attributesStyle = attributesStyle; |
| } |
| } |
| |
| /* Legacy exported object */ |
| self.SDK = self.SDK || {}; |
| |
| /* Legacy exported object */ |
| SDK = SDK || {}; |
| |
| /** @constructor */ |
| SDK.CSSModel = CSSModel; |
| |
| /** @enum {symbol} */ |
| SDK.CSSModel.Events = Events; |
| |
| /** @constructor */ |
| SDK.CSSModel.Edit = Edit; |
| |
| /** @constructor */ |
| SDK.CSSModel.InlineStyleResult = InlineStyleResult; |
| |
| /** @constructor */ |
| SDK.CSSLocation = CSSLocation; |
| |
| SDK.SDKModel.register(SDK.CSSModel, SDK.Target.Capability.DOM, true); |
| |
| /** @typedef {!{range: !Protocol.CSS.SourceRange, styleSheetId: !Protocol.CSS.StyleSheetId, wasUsed: boolean}} */ |
| SDK.CSSModel.RuleUsage; |
| |
| /** @typedef {{backgroundColors: ?Array<string>, computedFontSize: string, computedFontWeight: string}} */ |
| SDK.CSSModel.ContrastInfo; |