Implement proposed shadow tree cascade order.

This CL implements the shadow tree cascade order proposed in [1].

Previously, in Blink, specificity would win over scope origin, even if the
scopes had an inner/outer scope relationship, and the order-of-appearance
was governed by the CascadeOrder type. Also, !important rules did not
apply in the reverse scope order, as the current spec says for inner/outer
scopes, and the proposal in [1] says apply between all shadow scopes.

What has been done is:

1. CascadeOrder is not used, as it represents order-of-appearance
   (Removal of CascadeOrder is not done here to make the CL smaller. Will
   be removed in a follow-up CL).

2. When collecting rules, sort after each scope instead of just after UA
   and Author since we had:

   UA important
   Author important
   Author
   UA

   and now we have (with A(n) appearing before A(n+1) in the tree-of-trees
   order):

   UA important
   Author scope A(n) important
   ...
   Author scope A(1) important
   Author scope A(1)
   ...
   Author scope A(n)
   UA

   The applyProperties code is hot, and I have made performance runs for
   the micro-benchmarks in Layout and CSS without consistent regressions.

3. Since the cascading order between scopes are just the inner/outer
   relationship in the composed tree (direction decided by !important),
   which is the same as the tree-of-trees order of the shadow trees,
   we can just traverse the DocumentOrderedList of scopes in the reverse
   order instead of doing calculation tricks for CascadeOrder values.

   Because of this, TreeBoundaryCrossingRules is now reduced to a
   DocumentOrderedList of scoping nodes, so the TreeBoundaryCrossingRules
   class is removed.

[1] https://lists.w3.org/Archives/Public/www-style/2015Jun/0303.html

BUG=452542, 455148, 487125

NOTRY=true

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

git-svn-id: svn://svn.chromium.org/blink/trunk@200994 bbb929c8-8fbe-4397-9dbb-9b2b20218538
diff --git a/LayoutTests/fast/css/content-distributed-nodes-expected.txt b/LayoutTests/fast/css/content-distributed-nodes-expected.txt
index 7ee08e3..ca637a0 100644
--- a/LayoutTests/fast/css/content-distributed-nodes-expected.txt
+++ b/LayoutTests/fast/css/content-distributed-nodes-expected.txt
@@ -3,9 +3,9 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-PASS getComputedStyle(span).color is "rgb(255, 0, 0)"
+PASS getComputedStyle(span).color is "rgb(0, 128, 0)"
 PASS getComputedStyle(span).backgroundColor is "rgb(0, 128, 0)"
 PASS successfullyParsed is true
 
 TEST COMPLETE
-red?
+There should be no red
diff --git a/LayoutTests/fast/css/content-distributed-nodes.html b/LayoutTests/fast/css/content-distributed-nodes.html
index 5f9f239..0e5f7cc 100644
--- a/LayoutTests/fast/css/content-distributed-nodes.html
+++ b/LayoutTests/fast/css/content-distributed-nodes.html
@@ -2,7 +2,7 @@
 <script src="../../resources/js-test.js"></script>
 <div id="host">
   <div>
-    <span class="red" id="span">red?</span>
+    <span class="red" id="span">There should be no red</span>
   </div>
 </div>
 <script>
@@ -12,6 +12,6 @@
 var root2 = root.firstChild.createShadowRoot();
 root2.innerHTML = '<style>::content .red { background-color: green; color: red; }</style><content></content>';
 var span = document.querySelector('#span');
-shouldBeEqualToString('getComputedStyle(span).color', 'rgb(255, 0, 0)');
+shouldBeEqualToString('getComputedStyle(span).color', 'rgb(0, 128, 0)');
 shouldBeEqualToString('getComputedStyle(span).backgroundColor', 'rgb(0, 128, 0)');
 </script>
diff --git a/LayoutTests/fast/css/deep-cascade-order-expected.txt b/LayoutTests/fast/css/deep-cascade-order-expected.txt
new file mode 100644
index 0000000..8863659
--- /dev/null
+++ b/LayoutTests/fast/css/deep-cascade-order-expected.txt
@@ -0,0 +1,16 @@
+CONSOLE WARNING: /deep/ combinator is deprecated. See https://www.chromestatus.com/features/6750456638341120 for more details.
+Cascade order for inner/outer tree rules with /deep/.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS getComputedStyle(root1.querySelector('div')).color is green
+TODO(rune@opera.com): Currently fails because style attributes are added to the end, not in its element's scope's cascade order
+FAIL getComputedStyle(root1.querySelector('div + div')).color should be rgb(0, 128, 0). Was rgb(255, 0, 0).
+PASS getComputedStyle(root2.querySelector('div')).color is green
+TODO(rune@opera.com): Currently fails because style attributes are added to the end, not in its element's scope's cascade order
+FAIL getComputedStyle(root2.querySelector('div + div')).color should be rgb(0, 128, 0). Was rgb(255, 0, 0).
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/css/deep-cascade-order.html b/LayoutTests/fast/css/deep-cascade-order.html
new file mode 100644
index 0000000..6f6e774
--- /dev/null
+++ b/LayoutTests/fast/css/deep-cascade-order.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script src="../../resources/js-test.js"></script>
+<style>
+    .host1 /deep/ div { color: green }
+    #host2 /deep/ div { color: red !important }
+</style>
+<div id="host1" class="host1"></div>
+<div id="host2" class="host2"></div>
+<script>
+    description("Cascade order for inner/outer tree rules with /deep/.");
+
+    var root1 = host1.createShadowRoot();
+    root1.innerHTML = '<style>#d1 {color:red}</style><div id="d1">Should be green</div><div style="color: red">Should be green</div>';
+
+    var root2 = host2.createShadowRoot();
+    root2.innerHTML = '<style>.d1 {color:green !important}</style><div class="d1">Should be green</div><div style="color: green !important">Should be green</div>';
+
+    var green = "rgb(0, 128, 0)";
+
+    shouldBe("getComputedStyle(root1.querySelector('div')).color", "green");
+
+    debug("TODO(rune@opera.com): Currently fails because style attributes are added to the end, not in its element's scope's cascade order");
+    shouldBe("getComputedStyle(root1.querySelector('div + div')).color", "green");
+
+    shouldBe("getComputedStyle(root2.querySelector('div')).color", "green");
+
+    debug("TODO(rune@opera.com): Currently fails because style attributes are added to the end, not in its element's scope's cascade order");
+    shouldBe("getComputedStyle(root2.querySelector('div + div')).color", "green");
+</script>
diff --git a/LayoutTests/fast/css/getComputedStyle/computed-style-redistribution.html b/LayoutTests/fast/css/getComputedStyle/computed-style-redistribution.html
index 8bfb3e0..f030356 100644
--- a/LayoutTests/fast/css/getComputedStyle/computed-style-redistribution.html
+++ b/LayoutTests/fast/css/getComputedStyle/computed-style-redistribution.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <script src="../../../resources/js-test.js"></script>
 <style>
-.d1, .d2 { color: red }
+custom-element { color: red }
 </style>
 <custom-element>
     <div class="d1">A</div>
diff --git a/LayoutTests/fast/dom/shadow/cascade-of-treeboundary-crossing-rules-expected.txt b/LayoutTests/fast/dom/shadow/cascade-of-treeboundary-crossing-rules-expected.txt
index c32633a..37b65d8 100644
--- a/LayoutTests/fast/dom/shadow/cascade-of-treeboundary-crossing-rules-expected.txt
+++ b/LayoutTests/fast/dom/shadow/cascade-of-treeboundary-crossing-rules-expected.txt
@@ -7,6 +7,7 @@
 PASS borderColorOf(getNodeInTreeOfTrees("target")) is "rgb(0, 128, 0)"
 PASS borderColorOf(getNodeInTreeOfTrees("host/host2/target")) is "rgb(0, 128, 0)"
 PASS borderColorOf(getNodeInTreeOfTrees("host/target")) is "rgb(0, 128, 0)"
+PASS borderColorOf(getNodeInTreeOfTrees("host/target")) is "rgb(0, 128, 0)"
 PASS successfullyParsed is true
 
 TEST COMPLETE
diff --git a/LayoutTests/fast/dom/shadow/cascade-of-treeboundary-crossing-rules.html b/LayoutTests/fast/dom/shadow/cascade-of-treeboundary-crossing-rules.html
index 5d7fadd..8cbd70c 100644
--- a/LayoutTests/fast/dom/shadow/cascade-of-treeboundary-crossing-rules.html
+++ b/LayoutTests/fast/dom/shadow/cascade-of-treeboundary-crossing-rules.html
@@ -36,18 +36,18 @@
 
 description('Test for casacde of treeboundary crossing rules.');
 
-// Rules declared in inner treescope should win.
+// Rules declared in outer treescope should win.
 sandbox.appendChild(
     createDOM('div', {'id': 'host'},
         createDOM('style', {},
-            document.createTextNode('p:empty { border: 1px solid blue; }')),
+            document.createTextNode('p:empty { border: 1px solid green; }')),
         createShadowRoot(
             createDOM('style', {},
                 document.createTextNode('::content > p { border: 1px solid red; }')),
             createDOM('div', {},
                 createShadowRoot(
                     createDOM('style', {},
-                        document.createTextNode('::content > p { border: 1px solid green; }')),
+                        document.createTextNode('::content > p { border: 1px solid blue; }')),
                     createDOM('content', {})),
                 createDOM('content', {}))),
         createDOM('p', {'id': 'target'})));
@@ -103,6 +103,22 @@
 
 cleanUp();
 
+// Comparing important rules declared in outer treescope with important rules declared in inner treescope.
+// Inner's should win.
+sandbox.appendChild(
+    createDOM('div', {},
+        createDOM('style', {},
+            document.createTextNode('div { border: 1px solid red !important; }')),
+        createDOM('div', {'id': 'host'},
+            createShadowRoot(
+                createDOM('style', {},
+                    document.createTextNode('#target { border: 1px solid green !important; }')),
+                createDOM('p', {'id': 'target'})))));
+
+borderColorShouldBe('host/target', 'rgb(0, 128, 0)');
+
+cleanUp();
+
 </script>
 </html>
 
diff --git a/LayoutTests/fast/dom/shadow/inner-scope-important-wins-expected.txt b/LayoutTests/fast/dom/shadow/inner-scope-important-wins-expected.txt
new file mode 100644
index 0000000..e89ccb8
--- /dev/null
+++ b/LayoutTests/fast/dom/shadow/inner-scope-important-wins-expected.txt
@@ -0,0 +1,10 @@
+Inner scope !important rules wins, even with lower specificity.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS getComputedStyle(target).color is "rgb(0, 128, 0)"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/dom/shadow/inner-scope-important-wins.html b/LayoutTests/fast/dom/shadow/inner-scope-important-wins.html
new file mode 100644
index 0000000..a4e004f
--- /dev/null
+++ b/LayoutTests/fast/dom/shadow/inner-scope-important-wins.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="../../../resources/js-test.js"></script>
+<script src="resources/shadow-dom.js"></script>
+<div id="sandbox"></div>
+<script>
+description("Inner scope !important rules wins, even with lower specificity.");
+
+sandbox.appendChild(
+    createDOM('div', {'id': 'host'},
+        createShadowRoot(
+            createDOM('style', {},
+                document.createTextNode(
+                    '::content span { color: green !important; }')),
+            createDOM('content', {})),
+        createDOM('style', {},
+            document.createTextNode(
+                '#target { color: red !important; }')),
+        createDOM('span', {'id': 'target'})));
+
+shouldBeEqualToString("getComputedStyle(target).color", "rgb(0, 128, 0)");
+</script>
diff --git a/LayoutTests/fast/dom/shadow/outer-scope-lower-specificity-wins-expected.txt b/LayoutTests/fast/dom/shadow/outer-scope-lower-specificity-wins-expected.txt
new file mode 100644
index 0000000..c67c1e6
--- /dev/null
+++ b/LayoutTests/fast/dom/shadow/outer-scope-lower-specificity-wins-expected.txt
@@ -0,0 +1,10 @@
+Outer scope rules wins, even with lower specificity.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS getComputedStyle(target).color is "rgb(0, 128, 0)"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/dom/shadow/outer-scope-lower-specificity-wins.html b/LayoutTests/fast/dom/shadow/outer-scope-lower-specificity-wins.html
new file mode 100644
index 0000000..ab14595
--- /dev/null
+++ b/LayoutTests/fast/dom/shadow/outer-scope-lower-specificity-wins.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="../../../resources/js-test.js"></script>
+<script src="resources/shadow-dom.js"></script>
+<div id="sandbox"></div>
+<script>
+description("Outer scope rules wins, even with lower specificity.");
+
+sandbox.appendChild(
+    createDOM('div', {'id': 'host'},
+        createShadowRoot(
+            createDOM('style', {},
+                document.createTextNode(
+                    '::content #target { color: red; }')),
+            createDOM('content', {})),
+        createDOM('style', {},
+            document.createTextNode(
+                'span { color: green; }')),
+        createDOM('span', {'id': 'target'})));
+
+shouldBeEqualToString("getComputedStyle(target).color", "rgb(0, 128, 0)");
+</script>
diff --git a/LayoutTests/fast/dom/shadow/outer-scope-wins-expected.txt b/LayoutTests/fast/dom/shadow/outer-scope-wins-expected.txt
new file mode 100644
index 0000000..cc3680d
--- /dev/null
+++ b/LayoutTests/fast/dom/shadow/outer-scope-wins-expected.txt
@@ -0,0 +1,2 @@
+This text should be green.
+PASS
diff --git a/LayoutTests/fast/dom/shadow/outer-scope-wins.html b/LayoutTests/fast/dom/shadow/outer-scope-wins.html
new file mode 100644
index 0000000..011ed28
--- /dev/null
+++ b/LayoutTests/fast/dom/shadow/outer-scope-wins.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>.t { color: green }</style>
+<div id="host">
+    <span id="t" class="t">This text should be green.</span>
+</div>
+<script>
+if (window.testRunner)
+    testRunner.dumpAsText();
+
+var root = host.createShadowRoot();
+root.innerHTML = '<style>::content > * { color: red }</style><content></content>';
+document.write(getComputedStyle(t).color == "rgb(0, 128, 0)" ? "PASS" : "FAIL");
+</script>
diff --git a/LayoutTests/fast/dom/shadow/style-with-shadow-pseudo-element-expected.txt b/LayoutTests/fast/dom/shadow/style-with-shadow-pseudo-element-expected.txt
index 9b01aa6..dff8879 100644
--- a/LayoutTests/fast/dom/shadow/style-with-shadow-pseudo-element-expected.txt
+++ b/LayoutTests/fast/dom/shadow/style-with-shadow-pseudo-element-expected.txt
@@ -15,7 +15,7 @@
 PASS borderColorOf(getNodeInTreeOfTrees("host/host1/host2/host3/span4")) is not "rgb(0, 128, 0)"
 PASS borderColorOf(getNodeInTreeOfTrees("host/target")) is "rgb(0, 128, 0)"
 PASS borderColorOf(getNodeInTreeOfTrees("host/target")) is "rgb(0, 128, 0)"
-PASS borderColorOf(getNodeInTreeOfTrees("host/target")) is "rgb(255, 0, 0)"
+PASS borderColorOf(getNodeInTreeOfTrees("host/target")) is "rgb(0, 128, 0)"
 PASS borderColorOf(getNodeInTreeOfTrees("host/target")) is "rgb(0, 128, 0)"
 PASS borderColorOf(getNodeInTreeOfTrees("host/target")) is "rgb(0, 128, 0)"
 PASS borderColorOf(getNodeInTreeOfTrees("host/target")) is "rgb(0, 128, 0)"
diff --git a/LayoutTests/fast/dom/shadow/style-with-shadow-pseudo-element.html b/LayoutTests/fast/dom/shadow/style-with-shadow-pseudo-element.html
index c1b2b3f..72a703f 100644
--- a/LayoutTests/fast/dom/shadow/style-with-shadow-pseudo-element.html
+++ b/LayoutTests/fast/dom/shadow/style-with-shadow-pseudo-element.html
@@ -88,7 +88,7 @@
         createDOM('div', {'id': 'host'},
             createShadowRoot(
                 createDOM('span', {'id': 'target'},
-                    document.createTextNode('green border, because of hat.'))))));
+                    document.createTextNode('green border, because of ::shadow.'))))));
 
 borderColorShouldBe('host/target', 'rgb(0, 128, 0)');
 
@@ -104,7 +104,7 @@
                 createDOM('style', {},
                     document.createTextNode('span { border: 1px solid red; }')),
                 createDOM('span', {'id': 'target'},
-                    document.createTextNode('green border, because of hat.'))))));
+                    document.createTextNode('green border, because of ::shadow.'))))));
 
 borderColorShouldBe('host/target', 'rgb(0, 128, 0)');
 
@@ -119,10 +119,9 @@
                 createDOM('style', {},
                     document.createTextNode('span#target { border: 1px solid red; }')),
                 createDOM('span', {'id': 'target'},
-                    document.createTextNode('green border, because of hat.'))))));
+                    document.createTextNode('green border, because of ::shadow.'))))));
 
-// Need to clarify the spec, i.e. using specificity? Currently rgb(255,0,0).
-borderColorShouldBe('host/target', 'rgb(255, 0, 0)');
+borderColorShouldBe('host/target', 'rgb(0, 128, 0)');
 
 cleanUp();
 
@@ -133,7 +132,7 @@
         createDOM('div', {'id': 'host'},
             createShadowRoot(
                 createDOM('span', {'id': 'target'},
-                    document.createTextNode('green border, because of hat.'))))));
+                    document.createTextNode('green border, because of ::shadow.'))))));
 
 borderColorShouldBe('host/target', 'rgb(0, 128, 0)');
 
@@ -149,7 +148,7 @@
                     document.createTextNode('div > span { border: 1px solid red; }')),
                 createDOM('div', {},
                     createDOM('span', {'id': 'target'},
-                        document.createTextNode('green border, because parent hat wins.')))))));
+                        document.createTextNode('green border, because parent ::shadow wins.')))))));
 
 borderColorShouldBe('host/target', 'rgb(0, 128, 0)');
 
diff --git a/Source/core/core.gypi b/Source/core/core.gypi
index b68feb2..477998a 100644
--- a/Source/core/core.gypi
+++ b/Source/core/core.gypi
@@ -1202,8 +1202,6 @@
             'css/StyleSheetContents.h',
             'css/StyleSheetList.cpp',
             'css/StyleSheetList.h',
-            'css/TreeBoundaryCrossingRules.cpp',
-            'css/TreeBoundaryCrossingRules.h',
             'css/invalidation/DescendantInvalidationSet.cpp',
             'css/invalidation/DescendantInvalidationSet.h',
             'css/invalidation/StyleInvalidator.cpp',
diff --git a/Source/core/css/TreeBoundaryCrossingRules.cpp b/Source/core/css/TreeBoundaryCrossingRules.cpp
deleted file mode 100644
index ea986b0..0000000
--- a/Source/core/css/TreeBoundaryCrossingRules.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
- *           (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
- * Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com)
- * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved.
- * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
- * Copyright (C) 2007, 2008 Eric Seidel <eric@webkit.org>
- * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
- * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
- * Copyright (C) Research In Motion Limited 2011. All rights reserved.
- * Copyright (C) 2013 Google Inc. All rights reserved.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB.  If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include "config.h"
-#include "core/css/TreeBoundaryCrossingRules.h"
-
-#include "core/css/ElementRuleCollector.h"
-#include "core/css/StylePropertySet.h"
-#include "core/css/resolver/ScopedStyleResolver.h"
-
-namespace blink {
-
-void TreeBoundaryCrossingRules::collectTreeBoundaryCrossingRules(Element* element, ElementRuleCollector& collector, bool includeEmptyRules)
-{
-    if (m_scopingNodes.isEmpty())
-        return;
-
-    // When comparing rules declared in outer treescopes, outer's rules win.
-    CascadeOrder outerCascadeOrder = size() + size();
-    // When comparing rules declared in inner treescopes, inner's rules win.
-    CascadeOrder innerCascadeOrder = size();
-
-    ASSERT(!collector.scopeContainsLastMatchedElement());
-    collector.setScopeContainsLastMatchedElement(true);
-
-    for (const auto& scope : m_scopingNodes) {
-
-        bool isInnerTreeScope = element->treeScope().isInclusiveAncestorOf(scope->treeScope());
-        CascadeOrder cascadeOrder = isInnerTreeScope ? innerCascadeOrder : outerCascadeOrder;
-
-        scope->treeScope().scopedStyleResolver()->collectMatchingTreeBoundaryCrossingRules(collector, includeEmptyRules, cascadeOrder);
-
-        ++innerCascadeOrder;
-        --outerCascadeOrder;
-    }
-
-    collector.setScopeContainsLastMatchedElement(false);
-}
-
-void TreeBoundaryCrossingRules::addScope(ContainerNode& scopingNode)
-{
-    m_scopingNodes.add(&scopingNode);
-}
-
-void TreeBoundaryCrossingRules::removeScope(const ContainerNode& scopingNode)
-{
-    m_scopingNodes.remove(&scopingNode);
-}
-
-DEFINE_TRACE(TreeBoundaryCrossingRules)
-{
-#if ENABLE(OILPAN)
-    visitor->trace(m_scopingNodes);
-#endif
-}
-
-} // namespace blink
diff --git a/Source/core/css/TreeBoundaryCrossingRules.h b/Source/core/css/TreeBoundaryCrossingRules.h
deleted file mode 100644
index 70cf65e..0000000
--- a/Source/core/css/TreeBoundaryCrossingRules.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
- * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
- * Copyright (C) 2013 Google Inc. All rights reserved.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB.  If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- *
- */
-
-#ifndef TreeBoundaryCrossingRules_h
-#define TreeBoundaryCrossingRules_h
-
-#include "core/dom/DocumentOrderedList.h"
-
-#include "wtf/OwnPtr.h"
-#include "wtf/RefPtr.h"
-#include "wtf/Vector.h"
-
-namespace blink {
-
-class ContainerNode;
-class Element;
-class ElementRuleCollector;
-
-class TreeBoundaryCrossingRules final {
-    DISALLOW_ALLOCATION();
-public:
-    void addScope(ContainerNode&);
-    void removeScope(const ContainerNode&);
-    void collectTreeBoundaryCrossingRules(Element*, ElementRuleCollector&, bool includeEmptyRules);
-
-    DECLARE_TRACE();
-
-private:
-    size_t size() const { return m_scopingNodes.size(); }
-
-    DocumentOrderedList m_scopingNodes;
-};
-
-} // namespace blink
-
-#endif // TreeBoundaryCrossingRules_h
diff --git a/Source/core/css/resolver/ScopedStyleResolver.cpp b/Source/core/css/resolver/ScopedStyleResolver.cpp
index fc915a2..6fe4547 100644
--- a/Source/core/css/resolver/ScopedStyleResolver.cpp
+++ b/Source/core/css/resolver/ScopedStyleResolver.cpp
@@ -169,10 +169,18 @@
 
 void ScopedStyleResolver::collectMatchingTreeBoundaryCrossingRules(ElementRuleCollector& collector, bool includeEmptyRules, CascadeOrder cascadeOrder)
 {
+    if (!m_treeBoundaryCrossingRuleSet)
+        return;
+
+    ASSERT(!collector.scopeContainsLastMatchedElement());
+    collector.setScopeContainsLastMatchedElement(true);
+
     for (const auto& rules : *m_treeBoundaryCrossingRuleSet) {
         MatchRequest request(rules->m_ruleSet.get(), includeEmptyRules, &treeScope().rootNode(), rules->m_parentStyleSheet, rules->m_parentIndex);
         collector.collectMatchingRules(request, cascadeOrder, true);
     }
+
+    collector.setScopeContainsLastMatchedElement(false);
 }
 
 void ScopedStyleResolver::matchPageRules(PageRuleCollector& collector)
diff --git a/Source/core/css/resolver/ScopedStyleResolver.h b/Source/core/css/resolver/ScopedStyleResolver.h
index 72db049..79ae2e6 100644
--- a/Source/core/css/resolver/ScopedStyleResolver.h
+++ b/Source/core/css/resolver/ScopedStyleResolver.h
@@ -59,7 +59,7 @@
     void appendCSSStyleSheet(CSSStyleSheet&, const MediaQueryEvaluator&);
     void collectMatchingAuthorRules(ElementRuleCollector&, bool includeEmptyRules, CascadeOrder = ignoreCascadeOrder);
     void collectMatchingShadowHostRules(ElementRuleCollector&, bool includeEmptyRules, CascadeOrder = ignoreCascadeOrder);
-    void collectMatchingTreeBoundaryCrossingRules(ElementRuleCollector&, bool includeEmptyRules, CascadeOrder);
+    void collectMatchingTreeBoundaryCrossingRules(ElementRuleCollector&, bool includeEmptyRules, CascadeOrder = ignoreCascadeOrder);
     void matchPageRules(PageRuleCollector&);
     void collectFeaturesTo(RuleFeatureSet&, HashSet<const StyleSheetContents*>& visitedSharedStyleSheetContents) const;
     void resetAuthorStyle();
diff --git a/Source/core/css/resolver/StyleResolver.cpp b/Source/core/css/resolver/StyleResolver.cpp
index 1a1bcdf..75a480c 100644
--- a/Source/core/css/resolver/StyleResolver.cpp
+++ b/Source/core/css/resolver/StyleResolver.cpp
@@ -123,21 +123,6 @@
     return rightToLeftDecl;
 }
 
-static void collectScopedResolversForHostedShadowTrees(const Element* element, WillBeHeapVector<RawPtrWillBeMember<ScopedStyleResolver>, 8>& resolvers)
-{
-    ElementShadow* shadow = element->shadow();
-    if (!shadow)
-        return;
-
-    // Adding scoped resolver for active shadow roots for shadow host styling.
-    for (ShadowRoot* shadowRoot = shadow->youngestShadowRoot(); shadowRoot; shadowRoot = shadowRoot->olderShadowRoot()) {
-        if (shadowRoot->numberOfStyles() > 0) {
-            if (ScopedStyleResolver* resolver = shadowRoot->scopedStyleResolver())
-                resolvers.append(resolver);
-        }
-    }
-}
-
 StyleResolver::StyleResolver(Document& document)
     : m_document(document)
     , m_viewportStyleResolver(ViewportStyleResolver::create(&document))
@@ -242,12 +227,12 @@
 
 void StyleResolver::addTreeBoundaryCrossingScope(ContainerNode& scope)
 {
-    m_treeBoundaryCrossingRules.addScope(scope);
+    m_treeBoundaryCrossingScopes.add(&scope);
 }
 
 void StyleResolver::resetAuthorStyle(TreeScope& treeScope)
 {
-    m_treeBoundaryCrossingRules.removeScope(treeScope.rootNode());
+    m_treeBoundaryCrossingScopes.remove(&treeScope.rootNode());
 
     ScopedStyleResolver* resolver = treeScope.scopedStyleResolver();
     if (!resolver)
@@ -348,11 +333,11 @@
 {
 }
 
-static inline ScopedStyleResolver* scopedResolverFor(const Element* element)
+static inline ScopedStyleResolver* scopedResolverFor(const Element& element)
 {
     // Ideally, returning element->treeScope().scopedStyleResolver() should be
     // enough, but ::cue and custom pseudo elements like ::-webkit-meter-bar pierce
-    // through a shadow dom boundary, yet they are not part of m_treeBoundaryCrossingRules.
+    // through a shadow dom boundary, yet they are not part of m_treeBoundaryCrossingScopes.
     // The assumption here is that these rules only pierce through one boundary and
     // that the scope of these elements do not have a style resolver due to the fact
     // that VTT scopes and UA shadow trees don't have <style> elements. This is
@@ -361,40 +346,94 @@
     // FIXME: Make ::cue and custom pseudo elements part of boundary crossing rules
     // when moving those rules to ScopedStyleResolver as part of issue 401359.
 
-    TreeScope* treeScope = &element->treeScope();
+    TreeScope* treeScope = &element.treeScope();
     if (ScopedStyleResolver* resolver = treeScope->scopedStyleResolver()) {
-        ASSERT(element->shadowPseudoId().isEmpty());
-        ASSERT(!element->isVTTElement());
+        ASSERT(element.shadowPseudoId().isEmpty());
+        ASSERT(!element.isVTTElement());
         return resolver;
     }
 
     treeScope = treeScope->parentTreeScope();
     if (!treeScope)
         return nullptr;
-    if (element->shadowPseudoId().isEmpty() && !element->isVTTElement())
+    if (element.shadowPseudoId().isEmpty() && !element.isVTTElement())
         return nullptr;
     return treeScope->scopedStyleResolver();
 }
 
-void StyleResolver::matchAuthorRules(Element* element, ElementRuleCollector& collector, bool includeEmptyRules)
+void StyleResolver::matchHostRules(const Element& element, ElementRuleCollector& collector, bool includeEmptyRules)
+{
+    ElementShadow* shadow = element.shadow();
+    if (!shadow)
+        return;
+
+    for (ShadowRoot* shadowRoot = shadow->oldestShadowRoot(); shadowRoot; shadowRoot = shadowRoot->youngerShadowRoot()) {
+        if (!shadowRoot->numberOfStyles())
+            continue;
+        if (ScopedStyleResolver* resolver = shadowRoot->scopedStyleResolver()) {
+            collector.clearMatchedRules();
+            resolver->collectMatchingShadowHostRules(collector, includeEmptyRules);
+            collector.sortAndTransferMatchedRules();
+            collector.finishAddingAuthorRulesForTreeScope();
+        }
+    }
+}
+
+void StyleResolver::matchElementScopeRules(ScopedStyleResolver& elementScopeResolver, ElementRuleCollector& collector, bool includeEmptyRules)
 {
     collector.clearMatchedRules();
-
-    CascadeOrder cascadeOrder = 0;
-    WillBeHeapVector<RawPtrWillBeMember<ScopedStyleResolver>, 8> resolversInShadowTree;
-    collectScopedResolversForHostedShadowTrees(element, resolversInShadowTree);
-
-    // Apply :host and :host-context rules from inner scopes.
-    for (int j = resolversInShadowTree.size() - 1; j >= 0; --j)
-        resolversInShadowTree.at(j)->collectMatchingShadowHostRules(collector, includeEmptyRules, ++cascadeOrder);
-
-    // Apply normal rules from element scope.
-    if (ScopedStyleResolver* resolver = scopedResolverFor(element))
-        resolver->collectMatchingAuthorRules(collector, includeEmptyRules, ++cascadeOrder);
-
-    // Apply /deep/ and ::shadow rules from outer scopes, and ::content from inner.
-    m_treeBoundaryCrossingRules.collectTreeBoundaryCrossingRules(element, collector, includeEmptyRules);
+    elementScopeResolver.collectMatchingAuthorRules(collector, includeEmptyRules);
+    elementScopeResolver.collectMatchingTreeBoundaryCrossingRules(collector, includeEmptyRules);
     collector.sortAndTransferMatchedRules();
+    collector.finishAddingAuthorRulesForTreeScope();
+}
+
+void StyleResolver::matchScopedRules(const Element& element, ElementRuleCollector& collector, bool includeEmptyRules)
+{
+    // Match rules from treeScopes in the reverse tree-of-trees order, since the
+    // cascading order for normal rules is such that when comparing rules from
+    // different shadow trees, the rule from the tree which comes first in the
+    // tree-of-trees order wins. From other treeScopes than the element's own
+    // scope, only tree-boundary-crossing rules may match.
+
+    ScopedStyleResolver* elementScopeResolver = scopedResolverFor(element);
+    bool matchElementScopeDone = !elementScopeResolver;
+
+    for (auto it = m_treeBoundaryCrossingScopes.rbegin(); it != m_treeBoundaryCrossingScopes.rend(); ++it) {
+        const TreeScope& scope = (*it)->treeScope();
+        ScopedStyleResolver* resolver = scope.scopedStyleResolver();
+        ASSERT(resolver);
+
+        if (!matchElementScopeDone && scope.isInclusiveAncestorOf(element.treeScope())) {
+
+            matchElementScopeDone = true;
+
+            // At this point, the iterator has either encountered the scope for the element
+            // itself (if that scope has boundary-crossing rules), or the iterator has moved
+            // to a scope which appears before the element's scope in the tree-of-trees order.
+            // Try to match all rules from the element's scope.
+
+            matchElementScopeRules(*elementScopeResolver, collector, includeEmptyRules);
+            if (resolver == elementScopeResolver) {
+                // Boundary-crossing rules already collected in matchElementScopeRules.
+                continue;
+            }
+        }
+
+        collector.clearMatchedRules();
+        resolver->collectMatchingTreeBoundaryCrossingRules(collector, includeEmptyRules);
+        collector.sortAndTransferMatchedRules();
+        collector.finishAddingAuthorRulesForTreeScope();
+    }
+
+    if (!matchElementScopeDone)
+        matchElementScopeRules(*elementScopeResolver, collector, includeEmptyRules);
+}
+
+void StyleResolver::matchAuthorRules(const Element& element, ElementRuleCollector& collector, bool includeEmptyRules)
+{
+    matchHostRules(element, collector, includeEmptyRules);
+    matchScopedRules(element, collector, includeEmptyRules);
 }
 
 void StyleResolver::matchUARules(ElementRuleCollector& collector)
@@ -447,9 +486,15 @@
         }
     }
 
-    matchAuthorRules(state.element(), collector, false);
+    matchAuthorRules(*state.element(), collector, false);
 
     if (state.element()->isStyledElement()) {
+        // TODO(rune@opera.com): Adding style attribute rules here is probably too late
+        // when you have shadow piercing combinators. When we don't have piercing combinators,
+        // the style attribute always belong to the outermost scope whose rules apply to
+        // the element. Thus, applying inline style here is correct. Fixing this for piercing
+        // combinators means moving the code below into matchElementScopeRules and _not_
+        // invoking it for pseudo style requests.
         if (state.element()->inlineStyle()) {
             // Inline style is immutable as long as there is no CSSOM wrapper.
             bool isInlineStyleCacheable = !state.element()->inlineStyle()->isMutable();
@@ -758,7 +803,7 @@
         collector.setPseudoStyleRequest(pseudoStyleRequest);
 
         matchUARules(collector);
-        matchAuthorRules(state.element(), collector, false);
+        matchAuthorRules(*state.element(), collector, false);
         collector.finishAddingAuthorRulesForTreeScope();
 
         if (!collector.matchedResult().hasMatchedProperties())
@@ -909,7 +954,7 @@
 
     if (rulesToInclude & AuthorCSSRules) {
         collector.setSameOriginOnly(!(rulesToInclude & CrossOriginCSSRules));
-        matchAuthorRules(element, collector, rulesToInclude & EmptyCSSRules);
+        matchAuthorRules(*element, collector, rulesToInclude & EmptyCSSRules);
     }
 }
 
@@ -952,10 +997,25 @@
     return true;
 }
 
+static void collectScopedResolversForHostedShadowTrees(const Element& element, WillBeHeapVector<RawPtrWillBeMember<ScopedStyleResolver>, 8>& resolvers)
+{
+    ElementShadow* shadow = element.shadow();
+    if (!shadow)
+        return;
+
+    // Adding scoped resolver for active shadow roots for shadow host styling.
+    for (ShadowRoot* shadowRoot = shadow->youngestShadowRoot(); shadowRoot; shadowRoot = shadowRoot->olderShadowRoot()) {
+        if (shadowRoot->numberOfStyles() > 0) {
+            if (ScopedStyleResolver* resolver = shadowRoot->scopedStyleResolver())
+                resolvers.append(resolver);
+        }
+    }
+}
+
 StyleRuleKeyframes* StyleResolver::findKeyframesRule(const Element* element, const AtomicString& animationName)
 {
     WillBeHeapVector<RawPtrWillBeMember<ScopedStyleResolver>, 8> resolvers;
-    collectScopedResolversForHostedShadowTrees(element, resolvers);
+    collectScopedResolversForHostedShadowTrees(*element, resolvers);
     if (ScopedStyleResolver* scopedResolver = element->treeScope().scopedStyleResolver())
         resolvers.append(scopedResolver);
 
@@ -1446,7 +1506,7 @@
     visitor->trace(m_siblingRuleSet);
     visitor->trace(m_uncommonAttributeRuleSet);
     visitor->trace(m_watchedSelectorsRules);
-    visitor->trace(m_treeBoundaryCrossingRules);
+    visitor->trace(m_treeBoundaryCrossingScopes);
     visitor->trace(m_styleResourceLoader);
     visitor->trace(m_styleSharingLists);
     visitor->trace(m_pendingStyleSheets);
diff --git a/Source/core/css/resolver/StyleResolver.h b/Source/core/css/resolver/StyleResolver.h
index 6872b6a..6cb1ba1 100644
--- a/Source/core/css/resolver/StyleResolver.h
+++ b/Source/core/css/resolver/StyleResolver.h
@@ -30,12 +30,12 @@
 #include "core/css/RuleSet.h"
 #include "core/css/SelectorChecker.h"
 #include "core/css/SelectorFilter.h"
-#include "core/css/TreeBoundaryCrossingRules.h"
 #include "core/css/resolver/CSSPropertyPriority.h"
 #include "core/css/resolver/MatchedPropertiesCache.h"
 #include "core/css/resolver/StyleBuilder.h"
 #include "core/css/resolver/StyleResolverStats.h"
 #include "core/css/resolver/StyleResourceLoader.h"
+#include "core/dom/DocumentOrderedList.h"
 #include "core/style/AuthorStyleInfo.h"
 #include "core/style/CachedUAStyle.h"
 #include "platform/heap/Handle.h"
@@ -201,7 +201,10 @@
     void collectPseudoRulesForElement(Element*, ElementRuleCollector&, PseudoId, unsigned rulesToInclude);
     void matchRuleSet(ElementRuleCollector&, RuleSet*);
     void matchUARules(ElementRuleCollector&);
-    void matchAuthorRules(Element*, ElementRuleCollector&, bool includeEmptyRules);
+    void matchAuthorRules(const Element&, ElementRuleCollector&, bool includeEmptyRules);
+    void matchHostRules(const Element&, ElementRuleCollector&, bool includeEmptyRules);
+    void matchElementScopeRules(ScopedStyleResolver&, ElementRuleCollector&, bool includeEmptyRules);
+    void matchScopedRules(const Element&, ElementRuleCollector&, bool includeEmptyRules);
     void matchAllRules(StyleResolverState&, ElementRuleCollector&, bool includeSMILProperties);
     void collectFeatures();
     void resetRuleFeatures();
@@ -245,7 +248,7 @@
     OwnPtrWillBeMember<RuleSet> m_siblingRuleSet;
     OwnPtrWillBeMember<RuleSet> m_uncommonAttributeRuleSet;
     OwnPtrWillBeMember<RuleSet> m_watchedSelectorsRules;
-    TreeBoundaryCrossingRules m_treeBoundaryCrossingRules;
+    DocumentOrderedList m_treeBoundaryCrossingScopes;
 
     bool m_needCollectFeatures;
     bool m_printMediaType;
diff --git a/Source/core/dom/DocumentOrderedList.h b/Source/core/dom/DocumentOrderedList.h
index 7cc6d0f..0acb91b 100644
--- a/Source/core/dom/DocumentOrderedList.h
+++ b/Source/core/dom/DocumentOrderedList.h
@@ -50,10 +50,14 @@
     size_t size() const { return m_nodes.size(); }
 
     using iterator = WillBeHeapListHashSet<RawPtrWillBeMember<Node>, 32>::iterator;
+    using const_reverse_iterator = WillBeHeapListHashSet<RawPtrWillBeMember<Node>, 32>::const_reverse_iterator;
 
     iterator begin() { return m_nodes.begin(); }
     iterator end() { return m_nodes.end(); }
 
+    const_reverse_iterator rbegin() const { return m_nodes.rbegin(); }
+    const_reverse_iterator rend() const { return m_nodes.rend(); }
+
     DECLARE_TRACE();
 
 private: