Commit 800858c8 authored by esprehn@chromium.org's avatar esprehn@chromium.org

Skip child ShadowRoots when invalidation does not have boundary crossing rules

When the only things that changed are not tree boundary crossing rules we
can skip invalidation in descendant ShadowRoots. This means that
if you have lots of nested components with duplicate class names then using
a descendant selector in an enclosing component won't invalidate style in
nested ShadowRoots.

I also packed the booleans on DescendantInvalidationSet since we keep growing
it with new flags.

In the future we can make this tree boundary crossing logic even smarter so that
if you have both tree boundary and not tree boundary crossing invalidation
sets on the same subtree we can skip running the non-tree boundary crossing
rules when recursing into a ShadowRoot.

I also took this as an opportunity to clean up the addFeaturesToInvalidationSets
logic and merged the new treeBoundaryCrossing flag and the wholeSubtreeInvalid
flag into the InvalidationSetFeatures.

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

git-svn-id: svn://svn.chromium.org/blink/trunk@175171 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 3ed06682
Invalidation sets should not apply across shadow roots if tree boundary crossing rules are not used.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS internals.updateStyleAndReturnAffectedElementCount() is 11
PASS internals.updateStyleAndReturnAffectedElementCount() is 31
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE html>
<script src="../../../resources/js-test.js"></script>
<style>
.deep /deep/ .inside,
.shallow .inside {
padding: 0;
}
</style>
<div id="sandbox"></div>
<script>
description("Invalidation sets should not apply across shadow roots if tree boundary crossing rules are not used.");
var sandbox = document.getElementById("sandbox");
var sandboxRoot = sandbox.createShadowRoot();
sandboxRoot.innerHTML = "<content></content>";
for (var j = 0; j < 10; ++j) {
var childDiv = sandbox.appendChild(document.createElement("div"));
childDiv.className = "inside";
var shadowDiv = sandboxRoot.appendChild(document.createElement("div"));
shadowDiv.className = "inside";
shadowDiv.createShadowRoot().innerHTML = "<div class=inside><content></content></div>";
}
getComputedStyle(sandbox).color;
sandbox.className = "shallow";
if (window.internals)
shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "11");
sandbox.className = "";
getComputedStyle(sandbox).color;
sandbox.className = "deep";
if (window.internals)
shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "31");
</script>
...@@ -207,41 +207,50 @@ RuleFeatureSet::InvalidationSetMode RuleFeatureSet::updateInvalidationSets(const ...@@ -207,41 +207,50 @@ RuleFeatureSet::InvalidationSetMode RuleFeatureSet::updateInvalidationSets(const
return mode; return mode;
InvalidationSetFeatures features; InvalidationSetFeatures features;
const CSSSelector* current = extractInvalidationSetFeatures(selector, features); if (const CSSSelector* current = extractInvalidationSetFeatures(selector, features))
if (current) { addFeaturesToInvalidationSets(*current, features);
bool wholeSubtree = current->relation() == CSSSelector::DirectAdjacent || current->relation() == CSSSelector::IndirectAdjacent;
current = current->tagHistory();
if (current)
addFeaturesToInvalidationSets(*current, features, wholeSubtree);
}
return AddFeatures; return AddFeatures;
} }
const CSSSelector* RuleFeatureSet::extractInvalidationSetFeatures(const CSSSelector& selector, InvalidationSetFeatures& features) const CSSSelector* RuleFeatureSet::extractInvalidationSetFeatures(const CSSSelector& selector, InvalidationSetFeatures& features)
{ {
const CSSSelector* lastSelector = &selector; for (const CSSSelector* current = &selector; current; current = current->tagHistory()) {
for (; lastSelector; lastSelector = lastSelector->tagHistory()) { extractInvalidationSetFeature(*current, features);
extractInvalidationSetFeature(*lastSelector, features);
// Initialize the entry in the invalidation set map, if supported. // Initialize the entry in the invalidation set map, if supported.
invalidationSetForSelector(*lastSelector); invalidationSetForSelector(*current);
if (lastSelector->pseudoType() == CSSSelector::PseudoHost || lastSelector->pseudoType() == CSSSelector::PseudoAny) { if (current->pseudoType() == CSSSelector::PseudoHost || current->pseudoType() == CSSSelector::PseudoAny) {
if (const CSSSelectorList* selectorList = lastSelector->selectorList()) { if (const CSSSelectorList* selectorList = current->selectorList()) {
for (const CSSSelector* selector = selectorList->first(); selector; selector = CSSSelectorList::next(*selector)) for (const CSSSelector* selector = selectorList->first(); selector; selector = CSSSelectorList::next(*selector))
extractInvalidationSetFeatures(*selector, features); extractInvalidationSetFeatures(*selector, features);
} }
} }
if (lastSelector->relation() != CSSSelector::SubSelector) switch (current->relation()) {
case CSSSelector::SubSelector:
break; break;
case CSSSelector::ShadowPseudo:
case CSSSelector::ShadowDeep:
features.treeBoundaryCrossing = true;
return current->tagHistory();
case CSSSelector::DirectAdjacent:
case CSSSelector::IndirectAdjacent:
features.wholeSubtree = true;
return current->tagHistory();
case CSSSelector::Descendant:
case CSSSelector::Child:
return current->tagHistory();
}
} }
return lastSelector; return 0;
} }
void RuleFeatureSet::addFeaturesToInvalidationSets(const CSSSelector& selector, const InvalidationSetFeatures& features, bool wholeSubtree) void RuleFeatureSet::addFeaturesToInvalidationSets(const CSSSelector& selector, InvalidationSetFeatures& features)
{ {
for (const CSSSelector* current = &selector; current; current = current->tagHistory()) { for (const CSSSelector* current = &selector; current; current = current->tagHistory()) {
if (DescendantInvalidationSet* invalidationSet = invalidationSetForSelector(*current)) { if (DescendantInvalidationSet* invalidationSet = invalidationSetForSelector(*current)) {
if (wholeSubtree) { if (features.treeBoundaryCrossing)
invalidationSet->setTreeBoundaryCrossing();
if (features.wholeSubtree) {
invalidationSet->setWholeSubtreeInvalid(); invalidationSet->setWholeSubtreeInvalid();
} else { } else {
if (!features.id.isEmpty()) if (!features.id.isEmpty())
...@@ -256,27 +265,28 @@ void RuleFeatureSet::addFeaturesToInvalidationSets(const CSSSelector& selector, ...@@ -256,27 +265,28 @@ void RuleFeatureSet::addFeaturesToInvalidationSets(const CSSSelector& selector,
invalidationSet->setCustomPseudoInvalid(); invalidationSet->setCustomPseudoInvalid();
} }
} else if (current->pseudoType() == CSSSelector::PseudoHost || current->pseudoType() == CSSSelector::PseudoAny) { } else if (current->pseudoType() == CSSSelector::PseudoHost || current->pseudoType() == CSSSelector::PseudoAny) {
if (current->pseudoType() == CSSSelector::PseudoHost)
features.treeBoundaryCrossing = true;
if (const CSSSelectorList* selectorList = current->selectorList()) { if (const CSSSelectorList* selectorList = current->selectorList()) {
for (const CSSSelector* selector = selectorList->first(); selector; selector = CSSSelectorList::next(*selector)) for (const CSSSelector* selector = selectorList->first(); selector; selector = CSSSelectorList::next(*selector))
addFeaturesToInvalidationSets(*selector, features, wholeSubtree); addFeaturesToInvalidationSets(*selector, features);
} }
} }
switch (current->relation()) { switch (current->relation()) {
case CSSSelector::Descendant: case CSSSelector::SubSelector:
case CSSSelector::Child: break;
case CSSSelector::ShadowPseudo: case CSSSelector::ShadowPseudo:
case CSSSelector::ShadowDeep: case CSSSelector::ShadowDeep:
wholeSubtree = false; features.treeBoundaryCrossing = true;
features.wholeSubtree = false;
break;
case CSSSelector::Descendant:
case CSSSelector::Child:
features.wholeSubtree = false;
break; break;
case CSSSelector::DirectAdjacent: case CSSSelector::DirectAdjacent:
case CSSSelector::IndirectAdjacent: case CSSSelector::IndirectAdjacent:
wholeSubtree = true; features.wholeSubtree = true;
break;
case CSSSelector::SubSelector:
break;
default:
// All combinators should be handled above.
ASSERT_NOT_REACHED();
break; break;
} }
} }
......
...@@ -148,17 +148,23 @@ private: ...@@ -148,17 +148,23 @@ private:
InvalidationSetMode updateInvalidationSets(const CSSSelector&); InvalidationSetMode updateInvalidationSets(const CSSSelector&);
struct InvalidationSetFeatures { struct InvalidationSetFeatures {
InvalidationSetFeatures() : customPseudoElement(false) { } InvalidationSetFeatures()
: customPseudoElement(false)
, treeBoundaryCrossing(false)
, wholeSubtree(false)
{ }
Vector<AtomicString> classes; Vector<AtomicString> classes;
Vector<AtomicString> attributes; Vector<AtomicString> attributes;
AtomicString id; AtomicString id;
AtomicString tagName; AtomicString tagName;
bool customPseudoElement; bool customPseudoElement;
bool treeBoundaryCrossing;
bool wholeSubtree;
}; };
static void extractInvalidationSetFeature(const CSSSelector&, InvalidationSetFeatures&); static void extractInvalidationSetFeature(const CSSSelector&, InvalidationSetFeatures&);
const CSSSelector* extractInvalidationSetFeatures(const CSSSelector&, InvalidationSetFeatures&); const CSSSelector* extractInvalidationSetFeatures(const CSSSelector&, InvalidationSetFeatures&);
void addFeaturesToInvalidationSets(const CSSSelector&, const InvalidationSetFeatures&, bool wholeSubtree); void addFeaturesToInvalidationSets(const CSSSelector&, InvalidationSetFeatures&);
void addClassToInvalidationSet(const AtomicString& className, Element&); void addClassToInvalidationSet(const AtomicString& className, Element&);
......
...@@ -39,6 +39,7 @@ namespace WebCore { ...@@ -39,6 +39,7 @@ namespace WebCore {
DescendantInvalidationSet::DescendantInvalidationSet() DescendantInvalidationSet::DescendantInvalidationSet()
: m_allDescendantsMightBeInvalid(false) : m_allDescendantsMightBeInvalid(false)
, m_customPseudoInvalid(false) , m_customPseudoInvalid(false)
, m_treeBoundaryCrossing(false)
{ {
} }
...@@ -85,6 +86,9 @@ void DescendantInvalidationSet::combine(const DescendantInvalidationSet& other) ...@@ -85,6 +86,9 @@ void DescendantInvalidationSet::combine(const DescendantInvalidationSet& other)
if (other.customPseudoInvalid()) if (other.customPseudoInvalid())
setCustomPseudoInvalid(); setCustomPseudoInvalid();
if (other.treeBoundaryCrossing())
setTreeBoundaryCrossing();
if (other.m_classes) { if (other.m_classes) {
WillBeHeapHashSet<AtomicString>::const_iterator end = other.m_classes->end(); WillBeHeapHashSet<AtomicString>::const_iterator end = other.m_classes->end();
for (WillBeHeapHashSet<AtomicString>::const_iterator it = other.m_classes->begin(); it != end; ++it) for (WillBeHeapHashSet<AtomicString>::const_iterator it = other.m_classes->begin(); it != end; ++it)
...@@ -172,6 +176,7 @@ void DescendantInvalidationSet::setWholeSubtreeInvalid() ...@@ -172,6 +176,7 @@ void DescendantInvalidationSet::setWholeSubtreeInvalid()
return; return;
m_allDescendantsMightBeInvalid = true; m_allDescendantsMightBeInvalid = true;
m_treeBoundaryCrossing = false;
m_classes = nullptr; m_classes = nullptr;
m_ids = nullptr; m_ids = nullptr;
m_tagNames = nullptr; m_tagNames = nullptr;
......
...@@ -64,6 +64,9 @@ public: ...@@ -64,6 +64,9 @@ public:
void setWholeSubtreeInvalid(); void setWholeSubtreeInvalid();
bool wholeSubtreeInvalid() const { return m_allDescendantsMightBeInvalid; } bool wholeSubtreeInvalid() const { return m_allDescendantsMightBeInvalid; }
void setTreeBoundaryCrossing() { m_treeBoundaryCrossing = true; }
bool treeBoundaryCrossing() const { return m_treeBoundaryCrossing; }
void setCustomPseudoInvalid() { m_customPseudoInvalid = true; } void setCustomPseudoInvalid() { m_customPseudoInvalid = true; }
bool customPseudoInvalid() const { return m_customPseudoInvalid; } bool customPseudoInvalid() const { return m_customPseudoInvalid; }
...@@ -79,17 +82,20 @@ private: ...@@ -79,17 +82,20 @@ private:
WillBeHeapHashSet<AtomicString>& ensureTagNameSet(); WillBeHeapHashSet<AtomicString>& ensureTagNameSet();
WillBeHeapHashSet<AtomicString>& ensureAttributeSet(); WillBeHeapHashSet<AtomicString>& ensureAttributeSet();
// If true, all descendants might be invalidated, so a full subtree recalc is required.
bool m_allDescendantsMightBeInvalid;
// If true, all descendants which are custom pseudo elements must be invalidated.
bool m_customPseudoInvalid;
// FIXME: optimize this if it becomes a memory issue. // FIXME: optimize this if it becomes a memory issue.
OwnPtrWillBeMember<WillBeHeapHashSet<AtomicString> > m_classes; OwnPtrWillBeMember<WillBeHeapHashSet<AtomicString> > m_classes;
OwnPtrWillBeMember<WillBeHeapHashSet<AtomicString> > m_ids; OwnPtrWillBeMember<WillBeHeapHashSet<AtomicString> > m_ids;
OwnPtrWillBeMember<WillBeHeapHashSet<AtomicString> > m_tagNames; OwnPtrWillBeMember<WillBeHeapHashSet<AtomicString> > m_tagNames;
OwnPtrWillBeMember<WillBeHeapHashSet<AtomicString> > m_attributes; OwnPtrWillBeMember<WillBeHeapHashSet<AtomicString> > m_attributes;
// If true, all descendants might be invalidated, so a full subtree recalc is required.
unsigned m_allDescendantsMightBeInvalid : 1;
// If true, all descendants which are custom pseudo elements must be invalidated.
unsigned m_customPseudoInvalid : 1;
// If true, the invalidation must traverse into ShadowRoots with this set.
unsigned m_treeBoundaryCrossing : 1;
}; };
} // namespace WebCore } // namespace WebCore
......
...@@ -71,6 +71,8 @@ StyleInvalidator::~StyleInvalidator() ...@@ -71,6 +71,8 @@ StyleInvalidator::~StyleInvalidator()
void StyleInvalidator::RecursionData::pushInvalidationSet(const DescendantInvalidationSet& invalidationSet) void StyleInvalidator::RecursionData::pushInvalidationSet(const DescendantInvalidationSet& invalidationSet)
{ {
ASSERT(!m_wholeSubtreeInvalid); ASSERT(!m_wholeSubtreeInvalid);
if (invalidationSet.treeBoundaryCrossing())
m_treeBoundaryCrossing = true;
if (invalidationSet.wholeSubtreeInvalid()) { if (invalidationSet.wholeSubtreeInvalid()) {
m_wholeSubtreeInvalid = true; m_wholeSubtreeInvalid = true;
return; return;
...@@ -115,14 +117,16 @@ bool StyleInvalidator::invalidateChildren(Element& element) ...@@ -115,14 +117,16 @@ bool StyleInvalidator::invalidateChildren(Element& element)
{ {
bool someChildrenNeedStyleRecalc = false; bool someChildrenNeedStyleRecalc = false;
for (ShadowRoot* root = element.youngestShadowRoot(); root; root = root->olderShadowRoot()) { for (ShadowRoot* root = element.youngestShadowRoot(); root; root = root->olderShadowRoot()) {
for (Element* child = ElementTraversal::firstWithin(*root); child; child = ElementTraversal::nextSibling(*child)) { if (!m_recursionData.treeBoundaryCrossing() && !root->childNeedsStyleInvalidation() && !root->needsStyleInvalidation())
continue;
for (Element* child = ElementTraversal::firstChild(*root); child; child = ElementTraversal::nextSibling(*child)) {
bool childRecalced = invalidate(*child); bool childRecalced = invalidate(*child);
someChildrenNeedStyleRecalc = someChildrenNeedStyleRecalc || childRecalced; someChildrenNeedStyleRecalc = someChildrenNeedStyleRecalc || childRecalced;
} }
root->clearChildNeedsStyleInvalidation(); root->clearChildNeedsStyleInvalidation();
root->clearNeedsStyleInvalidation(); root->clearNeedsStyleInvalidation();
} }
for (Element* child = ElementTraversal::firstWithin(element); child; child = ElementTraversal::nextSibling(*child)) { for (Element* child = ElementTraversal::firstChild(element); child; child = ElementTraversal::nextSibling(*child)) {
bool childRecalced = invalidate(*child); bool childRecalced = invalidate(*child);
someChildrenNeedStyleRecalc = someChildrenNeedStyleRecalc || childRecalced; someChildrenNeedStyleRecalc = someChildrenNeedStyleRecalc || childRecalced;
} }
......
...@@ -38,17 +38,23 @@ private: ...@@ -38,17 +38,23 @@ private:
RecursionData() RecursionData()
: m_invalidateCustomPseudo(false) : m_invalidateCustomPseudo(false)
, m_wholeSubtreeInvalid(false) , m_wholeSubtreeInvalid(false)
, m_treeBoundaryCrossing(false)
{ } { }
void pushInvalidationSet(const DescendantInvalidationSet&); void pushInvalidationSet(const DescendantInvalidationSet&);
bool matchesCurrentInvalidationSets(Element&); bool matchesCurrentInvalidationSets(Element&);
bool hasInvalidationSets() const { return m_invalidationSets.size(); } bool hasInvalidationSets() const { return m_invalidationSets.size(); }
bool wholeSubtreeInvalid() const { return m_wholeSubtreeInvalid; } bool wholeSubtreeInvalid() const { return m_wholeSubtreeInvalid; }
void setWholeSubtreeInvalid() { m_wholeSubtreeInvalid = true; } void setWholeSubtreeInvalid() { m_wholeSubtreeInvalid = true; }
bool treeBoundaryCrossing() const { return m_treeBoundaryCrossing; }
typedef Vector<const DescendantInvalidationSet*, 16> InvalidationSets; typedef Vector<const DescendantInvalidationSet*, 16> InvalidationSets;
InvalidationSets m_invalidationSets; InvalidationSets m_invalidationSets;
bool m_invalidateCustomPseudo; bool m_invalidateCustomPseudo;
bool m_wholeSubtreeInvalid; bool m_wholeSubtreeInvalid;
bool m_treeBoundaryCrossing;
}; };
class RecursionCheckpoint { class RecursionCheckpoint {
...@@ -57,6 +63,7 @@ private: ...@@ -57,6 +63,7 @@ private:
: m_prevInvalidationSetsSize(data->m_invalidationSets.size()) : m_prevInvalidationSetsSize(data->m_invalidationSets.size())
, m_prevInvalidateCustomPseudo(data->m_invalidateCustomPseudo) , m_prevInvalidateCustomPseudo(data->m_invalidateCustomPseudo)
, m_prevWholeSubtreeInvalid(data->m_wholeSubtreeInvalid) , m_prevWholeSubtreeInvalid(data->m_wholeSubtreeInvalid)
, m_treeBoundaryCrossing(data->m_treeBoundaryCrossing)
, m_data(data) , m_data(data)
{ } { }
~RecursionCheckpoint() ~RecursionCheckpoint()
...@@ -64,12 +71,14 @@ private: ...@@ -64,12 +71,14 @@ private:
m_data->m_invalidationSets.remove(m_prevInvalidationSetsSize, m_data->m_invalidationSets.size() - m_prevInvalidationSetsSize); m_data->m_invalidationSets.remove(m_prevInvalidationSetsSize, m_data->m_invalidationSets.size() - m_prevInvalidationSetsSize);
m_data->m_invalidateCustomPseudo = m_prevInvalidateCustomPseudo; m_data->m_invalidateCustomPseudo = m_prevInvalidateCustomPseudo;
m_data->m_wholeSubtreeInvalid = m_prevWholeSubtreeInvalid; m_data->m_wholeSubtreeInvalid = m_prevWholeSubtreeInvalid;
m_data->m_treeBoundaryCrossing = m_treeBoundaryCrossing;
} }
private: private:
int m_prevInvalidationSetsSize; int m_prevInvalidationSetsSize;
bool m_prevInvalidateCustomPseudo; bool m_prevInvalidateCustomPseudo;
bool m_prevWholeSubtreeInvalid; bool m_prevWholeSubtreeInvalid;
bool m_treeBoundaryCrossing;
RecursionData* m_data; RecursionData* m_data;
}; };
......
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