diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 565dfa631c..268c6de6fa 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -3310,6 +3310,8 @@ void Document::run_the_update_intersection_observations_steps(HighResolutionTime // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#start-intersection-observing-a-lazy-loading-element void Document::start_intersection_observing_a_lazy_loading_element(Element& element) { + VERIFY(element.is_lazy_loading()); + auto& realm = this->realm(); // 1. Let doc be element's node document. @@ -3333,7 +3335,8 @@ void Document::start_intersection_observing_a_lazy_loading_element(Element& elem // 2. If entry.isIntersecting is true, then set resumptionSteps to entry.target's lazy load resumption steps. if (entry.is_intersecting()) { // 5. Set entry.target's lazy load resumption steps to null. - resumption_steps = verify_cast(*entry.target()).take_lazy_load_resumption_steps({}); + VERIFY(entry.target()->is_lazy_loading()); + resumption_steps = entry.target()->take_lazy_load_resumption_steps({}); } // 3. If resumptionSteps is null, then return. diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index 35ada02356..4fc4c68530 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -519,7 +520,7 @@ public: void run_the_update_intersection_observations_steps(HighResolutionTime::DOMHighResTimeStamp time); - void start_intersection_observing_a_lazy_loading_element(Element& element); + void start_intersection_observing_a_lazy_loading_element(Element&); void shared_declarative_refresh_steps(StringView input, JS::GCPtr meta_element = nullptr); diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h index 9951087af5..c3c52f6e34 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.h +++ b/Userland/Libraries/LibWeb/DOM/Element.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -372,6 +373,11 @@ public: Optional const& id() const { return m_id; } + virtual JS::GCPtr> take_lazy_load_resumption_steps(Badge) + { + return nullptr; + } + protected: Element(Document&, DOM::QualifiedName); virtual void initialize(JS::Realm&) override; diff --git a/Userland/Libraries/LibWeb/DOM/Node.h b/Userland/Libraries/LibWeb/DOM/Node.h index 2ab966c224..8710426dbe 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.h +++ b/Userland/Libraries/LibWeb/DOM/Node.h @@ -105,6 +105,7 @@ public: virtual bool is_html_button_element() const { return false; } virtual bool is_html_slot_element() const { return false; } virtual bool is_navigable_container() const { return false; } + virtual bool is_lazy_loading() const { return false; } WebIDL::ExceptionOr> pre_insert(JS::NonnullGCPtr, JS::GCPtr); WebIDL::ExceptionOr> pre_remove(JS::NonnullGCPtr); diff --git a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp index d069cf8044..afaebd0d77 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp @@ -71,7 +71,7 @@ void HTMLImageElement::visit_edges(Cell::Visitor& visitor) Base::visit_edges(visitor); visitor.visit(m_current_request); visitor.visit(m_pending_request); - visitor.visit(m_lazy_load_resumption_steps); + visit_lazy_loading_element(visitor); } void HTMLImageElement::apply_presentational_hints(CSS::StyleProperties& style) const @@ -165,11 +165,6 @@ void HTMLImageElement::set_visible_in_viewport(bool) // FIXME: Loosen grip on image data when it's not visible, e.g via volatile memory. } -void HTMLImageElement::set_lazy_load_resumption_steps(Function steps) -{ - m_lazy_load_resumption_steps = JS::create_heap_function(vm().heap(), move(steps)); -} - // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-width unsigned HTMLImageElement::width() const { @@ -528,7 +523,7 @@ after_step_7: m_pending_request = image_request; // 23. Let delay load event be true if the img's lazy loading attribute is in the Eager state, or if scripting is disabled for the img, and false otherwise. - auto delay_load_event = lazy_loading() == LazyLoading::Eager; + auto delay_load_event = lazy_loading_attribute() == LazyLoading::Eager; // When delay load event is true, fetching the image must delay the load event of the element's node document // until the task that is queued by the networking task source once the resource has been fetched (defined below) has been run. @@ -558,7 +553,7 @@ after_step_7: // FIXME: 22. Set request's priority to the current state of the element's fetchpriority attribute. // 24. If the will lazy load element steps given the img return true, then: - if (will_lazy_load()) { + if (will_lazy_load_element()) { // 1. Set the img's lazy load resumption steps to the rest of this algorithm starting with the step labeled fetch the image. set_lazy_load_resumption_steps([this, request, image_request]() { image_request->fetch_image(realm(), request); @@ -1008,36 +1003,4 @@ void HTMLImageElement::animate() layout_node()->set_needs_display(); } -// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes -HTMLImageElement::LazyLoading HTMLImageElement::lazy_loading() const -{ - auto value = deprecated_attribute(HTML::AttributeNames::loading); - if (value.equals_ignoring_ascii_case("lazy"sv)) - return LazyLoading::Lazy; - return LazyLoading::Eager; -} - -// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#will-lazy-load-element-steps -bool HTMLImageElement::will_lazy_load() const -{ - // 1. If scripting is disabled for element, then return false. - // Spec Note: This is an anti-tracking measure, because if a user agent supported lazy loading when scripting is - // disabled, it would still be possible for a site to track a user's approximate scroll position throughout - // a session, by strategically placing images in a page's markup such that a server can track how many - // images are requested and when. - if (is_scripting_disabled()) - return false; - - // 2. If element's lazy loading attribute is in the Lazy state, then return true. - // 3. Return false. - return lazy_loading() == LazyLoading::Lazy; -} - -JS::GCPtr> HTMLImageElement::take_lazy_load_resumption_steps(Badge) -{ - auto lazy_load_resumption_steps = m_lazy_load_resumption_steps; - m_lazy_load_resumption_steps = nullptr; - return lazy_load_resumption_steps; -} - } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.h b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.h index dea9147172..9bd5e573a2 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -25,11 +26,13 @@ namespace Web::HTML { class HTMLImageElement final : public HTMLElement , public FormAssociatedElement + , public LazyLoadingElement , public Layout::ImageProvider , public DOM::Document::ViewportClient { WEB_PLATFORM_OBJECT(HTMLImageElement, HTMLElement); JS_DECLARE_ALLOCATOR(HTMLImageElement); - FORM_ASSOCIATED_ELEMENT(HTMLElement, HTMLImageElement) + FORM_ASSOCIATED_ELEMENT(HTMLElement, HTMLImageElement); + LAZY_LOADING_ELEMENT(HTMLImageElement); public: virtual ~HTMLImageElement() override; @@ -77,12 +80,6 @@ public: ImageRequest const& current_request() const { return *m_current_request; } size_t current_frame_index() const { return m_current_frame_index; } - enum class LazyLoading { - Lazy, - Eager, - }; - [[nodiscard]] LazyLoading lazy_loading() const; - [[nodiscard]] bool will_lazy_load() const; // https://html.spec.whatwg.org/multipage/images.html#upgrade-the-pending-request-to-the-current-request void upgrade_pending_request_to_current_request(); @@ -94,9 +91,6 @@ public: virtual RefPtr current_image_bitmap(Gfx::IntSize = {}) const override; virtual void set_visible_in_viewport(bool) override; - void set_lazy_load_resumption_steps(Function); - JS::GCPtr> take_lazy_load_resumption_steps(Badge); - virtual void visit_edges(Cell::Visitor&) override; private: @@ -139,10 +133,6 @@ private: SourceSet m_source_set; - // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-load-resumption-steps - // Each img and iframe element has associated lazy load resumption steps, initially null. - JS::GCPtr> m_lazy_load_resumption_steps; - CSSPixelSize m_last_seen_viewport_size; }; diff --git a/Userland/Libraries/LibWeb/HTML/LazyLoadingElement.h b/Userland/Libraries/LibWeb/HTML/LazyLoadingElement.h new file mode 100644 index 0000000000..4636486c0c --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/LazyLoadingElement.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018-2023, Andreas Kling + * Copyright (c) 2023, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::HTML { + +// Lazy-loaded elements should invoke this macro to inject overridden LazyLoadingElement methods. +#define LAZY_LOADING_ELEMENT(ElementClass) \ +private: \ + virtual JS::GCPtr> take_lazy_load_resumption_steps(Badge) override \ + { \ + return take_lazy_load_resumption_steps_internal(); \ + } \ + \ + virtual bool is_lazy_loading() const override { return true; } + +enum class LazyLoading { + Lazy, + Eager, +}; + +template +class LazyLoadingElement { +public: + // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes + [[nodiscard]] LazyLoading lazy_loading_attribute() const + { + auto& element = static_cast(*this); + + auto value = element.attribute(HTML::AttributeNames::loading); + if (value.has_value() && value->equals_ignoring_ascii_case("lazy"sv)) + return LazyLoading::Lazy; + return LazyLoading::Eager; + } + + // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#will-lazy-load-element-steps + [[nodiscard]] bool will_lazy_load_element() const + { + auto& element = static_cast(*this); + + // 1. If scripting is disabled for element, then return false. + // Spec Note: This is an anti-tracking measure, because if a user agent supported lazy loading when scripting is + // disabled, it would still be possible for a site to track a user's approximate scroll position throughout + // a session, by strategically placing images in a page's markup such that a server can track how many + // images are requested and when. + if (element.is_scripting_disabled()) + return false; + + // 2. If element's lazy loading attribute is in the Lazy state, then return true. + // 3. Return false. + return lazy_loading_attribute() == LazyLoading::Lazy; + } + + void set_lazy_load_resumption_steps(Function steps) + { + auto& element = static_cast(*this); + + m_lazy_load_resumption_steps = JS::create_heap_function(element.vm().heap(), move(steps)); + } + + void visit_lazy_loading_element(JS::Cell::Visitor& visitor) + { + visitor.visit(m_lazy_load_resumption_steps); + } + +protected: + LazyLoadingElement() = default; + virtual ~LazyLoadingElement() = default; + + JS::GCPtr> take_lazy_load_resumption_steps_internal() + { + auto lazy_load_resumption_steps = m_lazy_load_resumption_steps; + m_lazy_load_resumption_steps = nullptr; + return lazy_load_resumption_steps; + } + +private: + // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-load-resumption-steps + // Each img and iframe element has associated lazy load resumption steps, initially null. + JS::GCPtr> m_lazy_load_resumption_steps; +}; + +}