Commit acea70b2 authored by evliu's avatar evliu Committed by Commit Bot

[WebVTT] Improvements for matching inline VTT selectors

This CL separates the selector matching flow for VTT selectors.
A side effect of this change is that |*::cue will not match, which is
consistent with how it is treated for author stylesheets as well as
inline & author stylesheets in Safari.

Bug: 1016375
Change-Id: Ic8ea2bfa198b7d0f2b3d9d4c7a01266f7fd054d0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1883134
Commit-Queue: Evan Liu <evliu@google.com>
Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/master@{#715564}
parent f77d2ef6
......@@ -94,17 +94,47 @@ static bool MatchesMultiSelectFocusPseudoClass(const Element& element) {
IsFrameFocused(element);
}
static bool MatchesTagName(
static bool MatchesTagName(const Element& element,
const QualifiedName& tag_q_name) {
if (tag_q_name == AnyQName())
return true;
const AtomicString& local_name = tag_q_name.LocalName();
if (local_name != CSSSelector::UniversalSelectorAtom() &&
local_name != element.localName()) {
if (element.IsHTMLElement() || !element.GetDocument().IsHTMLDocument())
return false;
// Non-html elements in html documents are normalized to their camel-cased
// version during parsing if applicable. Yet, type selectors are lower-cased
// for selectors in html documents. Compare the upper case converted names
// instead to allow matching SVG elements like foreignObject.
if (element.TagQName().LocalNameUpper() != tag_q_name.LocalNameUpper())
return false;
}
const AtomicString& namespace_uri = tag_q_name.NamespaceURI();
return namespace_uri == g_star_atom ||
namespace_uri == element.namespaceURI();
}
static bool MatchesTagNameForVTT(
const Element& element,
const QualifiedName& tag_q_name,
const SelectorChecker::SelectorCheckingContext& context) {
if (tag_q_name == AnyQName())
return true;
const AtomicString& local_name = tag_q_name.LocalName();
// The originating element for the cues has no explicit name.
if (!context.in_rightmost_compound && !local_name.IsEmpty() &&
local_name != CSSSelector::UniversalSelectorAtom()) {
return false;
}
if (local_name != CSSSelector::UniversalSelectorAtom() &&
local_name != element.localName()) {
if (element.IsHTMLElement() || !element.GetDocument().IsHTMLDocument())
return false;
// Non-html elements in html documents are normalized to their camel-cased
// version during parsing if applicable. Yet, type selectors are lower-cased
// for selectors in html documents. Compare the upper case converted names
......@@ -112,13 +142,9 @@ static bool MatchesTagName(
if (element.TagQName().LocalNameUpper() != tag_q_name.LocalNameUpper())
return false;
}
const AtomicString& namespace_uri = tag_q_name.NamespaceURI();
if (namespace_uri == g_star_atom)
return true;
// VTT style sheets should apply to a hypothetical document with no namespace
if (context.is_from_vtt)
return namespace_uri.IsEmpty();
return namespace_uri == element.namespaceURI();
return namespace_uri == g_star_atom || namespace_uri.IsEmpty();
}
static Element* ParentElement(
......@@ -228,12 +254,10 @@ bool SelectorChecker::MatchVTTBlockSelector(
const SelectorCheckingContext& context,
MatchResult& result) const {
DCHECK(context.selector);
if (context.selector->IsLastInTagHistory() ||
context.selector->TagHistory()->Specificity() != 0) {
if (context.selector->IsLastInTagHistory())
return false;
}
return MatchSelector(context, result) == kSelectorMatches;
return MatchSelectorForVTT(context, result) == kSelectorMatches;
}
// Recursive check of selectors and combinators
......@@ -281,6 +305,44 @@ SelectorChecker::MatchStatus SelectorChecker::MatchSelector(
return match;
}
SelectorChecker::MatchStatus SelectorChecker::MatchSelectorForVTT(
const SelectorCheckingContext& context,
MatchResult& result) const {
MatchResult sub_result;
if (!CheckOneForVTT(context, sub_result))
return kSelectorFailsLocally;
if (sub_result.dynamic_pseudo != kPseudoIdNone)
result.dynamic_pseudo = sub_result.dynamic_pseudo;
if (context.selector->IsLastInTagHistory()) {
if (ScopeContainsLastMatchedElement(context)) {
result.specificity += sub_result.specificity;
return kSelectorMatches;
}
return kSelectorFailsLocally;
}
MatchStatus match;
if (context.selector->Relation() != CSSSelector::kSubSelector) {
if (NextSelectorExceedsScope(context))
return kSelectorFailsCompletely;
if (context.pseudo_id != kPseudoIdNone &&
context.pseudo_id != result.dynamic_pseudo)
return kSelectorFailsCompletely;
base::AutoReset<PseudoId> dynamic_pseudo_scope(&result.dynamic_pseudo,
kPseudoIdNone);
match = MatchForRelationForVTT(context, result);
} else {
match = MatchForSubSelectorForVTT(context, result);
}
if (match == kSelectorMatches)
result.specificity += sub_result.specificity;
return match;
}
static inline SelectorChecker::SelectorCheckingContext
PrepareNextContextForRelation(
const SelectorChecker::SelectorCheckingContext& context) {
......@@ -316,6 +378,14 @@ SelectorChecker::MatchStatus SelectorChecker::MatchForSubSelector(
return MatchSelector(next_context, result);
}
SelectorChecker::MatchStatus SelectorChecker::MatchForSubSelectorForVTT(
const SelectorCheckingContext& context,
MatchResult& result) const {
SelectorCheckingContext next_context = PrepareNextContextForRelation(context);
next_context.is_sub_selector = true;
return MatchSelectorForVTT(next_context, result);
}
static inline bool IsV0ShadowRoot(const Node* node) {
auto* shadow_root = DynamicTo<ShadowRoot>(node);
return shadow_root && shadow_root->GetType() == ShadowRootType::V0;
......@@ -362,14 +432,6 @@ SelectorChecker::MatchStatus SelectorChecker::MatchForRelation(
next_context.previous_element = context.element;
next_context.pseudo_id = kPseudoIdNone;
// Rules that come from a WebVTT STYLE block apply to a hypothetical
// document with a single empty element with no explicit name, no namespace,
// no attribute, no classes, no IDs, and unknown primary language that acts
// as the originating element for the cue pseudo-elements. This element
// must not be generally selectable.
if (context.is_from_vtt && relation != CSSSelector::kShadowPseudo)
return kSelectorFailsCompletely;
switch (relation) {
case CSSSelector::kShadowDeepAsDescendant:
Deprecation::CountDeprecation(context.element->GetDocument(),
......@@ -565,6 +627,49 @@ SelectorChecker::MatchStatus SelectorChecker::MatchForRelation(
return kSelectorFailsCompletely;
}
SelectorChecker::MatchStatus SelectorChecker::MatchForRelationForVTT(
const SelectorCheckingContext& context,
MatchResult& result) const {
SelectorCheckingContext next_context = PrepareNextContextForRelation(context);
CSSSelector::RelationType relation = context.selector->Relation();
// Rules that come from a WebVTT STYLE block apply to a hypothetical
// document with a single empty element with no explicit name, no namespace,
// no attribute, no classes, no IDs, and unknown primary language that acts
// as the originating element for the cue pseudo-elements. This element
// must not be generally selectable.
if (relation != CSSSelector::kShadowPseudo)
return kSelectorFailsCompletely;
if (!context.is_sub_selector)
next_context.visited_match_type = kVisitedMatchDisabled;
next_context.in_rightmost_compound = false;
next_context.is_sub_selector = false;
next_context.previous_element = context.element;
next_context.pseudo_id = kPseudoIdNone;
DCHECK(mode_ == kQueryingRules ||
context.selector->GetPseudoType() != CSSSelector::kPseudoShadow);
if (context.selector->GetPseudoType() == CSSSelector::kPseudoShadow) {
UseCounter::Count(context.element->GetDocument(),
WebFeature::kPseudoShadowInStaticProfile);
}
// If we're in the same tree-scope as the scoping element, then following
// a shadow descendant combinator would escape that and thus the scope.
if (context.scope && context.scope->OwnerShadowHost() &&
context.scope->OwnerShadowHost()->GetTreeScope() ==
context.element->GetTreeScope())
return kSelectorFailsCompletely;
Element* shadow_host = context.element->OwnerShadowHost();
if (!shadow_host)
return kSelectorFailsCompletely;
next_context.element = shadow_host;
return MatchSelectorForVTT(next_context, result);
}
SelectorChecker::MatchStatus SelectorChecker::MatchForPseudoContent(
const SelectorCheckingContext& context,
const Element& element,
......@@ -730,7 +835,7 @@ bool SelectorChecker::CheckOne(const SelectorCheckingContext& context,
switch (selector.Match()) {
case CSSSelector::kTag:
return MatchesTagName(element, selector.TagQName(), context);
return MatchesTagName(element, selector.TagQName());
case CSSSelector::kClass:
return element.HasClass() &&
element.ClassNames().Contains(selector.Value());
......@@ -759,6 +864,35 @@ bool SelectorChecker::CheckOne(const SelectorCheckingContext& context,
}
}
bool SelectorChecker::CheckOneForVTT(const SelectorCheckingContext& context,
MatchResult& result) const {
DCHECK(context.element);
Element& element = *context.element;
DCHECK(context.selector);
const CSSSelector& selector = *context.selector;
switch (selector.Match()) {
case CSSSelector::kTag:
return MatchesTagNameForVTT(element, selector.TagQName(), context);
// Attribute selectors
case CSSSelector::kAttributeExact:
case CSSSelector::kAttributeSet:
case CSSSelector::kAttributeHyphen:
case CSSSelector::kAttributeList:
case CSSSelector::kAttributeContain:
case CSSSelector::kAttributeBegin:
case CSSSelector::kAttributeEnd:
return AnyAttributeMatches(element, selector.Match(), selector);
case CSSSelector::kPseudoClass:
return CheckPseudoClassForVTT(context, result);
case CSSSelector::kPseudoElement:
return CheckPseudoElementForVTT(context, result);
default:
return false;
}
}
bool SelectorChecker::CheckPseudoNot(const SelectorCheckingContext& context,
MatchResult& result) const {
const CSSSelector& selector = *context.selector;
......@@ -785,6 +919,32 @@ bool SelectorChecker::CheckPseudoNot(const SelectorCheckingContext& context,
return false;
}
bool SelectorChecker::CheckPseudoNotForVTT(
const SelectorCheckingContext& context,
MatchResult& result) const {
const CSSSelector& selector = *context.selector;
SelectorCheckingContext sub_context(context);
sub_context.is_sub_selector = true;
DCHECK(selector.SelectorList());
for (sub_context.selector = selector.SelectorList()->First();
sub_context.selector;
sub_context.selector = sub_context.selector->TagHistory()) {
// :not cannot nest. I don't really know why this is a
// restriction in CSS3, but it is, so let's honor it.
// the parser enforces that this never occurs
DCHECK_NE(sub_context.selector->GetPseudoType(), CSSSelector::kPseudoNot);
// We select between :visited and :link when applying. We don't know which
// one applied (or not) yet.
if (sub_context.selector->GetPseudoType() == CSSSelector::kPseudoVisited ||
(sub_context.selector->GetPseudoType() == CSSSelector::kPseudoLink &&
sub_context.visited_match_type == kVisitedMatchEnabled))
return true;
if (!CheckOneForVTT(sub_context, result))
return true;
}
return false;
}
bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context,
MatchResult& result) const {
Element& element = *context.element;
......@@ -1179,6 +1339,37 @@ bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context,
return false;
}
bool SelectorChecker::CheckPseudoClassForVTT(
const SelectorCheckingContext& context,
MatchResult& result) const {
const CSSSelector& selector = *context.selector;
switch (selector.GetPseudoType()) {
case CSSSelector::kPseudoNot:
return CheckPseudoNotForVTT(context, result);
case CSSSelector::kPseudoAny: {
SelectorCheckingContext sub_context(context);
sub_context.is_sub_selector = true;
DCHECK(selector.SelectorList());
for (sub_context.selector = selector.SelectorList()->First();
sub_context.selector; sub_context.selector = CSSSelectorList::Next(
*sub_context.selector)) {
MatchResult sub_result;
if (MatchSelectorForVTT(sub_context, sub_result) == kSelectorMatches)
return true;
}
} break;
case CSSSelector::kPseudoHostContext:
FALLTHROUGH;
case CSSSelector::kPseudoHost:
return false;
default:
return CheckPseudoClass(context, result);
break;
}
return false;
}
bool SelectorChecker::CheckPseudoElement(const SelectorCheckingContext& context,
MatchResult& result) const {
const CSSSelector& selector = *context.selector;
......@@ -1252,6 +1443,38 @@ bool SelectorChecker::CheckPseudoElement(const SelectorCheckingContext& context,
}
}
bool SelectorChecker::CheckPseudoElementForVTT(
const SelectorCheckingContext& context,
MatchResult& result) const {
const CSSSelector& selector = *context.selector;
Element& element = *context.element;
switch (selector.GetPseudoType()) {
case CSSSelector::kPseudoCue: {
SelectorCheckingContext sub_context(context);
sub_context.is_sub_selector = true;
sub_context.scope = nullptr;
sub_context.treat_shadow_host_as_normal_scope = false;
for (sub_context.selector = selector.SelectorList()->First();
sub_context.selector; sub_context.selector = CSSSelectorList::Next(
*sub_context.selector)) {
MatchResult sub_result;
if (MatchSelectorForVTT(sub_context, sub_result) == kSelectorMatches)
return true;
}
return false;
}
case CSSSelector::kPseudoWebKitCustomElement: {
if (ShadowRoot* root = element.ContainingShadowRoot())
return root->IsUserAgent() &&
element.ShadowPseudoId() == selector.Value();
return false;
}
default:
return false;
}
}
bool SelectorChecker::CheckPseudoHost(const SelectorCheckingContext& context,
MatchResult& result) const {
const CSSSelector& selector = *context.selector;
......
......@@ -158,6 +158,7 @@ class SelectorChecker {
// to by the context are a match. Delegates most of the work to the Check*
// methods below.
bool CheckOne(const SelectorCheckingContext&, MatchResult&) const;
bool CheckOneForVTT(const SelectorCheckingContext&, MatchResult&) const;
enum MatchStatus {
kSelectorMatches,
......@@ -184,10 +185,16 @@ class SelectorChecker {
// to try (e.g. same element, parent, sibling) depends on the combinators in
// the selectors.
MatchStatus MatchSelector(const SelectorCheckingContext&, MatchResult&) const;
MatchStatus MatchSelectorForVTT(const SelectorCheckingContext&,
MatchResult&) const;
MatchStatus MatchForSubSelector(const SelectorCheckingContext&,
MatchResult&) const;
MatchStatus MatchForSubSelectorForVTT(const SelectorCheckingContext&,
MatchResult&) const;
MatchStatus MatchForRelation(const SelectorCheckingContext&,
MatchResult&) const;
MatchStatus MatchForRelationForVTT(const SelectorCheckingContext&,
MatchResult&) const;
MatchStatus MatchForPseudoContent(const SelectorCheckingContext&,
const Element&,
MatchResult&) const;
......@@ -197,11 +204,16 @@ class SelectorChecker {
bool MatchVTTBlockSelector(const SelectorCheckingContext& context,
MatchResult& result) const;
bool CheckPseudoClass(const SelectorCheckingContext&, MatchResult&) const;
bool CheckPseudoClassForVTT(const SelectorCheckingContext&,
MatchResult&) const;
bool CheckPseudoElement(const SelectorCheckingContext&, MatchResult&) const;
bool CheckPseudoElementForVTT(const SelectorCheckingContext&,
MatchResult&) const;
bool CheckScrollbarPseudoClass(const SelectorCheckingContext&,
MatchResult&) const;
bool CheckPseudoHost(const SelectorCheckingContext&, MatchResult&) const;
bool CheckPseudoNot(const SelectorCheckingContext&, MatchResult&) const;
bool CheckPseudoNotForVTT(const SelectorCheckingContext&, MatchResult&) const;
Mode mode_;
bool is_ua_rule_;
......
......@@ -2,16 +2,17 @@
<html class="reftest-wait">
<title>Reference for Embedded Style: Selectors</title>
<style>
::cue {
font-size: 11px;
background: lime;
}
::cue(b) {
background: green;
color: green;
}
::cue(i) {
background: lime;
color: lime;
}
::cue {
font-size: 11px;
background: green;
color: green;
}
</style>
<script src="/common/reftest-wait.js"></script>
......
WEBVTT
NOTE
The first five selectors should apply. The rest should not apply because they do
The first six selectors should apply. The rest should not apply because they do
not apply to a hypothetical document with a single empty element with no explicit
name, no namespace, no attribute, no classes, no IDs, and unknown primary language
that acts as the originating element for the cue pseudo-elements.
STYLE
@namespace html url(http://www.w3.org/1999/xhtml);
:not(video)::cue {
background: lime;
}
*|*::cue(b) {
background: green;
}
|*::cue(i) {
color: lime;
color: green;
}
::cue(i) {
background: lime;
background: green;
}
*::cue(b) {
color: green;
......@@ -24,6 +27,9 @@ STYLE
video::cue {
background: red;
}
:not(|*)::cue {
background: red;
}
i {
color: red;
}
......
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