mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 11:38:11 +00:00
LibWeb: Implement loading=lazy
for <img>
elements
This commit is contained in:
parent
165abafb80
commit
71f6e5c942
4 changed files with 113 additions and 4 deletions
|
@ -14,6 +14,7 @@
|
||||||
#include <LibJS/Interpreter.h>
|
#include <LibJS/Interpreter.h>
|
||||||
#include <LibJS/Runtime/Array.h>
|
#include <LibJS/Runtime/Array.h>
|
||||||
#include <LibJS/Runtime/FunctionObject.h>
|
#include <LibJS/Runtime/FunctionObject.h>
|
||||||
|
#include <LibJS/Runtime/NativeFunction.h>
|
||||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||||
#include <LibWeb/CSS/MediaQueryList.h>
|
#include <LibWeb/CSS/MediaQueryList.h>
|
||||||
#include <LibWeb/CSS/MediaQueryListEvent.h>
|
#include <LibWeb/CSS/MediaQueryListEvent.h>
|
||||||
|
@ -375,6 +376,7 @@ void Document::visit_edges(Cell::Visitor& visitor)
|
||||||
visitor.visit(m_selection);
|
visitor.visit(m_selection);
|
||||||
visitor.visit(m_first_base_element_with_href_in_tree_order);
|
visitor.visit(m_first_base_element_with_href_in_tree_order);
|
||||||
visitor.visit(m_parser);
|
visitor.visit(m_parser);
|
||||||
|
visitor.visit(m_lazy_load_intersection_observer);
|
||||||
|
|
||||||
for (auto& script : m_scripts_to_execute_when_parsing_has_finished)
|
for (auto& script : m_scripts_to_execute_when_parsing_has_finished)
|
||||||
visitor.visit(script);
|
visitor.visit(script);
|
||||||
|
@ -2983,4 +2985,68 @@ 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)
|
||||||
|
{
|
||||||
|
auto& realm = this->realm();
|
||||||
|
|
||||||
|
// 1. Let doc be element's node document.
|
||||||
|
VERIFY(&element.document() == this);
|
||||||
|
|
||||||
|
// 2. If doc's lazy load intersection observer is null, set it to a new IntersectionObserver instance, initialized as follows:
|
||||||
|
if (!m_lazy_load_intersection_observer) {
|
||||||
|
// - The callback is these steps, with arguments entries and observer:
|
||||||
|
auto callback = JS::NativeFunction::create(realm, "", [this](JS::VM& vm) -> JS::ThrowCompletionOr<JS::Value> {
|
||||||
|
// For each entry in entries using a method of iteration which does not trigger developer-modifiable array accessors or iteration hooks:
|
||||||
|
auto& entries = verify_cast<JS::Array>(vm.argument(0).as_object());
|
||||||
|
auto entries_length = MUST(MUST(entries.get(vm.names.length)).to_length(vm));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < entries_length; ++i) {
|
||||||
|
auto property_key = JS::PropertyKey { i };
|
||||||
|
auto& entry = verify_cast<IntersectionObserver::IntersectionObserverEntry>(entries.get_without_side_effects(property_key).as_object());
|
||||||
|
|
||||||
|
// 1. Let resumptionSteps be null.
|
||||||
|
JS::SafeFunction<void()> resumption_steps;
|
||||||
|
|
||||||
|
// 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<HTML::HTMLImageElement>(*entry.target()).take_lazy_load_resumption_steps({});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. If resumptionSteps is null, then return.
|
||||||
|
if (!resumption_steps)
|
||||||
|
return JS::js_undefined();
|
||||||
|
|
||||||
|
// 4. Stop intersection-observing a lazy loading element for entry.target.
|
||||||
|
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#stop-intersection-observing-a-lazy-loading-element
|
||||||
|
// 1. Let doc be element's node document.
|
||||||
|
// NOTE: It's `this`.
|
||||||
|
|
||||||
|
// 2. Assert: doc's lazy load intersection observer is not null.
|
||||||
|
VERIFY(m_lazy_load_intersection_observer);
|
||||||
|
|
||||||
|
// 3. Call doc's lazy load intersection observer unobserve method with element as the argument.
|
||||||
|
m_lazy_load_intersection_observer->unobserve(entry.target());
|
||||||
|
|
||||||
|
// 6. Invoke resumptionSteps.
|
||||||
|
resumption_steps();
|
||||||
|
}
|
||||||
|
|
||||||
|
return JS::js_undefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
// FIXME: The options is an IntersectionObserverInit dictionary with the following dictionary members: «[ "rootMargin" → lazy load root margin ]»
|
||||||
|
// Spec Note: This allows for fetching the image during scrolling, when it does not yet — but is about to — intersect the viewport.
|
||||||
|
auto options = IntersectionObserver::IntersectionObserverInit {};
|
||||||
|
|
||||||
|
auto wrapped_callback = realm.heap().allocate_without_realm<WebIDL::CallbackType>(callback, Bindings::host_defined_environment_settings_object(realm));
|
||||||
|
m_lazy_load_intersection_observer = IntersectionObserver::IntersectionObserver::construct_impl(realm, wrapped_callback, options).release_value_but_fixme_should_propagate_errors();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Call doc's lazy load intersection observer's observe method with element as the argument.
|
||||||
|
VERIFY(m_lazy_load_intersection_observer);
|
||||||
|
m_lazy_load_intersection_observer->observe(element);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -493,6 +493,8 @@ public:
|
||||||
|
|
||||||
void run_the_update_intersection_observations_steps(HighResolutionTime::DOMHighResTimeStamp time);
|
void run_the_update_intersection_observations_steps(HighResolutionTime::DOMHighResTimeStamp time);
|
||||||
|
|
||||||
|
void start_intersection_observing_a_lazy_loading_element(Element& element);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
|
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
|
||||||
virtual void visit_edges(Cell::Visitor&) override;
|
virtual void visit_edges(Cell::Visitor&) override;
|
||||||
|
@ -670,6 +672,10 @@ private:
|
||||||
// https://www.w3.org/TR/intersection-observer/#document-intersectionobservertaskqueued
|
// https://www.w3.org/TR/intersection-observer/#document-intersectionobservertaskqueued
|
||||||
// Each document has an IntersectionObserverTaskQueued flag which is initialized to false.
|
// Each document has an IntersectionObserverTaskQueued flag which is initialized to false.
|
||||||
bool m_intersection_observer_task_queued { false };
|
bool m_intersection_observer_task_queued { false };
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-load-intersection-observer
|
||||||
|
// Each Document has a lazy load intersection observer, initially set to null but can be set to an IntersectionObserver instance.
|
||||||
|
JS::GCPtr<IntersectionObserver::IntersectionObserver> m_lazy_load_intersection_observer;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
|
|
@ -553,10 +553,19 @@ after_step_7:
|
||||||
|
|
||||||
// FIXME: 22. Set request's priority to the current state of the element's fetchpriority attribute.
|
// FIXME: 22. Set request's priority to the current state of the element's fetchpriority attribute.
|
||||||
|
|
||||||
// FIXME: 24. If the will lazy load element steps given the img return true, then:
|
// 24. If the will lazy load element steps given the img return true, then:
|
||||||
// FIXME: 1. Set the img's lazy load resumption steps to the rest of this algorithm starting with the step labeled fetch the image.
|
if (will_lazy_load()) {
|
||||||
// FIXME: 2. Start intersection-observing a lazy loading element for the img element.
|
// 1. Set the img's lazy load resumption steps to the rest of this algorithm starting with the step labeled fetch the image.
|
||||||
// FIXME: 3. Return.
|
m_lazy_load_resumption_steps = [this, request, image_request]() {
|
||||||
|
image_request->fetch_image(realm(), request);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Start intersection-observing a lazy loading element for the img element.
|
||||||
|
document().start_intersection_observing_a_lazy_loading_element(*this);
|
||||||
|
|
||||||
|
// 3. Return.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
image_request->fetch_image(realm(), request);
|
image_request->fetch_image(realm(), request);
|
||||||
});
|
});
|
||||||
|
@ -769,4 +778,25 @@ HTMLImageElement::LazyLoading HTMLImageElement::lazy_loading() const
|
||||||
return LazyLoading::Eager;
|
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::SafeFunction<void()> HTMLImageElement::take_lazy_load_resumption_steps(Badge<DOM::Document>)
|
||||||
|
{
|
||||||
|
return move(m_lazy_load_resumption_steps);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ public:
|
||||||
Eager,
|
Eager,
|
||||||
};
|
};
|
||||||
[[nodiscard]] LazyLoading lazy_loading() const;
|
[[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
|
// https://html.spec.whatwg.org/multipage/images.html#upgrade-the-pending-request-to-the-current-request
|
||||||
void upgrade_pending_request_to_current_request();
|
void upgrade_pending_request_to_current_request();
|
||||||
|
@ -83,6 +84,8 @@ public:
|
||||||
virtual RefPtr<Gfx::Bitmap const> current_image_bitmap(Gfx::IntSize = {}) const override;
|
virtual RefPtr<Gfx::Bitmap const> current_image_bitmap(Gfx::IntSize = {}) const override;
|
||||||
virtual void set_visible_in_viewport(bool) override;
|
virtual void set_visible_in_viewport(bool) override;
|
||||||
|
|
||||||
|
JS::SafeFunction<void()> take_lazy_load_resumption_steps(Badge<DOM::Document>);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HTMLImageElement(DOM::Document&, DOM::QualifiedName);
|
HTMLImageElement(DOM::Document&, DOM::QualifiedName);
|
||||||
|
|
||||||
|
@ -116,6 +119,10 @@ private:
|
||||||
RefPtr<ImageRequest> m_pending_request;
|
RefPtr<ImageRequest> m_pending_request;
|
||||||
|
|
||||||
SourceSet m_source_set;
|
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::SafeFunction<void()> m_lazy_load_resumption_steps;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue