Coverage Report

Created: 2025-11-16 07:46

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