blob: dc9e52434a8f9796deecd432da7d70ec21903bcf [file] [log] [blame]
// Copyright (c) 2014 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.
/**
* @constructor
* @param {!WebInspector.SectionCascade} cascade
* @param {?WebInspector.CSSRule} rule
* @param {!WebInspector.CSSStyleDeclaration} style
* @param {string} customSelectorText
* @param {?WebInspector.DOMNode=} inheritedFromNode
*/
WebInspector.StylesSectionModel = function(cascade, rule, style, customSelectorText, inheritedFromNode)
{
this._cascade = cascade;
this._rule = rule;
this._style = style;
this._customSelectorText = customSelectorText;
this._editable = !!(this._style && this._style.styleSheetId);
this._inheritedFromNode = inheritedFromNode || null;
}
WebInspector.StylesSectionModel.prototype = {
/**
* @return {!WebInspector.SectionCascade}
*/
cascade: function()
{
return this._cascade;
},
/**
* @return {boolean}
*/
hasMatchingSelectors: function()
{
return this.rule() ? this.rule().matchingSelectors.length > 0 && this.mediaMatches() : true;
},
/**
* @return {boolean}
*/
mediaMatches: function()
{
var media = this.media();
for (var i = 0; media && i < media.length; ++i) {
if (!media[i].active())
return false;
}
return true;
},
/**
* @return {boolean}
*/
inherited: function()
{
return !!this._inheritedFromNode;
},
/**
* @return {?WebInspector.DOMNode}
*/
parentNode: function()
{
return this._inheritedFromNode;
},
/**
* @return {string}
*/
selectorText: function()
{
if (this._customSelectorText)
return this._customSelectorText;
return this.rule() ? this.rule().selectorText() : "";
},
/**
* @return {boolean}
*/
editable: function()
{
return this._editable;
},
/**
* @param {boolean} editable
*/
setEditable: function(editable)
{
this._editable = editable;
},
/**
* @return {!WebInspector.CSSStyleDeclaration}
*/
style: function()
{
return this._style;
},
/**
* @return {?WebInspector.CSSRule}
*/
rule: function()
{
return this._rule;
},
/**
* @return {?Array.<!WebInspector.CSSMedia>}
*/
media: function()
{
return this.rule() ? this.rule().media : null;
},
/**
* @param {!WebInspector.CSSRule} rule
*/
updateRule: function(rule)
{
this._rule = rule;
this._style = rule.style;
this._cascade._resetUsedProperties();
},
resetCachedData: function()
{
this._cascade._resetUsedProperties();
},
/**
* @param {string} propertyName
* @return {boolean}
*/
isPropertyInCascade: function(propertyName)
{
if (!this.hasMatchingSelectors())
return false;
if (this.inherited() && !WebInspector.CSSMetadata.isPropertyInherited(propertyName))
return false;
return true;
},
/**
* @param {string} propertyName
* @return {boolean}
*/
isPropertyOverloaded: function(propertyName)
{
if (!this.isPropertyInCascade(propertyName))
return false;
var usedProperties = this._cascade._usedPropertiesForModel(this);
var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(propertyName);
return !usedProperties.has(canonicalName);
}
}
/**
* @constructor
*/
WebInspector.SectionCascade = function()
{
this._models = [];
this._resetUsedProperties();
}
WebInspector.SectionCascade.prototype = {
/**
* @return {!Array.<!WebInspector.StylesSectionModel>}
*/
sectionModels: function()
{
return this._models;
},
/**
* @param {!WebInspector.CSSRule} rule
* @param {?WebInspector.DOMNode=} inheritedFromNode
* @return {!WebInspector.StylesSectionModel}
*/
appendModelFromRule: function(rule, inheritedFromNode)
{
return this._insertModel(new WebInspector.StylesSectionModel(this, rule, rule.style, "", inheritedFromNode));
},
/**
* @param {!WebInspector.CSSRule} rule
* @param {!WebInspector.StylesSectionModel} insertAfterStyleRule
* @return {!WebInspector.StylesSectionModel}
*/
insertModelFromRule: function(rule, insertAfterStyleRule)
{
return this._insertModel(new WebInspector.StylesSectionModel(this, rule, rule.style, "", null), insertAfterStyleRule);
},
/**
* @param {!WebInspector.CSSStyleDeclaration} style
* @param {string} selectorText
* @param {?WebInspector.DOMNode=} inheritedFromNode
* @return {!WebInspector.StylesSectionModel}
*/
appendModelFromStyle: function(style, selectorText, inheritedFromNode)
{
return this._insertModel(new WebInspector.StylesSectionModel(this, null, style, selectorText, inheritedFromNode));
},
/**
* @return {!Set.<string>}
*/
allUsedProperties: function()
{
this._recomputeUsedPropertiesIfNeeded();
return this._allUsedProperties;
},
/**
* @param {!WebInspector.StylesSectionModel} model
* @param {!WebInspector.StylesSectionModel=} insertAfter
* @return {!WebInspector.StylesSectionModel}
*/
_insertModel: function(model, insertAfter)
{
if (insertAfter) {
var index = this._models.indexOf(insertAfter);
console.assert(index !== -1, "The insertAfter anchor could not be found in cascade");
this._models.splice(index + 1, 0, model);
} else {
this._models.push(model);
}
this._resetUsedProperties();
return model;
},
_recomputeUsedPropertiesIfNeeded: function()
{
if (this._usedPropertiesPerModel.size > 0)
return;
var usedProperties = WebInspector.SectionCascade._computeUsedProperties(this._models, this._allUsedProperties);
for (var i = 0; i < usedProperties.length; ++i)
this._usedPropertiesPerModel.set(this._models[i], usedProperties[i]);
},
_resetUsedProperties: function()
{
/** @type {!Set.<string>} */
this._allUsedProperties = new Set();
/** @type {!Map.<!WebInspector.StylesSectionModel, !Set.<string>>} */
this._usedPropertiesPerModel = new Map();
},
/**
* @param {!WebInspector.StylesSectionModel} model
* @return {!Set.<string>}
*/
_usedPropertiesForModel: function(model)
{
this._recomputeUsedPropertiesIfNeeded();
return /**@type {!Set.<string>}*/ (this._usedPropertiesPerModel.get(model));
}
}
/**
* @param {!Array.<!WebInspector.StylesSectionModel>} styleRules
* @param {!Set.<string>} allUsedProperties
* @return {!Array.<!Set.<string>>}
*/
WebInspector.SectionCascade._computeUsedProperties = function(styleRules, allUsedProperties)
{
/** @type {!Set.<string>} */
var foundImportantProperties = new Set();
/** @type {!Map.<string, !Set.<string>>} */
var propertyToEffectiveRule = new Map();
/** @type {!Map.<string, !WebInspector.DOMNode>} */
var inheritedPropertyToNode = new Map();
var stylesUsedProperties = [];
for (var i = 0; i < styleRules.length; ++i) {
var styleRule = styleRules[i];
/** @type {!Set.<string>} */
var styleRuleUsedProperties = new Set();
stylesUsedProperties.push(styleRuleUsedProperties);
if (!styleRule.hasMatchingSelectors())
continue;
var style = styleRule.style();
var allProperties = style.allProperties;
for (var j = 0; j < allProperties.length; ++j) {
var property = allProperties[j];
if (!property.activeInStyle())
continue;
// Do not pick non-inherited properties from inherited styles.
if (styleRule.inherited() && !WebInspector.CSSMetadata.isPropertyInherited(property.name))
continue;
var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(property.name);
if (foundImportantProperties.has(canonicalName))
continue;
if (!property.important && allUsedProperties.has(canonicalName))
continue;
var isKnownProperty = propertyToEffectiveRule.has(canonicalName);
var parentNode = styleRule.parentNode();
if (!isKnownProperty && parentNode && !inheritedPropertyToNode.has(canonicalName))
inheritedPropertyToNode.set(canonicalName, parentNode);
if (property.important) {
if (styleRule.inherited() && isKnownProperty && styleRule.parentNode() !== inheritedPropertyToNode.get(canonicalName))
continue;
foundImportantProperties.add(canonicalName);
if (isKnownProperty)
propertyToEffectiveRule.get(canonicalName).delete(canonicalName);
}
styleRuleUsedProperties.add(canonicalName);
allUsedProperties.add(canonicalName);
propertyToEffectiveRule.set(canonicalName, styleRuleUsedProperties);
}
// If every longhand of the shorthand is not active, then the shorthand is not active too.
for (var property of style.leadingProperties()) {
var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(property.name);
if (!styleRuleUsedProperties.has(canonicalName))
continue;
var longhands = style.longhandProperties(property.name);
if (!longhands.length)
continue;
var notUsed = true;
for (var longhand of longhands) {
var longhandCanonicalName = WebInspector.CSSMetadata.canonicalPropertyName(longhand.name);
notUsed = notUsed && !styleRuleUsedProperties.has(longhandCanonicalName);
}
if (!notUsed)
continue;
styleRuleUsedProperties.delete(canonicalName);
allUsedProperties.delete(canonicalName);
}
}
return stylesUsedProperties;
}