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
return mode;
InvalidationSetFeatures features;
const CSSSelector* current = extractInvalidationSetFeatures(selector, features);
if (current) {
bool wholeSubtree = current->relation() == CSSSelector::DirectAdjacent || current->relation() == CSSSelector::IndirectAdjacent;
current = current->tagHistory();
if (current)
addFeaturesToInvalidationSets(*current, features, wholeSubtree);
}
if (const CSSSelector* current = extractInvalidationSetFeatures(selector, features))
addFeaturesToInvalidationSets(*current, features);
return AddFeatures;
}
const CSSSelector* RuleFeatureSet::extractInvalidationSetFeatures(const CSSSelector& selector, InvalidationSetFeatures& features)
{
const CSSSelector* lastSelector = &selector;
for (; lastSelector; lastSelector = lastSelector->tagHistory()) {
extractInvalidationSetFeature(*lastSelector, features);
for (const CSSSelector* current = &selector; current; current = current->tagHistory()) {
extractInvalidationSetFeature(*current, features);
// Initialize the entry in the invalidation set map, if supported.
invalidationSetForSelector(*lastSelector);
if (lastSelector->pseudoType() == CSSSelector::PseudoHost || lastSelector->pseudoType() == CSSSelector::PseudoAny) {
if (const CSSSelectorList* selectorList = lastSelector->selectorList()) {
invalidationSetForSelector(*current);
if (current->pseudoType() == CSSSelector::PseudoHost || current->pseudoType() == CSSSelector::PseudoAny) {
if (const CSSSelectorList* selectorList = current->selectorList()) {
for (const CSSSelector* selector = selectorList->first(); selector; selector = CSSSelectorList::next(*selector))
extractInvalidationSetFeatures(*selector, features);
}
}
if (lastSelector->relation() != CSSSelector::SubSelector)
switch (current->relation()) {
case CSSSelector::SubSelector:
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()) {
if (DescendantInvalidationSet* invalidationSet = invalidationSetForSelector(*current)) {
if (wholeSubtree) {
if (features.treeBoundaryCrossing)
invalidationSet->setTreeBoundaryCrossing();
if (features.wholeSubtree) {
invalidationSet->setWholeSubtreeInvalid();
} else {
if (!features.id.isEmpty())
......@@ -256,27 +265,28 @@ void RuleFeatureSet::addFeaturesToInvalidationSets(const CSSSelector& selector,
invalidationSet->setCustomPseudoInvalid();
}
} else if (current->pseudoType() == CSSSelector::PseudoHost || current->pseudoType() == CSSSelector::PseudoAny) {
if (current->pseudoType() == CSSSelector::PseudoHost)
features.treeBoundaryCrossing = true;
if (const CSSSelectorList* selectorList = current->selectorList()) {
for (const CSSSelector* selector = selectorList->first(); selector; selector = CSSSelectorList::next(*selector))
addFeaturesToInvalidationSets(*selector, features, wholeSubtree);
addFeaturesToInvalidationSets(*selector, features);
}
}
switch (current->relation()) {
case CSSSelector::Descendant:
case CSSSelector::Child:
case CSSSelector::SubSelector:
break;
case CSSSelector::ShadowPseudo:
case CSSSelector::ShadowDeep:
wholeSubtree = false;
features.treeBoundaryCrossing = true;
features.wholeSubtree = false;
break;
case CSSSelector::Descendant:
case CSSSelector::Child:
features.wholeSubtree = false;
break;
case CSSSelector::DirectAdjacent:
case CSSSelector::IndirectAdjacent:
wholeSubtree = true;
break;
case CSSSelector::SubSelector:
break;
default:
// All combinators should be handled above.
ASSERT_NOT_REACHED();
features.wholeSubtree = true;
break;
}
}
......
......@@ -148,17 +148,23 @@ private:
InvalidationSetMode updateInvalidationSets(const CSSSelector&);
struct InvalidationSetFeatures {
InvalidationSetFeatures() : customPseudoElement(false) { }
InvalidationSetFeatures()
: customPseudoElement(false)
, treeBoundaryCrossing(false)
, wholeSubtree(false)
{ }
Vector<AtomicString> classes;
Vector<AtomicString> attributes;
AtomicString id;
AtomicString tagName;
bool customPseudoElement;
bool treeBoundaryCrossing;
bool wholeSubtree;
};
static void extractInvalidationSetFeature(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&);
......
......@@ -39,6 +39,7 @@ namespace WebCore {
DescendantInvalidationSet::DescendantInvalidationSet()
: m_allDescendantsMightBeInvalid(false)
, m_customPseudoInvalid(false)
, m_treeBoundaryCrossing(false)
{
}
......@@ -85,6 +86,9 @@ void DescendantInvalidationSet::combine(const DescendantInvalidationSet& other)
if (other.customPseudoInvalid())
setCustomPseudoInvalid();
if (other.treeBoundaryCrossing())
setTreeBoundaryCrossing();
if (other.m_classes) {
WillBeHeapHashSet<AtomicString>::const_iterator end = other.m_classes->end();
for (WillBeHeapHashSet<AtomicString>::const_iterator it = other.m_classes->begin(); it != end; ++it)
......@@ -172,6 +176,7 @@ void DescendantInvalidationSet::setWholeSubtreeInvalid()
return;
m_allDescendantsMightBeInvalid = true;
m_treeBoundaryCrossing = false;
m_classes = nullptr;
m_ids = nullptr;
m_tagNames = nullptr;
......
......@@ -64,6 +64,9 @@ public:
void setWholeSubtreeInvalid();
bool wholeSubtreeInvalid() const { return m_allDescendantsMightBeInvalid; }
void setTreeBoundaryCrossing() { m_treeBoundaryCrossing = true; }
bool treeBoundaryCrossing() const { return m_treeBoundaryCrossing; }
void setCustomPseudoInvalid() { m_customPseudoInvalid = true; }
bool customPseudoInvalid() const { return m_customPseudoInvalid; }
......@@ -79,17 +82,20 @@ private:
WillBeHeapHashSet<AtomicString>& ensureTagNameSet();
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.
OwnPtrWillBeMember<WillBeHeapHashSet<AtomicString> > m_classes;
OwnPtrWillBeMember<WillBeHeapHashSet<AtomicString> > m_ids;
OwnPtrWillBeMember<WillBeHeapHashSet<AtomicString> > m_tagNames;
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
......
......@@ -71,6 +71,8 @@ StyleInvalidator::~StyleInvalidator()
void StyleInvalidator::RecursionData::pushInvalidationSet(const DescendantInvalidationSet& invalidationSet)
{
ASSERT(!m_wholeSubtreeInvalid);
if (invalidationSet.treeBoundaryCrossing())
m_treeBoundaryCrossing = true;
if (invalidationSet.wholeSubtreeInvalid()) {
m_wholeSubtreeInvalid = true;
return;
......@@ -115,14 +117,16 @@ bool StyleInvalidator::invalidateChildren(Element& element)
{
bool someChildrenNeedStyleRecalc = false;
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);
someChildrenNeedStyleRecalc = someChildrenNeedStyleRecalc || childRecalced;
}
root->clearChildNeedsStyleInvalidation();
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);
someChildrenNeedStyleRecalc = someChildrenNeedStyleRecalc || childRecalced;
}
......
......@@ -38,17 +38,23 @@ private:
RecursionData()
: m_invalidateCustomPseudo(false)
, m_wholeSubtreeInvalid(false)
, m_treeBoundaryCrossing(false)
{ }
void pushInvalidationSet(const DescendantInvalidationSet&);
bool matchesCurrentInvalidationSets(Element&);
bool hasInvalidationSets() const { return m_invalidationSets.size(); }
bool wholeSubtreeInvalid() const { return m_wholeSubtreeInvalid; }
void setWholeSubtreeInvalid() { m_wholeSubtreeInvalid = true; }
bool treeBoundaryCrossing() const { return m_treeBoundaryCrossing; }
typedef Vector<const DescendantInvalidationSet*, 16> InvalidationSets;
InvalidationSets m_invalidationSets;
bool m_invalidateCustomPseudo;
bool m_wholeSubtreeInvalid;
bool m_treeBoundaryCrossing;
};
class RecursionCheckpoint {
......@@ -57,6 +63,7 @@ private:
: m_prevInvalidationSetsSize(data->m_invalidationSets.size())
, m_prevInvalidateCustomPseudo(data->m_invalidateCustomPseudo)
, m_prevWholeSubtreeInvalid(data->m_wholeSubtreeInvalid)
, m_treeBoundaryCrossing(data->m_treeBoundaryCrossing)
, m_data(data)
{ }
~RecursionCheckpoint()
......@@ -64,12 +71,14 @@ private:
m_data->m_invalidationSets.remove(m_prevInvalidationSetsSize, m_data->m_invalidationSets.size() - m_prevInvalidationSetsSize);
m_data->m_invalidateCustomPseudo = m_prevInvalidateCustomPseudo;
m_data->m_wholeSubtreeInvalid = m_prevWholeSubtreeInvalid;
m_data->m_treeBoundaryCrossing = m_treeBoundaryCrossing;
}
private:
int m_prevInvalidationSetsSize;
bool m_prevInvalidateCustomPseudo;
bool m_prevWholeSubtreeInvalid;
bool m_treeBoundaryCrossing;
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