blob: eaab18787675bd8367f1ce3a4d0abc3e1e661185 [file] [log] [blame]
/*
* 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 = function(nodeStyles, ownerStyleSheet, id, type, node, inherited, text, properties, styleSheetTextRange)
{
WebInspector.Object.call(this);
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.update(text, properties, styleSheetTextRange, true);
};
WebInspector.Object.addConstructorFunctions(WebInspector.CSSStyleDeclaration);
WebInspector.CSSStyleDeclaration.Event = {
PropertiesChanged: "css-style-declaration-properties-changed"
};
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"
};
WebInspector.CSSStyleDeclaration.prototype = {
constructor: WebInspector.CSSStyleDeclaration,
// 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()
{
return !!this._id && ((this._type === WebInspector.CSSStyleDeclaration.Type.Rule && this._ownerRule && this._ownerRule.editable) || this._type === WebInspector.CSSStyleDeclaration.Type.Inline);
},
update: function(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.contains(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.contains(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: addedProperties, removedProperties: 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;
this._nodeStyles.changeStyleText(this, text);
},
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;
},
propertyForName: function(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;
},
addProperty: function(property)
{
console.assert(property);
if (!property)
return;
console.assert(property.ownerStyle === this);
if (property.ownerStyle !== this)
return;
console.assert(this.editable);
if (!this.editable)
return;
this._nodeStyles.addProperty(property);
},
removeProperty: function(property)
{
console.assert(property);
if (!property)
return;
console.assert(property.ownerStyle === this);
if (property.ownerStyle !== this)
return;
console.assert(this.editable);
if (!this.editable)
return;
this._nodeStyles.removeProperty(property);
},
// Protected
get nodeStyles()
{
return this._nodeStyles;
}
};
WebInspector.CSSStyleDeclaration.prototype.__proto__ = WebInspector.Object.prototype;