diff options
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js b/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js new file mode 100644 index 000000000..8a7ba5f8f --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js @@ -0,0 +1,362 @@ +/* + * 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" +}; |