CSSImportRule.cpp 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /*
  2. * Copyright (c) 2021, the SerenityOS developers.
  3. * Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
  4. * Copyright (c) 2022-2024, Andreas Kling <andreas@ladybird.org>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include <AK/Debug.h>
  9. #include <AK/ScopeGuard.h>
  10. #include <LibTextCodec/Decoder.h>
  11. #include <LibURL/URL.h>
  12. #include <LibWeb/Bindings/CSSImportRulePrototype.h>
  13. #include <LibWeb/Bindings/Intrinsics.h>
  14. #include <LibWeb/CSS/CSSImportRule.h>
  15. #include <LibWeb/CSS/Fetch.h>
  16. #include <LibWeb/CSS/Parser/Parser.h>
  17. #include <LibWeb/CSS/StyleComputer.h>
  18. #include <LibWeb/DOM/Document.h>
  19. #include <LibWeb/HTML/Window.h>
  20. namespace Web::CSS {
  21. GC_DEFINE_ALLOCATOR(CSSImportRule);
  22. GC::Ref<CSSImportRule> CSSImportRule::create(URL::URL url, DOM::Document& document)
  23. {
  24. auto& realm = document.realm();
  25. return realm.create<CSSImportRule>(move(url), document);
  26. }
  27. CSSImportRule::CSSImportRule(URL::URL url, DOM::Document& document)
  28. : CSSRule(document.realm(), Type::Import)
  29. , m_url(move(url))
  30. , m_document(document)
  31. {
  32. }
  33. void CSSImportRule::initialize(JS::Realm& realm)
  34. {
  35. Base::initialize(realm);
  36. WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSImportRule);
  37. }
  38. void CSSImportRule::visit_edges(Cell::Visitor& visitor)
  39. {
  40. Base::visit_edges(visitor);
  41. visitor.visit(m_document);
  42. visitor.visit(m_style_sheet);
  43. }
  44. void CSSImportRule::set_parent_style_sheet(CSSStyleSheet* parent_style_sheet)
  45. {
  46. Base::set_parent_style_sheet(parent_style_sheet);
  47. // Crude detection of whether we're already fetching.
  48. if (m_style_sheet || m_document_load_event_delayer.has_value())
  49. return;
  50. fetch();
  51. }
  52. // https://www.w3.org/TR/cssom/#serialize-a-css-rule
  53. String CSSImportRule::serialized() const
  54. {
  55. StringBuilder builder;
  56. // The result of concatenating the following:
  57. // 1. The string "@import" followed by a single SPACE (U+0020).
  58. builder.append("@import "sv);
  59. // 2. The result of performing serialize a URL on the rule’s location.
  60. serialize_a_url(builder, m_url.to_string());
  61. // FIXME: 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.
  62. // 4. The string ";", i.e., SEMICOLON (U+003B).
  63. builder.append(';');
  64. return MUST(builder.to_string());
  65. }
  66. // https://drafts.csswg.org/css-cascade-4/#fetch-an-import
  67. void CSSImportRule::fetch()
  68. {
  69. dbgln_if(CSS_LOADER_DEBUG, "CSSImportRule: Loading import URL: {}", m_url);
  70. // To fetch an @import, given an @import rule rule:
  71. // 1. Let parentStylesheet be rule’s parent CSS style sheet. [CSSOM]
  72. VERIFY(parent_style_sheet());
  73. auto& parent_style_sheet = *this->parent_style_sheet();
  74. // FIXME: 2. If rule has a <supports-condition>, and that condition is not true, return.
  75. // 3. Let parsedUrl be the result of the URL parser steps with rule’s URL and parentStylesheet’s location.
  76. // If the algorithm returns an error, return. [CSSOM]
  77. // FIXME: Stop producing a URL::URL when parsing the @import
  78. auto parsed_url = url().to_string();
  79. // FIXME: Figure out the "correct" way to delay the load event.
  80. m_document_load_event_delayer.emplace(*m_document);
  81. // 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:
  82. fetch_a_style_resource(parsed_url, parent_style_sheet, Fetch::Infrastructure::Request::Destination::Style, CorsMode::NoCors,
  83. [strong_this = GC::Ref { *this }, parent_style_sheet = GC::Ref { parent_style_sheet }](auto response, auto maybe_byte_stream) {
  84. // AD-HOC: Stop delaying the load event.
  85. ScopeGuard guard = [strong_this] {
  86. strong_this->m_document_load_event_delayer.clear();
  87. };
  88. // 1. If byteStream is not a byte stream, return.
  89. auto byte_stream = maybe_byte_stream.template get_pointer<ByteBuffer>();
  90. if (!byte_stream)
  91. return;
  92. // FIXME: 2. If parentStylesheet is in quirks mode and response is CORS-same-origin, let content type be "text/css".
  93. // Otherwise, let content type be the Content Type metadata of response.
  94. auto content_type = "text/css"sv;
  95. // 3. If content type is not "text/css", return.
  96. if (content_type != "text/css"sv) {
  97. dbgln_if(CSS_LOADER_DEBUG, "CSSImportRule: Rejecting loaded style sheet; content type isn't text/css; is: '{}'", content_type);
  98. return;
  99. }
  100. // 4. Let importedStylesheet be the result of parsing byteStream given parsedUrl.
  101. // FIXME: Tidy up our parsing API. For now, do the decoding here.
  102. // FIXME: Get the encoding from the response somehow.
  103. auto encoding = "utf-8"sv;
  104. auto maybe_decoder = TextCodec::decoder_for(encoding);
  105. if (!maybe_decoder.has_value()) {
  106. dbgln_if(CSS_LOADER_DEBUG, "CSSImportRule: Failed to decode CSS file: {} Unsupported encoding: {}", strong_this->url(), encoding);
  107. return;
  108. }
  109. auto& decoder = maybe_decoder.release_value();
  110. auto decoded_or_error = TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(decoder, *byte_stream);
  111. if (decoded_or_error.is_error()) {
  112. dbgln_if(CSS_LOADER_DEBUG, "CSSImportRule: Failed to decode CSS file: {} Encoding was: {}", strong_this->url(), encoding);
  113. return;
  114. }
  115. auto decoded = decoded_or_error.release_value();
  116. auto* imported_style_sheet = parse_css_stylesheet(Parser::ParsingContext(*strong_this->m_document, strong_this->url()), decoded, strong_this->url());
  117. // 5. Set importedStylesheet’s origin-clean flag to parentStylesheet’s origin-clean flag.
  118. imported_style_sheet->set_origin_clean(parent_style_sheet->is_origin_clean());
  119. // 6. If response is not CORS-same-origin, unset importedStylesheet’s origin-clean flag.
  120. if (!response->is_cors_cross_origin())
  121. imported_style_sheet->set_origin_clean(false);
  122. // 7. Set rule’s styleSheet to importedStylesheet.
  123. strong_this->set_style_sheet(*imported_style_sheet);
  124. });
  125. }
  126. void CSSImportRule::set_style_sheet(GC::Ref<CSSStyleSheet> style_sheet)
  127. {
  128. m_style_sheet = style_sheet;
  129. m_style_sheet->set_owner_css_rule(this);
  130. m_document->style_computer().invalidate_rule_cache();
  131. m_document->style_computer().load_fonts_from_sheet(*m_style_sheet);
  132. m_document->invalidate_style(DOM::StyleInvalidationReason::CSSImportRule);
  133. }
  134. }