2021-09-28 15:17:11 +01:00
/*
2025-04-14 15:36:42 +01:00
* Copyright ( c ) 2021 - 2025 , Sam Atkins < sam @ ladybird . org >
2021-09-28 15:17:11 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2021-10-08 16:40:50 +01:00
# include <AK/TypeCasts.h>
2022-09-24 16:34:04 -06:00
# include <LibWeb/Bindings/CSSRuleListPrototype.h>
# include <LibWeb/Bindings/Intrinsics.h>
2021-10-08 16:40:50 +01:00
# include <LibWeb/CSS/CSSImportRule.h>
2023-05-26 23:30:54 +03:30
# include <LibWeb/CSS/CSSKeyframesRule.h>
2024-09-02 16:45:25 +01:00
# include <LibWeb/CSS/CSSLayerBlockRule.h>
2021-10-08 17:02:47 +01:00
# include <LibWeb/CSS/CSSMediaRule.h>
2025-04-14 15:36:42 +01:00
# include <LibWeb/CSS/CSSNestedDeclarations.h>
2022-08-07 15:46:44 +02:00
# include <LibWeb/CSS/CSSRule.h>
2021-09-28 15:17:11 +01:00
# include <LibWeb/CSS/CSSRuleList.h>
2021-10-08 17:48:14 +01:00
# include <LibWeb/CSS/CSSSupportsRule.h>
2022-04-23 11:29:31 +01:00
# include <LibWeb/CSS/Parser/Parser.h>
2022-08-28 13:42:07 +02:00
# include <LibWeb/HTML/Window.h>
2021-09-28 15:17:11 +01:00
namespace Web : : CSS {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( CSSRuleList ) ;
2023-11-19 19:47:52 +01:00
2025-04-14 16:02:29 +01:00
GC : : Ref < CSSRuleList > CSSRuleList : : create ( JS : : Realm & realm , ReadonlySpan < GC : : Ref < CSSRule > > rules )
2022-08-07 13:51:40 +02:00
{
2024-11-14 05:50:17 +13:00
auto rule_list = realm . create < CSSRuleList > ( realm ) ;
2025-04-14 16:02:29 +01:00
for ( auto rule : rules )
rule_list - > m_rules . append ( rule ) ;
2022-08-07 15:46:44 +02:00
return rule_list ;
2022-08-07 13:51:40 +02:00
}
2022-09-24 16:34:04 -06:00
CSSRuleList : : CSSRuleList ( JS : : Realm & realm )
2024-01-09 16:05:03 -07:00
: Bindings : : PlatformObject ( realm )
2021-09-28 15:17:11 +01:00
{
2024-01-09 16:05:03 -07:00
m_legacy_platform_object_flags = LegacyPlatformObjectFlags { . supports_indexed_properties = 1 } ;
2021-09-28 15:17:11 +01:00
}
2023-08-07 08:41:28 +02:00
void CSSRuleList : : initialize ( JS : : Realm & realm )
2023-01-10 06:56:59 -05:00
{
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( CSSRuleList ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2023-01-10 06:56:59 -05:00
}
2022-08-07 15:46:44 +02:00
void CSSRuleList : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2024-04-15 13:58:21 +02:00
visitor . visit ( m_rules ) ;
2025-04-14 17:27:00 +01:00
visitor . visit ( m_owner_rule ) ;
2022-08-07 15:46:44 +02:00
}
2025-06-23 22:40:37 +12:00
// AD-HOC: The spec doesn't include a declared_namespaces parameter, but we need it to handle parsing of namespaced selectors.
2025-04-14 15:36:42 +01:00
// https://drafts.csswg.org/cssom/#insert-a-css-rule
2025-06-23 22:40:37 +12:00
WebIDL : : ExceptionOr < unsigned > CSSRuleList : : insert_a_css_rule ( Variant < StringView , CSSRule * > rule , u32 index , Nested nested , HashTable < FlyString > const & declared_namespaces )
2021-09-29 21:12:39 +02:00
{
// 1. Set length to the number of items in list.
auto length = m_rules . size ( ) ;
// 2. If index is greater than length, then throw an IndexSizeError exception.
if ( index > length )
2025-08-07 19:31:52 -04:00
return WebIDL : : IndexSizeError : : create ( realm ( ) , " CSS rule index out of bounds. " _utf16 ) ;
2021-09-29 21:12:39 +02:00
// 3. Set new rule to the results of performing parse a CSS rule on argument rule.
2022-04-23 11:29:31 +01:00
// NOTE: The insert-a-css-rule spec expects `rule` to be a string, but the CSSStyleSheet.insertRule()
// spec calls this algorithm with an already-parsed CSSRule. So, we use a Variant and skip step 3
// if that variant holds a CSSRule already.
2025-06-23 22:40:37 +12:00
2022-08-07 15:46:44 +02:00
CSSRule * new_rule = nullptr ;
2022-04-23 11:29:31 +01:00
if ( rule . has < StringView > ( ) ) {
2025-06-23 22:40:37 +12:00
Parser : : ParsingParams parsing_params { realm ( ) } ;
parsing_params . declared_namespaces = declared_namespaces ;
new_rule = parse_css_rule ( parsing_params , rule . get < StringView > ( ) ) ;
2022-04-23 11:29:31 +01:00
} else {
2022-08-07 15:46:44 +02:00
new_rule = rule . get < CSSRule * > ( ) ;
2022-04-23 11:29:31 +01:00
}
2025-04-14 15:36:42 +01:00
// 4. If new rule is a syntax error, and nested is set, perform the following substeps:
if ( ! new_rule & & nested = = Nested : : Yes ) {
2025-06-21 21:57:57 +12:00
Parser : : ParsingParams parsing_params { realm ( ) } ;
parsing_params . rule_context = rule_context ( ) ;
2025-06-23 22:40:37 +12:00
parsing_params . declared_namespaces = declared_namespaces ;
2025-04-14 15:36:42 +01:00
// - Set declarations to the results of performing parse a CSS declaration block, on argument rule.
2025-04-14 17:27:00 +01:00
auto declarations = parse_css_property_declaration_block ( parsing_params , rule . get < StringView > ( ) ) ;
2025-04-14 15:36:42 +01:00
// - If declarations is empty, throw a SyntaxError exception.
if ( declarations . custom_properties . is_empty ( ) & & declarations . properties . is_empty ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : SyntaxError : : create ( realm ( ) , " Unable to parse CSS declarations block. " _utf16 ) ;
2025-04-14 15:36:42 +01:00
// - Otherwise, set new rule to a new nested declarations rule with declarations as it contents.
new_rule = CSSNestedDeclarations : : create ( realm ( ) , CSSStyleProperties : : create ( realm ( ) , move ( declarations . properties ) , move ( declarations . custom_properties ) ) ) ;
}
// 5. If new rule is a syntax error, throw a SyntaxError exception.
2022-04-23 11:29:31 +01:00
if ( ! new_rule )
2025-08-07 19:31:52 -04:00
return WebIDL : : SyntaxError : : create ( realm ( ) , " Unable to parse CSS rule. " _utf16 ) ;
2021-09-29 21:12:39 +02:00
2025-06-21 03:16:34 +12:00
auto has_rule_of_type_other_than_specified_before_index = [ & ] ( Vector < CSSRule : : Type > types , size_t index ) {
for ( size_t i = 0 ; i < index ; i + + ) {
if ( ! any_of ( types , [ & ] ( auto type ) { return m_rules [ i ] - > type ( ) = = type ; } ) )
return true ;
}
return false ;
} ;
auto has_rule_of_type_at_or_after_index = [ & ] ( CSSRule : : Type type , size_t index ) {
for ( size_t i = index ; i < m_rules . size ( ) ; i + + ) {
if ( m_rules [ i ] - > type ( ) = = type )
return true ;
}
return false ;
} ;
// 6. If new rule cannot be inserted into list at the zero-indexed position index due to constraints specified by CSS, then throw a HierarchyRequestError exception. [CSS21]
// "Any @import rules must precede all other valid at-rules and style rules in a style sheet
// (ignoring @charset and @layer statement rules) and must not have any other valid at-rules
// or style rules between it and previous @import rules, or else the @import rule is invalid."
// https://drafts.csswg.org/css-cascade-5/#at-import
//
// "Any @namespace rules must follow all @charset and @import rules and precede all other
// non-ignored at-rules and style rules in a style sheet.
auto rule_is_disallowed = false ;
switch ( new_rule - > type ( ) ) {
case CSSRule : : Type : : LayerStatement :
break ;
case CSSRule : : Type : : Import :
rule_is_disallowed = has_rule_of_type_other_than_specified_before_index ( { CSSRule : : Type : : Import , CSSRule : : Type : : LayerStatement } , index ) ;
break ;
case CSSRule : : Type : : Namespace :
rule_is_disallowed = has_rule_of_type_at_or_after_index ( CSSRule : : Type : : Import , index ) | | has_rule_of_type_other_than_specified_before_index ( { CSSRule : : Type : : Import , CSSRule : : Type : : Namespace , CSSRule : : Type : : LayerStatement } , index ) ;
break ;
default :
rule_is_disallowed = has_rule_of_type_at_or_after_index ( CSSRule : : Type : : Import , index ) | | has_rule_of_type_at_or_after_index ( CSSRule : : Type : : Namespace , index ) ;
break ;
}
2025-06-21 21:57:57 +12:00
// FIXME: There are more constraints that we should check here - Parser::is_valid_in_the_current_context is probably a good reference for that.
if ( rule_is_disallowed | | ( nested = = Nested : : Yes & & first_is_one_of ( new_rule - > type ( ) , CSSRule : : Type : : Import , CSSRule : : Type : : Namespace ) ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Cannot insert rule at specified index. " _utf16 ) ;
2021-09-29 21:12:39 +02:00
2025-06-09 22:33:36 +12:00
// 7. If new rule is an @namespace at-rule, and list contains anything other than @import at-rules, and @namespace at-rules, throw an InvalidStateError exception.
if ( new_rule - > type ( ) = = CSSRule : : Type : : Namespace & & any_of ( m_rules , [ ] ( auto existing_rule ) { return existing_rule - > type ( ) ! = CSSRule : : Type : : Import & & existing_rule - > type ( ) ! = CSSRule : : Type : : Namespace ; } ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : InvalidStateError : : create ( realm ( ) , " Cannot insert @namespace rule into a stylesheet with non-namespace/import rules " _utf16 ) ;
2021-09-29 21:12:39 +02:00
2025-04-14 15:36:42 +01:00
// 8. Insert new rule into list at the zero-indexed position index.
2022-08-07 15:46:44 +02:00
m_rules . insert ( index , * new_rule ) ;
2021-09-29 21:12:39 +02:00
2025-04-14 15:36:42 +01:00
// 9. Return index.
2023-07-31 19:48:50 +01:00
if ( on_change )
on_change ( ) ;
2021-09-29 21:12:39 +02:00
return index ;
}
2021-10-15 19:38:39 +01:00
// https://www.w3.org/TR/cssom/#remove-a-css-rule
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > CSSRuleList : : remove_a_css_rule ( u32 index )
2021-09-29 20:28:32 +02:00
{
// 1. Set length to the number of items in list.
auto length = m_rules . size ( ) ;
// 2. If index is greater than or equal to length, then throw an IndexSizeError exception.
if ( index > = length )
2025-08-07 19:31:52 -04:00
return WebIDL : : IndexSizeError : : create ( realm ( ) , " CSS rule index out of bounds. " _utf16 ) ;
2021-09-29 20:28:32 +02:00
// 3. Set old rule to the indexth item in list.
2022-08-07 15:46:44 +02:00
CSSRule & old_rule = m_rules [ index ] ;
2021-09-29 20:28:32 +02:00
2025-04-24 11:07:54 +02:00
// 4. If old rule is an @namespace at-rule, and list contains anything other than @import at-rules, and @namespace at-rules, throw an InvalidStateError exception.
if ( old_rule . type ( ) = = CSSRule : : Type : : Namespace ) {
for ( auto & rule : m_rules ) {
if ( rule - > type ( ) ! = CSSRule : : Type : : Import & & rule - > type ( ) ! = CSSRule : : Type : : Namespace )
2025-08-07 19:31:52 -04:00
return WebIDL : : InvalidStateError : : create ( realm ( ) , " Cannot remove @namespace rule from a stylesheet with non-namespace/import rules. " _utf16 ) ;
2025-04-24 11:07:54 +02:00
}
}
2021-09-29 20:28:32 +02:00
// 5. Remove rule old rule from list at the zero-indexed position index.
m_rules . remove ( index ) ;
2022-04-22 19:56:22 +01:00
// 6. Set old rule’ s parent CSS rule and parent CSS style sheet to null.
2025-06-24 00:42:19 +12:00
// NOTE: We set the parent stylesheet to null within set_parent_rule.
2022-08-07 15:46:44 +02:00
old_rule . set_parent_rule ( nullptr ) ;
2021-09-29 20:28:32 +02:00
2023-07-31 19:48:50 +01:00
if ( on_change )
on_change ( ) ;
2021-09-29 20:28:32 +02:00
return { } ;
}
2024-09-03 11:43:20 +01:00
void CSSRuleList : : for_each_effective_rule ( TraversalOrder order , Function < void ( Web : : CSS : : CSSRule const & ) > const & callback ) const
2021-10-08 16:40:50 +01:00
{
2022-01-19 09:42:49 +01:00
for ( auto const & rule : m_rules ) {
2024-09-03 11:43:20 +01:00
if ( order = = TraversalOrder : : Preorder )
callback ( rule ) ;
2023-05-26 23:30:54 +03:30
switch ( rule - > type ( ) ) {
case CSSRule : : Type : : Import : {
auto const & import_rule = static_cast < CSSImportRule const & > ( * rule ) ;
if ( import_rule . loaded_style_sheet ( ) )
2024-09-03 11:43:20 +01:00
import_rule . loaded_style_sheet ( ) - > for_each_effective_rule ( order , callback ) ;
2023-05-26 23:30:54 +03:30
break ;
}
2024-09-02 16:45:25 +01:00
case CSSRule : : Type : : LayerBlock :
2023-05-26 23:30:54 +03:30
case CSSRule : : Type : : Media :
2025-05-15 11:48:56 +01:00
case CSSRule : : Type : : Page :
2024-10-15 15:49:55 +01:00
case CSSRule : : Type : : Style :
2023-05-26 23:30:54 +03:30
case CSSRule : : Type : : Supports :
2024-09-03 11:43:20 +01:00
static_cast < CSSGroupingRule const & > ( * rule ) . for_each_effective_rule ( order , callback ) ;
2023-05-26 23:30:54 +03:30
break ;
2024-09-02 16:45:25 +01:00
case CSSRule : : Type : : FontFace :
case CSSRule : : Type : : Keyframe :
2024-09-03 11:43:20 +01:00
case CSSRule : : Type : : Keyframes :
2024-09-02 16:45:25 +01:00
case CSSRule : : Type : : LayerStatement :
2025-05-15 11:48:56 +01:00
case CSSRule : : Type : : Margin :
2023-07-29 11:51:15 -05:00
case CSSRule : : Type : : Namespace :
2024-10-15 15:59:31 +01:00
case CSSRule : : Type : : NestedDeclarations :
2024-10-17 23:28:09 +01:00
case CSSRule : : Type : : Property :
2023-07-29 11:51:15 -05:00
break ;
2021-10-08 16:40:50 +01:00
}
2024-09-03 11:43:20 +01:00
if ( order = = TraversalOrder : : Postorder )
callback ( rule ) ;
2021-10-08 16:40:50 +01:00
}
}
2025-10-07 00:54:19 +13:00
bool CSSRuleList : : evaluate_media_queries ( DOM : : Document const & document )
2021-10-08 20:21:46 +01:00
{
2022-02-17 12:34:36 +00:00
bool any_media_queries_changed_match_state = false ;
2021-10-08 20:21:46 +01:00
for ( auto & rule : m_rules ) {
2023-02-26 16:09:02 -07:00
switch ( rule - > type ( ) ) {
2021-10-08 20:21:46 +01:00
case CSSRule : : Type : : Import : {
2025-01-21 09:12:05 -05:00
auto & import_rule = as < CSSImportRule > ( * rule ) ;
2025-10-07 00:54:19 +13:00
if ( import_rule . loaded_style_sheet ( ) & & import_rule . loaded_style_sheet ( ) - > evaluate_media_queries ( document ) )
2022-02-17 12:34:36 +00:00
any_media_queries_changed_match_state = true ;
2021-10-08 20:21:46 +01:00
break ;
}
2024-09-02 16:45:25 +01:00
case CSSRule : : Type : : LayerBlock : {
2025-01-21 09:12:05 -05:00
auto & layer_rule = as < CSSLayerBlockRule > ( * rule ) ;
2025-10-07 00:54:19 +13:00
if ( layer_rule . css_rules ( ) . evaluate_media_queries ( document ) )
2024-09-02 16:45:25 +01:00
any_media_queries_changed_match_state = true ;
break ;
}
2021-10-08 20:21:46 +01:00
case CSSRule : : Type : : Media : {
2025-01-21 09:12:05 -05:00
auto & media_rule = as < CSSMediaRule > ( * rule ) ;
2022-02-17 12:34:36 +00:00
bool did_match = media_rule . condition_matches ( ) ;
2025-10-07 00:54:19 +13:00
bool now_matches = media_rule . evaluate ( document ) ;
2022-02-17 12:34:36 +00:00
if ( did_match ! = now_matches )
any_media_queries_changed_match_state = true ;
2025-10-07 00:54:19 +13:00
if ( now_matches & & media_rule . css_rules ( ) . evaluate_media_queries ( document ) )
2022-02-17 12:34:36 +00:00
any_media_queries_changed_match_state = true ;
2021-10-08 20:21:46 +01:00
break ;
}
case CSSRule : : Type : : Supports : {
2025-01-21 09:12:05 -05:00
auto & supports_rule = as < CSSSupportsRule > ( * rule ) ;
2025-10-07 00:54:19 +13:00
if ( supports_rule . condition_matches ( ) & & supports_rule . css_rules ( ) . evaluate_media_queries ( document ) )
2022-02-17 12:34:36 +00:00
any_media_queries_changed_match_state = true ;
2021-10-08 20:21:46 +01:00
break ;
}
2024-11-04 17:26:19 +00:00
case CSSRule : : Type : : Style : {
2025-01-21 09:12:05 -05:00
auto & style_rule = as < CSSStyleRule > ( * rule ) ;
2025-10-07 00:54:19 +13:00
if ( style_rule . css_rules ( ) . evaluate_media_queries ( document ) )
2024-11-04 17:26:19 +00:00
any_media_queries_changed_match_state = true ;
break ;
}
2024-09-02 16:45:25 +01:00
case CSSRule : : Type : : FontFace :
2023-05-26 23:30:54 +03:30
case CSSRule : : Type : : Keyframe :
case CSSRule : : Type : : Keyframes :
2024-09-02 16:45:25 +01:00
case CSSRule : : Type : : LayerStatement :
2025-05-15 11:48:56 +01:00
case CSSRule : : Type : : Margin :
2023-07-29 11:51:15 -05:00
case CSSRule : : Type : : Namespace :
2024-10-15 15:59:31 +01:00
case CSSRule : : Type : : NestedDeclarations :
2024-10-17 23:28:09 +01:00
case CSSRule : : Type : : Property :
2025-05-13 12:17:41 +01:00
case CSSRule : : Type : : Page :
2023-05-26 23:30:54 +03:30
break ;
2021-10-08 20:21:46 +01:00
}
}
2022-02-17 12:34:36 +00:00
return any_media_queries_changed_match_state ;
2021-10-08 20:21:46 +01:00
}
2024-07-25 18:15:51 +12:00
Optional < JS : : Value > CSSRuleList : : item_value ( size_t index ) const
2022-08-07 13:51:40 +02:00
{
2024-07-25 18:15:51 +12:00
if ( auto value = item ( index ) )
return value ;
return { } ;
2022-08-07 13:51:40 +02:00
}
2025-04-14 17:27:00 +01:00
Vector < Parser : : RuleContext > CSSRuleList : : rule_context ( ) const
{
Vector < Parser : : RuleContext > context ;
for ( auto * rule = m_owner_rule . ptr ( ) ; rule ; rule = rule - > parent_rule ( ) )
context . append ( Parser : : rule_context_type_for_rule ( rule - > type ( ) ) ) ;
context . reverse ( ) ;
return context ;
}
2021-09-28 15:17:11 +01:00
}