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 @@
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="resources/shadow-dom.js"></script>
<div id="x"><span>test1</span></div>
<div id="x"><span>test2</span></div>
<div id="x"><span></span></div>
<div id="x"><span></span></div>
<div id="host">
<template data-mode="open">
<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>
</div>
<script>
......@@ -22,17 +21,4 @@ test(() => {
assert_equals(host.shadowRoot.querySelectorAll('#y').length, 2);
assert_equals(host.shadowRoot.querySelectorAll('#y span').length, 2);
}, '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>
<!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 {
kV8Document_RootScroller_AttributeSetter = 1688,
kCustomElementRegistryDefine = 1689,
kLinkHeaderServiceWorker = 1690,
kCSSShadowPiercingDescendantCombinator = 1691,
// The above items are available in M56 branch.
kCSSFlexibleBox = 1692,
......
......@@ -862,9 +862,6 @@ String CSSSelector::SelectorText() const {
case kShadowDeepAsDescendant:
result = " /deep/ " + builder.ToString() + result;
break;
case kShadowPiercingDescendant:
result = " >>> " + builder.ToString() + result;
break;
case kDirectAdjacent:
result = " + " + builder.ToString() + result;
break;
......@@ -1085,7 +1082,6 @@ bool CSSSelector::HasDeepCombinatorOrShadowPseudo() const {
return ForAnyInTagHistory(
[](const CSSSelector& selector) -> bool {
return selector.Relation() == CSSSelector::kShadowDeep ||
selector.Relation() == CSSSelector::kShadowPiercingDescendant ||
selector.GetPseudoType() == CSSSelector::kPseudoShadow;
},
*this);
......
......@@ -133,7 +133,6 @@ class CORE_EXPORT CSSSelector {
kDirectAdjacent, // + combinator
kIndirectAdjacent, // ~ combinator
// Special cases for shadow DOM related selectors.
kShadowPiercingDescendant, // >>> combinator
kShadowDeep, // /deep/ combinator
kShadowDeepAsDescendant, // /deep/ as an alias for descendant
kShadowPseudo, // ::shadow pseudo element
......
......@@ -662,25 +662,8 @@ CSSSelector::RelationType CSSSelectorParser::ConsumeCombinator(
return CSSSelector::kIndirectAdjacent;
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();
return CSSSelector::kShadowPiercingDescendant;
return CSSSelector::kChild;
case '/': {
// Match /deep/
......
......@@ -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) {
const char* test_cases[] = {"[*]", "[*|*]"};
......
......@@ -306,14 +306,6 @@ static inline Element* ParentOrV0ShadowHostElement(const Element& element) {
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(
const SelectorCheckingContext& context,
MatchResult& result) const {
......@@ -483,28 +475,6 @@ SelectorChecker::MatchStatus SelectorChecker::MatchForRelation(
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: {
if (ToHTMLSlotElementIfSupportsAssignmentOrNull(*context.element))
return kSelectorFailsCompletely;
......
......@@ -182,7 +182,6 @@ void SelectorFilter::CollectIdentifierHashes(
case CSSSelector::kShadowPseudo:
case CSSSelector::kShadowPart:
case CSSSelector::kShadowDeep:
case CSSSelector::kShadowPiercingDescendant:
skip_over_subselectors = false;
CollectDescendantSelectorIdentifierHashes(*current, hash);
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