Commit 7fd0d339 authored by kochi@chromium.org's avatar kochi@chromium.org

Handle closed mode shadow for /deep/ and ::shadow selectors

For closed shadow roots, /deep/ combinator or
::shadow pseudo element should not pierce the
closed shadow root boundary.

BUG=459136
TEST=closed-shadow-and-deep-combinator.html, closed-mode-deep-combinators.html

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

git-svn-id: svn://svn.chromium.org/blink/trunk@200909 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 06af8b62
CONSOLE WARNING: /deep/ combinator is deprecated. See https://www.chromestatus.com/features/6750456638341120 for more details.
CONSOLE WARNING: ::shadow pseudo-element is deprecated. See https://www.chromestatus.com/features/6750456638341120 for more details.
(1/6) /deep/ style rule on top-level document.
PASS backgroundColorOf('host_open_open') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_open/div1') is "rgb(0, 0, 255)"
PASS backgroundColorOf('host_open_open/div1/div2') is "rgb(0, 0, 255)"
PASS backgroundColorOf('host_open_open/div1b') is "rgb(0, 0, 255)"
PASS backgroundColorOf('host_open_closed') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_closed/div3') is "rgb(0, 0, 255)"
PASS backgroundColorOf('host_open_closed/div3/div4') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_closed/div3b') is "rgb(0, 0, 255)"
PASS backgroundColorOf('host_closed_open') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_open/div5') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_open/div5/div6') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_open/div5b') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed/div7') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed/div7/div8') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed/div7b') is "rgba(0, 0, 0, 0)"
(2/6) ::shadow style rule on top-level document.
PASS backgroundColorOf('host_open_open') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_open/div1') is "rgb(0, 128, 0)"
PASS backgroundColorOf('host_open_open/div1/div2') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_open/div1b') is "rgb(0, 128, 0)"
PASS backgroundColorOf('host_open_closed') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_closed/div3') is "rgb(0, 128, 0)"
PASS backgroundColorOf('host_open_closed/div3/div4') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_closed/div3b') is "rgb(0, 128, 0)"
PASS backgroundColorOf('host_closed_open') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_open/div5') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_open/div5/div6') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_open/div5b') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed/div7') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed/div7/div8') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed/div7b') is "rgba(0, 0, 0, 0)"
(3/6) /deep/ style on shadow tree.
PASS backgroundColorOf('host_open_open/div1') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_open/div1/div2') is "rgb(0, 0, 255)"
PASS backgroundColorOf('host_open_open/div1b') is "rgb(0, 0, 255)"
PASS backgroundColorOf('host_open_closed/div3') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_closed/div3/div4') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_closed/div3b') is "rgb(0, 0, 255)"
PASS backgroundColorOf('host_closed_open/div5') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_open/div5/div6') is "rgb(0, 0, 255)"
PASS backgroundColorOf('host_closed_open/div5b') is "rgb(0, 0, 255)"
PASS backgroundColorOf('host_closed_closed/div7') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed/div7/div8') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed/div7b') is "rgb(0, 0, 255)"
(4/6) ::shadow style on shadow tree.
PASS backgroundColorOf('host_open_open/div1') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_open/div1/div2') is "rgb(0, 128, 0)"
PASS backgroundColorOf('host_open_open/div1b') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_closed/div3') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_closed/div3/div4') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_open_closed/div3b') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_open/div5') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_open/div5/div6') is "rgb(0, 128, 0)"
PASS backgroundColorOf('host_closed_open/div5b') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed/div7') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed/div7/div8') is "rgba(0, 0, 0, 0)"
PASS backgroundColorOf('host_closed_closed/div7b') is "rgba(0, 0, 0, 0)"
(5/6) /deep/ selector in querySelectorAll()
PASS results.length is 3
PASS node.id is "div1"
PASS node.id is "div2"
PASS node.id is "div1b"
PASS results.length is 2
PASS node.id is "div3"
PASS node.id is "div3b"
PASS host_closed_open.querySelectorAll("div /deep/ div").length is 0
PASS host_closed_closed.querySelectorAll("div /deep/ div").length is 0
PASS results.length is 2
PASS node.id is "div2"
PASS node.id is "div1b"
PASS results.length is 1
PASS node.id is "div3b"
PASS results.length is 2
PASS node.id is "div6"
PASS node.id is "div5b"
PASS results.length is 1
PASS node.id is "div7b"
(6/6) ::shadow selector in querySelectorAll()
PASS results.length is 2
PASS node.id is "div1"
PASS node.id is "div1b"
PASS results.length is 2
PASS node.id is "div3"
PASS node.id is "div3b"
PASS host_closed_open.querySelectorAll("div::shadow div").length is 0
PASS host_closed_closed.querySelectorAll("div::shadow div").length is 0
PASS results.length is 1
PASS node.id is "div2"
PASS div3.querySelectorAll("div::shadow div").length is 0
PASS results.length is 1
PASS node.id is "div6"
PASS div7.querySelectorAll("div::shadow div").length is 0
PASS successfullyParsed is true
TEST COMPLETE
<!doctype html>
<script src="../../../resources/js-test.js"></script>
<script src="resources/shadow-dom.js"></script>
<style id="style1">
</style>
<body></body>
<script>
function prepareShadowTree(hostId, mode1, mode2, div1, div2, div3) {
var parent = document.body;
parent.appendChild(
createDOM('div', {'id': hostId},
createShadowRoot({'mode': mode1},
createDOM('div', {'id': div1},
createShadowRoot({'mode': mode2},
createDOM('div', {'id': div2})),
createDOM('div', {'id': div3})))));
}
var results;
var expected;
var node;
function queryResultsShouldBe(host, query, expectedArgument) {
results = host.querySelectorAll(query);
expected = expectedArgument;
shouldBe('results.length', '' + expected.length);
for (var i = 0; i < expected.length; ++i) {
node = results[i];
shouldBeEqualToString.bind(this)('node.id', expected[i]);
}
}
prepareShadowTree('host_open_open', 'open', 'open', 'div1', 'div2', 'div1b');
prepareShadowTree('host_open_closed', 'open', 'closed', 'div3', 'div4', 'div3b');
prepareShadowTree('host_closed_open', 'closed', 'open', 'div5', 'div6', 'div5b');
prepareShadowTree('host_closed_closed', 'closed', 'closed', 'div7', 'div8', 'div7b');
debug('(1/6) /deep/ style rule on top-level document.');
var styleElement = document.getElementById('style1');
styleElement.textContent = 'div /deep/ div { background-color: blue; }';
backgroundColorShouldBe('host_open_open', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_open/div1', 'rgb(0, 0, 255)');
backgroundColorShouldBe('host_open_open/div1/div2', 'rgb(0, 0, 255)');
backgroundColorShouldBe('host_open_open/div1b', 'rgb(0, 0, 255)');
backgroundColorShouldBe('host_open_closed', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_closed/div3', 'rgb(0, 0, 255)');
backgroundColorShouldBe('host_open_closed/div3/div4', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_closed/div3b', 'rgb(0, 0, 255)');
backgroundColorShouldBe('host_closed_open', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_open/div5', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_open/div5/div6', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_open/div5b', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_closed', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_closed/div7', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_closed/div7/div8', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_closed/div7b', 'rgba(0, 0, 0, 0)');
debug('(2/6) ::shadow style rule on top-level document.');
styleElement.innerHTML = 'div::shadow div { background-color: green; }';
backgroundColorShouldBe('host_open_open', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_open/div1', 'rgb(0, 128, 0)');
backgroundColorShouldBe('host_open_open/div1/div2', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_open/div1b', 'rgb(0, 128, 0)');
backgroundColorShouldBe('host_open_closed', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_closed/div3', 'rgb(0, 128, 0)');
backgroundColorShouldBe('host_open_closed/div3/div4', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_closed/div3b', 'rgb(0, 128, 0)');
backgroundColorShouldBe('host_closed_open', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_open/div5', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_open/div5/div6', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_open/div5b', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_closed', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_closed/div7', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_closed/div7/div8', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_closed/div7b', 'rgba(0, 0, 0, 0)');
debug('(3/6) /deep/ style on shadow tree.');
styleElement.innerHTML = '';
var div1 = getNodeInTreeOfTrees('host_open_open/div1');
div1.insertAdjacentHTML('afterbegin', '<style>div /deep/ div { background-color: blue; }</style>');
backgroundColorShouldBe('host_open_open/div1', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_open/div1/div2', 'rgb(0, 0, 255)');
backgroundColorShouldBe('host_open_open/div1b', 'rgb(0, 0, 255)');
div1.removeChild(div1.firstElementChild);
var div3 = getNodeInTreeOfTrees('host_open_closed/div3');
div3.insertAdjacentHTML('afterbegin', '<style>div /deep/ div { background-color: blue; }</style>');
backgroundColorShouldBe('host_open_closed/div3', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_closed/div3/div4', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_closed/div3b', 'rgb(0, 0, 255)');
div3.removeChild(div3.firstElementChild);
var div5 = getNodeInTreeOfTrees('host_closed_open/div5');
div5.insertAdjacentHTML('afterbegin', '<style>div /deep/ div { background-color: blue; }</style>');
backgroundColorShouldBe('host_closed_open/div5', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_open/div5/div6', 'rgb(0, 0, 255)');
backgroundColorShouldBe('host_closed_open/div5b', 'rgb(0, 0, 255)');
div5.removeChild(div5.firstElementChild);
var div7 = getNodeInTreeOfTrees('host_closed_closed/div7');
div7.insertAdjacentHTML('afterbegin', '<style>div /deep/ div { background-color: blue; }</style>');
backgroundColorShouldBe('host_closed_closed/div7', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_closed/div7/div8', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_closed/div7b', 'rgb(0, 0, 255)');
div7.removeChild(div7.firstElementChild);
debug('(4/6) ::shadow style on shadow tree.');
div1.insertAdjacentHTML('afterbegin', '<style>div::shadow div { background-color: green; }</style>');
backgroundColorShouldBe('host_open_open/div1', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_open/div1/div2', 'rgb(0, 128, 0)');
backgroundColorShouldBe('host_open_open/div1b', 'rgba(0, 0, 0, 0)');
div1.removeChild(div1.firstElementChild);
div3.insertAdjacentHTML('afterbegin', '<style>div::shadow div { background-color: green; }</style>');
backgroundColorShouldBe('host_open_closed/div3', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_closed/div3/div4', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_open_closed/div3b', 'rgba(0, 0, 0, 0)');
div3.removeChild(div3.firstElementChild);
div5.insertAdjacentHTML('afterbegin', '<style>div::shadow div { background-color: green; }</style>');
backgroundColorShouldBe('host_closed_open/div5', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_open/div5/div6', 'rgb(0, 128, 0)');
backgroundColorShouldBe('host_closed_open/div5b', 'rgba(0, 0, 0, 0)');
div5.removeChild(div5.firstElementChild);
div7.insertAdjacentHTML('afterbegin', '<style>div::shadow div { background-color: green; }</style>');
backgroundColorShouldBe('host_closed_closed/div7', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_closed/div7/div8', 'rgba(0, 0, 0, 0)');
backgroundColorShouldBe('host_closed_closed/div7b', 'rgba(0, 0, 0, 0)');
div7.removeChild(div7.firstElementChild);
debug('(5/6) /deep/ selector in querySelectorAll()');
queryResultsShouldBe(host_open_open, 'div /deep/ div', ['div1', 'div2', 'div1b']);
queryResultsShouldBe(host_open_closed, 'div /deep/ div', ['div3', 'div3b']);
shouldBe('host_closed_open.querySelectorAll("div /deep/ div").length', '0');
shouldBe('host_closed_closed.querySelectorAll("div /deep/ div").length', '0');
queryResultsShouldBe(div1, 'div /deep/ div', ['div2', 'div1b']);
queryResultsShouldBe(div3, 'div /deep/ div', ['div3b']);
queryResultsShouldBe(div5, 'div /deep/ div', ['div6', 'div5b']);
queryResultsShouldBe(div7, 'div /deep/ div', ['div7b']);
debug('(6/6) ::shadow selector in querySelectorAll()');
queryResultsShouldBe(host_open_open, 'div::shadow div', ['div1', 'div1b']);
queryResultsShouldBe(host_open_closed, 'div::shadow div', ['div3', 'div3b']);
shouldBe('host_closed_open.querySelectorAll("div::shadow div").length', '0');
shouldBe('host_closed_closed.querySelectorAll("div::shadow div").length', '0');
queryResultsShouldBe(div1, 'div::shadow div', ['div2']);
shouldBe('div3.querySelectorAll("div::shadow div").length', '0');
queryResultsShouldBe(div5, 'div::shadow div', ['div6']);
shouldBe('div7.querySelectorAll("div::shadow div").length', '0');
</script>
CONSOLE WARNING: /deep/ combinator is deprecated. See https://www.chromestatus.com/features/6750456638341120 for more details.
PASS result.length is 5
PASS node.id is "openhost"
PASS node.id is "openhost_in_openshadow"
PASS node.id is "div_open_in_open"
PASS node.id is "closedhost_in_openshadow"
PASS node.id is "closedhost"
PASS successfullyParsed is true
TEST COMPLETE
<!doctype html>
<script src="../../../resources/js-test.js"></script>
<script src="resources/shadow-dom.js"></script>
<body>
<div id="parent"></div>
</body>
<script>
function prepareShadowTree() {
var parent = document.getElementById('parent');
parent.appendChild(
createDOM('div', {id: 'toplevel'},
createDOM('div', {id: 'openhost'},
createShadowRoot({mode: 'open'},
createDOM('div', {id: 'openhost_in_openshadow'},
createShadowRoot({'mode': 'open'},
createDOM('div', {id: 'div_open_in_open'}))),
createDOM('div', {id: 'closedhost_in_openshadow'},
createShadowRoot({'mode': 'closed'},
createDOM('div', {id: 'div_closed_in_open'}))))),
createDOM('div', {id: 'closedhost'},
createShadowRoot({mode: 'closed'},
createDOM('div', {id: 'openhost_in_closedshadow'},
createShadowRoot({'mode': 'open'},
createDOM('div', {id: 'div_open_in_closed'}))),
createDOM('div', {id: 'closedhost_in_closedshadow'},
createShadowRoot({'mode': 'closed'},
createDOM('div', {id: 'div_closed_in_closed'})))))));
}
prepareShadowTree();
var parent = document.getElementById('parent');
var result = parent.querySelectorAll("div /deep/ div /deep/ div");
shouldBe('result.length', '5');
var expected = [
'openhost',
'openhost_in_openshadow',
'div_open_in_open',
'closedhost_in_openshadow',
'closedhost'
];
var node;
for (var i = 0; i < result.length; ++i) {
node = result[i];
shouldBeEqualToString('node.id', expected[i]);
}
</script>
......@@ -309,6 +309,15 @@ SelectorChecker::Match SelectorChecker::matchForPseudoShadow(const SelectorCheck
return matchSelector(context, result);
}
static inline Element* parentOrShadowHostElementButDisallowClosedShadowTree(const Element& element)
{
if (element.parentNode() && element.parentNode()->isShadowRoot()) {
if (!toShadowRoot(element.parentNode())->isOpen())
return nullptr;
}
return element.parentOrShadowHostElement();
}
SelectorChecker::Match SelectorChecker::matchForRelation(const SelectorCheckingContext& context, MatchResult& result) const
{
SelectorCheckingContext nextContext = prepareNextContextForRelation(context);
......@@ -423,6 +432,7 @@ SelectorChecker::Match SelectorChecker::matchForRelation(const SelectorCheckingC
}
if (context.selector->relationIsAffectedByPseudoContent()) {
// TODO(kochi): closed mode tree should be handled as well for ::content.
for (Element* element = context.element; element; element = element->parentOrShadowHostElement()) {
if (matchForShadowDistributed(nextContext, *element, result) == SelectorMatches)
return SelectorMatches;
......@@ -432,7 +442,8 @@ SelectorChecker::Match SelectorChecker::matchForRelation(const SelectorCheckingC
nextContext.isSubSelector = false;
nextContext.inRightmostCompound = false;
for (nextContext.element = context.element->parentOrShadowHostElement(); nextContext.element; nextContext.element = nextContext.element->parentOrShadowHostElement()) {
for (nextContext.element = parentOrShadowHostElementButDisallowClosedShadowTree(*context.element); nextContext.element; nextContext.element = parentOrShadowHostElementButDisallowClosedShadowTree(*nextContext.element)) {
Match match = matchSelector(nextContext, result);
if (match == SelectorMatches || match == SelectorFailsCompletely)
return match;
......
......@@ -266,6 +266,11 @@ bool SharedStyleFinder::canShareStyleWithElement(Element& candidate) const
return false;
}
ShadowRoot* root1 = element().containingShadowRoot();
ShadowRoot* root2 = candidate.containingShadowRoot();
if (root1 && root2 && root1->type() != root2->type())
return false;
if (document().containsValidityStyleRules()) {
if (candidate.isValidElement() != element().isValidElement())
return false;
......
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