Commit c81707c5 authored by Takayoshi Kochi's avatar Takayoshi Kochi Committed by Commit Bot

Remove '>>>' (shadow-piercing combinator)

This was implemented in crbug.com/633007 but was never adopted
by any standards, and could not get much attention from developers
except for testing use cases.

As this was dismissed from the standard, we have to pursue the
testing use cases in other forms.

Bug: 829713
Change-Id: I2b053a15e96d68ff5a8c632e65b7b2feefb15dab
Reviewed-on: https://chromium-review.googlesource.com/999453Reviewed-by: default avatarHayato Ito <hayato@chromium.org>
Commit-Queue: Takayoshi Kochi <kochi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#575599}
parent 6ada5346
...@@ -2,13 +2,12 @@ ...@@ -2,13 +2,12 @@
<script src="../resources/testharness.js"></script> <script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script> <script src="../resources/testharnessreport.js"></script>
<script src="resources/shadow-dom.js"></script> <script src="resources/shadow-dom.js"></script>
<div id="x"><span>test1</span></div> <div id="x"><span></span></div>
<div id="x"><span>test2</span></div> <div id="x"><span></span></div>
<div id="host"> <div id="host">
<template data-mode="open"> <template data-mode="open">
<div id="y"><span></span></div> <div id="y"><span></span></div>
<div id="y"><span></span></div> <div id="y"><span></span></div>
<div><span id="x" class="y">Can you find this?</span></div>
</template> </template>
</div> </div>
<script> <script>
...@@ -22,17 +21,4 @@ test(() => { ...@@ -22,17 +21,4 @@ test(() => {
assert_equals(host.shadowRoot.querySelectorAll('#y').length, 2); assert_equals(host.shadowRoot.querySelectorAll('#y').length, 2);
assert_equals(host.shadowRoot.querySelectorAll('#y span').length, 2); assert_equals(host.shadowRoot.querySelectorAll('#y span').length, 2);
}, 'querySelectorAll for multiple #Ids in a shadow tree'); }, 'querySelectorAll for multiple #Ids in a shadow tree');
test(() => {
var spans = document.querySelectorAll('body >>> #x');
assert_equals(spans.length, 3);
assert_equals(spans[0].textContent, 'test1');
assert_equals(spans[1].textContent, 'test2');
assert_equals(spans[2].textContent, 'Can you find this?');
}, 'deep combinator >>> should work for querySelectorAll.');
test(() => {
var span = document.querySelector('body >>> .y');
assert_equals(span.textContent, 'Can you find this?');
}, 'deep combinator >>> should work for querySelector');
</script> </script>
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="resources/shadow-dom.js"></script>
<style>
body >>> #x { color: red; }
</style>
<div id="host">
<template data-mode="open">
<div><span id="x">In the shadow tree.</span></div>
</template>
</div>
<script>
convertTemplatesToShadowRootsWithin(host);
test(() => {
var span = document.querySelector('body >>> #x');
assert_equals(span.textContent, 'In the shadow tree.',
'>>> should work in static profile.');
var stylesheet = document.styleSheets[0];
assert_equals(stylesheet.cssRules.length, 0,
'A selector containing >>> should be discarded in dynamic profile.');
assert_equals(window.getComputedStyle(span).color, 'rgb(0, 0, 0)',
'>>> in dynamic profile should not match.');
}, 'deep descendant combinator >>> should work only in static profile.');
</script>
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="resources/shadow-dom.js"></script>
<body>
<div id="testroot">
<div id="openhost">
<template data-mode="open">
<div id="inner-open"></div>
</template>
</div>
<div id="closedhost">
<template data-mode="closed">
<div id="inner-closed"></div>
</template>
</div>
<div id="v0host">
<template data-mode="v0">
<div id="inner-v0"></div>
</template>
</div>
<div id="nestedhost">
<template data-mode="open" data-expose-as="nestedRoot">
<div id="inner-div">
<div>
<template data-mode="open">
<div id="inner-nested-open"></div>
</template>
</div>
<div>
<template data-mode="closed">
<div id="inner-nested-closed"></div>
</template>
</div>
<div>
<template data-mode="v0">
<div id="inner-nested-v0"></div>
</template>
</div>
</div>
</template>
</div>
</div>
<div id="testroot2">
<template data-mode="open">
<div id="div1">
<template data-mode="open">
<div id="div2">
<template data-mode="open">
<div id="div3">
<template data-mode="open" data-expose-as="deepestRoot">
<div id="div4">
</div>
</template>
</div>
</template>
</div>
</template>
</div>
</template>
</div>
</body>
<script>
'use strict';
convertTemplatesToShadowRootsWithin(testroot);
test(() => {
assert_equals(document.querySelectorAll('body >>> #inner-open').length, 1);
assert_equals(document.querySelectorAll('body >>> #inner-closed').length, 0);
assert_equals(document.querySelectorAll('body >>> #inner-v0').length, 0);
assert_equals(document.querySelectorAll('body >>> #inner-nested-open').length, 1);
assert_equals(document.querySelectorAll('body >>> #inner-nested-closed').length, 0);
assert_equals(document.querySelectorAll('body >>> #inner-nested-v0').length, 0);
}, '>>> should match only through open shadow roots.');
test(() => {
let innerDiv = nestedRoot.querySelector('#inner-div');
assert_equals(innerDiv.querySelectorAll('body >>> #inner-nested-open').length, 0);
assert_equals(innerDiv.querySelectorAll('body >>> #inner-nested-closed').length, 0);
assert_equals(innerDiv.querySelectorAll('body >>> #inner-nested-v0').length, 0);
assert_equals(innerDiv.querySelectorAll('#inner-div >>> #inner-nested-open').length, 1);
assert_equals(innerDiv.querySelectorAll('#inner-div >>> #inner-nested-closed').length, 0);
assert_equals(innerDiv.querySelectorAll('#inner-div >>> #inner-nested-v0').length, 0);
}, 'leftmost compound should match an element in the same node tree as context object.');
test(() => {
assert_equals(document.querySelector('#testroot >>> #openhost').id, 'openhost');
assert_equals(document.querySelector('#testroot >>> #closedhost').id, 'closedhost');
assert_equals(document.querySelector('#testroot >>> #v0host').id, 'v0host');
}, '>>> should match without piercing through shadow roots.');
test(() => {
convertTemplatesToShadowRootsWithin(testroot2);
let div4 = deepestRoot.querySelector('#div4');
assert_equals(document.querySelector('body >>> #div1 >>> #div2 >>> #div3 >>> #div4'), div4);
assert_equals(document.querySelector('body >>> #div1 >>> #div2 >>> #div4'), div4);
assert_equals(document.querySelector('body >>> #div1 >>> #div3 >>> #div4'), div4);
assert_equals(document.querySelector('body >>> #div1 >>> #div4'), div4);
assert_equals(document.querySelector('body >>> #div2 >>> #div4'), div4);
assert_equals(document.querySelector('#div1 >>> #div2 >>> #div3 >>> #div4'), null,
'leftmost compound should match an element in the same node tree as context object.');
}, 'Multiple >>>s in a selector should work.');
</script>
...@@ -1191,7 +1191,6 @@ enum WebFeature { ...@@ -1191,7 +1191,6 @@ enum WebFeature {
kV8Document_RootScroller_AttributeSetter = 1688, kV8Document_RootScroller_AttributeSetter = 1688,
kCustomElementRegistryDefine = 1689, kCustomElementRegistryDefine = 1689,
kLinkHeaderServiceWorker = 1690, kLinkHeaderServiceWorker = 1690,
kCSSShadowPiercingDescendantCombinator = 1691,
// The above items are available in M56 branch. // The above items are available in M56 branch.
kCSSFlexibleBox = 1692, kCSSFlexibleBox = 1692,
......
...@@ -862,9 +862,6 @@ String CSSSelector::SelectorText() const { ...@@ -862,9 +862,6 @@ String CSSSelector::SelectorText() const {
case kShadowDeepAsDescendant: case kShadowDeepAsDescendant:
result = " /deep/ " + builder.ToString() + result; result = " /deep/ " + builder.ToString() + result;
break; break;
case kShadowPiercingDescendant:
result = " >>> " + builder.ToString() + result;
break;
case kDirectAdjacent: case kDirectAdjacent:
result = " + " + builder.ToString() + result; result = " + " + builder.ToString() + result;
break; break;
...@@ -1085,7 +1082,6 @@ bool CSSSelector::HasDeepCombinatorOrShadowPseudo() const { ...@@ -1085,7 +1082,6 @@ bool CSSSelector::HasDeepCombinatorOrShadowPseudo() const {
return ForAnyInTagHistory( return ForAnyInTagHistory(
[](const CSSSelector& selector) -> bool { [](const CSSSelector& selector) -> bool {
return selector.Relation() == CSSSelector::kShadowDeep || return selector.Relation() == CSSSelector::kShadowDeep ||
selector.Relation() == CSSSelector::kShadowPiercingDescendant ||
selector.GetPseudoType() == CSSSelector::kPseudoShadow; selector.GetPseudoType() == CSSSelector::kPseudoShadow;
}, },
*this); *this);
......
...@@ -133,7 +133,6 @@ class CORE_EXPORT CSSSelector { ...@@ -133,7 +133,6 @@ class CORE_EXPORT CSSSelector {
kDirectAdjacent, // + combinator kDirectAdjacent, // + combinator
kIndirectAdjacent, // ~ combinator kIndirectAdjacent, // ~ combinator
// Special cases for shadow DOM related selectors. // Special cases for shadow DOM related selectors.
kShadowPiercingDescendant, // >>> combinator
kShadowDeep, // /deep/ combinator kShadowDeep, // /deep/ combinator
kShadowDeepAsDescendant, // /deep/ as an alias for descendant kShadowDeepAsDescendant, // /deep/ as an alias for descendant
kShadowPseudo, // ::shadow pseudo element kShadowPseudo, // ::shadow pseudo element
......
...@@ -662,25 +662,8 @@ CSSSelector::RelationType CSSSelectorParser::ConsumeCombinator( ...@@ -662,25 +662,8 @@ CSSSelector::RelationType CSSSelectorParser::ConsumeCombinator(
return CSSSelector::kIndirectAdjacent; return CSSSelector::kIndirectAdjacent;
case '>': case '>':
if (!RuntimeEnabledFeatures::
ShadowPiercingDescendantCombinatorEnabled() ||
context_->IsLiveProfile() ||
range.Peek(1).GetType() != kDelimiterToken ||
range.Peek(1).Delimiter() != '>') {
range.ConsumeIncludingWhitespace();
return CSSSelector::kChild;
}
range.Consume();
// Check the 3rd '>'.
if (range.Peek(1).GetType() != kDelimiterToken ||
range.Peek(1).Delimiter() != '>') {
// TODO: Treat '>>' as a CSSSelector::kDescendant here.
return CSSSelector::kChild;
}
range.Consume();
range.ConsumeIncludingWhitespace(); range.ConsumeIncludingWhitespace();
return CSSSelector::kShadowPiercingDescendant; return CSSSelector::kChild;
case '/': { case '/': {
// Match /deep/ // Match /deep/
......
...@@ -315,68 +315,6 @@ TEST(CSSSelectorParserTest, SerializedUniversal) { ...@@ -315,68 +315,6 @@ TEST(CSSSelectorParserTest, SerializedUniversal) {
} }
} }
TEST(CSSSelectorParserTest, InvalidDescendantCombinatorInLiveProfile) {
const char* test_cases[] = {"div >>>> span", "div >>> span", "div >> span"};
CSSParserContext* context = CSSParserContext::Create(
kHTMLStandardMode, SecureContextMode::kInsecureContext,
CSSParserContext::kLiveProfile);
StyleSheetContents* sheet = StyleSheetContents::Create(context);
for (auto* test_case : test_cases) {
SCOPED_TRACE(test_case);
CSSTokenizer tokenizer(test_case);
const auto tokens = tokenizer.TokenizeToEOF();
CSSParserTokenRange range(tokens);
CSSSelectorList list =
CSSSelectorParser::ParseSelector(range, context, sheet);
EXPECT_FALSE(list.IsValid());
}
}
TEST(CSSSelectorParserTest, InvalidDescendantCombinatorInSnapshotProfile) {
const char* test_cases[] = {"div >>>> span", "div >> span", "div >> > span",
"div > >> span", "div > > > span"};
CSSParserContext* context = CSSParserContext::Create(
kHTMLStandardMode, SecureContextMode::kInsecureContext,
CSSParserContext::kSnapshotProfile);
StyleSheetContents* sheet = StyleSheetContents::Create(context);
for (auto* test_case : test_cases) {
SCOPED_TRACE(test_case);
CSSTokenizer tokenizer(test_case);
const auto tokens = tokenizer.TokenizeToEOF();
CSSParserTokenRange range(tokens);
CSSSelectorList list =
CSSSelectorParser::ParseSelector(range, context, sheet);
EXPECT_FALSE(list.IsValid());
}
}
TEST(CSSSelectorParserTest, ShadowPiercingCombinatorInSnapshotProfile) {
const char* test_cases[][2] = {{"div >>> span", "div >>> span"},
{"div >>/**/> span", "div >>> span"},
{"div >/**/>> span", "div >>> span"},
{"div >/**/>/**/> span", "div >>> span"}};
CSSParserContext* context = CSSParserContext::Create(
kHTMLStandardMode, SecureContextMode::kInsecureContext,
CSSParserContext::kSnapshotProfile);
StyleSheetContents* sheet = StyleSheetContents::Create(context);
for (auto** test_case : test_cases) {
SCOPED_TRACE(test_case[0]);
CSSTokenizer tokenizer(test_case[0]);
const auto tokens = tokenizer.TokenizeToEOF();
CSSParserTokenRange range(tokens);
CSSSelectorList list =
CSSSelectorParser::ParseSelector(range, context, sheet);
EXPECT_TRUE(list.IsValid());
EXPECT_STREQ(test_case[1], list.SelectorsText().Ascii().data());
}
}
TEST(CSSSelectorParserTest, AttributeSelectorUniversalInvalid) { TEST(CSSSelectorParserTest, AttributeSelectorUniversalInvalid) {
const char* test_cases[] = {"[*]", "[*|*]"}; const char* test_cases[] = {"[*]", "[*|*]"};
......
...@@ -306,14 +306,6 @@ static inline Element* ParentOrV0ShadowHostElement(const Element& element) { ...@@ -306,14 +306,6 @@ static inline Element* ParentOrV0ShadowHostElement(const Element& element) {
return element.ParentOrShadowHostElement(); return element.ParentOrShadowHostElement();
} }
static inline Element* ParentOrOpenShadowHostElement(const Element& element) {
if (element.parentNode() && element.parentNode()->IsShadowRoot()) {
if (ToShadowRoot(element.parentNode())->GetType() != ShadowRootType::kOpen)
return nullptr;
}
return element.ParentOrShadowHostElement();
}
SelectorChecker::MatchStatus SelectorChecker::MatchForRelation( SelectorChecker::MatchStatus SelectorChecker::MatchForRelation(
const SelectorCheckingContext& context, const SelectorCheckingContext& context,
MatchResult& result) const { MatchResult& result) const {
...@@ -483,28 +475,6 @@ SelectorChecker::MatchStatus SelectorChecker::MatchForRelation( ...@@ -483,28 +475,6 @@ SelectorChecker::MatchStatus SelectorChecker::MatchForRelation(
return kSelectorFailsCompletely; return kSelectorFailsCompletely;
} }
case CSSSelector::kShadowPiercingDescendant: {
DCHECK_EQ(mode_, kQueryingRules);
UseCounter::Count(context.element->GetDocument(),
WebFeature::kCSSShadowPiercingDescendantCombinator);
// TODO(kochi): parentOrOpenShadowHostElement() is necessary because
// SelectorQuery can pass V0 shadow roots. All closed shadow roots are
// already filtered out, thus once V0 is removed this logic can use
// parentOrShadowHostElement() instead.
for (next_context.element =
ParentOrOpenShadowHostElement(*context.element);
next_context.element;
next_context.element =
ParentOrOpenShadowHostElement(*next_context.element)) {
MatchStatus match = MatchSelector(next_context, result);
if (match == kSelectorMatches || match == kSelectorFailsCompletely)
return match;
if (NextSelectorExceedsScope(next_context))
break;
}
return kSelectorFailsCompletely;
}
case CSSSelector::kShadowSlot: { case CSSSelector::kShadowSlot: {
if (ToHTMLSlotElementIfSupportsAssignmentOrNull(*context.element)) if (ToHTMLSlotElementIfSupportsAssignmentOrNull(*context.element))
return kSelectorFailsCompletely; return kSelectorFailsCompletely;
......
...@@ -182,7 +182,6 @@ void SelectorFilter::CollectIdentifierHashes( ...@@ -182,7 +182,6 @@ void SelectorFilter::CollectIdentifierHashes(
case CSSSelector::kShadowPseudo: case CSSSelector::kShadowPseudo:
case CSSSelector::kShadowPart: case CSSSelector::kShadowPart:
case CSSSelector::kShadowDeep: case CSSSelector::kShadowDeep:
case CSSSelector::kShadowPiercingDescendant:
skip_over_subselectors = false; skip_over_subselectors = false;
CollectDescendantSelectorIdentifierHashes(*current, hash); CollectDescendantSelectorIdentifierHashes(*current, hash);
break; break;
......
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