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 {