2020-01-18 09:38:21 +01:00
/*
2022-02-10 20:52:58 +01:00
* Copyright ( c ) 2018 - 2022 , 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>
# include <LibWeb/DOM/Document.h>
# include <LibWeb/DOM/Element.h>
# 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>
# 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>
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 ( ) ) {
auto lang = e - > attribute ( HTML : : AttributeNames : : lang ) ;
if ( ! lang . is_null ( ) ) {
2023-03-09 18:07:49 +01:00
element_language = FlyString : : from_deprecated_fly_string ( lang ) . release_value_but_fixme_should_propagate_errors ( ) ;
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 ( ) )
return false ;
if ( language = = " * " sv )
return true ;
2023-02-18 15:42:55 +00:00
if ( ! element_language . to_string ( ) . contains ( ' - ' ) )
return Infra : : is_ascii_case_insensitive_match ( element_language , language ) ;
auto parts = element_language . to_string ( ) . split_limit ( ' - ' , 2 ) . release_value_but_fixme_should_propagate_errors ( ) ;
return Infra : : is_ascii_case_insensitive_match ( parts [ 0 ] , language ) ;
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 ;
}
2022-02-05 17:41:43 +02:00
static inline bool matches_attribute ( CSS : : Selector : : SimpleSelector : : Attribute const & attribute , DOM : : Element const & element )
2021-07-12 14:58:03 +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.
2023-02-18 15:42:55 +00:00
return element . has_attribute ( attribute . name . to_string ( ) . to_deprecated_string ( ) ) ;
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
2023-02-18 15:42:55 +00:00
? Infra : : is_ascii_case_insensitive_match ( element . attribute ( attribute . name . to_string ( ) . to_deprecated_string ( ) ) , attribute . value )
: element . attribute ( attribute . name . to_string ( ) . to_deprecated_string ( ) ) = = attribute . value . to_deprecated_string ( ) ;
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 ;
}
2023-02-18 15:42:55 +00:00
auto const view = element . attribute ( attribute . name . to_string ( ) . to_deprecated_string ( ) ) . 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 ( )
2023-02-18 15:42:55 +00:00
& & element . attribute ( attribute . name . to_string ( ) . to_deprecated_string ( ) ) . contains ( attribute . value , case_sensitivity ) ;
2021-07-28 12:34:05 +01:00
case CSS : : Selector : : SimpleSelector : : Attribute : : MatchType : : StartsWithSegment : {
2023-02-18 15:42:55 +00:00
auto const element_attr_value = element . attribute ( attribute . name . to_string ( ) . to_deprecated_string ( ) ) ;
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 ;
}
auto segments = element_attr_value . 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 ( )
2023-02-18 15:42:55 +00:00
& & element . attribute ( attribute . name . to_string ( ) . to_deprecated_string ( ) ) . 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 ( )
2023-02-18 15:42:55 +00:00
& & element . attribute ( attribute . name . to_string ( ) . to_deprecated_string ( ) ) . 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-03-20 23:42:17 +01:00
static inline bool matches_pseudo_class ( CSS : : Selector : : SimpleSelector : : PseudoClass const & pseudo_class , 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 ) {
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Link :
2022-09-14 13:29:07 +02:00
return matches_link_pseudo_class ( element ) ;
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Visited :
2020-06-13 00:21:42 +02:00
// FIXME: Maybe match this selector sometimes?
return false ;
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Active :
return element . is_active ( ) ;
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Hover :
return matches_hover_pseudo_class ( element ) ;
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Focus :
return element . is_focused ( ) ;
2022-03-20 13:28:32 +00:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : FocusWithin : {
auto * focused_element = element . document ( ) . focused_element ( ) ;
return focused_element & & element . is_inclusive_ancestor_of ( * focused_element ) ;
}
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : FirstChild :
return ! element . previous_element_sibling ( ) ;
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : LastChild :
return ! element . next_element_sibling ( ) ;
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : OnlyChild :
return ! ( element . previous_element_sibling ( ) | | element . next_element_sibling ( ) ) ;
2022-02-25 17:14:14 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Empty : {
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 ;
}
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Root :
2022-02-10 20:52:58 +01:00
return is < HTML : : HTMLHtmlElement > ( element ) ;
2023-07-05 11:43:57 +02:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Host :
// FIXME: Implement :host selector.
return false ;
2023-03-20 23:42:17 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Scope :
return scope ? & element = = scope : is < HTML : : HTMLHtmlElement > ( element ) ;
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : FirstOfType :
2022-02-17 22:43:22 +01:00
return ! previous_sibling_with_same_tag_name ( element ) ;
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : LastOfType :
2022-02-17 22:43:22 +01:00
return ! next_sibling_with_same_tag_name ( element ) ;
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : OnlyOfType :
return ! previous_sibling_with_same_tag_name ( element ) & & ! next_sibling_with_same_tag_name ( element ) ;
2022-03-20 12:39:11 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Lang :
return matches_lang_pseudo_class ( element , pseudo_class . languages ) ;
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : 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 ( ) ;
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : 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 ( ) ;
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Checked :
2022-03-16 23:29:17 +01:00
return matches_checked_pseudo_class ( element ) ;
2023-03-20 04:35:43 -04:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Indeterminate :
return matches_indeterminate_pseudo_class ( element ) ;
2023-03-29 23:48:40 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Defined :
return element . is_defined ( ) ;
2022-03-17 15:28:42 +00:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Is :
2022-03-17 16:13:13 +00:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Where :
2022-03-17 15:28:42 +00:00
for ( auto & selector : pseudo_class . argument_selector_list ) {
if ( matches ( selector , element ) )
return true ;
}
return false ;
2021-07-12 17:58:47 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Not :
2022-03-17 15:28:42 +00:00
for ( auto & selector : pseudo_class . argument_selector_list ) {
2021-07-12 17:58:47 +01:00
if ( matches ( selector , element ) )
return false ;
}
return true ;
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : NthChild :
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : NthLastChild :
2022-02-26 13:53:13 +00:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : NthOfType :
2023-08-01 12:31:41 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : 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 ;
2022-03-17 19:13:51 +00:00
auto matches_selector_list = [ ] ( CSS : : SelectorList const & list , DOM : : Element const & element ) {
if ( list . is_empty ( ) )
return true ;
for ( auto const & child_selector : list ) {
if ( matches ( child_selector , element ) ) {
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 ) {
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : 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 ;
}
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : 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 ;
}
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : NthOfType : {
for ( auto * child = previous_sibling_with_same_tag_name ( element ) ; child ; child = previous_sibling_with_same_tag_name ( * child ) )
+ + index ;
break ;
}
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : NthLastOfType : {
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-01 12:31:41 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Playing : {
if ( ! is < HTML : : HTMLMediaElement > ( element ) )
return false ;
auto const & media_element = static_cast < HTML : : HTMLMediaElement const & > ( element ) ;
return ! media_element . paused ( ) ;
}
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Paused : {
if ( ! is < HTML : : HTMLMediaElement > ( element ) )
return false ;
auto const & media_element = static_cast < HTML : : HTMLMediaElement const & > ( element ) ;
return media_element . paused ( ) ;
}
2023-08-01 13:04:47 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Seeking : {
if ( ! is < HTML : : HTMLMediaElement > ( element ) )
return false ;
auto const & media_element = static_cast < HTML : : HTMLMediaElement const & > ( element ) ;
return media_element . seeking ( ) ;
}
2023-08-01 14:49:28 +01:00
case CSS : : Selector : : SimpleSelector : : PseudoClass : : Type : : Muted : {
if ( ! is < HTML : : HTMLMediaElement > ( element ) )
return false ;
auto const & media_element = static_cast < HTML : : HTMLMediaElement const & > ( element ) ;
return media_element . muted ( ) ;
}
2023-08-01 12:31:41 +01:00
}
2021-07-12 16:18:00 +01:00
return false ;
}
2023-03-20 23:42:17 +01:00
static inline bool matches ( CSS : : Selector : : SimpleSelector const & component , 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 :
2019-11-19 18:22:12 +01:00
return true ;
2020-07-26 20:01:35 +02:00
case CSS : : Selector : : SimpleSelector : : Type : : Id :
2023-02-18 15:42:55 +00:00
return component . name ( ) = = element . attribute ( HTML : : AttributeNames : : id ) . view ( ) ;
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 ( ) ) ;
2020-07-26 20:01:35 +02:00
case CSS : : Selector : : SimpleSelector : : Type : : TagName :
2022-07-03 20:54:13 +02:00
// See https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
2022-09-15 13:51:30 +02:00
if ( element . document ( ) . document_type ( ) = = DOM : : Document : : Type : : HTML )
2023-02-18 15:42:55 +00:00
return component . lowercase_name ( ) = = element . local_name ( ) . view ( ) ;
return Infra : : is_ascii_case_insensitive_match ( component . name ( ) , element . local_name ( ) ) ;
2021-07-12 14:58:03 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : Attribute :
2022-03-21 15:43:59 +00:00
return matches_attribute ( component . attribute ( ) , element ) ;
2021-07-12 16:18:00 +01:00
case CSS : : Selector : : SimpleSelector : : Type : : PseudoClass :
2023-03-20 23:42:17 +01:00
return matches_pseudo_class ( component . pseudo_class ( ) , 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-03-20 23:42:17 +01:00
static inline bool matches ( CSS : : Selector const & selector , 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-03-20 23:42:17 +01:00
if ( ! matches ( simple_selector , 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-03-20 23:42:17 +01:00
if ( matches ( selector , 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-03-20 23:42:17 +01:00
return matches ( selector , 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-03-20 23:42:17 +01:00
return matches ( selector , 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-03-20 23:42:17 +01:00
if ( matches ( selector , 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-03-20 23:42:17 +01:00
bool matches ( CSS : : Selector const & selector , DOM : : Element const & element , Optional < CSS : : Selector : : PseudoElement > 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 ( ) ) ;
2022-02-24 15:54:12 +00:00
if ( pseudo_element . has_value ( ) & & selector . pseudo_element ( ) ! = pseudo_element )
return false ;
if ( ! pseudo_element . has_value ( ) & & selector . pseudo_element ( ) . has_value ( ) )
return false ;
2023-03-20 23:42:17 +01:00
return matches ( selector , selector . compound_selectors ( ) . size ( ) - 1 , element , scope ) ;
2019-10-08 15:33:58 +02:00
}
}