| /* |
| * Copyright (C) 2013 Apple 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: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. |
| */ |
| |
| WebInspector.CSSStyleDeclaration = class CSSStyleDeclaration extends WebInspector.Object |
| { |
| constructor(nodeStyles, ownerStyleSheet, id, type, node, inherited, text, properties, styleSheetTextRange) |
| { |
| super(); |
| |
| console.assert(nodeStyles); |
| this._nodeStyles = nodeStyles; |
| |
| this._ownerRule = null; |
| |
| this._ownerStyleSheet = ownerStyleSheet || null; |
| this._id = id || null; |
| this._type = type || null; |
| this._node = node || null; |
| this._inherited = inherited || false; |
| |
| this._pendingProperties = []; |
| this._propertyNameMap = {}; |
| |
| this._initialText = text; |
| this._hasModifiedInitialText = false; |
| |
| this.update(text, properties, styleSheetTextRange, true); |
| } |
| |
| // Public |
| |
| get id() |
| { |
| return this._id; |
| } |
| |
| get ownerStyleSheet() |
| { |
| return this._ownerStyleSheet; |
| } |
| |
| get type() |
| { |
| return this._type; |
| } |
| |
| get inherited() |
| { |
| return this._inherited; |
| } |
| |
| get node() |
| { |
| return this._node; |
| } |
| |
| get editable() |
| { |
| if (!this._id) |
| return false; |
| |
| if (this._type === WebInspector.CSSStyleDeclaration.Type.Rule) |
| return this._ownerRule && this._ownerRule.editable; |
| |
| if (this._type === WebInspector.CSSStyleDeclaration.Type.Inline) |
| return !this._node.isInUserAgentShadowTree(); |
| |
| return false; |
| } |
| |
| update(text, properties, styleSheetTextRange, dontFireEvents) |
| { |
| text = text || ""; |
| properties = properties || []; |
| |
| var oldProperties = this._properties || []; |
| var oldText = this._text; |
| |
| this._text = text; |
| this._properties = properties; |
| this._styleSheetTextRange = styleSheetTextRange; |
| this._propertyNameMap = {}; |
| |
| delete this._visibleProperties; |
| |
| var editable = this.editable; |
| |
| for (var i = 0; i < this._properties.length; ++i) { |
| var property = this._properties[i]; |
| property.ownerStyle = this; |
| |
| // Store the property in a map if we arn't editable. This |
| // allows for quick lookup for computed style. Editable |
| // styles don't use the map since they need to account for |
| // overridden properties. |
| if (!editable) |
| this._propertyNameMap[property.name] = property; |
| else { |
| // Remove from pendingProperties (if it was pending). |
| this._pendingProperties.remove(property); |
| } |
| } |
| |
| var removedProperties = []; |
| for (var i = 0; i < oldProperties.length; ++i) { |
| var oldProperty = oldProperties[i]; |
| |
| if (!this._properties.includes(oldProperty)) { |
| // Clear the index, since it is no longer valid. |
| oldProperty.index = NaN; |
| |
| removedProperties.push(oldProperty); |
| |
| // Keep around old properties in pending in case they |
| // are needed again during editing. |
| if (editable) |
| this._pendingProperties.push(oldProperty); |
| } |
| } |
| |
| if (dontFireEvents) |
| return; |
| |
| var addedProperties = []; |
| for (var i = 0; i < this._properties.length; ++i) { |
| if (!oldProperties.includes(this._properties[i])) |
| addedProperties.push(this._properties[i]); |
| } |
| |
| // Don't fire the event if there is text and it hasn't changed. |
| if (oldText && this._text && oldText === this._text) { |
| // We shouldn't have any added or removed properties in this case. |
| console.assert(!addedProperties.length && !removedProperties.length); |
| if (!addedProperties.length && !removedProperties.length) |
| return; |
| } |
| |
| function delayed() |
| { |
| this.dispatchEventToListeners(WebInspector.CSSStyleDeclaration.Event.PropertiesChanged, {addedProperties, removedProperties}); |
| } |
| |
| // Delay firing the PropertiesChanged event so DOMNodeStyles has a chance to mark overridden and associated properties. |
| setTimeout(delayed.bind(this), 0); |
| } |
| |
| get ownerRule() |
| { |
| return this._ownerRule; |
| } |
| |
| set ownerRule(rule) |
| { |
| this._ownerRule = rule || null; |
| } |
| |
| get text() |
| { |
| return this._text; |
| } |
| |
| set text(text) |
| { |
| if (this._text === text) |
| return; |
| |
| let trimmedText = WebInspector.CSSStyleDeclarationTextEditor.PrefixWhitespace + text.trim(); |
| if (this._text === trimmedText) |
| return; |
| |
| if (trimmedText === WebInspector.CSSStyleDeclarationTextEditor.PrefixWhitespace || this._type === WebInspector.CSSStyleDeclaration.Type.Inline) |
| text = trimmedText; |
| |
| let modified = text !== this._initialText; |
| if (modified !== this._hasModifiedInitialText) { |
| this._hasModifiedInitialText = modified; |
| this.dispatchEventToListeners(WebInspector.CSSStyleDeclaration.Event.InitialTextModified); |
| } |
| |
| this._nodeStyles.changeStyleText(this, text); |
| } |
| |
| resetText() |
| { |
| this.text = this._initialText; |
| } |
| |
| get modified() |
| { |
| return this._hasModifiedInitialText; |
| } |
| |
| get properties() |
| { |
| return this._properties; |
| } |
| |
| get visibleProperties() |
| { |
| if (this._visibleProperties) |
| return this._visibleProperties; |
| |
| this._visibleProperties = this._properties.filter(function(property) { |
| return !!property.styleDeclarationTextRange; |
| }); |
| |
| return this._visibleProperties; |
| } |
| |
| get pendingProperties() |
| { |
| return this._pendingProperties; |
| } |
| |
| get styleSheetTextRange() |
| { |
| return this._styleSheetTextRange; |
| } |
| |
| get mediaList() |
| { |
| if (this._ownerRule) |
| return this._ownerRule.mediaList; |
| return []; |
| } |
| |
| get selectorText() |
| { |
| if (this._ownerRule) |
| return this._ownerRule.selectorText; |
| return this._node.appropriateSelectorFor(true); |
| } |
| |
| propertyForName(name, dontCreateIfMissing) |
| { |
| console.assert(name); |
| if (!name) |
| return null; |
| |
| if (!this.editable) |
| return this._propertyNameMap[name] || null; |
| |
| // Editable styles don't use the map since they need to |
| // account for overridden properties. |
| |
| function findMatch(properties) |
| { |
| for (var i = 0; i < properties.length; ++i) { |
| var property = properties[i]; |
| if (property.canonicalName !== name && property.name !== name) |
| continue; |
| if (bestMatchProperty && !bestMatchProperty.overridden && property.overridden) |
| continue; |
| bestMatchProperty = property; |
| } |
| } |
| |
| var bestMatchProperty = null; |
| |
| findMatch(this._properties); |
| |
| if (bestMatchProperty) |
| return bestMatchProperty; |
| |
| if (dontCreateIfMissing || !this.editable) |
| return null; |
| |
| findMatch(this._pendingProperties, true); |
| |
| if (bestMatchProperty) |
| return bestMatchProperty; |
| |
| var newProperty = new WebInspector.CSSProperty(NaN, null, name); |
| newProperty.ownerStyle = this; |
| |
| this._pendingProperties.push(newProperty); |
| |
| return newProperty; |
| } |
| |
| generateCSSRuleString() |
| { |
| let indentString = WebInspector.indentString(); |
| let styleText = ""; |
| let mediaList = this.mediaList; |
| let mediaQueriesCount = mediaList.length; |
| for (let i = mediaQueriesCount - 1; i >= 0; --i) |
| styleText += indentString.repeat(mediaQueriesCount - i - 1) + "@media " + mediaList[i].text + " {\n"; |
| |
| styleText += indentString.repeat(mediaQueriesCount) + this.selectorText + " {\n"; |
| |
| for (let property of this._properties) { |
| if (property.anonymous) |
| continue; |
| |
| styleText += indentString.repeat(mediaQueriesCount + 1) + property.text.trim(); |
| |
| if (!styleText.endsWith(";")) |
| styleText += ";"; |
| |
| styleText += "\n"; |
| } |
| |
| for (let i = mediaQueriesCount; i > 0; --i) |
| styleText += indentString.repeat(i) + "}\n"; |
| |
| styleText += "}"; |
| |
| return styleText; |
| } |
| |
| isInspectorRule() |
| { |
| return this._ownerRule && this._ownerRule.type === WebInspector.CSSStyleSheet.Type.Inspector; |
| } |
| |
| hasProperties() |
| { |
| return !!this._properties.length; |
| } |
| |
| // Protected |
| |
| get nodeStyles() |
| { |
| return this._nodeStyles; |
| } |
| }; |
| |
| WebInspector.CSSStyleDeclaration.Event = { |
| PropertiesChanged: "css-style-declaration-properties-changed", |
| InitialTextModified: "css-style-declaration-initial-text-modified" |
| }; |
| |
| WebInspector.CSSStyleDeclaration.Type = { |
| Rule: "css-style-declaration-type-rule", |
| Inline: "css-style-declaration-type-inline", |
| Attribute: "css-style-declaration-type-attribute", |
| Computed: "css-style-declaration-type-computed" |
| }; |