2020-01-18 09:38:21 +01:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2018 - 2024 , Andreas Kling < andreas @ ladybird . org >
2024-10-15 12:00:29 +01:00
* Copyright ( c ) 2021 - 2024 , Sam Atkins < sam @ ladybird . org >
2020-01-18 09:38:21 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 09:38:21 +01:00
*/
2024-12-20 11:32:17 +01:00
# include <LibWeb/CSS/ComputedProperties.h>
2024-08-14 14:06:03 +01:00
# include <LibWeb/CSS/Keyword.h>
2021-07-30 19:31:46 +01:00
# include <LibWeb/CSS/Parser/Parser.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/CSS/SelectorEngine.h>
2024-03-15 18:19:59 +01:00
# include <LibWeb/DOM/Attr.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/DOM/Document.h>
# include <LibWeb/DOM/Element.h>
2024-03-15 18:19:59 +01:00
# include <LibWeb/DOM/NamedNodeMap.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/DOM/Text.h>
2020-08-12 14:46:53 +02:00
# include <LibWeb/HTML/AttributeNames.h>
2022-09-14 13:29:07 +02:00
# include <LibWeb/HTML/HTMLAnchorElement.h>
2023-09-13 17:39:05 +01:00
# include <LibWeb/HTML/HTMLDetailsElement.h>
# include <LibWeb/HTML/HTMLDialogElement.h>
2022-09-30 19:40:19 +01:00
# include <LibWeb/HTML/HTMLFieldSetElement.h>
2025-02-02 20:41:23 +01:00
# include <LibWeb/HTML/HTMLFormElement.h>
2022-02-10 20:52:58 +01:00
# include <LibWeb/HTML/HTMLHtmlElement.h>
2022-02-15 23:50:16 +01:00
# include <LibWeb/HTML/HTMLInputElement.h>
2023-08-01 12:31:41 +01:00
# include <LibWeb/HTML/HTMLMediaElement.h>
2023-03-20 04:35:43 -04:00
# include <LibWeb/HTML/HTMLProgressElement.h>
2022-09-30 19:40:19 +01:00
# include <LibWeb/HTML/HTMLSelectElement.h>
# include <LibWeb/HTML/HTMLTextAreaElement.h>
2023-02-18 15:42:55 +00:00
# include <LibWeb/Infra/Strings.h>
2024-03-15 18:19:59 +01:00
# include <LibWeb/Namespace.h>
2024-11-06 18:07:28 +01:00
# include <LibWeb/SVG/SVGAElement.h>
2019-10-08 15:33:58 +02:00
2020-07-26 20:01:35 +02:00
namespace Web : : SelectorEngine {
2019-10-08 15:33:58 +02:00
2025-01-03 20:39:25 +03:00
static inline bool matches ( CSS : : Selector const & selector , int component_list_index , DOM : : Element const & element , GC : : Ptr < DOM : : Element const > shadow_host , MatchContext & context , GC : : Ptr < DOM : : ParentNode const > scope , SelectorKind selector_kind , GC : : Ptr < DOM : : Element const > anchor = nullptr ) ;
2024-10-27 12:15:19 +01:00
2024-07-23 15:22:28 +02:00
// Upward traversal for descendant (' ') and immediate child combinator ('>')
// If we're starting inside a shadow tree, traversal stops at the nearest shadow host.
// This is an implementation detail of the :host selector. Otherwise we would just traverse up to the document root.
2024-11-15 04:01:23 +13:00
static inline GC : : Ptr < DOM : : Node const > traverse_up ( GC : : Ptr < DOM : : Node const > node , GC : : Ptr < DOM : : Element const > shadow_host )
2024-07-23 15:22:28 +02:00
{
if ( ! node )
return nullptr ;
if ( shadow_host ) {
// NOTE: We only traverse up to the shadow host, not beyond.
if ( node = = shadow_host )
return nullptr ;
return node - > parent_or_shadow_host_element ( ) ;
}
return node - > parent ( ) ;
}
2022-03-20 12:39:11 +01:00
// https://drafts.csswg.org/selectors-4/#the-lang-pseudo
2023-02-18 15:42:55 +00:00
static inline bool matches_lang_pseudo_class ( DOM : : Element const & element , Vector < FlyString > const & languages )
2022-03-20 12:39:11 +01:00
{
2024-10-27 14:58:36 +01:00
auto maybe_element_language = element . lang ( ) ;
if ( ! maybe_element_language . has_value ( ) )
2022-03-20 12:39:11 +01:00
return false ;
2024-10-27 14:58:36 +01:00
auto element_language = maybe_element_language . release_value ( ) ;
2022-03-20 12:39:11 +01:00
// FIXME: This is ad-hoc. Implement a proper language range matching algorithm as recommended by BCP47.
for ( auto const & language : languages ) {
if ( language . is_empty ( ) )
2023-08-16 14:37:47 +01:00
continue ;
2022-03-20 12:39:11 +01:00
if ( language = = " * " sv )
return true ;
2024-10-27 14:58:36 +01:00
if ( ! element_language . contains ( ' - ' ) & & Infra : : is_ascii_case_insensitive_match ( element_language , language ) )
2023-08-16 14:37:47 +01:00
return true ;
2024-10-27 14:58:36 +01:00
auto parts = element_language . split_limit ( ' - ' , 2 ) . release_value_but_fixme_should_propagate_errors ( ) ;
2024-11-02 03:48:19 +13:00
if ( ! parts . is_empty ( ) & & Infra : : is_ascii_case_insensitive_match ( parts [ 0 ] , language ) )
2023-08-16 14:37:47 +01:00
return true ;
2022-03-20 12:39:11 +01:00
}
return false ;
}
2024-07-13 09:19:30 -07:00
// https://drafts.csswg.org/selectors-4/#relational
2025-01-03 20:39:25 +03:00
static inline bool matches_relative_selector ( CSS : : Selector const & selector , size_t compound_index , DOM : : Element const & element , GC : : Ptr < DOM : : Element const > shadow_host , MatchContext & context , GC : : Ref < DOM : : Element const > anchor )
2024-07-13 09:19:30 -07:00
{
2024-10-27 12:15:19 +01:00
if ( compound_index > = selector . compound_selectors ( ) . size ( ) )
2025-01-03 20:39:25 +03:00
return matches ( selector , element , shadow_host , context , { } , { } , SelectorKind : : Relative , anchor ) ;
2024-10-27 12:15:19 +01:00
switch ( selector . compound_selectors ( ) [ compound_index ] . combinator ) {
2024-07-13 09:19:30 -07:00
// Shouldn't be possible because we've parsed relative selectors, which always have a combinator, implicitly or explicitly.
case CSS : : Selector : : Combinator : : None :
VERIFY_NOT_REACHED ( ) ;
case CSS : : Selector : : Combinator : : Descendant : {
bool has = false ;
2024-10-27 12:15:19 +01:00
element . for_each_in_subtree ( [ & ] ( auto const & descendant ) {
2024-07-13 09:19:30 -07:00
if ( ! descendant . is_element ( ) )
return TraversalDecision : : Continue ;
auto const & descendant_element = static_cast < DOM : : Element const & > ( descendant ) ;
2025-01-03 20:39:25 +03:00
if ( matches ( selector , descendant_element , shadow_host , context , { } , { } , SelectorKind : : Relative , anchor ) ) {
2024-07-13 09:19:30 -07:00
has = true ;
return TraversalDecision : : Break ;
}
return TraversalDecision : : Continue ;
} ) ;
return has ;
}
case CSS : : Selector : : Combinator : : ImmediateChild : {
bool has = false ;
2024-10-27 12:15:19 +01:00
element . for_each_child ( [ & ] ( DOM : : Node const & child ) {
2024-07-13 09:19:30 -07:00
if ( ! child . is_element ( ) )
return IterationDecision : : Continue ;
auto const & child_element = static_cast < DOM : : Element const & > ( child ) ;
2025-01-03 20:39:25 +03:00
if ( ! matches ( selector , compound_index , child_element , shadow_host , context , { } , SelectorKind : : Relative , anchor ) )
2024-10-27 12:15:19 +01:00
return IterationDecision : : Continue ;
2025-01-03 20:39:25 +03:00
if ( matches_relative_selector ( selector , compound_index + 1 , child_element , shadow_host , context , anchor ) ) {
2024-07-13 09:19:30 -07:00
has = true ;
return IterationDecision : : Break ;
}
return IterationDecision : : Continue ;
} ) ;
return has ;
}
2024-10-27 12:15:19 +01:00
case CSS : : Selector : : Combinator : : NextSibling : {
2025-02-08 19:09:08 +01:00
if ( context . collect_per_element_selector_involvement_metadata ) {
const_cast < DOM : : Element & > ( * anchor ) . set_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator ( true ) ;
}
2024-10-27 12:15:19 +01:00
auto * sibling = element . next_element_sibling ( ) ;
if ( ! sibling )
return false ;
2025-01-03 20:39:25 +03:00
if ( ! matches ( selector , compound_index , * sibling , shadow_host , context , { } , SelectorKind : : Relative , anchor ) )
2024-10-27 12:15:19 +01:00
return false ;
2025-01-03 20:39:25 +03:00
return matches_relative_selector ( selector , compound_index + 1 , * sibling , shadow_host , context , anchor ) ;
2024-10-27 12:15:19 +01:00
}
2024-07-13 09:19:30 -07:00
case CSS : : Selector : : Combinator : : SubsequentSibling : {
2025-02-08 19:09:08 +01:00
if ( context . collect_per_element_selector_involvement_metadata ) {
const_cast < DOM : : Element & > ( * anchor ) . set_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator ( true ) ;
}
2024-10-27 12:15:19 +01:00
for ( auto const * sibling = element . next_element_sibling ( ) ; sibling ; sibling = sibling - > next_element_sibling ( ) ) {
2025-01-03 20:39:25 +03:00
if ( ! matches ( selector , compound_index , * sibling , shadow_host , context , { } , SelectorKind : : Relative , anchor ) )
2024-10-27 12:15:19 +01:00
continue ;
2025-01-03 20:39:25 +03:00
if ( matches_relative_selector ( selector , compound_index + 1 , * sibling , shadow_host , context , anchor ) )
2024-07-13 09:19:30 -07:00
return true ;
}
return false ;
}
case CSS : : Selector : : Combinator : : Column :
TODO ( ) ;
}
return false ;
}
2024-10-27 12:15:19 +01:00
// https://drafts.csswg.org/selectors-4/#relational
2025-01-03 20:39:25 +03:00
static inline bool matches_has_pseudo_class ( CSS : : Selector const & selector , DOM : : Element const & anchor , GC : : Ptr < DOM : : Element const > shadow_host , MatchContext & context )
2024-10-27 12:15:19 +01:00
{
2025-01-03 20:39:25 +03:00
return matches_relative_selector ( selector , 0 , anchor , shadow_host , context , anchor ) ;
2024-10-27 12:15:19 +01:00
}
2025-02-02 20:35:29 +01:00
static bool matches_hover_pseudo_class ( DOM : : Element const & element )
2019-10-14 17:54:17 +02:00
{
auto * hovered_node = element . document ( ) . hovered_node ( ) ;
if ( ! hovered_node )
return false ;
if ( & element = = hovered_node )
return true ;
2024-08-25 10:14:24 +02:00
return element . is_shadow_including_ancestor_of ( * hovered_node ) ;
2019-10-14 17:54:17 +02:00
}
2023-03-20 04:35:43 -04:00
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-indeterminate
static inline bool matches_indeterminate_pseudo_class ( DOM : : Element const & element )
{
// The :indeterminate pseudo-class must match any element falling into one of the following categories:
// - input elements whose type attribute is in the Checkbox state and whose indeterminate IDL attribute is set to true
// FIXME: - input elements whose type attribute is in the Radio Button state and whose radio button group contains no input elements whose checkedness state is true.
if ( is < HTML : : HTMLInputElement > ( element ) ) {
auto const & input_element = static_cast < HTML : : HTMLInputElement const & > ( element ) ;
switch ( input_element . type_state ( ) ) {
case HTML : : HTMLInputElement : : TypeAttributeState : : Checkbox :
2024-12-11 11:34:41 +09:00
// https://whatpr.org/html-attr-input-switch/9546/semantics-other.html#selector-indeterminate
// input elements whose type attribute is in the Checkbox state, whose switch attribute is not set
return input_element . indeterminate ( ) & & ! element . has_attribute ( HTML : : AttributeNames : : switch_ ) ;
2023-03-20 04:35:43 -04:00
default :
return false ;
}
}
// - progress elements with no value content attribute
if ( is < HTML : : HTMLProgressElement > ( element ) ) {
return ! element . has_attribute ( HTML : : AttributeNames : : value ) ;
}
return false ;
}
2025-01-03 20:39:25 +03:00
static inline Web : : DOM : : Attr const * get_optionally_namespaced_attribute ( CSS : : Selector : : SimpleSelector : : Attribute const & attribute , GC : : Ptr < CSS : : CSSStyleSheet const > style_sheet_for_rule , DOM : : Element const & element )
2021-07-12 14:58:03 +01:00
{
2024-11-27 20:24:29 +02:00
auto const & qualified_name = attribute . qualified_name ;
auto const & attribute_name = qualified_name . name . name ;
auto const & namespace_type = qualified_name . namespace_type ;
if ( element . namespace_uri ( ) = = Namespace : : HTML ) {
if ( namespace_type = = CSS : : Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Named ) {
return nullptr ;
}
return element . attributes ( ) - > get_attribute ( attribute_name ) ;
}
switch ( namespace_type ) {
// "In keeping with the Namespaces in the XML recommendation, default namespaces do not apply to attributes,
// therefore attribute selectors without a namespace component apply only to attributes that have no namespace (equivalent to "|attr")"
case CSS : : Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Default :
case CSS : : Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : None :
return element . attributes ( ) - > get_attribute ( attribute_name ) ;
case CSS : : Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Any :
return element . attributes ( ) - > get_attribute_namespace_agnostic ( attribute_name ) ;
case CSS : : Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Named :
2025-01-03 20:39:25 +03:00
if ( ! style_sheet_for_rule )
2024-11-27 20:24:29 +02:00
return nullptr ;
auto const & selector_namespace = style_sheet_for_rule - > namespace_uri ( qualified_name . namespace_ ) ;
if ( ! selector_namespace . has_value ( ) )
return nullptr ;
return element . attributes ( ) - > get_attribute_ns ( selector_namespace , attribute_name ) ;
}
VERIFY_NOT_REACHED ( ) ;
}
2023-08-08 16:19:20 +01:00
2025-01-03 20:39:25 +03:00
static inline bool matches_attribute ( CSS : : Selector : : SimpleSelector : : Attribute const & attribute , [[maybe_unused]] GC : : Ptr < CSS : : CSSStyleSheet const > style_sheet_for_rule , DOM : : Element const & element )
2024-11-27 20:24:29 +02:00
{
2023-11-04 22:45:04 +01:00
auto const & attribute_name = attribute . qualified_name . name . name ;
2023-08-08 16:19:20 +01:00
2024-11-27 20:24:29 +02:00
auto const * attr = get_optionally_namespaced_attribute ( attribute , style_sheet_for_rule , element ) ;
2024-03-15 18:19:59 +01:00
2022-03-29 18:01:36 +02:00
if ( attribute . match_type = = CSS : : Selector : : SimpleSelector : : Attribute : : MatchType : : HasAttribute ) {
// Early way out in case of an attribute existence selector.
2024-03-15 18:19:59 +01:00
return attr ! = nullptr ;
2022-03-29 18:01:36 +02:00
}
2024-03-15 18:19:59 +01:00
if ( ! attr )
return false ;
2024-08-18 21:17:08 +01:00
auto case_sensitivity = [ & ] ( CSS : : Selector : : SimpleSelector : : Attribute : : CaseType case_type ) {
switch ( case_type ) {
case CSS : : Selector : : SimpleSelector : : Attribute : : CaseType : : CaseInsensitiveMatch :
return CaseSensitivity : : CaseInsensitive ;
case CSS : : Selector : : SimpleSelector : : Attribute : : CaseType : : CaseSensitiveMatch :
return CaseSensitivity : : CaseSensitive ;
case CSS : : Selector : : SimpleSelector : : Attribute : : CaseType : : DefaultMatch :
// See: https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
if ( element . document ( ) . is_html_document ( )
& & element . namespace_uri ( ) = = Namespace : : HTML
& & attribute_name . is_one_of (
HTML : : AttributeNames : : accept , HTML : : AttributeNames : : accept_charset , HTML : : AttributeNames : : align ,
HTML : : AttributeNames : : alink , HTML : : AttributeNames : : axis , HTML : : AttributeNames : : bgcolor , HTML : : AttributeNames : : charset ,
HTML : : AttributeNames : : checked , HTML : : AttributeNames : : clear , HTML : : AttributeNames : : codetype , HTML : : AttributeNames : : color ,
HTML : : AttributeNames : : compact , HTML : : AttributeNames : : declare , HTML : : AttributeNames : : defer , HTML : : AttributeNames : : dir ,
HTML : : AttributeNames : : direction , HTML : : AttributeNames : : disabled , HTML : : AttributeNames : : enctype , HTML : : AttributeNames : : face ,
HTML : : AttributeNames : : frame , HTML : : AttributeNames : : hreflang , HTML : : AttributeNames : : http_equiv , HTML : : AttributeNames : : lang ,
HTML : : AttributeNames : : language , HTML : : AttributeNames : : link , HTML : : AttributeNames : : media , HTML : : AttributeNames : : method ,
HTML : : AttributeNames : : multiple , HTML : : AttributeNames : : nohref , HTML : : AttributeNames : : noresize , HTML : : AttributeNames : : noshade ,
HTML : : AttributeNames : : nowrap , HTML : : AttributeNames : : readonly , HTML : : AttributeNames : : rel , HTML : : AttributeNames : : rev ,
HTML : : AttributeNames : : rules , HTML : : AttributeNames : : scope , HTML : : AttributeNames : : scrolling , HTML : : AttributeNames : : selected ,
HTML : : AttributeNames : : shape , HTML : : AttributeNames : : target , HTML : : AttributeNames : : text , HTML : : AttributeNames : : type ,
HTML : : AttributeNames : : valign , HTML : : AttributeNames : : valuetype , HTML : : AttributeNames : : vlink ) ) {
return CaseSensitivity : : CaseInsensitive ;
}
return CaseSensitivity : : CaseSensitive ;
}
VERIFY_NOT_REACHED ( ) ;
} ( attribute . case_type ) ;
auto case_insensitive_match = case_sensitivity = = CaseSensitivity : : CaseInsensitive ;
2022-03-29 18:01:36 +02:00
switch ( attribute . match_type ) {
2021-07-12 14:58:03 +01:00
case CSS : : Selector : : SimpleSelector : : Attribute : : MatchType : : ExactValueMatch :
2022-03-29 18:01:36 +02:00
return case_insensitive_match
2024-03-15 18:19:59 +01:00
? Infra : : is_ascii_case_insensitive_match ( attr - > value ( ) , attribute . value )
: attr - > value ( ) = = attribute . value ;
2022-03-29 18:01:36 +02:00
case CSS : : Selector : : SimpleSelector : : Attribute : : MatchType : : ContainsWord : {
if ( attribute . value . is_empty ( ) ) {
// This selector is always false is match value is empty.
return false ;
}
2024-03-15 18:19:59 +01:00
auto const & attribute_value = attr - > value ( ) ;
2023-12-24 02:51:02 +01:00
auto const view = attribute_value . bytes_as_string_view ( ) . split_view ( ' ' ) ;
2022-03-29 18:01:36 +02:00
auto const size = view . size ( ) ;
for ( size_t i = 0 ; i < size ; + + i ) {
auto const value = view . at ( i ) ;
if ( case_insensitive_match
2023-02-18 15:42:55 +00:00
? Infra : : is_ascii_case_insensitive_match ( value , attribute . value )
2022-03-29 18:01:36 +02:00
: value = = attribute . value ) {
return true ;
}
}
return false ;
}
2021-07-12 14:58:03 +01:00
case CSS : : Selector : : SimpleSelector : : Attribute : : MatchType : : ContainsString :
2022-03-29 17:46:32 +02:00
return ! attribute . value . is_empty ( )
2024-03-15 18:19:59 +01:00
& & attr - > value ( ) . contains ( attribute . value , case_sensitivity ) ;
2021-07-28 12:34:05 +01:00
case CSS : : Selector : : SimpleSelector : : Attribute : : MatchType : : StartsWithSegment : {
2024-03-15 18:19:59 +01:00
auto const & element_attr_value = attr - > value ( ) ;
2022-03-29 17:46:32 +02:00
if ( element_attr_value . is_empty ( ) ) {
// If the attribute value on element is empty, the selector is true
// if the match value is also empty and false otherwise.
return attribute . value . is_empty ( ) ;
}
if ( attribute . value . is_empty ( ) ) {
return false ;
}
2023-11-04 22:45:04 +01:00
auto segments = element_attr_value . bytes_as_string_view ( ) . split_view ( ' - ' ) ;
2022-03-29 18:01:36 +02:00
return case_insensitive_match
2023-02-18 15:42:55 +00:00
? Infra : : is_ascii_case_insensitive_match ( segments . first ( ) , attribute . value )
2022-03-29 18:01:36 +02:00
: segments . first ( ) = = attribute . value ;
2021-07-28 12:34:05 +01:00
}
2021-07-12 14:58:03 +01:00
case CSS : : Selector : : SimpleSelector : : Attribute : : MatchType : : StartsWithString :
2022-03-29 17:46:32 +02:00
return ! attribute . value . is_empty ( )
2024-03-15 18:19:59 +01:00
& & attr - > value ( ) . bytes_as_string_view ( ) . starts_with ( attribute . value , case_sensitivity ) ;
2021-07-12 14:58:03 +01:00
case CSS : : Selector : : SimpleSelector : : Attribute : : MatchType : : EndsWithString :
2022-03-29 17:46:32 +02:00
return ! attribute . value . is_empty ( )
2024-03-15 18:19:59 +01:00
& & attr - > value ( ) . bytes_as_string_view ( ) . ends_with ( attribute . value , case_sensitivity ) ;
2022-03-29 18:01:36 +02:00
default :
break ;
2021-07-12 14:58:03 +01:00
}
return false ;
}
2022-02-17 22:43:22 +01:00
static inline DOM : : Element const * previous_sibling_with_same_tag_name ( DOM : : Element const & element )
{
for ( auto const * sibling = element . previous_element_sibling ( ) ; sibling ; sibling = sibling - > previous_element_sibling ( ) ) {
if ( sibling - > tag_name ( ) = = element . tag_name ( ) )
return sibling ;
}
return nullptr ;
}
static inline DOM : : Element const * next_sibling_with_same_tag_name ( DOM : : Element const & element )
{
for ( auto const * sibling = element . next_element_sibling ( ) ; sibling ; sibling = sibling - > next_element_sibling ( ) ) {
if ( sibling - > tag_name ( ) = = element . tag_name ( ) )
return sibling ;
}
return nullptr ;
}
2024-11-24 21:15:56 +02:00
/// Returns true if this selector should be blocked from matching against the shadow host from within a shadow tree.
/// Only :host pseudo-class is allowed to match the shadow host from within shadow tree, all other selectors (including other pseudo-classes) are blocked.
/// Compound selectors (:has(), :is(), :where()), nesting, and pseudo-elements are allowed as they may contain or be contained within :host.
static inline bool should_block_shadow_host_matching ( CSS : : Selector : : SimpleSelector const & component , GC : : Ptr < DOM : : Element const > shadow_host , DOM : : Element const & element )
{
if ( ! shadow_host | | & element ! = shadow_host . ptr ( ) )
return false ;
// From within shadow tree, only :host pseudo-class can match the host element
if ( component . type = = CSS : : Selector : : SimpleSelector : : Type : : PseudoClass ) {
auto const & pseudo_class = component . pseudo_class ( ) ;
return pseudo_class . type ! = CSS : : PseudoClass : : Host
& & pseudo_class . type ! = CSS : : PseudoClass : : Has
& & pseudo_class . type ! = CSS : : PseudoClass : : Is
& & pseudo_class . type ! = CSS : : PseudoClass : : Where ;
}
// Allow nesting and PseudoElement as it may contain :host class
if ( component . type = = CSS : : Selector : : SimpleSelector : : Type : : Nesting | | component . type = = CSS : : Selector : : SimpleSelector : : Type : : PseudoElement )
return false ;
return true ;
}
2023-08-22 16:28:06 +01:00
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-read-write
static bool matches_read_write_pseudo_class ( DOM : : Element const & element )
{
// The :read-write pseudo-class must match any element falling into one of the following categories,
// which for the purposes of Selectors are thus considered user-alterable: [SELECTORS]
// - input elements to which the readonly attribute applies, and that are mutable
// (i.e. that do not have the readonly attribute specified and that are not disabled)
if ( is < HTML : : HTMLInputElement > ( element ) ) {
auto & input_element = static_cast < HTML : : HTMLInputElement const & > ( element ) ;
if ( input_element . has_attribute ( HTML : : AttributeNames : : readonly ) )
return false ;
if ( ! input_element . enabled ( ) )
return false ;
return true ;
}
// - textarea elements that do not have a readonly attribute, and that are not disabled
if ( is < HTML : : HTMLTextAreaElement > ( element ) ) {
auto & input_element = static_cast < HTML : : HTMLTextAreaElement const & > ( element ) ;
if ( input_element . has_attribute ( HTML : : AttributeNames : : readonly ) )
return false ;
if ( ! input_element . enabled ( ) )
return false ;
return true ;
}
// - elements that are editing hosts or editable and are neither input elements nor textarea elements
2024-12-06 11:41:20 +01:00
return element . is_editable_or_editing_host ( ) ;
2023-08-22 16:28:06 +01:00
}
2025-01-31 15:34:34 +00:00
// https://drafts.csswg.org/selectors-4/#open-state
2023-09-13 17:39:05 +01:00
static bool matches_open_state_pseudo_class ( DOM : : Element const & element , bool open )
{
// The :open pseudo-class represents an element that has both “open” and “closed” states,
// and which is currently in the “open” state.
2025-01-31 15:34:34 +00:00
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-open
// The :open pseudo-class must match any element falling into one of the following categories:
// - details elements that have an open attribute
// - dialog elements that have an open attribute
2023-09-13 17:39:05 +01:00
if ( is < HTML : : HTMLDetailsElement > ( element ) | | is < HTML : : HTMLDialogElement > ( element ) )
return open = = element . has_attribute ( HTML : : AttributeNames : : open ) ;
2025-01-31 15:34:34 +00:00
// - select elements that are a drop-down box and whose drop-down boxes are open
if ( auto const * select = as_if < HTML : : HTMLSelectElement > ( element ) )
return open = = select - > is_open ( ) ;
// - input elements that support a picker and whose pickers are open
if ( auto const * input = as_if < HTML : : HTMLInputElement > ( element ) )
return open = = ( input - > supports_a_picker ( ) & & input - > is_open ( ) ) ;
2023-09-13 17:39:05 +01:00
return false ;
}
2024-07-23 15:22:28 +02:00
// https://drafts.csswg.org/css-scoping/#host-selector
2025-01-03 20:39:25 +03:00
static inline bool matches_host_pseudo_class ( GC : : Ref < DOM : : Element const > element , GC : : Ptr < DOM : : Element const > shadow_host , MatchContext & context , CSS : : SelectorList const & argument_selector_list )
2024-07-23 15:22:28 +02:00
{
// When evaluated in the context of a shadow tree, it matches the shadow tree’ s shadow host if the shadow host,
// in its normal context, matches the selector argument. In any other context, it matches nothing.
if ( ! shadow_host | | element ! = shadow_host )
return false ;
// NOTE: There's either 0 or 1 argument selector, since the syntax is :host or :host(<compound-selector>)
if ( ! argument_selector_list . is_empty ( ) )
2025-01-03 20:39:25 +03:00
return matches ( argument_selector_list . first ( ) , element , nullptr , context ) ;
2024-07-23 15:22:28 +02:00
return true ;
}
2025-01-03 20:39:25 +03:00
static inline bool matches_pseudo_class ( CSS : : Selector : : SimpleSelector : : PseudoClassSelector const & pseudo_class , DOM : : Element const & element , GC : : Ptr < DOM : : Element const > shadow_host , MatchContext & context , GC : : Ptr < DOM : : ParentNode const > scope , SelectorKind selector_kind )
2019-10-08 15:33:58 +02:00
{
2021-07-12 16:18:00 +01:00
switch ( pseudo_class . type ) {
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Link :
2023-08-22 15:24:12 +01:00
case CSS : : PseudoClass : : AnyLink :
// NOTE: AnyLink should match whether the link is visited or not, so if we ever start matching
// :visited, we'll need to handle these differently.
2025-02-09 17:02:51 +01:00
return element . matches_link_pseudo_class ( ) ;
2023-08-22 15:24:12 +01:00
case CSS : : PseudoClass : : LocalLink : {
2025-02-09 17:02:51 +01:00
return element . matches_local_link_pseudo_class ( ) ;
2023-08-22 15:24:12 +01:00
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Visited :
2020-06-13 00:21:42 +02:00
// FIXME: Maybe match this selector sometimes?
return false ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Active :
2021-07-12 16:18:00 +01:00
return element . is_active ( ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Hover :
2025-01-03 20:39:25 +03:00
context . did_match_any_hover_rules = true ;
2021-07-12 16:18:00 +01:00
return matches_hover_pseudo_class ( element ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Focus :
2021-07-12 16:18:00 +01:00
return element . is_focused ( ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : FocusVisible :
2023-08-02 14:17:04 +01:00
// FIXME: We should only apply this when a visible focus is useful. Decide when that is!
return element . is_focused ( ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : FocusWithin : {
2022-03-20 13:28:32 +00:00
auto * focused_element = element . document ( ) . focused_element ( ) ;
return focused_element & & element . is_inclusive_ancestor_of ( * focused_element ) ;
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : FirstChild :
2025-02-05 21:29:34 +01:00
if ( context . collect_per_element_selector_involvement_metadata ) {
const_cast < DOM : : Element & > ( element ) . set_affected_by_first_or_last_child_pseudo_class ( true ) ;
}
2021-07-12 16:18:00 +01:00
return ! element . previous_element_sibling ( ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : LastChild :
2025-02-05 21:29:34 +01:00
if ( context . collect_per_element_selector_involvement_metadata ) {
const_cast < DOM : : Element & > ( element ) . set_affected_by_first_or_last_child_pseudo_class ( true ) ;
}
2021-07-12 16:18:00 +01:00
return ! element . next_element_sibling ( ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : OnlyChild :
2021-07-12 16:18:00 +01:00
return ! ( element . previous_element_sibling ( ) | | element . next_element_sibling ( ) ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Empty : {
2022-02-25 17:14:14 +01:00
if ( ! element . has_children ( ) )
return true ;
if ( element . first_child_of_type < DOM : : Element > ( ) )
return false ;
// NOTE: CSS Selectors level 4 changed ":empty" to also match whitespace-only text nodes.
// However, none of the major browser supports this yet, so let's just hang back until they do.
bool has_nonempty_text_child = false ;
element . for_each_child_of_type < DOM : : Text > ( [ & ] ( auto const & text_child ) {
if ( ! text_child . data ( ) . is_empty ( ) ) {
has_nonempty_text_child = true ;
return IterationDecision : : Break ;
}
return IterationDecision : : Continue ;
} ) ;
return ! has_nonempty_text_child ;
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Root :
2022-02-10 20:52:58 +01:00
return is < HTML : : HTMLHtmlElement > ( element ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Host :
2025-01-03 20:39:25 +03:00
return matches_host_pseudo_class ( element , shadow_host , context , pseudo_class . argument_selector_list ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Scope :
2023-03-20 23:42:17 +01:00
return scope ? & element = = scope : is < HTML : : HTMLHtmlElement > ( element ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : FirstOfType :
2022-02-17 22:43:22 +01:00
return ! previous_sibling_with_same_tag_name ( element ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : LastOfType :
2022-02-17 22:43:22 +01:00
return ! next_sibling_with_same_tag_name ( element ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : OnlyOfType :
2022-02-17 22:43:22 +01:00
return ! previous_sibling_with_same_tag_name ( element ) & & ! next_sibling_with_same_tag_name ( element ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Lang :
2022-03-20 12:39:11 +01:00
return matches_lang_pseudo_class ( element , pseudo_class . languages ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Disabled :
2025-02-09 17:02:51 +01:00
return element . matches_disabled_pseudo_class ( ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Enabled :
2025-02-09 17:02:51 +01:00
return element . matches_enabled_pseudo_class ( ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Checked :
2025-02-09 17:02:51 +01:00
return element . matches_checked_pseudo_class ( ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Indeterminate :
2023-03-20 04:35:43 -04:00
return matches_indeterminate_pseudo_class ( element ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Defined :
2023-03-29 23:48:40 +01:00
return element . is_defined ( ) ;
2024-07-13 09:19:30 -07:00
case CSS : : PseudoClass : : Has :
// :has() cannot be nested in a :has()
if ( selector_kind = = SelectorKind : : Relative )
return false ;
2025-02-12 16:33:24 +01:00
if ( context . collect_per_element_selector_involvement_metadata ) {
if ( & element = = context . subject ) {
const_cast < DOM : : Element & > ( element ) . set_affected_by_has_pseudo_class_in_subject_position ( true ) ;
} else {
const_cast < DOM : : Element & > ( element ) . set_affected_by_has_pseudo_class_in_non_subject_position ( true ) ;
}
2025-01-29 03:31:46 +01:00
}
2024-07-13 09:19:30 -07:00
// These selectors should be relative selectors (https://drafts.csswg.org/selectors-4/#relative-selector)
for ( auto & selector : pseudo_class . argument_selector_list ) {
2025-01-03 20:39:25 +03:00
if ( matches_has_pseudo_class ( selector , element , shadow_host , context ) )
2024-07-13 09:19:30 -07:00
return true ;
}
return false ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Is :
case CSS : : PseudoClass : : Where :
2022-03-17 15:28:42 +00:00
for ( auto & selector : pseudo_class . argument_selector_list ) {
2025-01-03 20:39:25 +03:00
if ( matches ( selector , element , shadow_host , context ) )
2022-03-17 15:28:42 +00:00
return true ;
}
return false ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Not :
2022-03-17 15:28:42 +00:00
for ( auto & selector : pseudo_class . argument_selector_list ) {
2025-01-03 20:39:25 +03:00
if ( matches ( selector , element , shadow_host , context ) )
2021-07-12 17:58:47 +01:00
return false ;
}
return true ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : NthChild :
case CSS : : PseudoClass : : NthLastChild :
case CSS : : PseudoClass : : NthOfType :
case CSS : : PseudoClass : : NthLastOfType : {
2021-07-12 16:18:00 +01:00
auto const step_size = pseudo_class . nth_child_pattern . step_size ;
auto const offset = pseudo_class . nth_child_pattern . offset ;
2021-05-08 23:19:25 +03:00
if ( step_size = = 0 & & offset = = 0 )
return false ; // "If both a and b are equal to zero, the pseudo-class represents no element in the document tree."
2024-11-06 19:52:49 +01:00
auto const * parent = element . parent ( ) ;
2021-05-08 23:19:25 +03:00
if ( ! parent )
return false ;
2025-02-05 21:29:34 +01:00
if ( context . collect_per_element_selector_involvement_metadata ) {
const_cast < DOM : : Element & > ( element ) . set_affected_by_nth_child_pseudo_class ( true ) ;
}
2025-01-03 20:39:25 +03:00
auto matches_selector_list = [ & context , shadow_host ] ( CSS : : SelectorList const & list , DOM : : Element const & element ) {
2022-03-17 19:13:51 +00:00
if ( list . is_empty ( ) )
return true ;
for ( auto const & child_selector : list ) {
2025-01-03 20:39:25 +03:00
if ( matches ( child_selector , element , shadow_host , context ) ) {
2022-03-17 19:13:51 +00:00
return true ;
}
}
return false ;
} ;
2021-05-08 23:19:25 +03:00
int index = 1 ;
2022-02-26 13:53:13 +00:00
switch ( pseudo_class . type ) {
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : NthChild : {
2022-03-17 19:13:51 +00:00
if ( ! matches_selector_list ( pseudo_class . argument_selector_list , element ) )
return false ;
for ( auto * child = parent - > first_child_of_type < DOM : : Element > ( ) ; child & & child ! = & element ; child = child - > next_element_sibling ( ) ) {
if ( matches_selector_list ( pseudo_class . argument_selector_list , * child ) )
+ + index ;
}
2022-02-26 13:53:13 +00:00
break ;
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : NthLastChild : {
2022-03-17 19:13:51 +00:00
if ( ! matches_selector_list ( pseudo_class . argument_selector_list , element ) )
return false ;
for ( auto * child = parent - > last_child_of_type < DOM : : Element > ( ) ; child & & child ! = & element ; child = child - > previous_element_sibling ( ) ) {
if ( matches_selector_list ( pseudo_class . argument_selector_list , * child ) )
+ + index ;
}
2022-02-26 13:53:13 +00:00
break ;
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : NthOfType : {
2022-02-26 13:53:13 +00:00
for ( auto * child = previous_sibling_with_same_tag_name ( element ) ; child ; child = previous_sibling_with_same_tag_name ( * child ) )
+ + index ;
break ;
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : NthLastOfType : {
2022-02-26 13:53:13 +00:00
for ( auto * child = next_sibling_with_same_tag_name ( element ) ; child ; child = next_sibling_with_same_tag_name ( * child ) )
+ + index ;
break ;
}
default :
VERIFY_NOT_REACHED ( ) ;
2021-05-08 23:19:25 +03:00
}
2022-03-02 15:58:16 +00:00
// When "step_size == -1", selector represents first "offset" elements in document tree.
if ( step_size = = - 1 )
2021-07-12 16:18:00 +01:00
return ! ( offset < = 0 | | index > offset ) ;
2022-03-02 15:58:16 +00:00
// When "step_size == 1", selector represents last "offset" elements in document tree.
if ( step_size = = 1 )
2021-07-12 16:18:00 +01:00
return ! ( offset < 0 | | index < offset ) ;
2022-03-02 15:58:16 +00:00
// When "step_size == 0", selector picks only the "offset" element.
if ( step_size = = 0 )
return index = = offset ;
// If both are negative, nothing can match.
if ( step_size < 0 & & offset < 0 )
return false ;
2021-05-08 23:19:25 +03:00
// Like "a % b", but handles negative integers correctly.
2021-07-12 14:13:29 +01:00
auto const canonical_modulo = [ ] ( int a , int b ) - > int {
2021-05-08 23:19:25 +03:00
int c = a % b ;
if ( ( c < 0 & & b > 0 ) | | ( c > 0 & & b < 0 ) ) {
c + = b ;
}
return c ;
} ;
2022-03-02 15:58:16 +00:00
// When "step_size < 0", we start at "offset" and count backwards.
if ( step_size < 0 )
return index < = offset & & canonical_modulo ( index - offset , - step_size ) = = 0 ;
// Otherwise, we start at "offset" and count forwards.
return index > = offset & & canonical_modulo ( index - offset , step_size ) = = 0 ;
2021-07-12 16:18:00 +01:00
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Playing : {
2023-08-01 12:31:41 +01:00
if ( ! is < HTML : : HTMLMediaElement > ( element ) )
return false ;
auto const & media_element = static_cast < HTML : : HTMLMediaElement const & > ( element ) ;
return ! media_element . paused ( ) ;
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Paused : {
2023-08-01 12:31:41 +01:00
if ( ! is < HTML : : HTMLMediaElement > ( element ) )
return false ;
auto const & media_element = static_cast < HTML : : HTMLMediaElement const & > ( element ) ;
return media_element . paused ( ) ;
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Seeking : {
2023-08-01 13:04:47 +01:00
if ( ! is < HTML : : HTMLMediaElement > ( element ) )
return false ;
auto const & media_element = static_cast < HTML : : HTMLMediaElement const & > ( element ) ;
return media_element . seeking ( ) ;
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Muted : {
2023-08-01 14:49:28 +01:00
if ( ! is < HTML : : HTMLMediaElement > ( element ) )
return false ;
auto const & media_element = static_cast < HTML : : HTMLMediaElement const & > ( element ) ;
return media_element . muted ( ) ;
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : VolumeLocked : {
2023-08-01 15:00:03 +01:00
// FIXME: Currently we don't allow the user to specify an override volume, so this is always false.
// Once we do, implement this!
return false ;
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Buffering : {
2023-08-01 15:26:56 +01:00
if ( ! is < HTML : : HTMLMediaElement > ( element ) )
return false ;
auto const & media_element = static_cast < HTML : : HTMLMediaElement const & > ( element ) ;
return media_element . blocked ( ) ;
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Stalled : {
2023-08-01 15:26:56 +01:00
if ( ! is < HTML : : HTMLMediaElement > ( element ) )
return false ;
auto const & media_element = static_cast < HTML : : HTMLMediaElement const & > ( element ) ;
return media_element . stalled ( ) ;
}
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Target :
2023-08-11 20:07:00 +01:00
return element . is_target ( ) ;
2023-08-22 15:55:46 +01:00
case CSS : : PseudoClass : : TargetWithin : {
auto * target_element = element . document ( ) . target_element ( ) ;
if ( ! target_element )
return false ;
return element . is_inclusive_ancestor_of ( * target_element ) ;
}
2023-08-16 12:11:14 +01:00
case CSS : : PseudoClass : : Dir : {
// "Values other than ltr and rtl are not invalid, but do not match anything."
// - https://www.w3.org/TR/selectors-4/#the-dir-pseudo
2024-08-14 14:06:03 +01:00
if ( ! first_is_one_of ( pseudo_class . keyword , CSS : : Keyword : : Ltr , CSS : : Keyword : : Rtl ) )
2023-08-16 12:11:14 +01:00
return false ;
switch ( element . directionality ( ) ) {
case DOM : : Element : : Directionality : : Ltr :
2024-08-14 14:06:03 +01:00
return pseudo_class . keyword = = CSS : : Keyword : : Ltr ;
2023-08-16 12:11:14 +01:00
case DOM : : Element : : Directionality : : Rtl :
2024-08-14 14:06:03 +01:00
return pseudo_class . keyword = = CSS : : Keyword : : Rtl ;
2023-08-16 12:11:14 +01:00
}
VERIFY_NOT_REACHED ( ) ;
}
2023-08-22 16:28:06 +01:00
case CSS : : PseudoClass : : ReadOnly :
return ! matches_read_write_pseudo_class ( element ) ;
case CSS : : PseudoClass : : ReadWrite :
return matches_read_write_pseudo_class ( element ) ;
2023-08-22 16:51:42 +01:00
case CSS : : PseudoClass : : PlaceholderShown : {
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-placeholder-shown
// The :placeholder-shown pseudo-class must match any element falling into one of the following categories:
// - input elements that have a placeholder attribute whose value is currently being presented to the user.
if ( is < HTML : : HTMLInputElement > ( element ) & & element . has_attribute ( HTML : : AttributeNames : : placeholder ) ) {
auto const & input_element = static_cast < HTML : : HTMLInputElement const & > ( element ) ;
return input_element . placeholder_element ( ) & & input_element . placeholder_value ( ) . has_value ( ) ;
}
// - FIXME: textarea elements that have a placeholder attribute whose value is currently being presented to the user.
return false ;
}
2023-09-13 17:39:05 +01:00
case CSS : : PseudoClass : : Open :
return matches_open_state_pseudo_class ( element , pseudo_class . type = = CSS : : PseudoClass : : Open ) ;
2024-07-02 00:36:36 +01:00
case CSS : : PseudoClass : : Modal : {
// https://drafts.csswg.org/selectors/#modal-state
if ( is < HTML : : HTMLDialogElement > ( element ) ) {
auto const & dialog_element = static_cast < HTML : : HTMLDialogElement const & > ( element ) ;
return dialog_element . is_modal ( ) ;
}
// FIXME: fullscreen elements are also modal.
return false ;
}
2024-12-05 23:24:24 +00:00
case CSS : : PseudoClass : : PopoverOpen : {
2025-02-02 20:41:23 +01:00
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open
2024-12-05 23:24:24 +00:00
// The :popover-open pseudo-class is defined to match any HTML element whose popover attribute is not in the no popover state and whose popover visibility state is showing.
if ( is < HTML : : HTMLElement > ( element ) & & element . has_attribute ( HTML : : AttributeNames : : popover ) ) {
auto & html_element = static_cast < HTML : : HTMLElement const & > ( element ) ;
return html_element . popover_visibility_state ( ) = = HTML : : HTMLElement : : PopoverVisibilityState : : Showing ;
}
return false ;
}
2025-02-02 20:41:23 +01:00
case CSS : : PseudoClass : : Valid : {
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-valid
// The :valid pseudo-class must match any element falling into one of the following categories:
// - elements that are candidates for constraint validation and that satisfy their constraints
if ( auto form_associated_element = as_if < Web : : HTML : : FormAssociatedElement > ( element ) )
if ( form_associated_element - > is_candidate_for_constraint_validation ( ) & & form_associated_element - > satisfies_its_constraints ( ) )
return true ;
// - form elements that are not the form owner of any elements that themselves are candidates for constraint validation but do not satisfy their constraints
if ( auto form_element = as_if < Web : : HTML : : HTMLFormElement > ( element ) ) {
bool has_invalid_elements = false ;
element . for_each_in_subtree ( [ & ] ( auto & node ) {
if ( auto form_associated_element = as_if < Web : : HTML : : FormAssociatedElement > ( & node ) ) {
if ( form_associated_element - > form ( ) = = form_element & & form_associated_element - > is_candidate_for_constraint_validation ( ) & & ! form_associated_element - > satisfies_its_constraints ( ) ) {
has_invalid_elements = true ;
return TraversalDecision : : Break ;
}
}
return TraversalDecision : : Continue ;
} ) ;
if ( ! has_invalid_elements )
return true ;
}
// - fieldset elements that have no descendant elements that themselves are candidates for constraint validation but do not satisfy their constraints
if ( is < Web : : HTML : : HTMLFieldSetElement > ( element ) ) {
bool has_invalid_children = false ;
element . for_each_in_subtree ( [ & ] ( auto & node ) {
if ( auto form_associated_element = as_if < Web : : HTML : : FormAssociatedElement > ( & node ) ) {
if ( form_associated_element - > is_candidate_for_constraint_validation ( ) & & ! form_associated_element - > satisfies_its_constraints ( ) ) {
has_invalid_children = true ;
return TraversalDecision : : Break ;
}
}
return TraversalDecision : : Continue ;
} ) ;
if ( ! has_invalid_children )
return true ;
}
return false ;
}
case CSS : : PseudoClass : : Invalid : {
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-invalid
// The :invalid pseudo-class must match any element falling into one of the following categories:
// - elements that are candidates for constraint validation but that do not satisfy their constraints
if ( auto form_associated_element = as_if < Web : : HTML : : FormAssociatedElement > ( element ) )
if ( form_associated_element - > is_candidate_for_constraint_validation ( ) & & ! form_associated_element - > satisfies_its_constraints ( ) )
return true ;
// - form elements that are the form owner of one or more elements that themselves are candidates for constraint validation but do not satisfy their constraints
if ( auto form_element = as_if < Web : : HTML : : HTMLFormElement > ( element ) ) {
bool has_invalid_elements = false ;
element . for_each_in_subtree ( [ & ] ( auto & node ) {
if ( auto form_associated_element = as_if < Web : : HTML : : FormAssociatedElement > ( & node ) ) {
if ( form_associated_element - > form ( ) = = form_element & & form_associated_element - > is_candidate_for_constraint_validation ( ) & & ! form_associated_element - > satisfies_its_constraints ( ) ) {
has_invalid_elements = true ;
return TraversalDecision : : Break ;
}
}
return TraversalDecision : : Continue ;
} ) ;
if ( has_invalid_elements )
return true ;
}
// - fieldset elements that have of one or more descendant elements that themselves are candidates for constraint validation but do not satisfy their constraints
if ( is < Web : : HTML : : HTMLFieldSetElement > ( element ) ) {
bool has_invalid_children = false ;
element . for_each_in_subtree ( [ & ] ( auto & node ) {
if ( auto form_associated_element = as_if < Web : : HTML : : FormAssociatedElement > ( & node ) ) {
if ( form_associated_element - > is_candidate_for_constraint_validation ( ) & & ! form_associated_element - > satisfies_its_constraints ( ) ) {
has_invalid_children = true ;
return TraversalDecision : : Break ;
}
}
return TraversalDecision : : Continue ;
} ) ;
if ( has_invalid_children )
return true ;
}
return false ;
}
case CSS : : PseudoClass : : UserValid : {
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-user-valid
// The :user-valid pseudo-class must match input, textarea, and select elements whose user validity is true,
bool user_validity = false ;
if ( auto input_element = as_if < Web : : HTML : : HTMLInputElement > ( element ) ) {
user_validity = input_element - > user_validity ( ) ;
} else if ( auto select_element = as_if < Web : : HTML : : HTMLSelectElement > ( element ) ) {
user_validity = select_element - > user_validity ( ) ;
} else if ( auto text_area_element = as_if < Web : : HTML : : HTMLTextAreaElement > ( element ) ) {
user_validity = text_area_element - > user_validity ( ) ;
}
if ( ! user_validity )
return false ;
// are candidates for constraint validation, and that satisfy their constraints.
auto & form_associated_element = as < Web : : HTML : : FormAssociatedElement > ( element ) ;
if ( form_associated_element . is_candidate_for_constraint_validation ( ) & & form_associated_element . satisfies_its_constraints ( ) )
return true ;
return false ;
}
case CSS : : PseudoClass : : UserInvalid : {
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-user-invalid
// The :user-invalid pseudo-class must match input, textarea, and select elements whose user validity is true,
bool user_validity = false ;
if ( auto input_element = as_if < Web : : HTML : : HTMLInputElement > ( element ) ) {
user_validity = input_element - > user_validity ( ) ;
} else if ( auto select_element = as_if < Web : : HTML : : HTMLSelectElement > ( element ) ) {
user_validity = select_element - > user_validity ( ) ;
} else if ( auto text_area_element = as_if < Web : : HTML : : HTMLTextAreaElement > ( element ) ) {
user_validity = text_area_element - > user_validity ( ) ;
}
if ( ! user_validity )
return false ;
// are candidates for constraint validation but do not satisfy their constraints.
auto & form_associated_element = as < Web : : HTML : : FormAssociatedElement > ( element ) ;
if ( form_associated_element . is_candidate_for_constraint_validation ( ) & & ! form_associated_element . satisfies_its_constraints ( ) )
return true ;
return false ;
}
2023-08-01 12:31:41 +01:00
}
2021-07-12 16:18:00 +01:00
return false ;
}
2024-03-19 10:34:32 +01:00
static ALWAYS_INLINE bool matches_namespace (
CSS : : Selector : : SimpleSelector : : QualifiedName const & qualified_name ,
DOM : : Element const & element ,
2025-01-03 20:39:25 +03:00
GC : : Ptr < CSS : : CSSStyleSheet const > style_sheet_for_rule )
2024-03-19 10:34:32 +01:00
{
switch ( qualified_name . namespace_type ) {
case CSS : : Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Default :
// "if no default namespace has been declared for selectors, this is equivalent to *|E."
2025-01-03 20:39:25 +03:00
if ( ! style_sheet_for_rule | | ! style_sheet_for_rule - > default_namespace_rule ( ) )
2024-03-19 10:34:32 +01:00
return true ;
// "Otherwise it is equivalent to ns|E where ns is the default namespace."
return element . namespace_uri ( ) = = style_sheet_for_rule - > default_namespace_rule ( ) - > namespace_uri ( ) ;
case CSS : : Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : None :
// "elements with name E without a namespace"
return ! element . namespace_uri ( ) . has_value ( ) ;
case CSS : : Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Any :
// "elements with name E in any namespace, including those without a namespace"
return true ;
case CSS : : Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Named :
// "elements with name E in namespace ns"
// Unrecognized namespace prefixes are invalid, so don't match.
// (We can't detect this at parse time, since a namespace rule may be inserted later.)
// So, if we don't have a context to look up namespaces from, we fail to match.
2025-01-03 20:39:25 +03:00
if ( ! style_sheet_for_rule )
2024-03-19 10:34:32 +01:00
return false ;
auto selector_namespace = style_sheet_for_rule - > namespace_uri ( qualified_name . namespace_ ) ;
return selector_namespace . has_value ( ) & & selector_namespace . value ( ) = = element . namespace_uri ( ) ;
}
VERIFY_NOT_REACHED ( ) ;
}
2025-01-03 20:39:25 +03:00
static inline bool matches ( CSS : : Selector : : SimpleSelector const & component , DOM : : Element const & element , GC : : Ptr < DOM : : Element const > shadow_host , MatchContext & context , GC : : Ptr < DOM : : ParentNode const > scope , SelectorKind selector_kind , [[maybe_unused]] GC : : Ptr < DOM : : Element const > anchor )
2021-07-12 16:18:00 +01:00
{
2024-11-24 21:15:56 +02:00
if ( should_block_shadow_host_matching ( component , shadow_host , element ) )
return false ;
2019-10-08 15:33:58 +02:00
switch ( component . type ) {
2020-07-26 20:01:35 +02:00
case CSS : : Selector : : SimpleSelector : : Type : : Universal :
2023-08-08 15:11:48 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : TagName : {
2023-11-02 08:09:01 +01:00
auto const & qualified_name = component . qualified_name ( ) ;
2023-08-08 15:11:48 +01:00
// Reject if the tag name doesn't match
if ( component . type = = CSS : : Selector : : SimpleSelector : : Type : : TagName ) {
// See https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
2024-12-25 20:54:23 +09:00
if ( element . document ( ) . document_type ( ) = = DOM : : Document : : Type : : HTML & & element . namespace_uri ( ) = = Namespace : : HTML ) {
2023-10-01 20:07:44 +13:00
if ( qualified_name . name . lowercase_name ! = element . local_name ( ) )
2023-08-08 15:11:48 +01:00
return false ;
} else if ( ! Infra : : is_ascii_case_insensitive_match ( qualified_name . name . name , element . local_name ( ) ) ) {
return false ;
}
}
2025-01-03 20:39:25 +03:00
return matches_namespace ( qualified_name , element , context . style_sheet_for_rule ) ;
2023-08-08 15:11:48 +01:00
}
2020-07-26 20:01:35 +02:00
case CSS : : Selector : : SimpleSelector : : Type : : Id :
2023-11-02 14:57:40 +01:00
return component . name ( ) = = element . id ( ) ;
2024-08-02 22:21:12 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : Class : {
// Class selectors are matched case insensitively in quirks mode.
// See: https://drafts.csswg.org/selectors-4/#class-html
auto case_sensitivity = element . document ( ) . in_quirks_mode ( ) ? CaseSensitivity : : CaseInsensitive : CaseSensitivity : : CaseSensitive ;
return element . has_class ( component . name ( ) , case_sensitivity ) ;
}
2021-07-12 14:58:03 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : Attribute :
2025-01-03 20:39:25 +03:00
return matches_attribute ( component . attribute ( ) , context . style_sheet_for_rule , element ) ;
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : PseudoClass :
2025-01-03 20:39:25 +03:00
return matches_pseudo_class ( component . pseudo_class ( ) , element , shadow_host , context , scope , selector_kind ) ;
2021-07-12 16:34:18 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : PseudoElement :
2022-02-24 15:54:12 +00:00
// Pseudo-element matching/not-matching is handled in the top level matches().
return true ;
2024-10-15 12:00:29 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : Nesting :
2024-11-08 20:34:48 +00:00
// Nesting either behaves like :is(), or like :scope.
// :is() is handled already, by us replacing it with :is() directly, so if we
// got here, it's :scope.
2025-01-03 20:39:25 +03:00
return matches_pseudo_class ( CSS : : Selector : : SimpleSelector : : PseudoClassSelector { . type = CSS : : PseudoClass : : Scope } , element , shadow_host , context , scope , selector_kind ) ;
2024-11-13 15:49:43 +00:00
case CSS : : Selector : : SimpleSelector : : Type : : Invalid :
// Invalid selectors never match
return false ;
2019-10-08 15:33:58 +02:00
}
2024-10-15 12:00:29 +01:00
VERIFY_NOT_REACHED ( ) ;
2019-10-08 15:33:58 +02:00
}
2025-01-03 20:39:25 +03:00
bool matches ( CSS : : Selector const & selector , int component_list_index , DOM : : Element const & element , GC : : Ptr < DOM : : Element const > shadow_host , MatchContext & context , GC : : Ptr < DOM : : ParentNode const > scope , SelectorKind selector_kind , GC : : Ptr < DOM : : Element const > anchor )
2019-10-08 15:33:58 +02:00
{
2024-07-13 09:19:30 -07:00
auto & compound_selector = selector . compound_selectors ( ) [ component_list_index ] ;
for ( auto & simple_selector : compound_selector . simple_selectors ) {
2025-01-03 20:39:25 +03:00
if ( ! matches ( simple_selector , element , shadow_host , context , scope , selector_kind , anchor ) ) {
2019-11-27 20:37:36 +01:00
return false ;
2024-07-23 15:22:28 +02:00
}
2019-11-27 20:37:36 +01:00
}
2024-10-27 12:15:19 +01:00
if ( selector_kind = = SelectorKind : : Relative & & component_list_index = = 0 ) {
VERIFY ( anchor ) ;
return & element ! = anchor ;
}
2024-07-13 09:19:30 -07:00
switch ( compound_selector . combinator ) {
2021-07-23 15:24:33 +01:00
case CSS : : Selector : : Combinator : : None :
2024-07-13 09:19:30 -07:00
VERIFY ( selector_kind ! = SelectorKind : : Relative ) ;
2019-10-08 15:33:58 +02:00
return true ;
2021-07-23 15:24:33 +01:00
case CSS : : Selector : : Combinator : : Descendant :
2021-02-23 20:42:32 +01:00
VERIFY ( component_list_index ! = 0 ) ;
2024-07-23 15:22:28 +02:00
for ( auto ancestor = traverse_up ( element , shadow_host ) ; ancestor ; ancestor = traverse_up ( ancestor , shadow_host ) ) {
2020-07-26 19:37:56 +02:00
if ( ! is < DOM : : Element > ( * ancestor ) )
2019-10-08 15:33:58 +02:00
continue ;
2024-10-27 12:15:19 +01:00
if ( ancestor = = anchor )
return false ;
2025-01-03 20:39:25 +03:00
if ( matches ( selector , component_list_index - 1 , static_cast < DOM : : Element const & > ( * ancestor ) , shadow_host , context , scope , selector_kind , anchor ) )
2019-10-08 15:33:58 +02:00
return true ;
}
return false ;
2024-07-23 15:22:28 +02:00
case CSS : : Selector : : Combinator : : ImmediateChild : {
2021-02-23 20:42:32 +01:00
VERIFY ( component_list_index ! = 0 ) ;
2024-07-23 15:22:28 +02:00
auto parent = traverse_up ( element , shadow_host ) ;
if ( ! parent | | ! parent - > is_element ( ) )
2019-10-08 15:33:58 +02:00
return false ;
2025-01-03 20:39:25 +03:00
return matches ( selector , component_list_index - 1 , static_cast < DOM : : Element const & > ( * parent ) , shadow_host , context , scope , selector_kind , anchor ) ;
2024-07-23 15:22:28 +02:00
}
2021-07-23 15:24:33 +01:00
case CSS : : Selector : : Combinator : : NextSibling :
2025-02-05 21:29:34 +01:00
if ( context . collect_per_element_selector_involvement_metadata ) {
const_cast < DOM : : Element & > ( element ) . set_affected_by_sibling_combinator ( true ) ;
}
2021-02-23 20:42:32 +01:00
VERIFY ( component_list_index ! = 0 ) ;
2019-10-08 15:33:58 +02:00
if ( auto * sibling = element . previous_element_sibling ( ) )
2025-01-03 20:39:25 +03:00
return matches ( selector , component_list_index - 1 , * sibling , shadow_host , context , scope , selector_kind , anchor ) ;
2019-10-08 15:33:58 +02:00
return false ;
2021-07-23 15:24:33 +01:00
case CSS : : Selector : : Combinator : : SubsequentSibling :
2025-02-05 21:29:34 +01:00
if ( context . collect_per_element_selector_involvement_metadata ) {
const_cast < DOM : : Element & > ( element ) . set_affected_by_sibling_combinator ( true ) ;
}
2021-02-23 20:42:32 +01:00
VERIFY ( component_list_index ! = 0 ) ;
2019-10-08 15:33:58 +02:00
for ( auto * sibling = element . previous_element_sibling ( ) ; sibling ; sibling = sibling - > previous_element_sibling ( ) ) {
2025-01-03 20:39:25 +03:00
if ( matches ( selector , component_list_index - 1 , * sibling , shadow_host , context , scope , selector_kind , anchor ) )
2019-10-08 15:33:58 +02:00
return true ;
}
return false ;
2021-07-23 15:24:33 +01:00
case CSS : : Selector : : Combinator : : Column :
2021-04-29 21:16:28 +02:00
TODO ( ) ;
2019-10-08 15:33:58 +02:00
}
2021-02-23 20:42:32 +01:00
VERIFY_NOT_REACHED ( ) ;
2019-10-08 15:33:58 +02:00
}
2025-02-02 20:35:29 +01:00
bool fast_matches ( CSS : : Selector const & selector , DOM : : Element const & element_to_match , GC : : Ptr < DOM : : Element const > shadow_host , MatchContext & context ) ;
2025-01-03 20:39:25 +03:00
bool matches ( CSS : : Selector const & selector , DOM : : Element const & element , GC : : Ptr < DOM : : Element const > shadow_host , MatchContext & context , Optional < CSS : : Selector : : PseudoElement : : Type > pseudo_element , GC : : Ptr < DOM : : ParentNode const > scope , SelectorKind selector_kind , GC : : Ptr < DOM : : Element const > anchor )
2019-10-08 15:33:58 +02:00
{
2025-02-02 20:35:29 +01:00
if ( selector_kind = = SelectorKind : : Normal & & selector . can_use_fast_matches ( ) ) {
return fast_matches ( selector , element , shadow_host , context ) ;
}
2021-07-23 15:24:33 +01:00
VERIFY ( ! selector . compound_selectors ( ) . is_empty ( ) ) ;
2023-12-10 21:00:03 +13:00
if ( pseudo_element . has_value ( ) & & selector . pseudo_element ( ) . has_value ( ) & & selector . pseudo_element ( ) . value ( ) . type ( ) ! = pseudo_element )
2022-02-24 15:54:12 +00:00
return false ;
if ( ! pseudo_element . has_value ( ) & & selector . pseudo_element ( ) . has_value ( ) )
return false ;
2025-01-03 20:39:25 +03:00
return matches ( selector , selector . compound_selectors ( ) . size ( ) - 1 , element , shadow_host , context , scope , selector_kind , anchor ) ;
2019-10-08 15:33:58 +02:00
}
2025-01-03 20:39:25 +03:00
static bool fast_matches_simple_selector ( CSS : : Selector : : SimpleSelector const & simple_selector , DOM : : Element const & element , GC : : Ptr < DOM : : Element const > shadow_host , MatchContext & context )
2024-03-19 10:36:08 +01:00
{
2024-11-24 21:15:56 +02:00
if ( should_block_shadow_host_matching ( simple_selector , shadow_host , element ) )
return false ;
2024-03-19 10:36:08 +01:00
switch ( simple_selector . type ) {
case CSS : : Selector : : SimpleSelector : : Type : : Universal :
2025-01-03 20:39:25 +03:00
return matches_namespace ( simple_selector . qualified_name ( ) , element , context . style_sheet_for_rule ) ;
2024-03-19 10:36:08 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : TagName :
2025-02-02 20:41:23 +01:00
// https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
2025-01-08 23:07:23 +01:00
// When comparing a CSS element type selector to the names of HTML elements in HTML documents, the CSS element type selector must first be converted to ASCII lowercase. The
// same selector when compared to other elements must be compared according to its original case. In both cases, to match the values must be identical to each other (and therefore
// the comparison is case sensitive).
if ( element . namespace_uri ( ) = = Namespace : : HTML & & element . document ( ) . document_type ( ) = = DOM : : Document : : Type : : HTML ) {
2024-03-19 10:36:08 +01:00
if ( simple_selector . qualified_name ( ) . name . lowercase_name ! = element . local_name ( ) )
return false ;
2025-01-08 23:07:23 +01:00
} else if ( simple_selector . qualified_name ( ) . name . name ! = element . local_name ( ) ) {
// NOTE: Any other elements are either SVG, XHTML or MathML, all of which are case-sensitive.
2024-03-19 10:36:08 +01:00
return false ;
}
2025-01-03 20:39:25 +03:00
return matches_namespace ( simple_selector . qualified_name ( ) , element , context . style_sheet_for_rule ) ;
2024-08-02 22:21:12 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : Class : {
// Class selectors are matched case insensitively in quirks mode.
// See: https://drafts.csswg.org/selectors-4/#class-html
auto case_sensitivity = element . document ( ) . in_quirks_mode ( ) ? CaseSensitivity : : CaseInsensitive : CaseSensitivity : : CaseSensitive ;
return element . has_class ( simple_selector . name ( ) , case_sensitivity ) ;
}
2024-03-19 10:36:08 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : Id :
return simple_selector . name ( ) = = element . id ( ) ;
case CSS : : Selector : : SimpleSelector : : Type : : Attribute :
2025-01-03 20:39:25 +03:00
return matches_attribute ( simple_selector . attribute ( ) , context . style_sheet_for_rule , element ) ;
2024-03-19 10:58:10 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : PseudoClass :
2025-01-03 20:39:25 +03:00
return matches_pseudo_class ( simple_selector . pseudo_class ( ) , element , shadow_host , context , nullptr , SelectorKind : : Normal ) ;
2024-03-19 10:36:08 +01:00
default :
VERIFY_NOT_REACHED ( ) ;
}
}
2025-01-03 20:39:25 +03:00
static bool fast_matches_compound_selector ( CSS : : Selector : : CompoundSelector const & compound_selector , DOM : : Element const & element , GC : : Ptr < DOM : : Element const > shadow_host , MatchContext & context )
2024-03-19 10:36:08 +01:00
{
for ( auto const & simple_selector : compound_selector . simple_selectors ) {
2025-01-03 20:39:25 +03:00
if ( ! fast_matches_simple_selector ( simple_selector , element , shadow_host , context ) )
2024-03-19 10:36:08 +01:00
return false ;
}
return true ;
}
2025-01-03 20:39:25 +03:00
bool fast_matches ( CSS : : Selector const & selector , DOM : : Element const & element_to_match , GC : : Ptr < DOM : : Element const > shadow_host , MatchContext & context )
2024-03-19 10:36:08 +01:00
{
DOM : : Element const * current = & element_to_match ;
ssize_t compound_selector_index = selector . compound_selectors ( ) . size ( ) - 1 ;
2025-01-03 20:39:25 +03:00
if ( ! fast_matches_compound_selector ( selector . compound_selectors ( ) . last ( ) , * current , shadow_host , context ) )
2024-03-19 10:36:08 +01:00
return false ;
// NOTE: If we fail after following a child combinator, we may need to backtrack
// to the last matched descendant. We store the state here.
struct {
2024-11-15 04:01:23 +13:00
GC : : Ptr < DOM : : Element const > element ;
2024-03-19 10:36:08 +01:00
ssize_t compound_selector_index = 0 ;
} backtrack_state ;
for ( ; ; ) {
// NOTE: There should always be a leftmost compound selector without combinator that kicks us out of this loop.
VERIFY ( compound_selector_index > = 0 ) ;
auto const * compound_selector = & selector . compound_selectors ( ) [ compound_selector_index ] ;
switch ( compound_selector - > combinator ) {
case CSS : : Selector : : Combinator : : None :
return true ;
case CSS : : Selector : : Combinator : : Descendant :
backtrack_state = { current - > parent_element ( ) , compound_selector_index } ;
compound_selector = & selector . compound_selectors ( ) [ - - compound_selector_index ] ;
for ( current = current - > parent_element ( ) ; current ; current = current - > parent_element ( ) ) {
2025-01-03 20:39:25 +03:00
if ( fast_matches_compound_selector ( * compound_selector , * current , shadow_host , context ) )
2024-03-19 10:36:08 +01:00
break ;
}
if ( ! current )
return false ;
break ;
case CSS : : Selector : : Combinator : : ImmediateChild :
compound_selector = & selector . compound_selectors ( ) [ - - compound_selector_index ] ;
current = current - > parent_element ( ) ;
if ( ! current )
return false ;
2025-01-03 20:39:25 +03:00
if ( ! fast_matches_compound_selector ( * compound_selector , * current , shadow_host , context ) ) {
2024-03-19 10:36:08 +01:00
if ( backtrack_state . element ) {
current = backtrack_state . element ;
compound_selector_index = backtrack_state . compound_selector_index ;
continue ;
}
return false ;
}
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
}
}
2019-10-08 15:33:58 +02:00
}