2021-03-07 16:14:04 +01:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2019 - 2022 , Andreas Kling < andreas @ ladybird . org >
2024-08-23 16:46:33 +01:00
* Copyright ( c ) 2022 - 2024 , Sam Atkins < sam @ ladybird . org >
2025-04-15 18:01:54 +01:00
* Copyright ( c ) 2024 - 2025 , Tim Ledbetter < tim . ledbetter @ ladybird . org >
2021-03-07 16:14:04 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2021-03-07 16:14:04 +01:00
*/
2025-02-16 14:45:52 +13:00
# include <LibURL/Parser.h>
2022-09-24 16:34:04 -06:00
# include <LibWeb/Bindings/CSSStyleSheetPrototype.h>
# include <LibWeb/Bindings/Intrinsics.h>
2024-08-23 16:46:33 +01:00
# include <LibWeb/CSS/CSSImportRule.h>
2021-03-07 16:14:04 +01:00
# include <LibWeb/CSS/CSSStyleSheet.h>
2025-11-06 18:27:22 +13:00
# include <LibWeb/CSS/FontComputer.h>
2021-09-29 21:12:39 +02:00
# include <LibWeb/CSS/Parser/Parser.h>
2023-05-08 06:37:18 +02:00
# include <LibWeb/CSS/StyleComputer.h>
2022-03-09 19:57:15 +01:00
# include <LibWeb/CSS/StyleSheetList.h>
# include <LibWeb/DOM/Document.h>
2024-10-25 12:38:19 -06:00
# include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
2024-02-24 07:46:59 +00:00
# include <LibWeb/HTML/Window.h>
2024-02-24 07:46:59 +00:00
# include <LibWeb/Platform/EventLoopPlugin.h>
2022-09-25 17:03:42 +01:00
# include <LibWeb/WebIDL/ExceptionOr.h>
2021-03-07 16:14:04 +01:00
namespace Web : : CSS {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( CSSStyleSheet ) ;
2023-11-19 19:47:52 +01:00
2025-04-08 13:35:26 +01:00
GC : : Ref < CSSStyleSheet > CSSStyleSheet : : create ( JS : : Realm & realm , CSSRuleList & rules , MediaList & media , Optional < : : URL : : URL > location )
2021-03-07 16:14:04 +01:00
{
2024-11-14 05:50:17 +13:00
return realm . create < CSSStyleSheet > ( realm , rules , media , move ( location ) ) ;
2022-08-07 13:14:54 +02:00
}
2024-02-24 07:46:59 +00:00
// https://drafts.csswg.org/cssom/#dom-cssstylesheet-cssstylesheet
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < CSSStyleSheet > > CSSStyleSheet : : construct_impl ( JS : : Realm & realm , Optional < CSSStyleSheetInit > const & options )
2024-02-24 07:46:59 +00:00
{
// 1. Construct a new CSSStyleSheet object sheet.
2025-04-14 16:02:29 +01:00
auto sheet = create ( realm , CSSRuleList : : create ( realm ) , CSS : : MediaList : : create ( realm , { } ) , { } ) ;
2024-02-24 07:46:59 +00:00
2024-10-21 13:48:44 +13:00
// 2. Set sheet’ s location to the base URL of the associated Document for the current principal global object.
2025-01-21 09:12:05 -05:00
auto associated_document = as < HTML : : Window > ( HTML : : current_principal_global_object ( ) ) . document ( ) ;
2025-04-08 13:18:56 +01:00
sheet - > set_location ( associated_document - > base_url ( ) ) ;
2024-02-24 07:46:59 +00:00
// 3. Set sheet’ s stylesheet base URL to the baseURL attribute value from options.
if ( options . has_value ( ) & & options - > base_url . has_value ( ) ) {
2025-04-08 13:35:26 +01:00
Optional < : : URL : : URL > sheet_location_url ;
2024-02-24 07:46:59 +00:00
if ( sheet - > location ( ) . has_value ( ) )
2025-04-08 13:18:56 +01:00
sheet_location_url = sheet - > location ( ) . release_value ( ) ;
2024-02-24 07:46:59 +00:00
// AD-HOC: This isn't explicitly mentioned in the specification, but multiple modern browsers do this.
2025-04-08 13:35:26 +01:00
Optional < : : URL : : URL > url = sheet - > location ( ) . has_value ( ) ? sheet_location_url - > complete_url ( options - > base_url . value ( ) ) : : : URL : : Parser : : basic_parse ( options - > base_url . value ( ) ) ;
2025-02-15 22:55:46 +13:00
if ( ! url . has_value ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : NotAllowedError : : create ( realm , " Constructed style sheets must have a valid base URL " _utf16 ) ;
2024-02-24 07:46:59 +00:00
sheet - > set_base_url ( url ) ;
}
// 4. Set sheet’ s parent CSS style sheet to null.
sheet - > set_parent_css_style_sheet ( nullptr ) ;
// 5. Set sheet’ s owner node to null.
sheet - > set_owner_node ( nullptr ) ;
// 6. Set sheet’ s owner CSS rule to null.
sheet - > set_owner_css_rule ( nullptr ) ;
2025-02-22 10:59:50 +08:00
// 7. Set sheet’ s title to the empty string.
2024-02-24 07:46:59 +00:00
sheet - > set_title ( String { } ) ;
// 8. Unset sheet’ s alternate flag.
sheet - > set_alternate ( false ) ;
// 9. Set sheet’ s origin-clean flag.
sheet - > set_origin_clean ( true ) ;
// 10. Set sheet’ s constructed flag.
sheet - > set_constructed ( true ) ;
// 11. Set sheet’ s Constructor document to the associated Document for the current global object.
sheet - > set_constructor_document ( associated_document ) ;
// 12. If the media attribute of options is a string, create a MediaList object from the string and assign it as sheet’ s media.
// Otherwise, serialize a media query list from the attribute and then create a MediaList object from the resulting string and set it as sheet’ s media.
if ( options . has_value ( ) ) {
if ( options - > media . has < String > ( ) ) {
sheet - > set_media ( options - > media . get < String > ( ) ) ;
} else {
2024-11-15 04:01:23 +13:00
sheet - > m_media = * options - > media . get < GC : : Root < MediaList > > ( ) ;
2024-02-24 07:46:59 +00:00
}
}
// 13. If the disabled attribute of options is true, set sheet’ s disabled flag.
if ( options . has_value ( ) & & options - > disabled )
sheet - > set_disabled ( true ) ;
// 14. Return sheet
return sheet ;
}
2025-04-08 13:35:26 +01:00
CSSStyleSheet : : CSSStyleSheet ( JS : : Realm & realm , CSSRuleList & rules , MediaList & media , Optional < : : URL : : URL > location )
2022-10-23 21:05:34 +03:00
: StyleSheet ( realm , media )
2022-08-07 15:46:44 +02:00
, m_rules ( & rules )
2022-08-07 13:14:54 +02:00
{
2022-03-29 16:51:31 +01:00
if ( location . has_value ( ) )
2025-04-08 13:18:56 +01:00
set_location ( move ( location ) ) ;
2022-04-22 19:56:22 +01:00
for ( auto & rule : * m_rules )
2023-02-26 16:09:02 -07:00
rule - > set_parent_style_sheet ( this ) ;
2023-07-31 17:46:57 +01:00
2024-08-23 16:46:33 +01:00
recalculate_rule_caches ( ) ;
2023-07-31 17:46:57 +01:00
m_rules - > on_change = [ this ] ( ) {
2024-08-23 16:46:33 +01:00
recalculate_rule_caches ( ) ;
2023-07-31 17:46:57 +01:00
} ;
2021-03-07 16:14:04 +01:00
}
2023-08-07 08:41:28 +02:00
void CSSStyleSheet : : initialize ( JS : : Realm & realm )
2023-01-10 06:28:20 -05:00
{
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( CSSStyleSheet ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2023-01-10 06:28:20 -05:00
}
2022-08-07 13:29:49 +02:00
void CSSStyleSheet : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2022-08-07 15:46:44 +02:00
visitor . visit ( m_rules ) ;
visitor . visit ( m_owner_css_rule ) ;
2023-08-03 13:25:56 +02:00
visitor . visit ( m_default_namespace_rule ) ;
2024-02-24 07:46:59 +00:00
visitor . visit ( m_constructor_document ) ;
2024-04-15 13:58:21 +02:00
visitor . visit ( m_namespace_rules ) ;
2024-08-23 16:46:33 +01:00
visitor . visit ( m_import_rules ) ;
2025-01-10 20:00:43 +03:00
visitor . visit ( m_owning_documents_or_shadow_roots ) ;
2025-07-30 14:38:41 +02:00
visitor . visit ( m_associated_font_loaders ) ;
2022-08-07 13:29:49 +02:00
}
2021-10-15 19:38:39 +01:00
// https://www.w3.org/TR/cssom/#dom-cssstylesheet-insertrule
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < unsigned > CSSStyleSheet : : insert_rule ( StringView rule , unsigned index )
2021-09-29 20:28:32 +02:00
{
// FIXME: 1. If the origin-clean flag is unset, throw a SecurityError exception.
2024-02-24 07:46:59 +00:00
// If the disallow modification flag is set, throw a NotAllowedError DOMException.
if ( disallow_modification ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : NotAllowedError : : create ( realm ( ) , " Can't call insert_rule() on non-modifiable stylesheets. " _utf16 ) ;
2021-09-29 20:28:32 +02:00
2021-09-29 21:12:39 +02:00
// 3. Let parsed rule be the return value of invoking parse a rule with rule.
2025-04-08 18:10:38 +01:00
auto parsed_rule = parse_css_rule ( make_parsing_params ( ) , rule ) ;
2021-09-29 20:28:32 +02:00
2021-09-29 21:12:39 +02:00
// 4. If parsed rule is a syntax error, return parsed rule.
if ( ! parsed_rule )
2025-08-07 19:31:52 -04:00
return WebIDL : : SyntaxError : : create ( realm ( ) , " Unable to parse CSS rule. " _utf16 ) ;
2021-09-29 20:28:32 +02:00
2024-02-24 07:46:59 +00:00
// 5. If parsed rule is an @import rule, and the constructed flag is set, throw a SyntaxError DOMException.
if ( constructed ( ) & & parsed_rule - > type ( ) = = CSSRule : : Type : : Import )
2025-08-07 19:31:52 -04:00
return WebIDL : : SyntaxError : : create ( realm ( ) , " Can't insert @import rules into a constructed stylesheet. " _utf16 ) ;
2021-09-29 20:28:32 +02:00
2021-09-29 21:12:39 +02:00
// 6. Return the result of invoking insert a CSS rule rule in the CSS rules at index.
2025-06-23 22:40:37 +12:00
auto result = m_rules - > insert_a_css_rule ( parsed_rule , index , CSSRuleList : : Nested : : No , declared_namespaces ( ) ) ;
2022-03-09 19:57:15 +01:00
if ( ! result . is_exception ( ) ) {
2022-04-22 19:56:22 +01:00
// NOTE: The spec doesn't say where to set the parent style sheet, so we'll do it here.
2022-08-07 15:46:44 +02:00
parsed_rule - > set_parent_style_sheet ( this ) ;
2022-04-22 19:56:22 +01:00
2025-01-10 20:00:43 +03:00
invalidate_owners ( DOM : : StyleInvalidationReason : : StyleSheetInsertRule ) ;
2022-03-09 19:57:15 +01:00
}
return result ;
2021-09-29 20:28:32 +02:00
}
2021-10-15 19:38:39 +01:00
// https://www.w3.org/TR/cssom/#dom-cssstylesheet-deleterule
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > CSSStyleSheet : : delete_rule ( unsigned index )
2021-09-29 20:28:32 +02:00
{
// FIXME: 1. If the origin-clean flag is unset, throw a SecurityError exception.
2024-02-24 07:46:59 +00:00
// 2. If the disallow modification flag is set, throw a NotAllowedError DOMException.
if ( disallow_modification ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : NotAllowedError : : create ( realm ( ) , " Can't call delete_rule() on non-modifiable stylesheets. " _utf16 ) ;
2021-09-29 20:28:32 +02:00
// 3. Remove a CSS rule in the CSS rules at index.
2022-03-09 19:57:15 +01:00
auto result = m_rules - > remove_a_css_rule ( index ) ;
if ( ! result . is_exception ( ) ) {
2025-01-10 20:00:43 +03:00
invalidate_owners ( DOM : : StyleInvalidationReason : : StyleSheetDeleteRule ) ;
2022-03-09 19:57:15 +01:00
}
return result ;
2021-09-29 20:28:32 +02:00
}
2024-02-24 07:46:59 +00:00
// https://drafts.csswg.org/cssom/#dom-cssstylesheet-replace
2024-11-15 04:01:23 +13:00
GC : : Ref < WebIDL : : Promise > CSSStyleSheet : : replace ( String text )
2024-02-24 07:46:59 +00:00
{
2024-10-25 12:38:19 -06:00
auto & realm = this - > realm ( ) ;
2024-02-24 07:46:59 +00:00
// 1. Let promise be a promise
2024-10-25 12:38:19 -06:00
auto promise = WebIDL : : create_promise ( realm ) ;
2024-02-24 07:46:59 +00:00
// 2. If the constructed flag is not set, or the disallow modification flag is set, reject promise with a NotAllowedError DOMException and return promise.
if ( ! constructed ( ) ) {
2025-08-07 19:31:52 -04:00
WebIDL : : reject_promise ( realm , promise , WebIDL : : NotAllowedError : : create ( realm , " Can't call replace() on non-constructed stylesheets " _utf16 ) ) ;
2024-02-24 07:46:59 +00:00
return promise ;
}
if ( disallow_modification ( ) ) {
2025-08-07 19:31:52 -04:00
WebIDL : : reject_promise ( realm , promise , WebIDL : : NotAllowedError : : create ( realm , " Can't call replace() on non-modifiable stylesheets " _utf16 ) ) ;
2024-02-24 07:46:59 +00:00
return promise ;
}
// 3. Set the disallow modification flag.
set_disallow_modification ( true ) ;
// 4. In parallel, do these steps:
2024-11-15 04:01:23 +13:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( realm . heap ( ) , [ & realm , this , text = move ( text ) , promise = GC : : Root ( promise ) ] {
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
2024-10-25 12:38:19 -06:00
2024-02-24 07:46:59 +00:00
// 1. Let rules be the result of running parse a stylesheet’ s contents from text.
2025-04-15 18:01:54 +01:00
auto rules = CSS : : Parser : : Parser : : create ( make_parsing_params ( ) , text ) . parse_as_stylesheet_contents ( ) ;
2024-02-24 07:46:59 +00:00
// 2. If rules contains one or more @import rules, remove those rules from rules.
2024-12-26 14:32:52 +01:00
GC : : RootVector < GC : : Ref < CSSRule > > rules_without_import ( realm . heap ( ) ) ;
2024-02-24 07:46:59 +00:00
for ( auto rule : rules ) {
if ( rule - > type ( ) ! = CSSRule : : Type : : Import )
rules_without_import . append ( rule ) ;
}
// 3. Set sheet’ s CSS rules to rules.
m_rules - > set_rules ( { } , rules_without_import ) ;
// 4. Unset sheet’ s disallow modification flag.
set_disallow_modification ( false ) ;
// 5. Resolve promise with sheet.
2024-10-25 12:38:19 -06:00
WebIDL : : resolve_promise ( realm , * promise , this ) ;
2024-10-31 02:39:29 +13:00
} ) ) ;
2024-02-24 07:46:59 +00:00
return promise ;
}
2024-02-24 07:46:59 +00:00
// https://drafts.csswg.org/cssom/#dom-cssstylesheet-replacesync
WebIDL : : ExceptionOr < void > CSSStyleSheet : : replace_sync ( StringView text )
{
// 1. If the constructed flag is not set, or the disallow modification flag is set, throw a NotAllowedError DOMException.
if ( ! constructed ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : NotAllowedError : : create ( realm ( ) , " Can't call replaceSync() on non-constructed stylesheets " _utf16 ) ;
2024-02-24 07:46:59 +00:00
if ( disallow_modification ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : NotAllowedError : : create ( realm ( ) , " Can't call replaceSync() on non-modifiable stylesheets " _utf16 ) ;
2024-02-24 07:46:59 +00:00
// 2. Let rules be the result of running parse a stylesheet’ s contents from text.
2025-04-15 18:01:54 +01:00
auto rules = CSS : : Parser : : Parser : : create ( make_parsing_params ( ) , text ) . parse_as_stylesheet_contents ( ) ;
2024-02-24 07:46:59 +00:00
// 3. If rules contains one or more @import rules, remove those rules from rules.
2024-12-26 14:32:52 +01:00
GC : : RootVector < GC : : Ref < CSSRule > > rules_without_import ( realm ( ) . heap ( ) ) ;
2024-02-24 07:46:59 +00:00
for ( auto rule : rules ) {
if ( rule - > type ( ) ! = CSSRule : : Type : : Import )
rules_without_import . append ( rule ) ;
}
2025-04-24 14:25:50 +02:00
// NOTE: The spec doesn't say where to set the parent style sheet, so we'll do it here.
for ( auto & rule : rules_without_import ) {
rule - > set_parent_style_sheet ( this ) ;
}
2025-04-15 18:01:54 +01:00
// 4. Set sheet’ s CSS rules to rules.
2024-02-24 07:46:59 +00:00
m_rules - > set_rules ( { } , rules_without_import ) ;
return { } ;
}
2024-02-24 07:46:59 +00:00
// https://drafts.csswg.org/cssom/#dom-cssstylesheet-addrule
WebIDL : : ExceptionOr < WebIDL : : Long > CSSStyleSheet : : add_rule ( Optional < String > selector , Optional < String > style , Optional < WebIDL : : UnsignedLong > index )
{
// 1. Let rule be an empty string.
StringBuilder rule ;
// 2. Append selector to rule.
if ( selector . has_value ( ) )
rule . append ( selector . release_value ( ) ) ;
// 3. Append " { " to rule.
rule . append ( ' { ' ) ;
// 4. If block is not empty, append block, followed by a space, to rule.
if ( style . has_value ( ) & & ! style - > is_empty ( ) )
rule . appendff ( " {} " , style . release_value ( ) ) ;
// 5. Append "}" to rule.
rule . append ( ' } ' ) ;
// 6. Let index be optionalIndex if provided, or the number of CSS rules in the stylesheet otherwise.
// 7. Call insertRule(), with rule and index as arguments.
TRY ( insert_rule ( rule . string_view ( ) , index . value_or ( rules ( ) . length ( ) ) ) ) ;
// 8. Return -1.
return - 1 ;
}
2021-10-15 19:38:39 +01:00
// https://www.w3.org/TR/cssom/#dom-cssstylesheet-removerule
2024-02-24 07:46:59 +00:00
WebIDL : : ExceptionOr < void > CSSStyleSheet : : remove_rule ( Optional < WebIDL : : UnsignedLong > index )
2021-09-29 20:28:32 +02:00
{
// The removeRule(index) method must run the same steps as deleteRule().
2024-02-24 07:46:59 +00:00
return delete_rule ( index . value_or ( 0 ) ) ;
2021-09-29 20:28:32 +02:00
}
2024-09-03 11:43:20 +01:00
void CSSStyleSheet : : for_each_effective_rule ( TraversalOrder order , Function < void ( Web : : CSS : : CSSRule const & ) > const & callback ) const
{
if ( m_media - > matches ( ) )
m_rules - > for_each_effective_rule ( order , callback ) ;
}
2024-10-17 13:48:00 +01:00
void CSSStyleSheet : : for_each_effective_style_producing_rule ( Function < void ( CSSRule const & ) > const & callback ) const
2021-09-30 22:57:35 +02:00
{
2024-09-03 11:43:20 +01:00
for_each_effective_rule ( TraversalOrder : : Preorder , [ & ] ( CSSRule const & rule ) {
2024-10-17 13:48:00 +01:00
if ( rule . type ( ) = = CSSRule : : Type : : Style | | rule . type ( ) = = CSSRule : : Type : : NestedDeclarations )
callback ( rule ) ;
2024-09-03 11:43:20 +01:00
} ) ;
2021-09-30 22:57:35 +02:00
}
2023-05-26 23:30:54 +03:30
void CSSStyleSheet : : for_each_effective_keyframes_at_rule ( Function < void ( CSSKeyframesRule const & ) > const & callback ) const
{
2024-09-03 11:43:20 +01:00
for_each_effective_rule ( TraversalOrder : : Preorder , [ & ] ( CSSRule const & rule ) {
if ( rule . type ( ) = = CSSRule : : Type : : Keyframes )
callback ( static_cast < CSSKeyframesRule const & > ( rule ) ) ;
} ) ;
2023-05-26 23:30:54 +03:30
}
2025-01-10 20:00:43 +03:00
void CSSStyleSheet : : add_owning_document_or_shadow_root ( DOM : : Node & document_or_shadow_root )
{
VERIFY ( document_or_shadow_root . is_document ( ) | | document_or_shadow_root . is_shadow_root ( ) ) ;
m_owning_documents_or_shadow_roots . set ( document_or_shadow_root ) ;
2025-10-15 23:45:38 +13:00
2025-10-16 01:30:31 +13:00
// All owning documents or shadow roots must be part of the same document so we only need to load this style
// sheet's fonts against the document of the first
if ( this - > owning_documents_or_shadow_roots ( ) . size ( ) = = 1 )
2025-11-06 18:27:22 +13:00
document_or_shadow_root . document ( ) . font_computer ( ) . load_fonts_from_sheet ( * this ) ;
2025-10-16 01:30:31 +13:00
2025-10-15 23:45:38 +13:00
for ( auto const & import_rule : m_import_rules ) {
if ( import_rule - > loaded_style_sheet ( ) )
import_rule - > loaded_style_sheet ( ) - > add_owning_document_or_shadow_root ( document_or_shadow_root ) ;
}
2025-01-10 20:00:43 +03:00
}
void CSSStyleSheet : : remove_owning_document_or_shadow_root ( DOM : : Node & document_or_shadow_root )
{
m_owning_documents_or_shadow_roots . remove ( document_or_shadow_root ) ;
2025-10-15 23:45:38 +13:00
2025-10-16 01:30:31 +13:00
// All owning documents or shadow roots must be part of the same document so we only need to unload this style
// sheet's fonts once we have none remaining.
if ( this - > owning_documents_or_shadow_roots ( ) . size ( ) = = 0 )
2025-11-06 18:27:22 +13:00
document_or_shadow_root . document ( ) . font_computer ( ) . unload_fonts_from_sheet ( * this ) ;
2025-10-16 01:30:31 +13:00
2025-10-15 23:45:38 +13:00
for ( auto const & import_rule : m_import_rules ) {
if ( import_rule - > loaded_style_sheet ( ) )
import_rule - > loaded_style_sheet ( ) - > remove_owning_document_or_shadow_root ( document_or_shadow_root ) ;
}
2025-01-10 20:00:43 +03:00
}
void CSSStyleSheet : : invalidate_owners ( DOM : : StyleInvalidationReason reason )
{
2025-04-03 17:20:13 +01:00
m_did_match = { } ;
2025-01-10 20:00:43 +03:00
for ( auto & document_or_shadow_root : m_owning_documents_or_shadow_roots ) {
document_or_shadow_root - > invalidate_style ( reason ) ;
LibWeb: Add StyleScope to keep style caches per Document/ShadowRoot
Before this change, we've been maintaining various StyleComputer caches
at the document level.
This made sense for old-school documents without shadow trees, since
all the style information was document-wide anyway. However, documents
with many shadow trees ended up suffering since any time you mutated
a style sheet inside a shadow tree, *all* style caches for the entire
document would get invalidated.
This was particularly expensive on Reddit, which has tons of shadow
trees with their own style elements. Every time we'd create one of their
custom elements, we'd invalidate the document-level "rule cache" and
have to rebuild it, taking about ~60ms each time (ouch).
This commit introduces a new object called StyleScope.
Every Document and ShadowRoot has its own StyleScope. Rule caches etc
are moved from StyleComputer to StyleScope.
Rule cache invalidation now happens at StyleScope level. As an example,
rule cache rebuilds now take ~1ms on Reddit instead of ~60ms.
This is largely a mechanical change, moving things around, but there's
one key detail to be aware of: due to the :host selector, which works
across the shadow DOM boundary and reaches from inside a shadow tree out
into the light tree, there are various places where we have to check
both the shadow tree's StyleScope *and* the document-level StyleScope
in order to get all rules that may apply.
2025-11-13 19:08:08 +01:00
auto & style_scope = document_or_shadow_root - > is_shadow_root ( ) ? as < DOM : : ShadowRoot > ( * document_or_shadow_root ) . style_scope ( ) : document_or_shadow_root - > document ( ) . style_scope ( ) ;
style_scope . invalidate_rule_cache ( ) ;
2025-01-10 20:00:43 +03:00
}
}
2025-04-10 16:01:17 +01:00
GC : : Ptr < DOM : : Document > CSSStyleSheet : : owning_document ( ) const
{
if ( ! m_owning_documents_or_shadow_roots . is_empty ( ) )
return ( * m_owning_documents_or_shadow_roots . begin ( ) ) - > document ( ) ;
if ( auto * element = const_cast < CSSStyleSheet * > ( this ) - > owner_node ( ) )
return element - > document ( ) ;
return nullptr ;
}
2025-10-07 00:54:19 +13:00
bool CSSStyleSheet : : evaluate_media_queries ( DOM : : Document const & document )
2021-10-08 20:21:46 +01:00
{
2022-10-23 21:05:34 +03:00
bool any_media_queries_changed_match_state = false ;
2025-10-07 00:54:19 +13:00
bool now_matches = m_media - > evaluate ( document ) ;
2024-08-11 09:34:37 -06:00
if ( ! m_did_match . has_value ( ) | | m_did_match . value ( ) ! = now_matches )
2022-10-23 21:05:34 +03:00
any_media_queries_changed_match_state = true ;
2025-10-07 00:54:19 +13:00
if ( now_matches & & m_rules - > evaluate_media_queries ( document ) )
2022-10-23 21:05:34 +03:00
any_media_queries_changed_match_state = true ;
2024-08-11 09:34:37 -06:00
m_did_match = now_matches ;
2022-10-23 21:05:34 +03:00
return any_media_queries_changed_match_state ;
2021-10-08 20:21:46 +01:00
}
2023-11-24 19:14:24 +13:00
Optional < FlyString > CSSStyleSheet : : default_namespace ( ) const
2023-07-31 17:46:57 +01:00
{
if ( m_default_namespace_rule )
2023-12-01 13:36:40 +01:00
return m_default_namespace_rule - > namespace_uri ( ) ;
2023-07-31 17:46:57 +01:00
return { } ;
}
2025-06-23 22:40:37 +12:00
HashTable < FlyString > CSSStyleSheet : : declared_namespaces ( ) const
{
HashTable < FlyString > declared_namespaces ;
for ( auto namespace_ : m_namespace_rules . keys ( ) ) {
declared_namespaces . set ( namespace_ ) ;
}
return declared_namespaces ;
}
2023-11-24 19:14:24 +13:00
Optional < FlyString > CSSStyleSheet : : namespace_uri ( StringView namespace_prefix ) const
2023-08-04 17:02:29 +01:00
{
return m_namespace_rules . get ( namespace_prefix )
2024-11-15 04:01:23 +13:00
. map ( [ ] ( GC : : Ptr < CSSNamespaceRule > namespace_ ) {
2023-12-01 13:36:40 +01:00
return namespace_ - > namespace_uri ( ) ;
2023-08-04 17:02:29 +01:00
} ) ;
}
2024-08-23 16:46:33 +01:00
void CSSStyleSheet : : recalculate_rule_caches ( )
2023-07-29 11:51:15 -05:00
{
2023-08-04 17:02:29 +01:00
m_default_namespace_rule = nullptr ;
m_namespace_rules . clear ( ) ;
2024-08-23 16:46:33 +01:00
m_import_rules . clear ( ) ;
for ( auto const & rule : * m_rules ) {
// "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
//
2023-07-31 17:46:57 +01:00
// "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.
// ...
// A syntactically invalid @namespace rule (whether malformed or misplaced) must be ignored."
// https://drafts.csswg.org/css-namespaces/#syntax
switch ( rule - > type ( ) ) {
2024-08-23 16:46:33 +01:00
case CSSRule : : Type : : Import : {
// @import rules must appear before @namespace rules, so skip this if we've seen @namespace.
if ( ! m_namespace_rules . is_empty ( ) )
continue ;
2025-01-21 09:12:05 -05:00
m_import_rules . append ( as < CSSImportRule > ( * rule ) ) ;
2023-07-31 17:46:57 +01:00
break ;
2024-08-23 16:46:33 +01:00
}
case CSSRule : : Type : : Namespace : {
2025-01-21 09:12:05 -05:00
auto & namespace_rule = as < CSSNamespaceRule > ( * rule ) ;
2024-08-23 16:46:33 +01:00
if ( ! namespace_rule . namespace_uri ( ) . is_empty ( ) & & namespace_rule . prefix ( ) . is_empty ( ) )
m_default_namespace_rule = namespace_rule ;
2023-07-31 17:46:57 +01:00
2024-08-23 16:46:33 +01:00
m_namespace_rules . set ( namespace_rule . prefix ( ) , namespace_rule ) ;
break ;
}
2023-07-31 17:46:57 +01:00
default :
// Any other types mean that further @namespace rules are invalid, so we can stop here.
return ;
2023-07-29 11:51:15 -05:00
}
2023-07-31 17:46:57 +01:00
}
2023-07-29 11:51:15 -05:00
}
2024-08-22 17:23:54 +01:00
void CSSStyleSheet : : set_source_text ( String source )
{
m_source_text = move ( source ) ;
}
Optional < String > CSSStyleSheet : : source_text ( Badge < DOM : : Document > ) const
{
return m_source_text ;
}
2024-09-22 18:10:46 +02:00
bool CSSStyleSheet : : has_associated_font_loader ( FontLoader & font_loader ) const
{
for ( auto & loader : m_associated_font_loaders ) {
if ( loader . ptr ( ) = = & font_loader )
return true ;
}
return false ;
}
2025-04-08 18:10:38 +01:00
Parser : : ParsingParams CSSStyleSheet : : make_parsing_params ( ) const
{
2025-06-23 22:40:37 +12:00
Parser : : ParsingParams parsing_params ;
2025-04-10 16:01:17 +01:00
if ( auto document = owning_document ( ) )
2025-06-23 22:40:37 +12:00
parsing_params = Parser : : ParsingParams { * document } ;
else
parsing_params = Parser : : ParsingParams { realm ( ) } ;
parsing_params . declared_namespaces = declared_namespaces ( ) ;
return parsing_params ;
2025-04-08 18:10:38 +01:00
}
2021-03-07 16:14:04 +01:00
}