summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp339
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h70
2 files changed, 362 insertions, 47 deletions
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp
index 294f5ee67b..217ebdbde3 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp
@@ -2,6 +2,7 @@
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, the SerenityOS developers.
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
+ * Copyright (c) 2023, Srikavin Ramkumar <me@srikavin.me>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -11,7 +12,14 @@
#include <AK/URL.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
+#include <LibWeb/Fetch/Fetching/Fetching.h>
+#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
+#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
+#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
+#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/HTML/HTMLLinkElement.h>
+#include <LibWeb/HTML/PotentialCORSRequest.h>
#include <LibWeb/Infra/CharacterTypes.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/Page/Page.h>
@@ -36,25 +44,18 @@ JS::ThrowCompletionOr<void> HTMLLinkElement::initialize(JS::Realm& realm)
void HTMLLinkElement::inserted()
{
- if (has_attribute(AttributeNames::disabled) && (m_relationship & Relationship::Stylesheet))
- return;
-
HTMLElement::inserted();
+ // FIXME: Handle alternate stylesheets properly
if (m_relationship & Relationship::Stylesheet && !(m_relationship & Relationship::Alternate)) {
- auto url = document().parse_url(href());
- dbgln_if(CSS_LOADER_DEBUG, "HTMLLinkElement: Loading import URL: {}", url);
- auto request = LoadRequest::create_for_url_on_page(url, document().page());
- // NOTE: Mark this element as delaying the document load event *before* calling set_resource()
- // as it may trigger a synchronous resource_did_load() callback.
- m_document_load_event_delayer.emplace(document());
- set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, request));
-
- // NOTE: If we ended up not loading a resource for whatever reason, don't delay the load event.
- if (!resource())
- m_document_load_event_delayer.clear();
+ // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:fetch-and-process-the-linked-resource
+ // The appropriate times to fetch and process this type of link are:
+ // - When the external resource link is created on a link element that is already browsing-context connected.
+ // - When the external resource link's link element becomes browsing-context connected.
+ fetch_and_process_linked_resource();
}
+ // FIXME: Follow spec for fetching and processing these attributes as well
if (m_relationship & Relationship::Preload) {
// FIXME: Respect the "as" attribute.
LoadRequest request;
@@ -103,68 +104,314 @@ void HTMLLinkElement::parse_attribute(DeprecatedFlyString const& name, Deprecate
}
}
- if (name == HTML::AttributeNames::disabled && (m_relationship & Relationship::Stylesheet) && m_loaded_style_sheet)
- document().style_sheets().remove_sheet(*m_loaded_style_sheet);
+ if (m_relationship & Relationship::Stylesheet) {
+ if (name == HTML::AttributeNames::disabled && m_loaded_style_sheet)
+ document().style_sheets().remove_sheet(*m_loaded_style_sheet);
+
+ // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:fetch-and-process-the-linked-resource
+ // The appropriate times to fetch and process this type of link are:
+ if (
+ // - When the href attribute of the link element of an external resource link that is already browsing-context connected is changed.
+ name == AttributeNames::href ||
+ // - When the disabled attribute of the link element of an external resource link that is already browsing-context connected is set, changed, or removed.
+ name == AttributeNames::disabled ||
+ // - When the crossorigin attribute of the link element of an external resource link that is already browsing-context connected is set, changed, or removed.
+ name == AttributeNames::crossorigin
+ // 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.
+ // 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.
+ ) {
+ fetch_and_process_linked_resource();
+ }
+ }
}
void HTMLLinkElement::resource_did_fail()
{
dbgln_if(CSS_LOADER_DEBUG, "HTMLLinkElement: Resource did fail. URL: {}", resource()->url());
-
- m_document_load_event_delayer.clear();
}
void HTMLLinkElement::resource_did_load()
{
VERIFY(resource());
- VERIFY(m_relationship & (Relationship::Stylesheet | Relationship::Icon));
-
- if (m_relationship & Relationship::Stylesheet)
- resource_did_load_stylesheet();
- if (m_relationship & Relationship::Icon)
+ VERIFY(m_relationship & (Relationship::Icon));
+ if (m_relationship & Relationship::Icon) {
resource_did_load_favicon();
+ m_document_load_event_delayer.clear();
+ }
}
void HTMLLinkElement::did_remove_attribute(DeprecatedFlyString const& attr)
{
- if (attr == HTML::AttributeNames::disabled && (m_relationship & Relationship::Stylesheet)) {
- if (!resource())
- inserted();
- else
- resource_did_load_stylesheet();
+ if (m_relationship & Relationship::Stylesheet) {
+ // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:fetch-and-process-the-linked-resource
+ // The appropriate times to fetch and process this type of link are:
+ if (
+ // - When the href attribute of the link element of an external resource link that is already browsing-context connected is changed.
+ attr == AttributeNames::href ||
+ // - When the disabled attribute of the link element of an external resource link that is already browsing-context connected is set, changed, or removed.
+ attr == AttributeNames::disabled ||
+ // - When the crossorigin attribute of the link element of an external resource link that is already browsing-context connected is set, changed, or removed.
+ attr == AttributeNames::crossorigin
+ // 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.
+ ) {
+ fetch_and_process_linked_resource();
+ }
}
}
-void HTMLLinkElement::resource_did_load_stylesheet()
+// https://html.spec.whatwg.org/multipage/semantics.html#create-link-options-from-element
+HTMLLinkElement::LinkProcessingOptions HTMLLinkElement::create_link_options()
{
- VERIFY(m_relationship & Relationship::Stylesheet);
- m_document_load_event_delayer.clear();
+ // 1. Let document be el's node document.
+ auto& document = this->document();
+
+ // 2. Let options be a new link processing options with
+ LinkProcessingOptions options;
+ // FIXME: destination the result of translating the state of el's as attribute
+ // crossorigin the state of el's crossorigin content attribute
+ options.crossorigin = cors_setting_attribute_from_keyword(
+ has_attribute(AttributeNames::crossorigin) ? String::from_deprecated_string(get_attribute(AttributeNames::crossorigin)).release_value_but_fixme_should_propagate_errors()
+ : Optional<String> {});
+ // FIXME: referrer policy the state of el's referrerpolicy content attribute
+ // FIXME: source set el's source set
+ // base URL document's URL
+ options.base_url = document.url();
+ // origin document's origin
+ options.origin = document.origin();
+ // environment document's relevant settings object
+ options.environment = &document.relevant_settings_object();
+ // policy container document's policy container
+ options.policy_container = document.policy_container();
+ // document document
+ options.document = &document;
+ // FIXME: cryptographic nonce metadata The current value of el's [[CryptographicNonce]] internal slot
+
+ // 3. If el has an href attribute, then set options's href to the value of el's href attribute.
+ if (has_attribute(AttributeNames::href))
+ options.href = String::from_deprecated_string(get_attribute(AttributeNames::href)).release_value_but_fixme_should_propagate_errors();
+
+ // 4. If el has an integrity attribute, then set options's integrity to the value of el's integrity content attribute.
+ if (has_attribute(AttributeNames::integrity))
+ options.integrity = String::from_deprecated_string(get_attribute(AttributeNames::integrity)).release_value_but_fixme_should_propagate_errors();
+
+ // 5. If el has a type attribute, then set options's type to the value of el's type attribute.
+ if (has_attribute(AttributeNames::type))
+ options.type = String::from_deprecated_string(get_attribute(AttributeNames::type)).release_value_but_fixme_should_propagate_errors();
+
+ // FIXME: 6. Assert: options's href is not the empty string, or options's source set is not null.
+ // A link element with neither an href or an imagesrcset does not represent a link.
+
+ // 7. Return options.
+ return options;
+}
- if (!resource()->has_encoded_data()) {
- dbgln_if(CSS_LOADER_DEBUG, "HTMLLinkElement: Resource did load, no encoded data. URL: {}", resource()->url());
+// https://html.spec.whatwg.org/multipage/semantics.html#create-a-link-request
+JS::GCPtr<Fetch::Infrastructure::Request> HTMLLinkElement::create_link_request(HTMLLinkElement::LinkProcessingOptions const& options)
+{
+ // 1. Assert: options's href is not the empty string.
+
+ // FIXME: 2. If options's destination is not a destination, then return null.
+
+ // 3. Parse a URL given options's href, relative to options's base URL. If that fails, then return null. Otherwise, let url be the resulting URL record.
+ auto url = options.base_url.complete_url(options.href);
+ if (!url.is_valid())
+ return nullptr;
+
+ // 4. Let request be the result of creating a potential-CORS request given url, options's destination, and options's crossorigin.
+ auto request = create_potential_CORS_request(vm(), url, options.destination, options.crossorigin);
+
+ // 5. Set request's policy container to options's policy container.
+ request->set_policy_container(options.policy_container);
+
+ // 6. Set request's integrity metadata to options's integrity.
+ request->set_integrity_metadata(options.integrity);
+
+ // 7. Set request's cryptographic nonce metadata to options's cryptographic nonce metadata.
+ request->set_cryptographic_nonce_metadata(options.cryptographic_nonce_metadata);
+
+ // 8. Set request's referrer policy to options's referrer policy.
+ request->set_referrer_policy(options.referrer_policy);
+
+ // 9. Set request's client to options's environment.
+ request->set_client(options.environment);
+
+ // 10. Return request.
+ return request;
+}
+
+// https://html.spec.whatwg.org/multipage/semantics.html#fetch-and-process-the-linked-resource
+void HTMLLinkElement::fetch_and_process_linked_resource()
+{
+ default_fetch_and_process_linked_resource();
+}
+
+// https://html.spec.whatwg.org/multipage/semantics.html#default-fetch-and-process-the-linked-resource
+void HTMLLinkElement::default_fetch_and_process_linked_resource()
+{
+ // https://html.spec.whatwg.org/multipage/semantics.html#the-link-element:attr-link-href-4
+ // If both the href and imagesrcset attributes are absent, then the element does not define a link.
+ // FIXME: Support imagesrcset attribute
+ if (!has_attribute(AttributeNames::href) || href().is_empty())
+ return;
+
+ // 1. Let options be the result of creating link options from el.
+ auto options = create_link_options();
+
+ // 2. Let request be the result of creating a link request given options.
+ auto request = create_link_request(options);
+
+ // 3. If request is null, then return.
+ if (request == nullptr) {
+ return;
+ }
+
+ // FIXME: 4. Set request's synchronous flag.
+
+ // 5. Run the linked resource fetch setup steps, given el and request. If the result is false, then return.
+ if (!linked_resource_fetch_setup_steps(*request))
+ return;
+
+ // 6. Set request's initiator type to "css" if el's rel attribute contains the keyword stylesheet; "link" otherwise.
+ if (m_relationship & Relationship::Stylesheet) {
+ request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::CSS);
} else {
- dbgln_if(CSS_LOADER_DEBUG, "HTMLLinkElement: Resource did load, has encoded data. URL: {}", resource()->url());
+ request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Link);
+ }
- if (resource()->mime_type() != "text/css"sv) {
- dbgln_if(CSS_LOADER_DEBUG, "HTMLLinkElement: Resource did load, but MIME type was {} instead of text/css. URL: {}", resource()->mime_type(), resource()->url());
- return;
- }
+ // 7. Fetch request with processResponseConsumeBody set to the following steps given response response and null, failure, or a byte sequence bodyBytes:
+ Fetch::Fetching::fetch(
+ realm(), *request,
+ Fetch::Infrastructure::FetchAlgorithms::create(vm(),
+ { .process_request_body_chunk_length = {},
+ .process_request_end_of_body = {},
+ .process_early_hints_response = {},
+ .process_response = {},
+ .process_response_end_of_body = {},
+ .process_response_consume_body = [this, hr = options](auto response, auto body_bytes) {
+ // 1. Let success be true.
+ bool success = true;
+
+ // 2. If either of the following conditions are met:
+ // - bodyBytes is null or failure; or
+ // - response's status is not an ok status,
+ // NOTE: content-specific errors, e.g., CSS parse errors or PNG decoding errors, do not affect success.
+ if (body_bytes.template has<Empty>() || body_bytes.template has<Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag>() || !Fetch::Infrastructure::is_ok_status(response->status())) {
+ // then set success to false.
+ success = false;
+ }
+ // FIXME: 3. Otherwise, wait for the link resource's critical subresources to finish loading.
+
+ // 4. Process the linked resource given el, success, response, and bodyBytes.
+ process_linked_resource(success, response, body_bytes);
+ } }))
+ .release_value_but_fixme_should_propagate_errors();
+}
+
+// https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:process-the-linked-resource
+void HTMLLinkElement::process_stylesheet_resource(bool success, Fetch::Infrastructure::Response const& response, Variant<Empty, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag, ByteBuffer> body_bytes)
+{
+ // 1. If the resource's Content-Type metadata is not text/css, then set success to false.
+ auto extracted_mime_type = response.header_list()->extract_mime_type().release_value_but_fixme_should_propagate_errors();
+ if (!extracted_mime_type.has_value() || extracted_mime_type->essence() != "text/css") {
+ success = false;
}
- CSS::CSSStyleSheet* sheet = m_loaded_style_sheet;
- if (!sheet) {
- sheet = parse_css_stylesheet(CSS::Parser::ParsingContext(document(), resource()->url()), resource()->encoded_data());
+ // FIXME: 2. If el no longer creates an external resource link that contributes to the styling processing model,
+ // or if, since the resource in question was fetched, it has become appropriate to fetch it again, then return.
+
+ // 3. If el has an associated CSS style sheet, remove the CSS style sheet.
+ if (m_loaded_style_sheet) {
+ document().style_sheets().remove_sheet(*m_loaded_style_sheet);
+ m_loaded_style_sheet = nullptr;
+ }
- if (!sheet) {
+ // 4. If success is true, then:
+ if (success) {
+ // 1. Create a CSS style sheet with the following properties:
+ // type
+ // text/css
+ // location
+ // The resulting URL string determined during the fetch and process the linked resource algorithm.
+ // owner node
+ // element
+ // media
+ // The media attribute of element.
+ // title
+ // The title attribute of element, if element is in a document tree, or the empty string otherwise.
+ // alternate flag
+ // Set if the link is an alternative style sheet and element's explicitly enabled is false; unset otherwise.
+ // origin-clean flag
+ // Set if the resource is CORS-same-origin; unset otherwise.
+ // parent CSS style sheet
+ // owner CSS rule
+ // null
+ // disabled flag
+ // Left at its default value.
+ // CSS rules
+ // Left uninitialized.
+ //
+ // The CSS environment encoding is the result of running the following steps: [CSSSYNTAX]
+ // 1. If the element has a charset attribute, get an encoding from that attribute's value. If that succeeds, return the resulting encoding. [ENCODING]
+ // 2. Otherwise, return the document's character encoding. [DOM]
+ m_loaded_style_sheet = parse_css_stylesheet(CSS::Parser::ParsingContext(document(), *response.url()), body_bytes.template get<ByteBuffer>());
+
+ if (m_loaded_style_sheet) {
+ m_loaded_style_sheet->set_owner_node(this);
+ document().style_sheets().add_sheet(*m_loaded_style_sheet);
+ } else {
dbgln_if(CSS_LOADER_DEBUG, "HTMLLinkElement: Failed to parse stylesheet: {}", resource()->url());
- return;
}
- m_loaded_style_sheet = sheet;
+ // 2. Fire an event named load at el.
+ dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors());
}
+ // 5. Otherwise, fire an event named error at el.
+ else {
+ dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::error).release_value_but_fixme_should_propagate_errors());
+ }
+
+ // FIXME: 6. If el contributes a script-blocking style sheet, then:
+ // FIXME: 1. Assert: el's node document's script-blocking style sheet counter is greater than 0.
+ // FIXME: 2. Decrement el's node document's script-blocking style sheet counter by 1.
+
+ // 7. Unblock rendering on el.
+ m_document_load_event_delayer.clear();
+}
+
+// https://html.spec.whatwg.org/multipage/semantics.html#process-the-linked-resource
+void HTMLLinkElement::process_linked_resource(bool success, Fetch::Infrastructure::Response const& response, Variant<Empty, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag, ByteBuffer> body_bytes)
+{
+ if (m_relationship & Relationship::Stylesheet)
+ process_stylesheet_resource(success, response, body_bytes);
+}
+
+// https://html.spec.whatwg.org/multipage/semantics.html#linked-resource-fetch-setup-steps
+bool HTMLLinkElement::linked_resource_fetch_setup_steps(Fetch::Infrastructure::Request& request)
+{
+ if (m_relationship & Relationship::Stylesheet)
+ return stylesheet_linked_resource_fetch_setup_steps(request);
+
+ return true;
+}
+
+// https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:linked-resource-fetch-setup-steps
+bool HTMLLinkElement::stylesheet_linked_resource_fetch_setup_steps(Fetch::Infrastructure::Request& request)
+{
+ // 1. If el's disabled attribute is set, then return false.
+ if (has_attribute(AttributeNames::disabled))
+ return false;
+ // FIXME: 2. If el contributes a script-blocking style sheet, increment el's node document's script-blocking style sheet counter by 1.
+
+ // 3. If el's media attribute's value matches the environment and el is potentially render-blocking, then block rendering on el.
+ // FIXME: Check media attribute value.
+ m_document_load_event_delayer.emplace(document());
+
+ // 4. If el is currently render-blocking, then set request's render-blocking to true.
+ // FIXME: Check if el is currently render-blocking.
+ request.set_render_blocking(true);
- sheet->set_owner_node(this);
- document().style_sheets().add_sheet(*sheet);
+ // 5. Return true.
+ return true;
}
void HTMLLinkElement::resource_did_load_favicon()
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h
index ee78861ec6..819a16bfd1 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h
@@ -2,6 +2,7 @@
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, the SerenityOS developers.
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
+ * Copyright (c) 2023, Srikavin Ramkumar <me@srikavin.me>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -9,6 +10,9 @@
#pragma once
#include <LibWeb/DOM/DocumentLoadEventDelayer.h>
+#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
+#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
+#include <LibWeb/HTML/CORSSettingAttribute.h>
#include <LibWeb/HTML/HTMLElement.h>
namespace Web::HTML {
@@ -44,7 +48,71 @@ private:
virtual void did_remove_attribute(DeprecatedFlyString const&) override;
virtual void visit_edges(Cell::Visitor&) override;
- void resource_did_load_stylesheet();
+ struct LinkProcessingOptions {
+ // href (default the empty string)
+ String href {};
+ // destination (default the empty string)
+ Optional<Fetch::Infrastructure::Request::Destination> destination {};
+ // initiator (default "link")
+ Optional<Fetch::Infrastructure::Request::InitiatorType> initiator { Fetch::Infrastructure::Request::InitiatorType::Link };
+ // integrity (default the empty string)
+ String integrity {};
+ // type (default the empty string)
+ String type {};
+ // cryptographic nonce metadata (default the empty string)
+ // A string
+ String cryptographic_nonce_metadata {};
+ // crossorigin (default No CORS)
+ // A CORS settings attribute state
+ CORSSettingAttribute crossorigin { CORSSettingAttribute::NoCORS };
+ // referrer policy (default the empty string)
+ // A referrer policy
+ Optional<ReferrerPolicy::ReferrerPolicy> referrer_policy {};
+ // FIXME: source set (default null)
+ // Null or a source set
+ // base URL
+ // A URL
+ AK::URL base_url;
+ // origin
+ // An origin
+ HTML::Origin origin;
+ // environment
+ // An environment
+ HTML::EnvironmentSettingsObject* environment;
+ // policy container
+ // A policy container
+ HTML::PolicyContainer policy_container;
+ // document (default null)
+ // Null or a Document
+ Web::DOM::Document* document { nullptr };
+ // FIXME: on document ready (default null)
+ // Null or an algorithm accepting a Document
+ };
+
+ // https://html.spec.whatwg.org/multipage/semantics.html#create-link-options-from-element
+ LinkProcessingOptions create_link_options();
+
+ // https://html.spec.whatwg.org/multipage/semantics.html#create-a-link-request
+ JS::GCPtr<Fetch::Infrastructure::Request> create_link_request(LinkProcessingOptions const&);
+
+ // https://html.spec.whatwg.org/multipage/semantics.html#linked-resource-fetch-setup-steps
+ bool linked_resource_fetch_setup_steps(Fetch::Infrastructure::Request&);
+
+ // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:linked-resource-fetch-setup-steps
+ bool stylesheet_linked_resource_fetch_setup_steps(Fetch::Infrastructure::Request&);
+
+ // https://html.spec.whatwg.org/multipage/semantics.html#fetch-and-process-the-linked-resource
+ void fetch_and_process_linked_resource();
+
+ // https://html.spec.whatwg.org/multipage/semantics.html#process-the-linked-resource
+ void process_linked_resource(bool success, Fetch::Infrastructure::Response const&, Variant<Empty, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag, ByteBuffer>);
+
+ // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:process-the-linked-resource
+ void process_stylesheet_resource(bool success, Fetch::Infrastructure::Response const&, Variant<Empty, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag, ByteBuffer>);
+
+ // https://html.spec.whatwg.org/multipage/semantics.html#default-fetch-and-process-the-linked-resource
+ void default_fetch_and_process_linked_resource();
+
void resource_did_load_favicon();
struct Relationship {