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-08-25 18:15:31 +02:00
# 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>
2025-01-15 15:06:39 -07:00
# include <LibWeb/Bindings/PrincipalHostDefined.h>
2021-07-30 19:31:46 +01:00
# include <LibWeb/CSS/Parser/Parser.h>
2025-03-04 14:50:11 +01:00
# include <LibWeb/CSS/StyleComputer.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>
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>
2023-11-19 18:10:36 +13:00
# include <LibWeb/Infra/Strings.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-01-23 17:37:18 +01:00
void HTMLLinkElement : : removed_from ( Node * old_parent , Node & old_root )
2024-04-21 19:46:37 +02:00
{
2025-01-23 17:37:18 +01:00
Base : : removed_from ( old_parent , old_root ) ;
2024-04-21 19:46:37 +02:00
if ( m_loaded_style_sheet ) {
2025-04-02 16:53:26 +01:00
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 ) ;
2024-04-21 19:46:37 +02:00
m_loaded_style_sheet = nullptr ;
}
}
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
2024-04-24 17:57:18 +00:00
if ( ! document ( ) . browsing_context ( ) ) {
return ;
}
2024-04-16 21:02:50 +01:00
if ( m_relationship & Relationship : : Stylesheet ) {
2023-01-14 19:05:28 +05:30
// 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.
2025-03-03 13:48:37 +01:00
if ( is_browsing_context_connected ( ) )
fetch_and_process_linked_resource ( ) ;
2019-10-07 19:06:47 +02:00
}
2021-09-27 02:06:37 +02:00
2023-01-14 19:05:28 +05:30
// FIXME: Follow spec for fetching and processing these attributes as well
2021-09-27 02:06:37 +02:00
if ( m_relationship & Relationship : : Preload ) {
2025-01-23 19:40:57 +13:00
if ( auto maybe_href = document ( ) . encoding_parse_url ( get_attribute_value ( HTML : : AttributeNames : : href ) ) ; maybe_href . has_value ( ) ) {
// FIXME: Respect the "as" attribute.
LoadRequest request ;
request . set_url ( maybe_href . value ( ) ) ;
2025-01-15 15:06:39 -07:00
request . set_page ( Bindings : : principal_host_defined_page ( HTML : : principal_realm ( realm ( ) ) ) ) ;
2025-01-23 19:40:57 +13:00
set_resource ( ResourceLoader : : the ( ) . load_resource ( Resource : : Type : : Generic , request ) ) ;
}
2021-09-28 00:08:29 +03:30
} else if ( m_relationship & Relationship : : DNSPrefetch ) {
2025-01-23 19:40:57 +13:00
if ( auto dns_prefetch_url = document ( ) . encoding_parse_url ( get_attribute_value ( HTML : : AttributeNames : : href ) ) ; dns_prefetch_url . has_value ( ) ) {
ResourceLoader : : the ( ) . prefetch_dns ( dns_prefetch_url . value ( ) ) ;
}
2021-09-28 00:08:29 +03:30
} else if ( m_relationship & Relationship : : Preconnect ) {
2025-01-23 19:40:57 +13:00
if ( auto maybe_href = document ( ) . encoding_parse_url ( get_attribute_value ( HTML : : AttributeNames : : href ) ) ; maybe_href . has_value ( ) ) {
ResourceLoader : : the ( ) . preconnect ( maybe_href . value ( ) ) ;
}
2022-04-03 19:49:38 +02:00
} else if ( m_relationship & Relationship : : Icon ) {
2025-01-23 19:40:57 +13:00
if ( auto favicon_url = document ( ) . encoding_parse_url ( href ( ) ) ; favicon_url . has_value ( ) ) {
auto favicon_request = LoadRequest : : create_for_url_on_page ( favicon_url . value ( ) , & document ( ) . page ( ) ) ;
set_resource ( ResourceLoader : : the ( ) . load_resource ( Resource : : Type : : Generic , favicon_request ) ) ;
}
2021-09-27 02:06:37 +02:00
}
2019-10-07 19:06:47 +02:00
}
2020-03-07 10:27:02 +01:00
2024-06-04 08:04:44 +01:00
WebIDL : : ExceptionOr < void > HTMLLinkElement : : set_as ( String const & value )
{
return set_attribute ( HTML : : AttributeNames : : as , move ( value ) ) ;
}
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 )
{
( void ) set_attribute ( HTML : : AttributeNames : : media , media ) ;
if ( auto sheet = m_loaded_style_sheet )
sheet - > set_media ( media ) ;
}
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
{
return m_relationship & Relationship : : Icon & & resource ( ) & & resource ( ) - > is_loaded ( ) & & resource ( ) - > has_encoded_data ( ) ;
}
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 ;
if ( m_relationship & Relationship : : Stylesheet ) {
2025-02-01 15:47:02 +11:00
if ( name = = HTML : : AttributeNames : : disabled & & 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 ) ;
2025-02-01 15:47:02 +11:00
m_loaded_style_sheet = nullptr ;
}
2023-01-14 19:05:28 +05:30
// 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 (
2024-04-26 09:29:27 +02:00
is_browsing_context_connected ( )
& & (
2025-01-20 16:26:59 +11:00
// AD-HOC: When the link element's type becomes a stylesheet
! ( old_relationship & Relationship : : Stylesheet ) | |
2024-04-26 09:29:27 +02:00
// - 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.
) ) {
2023-01-14 19:05:28 +05:30
fetch_and_process_linked_resource ( ) ;
}
2024-08-10 18:07:02 -06:00
if ( name = = HTML : : AttributeNames : : media & & m_loaded_style_sheet ) {
m_loaded_style_sheet - > set_media ( value . value_or ( String { } ) ) ;
}
2023-01-14 19:05:28 +05:30
}
2020-06-15 20:25:25 +02:00
}
2021-11-18 19:22:59 +00:00
void HTMLLinkElement : : resource_did_fail ( )
{
dbgln_if ( CSS_LOADER_DEBUG , " HTMLLinkElement: Resource did fail. URL: {} " , resource ( ) - > url ( ) ) ;
2023-06-06 07:03:30 +02:00
if ( m_relationship & Relationship : : Preload ) {
2023-08-13 13:05:26 +02:00
dispatch_event ( * DOM : : Event : : create ( realm ( ) , HTML : : EventNames : : error ) ) ;
2023-06-06 07:03:30 +02:00
}
2021-11-18 19:22:59 +00:00
}
void HTMLLinkElement : : resource_did_load ( )
{
VERIFY ( resource ( ) ) ;
2023-01-14 19:05:28 +05:30
if ( m_relationship & Relationship : : Icon ) {
2022-04-03 19:49:38 +02:00
resource_did_load_favicon ( ) ;
2023-01-14 19:05:28 +05:30
m_document_load_event_delayer . clear ( ) ;
}
2023-06-06 07:03:30 +02:00
if ( m_relationship & Relationship : : Preload ) {
2023-08-13 13:05:26 +02:00
dispatch_event ( * DOM : : Event : : create ( realm ( ) , HTML : : EventNames : : load ) ) ;
2023-06-06 07:03:30 +02:00
}
2022-04-03 19:49:38 +02:00
}
2023-01-14 19:05:28 +05:30
// https://html.spec.whatwg.org/multipage/semantics.html#create-link-options-from-element
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-06-15 19:08:58 +12:00
LinkProcessingOptions options {
// FIXME: destination the result of translating the state of el's as attribute
2025-08-05 19:01:27 +01:00
// cryptographic nonce metadata the current value of el's [[CryptographicNonce]] internal slot
. cryptographic_nonce_metadata = m_cryptographic_nonce ,
2025-06-15 19:08:58 +12:00
// crossorigin the state of el's crossorigin content attribute
. crossorigin = cors_setting_attribute_from_keyword ( get_attribute ( AttributeNames : : crossorigin ) ) ,
// referrer policy the state of el's referrerpolicy content attribute
. referrer_policy = ReferrerPolicy : : from_string ( get_attribute ( AttributeNames : : referrerpolicy ) . value_or ( " " _string ) ) . value_or ( ReferrerPolicy : : ReferrerPolicy : : EmptyString ) ,
// FIXME: source set el's source set
// base URL document's document base URL
. base_url = document . base_url ( ) ,
// origin document's origin
. origin = document . origin ( ) ,
// environment document's relevant settings object
. environment = & document . relevant_settings_object ( ) ,
// policy container document's policy container
. policy_container = document . policy_container ( ) ,
// document document
. document = & document ,
// fetch priority the state of el's fetchpriority content attribute
. fetch_priority = 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 ( ) )
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 ( ) )
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 ( ) )
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 ( )
{
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 ) ;
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 { } ;
fetch_algorithms_input . process_response_consume_body = [ this , hr = options ] ( auto response , auto 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. 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,
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 ) ;
} ;
2024-09-22 13:29:49 +02:00
if ( m_fetch_controller )
m_fetch_controller - > abort ( realm ( ) , { } ) ;
m_fetch_controller = MUST ( Fetch : : Fetching : : fetch ( realm ( ) , * request , Fetch : : Infrastructure : : FetchAlgorithms : : create ( vm ( ) , move ( fetch_algorithms_input ) ) ) ) ;
2023-01-14 19:05:28 +05:30
}
// 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.
2025-02-21 10:19:43 +00:00
auto mime_type_string = m_mime_type ;
if ( ! mime_type_string . has_value ( ) ) {
auto extracted_mime_type = response . header_list ( ) - > extract_mime_type ( ) ;
if ( extracted_mime_type . has_value ( ) )
mime_type_string = extracted_mime_type - > essence ( ) ;
}
2025-03-01 20:28:17 +00: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]
2022-11-20 01:46:25 +01:00
2023-10-10 15:00:58 +03:30
Optional < String > encoding ;
if ( auto charset = attribute ( HTML : : AttributeNames : : charset ) ; charset . has_value ( ) )
encoding = charset . release_value ( ) ;
2023-07-04 09:50:47 +02:00
2023-10-10 15:00:58 +03:30
if ( ! encoding . has_value ( ) )
encoding = document ( ) . encoding_or_default ( ) ;
auto decoder = TextCodec : : decoder_for ( * encoding ) ;
2023-07-04 09:50:47 +02:00
if ( ! decoder . has_value ( ) ) {
// 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.
dbgln ( " FIXME: Style sheet encoding '{}' is not supported yet " , encoding ) ;
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 {
2023-07-04 09:50:47 +02:00
auto const & encoded_string = body_bytes . get < ByteBuffer > ( ) ;
auto maybe_decoded_string = TextCodec : : convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark ( * decoder , encoded_string ) ;
if ( maybe_decoded_string . is_error ( ) ) {
2024-03-18 16:22:27 +13:00
dbgln ( " Style sheet {} claimed to be '{}' but decoding failed " , response . url ( ) . value_or ( URL : : URL ( ) ) , encoding ) ;
2023-08-13 13:05:26 +02:00
dispatch_event ( * DOM : : Event : : create ( realm ( ) , HTML : : EventNames : : error ) ) ;
2023-07-04 09:50:47 +02:00
} else {
2025-04-14 17:44:17 +01:00
VERIFY ( ! response . url_list ( ) . is_empty ( ) ) ;
2025-04-14 13:53:11 +01:00
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 ) ;
2023-07-04 09:50:47 +02:00
// 2. Fire an event named load at el.
2023-08-13 13:05:26 +02:00
dispatch_event ( * DOM : : Event : : create ( realm ( ) , HTML : : EventNames : : load ) ) ;
2023-07-04 09:50:47 +02:00
}
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 ( ) ;
}
// 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 ;
2025-04-20 11:31:57 +02:00
// 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 ) ;
2023-01-14 19:05:28 +05:30
// 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.
2025-02-27 15:30:26 +01:00
if ( is_potentially_render_blocking ( ) )
block_rendering ( ) ;
2023-01-14 19:05:28 +05:30
m_document_load_event_delayer . emplace ( document ( ) ) ;
// 4. If el is currently render-blocking, then set request's render-blocking to true.
2025-02-27 15:30:26 +01:00
if ( document ( ) . is_render_blocking_element ( * this ) )
request . set_render_blocking ( true ) ;
2023-01-14 19:05:28 +05:30
2024-11-28 17:21:50 +00:00
// 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 ) ;
2023-01-14 19:05:28 +05:30
// 5. Return true.
return true ;
2021-11-18 19:22:59 +00:00
}
2025-02-27 15:30:26 +01:00
void HTMLLinkElement : : set_parser_document ( Badge < HTMLParser > , GC : : Ref < DOM : : Document > document )
{
m_parser_document = document - > make_weak_ptr < DOM : : Document > ( ) ;
}
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 ;
}
2022-04-03 19:49:38 +02:00
void HTMLLinkElement : : resource_did_load_favicon ( )
{
VERIFY ( m_relationship & ( Relationship : : Icon ) ) ;
if ( ! resource ( ) - > has_encoded_data ( ) ) {
dbgln_if ( SPAM_DEBUG , " Favicon downloaded, no encoded data " ) ;
return ;
}
dbgln_if ( SPAM_DEBUG , " Favicon downloaded, {} bytes from {} " , resource ( ) - > encoded_data ( ) . size ( ) , resource ( ) - > url ( ) ) ;
document ( ) . check_favicon_after_loading_link_resource ( ) ;
}
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-04-19 16:59:36 +12:00
auto promise = decode_favicon ( resource ( ) - > encoded_data ( ) , * resource ( ) - > 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
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < 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 ( ) )
return { } ;
if ( ! document - > url ( ) . scheme ( ) . is_one_of ( " http " sv , " https " sv ) )
return { } ;
// 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
} ;
TRY ( Fetch : : Fetching : : fetch ( realm , request , Fetch : : Infrastructure : : FetchAlgorithms : : create ( vm , move ( fetch_algorithms_input ) ) ) ) ;
return { } ;
2022-04-03 19:49:38 +02:00
}
2022-11-20 01:46:25 +01:00
void HTMLLinkElement : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2024-09-22 13:29:49 +02:00
visitor . visit ( m_fetch_controller ) ;
2022-11-20 01:46:25 +01:00
visitor . visit ( m_loaded_style_sheet ) ;
2024-05-17 14:06:27 -07:00
visitor . visit ( m_rel_list ) ;
2024-11-18 07:00:59 +13:00
visitor . visit ( m_sizes ) ;
2022-11-20 01:46:25 +01:00
}
2025-04-20 11:31:57 +02: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.
2025-04-24 12:36:54 +02:00
// el's style sheet was enabled when the element was created by the parser.
if ( ! m_was_enabled_when_created_by_parser )
2025-04-20 11:31:57 +02:00
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 ;
}
2020-03-07 10:27:02 +01:00
}