mirror of
				https://github.com/LadybirdBrowser/ladybird.git
				synced 2025-10-25 17:39:27 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			209 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			209 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | ||
|  * Copyright (c) 2021, the SerenityOS developers.
 | ||
|  * Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
 | ||
|  * Copyright (c) 2022-2024, Andreas Kling <andreas@ladybird.org>
 | ||
|  * Copyright (c) 2025, Lorenz Ackermann <me@lorenzackermann.xyz>
 | ||
|  *
 | ||
|  * SPDX-License-Identifier: BSD-2-Clause
 | ||
|  */
 | ||
| 
 | ||
| #include <AK/Debug.h>
 | ||
| #include <AK/ScopeGuard.h>
 | ||
| #include <LibTextCodec/Decoder.h>
 | ||
| #include <LibWeb/Bindings/CSSImportRulePrototype.h>
 | ||
| #include <LibWeb/Bindings/Intrinsics.h>
 | ||
| #include <LibWeb/CSS/CSSImportRule.h>
 | ||
| #include <LibWeb/CSS/Fetch.h>
 | ||
| #include <LibWeb/CSS/Parser/Parser.h>
 | ||
| #include <LibWeb/CSS/StyleComputer.h>
 | ||
| #include <LibWeb/DOM/Document.h>
 | ||
| #include <LibWeb/DOMURL/DOMURL.h>
 | ||
| #include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
 | ||
| #include <LibWeb/HTML/Window.h>
 | ||
| 
 | ||
| namespace Web::CSS {
 | ||
| 
 | ||
| GC_DEFINE_ALLOCATOR(CSSImportRule);
 | ||
| 
 | ||
| GC::Ref<CSSImportRule> CSSImportRule::create(JS::Realm& realm, URL url, GC::Ptr<DOM::Document> document, RefPtr<Supports> supports, Vector<NonnullRefPtr<MediaQuery>> media_query_list)
 | ||
| {
 | ||
|     return realm.create<CSSImportRule>(realm, move(url), document, move(supports), move(media_query_list));
 | ||
| }
 | ||
| 
 | ||
| CSSImportRule::CSSImportRule(JS::Realm& realm, URL url, GC::Ptr<DOM::Document> document, RefPtr<Supports> supports, Vector<NonnullRefPtr<MediaQuery>> media_query_list)
 | ||
|     : CSSRule(realm, Type::Import)
 | ||
|     , m_url(move(url))
 | ||
|     , m_document(document)
 | ||
|     , m_supports(move(supports))
 | ||
|     , m_media_query_list(move(media_query_list))
 | ||
| {
 | ||
| }
 | ||
| 
 | ||
| void CSSImportRule::initialize(JS::Realm& realm)
 | ||
| {
 | ||
|     WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSImportRule);
 | ||
|     Base::initialize(realm);
 | ||
| }
 | ||
| 
 | ||
| void CSSImportRule::visit_edges(Cell::Visitor& visitor)
 | ||
| {
 | ||
|     Base::visit_edges(visitor);
 | ||
|     visitor.visit(m_document);
 | ||
|     visitor.visit(m_style_sheet);
 | ||
| }
 | ||
| 
 | ||
| void CSSImportRule::set_parent_style_sheet(CSSStyleSheet* parent_style_sheet)
 | ||
| {
 | ||
|     Base::set_parent_style_sheet(parent_style_sheet);
 | ||
| 
 | ||
|     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);
 | ||
|     }
 | ||
| 
 | ||
|     // Crude detection of whether we're already fetching.
 | ||
|     if (m_style_sheet || m_document_load_event_delayer.has_value())
 | ||
|         return;
 | ||
| 
 | ||
|     // Only try to fetch if we now have a parent
 | ||
|     if (parent_style_sheet)
 | ||
|         fetch();
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/cssom/#serialize-a-css-rule
 | ||
| String CSSImportRule::serialized() const
 | ||
| {
 | ||
|     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.
 | ||
|     builder.append(m_url.to_string());
 | ||
| 
 | ||
|     // 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());
 | ||
| 
 | ||
|     // 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.
 | ||
|     if (!m_media_query_list.is_empty())
 | ||
|         builder.appendff(" {}", serialize_a_media_query_list(m_media_query_list));
 | ||
| 
 | ||
|     // 4. The string ";", i.e., SEMICOLON (U+003B).
 | ||
|     builder.append(';');
 | ||
| 
 | ||
|     return MUST(builder.to_string());
 | ||
| }
 | ||
| 
 | ||
| // https://drafts.csswg.org/css-cascade-4/#fetch-an-import
 | ||
| void CSSImportRule::fetch()
 | ||
| {
 | ||
|     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();
 | ||
| 
 | ||
|     // 2. If rule has a <supports-condition>, and that condition is not true, return.
 | ||
|     if (m_supports && !m_supports->matches()) {
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     // 3. Let parsedUrl be the result of the URL parser steps with rule’s URL and parentStylesheet’s location.
 | ||
|     //    If the algorithm returns an error, return. [CSSOM]
 | ||
|     auto parsed_url = DOMURL::parse(href(), parent_style_sheet.location());
 | ||
|     if (!parsed_url.has_value()) {
 | ||
|         dbgln("Unable to parse @import url `{}` parent location `{}` as a URL.", href(), parent_style_sheet.location());
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     // FIXME: Figure out the "correct" way to delay the load event.
 | ||
|     m_document_load_event_delayer.emplace(*m_document);
 | ||
| 
 | ||
|     // 4. Fetch a style resource from parsedUrl, with stylesheet parentStylesheet, destination "style", CORS mode "no-cors", and processResponse being the following steps given response response and byte stream, null or failure byteStream:
 | ||
|     (void)fetch_a_style_resource(parsed_url.value(), { parent_style_sheet }, Fetch::Infrastructure::Request::Destination::Style, CorsMode::NoCors,
 | ||
|         [strong_this = GC::Ref { *this }, parent_style_sheet = GC::Ref { parent_style_sheet }, parsed_url = parsed_url.value()](auto response, auto maybe_byte_stream) {
 | ||
|             // AD-HOC: Stop delaying the load event.
 | ||
|             ScopeGuard guard = [strong_this] {
 | ||
|                 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.
 | ||
|             Optional<String> mime_type_charset;
 | ||
|             if (auto extracted_mime_type = response->header_list()->extract_mime_type(); extracted_mime_type.has_value()) {
 | ||
|                 if (auto charset = extracted_mime_type->parameters().get("charset"sv); charset.has_value())
 | ||
|                     mime_type_charset = charset.value();
 | ||
|             }
 | ||
|             // 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);
 | ||
|             if (decoded_or_error.is_error()) {
 | ||
|                 dbgln_if(CSS_LOADER_DEBUG, "CSSImportRule: Failed to decode CSS file: {}", parsed_url);
 | ||
|                 return;
 | ||
|             }
 | ||
|             auto decoded = decoded_or_error.release_value();
 | ||
| 
 | ||
|             auto imported_style_sheet = parse_css_stylesheet(Parser::ParsingParams(*strong_this->m_document), decoded, parsed_url, strong_this->m_media_query_list);
 | ||
| 
 | ||
|             // 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.
 | ||
|             strong_this->set_style_sheet(imported_style_sheet);
 | ||
|         });
 | ||
| }
 | ||
| 
 | ||
| void CSSImportRule::set_style_sheet(GC::Ref<CSSStyleSheet> style_sheet)
 | ||
| {
 | ||
|     m_style_sheet = style_sheet;
 | ||
|     m_style_sheet->set_owner_css_rule(this);
 | ||
| 
 | ||
|     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);
 | ||
| }
 | ||
| 
 | ||
| // https://drafts.csswg.org/cssom/#dom-cssimportrule-media
 | ||
| GC::Ptr<MediaList> CSSImportRule::media() const
 | ||
| {
 | ||
|     // The media attribute must return the value of the media attribute of the associated CSS style sheet.
 | ||
|     if (!m_style_sheet)
 | ||
|         return nullptr;
 | ||
|     return m_style_sheet->media();
 | ||
| }
 | ||
| 
 | ||
| Optional<String> CSSImportRule::supports_text() const
 | ||
| {
 | ||
|     if (!m_supports)
 | ||
|         return {};
 | ||
|     return m_supports->to_string();
 | ||
| }
 | ||
| 
 | ||
| }
 |