Commit 600e1412 authored by rune@opera.com's avatar rune@opera.com

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
parent a60ae27a
...@@ -3,9 +3,9 @@ Checking if styles in the nested shadow roots apply properly to distributed elem ...@@ -3,9 +3,9 @@ Checking if styles in the nested shadow roots apply properly to distributed elem
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". 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 getComputedStyle(span).backgroundColor is "rgb(0, 128, 0)"
PASS successfullyParsed is true PASS successfullyParsed is true
TEST COMPLETE TEST COMPLETE
red? There should be no red
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<script src="../../resources/js-test.js"></script> <script src="../../resources/js-test.js"></script>
<div id="host"> <div id="host">
<div> <div>
<span class="red" id="span">red?</span> <span class="red" id="span">There should be no red</span>
</div> </div>
</div> </div>
<script> <script>
...@@ -12,6 +12,6 @@ root.innerHTML = '<div><content></content></div><style>::content .red { color: g ...@@ -12,6 +12,6 @@ root.innerHTML = '<div><content></content></div><style>::content .red { color: g
var root2 = root.firstChild.createShadowRoot(); var root2 = root.firstChild.createShadowRoot();
root2.innerHTML = '<style>::content .red { background-color: green; color: red; }</style><content></content>'; root2.innerHTML = '<style>::content .red { background-color: green; color: red; }</style><content></content>';
var span = document.querySelector('#span'); 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)'); shouldBeEqualToString('getComputedStyle(span).backgroundColor', 'rgb(0, 128, 0)');
</script> </script>
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
<!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>
<!DOCTYPE html> <!DOCTYPE html>
<script src="../../../resources/js-test.js"></script> <script src="../../../resources/js-test.js"></script>
<style> <style>
.d1, .d2 { color: red } custom-element { color: red }
</style> </style>
<custom-element> <custom-element>
<div class="d1">A</div> <div class="d1">A</div>
......
...@@ -7,6 +7,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE ...@@ -7,6 +7,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
PASS borderColorOf(getNodeInTreeOfTrees("target")) is "rgb(0, 128, 0)" PASS borderColorOf(getNodeInTreeOfTrees("target")) is "rgb(0, 128, 0)"
PASS borderColorOf(getNodeInTreeOfTrees("host/host2/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 borderColorOf(getNodeInTreeOfTrees("host/target")) is "rgb(0, 128, 0)"
PASS successfullyParsed is true PASS successfullyParsed is true
TEST COMPLETE TEST COMPLETE
......
...@@ -36,18 +36,18 @@ function cleanUp() ...@@ -36,18 +36,18 @@ function cleanUp()
description('Test for casacde of treeboundary crossing rules.'); description('Test for casacde of treeboundary crossing rules.');
// Rules declared in inner treescope should win. // Rules declared in outer treescope should win.
sandbox.appendChild( sandbox.appendChild(
createDOM('div', {'id': 'host'}, createDOM('div', {'id': 'host'},
createDOM('style', {}, createDOM('style', {},
document.createTextNode('p:empty { border: 1px solid blue; }')), document.createTextNode('p:empty { border: 1px solid green; }')),
createShadowRoot( createShadowRoot(
createDOM('style', {}, createDOM('style', {},
document.createTextNode('::content > p { border: 1px solid red; }')), document.createTextNode('::content > p { border: 1px solid red; }')),
createDOM('div', {}, createDOM('div', {},
createShadowRoot( createShadowRoot(
createDOM('style', {}, createDOM('style', {},
document.createTextNode('::content > p { border: 1px solid green; }')), document.createTextNode('::content > p { border: 1px solid blue; }')),
createDOM('content', {})), createDOM('content', {})),
createDOM('content', {}))), createDOM('content', {}))),
createDOM('p', {'id': 'target'}))); createDOM('p', {'id': 'target'})));
...@@ -103,6 +103,22 @@ borderColorShouldBe('host/target', 'rgb(0, 128, 0)'); ...@@ -103,6 +103,22 @@ borderColorShouldBe('host/target', 'rgb(0, 128, 0)');
cleanUp(); 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> </script>
</html> </html>
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
<!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>
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
<!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>
<!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>
...@@ -15,7 +15,7 @@ PASS borderColorOf(getNodeInTreeOfTrees("host/host1/host2/span3")) is "rgb(0, 12 ...@@ -15,7 +15,7 @@ PASS borderColorOf(getNodeInTreeOfTrees("host/host1/host2/span3")) is "rgb(0, 12
PASS borderColorOf(getNodeInTreeOfTrees("host/host1/host2/host3/span4")) is not "rgb(0, 128, 0)" 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(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)" 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)"
......
...@@ -88,7 +88,7 @@ sandbox.appendChild( ...@@ -88,7 +88,7 @@ sandbox.appendChild(
createDOM('div', {'id': 'host'}, createDOM('div', {'id': 'host'},
createShadowRoot( createShadowRoot(
createDOM('span', {'id': 'target'}, 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)'); borderColorShouldBe('host/target', 'rgb(0, 128, 0)');
...@@ -104,7 +104,7 @@ sandbox.appendChild( ...@@ -104,7 +104,7 @@ sandbox.appendChild(
createDOM('style', {}, createDOM('style', {},
document.createTextNode('span { border: 1px solid red; }')), document.createTextNode('span { border: 1px solid red; }')),
createDOM('span', {'id': 'target'}, 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)'); borderColorShouldBe('host/target', 'rgb(0, 128, 0)');
...@@ -119,10 +119,9 @@ sandbox.appendChild( ...@@ -119,10 +119,9 @@ sandbox.appendChild(
createDOM('style', {}, createDOM('style', {},
document.createTextNode('span#target { border: 1px solid red; }')), document.createTextNode('span#target { border: 1px solid red; }')),
createDOM('span', {'id': 'target'}, 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(0, 128, 0)');
borderColorShouldBe('host/target', 'rgb(255, 0, 0)');
cleanUp(); cleanUp();
...@@ -133,7 +132,7 @@ sandbox.appendChild( ...@@ -133,7 +132,7 @@ sandbox.appendChild(
createDOM('div', {'id': 'host'}, createDOM('div', {'id': 'host'},
createShadowRoot( createShadowRoot(
createDOM('span', {'id': 'target'}, 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)'); borderColorShouldBe('host/target', 'rgb(0, 128, 0)');
...@@ -149,7 +148,7 @@ sandbox.appendChild( ...@@ -149,7 +148,7 @@ sandbox.appendChild(
document.createTextNode('div > span { border: 1px solid red; }')), document.createTextNode('div > span { border: 1px solid red; }')),
createDOM('div', {}, createDOM('div', {},
createDOM('span', {'id': 'target'}, 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)'); borderColorShouldBe('host/target', 'rgb(0, 128, 0)');
......
...@@ -1202,8 +1202,6 @@ ...@@ -1202,8 +1202,6 @@
'css/StyleSheetContents.h', 'css/StyleSheetContents.h',
'css/StyleSheetList.cpp', 'css/StyleSheetList.cpp',
'css/StyleSheetList.h', 'css/StyleSheetList.h',
'css/TreeBoundaryCrossingRules.cpp',
'css/TreeBoundaryCrossingRules.h',
'css/invalidation/DescendantInvalidationSet.cpp', 'css/invalidation/DescendantInvalidationSet.cpp',
'css/invalidation/DescendantInvalidationSet.h', 'css/invalidation/DescendantInvalidationSet.h',
'css/invalidation/StyleInvalidator.cpp', 'css/invalidation/StyleInvalidator.cpp',
......
/*
* 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
/*
* 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
...@@ -169,10 +169,18 @@ void ScopedStyleResolver::collectMatchingShadowHostRules(ElementRuleCollector& c ...@@ -169,10 +169,18 @@ void ScopedStyleResolver::collectMatchingShadowHostRules(ElementRuleCollector& c
void ScopedStyleResolver::collectMatchingTreeBoundaryCrossingRules(ElementRuleCollector& collector, bool includeEmptyRules, CascadeOrder cascadeOrder) 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) { for (const auto& rules : *m_treeBoundaryCrossingRuleSet) {
MatchRequest request(rules->m_ruleSet.get(), includeEmptyRules, &treeScope().rootNode(), rules->m_parentStyleSheet, rules->m_parentIndex); MatchRequest request(rules->m_ruleSet.get(), includeEmptyRules, &treeScope().rootNode(), rules->m_parentStyleSheet, rules->m_parentIndex);
collector.collectMatchingRules(request, cascadeOrder, true); collector.collectMatchingRules(request, cascadeOrder, true);
} }
collector.setScopeContainsLastMatchedElement(false);
} }
void ScopedStyleResolver::matchPageRules(PageRuleCollector& collector) void ScopedStyleResolver::matchPageRules(PageRuleCollector& collector)
......
...@@ -59,7 +59,7 @@ public: ...@@ -59,7 +59,7 @@ public:
void appendCSSStyleSheet(CSSStyleSheet&, const MediaQueryEvaluator&); void appendCSSStyleSheet(CSSStyleSheet&, const MediaQueryEvaluator&);
void collectMatchingAuthorRules(ElementRuleCollector&, bool includeEmptyRules, CascadeOrder = ignoreCascadeOrder); void collectMatchingAuthorRules(ElementRuleCollector&, bool includeEmptyRules, CascadeOrder = ignoreCascadeOrder);
void collectMatchingShadowHostRules(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 matchPageRules(PageRuleCollector&);
void collectFeaturesTo(RuleFeatureSet&, HashSet<const StyleSheetContents*>& visitedSharedStyleSheetContents) const; void collectFeaturesTo(RuleFeatureSet&, HashSet<const StyleSheetContents*>& visitedSharedStyleSheetContents) const;
void resetAuthorStyle(); void resetAuthorStyle();
......
...@@ -123,21 +123,6 @@ static StylePropertySet* rightToLeftDeclaration() ...@@ -123,21 +123,6 @@ static StylePropertySet* rightToLeftDeclaration()
return rightToLeftDecl; 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) StyleResolver::StyleResolver(Document& document)
: m_document(document) : m_document(document)
, m_viewportStyleResolver(ViewportStyleResolver::create(&document)) , m_viewportStyleResolver(ViewportStyleResolver::create(&document))
...@@ -242,12 +227,12 @@ void StyleResolver::resetRuleFeatures() ...@@ -242,12 +227,12 @@ void StyleResolver::resetRuleFeatures()
void StyleResolver::addTreeBoundaryCrossingScope(ContainerNode& scope) void StyleResolver::addTreeBoundaryCrossingScope(ContainerNode& scope)
{ {
m_treeBoundaryCrossingRules.addScope(scope); m_treeBoundaryCrossingScopes.add(&scope);
} }
void StyleResolver::resetAuthorStyle(TreeScope& treeScope) void StyleResolver::resetAuthorStyle(TreeScope& treeScope)
{ {
m_treeBoundaryCrossingRules.removeScope(treeScope.rootNode()); m_treeBoundaryCrossingScopes.remove(&treeScope.rootNode());
ScopedStyleResolver* resolver = treeScope.scopedStyleResolver(); ScopedStyleResolver* resolver = treeScope.scopedStyleResolver();
if (!resolver) if (!resolver)
...@@ -348,11 +333,11 @@ StyleResolver::~StyleResolver() ...@@ -348,11 +333,11 @@ StyleResolver::~StyleResolver()
{ {
} }
static inline ScopedStyleResolver* scopedResolverFor(const Element* element) static inline ScopedStyleResolver* scopedResolverFor(const Element& element)
{ {
// Ideally, returning element->treeScope().scopedStyleResolver() should be // Ideally, returning element->treeScope().scopedStyleResolver() should be
// enough, but ::cue and custom pseudo elements like ::-webkit-meter-bar pierce // 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 // 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 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 // that VTT scopes and UA shadow trees don't have <style> elements. This is
...@@ -361,40 +346,94 @@ static inline ScopedStyleResolver* scopedResolverFor(const Element* element) ...@@ -361,40 +346,94 @@ static inline ScopedStyleResolver* scopedResolverFor(const Element* element)
// FIXME: Make ::cue and custom pseudo elements part of boundary crossing rules // FIXME: Make ::cue and custom pseudo elements part of boundary crossing rules
// when moving those rules to ScopedStyleResolver as part of issue 401359. // when moving those rules to ScopedStyleResolver as part of issue 401359.
TreeScope* treeScope = &element->treeScope(); TreeScope* treeScope = &element.treeScope();
if (ScopedStyleResolver* resolver = treeScope->scopedStyleResolver()) { if (ScopedStyleResolver* resolver = treeScope->scopedStyleResolver()) {
ASSERT(element->shadowPseudoId().isEmpty()); ASSERT(element.shadowPseudoId().isEmpty());
ASSERT(!element->isVTTElement()); ASSERT(!element.isVTTElement());
return resolver; return resolver;
} }
treeScope = treeScope->parentTreeScope(); treeScope = treeScope->parentTreeScope();
if (!treeScope) if (!treeScope)
return nullptr; return nullptr;
if (element->shadowPseudoId().isEmpty() && !element->isVTTElement()) if (element.shadowPseudoId().isEmpty() && !element.isVTTElement())
return nullptr; return nullptr;
return treeScope->scopedStyleResolver(); 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(); collector.clearMatchedRules();
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.
CascadeOrder cascadeOrder = 0; ScopedStyleResolver* elementScopeResolver = scopedResolverFor(element);
WillBeHeapVector<RawPtrWillBeMember<ScopedStyleResolver>, 8> resolversInShadowTree; bool matchElementScopeDone = !elementScopeResolver;
collectScopedResolversForHostedShadowTrees(element, resolversInShadowTree);
// Apply :host and :host-context rules from inner scopes. for (auto it = m_treeBoundaryCrossingScopes.rbegin(); it != m_treeBoundaryCrossingScopes.rend(); ++it) {
for (int j = resolversInShadowTree.size() - 1; j >= 0; --j) const TreeScope& scope = (*it)->treeScope();
resolversInShadowTree.at(j)->collectMatchingShadowHostRules(collector, includeEmptyRules, ++cascadeOrder); ScopedStyleResolver* resolver = scope.scopedStyleResolver();
ASSERT(resolver);
// Apply normal rules from element scope. if (!matchElementScopeDone && scope.isInclusiveAncestorOf(element.treeScope())) {
if (ScopedStyleResolver* resolver = scopedResolverFor(element))
resolver->collectMatchingAuthorRules(collector, includeEmptyRules, ++cascadeOrder);
// Apply /deep/ and ::shadow rules from outer scopes, and ::content from inner. matchElementScopeDone = true;
m_treeBoundaryCrossingRules.collectTreeBoundaryCrossingRules(element, collector, includeEmptyRules);
collector.sortAndTransferMatchedRules(); // 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) void StyleResolver::matchUARules(ElementRuleCollector& collector)
...@@ -447,9 +486,15 @@ void StyleResolver::matchAllRules(StyleResolverState& state, ElementRuleCollecto ...@@ -447,9 +486,15 @@ void StyleResolver::matchAllRules(StyleResolverState& state, ElementRuleCollecto
} }
} }
matchAuthorRules(state.element(), collector, false); matchAuthorRules(*state.element(), collector, false);
if (state.element()->isStyledElement()) { 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()) { if (state.element()->inlineStyle()) {
// Inline style is immutable as long as there is no CSSOM wrapper. // Inline style is immutable as long as there is no CSSOM wrapper.
bool isInlineStyleCacheable = !state.element()->inlineStyle()->isMutable(); bool isInlineStyleCacheable = !state.element()->inlineStyle()->isMutable();
...@@ -758,7 +803,7 @@ bool StyleResolver::pseudoStyleForElementInternal(Element& element, const Pseudo ...@@ -758,7 +803,7 @@ bool StyleResolver::pseudoStyleForElementInternal(Element& element, const Pseudo
collector.setPseudoStyleRequest(pseudoStyleRequest); collector.setPseudoStyleRequest(pseudoStyleRequest);
matchUARules(collector); matchUARules(collector);
matchAuthorRules(state.element(), collector, false); matchAuthorRules(*state.element(), collector, false);
collector.finishAddingAuthorRulesForTreeScope(); collector.finishAddingAuthorRulesForTreeScope();
if (!collector.matchedResult().hasMatchedProperties()) if (!collector.matchedResult().hasMatchedProperties())
...@@ -909,7 +954,7 @@ void StyleResolver::collectPseudoRulesForElement(Element* element, ElementRuleCo ...@@ -909,7 +954,7 @@ void StyleResolver::collectPseudoRulesForElement(Element* element, ElementRuleCo
if (rulesToInclude & AuthorCSSRules) { if (rulesToInclude & AuthorCSSRules) {
collector.setSameOriginOnly(!(rulesToInclude & CrossOriginCSSRules)); collector.setSameOriginOnly(!(rulesToInclude & CrossOriginCSSRules));
matchAuthorRules(element, collector, rulesToInclude & EmptyCSSRules); matchAuthorRules(*element, collector, rulesToInclude & EmptyCSSRules);
} }
} }
...@@ -952,10 +997,25 @@ bool StyleResolver::applyAnimatedProperties(StyleResolverState& state, const Ele ...@@ -952,10 +997,25 @@ bool StyleResolver::applyAnimatedProperties(StyleResolverState& state, const Ele
return true; 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) StyleRuleKeyframes* StyleResolver::findKeyframesRule(const Element* element, const AtomicString& animationName)
{ {
WillBeHeapVector<RawPtrWillBeMember<ScopedStyleResolver>, 8> resolvers; WillBeHeapVector<RawPtrWillBeMember<ScopedStyleResolver>, 8> resolvers;
collectScopedResolversForHostedShadowTrees(element, resolvers); collectScopedResolversForHostedShadowTrees(*element, resolvers);
if (ScopedStyleResolver* scopedResolver = element->treeScope().scopedStyleResolver()) if (ScopedStyleResolver* scopedResolver = element->treeScope().scopedStyleResolver())
resolvers.append(scopedResolver); resolvers.append(scopedResolver);
...@@ -1446,7 +1506,7 @@ DEFINE_TRACE(StyleResolver) ...@@ -1446,7 +1506,7 @@ DEFINE_TRACE(StyleResolver)
visitor->trace(m_siblingRuleSet); visitor->trace(m_siblingRuleSet);
visitor->trace(m_uncommonAttributeRuleSet); visitor->trace(m_uncommonAttributeRuleSet);
visitor->trace(m_watchedSelectorsRules); visitor->trace(m_watchedSelectorsRules);
visitor->trace(m_treeBoundaryCrossingRules); visitor->trace(m_treeBoundaryCrossingScopes);
visitor->trace(m_styleResourceLoader); visitor->trace(m_styleResourceLoader);
visitor->trace(m_styleSharingLists); visitor->trace(m_styleSharingLists);
visitor->trace(m_pendingStyleSheets); visitor->trace(m_pendingStyleSheets);
......
...@@ -30,12 +30,12 @@ ...@@ -30,12 +30,12 @@
#include "core/css/RuleSet.h" #include "core/css/RuleSet.h"
#include "core/css/SelectorChecker.h" #include "core/css/SelectorChecker.h"
#include "core/css/SelectorFilter.h" #include "core/css/SelectorFilter.h"
#include "core/css/TreeBoundaryCrossingRules.h"
#include "core/css/resolver/CSSPropertyPriority.h" #include "core/css/resolver/CSSPropertyPriority.h"
#include "core/css/resolver/MatchedPropertiesCache.h" #include "core/css/resolver/MatchedPropertiesCache.h"
#include "core/css/resolver/StyleBuilder.h" #include "core/css/resolver/StyleBuilder.h"
#include "core/css/resolver/StyleResolverStats.h" #include "core/css/resolver/StyleResolverStats.h"
#include "core/css/resolver/StyleResourceLoader.h" #include "core/css/resolver/StyleResourceLoader.h"
#include "core/dom/DocumentOrderedList.h"
#include "core/style/AuthorStyleInfo.h" #include "core/style/AuthorStyleInfo.h"
#include "core/style/CachedUAStyle.h" #include "core/style/CachedUAStyle.h"
#include "platform/heap/Handle.h" #include "platform/heap/Handle.h"
...@@ -201,7 +201,10 @@ private: ...@@ -201,7 +201,10 @@ private:
void collectPseudoRulesForElement(Element*, ElementRuleCollector&, PseudoId, unsigned rulesToInclude); void collectPseudoRulesForElement(Element*, ElementRuleCollector&, PseudoId, unsigned rulesToInclude);
void matchRuleSet(ElementRuleCollector&, RuleSet*); void matchRuleSet(ElementRuleCollector&, RuleSet*);
void matchUARules(ElementRuleCollector&); 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 matchAllRules(StyleResolverState&, ElementRuleCollector&, bool includeSMILProperties);
void collectFeatures(); void collectFeatures();
void resetRuleFeatures(); void resetRuleFeatures();
...@@ -245,7 +248,7 @@ private: ...@@ -245,7 +248,7 @@ private:
OwnPtrWillBeMember<RuleSet> m_siblingRuleSet; OwnPtrWillBeMember<RuleSet> m_siblingRuleSet;
OwnPtrWillBeMember<RuleSet> m_uncommonAttributeRuleSet; OwnPtrWillBeMember<RuleSet> m_uncommonAttributeRuleSet;
OwnPtrWillBeMember<RuleSet> m_watchedSelectorsRules; OwnPtrWillBeMember<RuleSet> m_watchedSelectorsRules;
TreeBoundaryCrossingRules m_treeBoundaryCrossingRules; DocumentOrderedList m_treeBoundaryCrossingScopes;
bool m_needCollectFeatures; bool m_needCollectFeatures;
bool m_printMediaType; bool m_printMediaType;
......
...@@ -50,10 +50,14 @@ public: ...@@ -50,10 +50,14 @@ public:
size_t size() const { return m_nodes.size(); } size_t size() const { return m_nodes.size(); }
using iterator = WillBeHeapListHashSet<RawPtrWillBeMember<Node>, 32>::iterator; 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 begin() { return m_nodes.begin(); }
iterator end() { return m_nodes.end(); } 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(); DECLARE_TRACE();
private: private:
......
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