diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index c5804f59e3..f84792455a 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -1560,4 +1561,48 @@ void Document::invalidate_stacking_context_tree() const_cast(paint_box)->invalidate_stacking_context(); } +void Document::check_favicon_after_loading_link_resource() +{ + // https://html.spec.whatwg.org/multipage/links.html#rel-icon + // NOTE: firefox also load favicons outside the head tag, which is against spec (see table 4.6.7) + auto head_element = head(); + auto favicon_link_elements = HTMLCollection::create(*head_element, [](Element const& element) { + if (!is(element)) + return false; + + return static_cast(element).has_loaded_icon(); + }); + + if (favicon_link_elements->length() == 0) { + dbgln_if(SPAM_DEBUG, "No favicon found to be used"); + return; + } + + // 4.6.7.8 Link type "icon" + // + // If there are multiple equally appropriate icons, user agents must use the last one declared + // in tree order at the time that the user agent collected the list of icons. + // + // If multiple icons are provided, the user agent must select the most appropriate icon + // according to the type, media, and sizes attributes. + // + // FIXME: There is no selective behavior yet for favicons. + for (auto i = favicon_link_elements->length(); i-- > 0;) { + auto favicon_element = favicon_link_elements->item(i); + + if (favicon_element == m_active_element) + return; + + // If the user agent tries to use an icon but that icon is determined, upon closer examination, + // to in fact be inappropriate (...), then the user agent must try the next-most-appropriate icon + // as determined by the attributes. + if (static_cast(favicon_element)->load_favicon_and_use_if_window_is_active()) { + m_active_favicon = favicon_element; + return; + } + } + + dbgln_if(SPAM_DEBUG, "No favicon found to be used"); +} + } diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index 4412efd5e9..d64ca68435 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -339,6 +339,8 @@ public: bool in_removed_last_ref() const { return m_in_removed_last_ref; } + void check_favicon_after_loading_link_resource(); + private: explicit Document(const AK::URL&); @@ -374,6 +376,7 @@ private: RefPtr m_style_sheets; RefPtr m_hovered_node; RefPtr m_inspected_node; + RefPtr m_active_favicon; WeakPtr m_browsing_context; AK::URL m_url; diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp index 722e2b2508..f6b601f9c3 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp @@ -12,7 +12,9 @@ #include #include #include +#include #include +#include namespace Web::HTML { @@ -50,9 +52,18 @@ void HTMLLinkElement::inserted() 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(FlyString const& name, String const& value) { // 4.6.7 Link types - https://html.spec.whatwg.org/multipage/links.html#linkTypes @@ -91,7 +102,17 @@ void HTMLLinkElement::resource_did_fail() void HTMLLinkElement::resource_did_load() { VERIFY(resource()); + VERIFY(m_relationship & (Relationship::Stylesheet | Relationship::Icon)); + if (m_relationship & Relationship::Stylesheet) + resource_did_load_stylesheet(); + if (m_relationship & Relationship::Icon) + resource_did_load_favicon(); +} + +void HTMLLinkElement::resource_did_load_stylesheet() +{ + VERIFY(m_relationship & Relationship::Stylesheet); m_document_load_event_delayer.clear(); if (!resource()->has_encoded_data()) { @@ -115,4 +136,45 @@ void HTMLLinkElement::resource_did_load() document().style_sheets().add_sheet(sheet.release_nonnull()); } +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 favicon_bitmap; + auto decoded_image = Web::image_decoder_client().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; +} + } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h index eab494ad09..844bba4149 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.h @@ -28,6 +28,9 @@ public: String type() const { return attribute(HTML::AttributeNames::type); } String href() const { return attribute(HTML::AttributeNames::href); } + bool has_loaded_icon() const; + bool load_favicon_and_use_if_window_is_active(); + private: void parse_attribute(FlyString const&, String const&) override; @@ -35,6 +38,9 @@ private: virtual void resource_did_fail() override; virtual void resource_did_load() override; + void resource_did_load_stylesheet(); + void resource_did_load_favicon(); + struct Relationship { enum { Alternate = 1 << 0,