2020-01-18 09:38:21 +01:00
/*
2024-03-19 10:36:08 +01:00
* Copyright ( c ) 2018 - 2024 , Andreas Kling < kling @ serenityos . org >
2023-02-18 15:42:55 +00:00
* Copyright ( c ) 2021 - 2023 , Sam Atkins < atkinssj @ serenityos . 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
*/
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>
2023-08-22 16:51:42 +01:00
# include <LibWeb/CSS/StyleProperties.h>
2023-08-16 12:11:14 +01:00
# include <LibWeb/CSS/ValueID.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>
# include <LibWeb/HTML/HTMLAreaElement.h>
2022-09-30 19:40:19 +01:00
# include <LibWeb/HTML/HTMLButtonElement.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>
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>
2022-09-30 19:40:19 +01:00
# include <LibWeb/HTML/HTMLOptGroupElement.h>
# include <LibWeb/HTML/HTMLOptionElement.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>
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
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
{
2023-02-18 15:42:55 +00:00
FlyString element_language ;
2022-03-20 12:39:11 +01:00
for ( auto const * e = & element ; e ; e = e - > parent_element ( ) ) {
2023-10-10 15:00:58 +03:30
auto lang = e - > attribute ( HTML : : AttributeNames : : lang ) ;
if ( lang . has_value ( ) ) {
element_language = lang . release_value ( ) ;
2022-03-20 12:39:11 +01:00
break ;
}
}
2023-02-18 15:42:55 +00:00
if ( element_language . is_empty ( ) )
2022-03-20 12:39:11 +01:00
return false ;
// 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 ;
2023-08-16 14:37:47 +01:00
if ( ! element_language . to_string ( ) . contains ( ' - ' ) & & Infra : : is_ascii_case_insensitive_match ( element_language , language ) )
return true ;
2023-02-18 15:42:55 +00:00
auto parts = element_language . to_string ( ) . split_limit ( ' - ' , 2 ) . release_value_but_fixme_should_propagate_errors ( ) ;
2023-08-16 14:37:47 +01:00
if ( Infra : : is_ascii_case_insensitive_match ( parts [ 0 ] , language ) )
return true ;
2022-03-20 12:39:11 +01:00
}
return false ;
}
2022-09-14 13:29:07 +02:00
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-link
static inline bool matches_link_pseudo_class ( DOM : : Element const & element )
{
// All a elements that have an href attribute, and all area elements that have an href attribute, must match one of :link and :visited.
if ( ! is < HTML : : HTMLAnchorElement > ( element ) & & ! is < HTML : : HTMLAreaElement > ( element ) )
return false ;
return element . has_attribute ( HTML : : AttributeNames : : href ) ;
}
2022-02-05 17:41:43 +02:00
static inline 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 ;
return element . is_ancestor_of ( * hovered_node ) ;
}
2022-03-16 23:29:17 +01:00
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-checked
static inline bool matches_checked_pseudo_class ( DOM : : Element const & element )
{
// The :checked 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 checkedness state is true
// - input elements whose type attribute is in the Radio Button state and 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 :
case HTML : : HTMLInputElement : : TypeAttributeState : : RadioButton :
return static_cast < HTML : : HTMLInputElement const & > ( element ) . checked ( ) ;
default :
return false ;
}
}
2023-03-20 04:33:22 -04:00
// - option elements whose selectedness is true
if ( is < HTML : : HTMLOptionElement > ( element ) ) {
return static_cast < HTML : : HTMLOptionElement const & > ( element ) . selected ( ) ;
}
2022-03-16 23:29:17 +01:00
return false ;
}
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 :
return input_element . indeterminate ( ) ;
default :
return false ;
}
}
// - progress elements with no value content attribute
if ( is < HTML : : HTMLProgressElement > ( element ) ) {
return ! element . has_attribute ( HTML : : AttributeNames : : value ) ;
}
return false ;
}
2023-08-08 16:19:20 +01:00
static inline bool matches_attribute ( CSS : : Selector : : SimpleSelector : : Attribute const & attribute , [[maybe_unused]] Optional < CSS : : CSSStyleSheet const & > style_sheet_for_rule , DOM : : Element const & element )
2021-07-12 14:58:03 +01:00
{
2023-08-08 16:19:20 +01:00
// FIXME: Check the attribute's namespace, once we support that in DOM::Element!
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-03-15 18:19:59 +01:00
auto const * attr = element . namespace_uri ( ) = = Namespace : : HTML ? element . attributes ( ) - > get_attribute_with_lowercase_qualified_name ( attribute_name )
: element . attributes ( ) - > get_attribute ( attribute_name ) ;
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 ;
2022-03-29 18:01:36 +02:00
auto const case_insensitive_match = ( attribute . case_type = = CSS : : Selector : : SimpleSelector : : Attribute : : CaseType : : CaseInsensitiveMatch ) ;
auto const case_sensitivity = case_insensitive_match
? CaseSensitivity : : CaseInsensitive
: CaseSensitivity : : CaseSensitive ;
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 ;
}
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
return element . is_editable ( ) ;
}
2023-09-13 17:39:05 +01:00
// https://www.w3.org/TR/selectors-4/#open-state
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.
// The :closed pseudo-class represents an element that has both “open” and “closed” states,
// and which is currently in the closed state.
// NOTE: Spec specifically suggests supporting <details>, <dialog>, and <select>.
// There may be others we want to treat as open or closed.
if ( is < HTML : : HTMLDetailsElement > ( element ) | | is < HTML : : HTMLDialogElement > ( element ) )
return open = = element . has_attribute ( HTML : : AttributeNames : : open ) ;
if ( is < HTML : : HTMLSelectElement > ( element ) )
return open = = static_cast < HTML : : HTMLSelectElement const & > ( element ) . is_open ( ) ;
return false ;
}
2023-08-11 21:26:04 +01:00
static inline bool matches_pseudo_class ( CSS : : Selector : : SimpleSelector : : PseudoClassSelector const & pseudo_class , Optional < CSS : : CSSStyleSheet const & > style_sheet_for_rule , DOM : : Element const & element , JS : : GCPtr < DOM : : ParentNode const > scope )
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.
2022-09-14 13:29:07 +02:00
return matches_link_pseudo_class ( element ) ;
2023-08-22 15:24:12 +01:00
case CSS : : PseudoClass : : LocalLink : {
// The :local-link pseudo-class allows authors to style hyperlinks based on the users current location
// within a site. It represents an element that is the source anchor of a hyperlink whose target’ s
// absolute URL matches the element’ s own document URL. If the hyperlink’ s target includes a fragment
// URL, then the fragment URL of the current URL must also match; if it does not, then the fragment
// URL portion of the current URL is not taken into account in the comparison.
if ( ! matches_link_pseudo_class ( element ) )
return false ;
auto document_url = element . document ( ) . url ( ) ;
2024-03-18 16:22:27 +13:00
URL : : URL target_url = element . document ( ) . parse_url ( element . attribute ( HTML : : AttributeNames : : href ) . value_or ( { } ) ) ;
2023-08-22 15:24:12 +01:00
if ( target_url . fragment ( ) . has_value ( ) )
2024-02-11 20:15:39 +13:00
return document_url . equals ( target_url , URL : : ExcludeFragment : : No ) ;
return document_url . equals ( target_url , URL : : ExcludeFragment : : Yes ) ;
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 :
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 :
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 :
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 :
2023-07-05 11:43:57 +02:00
// FIXME: Implement :host selector.
return false ;
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 :
2022-09-30 16:21:34 +01:00
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-disabled
// The :disabled pseudo-class must match any element that is actually disabled.
return element . is_actually_disabled ( ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Enabled :
2022-09-30 16:21:34 +01:00
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-enabled
// The :enabled pseudo-class must match any button, input, select, textarea, optgroup, option, fieldset element, or form-associated custom element that is not actually disabled.
2022-09-30 19:40:19 +01:00
return ( is < HTML : : HTMLButtonElement > ( element ) | | is < HTML : : HTMLInputElement > ( element ) | | is < HTML : : HTMLSelectElement > ( element ) | | is < HTML : : HTMLTextAreaElement > ( element ) | | is < HTML : : HTMLOptGroupElement > ( element ) | | is < HTML : : HTMLOptionElement > ( element ) | | is < HTML : : HTMLFieldSetElement > ( element ) )
& & ! element . is_actually_disabled ( ) ;
2023-08-11 21:26:04 +01:00
case CSS : : PseudoClass : : Checked :
2022-03-16 23:29:17 +01:00
return matches_checked_pseudo_class ( element ) ;
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 ( ) ;
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 ) {
2023-08-08 15:11:48 +01:00
if ( matches ( selector , style_sheet_for_rule , element ) )
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 ) {
2023-08-08 15:11:48 +01:00
if ( matches ( selector , style_sheet_for_rule , element ) )
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."
2021-07-12 14:13:29 +01:00
auto const * parent = element . parent_element ( ) ;
2021-05-08 23:19:25 +03:00
if ( ! parent )
return false ;
2023-08-08 15:11:48 +01:00
auto matches_selector_list = [ & style_sheet_for_rule ] ( 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 ) {
2023-08-08 15:11:48 +01:00
if ( matches ( child_selector , style_sheet_for_rule , element ) ) {
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
if ( ! first_is_one_of ( pseudo_class . identifier , CSS : : ValueID : : Ltr , CSS : : ValueID : : Rtl ) )
return false ;
switch ( element . directionality ( ) ) {
case DOM : : Element : : Directionality : : Ltr :
return pseudo_class . identifier = = CSS : : ValueID : : Ltr ;
case DOM : : Element : : Directionality : : Rtl :
return pseudo_class . identifier = = CSS : : ValueID : : Rtl ;
}
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 :
case CSS : : PseudoClass : : Closed :
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 ;
}
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 ,
Optional < CSS : : CSSStyleSheet const & > style_sheet_for_rule )
{
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."
if ( ! style_sheet_for_rule . has_value ( ) | | ! style_sheet_for_rule - > default_namespace_rule ( ) )
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.
if ( ! style_sheet_for_rule . has_value ( ) )
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 ( ) ;
}
2023-08-08 15:11:48 +01:00
static inline bool matches ( CSS : : Selector : : SimpleSelector const & component , Optional < CSS : : CSSStyleSheet const & > style_sheet_for_rule , DOM : : Element const & element , JS : : GCPtr < DOM : : ParentNode const > scope )
2021-07-12 16:18:00 +01:00
{
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
if ( element . document ( ) . document_type ( ) = = DOM : : Document : : Type : : 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 ;
}
}
2024-03-19 10:34:32 +01:00
return matches_namespace ( qualified_name , element , 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 ( ) ;
2020-07-26 20:01:35 +02:00
case CSS : : Selector : : SimpleSelector : : Type : : Class :
2023-03-07 19:54:01 +01:00
return element . has_class ( component . name ( ) ) ;
2021-07-12 14:58:03 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : Attribute :
2023-08-08 16:19:20 +01:00
return matches_attribute ( component . attribute ( ) , style_sheet_for_rule , element ) ;
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : PseudoClass :
2023-08-08 15:11:48 +01:00
return matches_pseudo_class ( component . pseudo_class ( ) , style_sheet_for_rule , element , scope ) ;
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 ;
2019-10-08 15:33:58 +02:00
default :
2021-02-23 20:42:32 +01:00
VERIFY_NOT_REACHED ( ) ;
2019-10-08 15:33:58 +02:00
}
}
2023-08-08 15:11:48 +01:00
static inline bool matches ( CSS : : Selector const & selector , Optional < CSS : : CSSStyleSheet const & > style_sheet_for_rule , int component_list_index , DOM : : Element const & element , JS : : GCPtr < DOM : : ParentNode const > scope )
2019-10-08 15:33:58 +02:00
{
2021-07-23 15:24:33 +01:00
auto & relative_selector = selector . compound_selectors ( ) [ component_list_index ] ;
for ( auto & simple_selector : relative_selector . simple_selectors ) {
2023-08-08 15:11:48 +01:00
if ( ! matches ( simple_selector , style_sheet_for_rule , element , scope ) )
2019-11-27 20:37:36 +01:00
return false ;
}
2021-07-23 15:24:33 +01:00
switch ( relative_selector . combinator ) {
case CSS : : Selector : : Combinator : : None :
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 ) ;
2019-10-08 15:33:58 +02:00
for ( auto * ancestor = element . parent ( ) ; ancestor ; ancestor = ancestor - > parent ( ) ) {
2020-07-26 19:37:56 +02:00
if ( ! is < DOM : : Element > ( * ancestor ) )
2019-10-08 15:33:58 +02:00
continue ;
2023-08-08 15:11:48 +01:00
if ( matches ( selector , style_sheet_for_rule , component_list_index - 1 , static_cast < DOM : : Element const & > ( * ancestor ) , scope ) )
2019-10-08 15:33:58 +02:00
return true ;
}
return false ;
2021-07-23 15:24:33 +01:00
case CSS : : Selector : : Combinator : : ImmediateChild :
2021-02-23 20:42:32 +01:00
VERIFY ( component_list_index ! = 0 ) ;
2020-07-26 19:37:56 +02:00
if ( ! element . parent ( ) | | ! is < DOM : : Element > ( * element . parent ( ) ) )
2019-10-08 15:33:58 +02:00
return false ;
2023-08-08 15:11:48 +01:00
return matches ( selector , style_sheet_for_rule , component_list_index - 1 , static_cast < DOM : : Element const & > ( * element . parent ( ) ) , scope ) ;
2021-07-23 15:24:33 +01:00
case CSS : : Selector : : Combinator : : NextSibling :
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 ( ) )
2023-08-08 15:11:48 +01:00
return matches ( selector , style_sheet_for_rule , component_list_index - 1 , * sibling , scope ) ;
2019-10-08 15:33:58 +02:00
return false ;
2021-07-23 15:24:33 +01:00
case CSS : : Selector : : Combinator : : SubsequentSibling :
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 ( ) ) {
2023-08-08 15:11:48 +01:00
if ( matches ( selector , style_sheet_for_rule , component_list_index - 1 , * sibling , scope ) )
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
}
2023-12-10 21:00:03 +13:00
bool matches ( CSS : : Selector const & selector , Optional < CSS : : CSSStyleSheet const & > style_sheet_for_rule , DOM : : Element const & element , Optional < CSS : : Selector : : PseudoElement : : Type > pseudo_element , JS : : GCPtr < DOM : : ParentNode const > scope )
2019-10-08 15:33:58 +02:00
{
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 ;
2023-08-08 15:11:48 +01:00
return matches ( selector , style_sheet_for_rule , selector . compound_selectors ( ) . size ( ) - 1 , element , scope ) ;
2019-10-08 15:33:58 +02:00
}
2024-03-19 10:36:08 +01:00
static bool fast_matches_simple_selector ( CSS : : Selector : : SimpleSelector const & simple_selector , Optional < CSS : : CSSStyleSheet const & > style_sheet_for_rule , DOM : : Element const & element )
{
switch ( simple_selector . type ) {
case CSS : : Selector : : SimpleSelector : : Type : : Universal :
return matches_namespace ( simple_selector . qualified_name ( ) , element , style_sheet_for_rule ) ;
case CSS : : Selector : : SimpleSelector : : Type : : TagName :
if ( element . document ( ) . document_type ( ) = = DOM : : Document : : Type : : HTML ) {
if ( simple_selector . qualified_name ( ) . name . lowercase_name ! = element . local_name ( ) )
return false ;
} else if ( ! Infra : : is_ascii_case_insensitive_match ( simple_selector . qualified_name ( ) . name . name , element . local_name ( ) ) ) {
return false ;
}
return matches_namespace ( simple_selector . qualified_name ( ) , element , style_sheet_for_rule ) ;
case CSS : : Selector : : SimpleSelector : : Type : : Class :
return element . has_class ( simple_selector . name ( ) ) ;
case CSS : : Selector : : SimpleSelector : : Type : : Id :
return simple_selector . name ( ) = = element . id ( ) ;
case CSS : : Selector : : SimpleSelector : : Type : : Attribute :
return matches_attribute ( simple_selector . attribute ( ) , style_sheet_for_rule , element ) ;
2024-03-19 10:58:10 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : PseudoClass :
return matches_pseudo_class ( simple_selector . pseudo_class ( ) , style_sheet_for_rule , element , nullptr ) ;
2024-03-19 10:36:08 +01:00
default :
VERIFY_NOT_REACHED ( ) ;
}
}
static bool fast_matches_compound_selector ( CSS : : Selector : : CompoundSelector const & compound_selector , Optional < CSS : : CSSStyleSheet const & > style_sheet_for_rule , DOM : : Element const & element )
{
for ( auto const & simple_selector : compound_selector . simple_selectors ) {
if ( ! fast_matches_simple_selector ( simple_selector , style_sheet_for_rule , element ) )
return false ;
}
return true ;
}
2024-03-20 09:00:44 -04:00
bool fast_matches ( CSS : : Selector const & selector , Optional < CSS : : CSSStyleSheet const & > style_sheet_for_rule , DOM : : Element const & element_to_match )
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 ;
if ( ! fast_matches_compound_selector ( selector . compound_selectors ( ) . last ( ) , style_sheet_for_rule , * current ) )
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-04-05 13:47:48 -07:00
JS : : GCPtr < 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 ( ) ) {
if ( fast_matches_compound_selector ( * compound_selector , style_sheet_for_rule , * current ) )
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 ;
if ( ! fast_matches_compound_selector ( * compound_selector , style_sheet_for_rule , * current ) ) {
if ( backtrack_state . element ) {
current = backtrack_state . element ;
compound_selector_index = backtrack_state . compound_selector_index ;
continue ;
}
return false ;
}
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
}
}
bool can_use_fast_matches ( CSS : : Selector const & selector )
{
for ( auto const & compound_selector : selector . compound_selectors ( ) ) {
if ( compound_selector . combinator ! = CSS : : Selector : : Combinator : : None
& & compound_selector . combinator ! = CSS : : Selector : : Combinator : : Descendant
& & compound_selector . combinator ! = CSS : : Selector : : Combinator : : ImmediateChild ) {
return false ;
}
for ( auto const & simple_selector : compound_selector . simple_selectors ) {
2024-03-19 10:58:10 +01:00
if ( simple_selector . type = = CSS : : Selector : : SimpleSelector : : Type : : PseudoClass ) {
auto const pseudo_class = simple_selector . pseudo_class ( ) . type ;
if ( pseudo_class ! = CSS : : PseudoClass : : FirstChild
& & pseudo_class ! = CSS : : PseudoClass : : LastChild
& & pseudo_class ! = CSS : : PseudoClass : : OnlyChild
& & pseudo_class ! = CSS : : PseudoClass : : Hover
& & pseudo_class ! = CSS : : PseudoClass : : Active
& & pseudo_class ! = CSS : : PseudoClass : : Focus
& & pseudo_class ! = CSS : : PseudoClass : : FocusVisible
& & pseudo_class ! = CSS : : PseudoClass : : FocusWithin
& & pseudo_class ! = CSS : : PseudoClass : : Link
& & pseudo_class ! = CSS : : PseudoClass : : AnyLink
& & pseudo_class ! = CSS : : PseudoClass : : Visited
& & pseudo_class ! = CSS : : PseudoClass : : LocalLink
& & pseudo_class ! = CSS : : PseudoClass : : Empty
& & pseudo_class ! = CSS : : PseudoClass : : Root
& & pseudo_class ! = CSS : : PseudoClass : : Enabled
& & pseudo_class ! = CSS : : PseudoClass : : Disabled
& & pseudo_class ! = CSS : : PseudoClass : : Checked ) {
return false ;
}
} else if ( simple_selector . type ! = CSS : : Selector : : SimpleSelector : : Type : : TagName
2024-03-19 10:36:08 +01:00
& & simple_selector . type ! = CSS : : Selector : : SimpleSelector : : Type : : Universal
& & simple_selector . type ! = CSS : : Selector : : SimpleSelector : : Type : : Class
& & simple_selector . type ! = CSS : : Selector : : SimpleSelector : : Type : : Id
& & simple_selector . type ! = CSS : : Selector : : SimpleSelector : : Type : : Attribute ) {
return false ;
}
}
}
return true ;
}
2019-10-08 15:33:58 +02:00
}