HTMLLinkElement.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. /*
  2. * Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
  3. * Copyright (c) 2021, the SerenityOS developers.
  4. * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
  5. * Copyright (c) 2023, Srikavin Ramkumar <me@srikavin.me>
  6. *
  7. * SPDX-License-Identifier: BSD-2-Clause
  8. */
  9. #include <AK/ByteBuffer.h>
  10. #include <AK/Debug.h>
  11. #include <LibTextCodec/Decoder.h>
  12. #include <LibURL/URL.h>
  13. #include <LibWeb/Bindings/HTMLLinkElementPrototype.h>
  14. #include <LibWeb/CSS/Parser/Parser.h>
  15. #include <LibWeb/DOM/DOMTokenList.h>
  16. #include <LibWeb/DOM/Document.h>
  17. #include <LibWeb/DOM/Event.h>
  18. #include <LibWeb/DOM/ShadowRoot.h>
  19. #include <LibWeb/Fetch/Fetching/Fetching.h>
  20. #include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
  21. #include <LibWeb/Fetch/Infrastructure/FetchController.h>
  22. #include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
  23. #include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
  24. #include <LibWeb/HTML/EventNames.h>
  25. #include <LibWeb/HTML/HTMLLinkElement.h>
  26. #include <LibWeb/HTML/PotentialCORSRequest.h>
  27. #include <LibWeb/HTML/TraversableNavigable.h>
  28. #include <LibWeb/Infra/CharacterTypes.h>
  29. #include <LibWeb/Infra/Strings.h>
  30. #include <LibWeb/Loader/ResourceLoader.h>
  31. #include <LibWeb/Page/Page.h>
  32. #include <LibWeb/Platform/ImageCodecPlugin.h>
  33. namespace Web::HTML {
  34. GC_DEFINE_ALLOCATOR(HTMLLinkElement);
  35. HTMLLinkElement::HTMLLinkElement(DOM::Document& document, DOM::QualifiedName qualified_name)
  36. : HTMLElement(document, move(qualified_name))
  37. {
  38. }
  39. HTMLLinkElement::~HTMLLinkElement() = default;
  40. void HTMLLinkElement::initialize(JS::Realm& realm)
  41. {
  42. Base::initialize(realm);
  43. WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLLinkElement);
  44. }
  45. void HTMLLinkElement::removed_from(Node* old_parent)
  46. {
  47. Base::removed_from(old_parent);
  48. if (m_loaded_style_sheet) {
  49. document_or_shadow_root_style_sheets().remove_a_css_style_sheet(*m_loaded_style_sheet);
  50. m_loaded_style_sheet = nullptr;
  51. }
  52. }
  53. void HTMLLinkElement::inserted()
  54. {
  55. HTMLElement::inserted();
  56. if (!document().browsing_context()) {
  57. return;
  58. }
  59. if (m_relationship & Relationship::Stylesheet) {
  60. // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:fetch-and-process-the-linked-resource
  61. // The appropriate times to fetch and process this type of link are:
  62. // - When the external resource link is created on a link element that is already browsing-context connected.
  63. // - When the external resource link's link element becomes browsing-context connected.
  64. fetch_and_process_linked_resource();
  65. }
  66. // FIXME: Follow spec for fetching and processing these attributes as well
  67. if (m_relationship & Relationship::Preload) {
  68. // FIXME: Respect the "as" attribute.
  69. LoadRequest request;
  70. request.set_url(document().parse_url(get_attribute_value(HTML::AttributeNames::href)));
  71. set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, request));
  72. } else if (m_relationship & Relationship::DNSPrefetch) {
  73. ResourceLoader::the().prefetch_dns(document().parse_url(get_attribute_value(HTML::AttributeNames::href)));
  74. } else if (m_relationship & Relationship::Preconnect) {
  75. ResourceLoader::the().preconnect(document().parse_url(get_attribute_value(HTML::AttributeNames::href)));
  76. } else if (m_relationship & Relationship::Icon) {
  77. auto favicon_url = document().parse_url(href());
  78. auto favicon_request = LoadRequest::create_for_url_on_page(favicon_url, &document().page());
  79. set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, favicon_request));
  80. }
  81. }
  82. // https://html.spec.whatwg.org/multipage/semantics.html#dom-link-as
  83. String HTMLLinkElement::as() const
  84. {
  85. String attribute_value = get_attribute_value(HTML::AttributeNames::as);
  86. if (attribute_value.equals_ignoring_ascii_case("fetch"sv)
  87. || attribute_value.equals_ignoring_ascii_case("image"sv)
  88. || attribute_value.equals_ignoring_ascii_case("script"sv)
  89. || attribute_value.equals_ignoring_ascii_case("style"sv)
  90. || attribute_value.equals_ignoring_ascii_case("video"sv)
  91. || attribute_value.equals_ignoring_ascii_case("audio"sv)
  92. || attribute_value.equals_ignoring_ascii_case("track"sv)
  93. || attribute_value.equals_ignoring_ascii_case("font"sv)) {
  94. return attribute_value.to_lowercase().release_value();
  95. }
  96. return String {};
  97. }
  98. WebIDL::ExceptionOr<void> HTMLLinkElement::set_as(String const& value)
  99. {
  100. return set_attribute(HTML::AttributeNames::as, move(value));
  101. }
  102. // https://html.spec.whatwg.org/multipage/semantics.html#dom-link-rellist
  103. GC::Ref<DOM::DOMTokenList> HTMLLinkElement::rel_list()
  104. {
  105. // The relList IDL attribute must reflect the rel content attribute.
  106. if (!m_rel_list)
  107. m_rel_list = DOM::DOMTokenList::create(*this, HTML::AttributeNames::rel);
  108. return *m_rel_list;
  109. }
  110. // https://html.spec.whatwg.org/multipage/semantics.html#dom-link-sizes
  111. GC::Ref<DOM::DOMTokenList> HTMLLinkElement::sizes()
  112. {
  113. // The size IDL attribute must reflect the size content attribute.
  114. if (!m_sizes)
  115. m_sizes = DOM::DOMTokenList::create(*this, HTML::AttributeNames::sizes);
  116. return *m_sizes;
  117. }
  118. bool HTMLLinkElement::has_loaded_icon() const
  119. {
  120. return m_relationship & Relationship::Icon && resource() && resource()->is_loaded() && resource()->has_encoded_data();
  121. }
  122. void HTMLLinkElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)
  123. {
  124. Base::attribute_changed(name, old_value, value, namespace_);
  125. // 4.6.7 Link types - https://html.spec.whatwg.org/multipage/links.html#linkTypes
  126. if (name == HTML::AttributeNames::rel) {
  127. m_relationship = 0;
  128. // Keywords are always ASCII case-insensitive, and must be compared as such.
  129. auto lowercased_value = value.value_or(String {}).to_ascii_lowercase();
  130. // To determine which link types apply to a link, a, area, or form element,
  131. // the element's rel attribute must be split on ASCII whitespace.
  132. // The resulting tokens are the keywords for the link types that apply to that element.
  133. auto parts = lowercased_value.bytes_as_string_view().split_view_if(Infra::is_ascii_whitespace);
  134. for (auto& part : parts) {
  135. if (part == "stylesheet"sv)
  136. m_relationship |= Relationship::Stylesheet;
  137. else if (part == "alternate"sv)
  138. m_relationship |= Relationship::Alternate;
  139. else if (part == "preload"sv)
  140. m_relationship |= Relationship::Preload;
  141. else if (part == "dns-prefetch"sv)
  142. m_relationship |= Relationship::DNSPrefetch;
  143. else if (part == "preconnect"sv)
  144. m_relationship |= Relationship::Preconnect;
  145. else if (part == "icon"sv)
  146. m_relationship |= Relationship::Icon;
  147. }
  148. if (m_rel_list)
  149. m_rel_list->associated_attribute_changed(value.value_or(String {}));
  150. }
  151. // https://html.spec.whatwg.org/multipage/semantics.html#the-link-element:explicitly-enabled
  152. // Whenever the disabled attribute is removed, set the link element's explicitly enabled attribute to true.
  153. if (!value.has_value() && name == HTML::AttributeNames::disabled)
  154. m_explicitly_enabled = true;
  155. if (m_relationship & Relationship::Stylesheet) {
  156. if (name == HTML::AttributeNames::disabled && m_loaded_style_sheet)
  157. document_or_shadow_root_style_sheets().remove_a_css_style_sheet(*m_loaded_style_sheet);
  158. // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:fetch-and-process-the-linked-resource
  159. // The appropriate times to fetch and process this type of link are:
  160. if (
  161. is_browsing_context_connected()
  162. && (
  163. // AD-HOC: When the rel attribute changes
  164. name == AttributeNames::rel ||
  165. // - When the href attribute of the link element of an external resource link that is already browsing-context connected is changed.
  166. name == AttributeNames::href ||
  167. // - When the disabled attribute of the link element of an external resource link that is already browsing-context connected is set, changed, or removed.
  168. name == AttributeNames::disabled ||
  169. // - When the crossorigin attribute of the link element of an external resource link that is already browsing-context connected is set, changed, or removed.
  170. name == AttributeNames::crossorigin
  171. // FIXME: - When the type attribute of the link element of an external resource link that is already browsing-context connected is set or changed to a value that does not or no longer matches the Content-Type metadata of the previous obtained external resource, if any.
  172. // FIXME: - When the type attribute of the link element of an external resource link that is already browsing-context connected, but was previously not obtained due to the type attribute specifying an unsupported type, is removed or changed.
  173. )) {
  174. fetch_and_process_linked_resource();
  175. }
  176. if (name == HTML::AttributeNames::media && m_loaded_style_sheet) {
  177. m_loaded_style_sheet->set_media(value.value_or(String {}));
  178. }
  179. }
  180. }
  181. void HTMLLinkElement::resource_did_fail()
  182. {
  183. dbgln_if(CSS_LOADER_DEBUG, "HTMLLinkElement: Resource did fail. URL: {}", resource()->url());
  184. if (m_relationship & Relationship::Preload) {
  185. dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::error));
  186. }
  187. }
  188. void HTMLLinkElement::resource_did_load()
  189. {
  190. VERIFY(resource());
  191. if (m_relationship & Relationship::Icon) {
  192. resource_did_load_favicon();
  193. m_document_load_event_delayer.clear();
  194. }
  195. if (m_relationship & Relationship::Preload) {
  196. dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::load));
  197. }
  198. }
  199. // https://html.spec.whatwg.org/multipage/semantics.html#create-link-options-from-element
  200. HTMLLinkElement::LinkProcessingOptions HTMLLinkElement::create_link_options()
  201. {
  202. // 1. Let document be el's node document.
  203. auto& document = this->document();
  204. // 2. Let options be a new link processing options with
  205. LinkProcessingOptions options;
  206. // FIXME: destination the result of translating the state of el's as attribute
  207. // crossorigin the state of el's crossorigin content attribute
  208. options.crossorigin = cors_setting_attribute_from_keyword(get_attribute(AttributeNames::crossorigin));
  209. // referrer policy the state of el's referrerpolicy content attribute
  210. options.referrer_policy = ReferrerPolicy::from_string(get_attribute(AttributeNames::referrerpolicy).value_or(""_string)).value_or(ReferrerPolicy::ReferrerPolicy::EmptyString);
  211. // FIXME: source set el's source set
  212. // base URL document's document base URL
  213. options.base_url = document.base_url();
  214. // origin document's origin
  215. options.origin = document.origin();
  216. // environment document's relevant settings object
  217. options.environment = &document.relevant_settings_object();
  218. // policy container document's policy container
  219. options.policy_container = document.policy_container();
  220. // document document
  221. options.document = &document;
  222. // FIXME: cryptographic nonce metadata The current value of el's [[CryptographicNonce]] internal slot
  223. // fetch priority the state of el's fetchpriority content attribute
  224. options.fetch_priority = Fetch::Infrastructure::request_priority_from_string(get_attribute_value(HTML::AttributeNames::fetchpriority)).value_or(Fetch::Infrastructure::Request::Priority::Auto);
  225. // 3. If el has an href attribute, then set options's href to the value of el's href attribute.
  226. if (auto maybe_href = get_attribute(AttributeNames::href); maybe_href.has_value())
  227. options.href = maybe_href.value();
  228. // 4. If el has an integrity attribute, then set options's integrity to the value of el's integrity content attribute.
  229. if (auto maybe_integrity = get_attribute(AttributeNames::integrity); maybe_integrity.has_value())
  230. options.integrity = maybe_integrity.value();
  231. // 5. If el has a type attribute, then set options's type to the value of el's type attribute.
  232. if (auto maybe_type = get_attribute(AttributeNames::type); maybe_type.has_value())
  233. options.type = maybe_type.value();
  234. // FIXME: 6. Assert: options's href is not the empty string, or options's source set is not null.
  235. // A link element with neither an href or an imagesrcset does not represent a link.
  236. // 7. Return options.
  237. return options;
  238. }
  239. // https://html.spec.whatwg.org/multipage/semantics.html#create-a-link-request
  240. GC::Ptr<Fetch::Infrastructure::Request> HTMLLinkElement::create_link_request(HTMLLinkElement::LinkProcessingOptions const& options)
  241. {
  242. // 1. Assert: options's href is not the empty string.
  243. VERIFY(!options.href.is_empty());
  244. // FIXME: 2. If options's destination is null, then return null.
  245. // 3. Let url be the result of encoding-parsing a URL given options's href, relative to options's base URL.
  246. auto url = options.base_url.complete_url(options.href);
  247. // 4. If url is failure, then return null.
  248. if (!url.is_valid())
  249. return nullptr;
  250. // 5. Let request be the result of creating a potential-CORS request given url, options's destination, and options's crossorigin.
  251. auto request = create_potential_CORS_request(vm(), url, options.destination, options.crossorigin);
  252. // 6. Set request's policy container to options's policy container.
  253. request->set_policy_container(options.policy_container);
  254. // 7. Set request's integrity metadata to options's integrity.
  255. request->set_integrity_metadata(options.integrity);
  256. // 8. Set request's cryptographic nonce metadata to options's cryptographic nonce metadata.
  257. request->set_cryptographic_nonce_metadata(options.cryptographic_nonce_metadata);
  258. // 9. Set request's referrer policy to options's referrer policy.
  259. request->set_referrer_policy(options.referrer_policy);
  260. // 10. Set request's client to options's environment.
  261. request->set_client(options.environment);
  262. // 11. Set request's priority to options's fetch priority.
  263. request->set_priority(options.fetch_priority);
  264. // 12. Return request.
  265. return request;
  266. }
  267. // https://html.spec.whatwg.org/multipage/semantics.html#fetch-and-process-the-linked-resource
  268. void HTMLLinkElement::fetch_and_process_linked_resource()
  269. {
  270. default_fetch_and_process_linked_resource();
  271. }
  272. // https://html.spec.whatwg.org/multipage/semantics.html#default-fetch-and-process-the-linked-resource
  273. void HTMLLinkElement::default_fetch_and_process_linked_resource()
  274. {
  275. // https://html.spec.whatwg.org/multipage/semantics.html#the-link-element:attr-link-href-4
  276. // If both the href and imagesrcset attributes are absent, then the element does not define a link.
  277. // FIXME: Support imagesrcset attribute
  278. if (!has_attribute(AttributeNames::href) || href().is_empty())
  279. return;
  280. // 1. Let options be the result of creating link options from el.
  281. auto options = create_link_options();
  282. // 2. Let request be the result of creating a link request given options.
  283. auto request = create_link_request(options);
  284. // 3. If request is null, then return.
  285. if (request == nullptr) {
  286. return;
  287. }
  288. // FIXME: 4. Set request's synchronous flag.
  289. // 5. Run the linked resource fetch setup steps, given el and request. If the result is false, then return.
  290. if (!linked_resource_fetch_setup_steps(*request))
  291. return;
  292. // 6. Set request's initiator type to "css" if el's rel attribute contains the keyword stylesheet; "link" otherwise.
  293. if (m_relationship & Relationship::Stylesheet) {
  294. request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::CSS);
  295. } else {
  296. request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Link);
  297. }
  298. // 7. Fetch request with processResponseConsumeBody set to the following steps given response response and null, failure, or a byte sequence bodyBytes:
  299. Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {};
  300. fetch_algorithms_input.process_response_consume_body = [this, hr = options](auto response, auto body_bytes) {
  301. // FIXME: If the response is CORS cross-origin, we must use its internal response to query any of its data. See:
  302. // https://github.com/whatwg/html/issues/9355
  303. response = response->unsafe_response();
  304. // 1. Let success be true.
  305. bool success = true;
  306. // 2. If either of the following conditions are met:
  307. // - bodyBytes is null or failure; or
  308. // - response's status is not an ok status,
  309. if (body_bytes.template has<Empty>() || body_bytes.template has<Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag>() || !Fetch::Infrastructure::is_ok_status(response->status())) {
  310. // then set success to false.
  311. success = false;
  312. }
  313. // FIXME: 3. Otherwise, wait for the link resource's critical subresources to finish loading.
  314. // 4. Process the linked resource given el, success, response, and bodyBytes.
  315. process_linked_resource(success, response, body_bytes);
  316. };
  317. if (m_fetch_controller)
  318. m_fetch_controller->abort(realm(), {});
  319. m_fetch_controller = MUST(Fetch::Fetching::fetch(realm(), *request, Fetch::Infrastructure::FetchAlgorithms::create(vm(), move(fetch_algorithms_input))));
  320. }
  321. // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:process-the-linked-resource
  322. void HTMLLinkElement::process_stylesheet_resource(bool success, Fetch::Infrastructure::Response const& response, Variant<Empty, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag, ByteBuffer> body_bytes)
  323. {
  324. // 1. If the resource's Content-Type metadata is not text/css, then set success to false.
  325. auto extracted_mime_type = response.header_list()->extract_mime_type();
  326. if (!extracted_mime_type.has_value() || extracted_mime_type->essence() != "text/css") {
  327. success = false;
  328. }
  329. // FIXME: 2. If el no longer creates an external resource link that contributes to the styling processing model,
  330. // or if, since the resource in question was fetched, it has become appropriate to fetch it again, then return.
  331. // 3. If el has an associated CSS style sheet, remove the CSS style sheet.
  332. if (m_loaded_style_sheet) {
  333. document_or_shadow_root_style_sheets().remove_a_css_style_sheet(*m_loaded_style_sheet);
  334. m_loaded_style_sheet = nullptr;
  335. }
  336. // 4. If success is true, then:
  337. if (success) {
  338. // 1. Create a CSS style sheet with the following properties:
  339. // type
  340. // text/css
  341. // location
  342. // response's URL list[0]
  343. // owner node
  344. // element
  345. // media
  346. // The media attribute of element.
  347. // title
  348. // The title attribute of element, if element is in a document tree, or the empty string otherwise.
  349. // alternate flag
  350. // Set if the link is an alternative style sheet and element's explicitly enabled is false; unset otherwise.
  351. // origin-clean flag
  352. // Set if the resource is CORS-same-origin; unset otherwise.
  353. // parent CSS style sheet
  354. // owner CSS rule
  355. // null
  356. // disabled flag
  357. // Left at its default value.
  358. // CSS rules
  359. // Left uninitialized.
  360. //
  361. // The CSS environment encoding is the result of running the following steps: [CSSSYNTAX]
  362. // 1. If the element has a charset attribute, get an encoding from that attribute's value. If that succeeds, return the resulting encoding. [ENCODING]
  363. // 2. Otherwise, return the document's character encoding. [DOM]
  364. Optional<String> encoding;
  365. if (auto charset = attribute(HTML::AttributeNames::charset); charset.has_value())
  366. encoding = charset.release_value();
  367. if (!encoding.has_value())
  368. encoding = document().encoding_or_default();
  369. auto decoder = TextCodec::decoder_for(*encoding);
  370. if (!decoder.has_value()) {
  371. // If we don't support the encoding yet, let's error out instead of trying to decode it as something it's most likely not.
  372. dbgln("FIXME: Style sheet encoding '{}' is not supported yet", encoding);
  373. dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::error));
  374. } else {
  375. auto const& encoded_string = body_bytes.get<ByteBuffer>();
  376. auto maybe_decoded_string = TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, encoded_string);
  377. if (maybe_decoded_string.is_error()) {
  378. dbgln("Style sheet {} claimed to be '{}' but decoding failed", response.url().value_or(URL::URL()), encoding);
  379. dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::error));
  380. } else {
  381. auto const decoded_string = maybe_decoded_string.release_value();
  382. m_loaded_style_sheet = parse_css_stylesheet(CSS::Parser::ParsingContext(document(), *response.url()), decoded_string);
  383. if (m_loaded_style_sheet) {
  384. Optional<String> location;
  385. if (!response.url_list().is_empty())
  386. location = MUST(response.url_list().first().to_string());
  387. document().style_sheets().create_a_css_style_sheet(
  388. "text/css"_string,
  389. this,
  390. attribute(HTML::AttributeNames::media).value_or({}),
  391. in_a_document_tree() ? attribute(HTML::AttributeNames::title).value_or({}) : String {},
  392. m_relationship & Relationship::Alternate && !m_explicitly_enabled,
  393. true,
  394. move(location),
  395. nullptr,
  396. nullptr,
  397. *m_loaded_style_sheet);
  398. } else {
  399. dbgln_if(CSS_LOADER_DEBUG, "HTMLLinkElement: Failed to parse stylesheet: {}", resource()->url());
  400. }
  401. // 2. Fire an event named load at el.
  402. dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::load));
  403. }
  404. }
  405. }
  406. // 5. Otherwise, fire an event named error at el.
  407. else {
  408. dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::error));
  409. }
  410. // FIXME: 6. If el contributes a script-blocking style sheet, then:
  411. // FIXME: 1. Assert: el's node document's script-blocking style sheet counter is greater than 0.
  412. // FIXME: 2. Decrement el's node document's script-blocking style sheet counter by 1.
  413. // 7. Unblock rendering on el.
  414. m_document_load_event_delayer.clear();
  415. }
  416. // https://html.spec.whatwg.org/multipage/semantics.html#process-the-linked-resource
  417. void HTMLLinkElement::process_linked_resource(bool success, Fetch::Infrastructure::Response const& response, Variant<Empty, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag, ByteBuffer> body_bytes)
  418. {
  419. if (m_relationship & Relationship::Stylesheet)
  420. process_stylesheet_resource(success, response, body_bytes);
  421. }
  422. // https://html.spec.whatwg.org/multipage/semantics.html#linked-resource-fetch-setup-steps
  423. bool HTMLLinkElement::linked_resource_fetch_setup_steps(Fetch::Infrastructure::Request& request)
  424. {
  425. if (m_relationship & Relationship::Stylesheet)
  426. return stylesheet_linked_resource_fetch_setup_steps(request);
  427. return true;
  428. }
  429. // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:linked-resource-fetch-setup-steps
  430. bool HTMLLinkElement::stylesheet_linked_resource_fetch_setup_steps(Fetch::Infrastructure::Request& request)
  431. {
  432. // 1. If el's disabled attribute is set, then return false.
  433. if (has_attribute(AttributeNames::disabled))
  434. return false;
  435. // FIXME: 2. If el contributes a script-blocking style sheet, increment el's node document's script-blocking style sheet counter by 1.
  436. // 3. If el's media attribute's value matches the environment and el is potentially render-blocking, then block rendering on el.
  437. // FIXME: Check media attribute value.
  438. m_document_load_event_delayer.emplace(document());
  439. // 4. If el is currently render-blocking, then set request's render-blocking to true.
  440. // FIXME: Check if el is currently render-blocking.
  441. request.set_render_blocking(true);
  442. // 5. Return true.
  443. return true;
  444. }
  445. void HTMLLinkElement::resource_did_load_favicon()
  446. {
  447. VERIFY(m_relationship & (Relationship::Icon));
  448. if (!resource()->has_encoded_data()) {
  449. dbgln_if(SPAM_DEBUG, "Favicon downloaded, no encoded data");
  450. return;
  451. }
  452. dbgln_if(SPAM_DEBUG, "Favicon downloaded, {} bytes from {}", resource()->encoded_data().size(), resource()->url());
  453. document().check_favicon_after_loading_link_resource();
  454. }
  455. static NonnullRefPtr<Core::Promise<Web::Platform::DecodedImage>> decode_favicon(ReadonlyBytes favicon_data, URL::URL const& favicon_url, GC::Ref<DOM::Document> document)
  456. {
  457. auto on_failed_decode = [favicon_url]([[maybe_unused]] Error& error) {
  458. dbgln_if(IMAGE_DECODER_DEBUG, "Failed to decode favicon {}: {}", favicon_url, error);
  459. };
  460. auto on_successful_decode = [document = GC::Root(document)](Web::Platform::DecodedImage& decoded_image) -> ErrorOr<void> {
  461. auto favicon_bitmap = decoded_image.frames[0].bitmap;
  462. dbgln_if(IMAGE_DECODER_DEBUG, "Decoded favicon, {}", favicon_bitmap->size());
  463. auto navigable = document->navigable();
  464. if (navigable && navigable->is_traversable())
  465. navigable->traversable_navigable()->page().client().page_did_change_favicon(*favicon_bitmap);
  466. return {};
  467. };
  468. auto promise = Platform::ImageCodecPlugin::the().decode_image(favicon_data, move(on_successful_decode), move(on_failed_decode));
  469. return promise;
  470. }
  471. bool HTMLLinkElement::load_favicon_and_use_if_window_is_active()
  472. {
  473. if (!has_loaded_icon())
  474. return false;
  475. // FIXME: Refactor the caller(s) to handle the async nature of image loading
  476. auto promise = decode_favicon(resource()->encoded_data(), resource()->url(), document());
  477. auto result = promise->await();
  478. return !result.is_error();
  479. }
  480. // https://html.spec.whatwg.org/multipage/links.html#rel-icon:the-link-element-3
  481. WebIDL::ExceptionOr<void> HTMLLinkElement::load_fallback_favicon_if_needed(GC::Ref<DOM::Document> document)
  482. {
  483. auto& realm = document->realm();
  484. auto& vm = realm.vm();
  485. // In the absence of a link with the icon keyword, for Document objects whose URL's scheme is an HTTP(S) scheme,
  486. // user agents may instead run these steps in parallel:
  487. if (document->has_active_favicon())
  488. return {};
  489. if (!document->url().scheme().is_one_of("http"sv, "https"sv))
  490. return {};
  491. // 1. Let request be a new request whose URL is the URL record obtained by resolving the URL "/favicon.ico" against
  492. // the Document object's URL, client is the Document object's relevant settings object, destination is "image",
  493. // synchronous flag is set, credentials mode is "include", and whose use-URL-credentials flag is set.
  494. // NOTE: Fetch requests no longer have a synchronous flag, see https://github.com/whatwg/fetch/pull/1165
  495. auto request = Fetch::Infrastructure::Request::create(vm);
  496. request->set_url(document->parse_url("/favicon.ico"sv));
  497. request->set_client(&document->relevant_settings_object());
  498. request->set_destination(Fetch::Infrastructure::Request::Destination::Image);
  499. request->set_credentials_mode(Fetch::Infrastructure::Request::CredentialsMode::Include);
  500. request->set_use_url_credentials(true);
  501. // 2. Let response be the result of fetching request.
  502. Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {};
  503. fetch_algorithms_input.process_response = [document, request](GC::Ref<Fetch::Infrastructure::Response> response) {
  504. auto& realm = document->realm();
  505. auto global = GC::Ref { realm.global_object() };
  506. auto process_body = GC::create_function(realm.heap(), [document, request](ByteBuffer body) {
  507. (void)decode_favicon(body, request->url(), document);
  508. });
  509. auto process_body_error = GC::create_function(realm.heap(), [](JS::Value) {
  510. });
  511. // Check for failed favicon response
  512. if (!Fetch::Infrastructure::is_ok_status(response->status()) || !response->body()) {
  513. return;
  514. }
  515. // 3. Use response's unsafe response as an icon as if it had been declared using the icon keyword.
  516. if (auto body = response->unsafe_response()->body())
  517. body->fully_read(realm, process_body, process_body_error, global);
  518. };
  519. TRY(Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input))));
  520. return {};
  521. }
  522. void HTMLLinkElement::visit_edges(Cell::Visitor& visitor)
  523. {
  524. Base::visit_edges(visitor);
  525. visitor.visit(m_fetch_controller);
  526. visitor.visit(m_loaded_style_sheet);
  527. visitor.visit(m_rel_list);
  528. visitor.visit(m_sizes);
  529. }
  530. }