| // 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. | |
| export default class CSSStyleDeclaration { | |
| /** | |
| * @param {!SDK.CSSModel} cssModel | |
| * @param {?SDK.CSSRule} parentRule | |
| * @param {!Protocol.CSS.CSSStyle} payload | |
| * @param {!Type} type | |
| */ | |
| constructor(cssModel, parentRule, payload, type) { | |
| this._cssModel = cssModel; | |
| this.parentRule = parentRule; | |
| /** @type {!Array<!SDK.CSSProperty>} */ | |
| this._allProperties; | |
| /** @type {string|undefined} */ | |
| this.styleSheetId; | |
| /** @type {?TextUtils.TextRange} */ | |
| this.range; | |
| /** @type {string|undefined} */ | |
| this.cssText; | |
| /** @type {!Map<string, string>} */ | |
| this._shorthandValues; | |
| /** @type {!Set<string>} */ | |
| this._shorthandIsImportant; | |
| /** @type {!Map<string, !SDK.CSSProperty>} */ | |
| this._activePropertyMap; | |
| /** @type {?Array<!SDK.CSSProperty>} */ | |
| this._leadingProperties; | |
| this._reinitialize(payload); | |
| this.type = type; | |
| } | |
| /** | |
| * @param {!SDK.CSSModel.Edit} edit | |
| */ | |
| rebase(edit) { | |
| if (this.styleSheetId !== edit.styleSheetId || !this.range) { | |
| return; | |
| } | |
| if (edit.oldRange.equal(this.range)) { | |
| this._reinitialize(/** @type {!Protocol.CSS.CSSStyle} */ (edit.payload)); | |
| } else { | |
| this.range = this.range.rebaseAfterTextEdit(edit.oldRange, edit.newRange); | |
| for (let i = 0; i < this._allProperties.length; ++i) { | |
| this._allProperties[i].rebase(edit); | |
| } | |
| } | |
| } | |
| /** | |
| * @param {!Protocol.CSS.CSSStyle} payload | |
| */ | |
| _reinitialize(payload) { | |
| this.styleSheetId = payload.styleSheetId; | |
| this.range = payload.range ? TextUtils.TextRange.fromObject(payload.range) : null; | |
| const shorthandEntries = payload.shorthandEntries; | |
| this._shorthandValues = new Map(); | |
| this._shorthandIsImportant = new Set(); | |
| for (let i = 0; i < shorthandEntries.length; ++i) { | |
| this._shorthandValues.set(shorthandEntries[i].name, shorthandEntries[i].value); | |
| if (shorthandEntries[i].important) { | |
| this._shorthandIsImportant.add(shorthandEntries[i].name); | |
| } | |
| } | |
| this._allProperties = []; | |
| if (payload.cssText && this.range) { | |
| const cssText = new TextUtils.Text(payload.cssText); | |
| let start = {line: this.range.startLine, column: this.range.startColumn}; | |
| for (const cssProperty of payload.cssProperties) { | |
| const range = cssProperty.range; | |
| if (range) { | |
| parseUnusedText.call(this, cssText, start.line, start.column, range.startLine, range.startColumn); | |
| start = {line: range.endLine, column: range.endColumn}; | |
| } | |
| this._allProperties.push(SDK.CSSProperty.parsePayload(this, this._allProperties.length, cssProperty)); | |
| } | |
| parseUnusedText.call(this, cssText, start.line, start.column, this.range.endLine, this.range.endColumn); | |
| } else { | |
| for (const cssProperty of payload.cssProperties) { | |
| this._allProperties.push(SDK.CSSProperty.parsePayload(this, this._allProperties.length, cssProperty)); | |
| } | |
| } | |
| this._generateSyntheticPropertiesIfNeeded(); | |
| this._computeInactiveProperties(); | |
| this._activePropertyMap = new Map(); | |
| for (const property of this._allProperties) { | |
| if (!property.activeInStyle()) { | |
| continue; | |
| } | |
| this._activePropertyMap.set(property.name, property); | |
| } | |
| this.cssText = payload.cssText; | |
| this._leadingProperties = null; | |
| /** | |
| * @this {CSSStyleDeclaration} | |
| * @param {!TextUtils.Text} cssText | |
| * @param {number} startLine | |
| * @param {number} startColumn | |
| * @param {number} endLine | |
| * @param {number} endColumn | |
| */ | |
| function parseUnusedText(cssText, startLine, startColumn, endLine, endColumn) { | |
| const tr = new TextUtils.TextRange(startLine, startColumn, endLine, endColumn); | |
| const missingText = cssText.extract(tr.relativeTo(this.range.startLine, this.range.startColumn)); | |
| // Try to fit the malformed css into properties. | |
| const lines = missingText.split('\n'); | |
| let lineNumber = 0; | |
| let inComment = false; | |
| for (const line of lines) { | |
| let column = 0; | |
| for (const property of line.split(';')) { | |
| const strippedProperty = stripComments(property, inComment); | |
| const trimmedProperty = strippedProperty.text.trim(); | |
| inComment = strippedProperty.inComment; | |
| if (trimmedProperty) { | |
| let name; | |
| let value; | |
| const colonIndex = trimmedProperty.indexOf(':'); | |
| if (colonIndex === -1) { | |
| name = trimmedProperty; | |
| value = ''; | |
| } else { | |
| name = trimmedProperty.substring(0, colonIndex).trim(); | |
| value = trimmedProperty.substring(colonIndex + 1).trim(); | |
| } | |
| const range = new TextUtils.TextRange(lineNumber, column, lineNumber, column + property.length); | |
| this._allProperties.push(new SDK.CSSProperty( | |
| this, this._allProperties.length, name, value, false, false, false, false, property, | |
| range.relativeFrom(startLine, startColumn))); | |
| } | |
| column += property.length + 1; | |
| } | |
| lineNumber++; | |
| } | |
| } | |
| /** | |
| * @param {string} text | |
| * @param {boolean} inComment | |
| * @return {{text: string, inComment: boolean}} | |
| */ | |
| function stripComments(text, inComment) { | |
| let output = ''; | |
| for (let i = 0; i < text.length; i++) { | |
| if (!inComment && text.substring(i, i + 2) === '/*') { | |
| inComment = true; | |
| i++; | |
| } else if (inComment && text.substring(i, i + 2) === '*/') { | |
| inComment = false; | |
| i++; | |
| } else if (!inComment) { | |
| output += text[i]; | |
| } | |
| } | |
| return {text: output, inComment}; | |
| } | |
| } | |
| _generateSyntheticPropertiesIfNeeded() { | |
| if (this.range) { | |
| return; | |
| } | |
| if (!this._shorthandValues.size) { | |
| return; | |
| } | |
| const propertiesSet = new Set(); | |
| for (const property of this._allProperties) { | |
| propertiesSet.add(property.name); | |
| } | |
| const generatedProperties = []; | |
| // For style-based properties, generate shorthands with values when possible. | |
| for (const property of this._allProperties) { | |
| // For style-based properties, try generating shorthands. | |
| const shorthands = SDK.cssMetadata().shorthands(property.name) || []; | |
| for (const shorthand of shorthands) { | |
| if (propertiesSet.has(shorthand)) { | |
| continue; | |
| } // There already is a shorthand this longhands falls under. | |
| const shorthandValue = this._shorthandValues.get(shorthand); | |
| if (!shorthandValue) { | |
| continue; | |
| } // Never generate synthetic shorthands when no value is available. | |
| // Generate synthetic shorthand we have a value for. | |
| const shorthandImportance = !!this._shorthandIsImportant.has(shorthand); | |
| const shorthandProperty = new SDK.CSSProperty( | |
| this, this.allProperties().length, shorthand, shorthandValue, shorthandImportance, false, true, false); | |
| generatedProperties.push(shorthandProperty); | |
| propertiesSet.add(shorthand); | |
| } | |
| } | |
| this._allProperties = this._allProperties.concat(generatedProperties); | |
| } | |
| /** | |
| * @return {!Array.<!SDK.CSSProperty>} | |
| */ | |
| _computeLeadingProperties() { | |
| /** | |
| * @param {!SDK.CSSProperty} property | |
| * @return {boolean} | |
| */ | |
| function propertyHasRange(property) { | |
| return !!property.range; | |
| } | |
| if (this.range) { | |
| return this._allProperties.filter(propertyHasRange); | |
| } | |
| const leadingProperties = []; | |
| for (const property of this._allProperties) { | |
| const shorthands = SDK.cssMetadata().shorthands(property.name) || []; | |
| let belongToAnyShorthand = false; | |
| for (const shorthand of shorthands) { | |
| if (this._shorthandValues.get(shorthand)) { | |
| belongToAnyShorthand = true; | |
| break; | |
| } | |
| } | |
| if (!belongToAnyShorthand) { | |
| leadingProperties.push(property); | |
| } | |
| } | |
| return leadingProperties; | |
| } | |
| /** | |
| * @return {!Array.<!SDK.CSSProperty>} | |
| */ | |
| leadingProperties() { | |
| if (!this._leadingProperties) { | |
| this._leadingProperties = this._computeLeadingProperties(); | |
| } | |
| return this._leadingProperties; | |
| } | |
| /** | |
| * @return {!SDK.Target} | |
| */ | |
| target() { | |
| return this._cssModel.target(); | |
| } | |
| /** | |
| * @return {!SDK.CSSModel} | |
| */ | |
| cssModel() { | |
| return this._cssModel; | |
| } | |
| _computeInactiveProperties() { | |
| const activeProperties = {}; | |
| for (let i = 0; i < this._allProperties.length; ++i) { | |
| const property = this._allProperties[i]; | |
| if (property.disabled || !property.parsedOk) { | |
| property.setActive(false); | |
| continue; | |
| } | |
| const canonicalName = SDK.cssMetadata().canonicalPropertyName(property.name); | |
| const activeProperty = activeProperties[canonicalName]; | |
| if (!activeProperty) { | |
| activeProperties[canonicalName] = property; | |
| } else if (!activeProperty.important || property.important) { | |
| activeProperty.setActive(false); | |
| activeProperties[canonicalName] = property; | |
| } else { | |
| property.setActive(false); | |
| } | |
| } | |
| } | |
| /** | |
| * @return {!Array<!SDK.CSSProperty>} | |
| */ | |
| allProperties() { | |
| return this._allProperties; | |
| } | |
| /** | |
| * @param {string} name | |
| * @return {string} | |
| */ | |
| getPropertyValue(name) { | |
| const property = this._activePropertyMap.get(name); | |
| return property ? property.value : ''; | |
| } | |
| /** | |
| * @param {string} name | |
| * @return {boolean} | |
| */ | |
| isPropertyImplicit(name) { | |
| const property = this._activePropertyMap.get(name); | |
| return property ? property.implicit : false; | |
| } | |
| /** | |
| * @param {string} name | |
| * @return {!Array.<!SDK.CSSProperty>} | |
| */ | |
| longhandProperties(name) { | |
| const longhands = SDK.cssMetadata().longhands(name); | |
| const result = []; | |
| for (let i = 0; longhands && i < longhands.length; ++i) { | |
| const property = this._activePropertyMap.get(longhands[i]); | |
| if (property) { | |
| result.push(property); | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * @param {number} index | |
| * @return {?SDK.CSSProperty} | |
| */ | |
| propertyAt(index) { | |
| return (index < this.allProperties().length) ? this.allProperties()[index] : null; | |
| } | |
| /** | |
| * @return {number} | |
| */ | |
| pastLastSourcePropertyIndex() { | |
| for (let i = this.allProperties().length - 1; i >= 0; --i) { | |
| if (this.allProperties()[i].range) { | |
| return i + 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| /** | |
| * @param {number} index | |
| * @return {!TextUtils.TextRange} | |
| */ | |
| _insertionRange(index) { | |
| const property = this.propertyAt(index); | |
| return property && property.range ? property.range.collapseToStart() : this.range.collapseToEnd(); | |
| } | |
| /** | |
| * @param {number=} index | |
| * @return {!SDK.CSSProperty} | |
| */ | |
| newBlankProperty(index) { | |
| index = (typeof index === 'undefined') ? this.pastLastSourcePropertyIndex() : index; | |
| const property = | |
| new SDK.CSSProperty(this, index, '', '', false, false, true, false, '', this._insertionRange(index)); | |
| return property; | |
| } | |
| /** | |
| * @param {string} text | |
| * @param {boolean} majorChange | |
| * @return {!Promise.<boolean>} | |
| */ | |
| setText(text, majorChange) { | |
| if (!this.range || !this.styleSheetId) { | |
| return Promise.resolve(false); | |
| } | |
| return this._cssModel.setStyleText(this.styleSheetId, this.range, text, majorChange); | |
| } | |
| /** | |
| * @param {number} index | |
| * @param {string} name | |
| * @param {string} value | |
| * @param {function(boolean)=} userCallback | |
| */ | |
| insertPropertyAt(index, name, value, userCallback) { | |
| this.newBlankProperty(index).setText(name + ': ' + value + ';', false, true).then(userCallback); | |
| } | |
| /** | |
| * @param {string} name | |
| * @param {string} value | |
| * @param {function(boolean)=} userCallback | |
| */ | |
| appendProperty(name, value, userCallback) { | |
| this.insertPropertyAt(this.allProperties().length, name, value, userCallback); | |
| } | |
| } | |
| /** @enum {string} */ | |
| export const Type = { | |
| Regular: 'Regular', | |
| Inline: 'Inline', | |
| Attributes: 'Attributes' | |
| }; | |
| /* Legacy exported object */ | |
| self.SDK = self.SDK || {}; | |
| /* Legacy exported object */ | |
| SDK = SDK || {}; | |
| /** @constructor */ | |
| SDK.CSSStyleDeclaration = CSSStyleDeclaration; | |
| /** @enum {string} */ | |
| SDK.CSSStyleDeclaration.Type = Type; |