Handle ::content and :host-context correctly in SelectorQuery.
SelectorQuery was checking for selectors that cross tree scopes (::shadow and
/deep/) to decide if it should update distribution, but it should have been
checking for selectors that need distribution (::content and :host-context).
SelectorQuery's ::matches and ::closest modes were also missing the step to
update the distribution.
BUG=491641
Review URL: https://codereview.chromium.org/1153873004
git-svn-id: svn://svn.chromium.org/blink/trunk@195882 bbb929c8-8fbe-4397-9dbb-9b2b20218538
diff --git a/LayoutTests/fast/dom/shadow/update-distribution-for-matches-expected.txt b/LayoutTests/fast/dom/shadow/update-distribution-for-matches-expected.txt
deleted file mode 100644
index 5b6482b..0000000
--- a/LayoutTests/fast/dom/shadow/update-distribution-for-matches-expected.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-crbug.com/489911 blink crashy with shadow DOM
-If this test passes, no crash occurs and "PASS" is shown.
-PASS
diff --git a/LayoutTests/fast/dom/shadow/update-distribution-for-matches.html b/LayoutTests/fast/dom/shadow/update-distribution-for-matches.html
deleted file mode 100644
index de14acc..0000000
--- a/LayoutTests/fast/dom/shadow/update-distribution-for-matches.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!doctype html>
-<script>
-var proto = Object.create(HTMLElement.prototype);
-proto.createdCallback = function() {
- var root = this.createShadowRoot();
- document.querySelector('li').matches('::content *');
-};
-var MyList = document.registerElement("my-list", { prototype: proto });
-</script>
-<my-list>
- <li></li>
-</my-list>
-<div>crbug.com/489911 blink crashy with shadow DOM</div>
-<div>If this test passes, no crash occurs and "PASS" is shown.</div>
-<div id="result"></div>
-<script>
-if (window.testRunner)
- testRunner.dumpAsText();
-result.textContent = "PASS";
-</script>
diff --git a/LayoutTests/fast/selectors/query-update-distribution-expected.txt b/LayoutTests/fast/selectors/query-update-distribution-expected.txt
new file mode 100644
index 0000000..4189371
--- /dev/null
+++ b/LayoutTests/fast/selectors/query-update-distribution-expected.txt
@@ -0,0 +1,18 @@
+Should update distribution when needed for querySelector and related methods.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS aRoot.querySelector(':host-context(#c) #d') is d
+PASS toArray(aRoot.querySelectorAll(':host-context(#c) #d')) is [d]
+PASS hostRoot.querySelector('::content #a') is null
+PASS toArray(hostRoot.querySelectorAll('::content #a')) is []
+PASS a.matches('::content #a') is false
+PASS d.matches(':host-context(#host) #d') is true
+PASS d.matches(':host-context(#c) #d') is true
+PASS b.closest('::content #a') is null
+PASS e.closest(':host-context(#host) #d') is d
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/selectors/query-update-distribution.html b/LayoutTests/fast/selectors/query-update-distribution.html
new file mode 100644
index 0000000..a05684d
--- /dev/null
+++ b/LayoutTests/fast/selectors/query-update-distribution.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<script src="../../resources/js-test.js"></script>
+
+<div id="sandbox"></div>
+
+<script>
+description("Should update distribution when needed for querySelector and related methods.");
+
+function test(fn)
+{
+ var sandbox = document.getElementById("sandbox");
+
+ sandbox.innerHTML = "<div id=host><div id=a><div id=b></div></div>";
+ host = document.getElementById("host");
+ hostRoot = host.createShadowRoot();
+ hostRoot.innerHTML = "<div id=c><content></content></div>";
+
+ a = document.getElementById("a");
+ b = document.getElementById("b");
+
+ aRoot = a.createShadowRoot();
+ aRoot.innerHTML = "<div id=d><div id=e></div></div>";
+
+ c = hostRoot.getElementById("c");
+ d = aRoot.getElementById("d");
+ e = aRoot.getElementById("e");
+
+ sandbox.appendChild(host);
+
+ fn();
+
+ sandbox.innerHTML = "";
+}
+
+function toArray(list)
+{
+ return Array.prototype.slice.call(list);
+}
+
+test(function() {
+ shouldBe("aRoot.querySelector(':host-context(#c) #d')", "d");
+});
+test(function() {
+ shouldBe("toArray(aRoot.querySelectorAll(':host-context(#c) #d'))", "[d]");
+});
+test(function() {
+ shouldBeNull("hostRoot.querySelector('::content #a')");
+});
+test(function() {
+ shouldBe("toArray(hostRoot.querySelectorAll('::content #a'))", "[]");
+});
+test(function() {
+ shouldBeFalse("a.matches('::content #a')");
+});
+test(function() {
+ shouldBeTrue("d.matches(':host-context(#host) #d')");
+});
+test(function() {
+ shouldBeTrue("d.matches(':host-context(#c) #d')");
+});
+test(function() {
+ shouldBeNull("b.closest('::content #a')");
+});
+test(function() {
+ shouldBe("e.closest(':host-context(#host) #d')", "d");
+});
+</script>
diff --git a/Source/core/css/CSSSelectorList.cpp b/Source/core/css/CSSSelectorList.cpp
index a2fc50a..95a6905 100644
--- a/Source/core/css/CSSSelectorList.cpp
+++ b/Source/core/css/CSSSelectorList.cpp
@@ -195,4 +195,18 @@
return forEachTagSelector(functor, selectorAt(index));
}
+class SelectorNeedsUpdatedDistribution {
+public:
+ bool operator()(const CSSSelector& selector)
+ {
+ return selector.relationIsAffectedByPseudoContent() || selector.pseudoType() == CSSSelector::PseudoHostContext;
+ }
+};
+
+bool CSSSelectorList::selectorNeedsUpdatedDistribution(size_t index) const
+{
+ SelectorNeedsUpdatedDistribution functor;
+ return forEachTagSelector(functor, selectorAt(index));
+}
+
} // namespace blink
diff --git a/Source/core/css/CSSSelectorList.h b/Source/core/css/CSSSelectorList.h
index 0f6b401..27b203f 100644
--- a/Source/core/css/CSSSelectorList.h
+++ b/Source/core/css/CSSSelectorList.h
@@ -61,6 +61,9 @@
bool selectorsNeedNamespaceResolution();
+ bool selectorNeedsUpdatedDistribution(size_t index) const;
+
+ // TODO(esprehn): These methods are confusing and incorrectly named.
bool hasShadowDistributedAt(size_t index) const;
bool selectorCrossesTreeScopes(size_t index) const;
diff --git a/Source/core/dom/Element.cpp b/Source/core/dom/Element.cpp
index dc60ade..5de0390 100644
--- a/Source/core/dom/Element.cpp
+++ b/Source/core/dom/Element.cpp
@@ -2783,7 +2783,6 @@
bool Element::matches(const String& selectors, ExceptionState& exceptionState)
{
- updateDistribution();
SelectorQuery* selectorQuery = document().selectorQueryCache().add(AtomicString(selectors), document(), exceptionState);
if (!selectorQuery)
return false;
diff --git a/Source/core/dom/SelectorQuery.cpp b/Source/core/dom/SelectorQuery.cpp
index 15dcc96..4b0b089 100644
--- a/Source/core/dom/SelectorQuery.cpp
+++ b/Source/core/dom/SelectorQuery.cpp
@@ -107,11 +107,13 @@
selectorCount++;
m_crossesTreeBoundary = false;
+ m_needsUpdatedDistribution = false;
m_selectors.reserveInitialCapacity(selectorCount);
unsigned index = 0;
for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(*selector), ++index) {
m_selectors.uncheckedAppend(selector);
m_crossesTreeBoundary |= selectorList.selectorCrossesTreeScopes(index);
+ m_needsUpdatedDistribution |= selectorList.selectorNeedsUpdatedDistribution(index);
}
}
@@ -128,6 +130,9 @@
bool SelectorDataList::matches(Element& targetElement) const
{
+ if (m_needsUpdatedDistribution)
+ targetElement.updateDistribution();
+
unsigned selectorCount = m_selectors.size();
for (unsigned i = 0; i < selectorCount; ++i) {
if (selectorMatches(*m_selectors[i], targetElement, targetElement))
@@ -139,6 +144,9 @@
Element* SelectorDataList::closest(Element& targetElement) const
{
+ if (m_needsUpdatedDistribution)
+ targetElement.updateDistribution();
+
unsigned selectorCount = m_selectors.size();
for (Element* currentElement = &targetElement; currentElement; currentElement = currentElement->parentElement()) {
for (unsigned i = 0; i < selectorCount; ++i) {
@@ -207,7 +215,15 @@
inline bool SelectorDataList::canUseFastQuery(const ContainerNode& rootNode) const
{
- return m_selectors.size() == 1 && !m_crossesTreeBoundary && rootNode.inDocument() && !rootNode.document().inQuirksMode();
+ if (m_crossesTreeBoundary)
+ return false;
+ if (m_needsUpdatedDistribution)
+ return false;
+ if (rootNode.document().inQuirksMode())
+ return false;
+ if (!rootNode.inDocument())
+ return false;
+ return m_selectors.size() == 1;
}
inline bool ancestorHasClassName(ContainerNode& rootNode, const AtomicString& className)
@@ -439,8 +455,9 @@
void SelectorDataList::execute(ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
{
if (!canUseFastQuery(rootNode)) {
- if (m_crossesTreeBoundary) {
+ if (m_needsUpdatedDistribution)
rootNode.updateDistribution();
+ if (m_crossesTreeBoundary) {
executeSlowTraversingShadowTree<SelectorQueryTrait>(rootNode, output);
} else {
executeSlow<SelectorQueryTrait>(rootNode, output);
diff --git a/Source/core/dom/SelectorQuery.h b/Source/core/dom/SelectorQuery.h
index b0f99f1..e3abe92 100644
--- a/Source/core/dom/SelectorQuery.h
+++ b/Source/core/dom/SelectorQuery.h
@@ -80,7 +80,8 @@
const CSSSelector* selectorForIdLookup(const CSSSelector&) const;
Vector<const CSSSelector*> m_selectors;
- bool m_crossesTreeBoundary;
+ bool m_crossesTreeBoundary : 1;
+ bool m_needsUpdatedDistribution : 1;
};
class SelectorQuery {