summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp
blob: 58a17ab15f173dc76e909b4f8e56baae0dc3add2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
/*
 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
 * Copyright (c) 2021, the SerenityOS developers.
 * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
 * Copyright (c) 2023, Srikavin Ramkumar <me@srikavin.me>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/ByteBuffer.h>
#include <AK/Debug.h>
#include <AK/URL.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/Fetch/Fetching/Fetching.h>
#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/HTML/HTMLLinkElement.h>
#include <LibWeb/HTML/PotentialCORSRequest.h>
#include <LibWeb/Infra/CharacterTypes.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Platform/ImageCodecPlugin.h>

namespace Web::HTML {

HTMLLinkElement::HTMLLinkElement(DOM::Document& document, DOM::QualifiedName qualified_name)
    : HTMLElement(document, move(qualified_name))
{
}

HTMLLinkElement::~HTMLLinkElement() = default;

JS::ThrowCompletionOr<void> HTMLLinkElement::initialize(JS::Realm& realm)
{
    MUST_OR_THROW_OOM(Base::initialize(realm));
    set_prototype(&Bindings::ensure_web_prototype<Bindings::HTMLLinkElementPrototype>(realm, "HTMLLinkElement"));

    return {};
}

void HTMLLinkElement::inserted()
{
    HTMLElement::inserted();

    // FIXME: Handle alternate stylesheets properly
    if (m_relationship & Relationship::Stylesheet && !(m_relationship & Relationship::Alternate)) {
        // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:fetch-and-process-the-linked-resource
        // The appropriate times to fetch and process this type of link are:
        //  - When the external resource link is created on a link element that is already browsing-context connected.
        //  - When the external resource link's link element becomes browsing-context connected.
        fetch_and_process_linked_resource();
    }

    // FIXME: Follow spec for fetching and processing these attributes as well
    if (m_relationship & Relationship::Preload) {
        // FIXME: Respect the "as" attribute.
        LoadRequest request;
        request.set_url(document().parse_url(attribute(HTML::AttributeNames::href)));
        m_preload_resource = ResourceLoader::the().load_resource(Resource::Type::Generic, request);
    } else if (m_relationship & Relationship::DNSPrefetch) {
        ResourceLoader::the().prefetch_dns(document().parse_url(attribute(HTML::AttributeNames::href)));
    } else if (m_relationship & Relationship::Preconnect) {
        ResourceLoader::the().preconnect(document().parse_url(attribute(HTML::AttributeNames::href)));
    } else if (m_relationship & Relationship::Icon) {
        auto favicon_url = document().parse_url(href());
        auto favicon_request = LoadRequest::create_for_url_on_page(favicon_url, document().page());
        set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, favicon_request));
    }
}

bool HTMLLinkElement::has_loaded_icon() const
{
    return m_relationship & Relationship::Icon && resource() && resource()->is_loaded() && resource()->has_encoded_data();
}

void HTMLLinkElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value)
{
    // 4.6.7 Link types - https://html.spec.whatwg.org/multipage/links.html#linkTypes
    if (name == HTML::AttributeNames::rel) {
        m_relationship = 0;
        // Keywords are always ASCII case-insensitive, and must be compared as such.
        auto lowercased_value = value.to_lowercase();
        // 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.
        auto parts = lowercased_value.split_view(Infra::is_ascii_whitespace);
        for (auto& part : parts) {
            if (part == "stylesheet"sv)
                m_relationship |= Relationship::Stylesheet;
            else if (part == "alternate"sv)
                m_relationship |= Relationship::Alternate;
            else if (part == "preload"sv)
                m_relationship |= Relationship::Preload;
            else if (part == "dns-prefetch"sv)
                m_relationship |= Relationship::DNSPrefetch;
            else if (part == "preconnect"sv)
                m_relationship |= Relationship::Preconnect;
            else if (part == "icon"sv)
                m_relationship |= Relationship::Icon;
        }
    }

    if (m_relationship & Relationship::Stylesheet) {
        if (name == HTML::AttributeNames::disabled && m_loaded_style_sheet)
            document().style_sheets().remove_sheet(*m_loaded_style_sheet);

        // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:fetch-and-process-the-linked-resource
        // The appropriate times to fetch and process this type of link are:
        if (
            // - When the href attribute of the link element of an external resource link that is already browsing-context connected is changed.
            name == AttributeNames::href ||
            // - When the disabled attribute of the link element of an external resource link that is already browsing-context connected is set, changed, or removed.
            name == AttributeNames::disabled ||
            // - When the crossorigin attribute of the link element of an external resource link that is already browsing-context connected is set, changed, or removed.
            name == AttributeNames::crossorigin
            // FIXME: - When the type attribute of the link element of an external resource link that is already browsing-context connected is set or changed to a value that does not or no longer matches the Content-Type metadata of the previous obtained external resource, if any.
            // FIXME: - When the type attribute of the link element of an external resource link that is already browsing-context connected, but was previously not obtained due to the type attribute specifying an unsupported type, is removed or changed.
        ) {
            fetch_and_process_linked_resource();
        }
    }
}

void HTMLLinkElement::resource_did_fail()
{
    dbgln_if(CSS_LOADER_DEBUG, "HTMLLinkElement: Resource did fail. URL: {}", resource()->url());
}

void HTMLLinkElement::resource_did_load()
{
    VERIFY(resource());
    VERIFY(m_relationship & (Relationship::Icon));
    if (m_relationship & Relationship::Icon) {
        resource_did_load_favicon();
        m_document_load_event_delayer.clear();
    }
}

void HTMLLinkElement::did_remove_attribute(DeprecatedFlyString const& attr)
{
    if (m_relationship & Relationship::Stylesheet) {
        // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:fetch-and-process-the-linked-resource
        // The appropriate times to fetch and process this type of link are:
        if (
            // - When the href attribute of the link element of an external resource link that is already browsing-context connected is changed.
            attr == AttributeNames::href ||
            // - When the disabled attribute of the link element of an external resource link that is already browsing-context connected is set, changed, or removed.
            attr == AttributeNames::disabled ||
            // - When the crossorigin attribute of the link element of an external resource link that is already browsing-context connected is set, changed, or removed.
            attr == AttributeNames::crossorigin
            // FIXME: - When the type attribute of the link element of an external resource link that is already browsing-context connected, but was previously not obtained due to the type attribute specifying an unsupported type, is removed or changed.
        ) {
            fetch_and_process_linked_resource();
        }
    }
}

// https://html.spec.whatwg.org/multipage/semantics.html#create-link-options-from-element
HTMLLinkElement::LinkProcessingOptions HTMLLinkElement::create_link_options()
{
    // 1. Let document be el's node document.
    auto& document = this->document();

    // 2. Let options be a new link processing options with
    LinkProcessingOptions options;
    // FIXME: destination                      the result of translating the state of el's as attribute
    // crossorigin                      the state of el's crossorigin content attribute
    options.crossorigin = cors_setting_attribute_from_keyword(
        has_attribute(AttributeNames::crossorigin) ? String::from_deprecated_string(get_attribute(AttributeNames::crossorigin)).release_value_but_fixme_should_propagate_errors()
                                                   : Optional<String> {});
    // FIXME: referrer policy                  the state of el's referrerpolicy content attribute
    // FIXME: source set                       el's source set
    // base URL                         document's URL
    options.base_url = document.url();
    // origin                           document's origin
    options.origin = document.origin();
    // environment                      document's relevant settings object
    options.environment = &document.relevant_settings_object();
    // policy container                 document's policy container
    options.policy_container = document.policy_container();
    // document                         document
    options.document = &document;
    // FIXME: cryptographic nonce metadata     The current value of el's [[CryptographicNonce]] internal slot

    // 3. If el has an href attribute, then set options's href to the value of el's href attribute.
    if (has_attribute(AttributeNames::href))
        options.href = String::from_deprecated_string(get_attribute(AttributeNames::href)).release_value_but_fixme_should_propagate_errors();

    // 4. If el has an integrity attribute, then set options's integrity to the value of el's integrity content attribute.
    if (has_attribute(AttributeNames::integrity))
        options.integrity = String::from_deprecated_string(get_attribute(AttributeNames::integrity)).release_value_but_fixme_should_propagate_errors();

    // 5. If el has a type attribute, then set options's type to the value of el's type attribute.
    if (has_attribute(AttributeNames::type))
        options.type = String::from_deprecated_string(get_attribute(AttributeNames::type)).release_value_but_fixme_should_propagate_errors();

    // FIXME: 6. Assert: options's href is not the empty string, or options's source set is not null.
    //           A link element with neither an href or an imagesrcset does not represent a link.

    // 7. Return options.
    return options;
}

// https://html.spec.whatwg.org/multipage/semantics.html#create-a-link-request
JS::GCPtr<Fetch::Infrastructure::Request> HTMLLinkElement::create_link_request(HTMLLinkElement::LinkProcessingOptions const& options)
{
    // 1. Assert: options's href is not the empty string.

    // FIXME: 2. If options's destination is not a destination, then return null.

    // 3. Parse a URL given options's href, relative to options's base URL. If that fails, then return null. Otherwise, let url be the resulting URL record.
    auto url = options.base_url.complete_url(options.href);
    if (!url.is_valid())
        return nullptr;

    // 4. Let request be the result of creating a potential-CORS request given url, options's destination, and options's crossorigin.
    auto request = create_potential_CORS_request(vm(), url, options.destination, options.crossorigin);

    // 5. Set request's policy container to options's policy container.
    request->set_policy_container(options.policy_container);

    // 6. Set request's integrity metadata to options's integrity.
    request->set_integrity_metadata(options.integrity);

    // 7. Set request's cryptographic nonce metadata to options's cryptographic nonce metadata.
    request->set_cryptographic_nonce_metadata(options.cryptographic_nonce_metadata);

    // 8. Set request's referrer policy to options's referrer policy.
    request->set_referrer_policy(options.referrer_policy);

    // 9. Set request's client to options's environment.
    request->set_client(options.environment);

    // 10. Return request.
    return request;
}

// https://html.spec.whatwg.org/multipage/semantics.html#fetch-and-process-the-linked-resource
void HTMLLinkElement::fetch_and_process_linked_resource()
{
    default_fetch_and_process_linked_resource();
}

// https://html.spec.whatwg.org/multipage/semantics.html#default-fetch-and-process-the-linked-resource
void HTMLLinkElement::default_fetch_and_process_linked_resource()
{
    // https://html.spec.whatwg.org/multipage/semantics.html#the-link-element:attr-link-href-4
    // If both the href and imagesrcset attributes are absent, then the element does not define a link.
    // FIXME: Support imagesrcset attribute
    if (!has_attribute(AttributeNames::href) || href().is_empty())
        return;

    // 1. Let options be the result of creating link options from el.
    auto options = create_link_options();

    // 2. Let request be the result of creating a link request given options.
    auto request = create_link_request(options);

    // 3. If request is null, then return.
    if (request == nullptr) {
        return;
    }

    // FIXME: 4. Set request's synchronous flag.

    // 5. Run the linked resource fetch setup steps, given el and request. If the result is false, then return.
    if (!linked_resource_fetch_setup_steps(*request))
        return;

    // 6. Set request's initiator type to "css" if el's rel attribute contains the keyword stylesheet; "link" otherwise.
    if (m_relationship & Relationship::Stylesheet) {
        request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::CSS);
    } else {
        request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Link);
    }

    // 7. Fetch request with processResponseConsumeBody set to the following steps given response response and null, failure, or a byte sequence bodyBytes:
    Fetch::Fetching::fetch(
        realm(), *request,
        Fetch::Infrastructure::FetchAlgorithms::create(vm(),
            { .process_request_body_chunk_length = {},
                .process_request_end_of_body = {},
                .process_early_hints_response = {},
                .process_response = {},
                .process_response_end_of_body = {},
                .process_response_consume_body = [this, hr = options](auto response, auto body_bytes) {
                    // 1. Let success be true.
                    bool success = true;

                    // 2. If either of the following conditions are met:
                    // - bodyBytes is null or failure; or
                    // - response's status is not an ok status,
                    // then set success to false.
                    // NOTE: content-specific errors, e.g., CSS parse errors or PNG decoding errors, do not affect success.
                    if (body_bytes.template has<Empty>()) {
                        // CORS cross-origin responses in the No CORS request mode provide an opaque filtered response, which is the original response
                        // with certain attributes removed/changed.

                        // The relevant effect it has is setting the body to `null`, which means `body_bytes` has `Empty` here. This effectively
                        // disables cross-origin linked resources (e.g. stylesheets).

                        // However, the web actually depends on this, especially for stylesheets retrieved from a cross-origin CDN. For example,
                        // Shopify websites request stylesheets from `cdn.shopify.com` and Substack websites request stylesheets from `substackcdn.com`.

                        // This makes this a specification bug, as this code was written from it.

                        // The workaround is to read the actual body from the unfiltered response and then call `process_linked_resource` from there.

                        // This _should_ be safe to do, as linked resource fetches do not include credentials (i.e. cookies and the Authorization
                        // header), so it cannot provide personalized responses.

                        // FIXME: Replace this workaround with a proper fix that has landed in the specification.
                        //        See: https://github.com/whatwg/html/issues/9066
                        if (is<Fetch::Infrastructure::OpaqueFilteredResponse>(response.ptr())) {
                            auto unsafe_response = static_cast<Fetch::Infrastructure::OpaqueFilteredResponse const&>(*response).internal_response();
                            if (unsafe_response->body().has_value()) {
                                // NOTE: `this` and `unsafe_response` are protected by `fully_read` using JS::SafeFunction.
                                auto process_body = [this, unsafe_response](ByteBuffer bytes) {
                                    process_linked_resource(true, unsafe_response, bytes);
                                };

                                // NOTE: `this` and `unsafe_response` are protected by `fully_read` using JS::SafeFunction.
                                auto process_body_error = [this, unsafe_response](auto&) {
                                    process_linked_resource(false, unsafe_response, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag {});
                                };

                                unsafe_response->body()->fully_read(realm(), move(process_body), move(process_body_error), JS::NonnullGCPtr { realm().global_object() }).release_value_but_fixme_should_propagate_errors();
                                return;
                            } else {
                                success = false;
                            }
                        } else {
                            success = false;
                        }
                    } else if (body_bytes.template has<Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag>() || !Fetch::Infrastructure::is_ok_status(response->status())) {
                        success = false;
                    }
                    // FIXME: 3. Otherwise, wait for the link resource's critical subresources to finish loading.

                    // 4. Process the linked resource given el, success, response, and bodyBytes.
                    process_linked_resource(success, response, body_bytes);
                } }))
        .release_value_but_fixme_should_propagate_errors();
}

// https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:process-the-linked-resource
void HTMLLinkElement::process_stylesheet_resource(bool success, Fetch::Infrastructure::Response const& response, Variant<Empty, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag, ByteBuffer> body_bytes)
{
    // 1. If the resource's Content-Type metadata is not text/css, then set success to false.
    auto extracted_mime_type = response.header_list()->extract_mime_type().release_value_but_fixme_should_propagate_errors();
    if (!extracted_mime_type.has_value() || extracted_mime_type->essence() != "text/css") {
        success = false;
    }

    // FIXME: 2. If el no longer creates an external resource link that contributes to the styling processing model,
    //           or if, since the resource in question was fetched, it has become appropriate to fetch it again, then return.

    // 3. If el has an associated CSS style sheet, remove the CSS style sheet.
    if (m_loaded_style_sheet) {
        document().style_sheets().remove_sheet(*m_loaded_style_sheet);
        m_loaded_style_sheet = nullptr;
    }

    // 4. If success is true, then:
    if (success) {
        // 1. Create a CSS style sheet with the following properties:
        //        type
        //            text/css
        //        location
        //            The resulting URL string determined during the fetch and process the linked resource algorithm.
        //        owner node
        //            element
        //        media
        //            The media attribute of element.
        //        title
        //            The title attribute of element, if element is in a document tree, or the empty string otherwise.
        //        alternate flag
        //            Set if the link is an alternative style sheet and element's explicitly enabled is false; unset otherwise.
        //        origin-clean flag
        //            Set if the resource is CORS-same-origin; unset otherwise.
        //        parent CSS style sheet
        //        owner CSS rule
        //            null
        //        disabled flag
        //            Left at its default value.
        //        CSS rules
        //          Left uninitialized.
        //
        // The CSS environment encoding is the result of running the following steps: [CSSSYNTAX]
        //     1. If the element has a charset attribute, get an encoding from that attribute's value. If that succeeds, return the resulting encoding. [ENCODING]
        //     2. Otherwise, return the document's character encoding. [DOM]
        m_loaded_style_sheet = parse_css_stylesheet(CSS::Parser::ParsingContext(document(), *response.url()), body_bytes.template get<ByteBuffer>());

        if (m_loaded_style_sheet) {
            m_loaded_style_sheet->set_owner_node(this);
            document().style_sheets().add_sheet(*m_loaded_style_sheet);
        } else {
            dbgln_if(CSS_LOADER_DEBUG, "HTMLLinkElement: Failed to parse stylesheet: {}", resource()->url());
        }

        // 2. Fire an event named load at el.
        dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors());
    }
    // 5. Otherwise, fire an event named error at el.
    else {
        dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::error).release_value_but_fixme_should_propagate_errors());
    }

    // FIXME: 6. If el contributes a script-blocking style sheet, then:
    //     FIXME: 1. Assert: el's node document's script-blocking style sheet counter is greater than 0.
    //     FIXME: 2. Decrement el's node document's script-blocking style sheet counter by 1.

    // 7. Unblock rendering on el.
    m_document_load_event_delayer.clear();
}

// https://html.spec.whatwg.org/multipage/semantics.html#process-the-linked-resource
void HTMLLinkElement::process_linked_resource(bool success, Fetch::Infrastructure::Response const& response, Variant<Empty, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag, ByteBuffer> body_bytes)
{
    if (m_relationship & Relationship::Stylesheet)
        process_stylesheet_resource(success, response, body_bytes);
}

// https://html.spec.whatwg.org/multipage/semantics.html#linked-resource-fetch-setup-steps
bool HTMLLinkElement::linked_resource_fetch_setup_steps(Fetch::Infrastructure::Request& request)
{
    if (m_relationship & Relationship::Stylesheet)
        return stylesheet_linked_resource_fetch_setup_steps(request);

    return true;
}

// https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:linked-resource-fetch-setup-steps
bool HTMLLinkElement::stylesheet_linked_resource_fetch_setup_steps(Fetch::Infrastructure::Request& request)
{
    // 1. If el's disabled attribute is set, then return false.
    if (has_attribute(AttributeNames::disabled))
        return false;
    // FIXME: 2. If el contributes a script-blocking style sheet, increment el's node document's script-blocking style sheet counter by 1.

    // 3. If el's media attribute's value matches the environment and el is potentially render-blocking, then block rendering on el.
    // FIXME: Check media attribute value.
    m_document_load_event_delayer.emplace(document());

    // 4. If el is currently render-blocking, then set request's render-blocking to true.
    // FIXME: Check if el is currently render-blocking.
    request.set_render_blocking(true);

    // 5. Return true.
    return true;
}

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();
}

bool HTMLLinkElement::load_favicon_and_use_if_window_is_active()
{
    if (!has_loaded_icon())
        return false;

    RefPtr<Gfx::Bitmap> favicon_bitmap;
    auto decoded_image = Platform::ImageCodecPlugin::the().decode_image(resource()->encoded_data());
    if (!decoded_image.has_value() || decoded_image->frames.is_empty()) {
        dbgln("Could not decode favicon {}", resource()->url());
        return false;
    }

    favicon_bitmap = decoded_image->frames[0].bitmap;
    dbgln_if(IMAGE_DECODER_DEBUG, "Decoded favicon, {}", favicon_bitmap->size());

    auto* page = document().page();
    if (!page)
        return favicon_bitmap;

    if (document().browsing_context() == &page->top_level_browsing_context())
        if (favicon_bitmap) {
            page->client().page_did_change_favicon(*favicon_bitmap);
            return true;
        }

    return false;
}

void HTMLLinkElement::visit_edges(Cell::Visitor& visitor)
{
    Base::visit_edges(visitor);
    visitor.visit(m_loaded_style_sheet);
}

}