2020-01-18 09:38:21 +01:00
/*
2025-02-27 15:30:26 +01:00
* Copyright ( c ) 2018 - 2025 , Andreas Kling < andreas @ ladybird . org >
2021-02-21 18:44:17 +02:00
* Copyright ( c ) 2021 , the SerenityOS developers .
2021-11-18 19:22:59 +00:00
* Copyright ( c ) 2021 , Sam Atkins < atkinssj @ serenityos . org >
2023-01-14 19:05:28 +05:30
* Copyright ( c ) 2023 , Srikavin Ramkumar < me @ srikavin . me >
2020-01-18 09:38:21 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 09:38:21 +01:00
*/
2020-02-14 22:29:06 +01:00
# include <AK/ByteBuffer.h>
2021-11-18 19:22:59 +00:00
# include <AK/Debug.h>
2025-11-23 13:07:35 +01:00
# include <LibGfx/Bitmap.h>
# include <LibGfx/ImmutableBitmap.h>
2023-07-04 09:50:47 +02:00
# include <LibTextCodec/Decoder.h>
2024-03-18 16:22:27 +13:00
# include <LibURL/URL.h>
2024-04-27 12:09:58 +12:00
# include <LibWeb/Bindings/HTMLLinkElementPrototype.h>
2021-07-30 19:31:46 +01:00
# include <LibWeb/CSS/Parser/Parser.h>
2024-05-16 06:02:56 +01:00
# include <LibWeb/DOM/DOMTokenList.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/DOM/Document.h>
2023-01-14 19:05:28 +05:30
# include <LibWeb/DOM/Event.h>
2023-03-19 15:13:45 +01:00
# include <LibWeb/DOM/ShadowRoot.h>
2025-01-21 18:03:37 +13:00
# include <LibWeb/DOMURL/DOMURL.h>
2023-01-14 19:05:28 +05:30
# include <LibWeb/Fetch/Fetching/Fetching.h>
# include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
2024-09-22 13:29:49 +02:00
# include <LibWeb/Fetch/Infrastructure/FetchController.h>
2025-11-26 13:32:39 -05:00
# include <LibWeb/Fetch/Infrastructure/HTTP/MIME.h>
2023-01-14 19:05:28 +05:30
# include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
# include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
# include <LibWeb/HTML/EventNames.h>
2020-07-26 15:08:16 +02:00
# include <LibWeb/HTML/HTMLLinkElement.h>
2023-01-14 19:05:28 +05:30
# include <LibWeb/HTML/PotentialCORSRequest.h>
2023-08-27 17:06:39 +02:00
# include <LibWeb/HTML/TraversableNavigable.h>
2022-10-01 18:29:18 +01:00
# include <LibWeb/Infra/CharacterTypes.h>
2020-06-01 20:42:50 +02:00
# include <LibWeb/Loader/ResourceLoader.h>
2022-04-03 19:49:38 +02:00
# include <LibWeb/Page/Page.h>
2022-09-16 15:01:47 +02:00
# include <LibWeb/Platform/ImageCodecPlugin.h>
2025-08-25 18:15:31 +02:00
# include <LibWeb/SVG/SVGDecodedImageData.h>
2019-10-07 19:06:47 +02:00
2020-07-28 18:20:36 +02:00
namespace Web : : HTML {
2020-03-07 10:27:02 +01:00
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( HTMLLinkElement ) ;
2023-11-19 19:47:52 +01:00
2022-02-18 21:00:52 +01:00
HTMLLinkElement : : HTMLLinkElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
2021-02-07 11:20:15 +01:00
: HTMLElement ( document , move ( qualified_name ) )
2019-10-07 19:06:47 +02:00
{
}
2022-03-14 13:21:51 -06:00
HTMLLinkElement : : ~ HTMLLinkElement ( ) = default ;
2019-10-07 19:06:47 +02:00
2023-08-07 08:41:28 +02:00
void HTMLLinkElement : : initialize ( JS : : Realm & realm )
2023-01-10 06:28:20 -05:00
{
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( HTMLLinkElement ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2023-01-10 06:28:20 -05:00
}
2025-11-05 07:05:26 -05:00
void HTMLLinkElement : : visit_edges ( Cell : : Visitor & visitor )
2024-04-21 19:46:37 +02:00
{
2025-11-05 07:05:26 -05:00
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_fetch_controller ) ;
visitor . visit ( m_loaded_style_sheet ) ;
visitor . visit ( m_rel_list ) ;
visitor . visit ( m_sizes ) ;
2024-04-21 19:46:37 +02:00
}
2021-04-06 17:58:20 +01:00
void HTMLLinkElement : : inserted ( )
2020-06-02 12:53:29 +02:00
{
2021-04-06 17:58:20 +01:00
HTMLElement : : inserted ( ) ;
2020-06-02 12:53:29 +02:00
2025-11-05 07:35:17 -05:00
if ( ! document ( ) . browsing_context ( ) )
2024-04-24 17:57:18 +00:00
return ;
2025-11-05 07:35:17 -05:00
if ( should_fetch_and_process_resource_type ( ) & & is_browsing_context_connected ( ) ) {
2023-01-14 19:05:28 +05:30
// 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.
2025-11-05 07:35:17 -05:00
fetch_and_process_linked_resource ( ) ;
2021-09-27 02:06:37 +02:00
}
2019-10-07 19:06:47 +02:00
}
2020-03-07 10:27:02 +01:00
2025-11-05 07:05:26 -05:00
void HTMLLinkElement : : removed_from ( Node * old_parent , Node & old_root )
2024-06-04 08:04:44 +01:00
{
2025-11-05 07:05:26 -05:00
Base : : removed_from ( old_parent , old_root ) ;
if ( m_loaded_style_sheet ) {
auto & style_sheet_list = [ & old_root ] - > CSS : : StyleSheetList & {
if ( auto * shadow_root = as_if < DOM : : ShadowRoot > ( old_root ) ; shadow_root )
return shadow_root - > style_sheets ( ) ;
return as < DOM : : Document > ( old_root ) . style_sheets ( ) ;
} ( ) ;
style_sheet_list . remove_a_css_style_sheet ( * m_loaded_style_sheet ) ;
m_loaded_style_sheet = nullptr ;
}
2024-06-04 08:04:44 +01:00
}
2024-05-16 06:02:56 +01:00
// https://html.spec.whatwg.org/multipage/semantics.html#dom-link-rellist
2024-11-15 04:01:23 +13:00
GC : : Ref < DOM : : DOMTokenList > HTMLLinkElement : : rel_list ( )
2024-05-16 06:02:56 +01:00
{
// The relList IDL attribute must reflect the rel content attribute.
if ( ! m_rel_list )
m_rel_list = DOM : : DOMTokenList : : create ( * this , HTML : : AttributeNames : : rel ) ;
2024-05-18 05:54:15 +01:00
return * m_rel_list ;
2024-05-16 06:02:56 +01:00
}
2024-11-18 07:00:59 +13:00
// https://html.spec.whatwg.org/multipage/semantics.html#dom-link-sizes
GC : : Ref < DOM : : DOMTokenList > HTMLLinkElement : : sizes ( )
{
// The size IDL attribute must reflect the size content attribute.
if ( ! m_sizes )
m_sizes = DOM : : DOMTokenList : : create ( * this , HTML : : AttributeNames : : sizes ) ;
return * m_sizes ;
}
2025-03-04 14:50:11 +01:00
void HTMLLinkElement : : set_media ( String media )
{
2025-10-31 12:30:47 +00:00
set_attribute_value ( HTML : : AttributeNames : : media , media ) ;
2025-03-04 14:50:11 +01:00
if ( auto sheet = m_loaded_style_sheet )
2025-11-05 07:05:26 -05:00
sheet - > set_media ( move ( media ) ) ;
2025-03-04 14:50:11 +01:00
}
String HTMLLinkElement : : media ( ) const
{
return attribute ( HTML : : AttributeNames : : media ) . value_or ( String { } ) ;
}
2025-03-15 15:39:32 +01:00
// https://drafts.csswg.org/cssom/#dom-linkstyle-sheet
GC : : Ptr < CSS : : CSSStyleSheet > HTMLLinkElement : : sheet ( ) const
{
return m_loaded_style_sheet ;
}
2022-04-03 19:49:38 +02:00
bool HTMLLinkElement : : has_loaded_icon ( ) const
{
2025-11-05 07:35:17 -05:00
return m_relationship & Relationship : : Icon & & m_loaded_icon . has_value ( ) ;
2022-04-03 19:49:38 +02:00
}
2024-11-14 08:14:16 -05:00
void HTMLLinkElement : : attribute_changed ( FlyString const & name , Optional < String > const & old_value , Optional < String > const & value , Optional < FlyString > const & namespace_ )
2020-06-15 20:25:25 +02:00
{
2024-11-14 08:14:16 -05:00
Base : : attribute_changed ( name , old_value , value , namespace_ ) ;
2023-06-06 07:07:27 +02:00
2025-02-21 10:19:43 +00:00
// https://html.spec.whatwg.org/multipage/semantics.html#processing-the-type-attribute:attr-link-type
if ( name = = HTML : : AttributeNames : : type ) {
if ( value . has_value ( ) )
m_mime_type = value - > to_ascii_lowercase ( ) ;
else {
m_mime_type = { } ;
}
return ;
}
2022-04-03 19:39:38 +02:00
// 4.6.7 Link types - https://html.spec.whatwg.org/multipage/links.html#linkTypes
2025-01-20 16:26:59 +11:00
auto old_relationship = m_relationship ;
2020-06-15 20:25:25 +02:00
if ( name = = HTML : : AttributeNames : : rel ) {
m_relationship = 0 ;
2022-04-03 19:39:38 +02:00
// Keywords are always ASCII case-insensitive, and must be compared as such.
2024-10-14 10:51:15 +02:00
auto lowercased_value = value . value_or ( String { } ) . to_ascii_lowercase ( ) ;
2022-04-03 19:39:38 +02:00
// To determine which link types apply to a link, a, area, or form element,
// the element's rel attribute must be split on ASCII whitespace.
// The resulting tokens are the keywords for the link types that apply to that element.
2023-11-19 18:10:36 +13:00
auto parts = lowercased_value . bytes_as_string_view ( ) . split_view_if ( Infra : : is_ascii_whitespace ) ;
2020-06-15 20:25:25 +02:00
for ( auto & part : parts ) {
2021-09-28 00:08:29 +03:30
if ( part = = " stylesheet " sv )
2020-06-15 20:25:25 +02:00
m_relationship | = Relationship : : Stylesheet ;
2021-09-28 00:08:29 +03:30
else if ( part = = " alternate " sv )
2020-06-15 20:25:25 +02:00
m_relationship | = Relationship : : Alternate ;
2021-09-28 00:08:29 +03:30
else if ( part = = " preload " sv )
2021-09-27 02:06:37 +02:00
m_relationship | = Relationship : : Preload ;
2021-09-28 00:08:29 +03:30
else if ( part = = " dns-prefetch " sv )
m_relationship | = Relationship : : DNSPrefetch ;
else if ( part = = " preconnect " sv )
m_relationship | = Relationship : : Preconnect ;
2022-04-03 19:39:38 +02:00
else if ( part = = " icon " sv )
m_relationship | = Relationship : : Icon ;
2020-06-15 20:25:25 +02:00
}
2024-05-16 06:02:56 +01:00
if ( m_rel_list )
m_rel_list - > associated_attribute_changed ( value . value_or ( String { } ) ) ;
2020-06-15 20:25:25 +02:00
}
2022-11-20 01:46:25 +01:00
2024-04-16 21:02:50 +01:00
// https://html.spec.whatwg.org/multipage/semantics.html#the-link-element:explicitly-enabled
// Whenever the disabled attribute is removed, set the link element's explicitly enabled attribute to true.
if ( ! value . has_value ( ) & & name = = HTML : : AttributeNames : : disabled )
m_explicitly_enabled = true ;
2025-11-05 07:35:17 -05:00
if ( ( m_relationship & Relationship : : Stylesheet ) & & m_loaded_style_sheet ) {
if ( name = = HTML : : AttributeNames : : disabled ) {
2024-04-15 21:42:46 +01:00
document_or_shadow_root_style_sheets ( ) . remove_a_css_style_sheet ( * m_loaded_style_sheet ) ;
2025-02-01 15:47:02 +11:00
m_loaded_style_sheet = nullptr ;
2025-11-05 07:35:17 -05:00
} else if ( name = = HTML : : AttributeNames : : media ) {
m_loaded_style_sheet - > set_media ( value . value_or ( String { } ) ) ;
2025-02-01 15:47:02 +11:00
}
2025-11-05 07:35:17 -05:00
}
2023-01-14 19:05:28 +05:30
2025-11-05 07:35:17 -05:00
if ( should_fetch_and_process_resource_type ( ) & & is_browsing_context_connected ( ) ) {
2023-01-14 19:05:28 +05:30
// The appropriate times to fetch and process this type of link are:
2025-11-05 07:35:17 -05:00
// - When the href attribute of the link element of an external resource link that is already browsing-context connected is changed.
auto fetch = name = = AttributeNames : : href ;
if ( ! fetch & & ( m_relationship & ( Relationship : : Preconnect | Relationship : : Stylesheet ) ) ) {
// - When the crossorigin attribute of the link element of an external resource link that is already browsing-context connected is set, changed, or removed.
fetch = name = = AttributeNames : : crossorigin ;
}
if ( ! fetch & & ( m_relationship & Relationship : : Preload ) ) {
fetch =
// - When the as attribute of the link element of an external resource link that is already browsing-context connected is changed.
name = = AttributeNames : : as
// 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 for the request destination, is set, removed, or changed.
// FIXME: - When the media attribute of the link element of an external resource link that is already browsing-context connected, but was previously not obtained due to the media attribute not matching the environment, is changed or removed.
;
}
if ( ! fetch & & ( m_relationship & Relationship : : Stylesheet ) ) {
fetch =
2024-04-26 09:29:27 +02:00
// - When the disabled attribute of the link element of an external resource link that is already browsing-context connected is set, changed, or removed.
2025-11-05 07:35:17 -05:00
name = = AttributeNames : : disabled
2024-04-26 09:29:27 +02:00
// 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.
2024-08-10 18:07:02 -06:00
2025-11-05 07:35:17 -05:00
// AD-HOC: When the link element's type becomes a stylesheet
| | ! ( old_relationship & Relationship : : Stylesheet ) ;
2024-08-10 18:07:02 -06:00
}
2025-11-05 07:35:17 -05:00
if ( fetch )
fetch_and_process_linked_resource ( ) ;
2023-01-14 19:05:28 +05:30
}
2020-06-15 20:25:25 +02:00
}
2025-11-05 07:05:26 -05:00
// https://html.spec.whatwg.org/multipage/semantics.html#contributes-a-script-blocking-style-sheet
bool HTMLLinkElement : : contributes_a_script_blocking_style_sheet ( ) const
{
// An element el in the context of a Document of an HTML parser or XML parser
// contributes a script-blocking style sheet if all of the following are true:
// el was created by that Document's parser.
if ( m_parser_document ! = & document ( ) )
return false ;
// FIXME: el is either a style element or a link element that was an external resource link that contributes to the styling processing model when the el was created by the parser.
// FIXME: el's media attribute's value matches the environment.
// el's style sheet was enabled when the element was created by the parser.
if ( ! m_was_enabled_when_created_by_parser )
return false ;
// FIXME: The last time the event loop reached step 1, el's root was that Document.
// The user agent hasn't given up on loading that particular style sheet yet.
// A user agent may give up on loading a style sheet at any time.
if ( m_fetch_controller & & m_fetch_controller - > state ( ) = = Fetch : : Infrastructure : : FetchController : : State : : Terminated )
return false ;
if ( m_fetch_controller & & m_fetch_controller - > state ( ) = = Fetch : : Infrastructure : : FetchController : : State : : Aborted )
return false ;
return true ;
}
bool HTMLLinkElement : : is_implicitly_potentially_render_blocking ( ) const
{
// A link element of this type is implicitly potentially render-blocking if the element was created by its node document's parser.
return & document ( ) = = m_parser_document ;
}
2023-01-14 19:05:28 +05:30
// https://html.spec.whatwg.org/multipage/semantics.html#create-link-options-from-element
2025-11-05 07:35:17 -05:00
GC : : Ref < HTMLLinkElement : : LinkProcessingOptions > HTMLLinkElement : : create_link_options ( )
2022-04-03 19:49:38 +02:00
{
2023-01-14 19:05:28 +05:30
// 1. Let document be el's node document.
auto & document = this - > document ( ) ;
2021-11-18 19:22:59 +00:00
2023-01-14 19:05:28 +05:30
// 2. Let options be a new link processing options with
2025-11-05 07:35:17 -05:00
auto options = realm ( ) . create < LinkProcessingOptions > (
// crossorigin
// the state of el's crossorigin content attribute
cors_setting_attribute_from_keyword ( get_attribute ( AttributeNames : : crossorigin ) ) ,
// referrer policy
// the state of el's referrerpolicy content attribute
ReferrerPolicy : : from_string ( get_attribute ( AttributeNames : : referrerpolicy ) . value_or ( { } ) ) . value_or ( ReferrerPolicy : : ReferrerPolicy : : EmptyString ) ,
// FIXME: source set
// el's source set
// base URL
// document's document base URL
document . base_url ( ) ,
// origin
// document's origin
document . origin ( ) ,
// environment
// document's relevant settings object
document . relevant_settings_object ( ) ,
// policy container
// document's policy container
document . policy_container ( ) ,
// document
// document
document ,
// cryptographic nonce metadata
// the current value of el's [[CryptographicNonce]] internal slot
m_cryptographic_nonce ,
// fetch priority
// the state of el's fetchpriority content attribute
Fetch : : Infrastructure : : request_priority_from_string ( get_attribute_value ( HTML : : AttributeNames : : fetchpriority ) ) . value_or ( Fetch : : Infrastructure : : Request : : Priority : : Auto ) ) ;
2023-01-14 19:05:28 +05:30
// 3. If el has an href attribute, then set options's href to the value of el's href attribute.
2023-10-01 17:46:26 +13:00
if ( auto maybe_href = get_attribute ( AttributeNames : : href ) ; maybe_href . has_value ( ) )
2025-11-05 07:35:17 -05:00
options - > href = maybe_href . value ( ) ;
2023-01-14 19:05:28 +05:30
// 4. If el has an integrity attribute, then set options's integrity to the value of el's integrity content attribute.
2023-10-01 17:46:26 +13:00
if ( auto maybe_integrity = get_attribute ( AttributeNames : : integrity ) ; maybe_integrity . has_value ( ) )
2025-11-05 07:35:17 -05:00
options - > integrity = maybe_integrity . value ( ) ;
2023-01-14 19:05:28 +05:30
// 5. If el has a type attribute, then set options's type to the value of el's type attribute.
2023-10-01 17:46:26 +13:00
if ( auto maybe_type = get_attribute ( AttributeNames : : type ) ; maybe_type . has_value ( ) )
2025-11-05 07:35:17 -05:00
options - > type = maybe_type . value ( ) ;
2023-01-14 19:05:28 +05:30
// 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 ;
}
// https://html.spec.whatwg.org/multipage/semantics.html#create-a-link-request
2024-11-15 04:01:23 +13:00
GC : : Ptr < Fetch : : Infrastructure : : Request > HTMLLinkElement : : create_link_request ( HTMLLinkElement : : LinkProcessingOptions const & options )
2023-01-14 19:05:28 +05:30
{
// 1. Assert: options's href is not the empty string.
2024-05-30 21:31:14 +01:00
VERIFY ( ! options . href . is_empty ( ) ) ;
2023-01-14 19:05:28 +05:30
2024-05-30 21:31:14 +01:00
// FIXME: 2. If options's destination is null, then return null.
2023-01-14 19:05:28 +05:30
2024-05-30 21:31:14 +01:00
// 3. Let url be the result of encoding-parsing a URL given options's href, relative to options's base URL.
2024-12-06 16:24:08 -05:00
// FIXME: Spec issue: We should be parsing this URL relative to a document or environment settings object.
// https://github.com/whatwg/html/issues/9715
2025-01-21 18:03:37 +13:00
auto url = DOMURL : : parse ( options . href , options . base_url ) ;
2024-05-30 21:31:14 +01:00
// 4. If url is failure, then return null.
2025-01-22 17:35:52 +13:00
if ( ! url . has_value ( ) )
2023-01-14 19:05:28 +05:30
return nullptr ;
2024-05-30 21:31:14 +01:00
// 5. Let request be the result of creating a potential-CORS request given url, options's destination, and options's crossorigin.
2025-01-22 17:35:52 +13:00
auto request = create_potential_CORS_request ( vm ( ) , * url , options . destination , options . crossorigin ) ;
2023-01-14 19:05:28 +05:30
2024-05-30 21:31:14 +01:00
// 6. Set request's policy container to options's policy container.
2024-11-25 14:30:12 +00:00
request - > set_policy_container ( GC : : Ref { * options . policy_container } ) ;
2023-01-14 19:05:28 +05:30
2024-05-30 21:31:14 +01:00
// 7. Set request's integrity metadata to options's integrity.
2023-01-14 19:05:28 +05:30
request - > set_integrity_metadata ( options . integrity ) ;
2024-05-30 21:31:14 +01:00
// 8. Set request's cryptographic nonce metadata to options's cryptographic nonce metadata.
2023-01-14 19:05:28 +05:30
request - > set_cryptographic_nonce_metadata ( options . cryptographic_nonce_metadata ) ;
2024-05-30 21:31:14 +01:00
// 9. Set request's referrer policy to options's referrer policy.
2023-01-14 19:05:28 +05:30
request - > set_referrer_policy ( options . referrer_policy ) ;
2024-05-30 21:31:14 +01:00
// 10. Set request's client to options's environment.
2023-01-14 19:05:28 +05:30
request - > set_client ( options . environment ) ;
2024-05-30 21:31:14 +01:00
// 11. Set request's priority to options's fetch priority.
request - > set_priority ( options . fetch_priority ) ;
// 12. Return request.
2023-01-14 19:05:28 +05:30
return request ;
}
// https://html.spec.whatwg.org/multipage/semantics.html#fetch-and-process-the-linked-resource
void HTMLLinkElement : : fetch_and_process_linked_resource ( )
{
2025-11-05 07:35:17 -05:00
if ( m_relationship & Relationship : : DNSPrefetch )
fetch_and_process_linked_dns_prefetch_resource ( ) ;
else if ( m_relationship & Relationship : : Preconnect )
fetch_and_process_linked_preconnect_resource ( ) ;
else if ( m_relationship & Relationship : : Preload )
fetch_and_process_linked_preload_resource ( ) ;
else
default_fetch_and_process_linked_resource ( ) ;
2023-01-14 19:05:28 +05:30
}
// 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 ) ;
2021-11-18 19:22:59 +00:00
} else {
2023-01-14 19:05:28 +05:30
request - > set_initiator_type ( Fetch : : Infrastructure : : Request : : InitiatorType : : Link ) ;
}
2022-03-20 17:20:59 +01:00
2023-01-14 19:05:28 +05:30
// 7. Fetch request with processResponseConsumeBody set to the following steps given response response and null, failure, or a byte sequence bodyBytes:
2023-05-26 09:51:53 -04:00
Fetch : : Infrastructure : : FetchAlgorithms : : Input fetch_algorithms_input { } ;
2025-11-05 07:35:17 -05:00
fetch_algorithms_input . process_response_consume_body = [ this ] ( auto response , auto body_bytes ) {
2023-05-26 09:51:53 -04:00
// FIXME: If the response is CORS cross-origin, we must use its internal response to query any of its data. See:
// https://github.com/whatwg/html/issues/9355
response = response - > unsafe_response ( ) ;
// 1. Let success be true.
bool success = true ;
2025-11-05 07:17:09 -05:00
ByteBuffer successful_body_bytes ;
2023-05-26 09:51:53 -04:00
// 2. If either of the following conditions are met:
// - bodyBytes is null or failure; or
// - response's status is not an ok status,
2025-11-05 07:17:09 -05:00
// then set success to false.
body_bytes . visit (
[ & ] ( ByteBuffer & body_bytes ) {
if ( Fetch : : Infrastructure : : is_ok_status ( response - > status ( ) ) )
successful_body_bytes = move ( body_bytes ) ;
else
success = false ;
} ,
[ & ] ( auto ) { success = false ; } ) ;
2023-05-26 09:51:53 -04:00
// 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.
2025-11-05 07:17:09 -05:00
process_linked_resource ( success , response , move ( successful_body_bytes ) ) ;
2023-05-26 09:51:53 -04:00
} ;
2024-09-22 13:29:49 +02:00
if ( m_fetch_controller )
m_fetch_controller - > abort ( realm ( ) , { } ) ;
2025-09-30 17:15:55 +01:00
m_fetch_controller = Fetch : : Fetching : : fetch ( realm ( ) , * request , Fetch : : Infrastructure : : FetchAlgorithms : : create ( vm ( ) , move ( fetch_algorithms_input ) ) ) ;
2023-01-14 19:05:28 +05:30
}
2025-11-05 07:35:17 -05:00
// https://html.spec.whatwg.org/multipage/links.html#link-type-dns-prefetch:fetch-and-process-the-linked-resource-2
void HTMLLinkElement : : fetch_and_process_linked_dns_prefetch_resource ( )
{
auto href = get_attribute ( AttributeNames : : href ) ;
if ( ! href . has_value ( ) )
return ;
// 1. Let url be the result of encoding-parsing a URL given el's href attribute's value, relative to el's node document.
auto url = document ( ) . encoding_parse_url ( * href ) ;
// 2. If url is failure, then return.
if ( ! url . has_value ( ) )
return ;
// FIXME: 3. Let partitionKey be the result of determining the network partition key given el's node document's relevant
// settings object.
// 4. The user agent should resolve an origin given partitionKey and url's origin.
// FIXME: This should go through Fetch: https://fetch.spec.whatwg.org/#resolve-an-origin
ResourceLoader : : the ( ) . prefetch_dns ( url . value ( ) ) ;
}
// https://html.spec.whatwg.org/multipage/links.html#link-type-preconnect:fetch-and-process-the-linked-resource-2
void HTMLLinkElement : : fetch_and_process_linked_preconnect_resource ( )
{
// The fetch and process the linked resource steps for this type of linked resource, given a link element el, are to
// create link options from el and to preconnect given the result.
preconnect ( create_link_options ( ) ) ;
}
// https://html.spec.whatwg.org/multipage/links.html#translate-a-preload-destination
static Variant < Empty , Optional < Fetch : : Infrastructure : : Request : : Destination > > translate_a_preload_destination ( Optional < String > const & destination )
{
// 1. If destination is not "fetch", "font", "image", "script", "style", or "track", then return null.
if ( ! destination . has_value ( ) | | ! destination - > is_one_of ( " fetch " sv , " font " sv , " image " sv , " script " sv , " style " sv , " track " sv ) )
return { } ;
// 2. Return the result of translating destination.
return Fetch : : Infrastructure : : translate_potential_destination ( * destination ) ;
}
// https://html.spec.whatwg.org/multipage/links.html#link-type-preload:fetch-and-process-the-linked-resource-2
void HTMLLinkElement : : fetch_and_process_linked_preload_resource ( )
{
// FIXME: 1. Update the source set for el.
// 2. Let options be the result of creating link options from el.
auto options = create_link_options ( ) ;
// 3. Let destination be the result of translating the keyword representing the state of el's as attribute.
auto destination = translate_a_preload_destination ( get_attribute ( HTML : : AttributeNames : : as ) ) ;
// 4. If destination is null, then return.
if ( destination . has < Empty > ( ) )
return ;
// 5. Set options's destination to destination.
options - > destination = destination . get < Optional < Fetch : : Infrastructure : : Request : : Destination > > ( ) ;
// 6. Preload options, with the following steps given a response response:
preload ( options , GC : : Function < void ( Fetch : : Infrastructure : : Response & ) > : : create ( heap ( ) , [ this ] ( Fetch : : Infrastructure : : Response & response ) {
// 1. If response is a network error, fire an event named error at el. Otherwise, fire an event named load at el.
if ( response . is_network_error ( ) )
dispatch_event ( DOM : : Event : : create ( realm ( ) , HTML : : EventNames : : error ) ) ;
else
dispatch_event ( DOM : : Event : : create ( realm ( ) , HTML : : EventNames : : load ) ) ;
} ) ) ;
}
2025-11-05 07:05:26 -05:00
// https://html.spec.whatwg.org/multipage/semantics.html#linked-resource-fetch-setup-steps
bool HTMLLinkElement : : linked_resource_fetch_setup_steps ( Fetch : : Infrastructure : : Request & request )
{
2025-11-05 07:35:17 -05:00
if ( m_relationship & Relationship : : Icon )
return icon_linked_resource_fetch_setup_steps ( request ) ;
2025-11-05 07:05:26 -05:00
if ( m_relationship & Relationship : : Stylesheet )
return stylesheet_linked_resource_fetch_setup_steps ( request ) ;
2025-11-05 07:35:17 -05:00
return true ;
}
// https://html.spec.whatwg.org/multipage/links.html#rel-icon:linked-resource-fetch-setup-steps
bool HTMLLinkElement : : icon_linked_resource_fetch_setup_steps ( Fetch : : Infrastructure : : Request & request )
{
// 1. Set request's destination to "image".
request . set_destination ( Fetch : : Infrastructure : : Request : : Destination : : Image ) ;
2025-11-05 07:05:26 -05:00
2025-11-05 07:35:17 -05:00
// 2. Return true.
2025-11-05 07:05:26 -05:00
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 ;
// 2. If el contributes a script-blocking style sheet, append el to its node document's script-blocking style sheet set.
if ( contributes_a_script_blocking_style_sheet ( ) )
document ( ) . script_blocking_style_sheet_set ( ) . set ( * this ) ;
// 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.
if ( is_potentially_render_blocking ( ) )
block_rendering ( ) ;
m_document_load_event_delayer . emplace ( document ( ) ) ;
// 4. If el is currently render-blocking, then set request's render-blocking to true.
if ( document ( ) . is_render_blocking_element ( * this ) )
request . set_render_blocking ( true ) ;
// FIXME: We currently don't set the destination for stylesheets, so we do it here.
// File a spec issue that the destination for stylesheets is not actually set if the `as` attribute is missing.
request . set_destination ( Fetch : : Infrastructure : : Request : : Destination : : Style ) ;
// 5. Return true.
return true ;
}
2025-11-05 07:35:17 -05:00
// https://html.spec.whatwg.org/multipage/links.html#preconnect
void HTMLLinkElement : : preconnect ( LinkProcessingOptions const & options )
{
// 1. If options's href is an empty string, return.
if ( options . href . is_empty ( ) )
return ;
// 2. Let url be the result of encoding-parsing a URL given options's href, relative to options's base URL.
// FIXME: Spec issue: We should be parsing this URL relative to a document or environment settings object.
// https://github.com/whatwg/html/issues/9715
auto url = DOMURL : : parse ( options . href , options . base_url ) ;
// 3. If url is failure, then return.
if ( ! url . has_value ( ) )
return ;
// 4. If url's scheme is not an HTTP(S) scheme, then return.
if ( ! url - > scheme ( ) . is_one_of ( " http " sv , " https " sv ) )
return ;
// FIXME: 5. Let partitionKey be the result of determining the network partition key given options's environment.
// FIXME: 6. Let useCredentials be true.
// FIXME: 7. If options's crossorigin is Anonymous and options's origin does not have the same origin as url's origin,
// then set useCredentials to false.
// 8. The user agent should obtain a connection given partitionKey, url's origin, and useCredentials.
// FIXME: This should go through Fetch: https://fetch.spec.whatwg.org/#concept-connection-obtain
ResourceLoader : : the ( ) . preconnect ( * url ) ;
}
// https://html.spec.whatwg.org/multipage/links.html#match-preload-type
static bool type_matches_destination ( StringView type , Optional < Fetch : : Infrastructure : : Request : : Destination > destination )
{
using enum Fetch : : Infrastructure : : Request : : Destination ;
// 1. If type is an empty string, then return true.
if ( type . is_empty ( ) )
return true ;
// 2. If destination is "fetch", then return true.
// FIXME: Spec issue: "fetch" will have been turned to an empty string by this point.
if ( ! destination . has_value ( ) )
return true ;
// 3. Let mimeTypeRecord be the result of parsing type.
auto mime_type_record = MimeSniff : : MimeType : : parse ( type ) ;
// 4. If mimeTypeRecord is failure, then return false.
if ( ! mime_type_record . has_value ( ) )
return false ;
// FIXME: 5. If mimeTypeRecord is not supported by the user agent, then return false.
// 6. If any of the following are true:
if (
// destination is "audio" or "video", and mimeTypeRecord is an audio or video MIME type;
( ( destination = = Audio | | destination = = Video ) & & mime_type_record - > is_audio_or_video ( ) )
// destination is a script-like destination and mimeTypeRecord is a JavaScript MIME type;
| | ( Fetch : : Infrastructure : : destination_is_script_like ( * destination ) & & mime_type_record - > is_javascript ( ) )
// destination is "image" and mimeTypeRecord is an image MIME type;
| | ( destination = = Image & & mime_type_record - > is_image ( ) )
// destination is "font" and mimeTypeRecord is a font MIME type;
| | ( destination = = Font & & mime_type_record - > is_font ( ) )
// destination is "json" and mimeTypeRecord is a JSON MIME type;
| | ( destination = = JSON & & mime_type_record - > is_json ( ) )
// destination is "style" and mimeTypeRecord's essence is text/css; or
| | ( destination = = Style & & mime_type_record - > essence ( ) = = " text/css " sv )
// destination is "track" and mimeTypeRecord's essence is text/vtt,
| | ( destination = = Track & & mime_type_record - > essence ( ) = = " text/vtt " sv ) ) {
// then return true.
return true ;
}
// 7. Return false.
return false ;
}
// https://html.spec.whatwg.org/multipage/links.html#preload
void HTMLLinkElement : : preload ( LinkProcessingOptions & options , GC : : Ptr < GC : : Function < void ( Fetch : : Infrastructure : : Response & ) > > process_response )
{
auto & realm = this - > realm ( ) ;
auto & vm = realm . vm ( ) ;
// 1. If options's type doesn't match options's destination, then return.
if ( ! type_matches_destination ( options . type , options . destination ) )
return ;
// FIXME: 2. If options's destination is "image" and options's source set is not null, then set options's href to the
// result of selecting an image source from options's source set.
if ( options . href . is_empty ( ) )
return ;
// 3. Let request be the result of creating a link request given options.
auto request = create_link_request ( options ) ;
// 4. If request is null, then return.
if ( ! request )
return ;
// FIXME: 5. Let unsafeEndTime be 0.
// 6. Let entry be a new preload entry whose integrity metadata is options's integrity.
auto entry = realm . create < PreloadEntry > ( ) ;
entry - > integrity_metadata = options . integrity ;
// 7. Let key be the result of creating a preload key given request.
auto key = PreloadKey : : create ( * request ) ;
// 8. If options's document is null, then set request's initiator type to "early hint".
if ( ! options . document )
request - > set_initiator_type ( Fetch : : Infrastructure : : Request : : InitiatorType : : EarlyHint ) ;
// 9. Let controller be null.
m_fetch_controller = nullptr ;
// 10. Let reportTiming given a Document document be to report timing for controller given document's relevant global object.
auto report_timing = GC : : Function < void ( DOM : : Document const & ) > : : create ( realm . heap ( ) , [ this ] ( DOM : : Document const & document ) {
m_fetch_controller - > report_timing ( relevant_global_object ( document ) ) ;
} ) ;
// 11. Set controller to the result of fetching request, with processResponseConsumeBody set to the following steps
// given a response response and null, failure, or a byte sequence bodyBytes:
Fetch : : Infrastructure : : FetchAlgorithms : : Input fetch_algorithms_input { } ;
fetch_algorithms_input . process_response_consume_body = [ & realm , options = GC : : Ref { options } , process_response , entry , report_timing ] ( GC : : Ref < Fetch : : Infrastructure : : Response > response , Fetch : : Infrastructure : : FetchAlgorithms : : BodyBytes body_bytes ) {
// FIXME: If the response is CORS cross-origin, we must use its internal response to query any of its data. See:
// https://github.com/whatwg/html/issues/9355
response = response - > unsafe_response ( ) ;
// 1. If bodyBytes is a byte sequence, then set response's body to bodyBytes as a body.
if ( auto * byte_sequence = body_bytes . get_pointer < ByteBuffer > ( ) )
response - > set_body ( Fetch : : Infrastructure : : byte_sequence_as_body ( realm , * byte_sequence ) ) ;
// 2. Otherwise, set response to a network error.
else
response = Fetch : : Infrastructure : : Response : : network_error ( realm . vm ( ) , " Expected preload response to contain a body " _string ) ;
// FIXME: 3. Set unsafeEndTime to the unsafe shared current time.
// 4. If options's document is not null, then call reportTiming given options's document.
if ( options - > document )
report_timing - > function ( ) ( * options - > document ) ;
// 5. If entry's on response available is null, then set entry's response to response; otherwise call entry's
// on response available given response.
if ( ! entry - > on_response_available )
entry - > response = response ;
else
entry - > on_response_available - > function ( ) ( response ) ;
// 6. If processResponse is given, then call processResponse with response.
if ( process_response )
process_response - > function ( ) ( response ) ;
} ;
2025-09-30 17:15:55 +01:00
m_fetch_controller = Fetch : : Fetching : : fetch ( realm , * request , Fetch : : Infrastructure : : FetchAlgorithms : : create ( vm , move ( fetch_algorithms_input ) ) ) ;
2025-11-05 07:35:17 -05:00
// 12. Let commit be the following steps given a Document document:
auto commit = GC : : Function < void ( DOM : : Document & ) > : : create ( realm . heap ( ) , [ entry , report_timing ] ( DOM : : Document & document ) {
// 1. If entry's response is not null, then call reportTiming given document.
if ( entry - > response )
report_timing - > function ( ) ( document ) ;
// FIXME: 2. Set document's map of preloaded resources[key] to entry.
} ) ;
// 13. If options's document is null, then set options's on document ready to commit. Otherwise, call commit with
// options's document.
if ( ! options . document )
options . on_document_ready = commit ;
else
commit - > function ( ) ( * options . document ) ;
}
2025-11-05 07:05:26 -05:00
// https://html.spec.whatwg.org/multipage/semantics.html#process-the-linked-resource
2025-11-05 07:17:09 -05:00
void HTMLLinkElement : : process_linked_resource ( bool success , Fetch : : Infrastructure : : Response const & response , ByteBuffer body_bytes )
2025-11-05 07:05:26 -05:00
{
2025-11-05 07:35:17 -05:00
if ( m_relationship & Relationship : : Icon )
process_icon_resource ( success , response , move ( body_bytes ) ) ;
else if ( m_relationship & Relationship : : Stylesheet )
2025-11-05 07:17:09 -05:00
process_stylesheet_resource ( success , response , move ( body_bytes ) ) ;
2025-11-05 07:05:26 -05:00
}
2025-11-05 07:35:17 -05:00
// AD-HOC: The spec is underspecified for fetching and processing rel="icon" See:
// https://github.com/whatwg/html/issues/1769
void HTMLLinkElement : : process_icon_resource ( bool success , Fetch : : Infrastructure : : Response const & response , ByteBuffer body_bytes )
2025-11-05 07:05:26 -05:00
{
2025-11-05 07:35:17 -05:00
if ( ! success )
2025-11-05 07:05:26 -05:00
return ;
2025-11-05 07:35:17 -05:00
m_loaded_icon = { response . url ( ) . value_or ( { } ) , move ( body_bytes ) } ;
2025-11-05 07:05:26 -05:00
document ( ) . check_favicon_after_loading_link_resource ( ) ;
}
2023-01-14 19:05:28 +05:30
// https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:process-the-linked-resource
2025-11-05 07:17:09 -05:00
void HTMLLinkElement : : process_stylesheet_resource ( bool success , Fetch : : Infrastructure : : Response const & response , ByteBuffer body_bytes )
2023-01-14 19:05:28 +05:30
{
2025-11-17 01:26:27 +01:00
if ( ! document ( ) . is_fully_active ( ) )
return ;
2023-01-14 19:05:28 +05:30
// 1. If the resource's Content-Type metadata is not text/css, then set success to false.
2025-02-21 10:19:43 +00:00
auto mime_type_string = m_mime_type ;
2025-09-16 21:01:23 +02:00
Optional < String > mime_type_charset ;
2025-11-26 13:32:39 -05:00
auto extracted_mime_type = Fetch : : Infrastructure : : extract_mime_type ( response . header_list ( ) ) ;
2025-09-16 21:01:23 +02:00
if ( extracted_mime_type . has_value ( ) ) {
if ( ! mime_type_string . has_value ( ) )
2025-02-21 10:19:43 +00:00
mime_type_string = extracted_mime_type - > essence ( ) ;
2025-09-16 21:01:23 +02:00
if ( auto charset = extracted_mime_type - > parameters ( ) . get ( " charset " sv ) ; charset . has_value ( ) )
mime_type_charset = charset . value ( ) ;
2025-02-21 10:19:43 +00:00
}
2025-11-05 07:17:09 -05:00
if ( mime_type_string . has_value ( ) & & mime_type_string ! = " text/css " sv )
2023-01-14 19:05:28 +05:30
success = false ;
// 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 ) {
2024-04-15 21:42:46 +01:00
document_or_shadow_root_style_sheets ( ) . remove_a_css_style_sheet ( * m_loaded_style_sheet ) ;
2023-01-14 19:05:28 +05:30
m_loaded_style_sheet = nullptr ;
2021-11-18 19:22:59 +00:00
}
2023-01-14 19:05:28 +05:30
// 4. If success is true, then:
if ( success ) {
// 1. Create a CSS style sheet with the following properties:
// type
// text/css
// location
2024-08-21 14:49:08 +01:00
// response's URL list[0]
2023-01-14 19:05:28 +05:30
// 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]
2025-09-16 21:01:23 +02:00
Optional < StringView > environment_encoding ;
if ( auto charset = attribute ( HTML : : AttributeNames : : charset ) ; charset . has_value ( ) ) {
if ( auto environment_encoding = TextCodec : : get_standardized_encoding ( charset . release_value ( ) ) ; environment_encoding . has_value ( ) )
environment_encoding = environment_encoding . value ( ) ;
}
if ( ! environment_encoding . has_value ( ) & & document ( ) . encoding ( ) . has_value ( ) )
environment_encoding = document ( ) . encoding ( ) . value ( ) ;
2022-11-20 01:46:25 +01:00
2025-11-05 07:17:09 -05:00
auto maybe_decoded_string = css_decode_bytes ( environment_encoding , mime_type_charset , body_bytes ) ;
2025-09-16 21:01:23 +02:00
if ( maybe_decoded_string . is_error ( ) ) {
dbgln ( " Failed to decode CSS file: {} " , response . url ( ) . value_or ( URL : : URL ( ) ) ) ;
2023-08-13 13:05:26 +02:00
dispatch_event ( * DOM : : Event : : create ( realm ( ) , HTML : : EventNames : : error ) ) ;
2023-01-14 19:05:28 +05:30
} else {
2025-09-16 21:01:23 +02:00
VERIFY ( ! response . url_list ( ) . is_empty ( ) ) ;
m_loaded_style_sheet = document_or_shadow_root_style_sheets ( ) . create_a_css_style_sheet (
maybe_decoded_string . release_value ( ) ,
" text/css " _string ,
this ,
attribute ( HTML : : AttributeNames : : media ) . value_or ( { } ) ,
in_a_document_tree ( ) ? attribute ( HTML : : AttributeNames : : title ) . value_or ( { } ) : String { } ,
( m_relationship & Relationship : : Alternate & & ! m_explicitly_enabled ) ? CSS : : StyleSheetList : : Alternate : : Yes : CSS : : StyleSheetList : : Alternate : : No ,
CSS : : StyleSheetList : : OriginClean : : Yes ,
response . url_list ( ) . first ( ) ,
nullptr ,
nullptr ) ;
// 2. Fire an event named load at el.
dispatch_event ( * DOM : : Event : : create ( realm ( ) , HTML : : EventNames : : load ) ) ;
2022-11-20 01:46:25 +01:00
}
2023-01-14 19:05:28 +05:30
}
// 5. Otherwise, fire an event named error at el.
else {
2023-08-13 13:05:26 +02:00
dispatch_event ( * DOM : : Event : : create ( realm ( ) , HTML : : EventNames : : error ) ) ;
2021-11-18 19:22:59 +00:00
}
2025-04-20 11:31:57 +02:00
// 6. If el contributes a script-blocking style sheet, then:
if ( contributes_a_script_blocking_style_sheet ( ) ) {
// 1. Assert: el's node document's script-blocking style sheet set contains el.
VERIFY ( document ( ) . script_blocking_style_sheet_set ( ) . contains ( * this ) ) ;
// 2. Remove el from its node document's script-blocking style sheet set.
document ( ) . script_blocking_style_sheet_set ( ) . remove ( * this ) ;
}
2023-01-14 19:05:28 +05:30
// 7. Unblock rendering on el.
2025-02-27 15:30:26 +01:00
unblock_rendering ( ) ;
2023-01-14 19:05:28 +05:30
m_document_load_event_delayer . clear ( ) ;
}
2025-08-25 18:15:31 +02:00
static NonnullRefPtr < Core : : Promise < bool > > decode_favicon ( ReadonlyBytes favicon_data , URL : : URL const & favicon_url , GC : : Ref < DOM : : Document > document )
2022-04-03 19:49:38 +02:00
{
2025-10-10 13:26:46 -04:00
auto promise = Core : : Promise < bool > : : construct ( ) ;
2025-08-25 18:15:31 +02:00
if ( favicon_url . basename ( ) . ends_with ( " .svg " sv ) ) {
auto result = SVG : : SVGDecodedImageData : : create ( document - > realm ( ) , document - > page ( ) , favicon_url , favicon_data ) ;
if ( result . is_error ( ) ) {
promise - > reject ( Error : : from_string_view ( " Failed to decode SVG favicon " sv ) ) ;
return promise ;
}
// FIXME: Calculate size based on device pixel ratio
Gfx : : IntSize size { 32 , 32 } ;
auto immutable_bitmap = result . release_value ( ) - > bitmap ( 0 , size ) ;
auto bitmap = immutable_bitmap - > bitmap ( ) ;
if ( ! bitmap ) {
promise - > reject ( Error : : from_string_view ( " Failed to get bitmap from SVG favicon " sv ) ) ;
return promise ;
}
auto navigable = document - > navigable ( ) ;
if ( navigable & & navigable - > is_traversable ( ) )
navigable - > traversable_navigable ( ) - > page ( ) . client ( ) . page_did_change_favicon ( * bitmap ) ;
promise - > resolve ( true ) ;
return promise ;
}
2025-10-10 13:26:46 -04:00
auto on_failed_decode = [ favicon_url , promise ] ( [[maybe_unused]] Error & error ) {
2024-04-19 15:55:54 -06:00
dbgln_if ( IMAGE_DECODER_DEBUG , " Failed to decode favicon {}: {} " , favicon_url , error ) ;
2025-10-10 13:26:46 -04:00
promise - > reject ( move ( error ) ) ;
2024-04-19 15:55:54 -06:00
} ;
2025-10-10 13:26:46 -04:00
auto on_successful_decode = [ document = GC : : Root ( document ) , promise ] ( Web : : Platform : : DecodedImage & decoded_image ) - > ErrorOr < void > {
2024-04-19 15:55:54 -06:00
auto favicon_bitmap = decoded_image . frames [ 0 ] . bitmap ;
dbgln_if ( IMAGE_DECODER_DEBUG , " Decoded favicon, {} " , favicon_bitmap - > size ( ) ) ;
2022-04-03 19:49:38 +02:00
2024-08-21 14:52:51 +02:00
auto navigable = document - > navigable ( ) ;
2024-04-19 15:55:54 -06:00
if ( navigable & & navigable - > is_traversable ( ) )
navigable - > traversable_navigable ( ) - > page ( ) . client ( ) . page_did_change_favicon ( * favicon_bitmap ) ;
2025-10-10 13:26:46 -04:00
promise - > resolve ( true ) ;
2024-04-19 15:55:54 -06:00
return { } ;
} ;
2022-04-03 19:49:38 +02:00
2025-10-10 13:26:46 -04:00
( void ) Platform : : ImageCodecPlugin : : the ( ) . decode_image ( favicon_data , move ( on_successful_decode ) , move ( on_failed_decode ) ) ;
return promise ;
2023-10-30 10:47:34 -04:00
}
2022-04-03 19:49:38 +02:00
2023-10-30 10:47:34 -04:00
bool HTMLLinkElement : : load_favicon_and_use_if_window_is_active ( )
{
if ( ! has_loaded_icon ( ) )
return false ;
2024-04-19 15:55:54 -06:00
// FIXME: Refactor the caller(s) to handle the async nature of image loading
2025-11-05 07:35:17 -05:00
auto promise = decode_favicon ( m_loaded_icon - > icon , m_loaded_icon - > url , document ( ) ) ;
2024-04-19 15:55:54 -06:00
auto result = promise - > await ( ) ;
return ! result . is_error ( ) ;
2023-10-30 10:47:34 -04:00
}
// https://html.spec.whatwg.org/multipage/links.html#rel-icon:the-link-element-3
2025-09-30 17:15:55 +01:00
void HTMLLinkElement : : load_fallback_favicon_if_needed ( GC : : Ref < DOM : : Document > document )
2023-10-30 10:47:34 -04:00
{
auto & realm = document - > realm ( ) ;
auto & vm = realm . vm ( ) ;
// In the absence of a link with the icon keyword, for Document objects whose URL's scheme is an HTTP(S) scheme,
// user agents may instead run these steps in parallel:
if ( document - > has_active_favicon ( ) )
2025-09-30 17:15:55 +01:00
return ;
2023-10-30 10:47:34 -04:00
if ( ! document - > url ( ) . scheme ( ) . is_one_of ( " http " sv , " https " sv ) )
2025-09-30 17:15:55 +01:00
return ;
2023-10-30 10:47:34 -04:00
// 1. Let request be a new request whose URL is the URL record obtained by resolving the URL "/favicon.ico" against
// the Document object's URL, client is the Document object's relevant settings object, destination is "image",
// synchronous flag is set, credentials mode is "include", and whose use-URL-credentials flag is set.
// NOTE: Fetch requests no longer have a synchronous flag, see https://github.com/whatwg/fetch/pull/1165
auto request = Fetch : : Infrastructure : : Request : : create ( vm ) ;
2025-06-24 15:35:11 +01:00
request - > set_url ( * document - > encoding_parse_url ( " /favicon.ico " sv ) ) ;
2023-10-30 10:47:34 -04:00
request - > set_client ( & document - > relevant_settings_object ( ) ) ;
request - > set_destination ( Fetch : : Infrastructure : : Request : : Destination : : Image ) ;
request - > set_credentials_mode ( Fetch : : Infrastructure : : Request : : CredentialsMode : : Include ) ;
request - > set_use_url_credentials ( true ) ;
// 2. Let response be the result of fetching request.
Fetch : : Infrastructure : : FetchAlgorithms : : Input fetch_algorithms_input { } ;
2024-11-15 04:01:23 +13:00
fetch_algorithms_input . process_response = [ document , request ] ( GC : : Ref < Fetch : : Infrastructure : : Response > response ) {
2023-10-30 10:47:34 -04:00
auto & realm = document - > realm ( ) ;
2024-11-15 04:01:23 +13:00
auto global = GC : : Ref { realm . global_object ( ) } ;
2023-10-30 10:47:34 -04:00
2024-11-15 04:01:23 +13:00
auto process_body = GC : : create_function ( realm . heap ( ) , [ document , request ] ( ByteBuffer body ) {
2024-08-21 14:52:51 +02:00
( void ) decode_favicon ( body , request - > url ( ) , document ) ;
2024-04-23 10:25:20 +02:00
} ) ;
2024-11-15 04:01:23 +13:00
auto process_body_error = GC : : create_function ( realm . heap ( ) , [ ] ( JS : : Value ) {
2024-04-23 10:25:20 +02:00
} ) ;
2023-10-30 10:47:34 -04:00
2024-07-06 14:42:57 -05:00
// Check for failed favicon response
if ( ! Fetch : : Infrastructure : : is_ok_status ( response - > status ( ) ) | | ! response - > body ( ) ) {
return ;
}
2023-10-30 10:47:34 -04:00
// 3. Use response's unsafe response as an icon as if it had been declared using the icon keyword.
2024-01-05 22:38:29 +01:00
if ( auto body = response - > unsafe_response ( ) - > body ( ) )
2024-04-26 14:57:40 -04:00
body - > fully_read ( realm , process_body , process_body_error , global ) ;
2023-10-30 10:47:34 -04:00
} ;
2025-09-30 17:15:55 +01:00
Fetch : : Fetching : : fetch ( realm , request , Fetch : : Infrastructure : : FetchAlgorithms : : create ( vm , move ( fetch_algorithms_input ) ) ) ;
2022-04-03 19:49:38 +02:00
}
2025-11-05 07:35:17 -05:00
bool HTMLLinkElement : : should_fetch_and_process_resource_type ( ) const
{
// https://html.spec.whatwg.org/multipage/links.html#link-type-dns-prefetch:fetch-and-process-the-linked-resource
// https://html.spec.whatwg.org/multipage/links.html#link-type-preconnect:fetch-and-process-the-linked-resource
// https://html.spec.whatwg.org/multipage/links.html#link-type-preload:fetch-and-process-the-linked-resource
// https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:fetch-and-process-the-linked-resource
if ( m_relationship & ( Relationship : : DNSPrefetch | Relationship : : Preconnect | Relationship : : Preload | Relationship : : Stylesheet ) )
return true ;
// AD-HOC: The spec is underspecified for fetching and processing rel="icon". See:
// https://github.com/whatwg/html/issues/1769
return m_relationship & Relationship : : Icon ;
}
HTMLLinkElement : : LinkProcessingOptions : : LinkProcessingOptions (
CORSSettingAttribute crossorigin ,
ReferrerPolicy : : ReferrerPolicy referrer_policy ,
URL : : URL base_url ,
URL : : Origin origin ,
GC : : Ref < HTML : : EnvironmentSettingsObject > environment ,
GC : : Ref < HTML : : PolicyContainer > policy_container ,
GC : : Ptr < Web : : DOM : : Document > document ,
String cryptographic_nonce_metadata ,
Fetch : : Infrastructure : : Request : : Priority fetch_priority )
: cryptographic_nonce_metadata ( move ( cryptographic_nonce_metadata ) )
, crossorigin ( crossorigin )
, referrer_policy ( referrer_policy )
, base_url ( move ( base_url ) )
, origin ( move ( origin ) )
, environment ( environment )
, policy_container ( policy_container )
, document ( document )
, fetch_priority ( fetch_priority )
{
}
void HTMLLinkElement : : LinkProcessingOptions : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( environment ) ;
visitor . visit ( policy_container ) ;
visitor . visit ( document ) ;
visitor . visit ( on_document_ready ) ;
}
// https://html.spec.whatwg.org/multipage/links.html#create-a-preload-key
HTMLLinkElement : : PreloadKey HTMLLinkElement : : PreloadKey : : create ( Fetch : : Infrastructure : : Request const & request )
{
// To create a preload key for a request request, return a new preload key whose URL is request's URL, destination
// is request's destination, mode is request's mode, and credentials mode is request's credentials mode.
return PreloadKey {
. url = request . url ( ) ,
. destination = request . destination ( ) ,
. mode = request . mode ( ) ,
. credentials_mode = request . credentials_mode ( ) ,
} ;
}
void HTMLLinkElement : : PreloadEntry : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( response ) ;
visitor . visit ( on_response_available ) ;
}
GC_DEFINE_ALLOCATOR ( HTMLLinkElement : : LinkProcessingOptions ) ;
GC_DEFINE_ALLOCATOR ( HTMLLinkElement : : PreloadEntry ) ;
2020-03-07 10:27:02 +01:00
}