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>
2023-08-16 12:11:14 +01:00
# include <LibWeb/CSS/ValueID.h>
2020-03-07 10:32:51 +01:00
# 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 ( ) )
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!
auto attribute_name = attribute . qualified_name . name . name . to_deprecated_fly_string ( ) ;
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-08-08 16:19:20 +01:00
return element . has_attribute ( attribute_name ) ;
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-08-08 16:19:20 +01:00
? Infra : : is_ascii_case_insensitive_match ( element . attribute ( attribute_name ) , attribute . value )
: element . attribute ( attribute_name ) = = 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-08-08 16:19:20 +01:00
auto const view = element . attribute ( attribute_name ) . 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-08-08 16:19:20 +01:00
& & element . attribute ( attribute_name ) . contains ( attribute . value , case_sensitivity ) ;
2021-07-28 12:34:05 +01:00
case CSS : : Selector : : SimpleSelector : : Attribute : : MatchType : : StartsWithSegment : {
2023-08-08 16:19:20 +01:00
auto const element_attr_value = element . attribute ( attribute_name ) ;
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-08-08 16:19:20 +01:00
& & element . attribute ( attribute_name ) . 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-08-08 16:19:20 +01:00
& & element . attribute ( attribute_name ) . 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-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 :
2022-09-14 13:29:07 +02:00
return matches_link_pseudo_class ( element ) ;
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-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-01 12:31:41 +01:00
}
2021-07-12 16:18:00 +01:00
return false ;
}
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 : {
auto qualified_name = component . qualified_name ( ) ;
// 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 ) {
if ( qualified_name . name . lowercase_name ! = element . local_name ( ) . view ( ) )
return false ;
} else if ( ! Infra : : is_ascii_case_insensitive_match ( qualified_name . name . name , element . local_name ( ) ) ) {
return false ;
}
}
// Match the namespace
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 ( ) . has_value ( ) )
return true ;
// "Otherwise it is equivalent to ns|E where ns is the default namespace."
return element . namespace_ ( ) = = style_sheet_for_rule - > default_namespace ( ) ;
case CSS : : Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : None :
// "elements with name E without a namespace"
return element . namespace_ ( ) . is_empty ( ) ;
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_ ( ) ;
}
VERIFY_NOT_REACHED ( ) ;
}
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 ( ) ) ;
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-08-08 15:11:48 +01:00
bool matches ( CSS : : Selector const & selector , Optional < CSS : : CSSStyleSheet const & > style_sheet_for_rule , 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-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
}
}