diff --git a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp
index 30c07eb4cc..2843b2161a 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp
@@ -38,10 +38,20 @@ HTMLImageElement::HTMLImageElement(DOM::Document& document, DOM::QualifiedName q
{
m_animation_timer = Core::Timer::try_create().release_value_but_fixme_should_propagate_errors();
m_animation_timer->on_timeout = [this] { animate(); };
+
+ if (auto* browsing_context = document.browsing_context())
+ browsing_context->register_viewport_client(*this);
}
HTMLImageElement::~HTMLImageElement() = default;
+void HTMLImageElement::finalize()
+{
+ Base::finalize();
+ if (auto* browsing_context = document().browsing_context())
+ browsing_context->unregister_viewport_client(*this);
+}
+
JS::ThrowCompletionOr HTMLImageElement::initialize(JS::Realm& realm)
{
MUST_OR_THROW_OOM(Base::initialize(realm));
@@ -505,71 +515,7 @@ after_step_7:
if (delay_load_event)
m_load_event_delayer.emplace(document());
- image_request->add_callbacks(
- [this, image_request, maybe_omit_events, url_string, previous_url]() {
- batching_dispatcher().enqueue([this, image_request, maybe_omit_events, url_string, previous_url] {
- VERIFY(image_request->shared_image_request());
- auto image_data = image_request->shared_image_request()->image_data();
- image_request->set_image_data(image_data);
-
- ListOfAvailableImages::Key key;
- key.url = url_string;
- key.mode = m_cors_setting;
- key.origin = document().origin();
-
- // 1. If image request is the pending request, abort the image request for the current request,
- // upgrade the pending request to the current request
- // and prepare image request for presentation given the img element.
- if (image_request == m_pending_request) {
- abort_the_image_request(realm(), m_current_request);
- upgrade_pending_request_to_current_request();
- image_request->prepare_for_presentation(*this);
- }
-
- // 2. Set image request to the completely available state.
- image_request->set_state(ImageRequest::State::CompletelyAvailable);
-
- // 3. Add the image to the list of available images using the key key, with the ignore higher-layer caching flag set.
- document().list_of_available_images().add(key, *image_data, true).release_value_but_fixme_should_propagate_errors();
-
- // 4. If maybe omit events is not set or previousURL is not equal to urlString, then fire an event named load at the img element.
- if (!maybe_omit_events || previous_url != url_string)
- dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors());
-
- set_needs_style_update(true);
- document().set_needs_layout();
-
- if (image_data->is_animated() && image_data->frame_count() > 1) {
- m_current_frame_index = 0;
- m_animation_timer->set_interval(image_data->frame_duration(0));
- m_animation_timer->start();
- }
-
- m_load_event_delayer.clear();
- });
- },
- [this, image_request, maybe_omit_events, url_string, previous_url, selected_source]() {
- // The image data is not in a supported file format;
-
- // the user agent must set image request's state to broken,
- image_request->set_state(ImageRequest::State::Broken);
-
- // abort the image request for the current request and the pending request,
- abort_the_image_request(realm(), m_current_request);
- abort_the_image_request(realm(), m_pending_request);
-
- // upgrade the pending request to the current request if image request is the pending request,
- if (image_request == m_pending_request)
- upgrade_pending_request_to_current_request();
-
- // and then, if maybe omit events is not set or previousURL is not equal to urlString,
- // queue an element task on the DOM manipulation task source given the img element
- // to fire an event named error at the img element.
- if (!maybe_omit_events || previous_url != url_string)
- dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error).release_value_but_fixme_should_propagate_errors());
-
- m_load_event_delayer.clear();
- });
+ add_callbacks_to_image_request(image_request, maybe_omit_events, url_string, previous_url);
// AD-HOC: If the image request is already available or fetching, no need to start another fetch.
if (image_request->is_available() || image_request->is_fetching())
@@ -610,6 +556,175 @@ after_step_7:
return {};
}
+void HTMLImageElement::add_callbacks_to_image_request(NonnullRefPtr image_request, bool maybe_omit_events, AK::URL const& url_string, AK::URL const& previous_url)
+{
+ image_request->add_callbacks(
+ [this, image_request, maybe_omit_events, url_string, previous_url]() {
+ batching_dispatcher().enqueue([this, image_request, maybe_omit_events, url_string, previous_url] {
+ VERIFY(image_request->shared_image_request());
+ auto image_data = image_request->shared_image_request()->image_data();
+ image_request->set_image_data(image_data);
+
+ ListOfAvailableImages::Key key;
+ key.url = url_string;
+ key.mode = m_cors_setting;
+ key.origin = document().origin();
+
+ // 1. If image request is the pending request, abort the image request for the current request,
+ // upgrade the pending request to the current request
+ // and prepare image request for presentation given the img element.
+ if (image_request == m_pending_request) {
+ abort_the_image_request(realm(), m_current_request);
+ upgrade_pending_request_to_current_request();
+ image_request->prepare_for_presentation(*this);
+ }
+
+ // 2. Set image request to the completely available state.
+ image_request->set_state(ImageRequest::State::CompletelyAvailable);
+
+ // 3. Add the image to the list of available images using the key key, with the ignore higher-layer caching flag set.
+ document().list_of_available_images().add(key, *image_data, true).release_value_but_fixme_should_propagate_errors();
+
+ // 4. If maybe omit events is not set or previousURL is not equal to urlString, then fire an event named load at the img element.
+ if (!maybe_omit_events || previous_url != url_string)
+ dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors());
+
+ set_needs_style_update(true);
+ document().set_needs_layout();
+
+ if (image_data->is_animated() && image_data->frame_count() > 1) {
+ m_current_frame_index = 0;
+ m_animation_timer->set_interval(image_data->frame_duration(0));
+ m_animation_timer->start();
+ }
+
+ m_load_event_delayer.clear();
+ });
+ },
+ [this, image_request, maybe_omit_events, url_string, previous_url]() {
+ // The image data is not in a supported file format;
+
+ // the user agent must set image request's state to broken,
+ image_request->set_state(ImageRequest::State::Broken);
+
+ // abort the image request for the current request and the pending request,
+ abort_the_image_request(realm(), m_current_request);
+ abort_the_image_request(realm(), m_pending_request);
+
+ // upgrade the pending request to the current request if image request is the pending request,
+ if (image_request == m_pending_request)
+ upgrade_pending_request_to_current_request();
+
+ // and then, if maybe omit events is not set or previousURL is not equal to urlString,
+ // queue an element task on the DOM manipulation task source given the img element
+ // to fire an event named error at the img element.
+ if (!maybe_omit_events || previous_url != url_string)
+ dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error).release_value_but_fixme_should_propagate_errors());
+
+ m_load_event_delayer.clear();
+ });
+}
+
+void HTMLImageElement::browsing_context_did_set_viewport_rect(CSSPixelRect const& viewport_rect)
+{
+ if (viewport_rect.size() == m_last_seen_viewport_size)
+ return;
+ m_last_seen_viewport_size = viewport_rect.size();
+ batching_dispatcher().enqueue([this] {
+ react_to_changes_in_the_environment();
+ });
+}
+
+// https://html.spec.whatwg.org/multipage/images.html#img-environment-changes
+void HTMLImageElement::react_to_changes_in_the_environment()
+{
+ // FIXME: 1. Await a stable state.
+ // The synchronous section consists of all the remaining steps of this algorithm
+ // until the algorithm says the synchronous section has ended.
+ // (Steps in synchronous sections are marked with ⌛.)
+
+ // 2. ⌛ If the img element does not use srcset or picture,
+ // its node document is not fully active,
+ // FIXME: has image data whose resource type is multipart/x-mixed-replace,
+ // or the pending request is not null,
+ // then return.
+ if (!uses_srcset_or_picture() || !document().is_fully_active() || m_pending_request)
+ return;
+
+ // 3. ⌛ Let selected source and selected pixel density be the URL and pixel density
+ // that results from selecting an image source, respectively.
+ Optional selected_source;
+ Optional pixel_density;
+ if (auto result = select_an_image_source(); result.has_value()) {
+ selected_source = result.value().source.url;
+ pixel_density = result.value().pixel_density;
+ }
+
+ // 4. ⌛ If selected source is null, then return.
+ if (!selected_source.has_value())
+ return;
+
+ // 5. ⌛ If selected source and selected pixel density are the same
+ // as the element's last selected source and current pixel density, then return.
+ if (selected_source == m_last_selected_source && pixel_density == m_current_request->current_pixel_density())
+ return;
+
+ // 6. ⌛ Parse selected source, relative to the element's node document,
+ // and let urlString be the resulting URL string. If that is not successful, then return.
+ auto url_string = document().parse_url(selected_source.value());
+ if (!url_string.is_valid())
+ return;
+
+ // 7. ⌛ Let corsAttributeState be the state of the element's crossorigin content attribute.
+ auto cors_attribute_state = m_cors_setting;
+
+ // 8. ⌛ Let origin be the img element's node document's origin.
+ auto origin = document().origin();
+
+ // 9. ⌛ Let client be the img element's node document's relevant settings object.
+ auto& client = document().relevant_settings_object();
+
+ // 10. ⌛ Let key be a tuple consisting of urlString, corsAttributeState, and, if corsAttributeState is not No CORS, origin.
+ ListOfAvailableImages::Key key;
+ key.url = url_string;
+ key.mode = m_cors_setting;
+ if (cors_attribute_state != CORSSettingAttribute::NoCORS)
+ key.origin = document().origin();
+
+ // 11. ⌛ Let image request be a new image request whose current URL is urlString
+ auto image_request = ImageRequest::create(*document().page()).release_value_but_fixme_should_propagate_errors();
+ image_request->set_current_url(url_string);
+
+ // 12. ⌛ Let the element's pending request be image request.
+ m_pending_request = image_request;
+
+ // FIXME: 13. End the synchronous section, continuing the remaining steps in parallel.
+
+ // 14. If the list of available images contains an entry for key, then set image request's image data to that of the entry.
+ // Continue to the next step.
+ if (auto entry = document().list_of_available_images().get(key)) {
+ image_request->set_image_data(entry->image_data);
+ }
+ // Otherwise:
+ else {
+ // 1. Let request be the result of creating a potential-CORS request given urlString, "image", and corsAttributeState.
+ auto request = create_potential_CORS_request(vm(), url_string, Fetch::Infrastructure::Request::Destination::Image, m_cors_setting);
+
+ // 2. Set request's client to client, initiator to "imageset", and set request's synchronous flag.
+ request->set_client(&client);
+ request->set_initiator(Fetch::Infrastructure::Request::Initiator::ImageSet);
+
+ // 3. Set request's referrer policy to the current state of the element's referrerpolicy attribute.
+ request->set_referrer_policy(ReferrerPolicy::from_string(attribute(HTML::AttributeNames::referrerpolicy)));
+
+ // FIXME: 4. Set request's priority to the current state of the element's fetchpriority attribute.
+
+ // 5. Let response be the result of fetching request.
+ add_callbacks_to_image_request(image_request, false, url_string, AK::URL());
+ image_request->fetch_image(realm(), request);
+ }
+}
+
// https://html.spec.whatwg.org/multipage/images.html#upgrade-the-pending-request-to-the-current-request
void HTMLImageElement::upgrade_pending_request_to_current_request()
{
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.h b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.h
index ea854589b5..6be6361bf2 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.h
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -21,7 +22,8 @@ namespace Web::HTML {
class HTMLImageElement final
: public HTMLElement
, public FormAssociatedElement
- , public Layout::ImageProvider {
+ , public Layout::ImageProvider
+ , public BrowsingContext::ViewportClient {
WEB_PLATFORM_OBJECT(HTMLImageElement, HTMLElement);
FORM_ASSOCIATED_ELEMENT(HTMLElement, HTMLImageElement)
@@ -49,6 +51,9 @@ public:
virtual Optional default_role() const override;
+ // https://html.spec.whatwg.org/multipage/images.html#img-environment-changes
+ void react_to_changes_in_the_environment();
+
// https://html.spec.whatwg.org/multipage/images.html#update-the-image-data
ErrorOr update_the_image_data(bool restart_the_animations = false, bool maybe_omit_events = false);
@@ -90,13 +95,17 @@ private:
HTMLImageElement(DOM::Document&, DOM::QualifiedName);
virtual JS::ThrowCompletionOr initialize(JS::Realm&) override;
+ virtual void finalize() override;
virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
virtual JS::GCPtr create_layout_node(NonnullRefPtr) override;
+ virtual void browsing_context_did_set_viewport_rect(CSSPixelRect const&) override;
+
void handle_successful_fetch(AK::URL const&, StringView mime_type, ImageRequest&, ByteBuffer, bool maybe_omit_events, AK::URL const& previous_url);
void handle_failed_fetch();
+ void add_callbacks_to_image_request(NonnullRefPtr, bool maybe_omit_events, AK::URL const& url_string, AK::URL const& previous_url);
void animate();
@@ -123,6 +132,8 @@ 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::SafeFunction m_lazy_load_resumption_steps;
+
+ CSSPixelSize m_last_seen_viewport_size;
};
}