2021-02-21 13:36:34 -03:00
/*
* Copyright ( c ) 2021 , the SerenityOS developers .
2025-12-04 09:03:01 -03:00
* Copyright ( c ) 2021 - 2025 , Sam Atkins < sam @ ladybird . org >
2024-10-12 11:52:36 -03:00
* Copyright ( c ) 2022 - 2024 , Andreas Kling < andreas @ ladybird . org >
2025-09-16 16:01:23 -03:00
* Copyright ( c ) 2025 , Lorenz Ackermann < me @ lorenzackermann . xyz >
2021-02-21 13:36:34 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2021-02-21 13:36:34 -03:00
*/
2021-11-18 14:34:53 -03:00
# include <AK/Debug.h>
2024-12-20 17:25:40 -03:00
# include <AK/ScopeGuard.h>
2024-10-12 11:52:36 -03:00
# include <LibTextCodec/Decoder.h>
2022-09-24 19:34:04 -03:00
# include <LibWeb/Bindings/CSSImportRulePrototype.h>
# include <LibWeb/Bindings/Intrinsics.h>
2021-02-21 13:36:34 -03:00
# include <LibWeb/CSS/CSSImportRule.h>
2025-12-04 11:34:28 -03:00
# include <LibWeb/CSS/CSSLayerBlockRule.h>
2024-12-20 17:25:40 -03:00
# include <LibWeb/CSS/Fetch.h>
2021-11-18 14:34:53 -03:00
# include <LibWeb/CSS/Parser/Parser.h>
2023-05-08 01:37:18 -03:00
# include <LibWeb/CSS/StyleComputer.h>
2021-11-18 14:34:53 -03:00
# include <LibWeb/DOM/Document.h>
2025-04-08 14:16:38 -03:00
# include <LibWeb/DOMURL/DOMURL.h>
2025-12-04 09:03:01 -03:00
# include <LibWeb/Dump.h>
2025-11-26 15:32:39 -03:00
# include <LibWeb/Fetch/Infrastructure/HTTP/MIME.h>
2025-10-16 10:07:09 -03:00
# include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
2022-08-28 08:42:07 -03:00
# include <LibWeb/HTML/Window.h>
2021-02-21 13:36:34 -03:00
namespace Web : : CSS {
2024-11-14 12:01:23 -03:00
GC_DEFINE_ALLOCATOR ( CSSImportRule ) ;
2023-11-19 15:47:52 -03:00
2025-12-04 13:48:19 -03:00
GC : : Ref < CSSImportRule > CSSImportRule : : create ( JS : : Realm & realm , URL url , GC : : Ptr < DOM : : Document > document , Optional < FlyString > layer , RefPtr < Supports > supports , GC : : Ref < MediaList > media )
2022-08-07 10:46:44 -03:00
{
2025-12-04 13:48:19 -03:00
return realm . create < CSSImportRule > ( realm , move ( url ) , document , move ( layer ) , move ( supports ) , move ( media ) ) ;
2022-08-07 10:46:44 -03:00
}
2025-12-04 13:48:19 -03:00
CSSImportRule : : CSSImportRule ( JS : : Realm & realm , URL url , GC : : Ptr < DOM : : Document > document , Optional < FlyString > layer , RefPtr < Supports > supports , GC : : Ref < MediaList > media )
2025-04-08 12:50:51 -03:00
: CSSRule ( realm , Type : : Import )
2022-08-07 10:46:44 -03:00
, m_url ( move ( url ) )
2021-11-18 14:34:53 -03:00
, m_document ( document )
2025-12-04 11:15:07 -03:00
, m_layer ( move ( layer ) )
2025-04-08 12:50:51 -03:00
, m_supports ( move ( supports ) )
2025-12-04 13:48:19 -03:00
, m_media ( move ( media ) )
2021-02-21 13:36:34 -03:00
{
2025-12-04 11:34:28 -03:00
if ( m_layer . has_value ( ) & & m_layer - > is_empty ( ) ) {
m_layer_internal = CSSLayerBlockRule : : next_unique_anonymous_layer_name ( ) ;
} else {
m_layer_internal = m_layer ;
}
2021-02-21 13:36:34 -03:00
}
2025-11-29 06:16:38 -03:00
CSSImportRule : : ~ CSSImportRule ( ) = default ;
2023-08-07 03:41:28 -03:00
void CSSImportRule : : initialize ( JS : : Realm & realm )
2023-01-10 08:28:20 -03:00
{
2024-03-16 09:13:08 -03:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( CSSImportRule ) ;
2025-04-20 11:22:57 -03:00
Base : : initialize ( realm ) ;
2023-01-10 08:28:20 -03:00
}
2022-08-07 10:46:44 -03:00
void CSSImportRule : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2022-09-03 10:44:06 -03:00
visitor . visit ( m_document ) ;
2025-12-04 13:48:19 -03:00
visitor . visit ( m_media ) ;
2022-08-07 10:46:44 -03:00
visitor . visit ( m_style_sheet ) ;
}
2024-12-20 17:25:40 -03:00
void CSSImportRule : : set_parent_style_sheet ( CSSStyleSheet * parent_style_sheet )
{
Base : : set_parent_style_sheet ( parent_style_sheet ) ;
2025-10-15 07:45:38 -03:00
if ( m_style_sheet & & parent_style_sheet ) {
for ( auto owning_document_or_shadow_root : parent_style_sheet - > owning_documents_or_shadow_roots ( ) )
m_style_sheet - > add_owning_document_or_shadow_root ( * owning_document_or_shadow_root ) ;
}
2024-12-20 17:25:40 -03:00
// Crude detection of whether we're already fetching.
if ( m_style_sheet | | m_document_load_event_delayer . has_value ( ) )
return ;
2025-04-09 07:33:18 -03:00
// Only try to fetch if we now have a parent
if ( parent_style_sheet )
fetch ( ) ;
2024-12-20 17:25:40 -03:00
}
2021-10-15 15:38:39 -03:00
// https://www.w3.org/TR/cssom/#serialize-a-css-rule
2023-11-20 07:16:39 -03:00
String CSSImportRule : : serialized ( ) const
2021-10-01 14:57:45 -03:00
{
StringBuilder builder ;
// The result of concatenating the following:
// 1. The string "@import" followed by a single SPACE (U+0020).
builder . append ( " @import " sv ) ;
// 2. The result of performing serialize a URL on the rule’ s location.
2025-04-08 14:16:38 -03:00
builder . append ( m_url . to_string ( ) ) ;
2021-10-01 14:57:45 -03:00
2025-12-04 11:15:07 -03:00
// AD-HOC: Serialize the rule's layer if it exists.
if ( m_layer . has_value ( ) ) {
if ( m_layer - > is_empty ( ) ) {
builder . append ( " layer " sv ) ;
} else {
builder . appendff ( " layer({}) " , m_layer ) ;
}
}
2025-03-19 08:23:33 -03:00
// AD-HOC: Serialize the rule's supports condition if it exists.
// This isn't currently specified, but major browsers include this in their serialization of import rules
if ( m_supports )
builder . appendff ( " supports({}) " , m_supports - > to_string ( ) ) ;
2025-04-01 10:32:11 -03:00
// 3. If the rule’ s associated media list is not empty, a single SPACE (U+0020) followed by the result of performing serialize a media query list on the media list.
2025-12-04 13:48:19 -03:00
if ( m_media - > length ( ) ! = 0 )
builder . appendff ( " {} " , m_media - > media_text ( ) ) ;
2021-10-01 14:57:45 -03:00
// 4. The string ";", i.e., SEMICOLON (U+003B).
builder . append ( ' ; ' ) ;
2023-11-20 07:16:39 -03:00
return MUST ( builder . to_string ( ) ) ;
2021-10-01 14:57:45 -03:00
}
2024-12-20 17:25:40 -03:00
// https://drafts.csswg.org/css-cascade-4/#fetch-an-import
void CSSImportRule : : fetch ( )
2021-11-18 14:34:53 -03:00
{
2024-12-20 17:25:40 -03:00
dbgln_if ( CSS_LOADER_DEBUG , " CSSImportRule: Loading import URL: {} " , m_url ) ;
// To fetch an @import, given an @import rule rule:
// 1. Let parentStylesheet be rule’ s parent CSS style sheet. [CSSOM]
VERIFY ( parent_style_sheet ( ) ) ;
auto & parent_style_sheet = * this - > parent_style_sheet ( ) ;
2025-03-19 08:23:33 -03:00
// 2. If rule has a <supports-condition>, and that condition is not true, return.
if ( m_supports & & ! m_supports - > matches ( ) ) {
return ;
}
2024-12-20 17:25:40 -03:00
// FIXME: Figure out the "correct" way to delay the load event.
m_document_load_event_delayer . emplace ( * m_document ) ;
2025-11-15 10:21:20 -03:00
// AD-HOC: Track pending import rules to block rendering until they are done.
m_document - > add_pending_css_import_rule ( { } , * this ) ;
2025-12-28 08:12:59 -03:00
// 3. Fetch a style resource from rule’ s URL, with ruleOrDeclaration rule, destination "style", CORS mode "no-cors", and
// processResponse being the following steps given response response and byte stream, null or failure byteStream:
RuleOrDeclaration rule_or_declaration {
. environment_settings_object = HTML : : relevant_settings_object ( parent_style_sheet ) ,
. value = RuleOrDeclaration : : Rule {
. parent_style_sheet = & parent_style_sheet ,
}
} ;
( void ) fetch_a_style_resource ( URL { href ( ) } , rule_or_declaration , Fetch : : Infrastructure : : Request : : Destination : : Style , CorsMode : : NoCors ,
[ strong_this = GC : : Ref { * this } , parent_style_sheet = GC : : Ref { parent_style_sheet } , document = m_document ] ( auto response , auto maybe_byte_stream ) {
2024-12-20 17:25:40 -03:00
// AD-HOC: Stop delaying the load event.
2025-11-15 10:21:20 -03:00
ScopeGuard guard = [ strong_this , document ] {
document - > remove_pending_css_import_rule ( { } , strong_this ) ;
2024-12-20 17:25:40 -03:00
strong_this - > m_document_load_event_delayer . clear ( ) ;
} ;
// 1. If byteStream is not a byte stream, return.
auto byte_stream = maybe_byte_stream . template get_pointer < ByteBuffer > ( ) ;
if ( ! byte_stream )
return ;
// FIXME: 2. If parentStylesheet is in quirks mode and response is CORS-same-origin, let content type be "text/css".
// Otherwise, let content type be the Content Type metadata of response.
auto content_type = " text/css " sv ;
// 3. If content type is not "text/css", return.
if ( content_type ! = " text/css " sv ) {
dbgln_if ( CSS_LOADER_DEBUG , " CSSImportRule: Rejecting loaded style sheet; content type isn't text/css; is: '{}' " , content_type ) ;
return ;
}
// 4. Let importedStylesheet be the result of parsing byteStream given parsedUrl.
// FIXME: Tidy up our parsing API. For now, do the decoding here.
2025-12-28 08:12:59 -03:00
// FIXME: Spec issue: parsedURL is not defined - we instead need to get that from the response.
// https://github.com/w3c/csswg-drafts/issues/12288
auto url = response - > unsafe_response ( ) - > url ( ) . value ( ) ;
2025-09-16 16:01:23 -03:00
Optional < String > mime_type_charset ;
2025-11-26 15:32:39 -03:00
if ( auto extracted_mime_type = Fetch : : Infrastructure : : extract_mime_type ( response - > header_list ( ) ) ; extracted_mime_type . has_value ( ) ) {
2025-09-16 16:01:23 -03:00
if ( auto charset = extracted_mime_type - > parameters ( ) . get ( " charset " sv ) ; charset . has_value ( ) )
mime_type_charset = charset . value ( ) ;
2024-12-20 17:25:40 -03:00
}
2025-09-16 16:01:23 -03:00
// The environment encoding of an imported style sheet is the encoding of the style sheet that imported it. [css-syntax-3]
// FIXME: Save encoding on Stylesheet to get it here
Optional < StringView > environment_encoding ;
auto decoded_or_error = css_decode_bytes ( environment_encoding , mime_type_charset , * byte_stream ) ;
2024-12-20 17:25:40 -03:00
if ( decoded_or_error . is_error ( ) ) {
2025-12-28 08:12:59 -03:00
dbgln_if ( CSS_LOADER_DEBUG , " CSSImportRule: Failed to decode CSS file: {} " , url ) ;
2024-12-20 17:25:40 -03:00
return ;
}
auto decoded = decoded_or_error . release_value ( ) ;
2025-12-28 08:12:59 -03:00
auto imported_style_sheet = parse_css_stylesheet ( Parser : : ParsingParams ( * strong_this - > m_document ) , decoded , url , strong_this - > m_media ) ;
2024-12-20 17:25:40 -03:00
// 5. Set importedStylesheet’ s origin-clean flag to parentStylesheet’ s origin-clean flag.
imported_style_sheet - > set_origin_clean ( parent_style_sheet - > is_origin_clean ( ) ) ;
// 6. If response is not CORS-same-origin, unset importedStylesheet’ s origin-clean flag.
if ( ! response - > is_cors_cross_origin ( ) )
imported_style_sheet - > set_origin_clean ( false ) ;
// 7. Set rule’ s styleSheet to importedStylesheet.
2025-04-14 10:04:36 -03:00
strong_this - > set_style_sheet ( imported_style_sheet ) ;
2024-12-20 17:25:40 -03:00
} ) ;
2021-11-18 14:34:53 -03:00
}
2024-12-20 17:25:40 -03:00
void CSSImportRule : : set_style_sheet ( GC : : Ref < CSSStyleSheet > style_sheet )
2021-11-18 14:34:53 -03:00
{
2024-12-20 17:25:40 -03:00
m_style_sheet = style_sheet ;
2024-08-23 13:47:05 -03:00
m_style_sheet - > set_owner_css_rule ( this ) ;
2025-10-15 07:45:38 -03:00
if ( m_parent_style_sheet ) {
for ( auto owning_document_or_shadow_root : m_parent_style_sheet - > owning_documents_or_shadow_roots ( ) )
m_style_sheet - > add_owning_document_or_shadow_root ( * owning_document_or_shadow_root ) ;
}
m_style_sheet - > invalidate_owners ( DOM : : StyleInvalidationReason : : CSSImportRule ) ;
2021-11-18 14:34:53 -03:00
}
2025-04-02 05:39:51 -03:00
// https://drafts.csswg.org/cssom/#dom-cssimportrule-media
2025-12-04 13:48:19 -03:00
GC : : Ref < MediaList > CSSImportRule : : media ( ) const
2025-04-02 05:39:51 -03:00
{
// The media attribute must return the value of the media attribute of the associated CSS style sheet.
2025-12-04 13:48:19 -03:00
// AD-HOC: Return our own MediaList.
// https://github.com/w3c/csswg-drafts/issues/12063
return m_media ;
2025-04-02 05:39:51 -03:00
}
2025-12-02 09:38:16 -03:00
// https://drafts.csswg.org/cssom/#dom-cssimportrule-layername
Optional < FlyString > CSSImportRule : : layer_name ( ) const
{
// The layerName attribute must return the layer name declared in the at-rule itself, or an empty string if the
// layer is anonymous, or null if the at-rule does not declare a layer.
if ( ! m_layer . has_value ( ) )
return { } ;
return m_layer ;
}
// https://drafts.csswg.org/cssom/#dom-cssimportrule-supportstext
2025-03-19 08:32:34 -03:00
Optional < String > CSSImportRule : : supports_text ( ) const
{
2025-12-02 09:38:16 -03:00
// The supportsText attribute must return the <supports-condition> declared in the at-rule itself, or null if the
// at-rule does not declare a supports condition.
2025-03-19 08:32:34 -03:00
if ( ! m_supports )
return { } ;
return m_supports - > to_string ( ) ;
}
2025-12-04 11:34:28 -03:00
Optional < FlyString > CSSImportRule : : internal_qualified_layer_name ( Badge < StyleScope > ) const
{
if ( ! m_layer . has_value ( ) )
return { } ;
auto const & parent_name = parent_layer_internal_qualified_name ( ) ;
if ( parent_name . is_empty ( ) )
return m_layer_internal . value ( ) ;
return MUST ( String : : formatted ( " {}.{} " , parent_name , m_layer_internal . value ( ) ) ) ;
}
bool CSSImportRule : : matches ( ) const
{
if ( m_supports & & ! m_supports - > matches ( ) )
return false ;
return m_media - > matches ( ) ;
}
2025-12-04 09:03:01 -03:00
void CSSImportRule : : dump ( StringBuilder & builder , int indent_levels ) const
{
Base : : dump ( builder , indent_levels ) ;
dump_indent ( builder , indent_levels + 1 ) ;
builder . appendff ( " Document URL: {} \n " , url ( ) . to_string ( ) ) ;
2025-12-04 09:58:44 -03:00
dump_indent ( builder , indent_levels + 1 ) ;
builder . appendff ( " Has document load delayer: {} \n " , m_document_load_event_delayer . has_value ( ) ) ;
2025-12-04 11:15:07 -03:00
if ( m_layer . has_value ( ) ) {
dump_indent ( builder , indent_levels + 1 ) ;
2025-12-04 11:34:28 -03:00
builder . appendff ( " Layer: `{}` (internal: `{}`) \n " , * m_layer , * m_layer_internal ) ;
2025-12-04 11:15:07 -03:00
}
2025-12-04 13:48:19 -03:00
if ( m_media - > length ( ) ! = 0 )
m_media - > dump ( builder , indent_levels + 1 ) ;
2025-12-04 09:58:44 -03:00
if ( m_supports )
m_supports - > dump ( builder , indent_levels + 1 ) ;
if ( m_style_sheet ) {
dump_sheet ( builder , * m_style_sheet , indent_levels + 1 ) ;
} else {
dump_indent ( builder , indent_levels + 1 ) ;
builder . append ( " Style sheet not loaded \n " sv ) ;
}
2025-12-04 09:03:01 -03:00
}
2021-02-21 13:36:34 -03:00
}