Commit 0cd41aa3 authored by Rune Lillesveen's avatar Rune Lillesveen Committed by Commit Bot

Support ::placeholder after ::slotted().

Tree-abiding pseudo elements are allowed after ::slotted() according to
css-scoping-1. According to css-pseudo-4 they are ::before, ::after,
::marker, and ::placeholder. We already allowed ::before and ::after. We
do not support ::marker. This CL allows ::placeholder.

TEST=external/wpt/css/css-scoping/slotted-parsing.html
TEST=external/wpt/css/css-scoping/slotted-placeholder.html

Bug: 902518
Change-Id: I963fa93e68c8fbd33fc8090e930be9a00f3c9bd4
Reviewed-on: https://chromium-review.googlesource.com/c/1323049
Commit-Queue: Rune Lillesveen <futhark@chromium.org>
Reviewed-by: default avatarFergal Daly <fergal@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606791}
parent f21161ed
...@@ -2892,7 +2892,6 @@ crbug.com/893480 external/wpt/infrastructure/testdriver/actions/multiDevice.html ...@@ -2892,7 +2892,6 @@ crbug.com/893480 external/wpt/infrastructure/testdriver/actions/multiDevice.html
# ====== New tests from wpt-importer added here ====== # ====== New tests from wpt-importer added here ======
crbug.com/626703 external/wpt/css/css-multicol/multicol-span-all-restyle-003.html [ Failure ] crbug.com/626703 external/wpt/css/css-multicol/multicol-span-all-restyle-003.html [ Failure ]
crbug.com/626703 external/wpt/css/css-scoping/slotted-placeholder.html [ Failure ]
crbug.com/626703 external/wpt/css/filter-effects/filter-grayscale-003.html [ Failure ] crbug.com/626703 external/wpt/css/filter-effects/filter-grayscale-003.html [ Failure ]
crbug.com/626703 external/wpt/css/filter-effects/filter-grayscale-002.html [ Failure ] crbug.com/626703 external/wpt/css/filter-effects/filter-grayscale-002.html [ Failure ]
crbug.com/626703 external/wpt/css/filter-effects/filter-external-002-test.html [ Failure ] crbug.com/626703 external/wpt/css/filter-effects/filter-external-002-test.html [ Failure ]
......
...@@ -15,7 +15,7 @@ PASS Should be a valid selector: '::slotted([attr]:hover)' ...@@ -15,7 +15,7 @@ PASS Should be a valid selector: '::slotted([attr]:hover)'
PASS Should be a valid selector: '::slotted(:not(.a))' PASS Should be a valid selector: '::slotted(:not(.a))'
PASS Should be a valid selector: '::slotted(*)::before' PASS Should be a valid selector: '::slotted(*)::before'
PASS Should be a valid selector: '::slotted(*)::after' PASS Should be a valid selector: '::slotted(*)::after'
FAIL Should be a valid selector: '::slotted(*)::placeholder' assert_true: expected true got false PASS Should be a valid selector: '::slotted(*)::placeholder'
FAIL Should be a valid selector: '::slotted(*)::marker' assert_true: expected true got false FAIL Should be a valid selector: '::slotted(*)::marker' assert_true: expected true got false
PASS Should be an invalid selector: '::slotted(*)::first-line' PASS Should be an invalid selector: '::slotted(*)::first-line'
PASS Should be an invalid selector: '::slotted(*)::first-letter' PASS Should be an invalid selector: '::slotted(*)::first-letter'
......
...@@ -1036,6 +1036,12 @@ bool CSSSelector::MatchesPseudoElement() const { ...@@ -1036,6 +1036,12 @@ bool CSSSelector::MatchesPseudoElement() const {
return false; return false;
} }
bool CSSSelector::IsTreeAbidingPseudoElement() const {
return Match() == CSSSelector::kPseudoElement &&
(GetPseudoType() == kPseudoBefore || GetPseudoType() == kPseudoAfter ||
GetPseudoType() == kPseudoPlaceholder);
}
template <typename Functor> template <typename Functor>
static bool ForAnyInTagHistory(const Functor& functor, static bool ForAnyInTagHistory(const Functor& functor,
const CSSSelector& selector) { const CSSSelector& selector) {
......
...@@ -380,6 +380,7 @@ class CORE_EXPORT CSSSelector { ...@@ -380,6 +380,7 @@ class CORE_EXPORT CSSSelector {
} }
bool MatchesPseudoElement() const; bool MatchesPseudoElement() const;
bool IsTreeAbidingPseudoElement() const;
bool HasContentPseudo() const; bool HasContentPseudo() const;
bool HasSlottedPseudo() const; bool HasSlottedPseudo() const;
......
...@@ -96,6 +96,9 @@ class CORE_EXPORT CSSParserSelector { ...@@ -96,6 +96,9 @@ class CORE_EXPORT CSSParserSelector {
CSSSelector::PseudoType GetPseudoType() const { CSSSelector::PseudoType GetPseudoType() const {
return selector_->GetPseudoType(); return selector_->GetPseudoType();
} }
bool IsTreeAbidingPseudoElement() const {
return selector_->IsTreeAbidingPseudoElement();
}
const CSSSelectorList* SelectorList() const { const CSSSelectorList* SelectorList() const {
return selector_->SelectorList(); return selector_->SelectorList();
} }
......
...@@ -266,12 +266,8 @@ bool IsSimpleSelectorValidAfterPseudoElement( ...@@ -266,12 +266,8 @@ bool IsSimpleSelectorValidAfterPseudoElement(
return true; return true;
if (compound_pseudo_element == CSSSelector::kPseudoContent) if (compound_pseudo_element == CSSSelector::kPseudoContent)
return simple_selector.Match() != CSSSelector::kPseudoElement; return simple_selector.Match() != CSSSelector::kPseudoElement;
if (compound_pseudo_element == CSSSelector::kPseudoSlotted) { if (compound_pseudo_element == CSSSelector::kPseudoSlotted)
return simple_selector.Match() == CSSSelector::kPseudoElement && return simple_selector.IsTreeAbidingPseudoElement();
(simple_selector.GetPseudoType() == CSSSelector::kPseudoBefore ||
simple_selector.GetPseudoType() == CSSSelector::kPseudoAfter);
}
if (simple_selector.Match() != CSSSelector::kPseudoClass) if (simple_selector.Match() != CSSSelector::kPseudoClass)
return false; return false;
CSSSelector::PseudoType pseudo = simple_selector.GetPseudoType(); CSSSelector::PseudoType pseudo = simple_selector.GetPseudoType();
...@@ -917,12 +913,16 @@ CSSSelectorParser::SplitCompoundAtImplicitShadowCrossingCombinator( ...@@ -917,12 +913,16 @@ CSSSelectorParser::SplitCompoundAtImplicitShadowCrossingCombinator(
if (!split_after || !split_after->TagHistory()) if (!split_after || !split_after->TagHistory())
return compound_selector; return compound_selector;
std::unique_ptr<CSSParserSelector> second_compound = std::unique_ptr<CSSParserSelector> remaining =
split_after->ReleaseTagHistory(); split_after->ReleaseTagHistory();
second_compound->AppendTagHistory( CSSSelector::RelationType relation =
second_compound->GetImplicitShadowCombinatorForMatching(), remaining->GetImplicitShadowCombinatorForMatching();
std::move(compound_selector)); // We might need to split the compound twice since ::placeholder is allowed
return second_compound; // after ::slotted and they both need an implicit combinator for matching.
remaining =
SplitCompoundAtImplicitShadowCrossingCombinator(std::move(remaining));
remaining->AppendTagHistory(relation, std::move(compound_selector));
return remaining;
} }
void CSSSelectorParser::RecordUsageAndDeprecations( void CSSSelectorParser::RecordUsageAndDeprecations(
......
...@@ -700,4 +700,75 @@ TEST(CSSSelectorParserTest, UseCountShadowPseudo) { ...@@ -700,4 +700,75 @@ TEST(CSSSelectorParserTest, UseCountShadowPseudo) {
WebFeature::kCSSSelectorWebkitUnknownPseudo); WebFeature::kCSSSelectorWebkitUnknownPseudo);
} }
TEST(CSSSelectorParserTest, ImplicitShadowCrossingCombinators) {
struct ShadowCombinatorTest {
const char* input;
std::vector<std::pair<AtomicString, CSSSelector::RelationType>> expectation;
};
const ShadowCombinatorTest test_cases[] = {
{
"*::placeholder",
{
{"placeholder", CSSSelector::kShadowPseudo},
{g_null_atom, CSSSelector::kSubSelector},
},
},
{
"div::slotted(*)",
{
{"slotted", CSSSelector::kShadowSlot},
{"div", CSSSelector::kSubSelector},
},
},
{
"::slotted(*)::placeholder",
{
{"placeholder", CSSSelector::kShadowPseudo},
{"slotted", CSSSelector::kShadowSlot},
{g_null_atom, CSSSelector::kSubSelector},
},
},
{
"span::part(my-part)",
{
{"part", CSSSelector::kShadowPart},
{"span", CSSSelector::kSubSelector},
},
},
{
"video::-webkit-media-controls",
{
{"-webkit-media-controls", CSSSelector::kShadowPseudo},
{"video", CSSSelector::kSubSelector},
},
},
};
CSSParserContext* context = CSSParserContext::Create(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
StyleSheetContents* sheet = StyleSheetContents::Create(context);
for (auto test_case : test_cases) {
SCOPED_TRACE(test_case.input);
CSSTokenizer tokenizer(test_case.input);
const auto tokens = tokenizer.TokenizeToEOF();
CSSParserTokenRange range(tokens);
CSSSelectorList list =
CSSSelectorParser::ParseSelector(range, context, sheet);
EXPECT_TRUE(list.IsValid());
const CSSSelector* selector = list.First();
for (auto sub_expectation : test_case.expectation) {
ASSERT_TRUE(selector);
AtomicString selector_value = selector->Match() == CSSSelector::kTag
? selector->TagQName().LocalName()
: selector->Value();
EXPECT_EQ(sub_expectation.first, selector_value);
EXPECT_EQ(sub_expectation.second, selector->Relation());
selector = selector->TagHistory();
}
EXPECT_FALSE(selector);
}
}
} // namespace blink } // namespace blink
...@@ -239,12 +239,44 @@ static void MatchHostAndCustomElementRules(const Element& element, ...@@ -239,12 +239,44 @@ static void MatchHostAndCustomElementRules(const Element& element,
collector.FinishAddingAuthorRulesForTreeScope(); collector.FinishAddingAuthorRulesForTreeScope();
} }
static void MatchSlottedRules(const Element&, ElementRuleCollector&);
static void MatchSlottedRulesForUAHost(const Element& element,
ElementRuleCollector& collector) {
if (element.ShadowPseudoId() != "-webkit-input-placeholder")
return;
// We allow ::placeholder pseudo element after ::slotted(). Since we are
// matching such pseudo elements starting from inside the UA shadow DOM of
// the element having the placeholder, we need to match ::slotted rules from
// the scopes to which the placeholder's host element may be slotted.
//
// Example:
//
// <div id=host>
// <:shadow-root>
// <style>::slotted(input)::placeholder { color: green }</style>
// <slot />
// </:shadow-root>
// <input placeholder="PLACEHOLDER-TEXT">
// <:ua-shadow-root>
// ... <placeholder>PLACEHOLDER-TEXT</placeholder> ...
// </:ua-shadow-root>
// </input>
// </div>
//
// Here we need to match the ::slotted rule from the #host shadow tree where
// the input is slotted on the placeholder element.
DCHECK(element.OwnerShadowHost());
MatchSlottedRules(*element.OwnerShadowHost(), collector);
}
// Matches `::slotted` selectors. It matches rules in the element's slot's // Matches `::slotted` selectors. It matches rules in the element's slot's
// scope. If that slot is itself slotted it will match rules in the slot's // scope. If that slot is itself slotted it will match rules in the slot's
// slot's scope and so on. The result is that it considers a chain of scopes // slot's scope and so on. The result is that it considers a chain of scopes
// descending from the element's own scope. // descending from the element's own scope.
static void MatchSlottedRules(const Element& element, static void MatchSlottedRules(const Element& element,
ElementRuleCollector& collector) { ElementRuleCollector& collector) {
MatchSlottedRulesForUAHost(element, collector);
HTMLSlotElement* slot = element.AssignedSlot(); HTMLSlotElement* slot = element.AssignedSlot();
if (!slot) if (!slot)
return; return;
......
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