Commit 3cb03d1b authored by chrishtr@chromium.org's avatar chrishtr@chromium.org

Add support for attribute selectors in TargetedStyleRecalc.

Like with classes, even if local TargetedStyleRecalc is not
supported for an attribute selector, this also causes style
invalidation tree walks rather than
setting style recalc dirty bits directly.


BUG=339729,352300

Review URL: https://codereview.chromium.org/208323003

git-svn-id: svn://svn.chromium.org/blink/trunk@169793 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 28675f60
Test that adding and removing class names only updates the elements that are affected.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS internals.updateStyleAndReturnAffectedElementCount() is 2
PASS getComputedStyle(inner).width is "200px"
PASS internals.updateStyleAndReturnAffectedElementCount() is 1
PASS getComputedStyle(inner).width is "100px"
PASS internals.updateStyleAndReturnAffectedElementCount() is 1
PASS getComputedStyle(outer2).width is "150px"
PASS internals.updateStyleAndReturnAffectedElementCount() is 1
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE html>
<script src="../../../resources/js-test.js"></script>
<style>
div { width: 100px }
[outer="1"] [inner="1"] { width: 200px }
[outer="2"] { width: 150px }
[outer="3"][nomatch="1"] { width: 300px; }
</style>
<div id="outer">
<div id="mid">
<div id="inner" inner="1">
<div id="innerChild">
</div>
</div>
<div id="inner2">
</div>
</div>
</div>
<div id="outer2">
<div id="inner3"></div>
</div>
<div id="outer3">
<div class="nomatch"></div>
<div class="outer3"></div>
</div>
<script>
description("Test that adding and removing class names only updates the elements that are affected.");
function insertStyleSheet(css)
{
var styleElement = document.createElement("style");
styleElement.textContent = css;
(document.head || document.documentElement).appendChild(styleElement);
}
var outer = document.getElementById('outer');
var inner = document.getElementById('inner');
var outer2 = document.getElementById('outer2');
var outer3 = document.getElementById('outer3');
var count;
if (internals && internals.runtimeFlags.targetedStyleRecalcEnabled)
count = 2;
else
count = 5;
// Style recalc should happen on "inner" and "outer", but not "inner2" or "mid".
outer.offsetTop;
outer.setAttribute('outer', '1');
shouldBe("internals.updateStyleAndReturnAffectedElementCount()", '' + count);
shouldBe("getComputedStyle(inner).width", '"200px"');
if (internals.runtimeFlags.targetedStyleRecalcEnabled)
count = 1;
else
count = 2;
// Style recalc should happen on "inner", but not "innerChild".
inner.offsetTop;
inner.removeAttribute('inner');
shouldBe("internals.updateStyleAndReturnAffectedElementCount()", '' + count);
shouldBe("getComputedStyle(inner).width", '"100px"');
if (internals.runtimeFlags.targetedStyleRecalcEnabled)
count = 1;
else
count = 2;
// Style recalc should happen on "outer2", but not "inner3".
outer2.offsetTop;
outer2.setAttribute('outer', '2');
shouldBe("internals.updateStyleAndReturnAffectedElementCount()", '' + count);
shouldBe("getComputedStyle(outer2).width", '"150px"');
if (internals.runtimeFlags.targetedStyleRecalcEnabled)
count = 1;
else
count = 3;
// Style recalc should happen on "outer3", but none of its children.
outer3.offsetTop;
outer3.setAttribute('outer', '3');
shouldBe("internals.updateStyleAndReturnAffectedElementCount()", '' + count);
</script>
......@@ -34,15 +34,8 @@
#include "core/css/CSSSelector.h"
#include "core/css/CSSSelectorList.h"
#include "core/css/RuleSet.h"
#include "core/css/invalidation/StyleInvalidator.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/Node.h"
#include "core/dom/NodeRenderStyle.h"
#include "core/dom/shadow/ElementShadow.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/rendering/RenderObject.h"
#include "wtf/BitVector.h"
namespace WebCore {
......@@ -116,7 +109,7 @@ RuleFeatureSet::InvalidationSetMode RuleFeatureSet::supportsClassDescendantInval
for (const CSSSelector* component = &selector; component; component = component->tagHistory()) {
// FIXME: next up: Tag and Id.
if (component->m_match == CSSSelector::Class) {
if (component->m_match == CSSSelector::Class || component->isAttributeSelector()) {
if (!foundDescendantRelation)
foundIdent = true;
} else if (!isSkippableComponentForInvalidation(*component)) {
......@@ -138,7 +131,7 @@ RuleFeatureSet::InvalidationSetMode RuleFeatureSet::supportsClassDescendantInval
return foundIdent ? AddFeatures : UseLocalStyleChange;
}
void extractClassIdOrTag(const CSSSelector& selector, Vector<AtomicString>& classes, AtomicString& id, AtomicString& tagName)
void extractClassIdTagOrAttribute(const CSSSelector& selector, Vector<AtomicString>& classes, AtomicString& id, AtomicString& tagName, Vector<AtomicString>& attributes)
{
if (selector.m_match == CSSSelector::Tag)
tagName = selector.tagQName().localName();
......@@ -146,6 +139,8 @@ void extractClassIdOrTag(const CSSSelector& selector, Vector<AtomicString>& clas
id = selector.value();
else if (selector.m_match == CSSSelector::Class)
classes.append(selector.value());
else if (selector.isAttributeSelector())
attributes.append(selector.attribute().localName());
}
RuleFeatureSet::RuleFeatureSet()
......@@ -153,7 +148,16 @@ RuleFeatureSet::RuleFeatureSet()
{
}
RuleFeatureSet::InvalidationSetMode RuleFeatureSet::updateClassInvalidationSets(const CSSSelector& selector)
DescendantInvalidationSet* RuleFeatureSet::invalidationSetForSelector(const CSSSelector& selector)
{
if (selector.m_match == CSSSelector::Class)
return &ensureClassInvalidationSet(selector.value());
if (selector.isAttributeSelector())
return &ensureAttributeInvalidationSet(selector.attribute().localName());
return 0;
}
RuleFeatureSet::InvalidationSetMode RuleFeatureSet::updateInvalidationSets(const CSSSelector& selector)
{
InvalidationSetMode mode = supportsClassDescendantInvalidation(selector);
if (mode != AddFeatures)
......@@ -162,37 +166,39 @@ RuleFeatureSet::InvalidationSetMode RuleFeatureSet::updateClassInvalidationSets(
Vector<AtomicString> classes;
AtomicString id;
AtomicString tagName;
Vector<AtomicString> attributes;
const CSSSelector* lastSelector = &selector;
for (; lastSelector; lastSelector = lastSelector->tagHistory()) {
extractClassIdOrTag(*lastSelector, classes, id, tagName);
if (lastSelector->m_match == CSSSelector::Class)
ensureClassInvalidationSet(lastSelector->value());
extractClassIdTagOrAttribute(*lastSelector, classes, id, tagName, attributes);
// Initialize the entry in the invalidation set map, if supported.
invalidationSetForSelector(*lastSelector);
if (lastSelector->relation() != CSSSelector::SubSelector)
break;
}
if (!lastSelector)
return AddFeatures;
for (const CSSSelector* current = lastSelector->tagHistory(); current; current = current->tagHistory()) {
if (current->m_match == CSSSelector::Class) {
DescendantInvalidationSet& invalidationSet = ensureClassInvalidationSet(current->value());
if (DescendantInvalidationSet* invalidationSet = invalidationSetForSelector(*current)) {
if (!id.isEmpty())
invalidationSet.addId(id);
invalidationSet->addId(id);
if (!tagName.isEmpty())
invalidationSet.addTagName(tagName);
for (Vector<AtomicString>::const_iterator it = classes.begin(); it != classes.end(); ++it) {
invalidationSet.addClass(*it);
}
invalidationSet->addTagName(tagName);
for (Vector<AtomicString>::const_iterator it = classes.begin(); it != classes.end(); ++it)
invalidationSet->addClass(*it);
for (Vector<AtomicString>::const_iterator it = attributes.begin(); it != attributes.end(); ++it)
invalidationSet->addAttribute(*it);
}
}
return AddFeatures;
}
void RuleFeatureSet::addAttributeInASelector(const AtomicString& attributeName)
void RuleFeatureSet::addContentAttr(const AtomicString& attributeName)
{
m_metadata.attrsInRules.add(attributeName);
DescendantInvalidationSet& invalidationSet = ensureAttributeInvalidationSet(attributeName);
invalidationSet.setWholeSubtreeInvalid();
}
void RuleFeatureSet::collectFeaturesFromRuleData(const RuleData& ruleData)
......@@ -200,7 +206,7 @@ void RuleFeatureSet::collectFeaturesFromRuleData(const RuleData& ruleData)
FeatureMetadata metadata;
InvalidationSetMode mode = UseSubtreeStyleChange;
if (m_targetedStyleRecalcEnabled)
mode = updateClassInvalidationSets(ruleData.selector());
mode = updateInvalidationSets(ruleData.selector());
collectFeaturesFromSelector(ruleData.selector(), metadata, mode);
m_metadata.add(metadata);
......@@ -219,6 +225,13 @@ DescendantInvalidationSet& RuleFeatureSet::ensureClassInvalidationSet(const Atom
return *addResult.storedValue->value;
}
DescendantInvalidationSet& RuleFeatureSet::ensureAttributeInvalidationSet(const AtomicString& attributeName)
{
InvalidationSetMap::AddResult addResult = m_attributeInvalidationSets.add(attributeName, nullptr);
if (addResult.isNewEntry)
addResult.storedValue->value = DescendantInvalidationSet::create();
return *addResult.storedValue->value;
}
void RuleFeatureSet::collectFeaturesFromSelector(const CSSSelector& selector)
{
collectFeaturesFromSelector(selector, m_metadata, UseSubtreeStyleChange);
......@@ -231,12 +244,11 @@ void RuleFeatureSet::collectFeaturesFromSelector(const CSSSelector& selector, Ru
for (const CSSSelector* current = &selector; current; current = current->tagHistory()) {
if (current->m_match == CSSSelector::Id) {
metadata.idsInRules.add(current->value());
} else if (current->m_match == CSSSelector::Class && mode != AddFeatures) {
DescendantInvalidationSet& invalidationSet = ensureClassInvalidationSet(current->value());
} else if (mode != AddFeatures && (current->m_match == CSSSelector::Class || current->isAttributeSelector())) {
DescendantInvalidationSet* invalidationSet = invalidationSetForSelector(*current);
ASSERT(invalidationSet);
if (mode == UseSubtreeStyleChange)
invalidationSet.setWholeSubtreeInvalid();
} else if (current->isAttributeSelector()) {
metadata.attrsInRules.add(current->attribute().localName());
invalidationSet->setWholeSubtreeInvalid();
}
if (current->pseudoType() == CSSSelector::PseudoFirstLine)
metadata.usesFirstLineRules = true;
......@@ -276,15 +288,11 @@ void RuleFeatureSet::FeatureMetadata::add(const FeatureMetadata& other)
HashSet<AtomicString>::const_iterator end = other.idsInRules.end();
for (HashSet<AtomicString>::const_iterator it = other.idsInRules.begin(); it != end; ++it)
idsInRules.add(*it);
end = other.attrsInRules.end();
for (HashSet<AtomicString>::const_iterator it = other.attrsInRules.begin(); it != end; ++it)
attrsInRules.add(*it);
}
void RuleFeatureSet::FeatureMetadata::clear()
{
idsInRules.clear();
attrsInRules.clear();
usesFirstLineRules = false;
foundSiblingSelector = false;
maxDirectAdjacentSelectors = 0;
......@@ -292,9 +300,10 @@ void RuleFeatureSet::FeatureMetadata::clear()
void RuleFeatureSet::add(const RuleFeatureSet& other)
{
for (InvalidationSetMap::const_iterator it = other.m_classInvalidationSets.begin(); it != other.m_classInvalidationSets.end(); ++it) {
for (InvalidationSetMap::const_iterator it = other.m_classInvalidationSets.begin(); it != other.m_classInvalidationSets.end(); ++it)
ensureClassInvalidationSet(it->key).combine(*it->value);
}
for (InvalidationSetMap::const_iterator it = other.m_attributeInvalidationSets.begin(); it != other.m_attributeInvalidationSets.end(); ++it)
ensureAttributeInvalidationSet(it->key).combine(*it->value);
m_metadata.add(other.m_metadata);
......@@ -308,6 +317,7 @@ void RuleFeatureSet::clear()
uncommonAttributeRules.clear();
m_metadata.clear();
m_classInvalidationSets.clear();
m_attributeInvalidationSets.clear();
m_pendingInvalidationMap.clear();
}
......@@ -352,6 +362,14 @@ void RuleFeatureSet::scheduleStyleInvalidationForClassChange(const SpaceSplitStr
}
}
void RuleFeatureSet::scheduleStyleInvalidationForAttributeChange(const QualifiedName& attributeName, Element* element)
{
if (RefPtr<DescendantInvalidationSet> invalidationSet = m_attributeInvalidationSets.get(attributeName.localName())) {
ensurePendingInvalidationList(element).append(invalidationSet);
element->setNeedsStyleInvalidation();
}
}
void RuleFeatureSet::addClassToInvalidationSet(const AtomicString& className, Element* element)
{
if (RefPtr<DescendantInvalidationSet> invalidationSet = m_classInvalidationSets.get(className)) {
......
......@@ -29,14 +29,15 @@
namespace WebCore {
class Document;
class Node;
class ShadowRoot;
class StyleRule;
class CSSSelector;
class CSSSelectorList;
class Document;
class Node;
class QualifiedName;
class RuleData;
class ShadowRoot;
class SpaceSplitString;
class StyleRule;
struct RuleFeature {
RuleFeature(StyleRule* rule, unsigned selectorIndex, bool hasDocumentSecurityOrigin)
......@@ -69,7 +70,7 @@ public:
inline bool hasSelectorForAttribute(const AtomicString& attributeName) const
{
ASSERT(!attributeName.isEmpty());
return m_metadata.attrsInRules.contains(attributeName);
return m_attributeInvalidationSets.get(attributeName);
}
inline bool hasSelectorForClass(const AtomicString& classValue) const
......@@ -86,6 +87,8 @@ public:
void scheduleStyleInvalidationForClassChange(const SpaceSplitString& changedClasses, Element*);
void scheduleStyleInvalidationForClassChange(const SpaceSplitString& oldClasses, const SpaceSplitString& newClasses, Element*);
void scheduleStyleInvalidationForAttributeChange(const QualifiedName& attributeName, Element*);
// Clears all style invalidation state for the passed node.
void clearStyleInvalidation(Node*);
......@@ -96,7 +99,8 @@ public:
// Marks the given attribute name as "appearing in a selector". Used for
// CSS properties such as content: ... attr(...) ...
void addAttributeInASelector(const AtomicString& attributeName);
// FIXME: record these internally to this class instead calls from StyleResolver to here.
void addContentAttr(const AtomicString& attributeName);
Vector<RuleFeature> siblingRules;
Vector<RuleFeature> uncommonAttributeRules;
......@@ -122,7 +126,6 @@ private:
bool foundSiblingSelector;
unsigned maxDirectAdjacentSelectors;
HashSet<AtomicString> idsInRules;
HashSet<AtomicString> attrsInRules;
};
enum InvalidationSetMode {
......@@ -137,7 +140,10 @@ private:
void collectFeaturesFromSelectorList(const CSSSelectorList*, FeatureMetadata&, InvalidationSetMode);
DescendantInvalidationSet& ensureClassInvalidationSet(const AtomicString& className);
InvalidationSetMode updateClassInvalidationSets(const CSSSelector&);
DescendantInvalidationSet& ensureAttributeInvalidationSet(const AtomicString& className);
DescendantInvalidationSet* invalidationSetForSelector(const CSSSelector&);
InvalidationSetMode updateInvalidationSets(const CSSSelector&);
void addClassToInvalidationSet(const AtomicString& className, Element*);
......@@ -145,6 +151,8 @@ private:
FeatureMetadata m_metadata;
InvalidationSetMap m_classInvalidationSets;
InvalidationSetMap m_attributeInvalidationSets;
PendingInvalidationMap m_pendingInvalidationMap;
bool m_targetedStyleRecalcEnabled;
......
......@@ -69,6 +69,12 @@ void DescendantInvalidationSet::combine(const DescendantInvalidationSet& other)
for (HashSet<AtomicString>::const_iterator it = other.m_tagNames->begin(); it != end; ++it)
addTagName(*it);
}
if (other.m_attributes) {
HashSet<AtomicString>::const_iterator end = other.m_attributes->end();
for (HashSet<AtomicString>::const_iterator it = other.m_attributes->begin(); it != end; ++it)
addAttribute(*it);
}
}
HashSet<AtomicString>& DescendantInvalidationSet::ensureClassSet()
......@@ -92,6 +98,13 @@ HashSet<AtomicString>& DescendantInvalidationSet::ensureTagNameSet()
return *m_tagNames;
}
HashSet<AtomicString>& DescendantInvalidationSet::ensureAttributeSet()
{
if (!m_attributes)
m_attributes = adoptPtr(new HashSet<AtomicString>);
return *m_attributes;
}
void DescendantInvalidationSet::addClass(const AtomicString& className)
{
if (wholeSubtreeInvalid())
......@@ -113,6 +126,13 @@ void DescendantInvalidationSet::addTagName(const AtomicString& tagName)
ensureTagNameSet().add(tagName);
}
void DescendantInvalidationSet::addAttribute(const AtomicString& attribute)
{
if (wholeSubtreeInvalid())
return;
ensureAttributeSet().add(attribute);
}
void DescendantInvalidationSet::getClasses(Vector<AtomicString>& classes) const
{
if (!m_classes)
......@@ -121,6 +141,14 @@ void DescendantInvalidationSet::getClasses(Vector<AtomicString>& classes) const
classes.append(*it);
}
void DescendantInvalidationSet::getAttributes(Vector<AtomicString>& attributes) const
{
if (!m_attributes)
return;
for (HashSet<AtomicString>::const_iterator it = m_attributes->begin(); it != m_attributes->end(); ++it)
attributes.append(*it);
}
void DescendantInvalidationSet::setWholeSubtreeInvalid()
{
if (m_allDescendantsMightBeInvalid)
......@@ -130,6 +158,7 @@ void DescendantInvalidationSet::setWholeSubtreeInvalid()
m_classes = nullptr;
m_ids = nullptr;
m_tagNames = nullptr;
m_attributes = nullptr;
}
} // namespace WebCore
......@@ -56,10 +56,14 @@ public:
void addClass(const AtomicString& className);
void addId(const AtomicString& id);
void addTagName(const AtomicString& tagName);
void addAttribute(const AtomicString& attributeLocalName);
// Appends the classes in this DescendantInvalidationSet to the vector.
void getClasses(Vector<AtomicString>& classes) const;
// Appends the attributes in this DescendantInvalidationSet to the vector.
void getAttributes(Vector<AtomicString>& attributes) const;
void setWholeSubtreeInvalid();
bool wholeSubtreeInvalid() const { return m_allDescendantsMightBeInvalid; }
private:
......@@ -68,6 +72,7 @@ private:
HashSet<AtomicString>& ensureClassSet();
HashSet<AtomicString>& ensureIdSet();
HashSet<AtomicString>& ensureTagNameSet();
HashSet<AtomicString>& ensureAttributeSet();
// If true, all descendants might be invalidated, so a full subtree recalc is required.
bool m_allDescendantsMightBeInvalid;
......@@ -76,6 +81,7 @@ private:
OwnPtr<HashSet<AtomicString> > m_classes;
OwnPtr<HashSet<AtomicString> > m_ids;
OwnPtr<HashSet<AtomicString> > m_tagNames;
OwnPtr<HashSet<AtomicString> > m_attributes;
};
} // namespace WebCore
......
......@@ -33,18 +33,24 @@ StyleInvalidator::StyleInvalidator(Document& document)
void StyleInvalidator::RecursionData::pushInvalidationSet(const DescendantInvalidationSet& invalidationSet)
{
invalidationSet.getClasses(m_invalidationClasses);
invalidationSet.getAttributes(m_invalidationAttributes);
m_foundInvalidationSet = true;
}
bool StyleInvalidator::RecursionData::matchesCurrentInvalidationSets(Element& element)
{
if (!element.hasClass())
return false;
const SpaceSplitString& classNames = element.classNames();
for (Vector<AtomicString>::const_iterator it = m_invalidationClasses.begin(); it != m_invalidationClasses.end(); ++it) {
if (classNames.contains(*it))
return true;
if (element.hasClass()) {
const SpaceSplitString& classNames = element.classNames();
for (Vector<AtomicString>::const_iterator it = m_invalidationClasses.begin(); it != m_invalidationClasses.end(); ++it) {
if (classNames.contains(*it))
return true;
}
}
if (element.hasAttributes()) {
for (Vector<AtomicString>::const_iterator it = m_invalidationAttributes.begin(); it != m_invalidationAttributes.end(); ++it) {
if (element.hasAttribute(*it))
return true;
}
}
return false;
......
......@@ -29,6 +29,7 @@ private:
bool foundInvalidationSet() { return m_foundInvalidationSet; }
Vector<AtomicString> m_invalidationClasses;
Vector<AtomicString> m_invalidationAttributes;
bool m_foundInvalidationSet;
};
......@@ -36,6 +37,7 @@ private:
public:
RecursionCheckpoint(RecursionData* data)
: m_prevClassLength(data->m_invalidationClasses.size()),
m_prevAttributeLength(data->m_invalidationAttributes.size()),
m_prevFoundInvalidationSet(data->m_foundInvalidationSet),
m_data(data)
{ }
......@@ -47,6 +49,7 @@ private:
private:
int m_prevClassLength;
int m_prevAttributeLength;
bool m_prevFoundInvalidationSet;
RecursionData* m_data;
};
......
......@@ -626,7 +626,7 @@ static inline void resetDirectionAndWritingModeOnDocument(Document& document)
static void addContentAttrValuesToFeatures(const Vector<AtomicString>& contentAttrValues, RuleFeatureSet& features)
{
for (size_t i = 0; i < contentAttrValues.size(); ++i)
features.addAttributeInASelector(contentAttrValues[i]);
features.addContentAttr(contentAttrValues[i]);
}
// Start loading resources referenced by this style.
......
......@@ -2896,11 +2896,6 @@ void Element::updateLabel(TreeScope& scope, const AtomicString& oldForAttributeV
scope.addLabel(newForAttributeValue, toHTMLLabelElement(this));
}
static bool hasSelectorForAttribute(Document* document, const AtomicString& localName)
{
return document->ensureStyleResolver().ensureUpdatedRuleFeatureSet().hasSelectorForAttribute(localName);
}
void Element::willModifyAttribute(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue)
{
if (isIdAttributeName(name)) {
......@@ -2914,8 +2909,8 @@ void Element::willModifyAttribute(const QualifiedName& name, const AtomicString&
}
if (oldValue != newValue) {
if (inActiveDocument() && hasSelectorForAttribute(&document(), name.localName()))
setNeedsStyleRecalc(SubtreeStyleChange);
if (inActiveDocument())
document().ensureStyleResolver().ensureUpdatedRuleFeatureSet().scheduleStyleInvalidationForAttributeChange(name, this);
if (isUpgradedCustomElement())
CustomElement::attributeDidChange(this, name.localName(), oldValue, newValue);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment