diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 9c46daeed8..243b8b067f 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -688,6 +688,7 @@ set(SOURCES HTML/SessionHistoryTraversalQueue.cpp HTML/SharedResourceRequest.cpp HTML/AnimatedBitmapDecodedImageData.cpp + HTML/AnimatedDecodedImageData.cpp HTML/SharedWorker.cpp HTML/SharedWorkerGlobalScope.cpp HTML/SourceSet.cpp diff --git a/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.cpp index 0cf59d2185..eaac46b87b 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.cpp @@ -8,7 +8,6 @@ */ #include -#include #include #include #include @@ -22,7 +21,6 @@ #include #include #include -#include namespace Web::CSS { @@ -34,7 +32,7 @@ ImageStyleValueResource::ImageStyleValueResource(GC::Refcss_image_resource(url)) - resource->on_decoded_image_data_loaded(*document); + resource->on_decoded_image_data_loaded(); } }, nullptr); @@ -42,7 +40,6 @@ ImageStyleValueResource::ImageStyleValueResource(GC::Ref ImageStyleValueResource::decoded_image_data() const @@ -74,15 +67,9 @@ GC::Ptr ImageStyleValueResource::decoded_image_data() co return m_resource_request->image_data(); } -bool ImageStyleValueResource::has_active_animation_timer() const -{ - return m_timer && m_timer->is_active(); -} - -void ImageStyleValueResource::on_decoded_image_data_loaded(DOM::Document& document) +void ImageStyleValueResource::on_decoded_image_data_loaded() { notify_image_style_values_did_update(); - start_animation_timer_if_needed(document); if (!m_image_style_values.is_empty()) register_with_decoded_image_data_if_needed(); } @@ -93,79 +80,6 @@ void ImageStyleValueResource::notify_image_style_values_did_update() image_style_value->notify_clients_did_update(); } -void ImageStyleValueResource::start_animation_timer_if_needed(DOM::Document& document) -{ - if (m_image_style_values.is_empty() || !is_animatable()) - return; - - if (m_timer && m_timer->is_active()) - return; - - if (!m_timer) { - auto timer = Platform::Timer::create(document.heap()); - m_timer = timer; - timer->on_timeout = GC::create_function(document.heap(), [weak_document = GC::Weak(document), url = m_resource_request->url()] { - if (auto document = weak_document.ptr()) - document->animate_css_image_resource(url); - }); - } - - m_timer->set_interval(current_frame_duration()); - m_timer->start(); -} - -void ImageStyleValueResource::stop_animation_timer() -{ - if (m_timer && m_timer->is_active()) - m_timer->stop(); -} - -bool ImageStyleValueResource::is_animatable() const -{ - auto image_data = this->decoded_image_data(); - if (!image_data || !image_data->is_animated() || image_data->frame_count() <= 1) - return false; - - return !animation_has_completed(); -} - -bool ImageStyleValueResource::animation_has_completed() const -{ - auto image_data = this->decoded_image_data(); - return image_data && image_data->loop_count() > 0 && m_loops_completed == image_data->loop_count(); -} - -int ImageStyleValueResource::current_frame_duration() const -{ - auto image_data = this->decoded_image_data(); - if (!image_data) - return 0; - - return image_data->frame_duration(m_current_frame_index); -} - -void ImageStyleValueResource::animate(DOM::Document&) -{ - auto image_data = m_resource_request->image_data(); - if (!image_data) - return; - - m_current_frame_index = (m_current_frame_index + 1) % image_data->frame_count(); - m_current_frame_index = image_data->notify_frame_advanced(m_current_frame_index); - auto current_frame_duration = image_data->frame_duration(m_current_frame_index); - - if (m_timer && current_frame_duration != m_timer->interval()) - m_timer->restart(current_frame_duration); - - if (m_current_frame_index == image_data->frame_count() - 1) { - ++m_loops_completed; - if (animation_has_completed()) - stop_animation_timer(); - } - - notify_image_style_values_did_update(); -} - ValueComparingNonnullRefPtr ImageStyleValue::create(URL const& url) { return adopt_ref(*new (nothrow) ImageStyleValue(url)); @@ -190,11 +104,6 @@ ImageStyleValue::ImageStyleValue(URL const& url, Optional<::URL::URL> style_reso ImageStyleValue::~ImageStyleValue() = default; -u64 ImageStyleValue::active_animation_timer_count(DOM::Document const& document) -{ - return document.active_css_image_animation_timer_count(); -} - GC::Ptr ImageStyleValue::fetch_image(DOM::Document& document) const { RuleOrDeclaration rule_or_declaration { @@ -256,30 +165,17 @@ void ImageStyleValue::paint(DisplayListRecordingContext& context, DOM::Document if (!image_data) return; - auto current_frame_index = this->current_frame_index(document); auto dest_int_rect = dest_rect.to_type(); - image_data->paint(context, current_frame_index, dest_int_rect, image_rendering); + image_data->paint(context, dest_int_rect, image_rendering); } Optional ImageStyleValue::current_frame(DOM::Document const& document, DevicePixelRect const& dest_rect) const { if (auto image_data = this->image_data(document)) - return image_data->frame(current_frame_index(document), dest_rect.size().to_type()); - + return image_data->current_frame(dest_rect.size().to_type()); return {}; } -size_t ImageStyleValue::current_frame_index(DOM::Document const& document) const -{ - auto resolved_url = this->resolved_url(document); - if (!resolved_url.has_value()) - return 0; - - if (auto const* resource = document.css_image_resource(*resolved_url)) - return resource->current_frame_index(); - return 0; -} - GC::Ptr ImageStyleValue::image_data(DOM::Document const& document) const { auto resolved_url = this->resolved_url(document); @@ -397,7 +293,7 @@ void ImageStyleValue::register_client(Client& client) const resource = document->create_css_image_resource(*resource_request); } - resource->register_image_style_value(*document, *this); + resource->register_image_style_value(*this); } void ImageStyleValue::unregister_client(Client& client) const diff --git a/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.h index 7d22e17fca..8273235061 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.h @@ -32,32 +32,20 @@ public: void visit_edges(JS::Cell::Visitor&); - void register_image_style_value(DOM::Document&, ImageStyleValue const&); + void register_image_style_value(ImageStyleValue const&); void unregister_image_style_value(ImageStyleValue const&); bool can_be_removed() const { return m_image_style_values.is_empty(); } [[nodiscard]] virtual GC::Ptr decoded_image_data() const override; - [[nodiscard]] size_t current_frame_index() const { return m_current_frame_index; } - [[nodiscard]] bool has_active_animation_timer() const; - - void animate(DOM::Document&); private: virtual void decoded_image_data_did_update() override { notify_image_style_values_did_update(); } - void on_decoded_image_data_loaded(DOM::Document&); + void on_decoded_image_data_loaded(); void notify_image_style_values_did_update(); - void start_animation_timer_if_needed(DOM::Document&); - void stop_animation_timer(); - bool is_animatable() const; - bool animation_has_completed() const; - int current_frame_duration() const; GC::Ref m_resource_request; - GC::Ptr m_timer; HashTable m_image_style_values; - size_t m_current_frame_index { 0 }; - size_t m_loops_completed { 0 }; }; class ImageStyleValue final @@ -88,7 +76,6 @@ public: static ValueComparingNonnullRefPtr create(URL const&, Optional<::URL::URL> style_resource_base_url); static ValueComparingNonnullRefPtr create(::URL::URL const&); virtual ~ImageStyleValue() override; - static u64 active_animation_timer_count(DOM::Document const&); virtual void serialize(StringBuilder&, SerializationMode) const override; virtual bool equals(StyleValue const& other) const override; @@ -106,7 +93,6 @@ public: virtual Optional color_if_single_pixel_bitmap(DOM::Document const&) const override; Optional current_frame(DOM::Document const&, DevicePixelRect const& dest_rect = {}) const; - size_t current_frame_index(DOM::Document const&) const; GC::Ptr image_data(DOM::Document const&) const; diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 1223c86bfe..66edb4e983 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -6798,22 +6798,6 @@ void Document::remove_css_image_resource_if_unused(URL::URL const& url) m_css_image_resources.remove(it); } -void Document::animate_css_image_resource(URL::URL const& url) -{ - if (auto* resource = css_image_resource(url)) - resource->animate(*this); -} - -u64 Document::active_css_image_animation_timer_count() const -{ - u64 count = 0; - for (auto const& it : m_css_image_resources) { - if (it.value->has_active_animation_timer()) - ++count; - } - return count; -} - void Document::prune_image_resource_caches() { static constexpr size_t decoded_image_resource_cache_limit = 8 * MiB; diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index ff3371ef5c..314fe1b639 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -831,8 +831,6 @@ public: CSS::ImageStyleValueResource const* css_image_resource(URL::URL const&) const; CSS::ImageStyleValueResource& create_css_image_resource(GC::Ref); void remove_css_image_resource_if_unused(URL::URL const&); - void animate_css_image_resource(URL::URL const&); - u64 active_css_image_animation_timer_count() const; void prune_image_resource_caches(); void restore_the_history_object_state(NonnullRefPtr entry); diff --git a/Libraries/LibWeb/HTML/AnimatedBitmapDecodedImageData.cpp b/Libraries/LibWeb/HTML/AnimatedBitmapDecodedImageData.cpp index 16fcfe45e0..c1076fd893 100644 --- a/Libraries/LibWeb/HTML/AnimatedBitmapDecodedImageData.cpp +++ b/Libraries/LibWeb/HTML/AnimatedBitmapDecodedImageData.cpp @@ -9,11 +9,14 @@ #include #include #include +#include #include +#include #include #include #include #include +#include namespace Web::HTML { @@ -55,6 +58,7 @@ void AnimatedBitmapDecodedImageData::deliver_frames_for_session(i64 session_id, GC::Ref AnimatedBitmapDecodedImageData::create( JS::Realm& realm, + DOM::Document& document, i64 session_id, u32 frame_count, u32 loop_count, @@ -63,8 +67,11 @@ GC::Ref AnimatedBitmapDecodedImageData::create( Vector durations, Vector> initial_bitmaps) { + auto animation_timer = Platform::Timer::create(realm.heap()); + auto document_observer = realm.create(realm, document); + auto data = realm.create( - session_id, frame_count, loop_count, size, move(color_space), move(durations)); + session_id, frame_count, loop_count, size, move(color_space), move(durations), animation_timer, document_observer); // Place initial bitmaps into the buffer pool. for (u32 i = 0; i < initial_bitmaps.size(); ++i) { @@ -89,18 +96,32 @@ AnimatedBitmapDecodedImageData::AnimatedBitmapDecodedImageData( u32 loop_count, Gfx::IntSize size, Gfx::ColorSpace color_space, - Vector durations) - : m_session_id(session_id) + Vector durations, + GC::Ref animation_timer, + GC::Ref document_observer) + : AnimatedDecodedImageData(document_observer) + , m_session_id(session_id) , m_frame_count(frame_count) , m_loop_count(loop_count) , m_size(size) , m_color_space(move(color_space)) , m_durations(move(durations)) + , m_animation_timer(animation_timer) { + m_animation_timer->on_timeout = GC::create_function(vm().heap(), [weak_this = GC::Weak { *this }] { + if (auto self = weak_this.ptr()) + self->advance_animation(); + }); } AnimatedBitmapDecodedImageData::~AnimatedBitmapDecodedImageData() = default; +void AnimatedBitmapDecodedImageData::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_animation_timer); +} + size_t AnimatedBitmapDecodedImageData::external_memory_size() const { size_t size = JS::vector_external_memory_size(m_durations); @@ -114,10 +135,56 @@ size_t AnimatedBitmapDecodedImageData::external_memory_size() const void AnimatedBitmapDecodedImageData::finalize() { Base::finalize(); + m_animation_timer->stop(); session_registry().remove(m_session_id); Platform::ImageCodecPlugin::the().stop_animation_decode(m_session_id); } +bool AnimatedBitmapDecodedImageData::animation_has_completed() const +{ + return m_loop_count > 0 && m_loops_completed == m_loop_count; +} + +void AnimatedBitmapDecodedImageData::reset_animation() +{ + m_current_frame_index = 0; + m_loops_completed = 0; + maybe_request_more_frames(m_current_frame_index); + notify_clients_did_update(); +} + +void AnimatedBitmapDecodedImageData::start_animation() +{ + // NB: We should only ever start the animation when the first client is registered, or when animation restarts, both + // of which should guarantee that we are at the beginning of the animation. + VERIFY(m_current_frame_index == 0 && m_loops_completed == 0); + + m_animation_timer->start(frame_duration(0)); +} + +void AnimatedBitmapDecodedImageData::stop_animation() +{ + m_animation_timer->stop(); +} + +void AnimatedBitmapDecodedImageData::advance_animation() +{ + m_current_frame_index = (m_current_frame_index + 1) % m_frame_count; + maybe_request_more_frames(m_current_frame_index); + + auto current_frame_duration = frame_duration(m_current_frame_index); + if (current_frame_duration != m_animation_timer->interval()) + m_animation_timer->restart(current_frame_duration); + + if (m_current_frame_index == m_frame_count - 1) { + ++m_loops_completed; + if (animation_has_completed()) + stop_animation(); + } + + notify_clients_did_update(); +} + AnimatedBitmapDecodedImageData::BufferSlot const* AnimatedBitmapDecodedImageData::find_slot(u32 frame_index) const { for (auto const& slot : m_buffer_slots) { @@ -159,6 +226,11 @@ Optional AnimatedBitmapDecodedImageData::default_frame(G return frame(0, size); } +Optional AnimatedBitmapDecodedImageData::current_frame(Gfx::IntSize size) const +{ + return frame(m_current_frame_index, size); +} + int AnimatedBitmapDecodedImageData::frame_duration(size_t frame_index) const { if (frame_index >= m_durations.size()) @@ -181,9 +253,9 @@ Optional AnimatedBitmapDecodedImageData::intrinsic_aspect_rati return CSSPixels(m_size.width()) / CSSPixels(m_size.height()); } -void AnimatedBitmapDecodedImageData::paint(DisplayListRecordingContext& context, size_t frame_index, Gfx::IntRect dst_rect, CSS::ImageRendering image_rendering) const +void AnimatedBitmapDecodedImageData::paint(DisplayListRecordingContext& context, Gfx::IntRect dst_rect, CSS::ImageRendering image_rendering) const { - auto decoded_frame = frame(frame_index); + auto decoded_frame = current_frame(); if (!decoded_frame.has_value()) return; @@ -212,23 +284,14 @@ void AnimatedBitmapDecodedImageData::receive_frames(Vector #include #include +#include #include namespace Web::HTML { -class AnimatedBitmapDecodedImageData final : public DecodedImageData { - GC_CELL(AnimatedBitmapDecodedImageData, DecodedImageData); +class AnimatedBitmapDecodedImageData final : public AnimatedDecodedImageData { + GC_CELL(AnimatedBitmapDecodedImageData, AnimatedDecodedImageData); GC_DECLARE_ALLOCATOR(AnimatedBitmapDecodedImageData); + friend class Web::Internals::Internals; public: static constexpr bool OVERRIDES_FINALIZE = true; static GC::Ref create( JS::Realm&, + DOM::Document&, i64 session_id, u32 frame_count, u32 loop_count, @@ -35,22 +38,16 @@ public: virtual ~AnimatedBitmapDecodedImageData() override; virtual void finalize() override; + virtual void visit_edges(Cell::Visitor&) override; virtual Optional default_frame(Gfx::IntSize = {}) const override; - virtual Optional frame(size_t frame_index, Gfx::IntSize = {}) const override; - virtual int frame_duration(size_t frame_index) const override; - - virtual size_t frame_count() const override { return m_frame_count; } - virtual size_t loop_count() const override { return m_loop_count; } - virtual bool is_animated() const override { return true; } + virtual Optional current_frame(Gfx::IntSize = {}) const override; virtual Optional intrinsic_width() const override; virtual Optional intrinsic_height() const override; virtual Optional intrinsic_aspect_ratio() const override; - virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, CSS::ImageRendering) const override; - - virtual size_t notify_frame_advanced(size_t caller_frame_index) override; + virtual void paint(DisplayListRecordingContext&, Gfx::IntRect dst_rect, CSS::ImageRendering) const override; void receive_frames(Vector>, u32 start_frame_index); @@ -77,10 +74,22 @@ private: u32 loop_count, Gfx::IntSize, Gfx::ColorSpace, - Vector durations); + Vector durations, + GC::Ref, + GC::Ref); virtual size_t external_memory_size() const override; + virtual void reset_animation() override; + virtual void start_animation() override; + virtual void stop_animation() override; + + void advance_animation(); + bool animation_has_completed() const; + + int frame_duration(size_t frame_index) const; + Optional frame(size_t frame_index, Gfx::IntSize = {}) const; + BufferSlot const* find_slot(u32 frame_index) const; BufferSlot& evict_oldest_slot(); void maybe_request_more_frames(size_t current_frame_index); @@ -98,6 +107,8 @@ private: bool m_request_in_flight { false }; u32 m_current_frame_index { 0 }; u32 m_last_requested_start_frame { 0 }; + u32 m_loops_completed { 0 }; + GC::Ref m_animation_timer; }; } diff --git a/Libraries/LibWeb/HTML/AnimatedDecodedImageData.cpp b/Libraries/LibWeb/HTML/AnimatedDecodedImageData.cpp new file mode 100644 index 0000000000..568aec3240 --- /dev/null +++ b/Libraries/LibWeb/HTML/AnimatedDecodedImageData.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2026, Callum Law + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "AnimatedDecodedImageData.h" +#include +#include + +namespace Web::HTML { + +void AnimatedDecodedImageData::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_document_observer); +} + +AnimatedDecodedImageData::AnimatedDecodedImageData(GC::Ref document_observer) + : m_document_observer(document_observer) +{ + auto weak_this = GC::Weak { *this }; + + // OPTIMIZATION: To avoid CPU churn in background tabs we cancel the animation when the document is inactive or + // hidden. Other browsers disagree on what should happen when the document becomes active again, + // Blink continues the animation from where it would be if it had been running the whole time, and + // Gecko restarts the animation. For now we restart the animation since that's simpler. + m_document_observer->set_document_became_inactive([weak_this] { + if (auto self = weak_this.ptr()) + self->stop_animation(); + }); + + m_document_observer->set_document_became_active([weak_this] { + if (auto self = weak_this.ptr()) { + if (!self->has_clients()) + self->m_should_start_animation_on_client_registration = true; + + self->restart_animation(); + } + }); + + m_document_observer->set_document_visibility_state_observer([weak_this](HTML::VisibilityState visibility_state) { + if (auto self = weak_this.ptr()) { + switch (visibility_state) { + case HTML::VisibilityState::Hidden: + self->stop_animation(); + break; + case HTML::VisibilityState::Visible: + if (!self->has_clients()) + self->m_should_start_animation_on_client_registration = true; + self->restart_animation(); + break; + } + } + }); +} + +void AnimatedDecodedImageData::restart_animation() +{ + stop_animation(); + reset_animation(); + start_animation_if_needed(); +} + +void AnimatedDecodedImageData::on_client_registered() +{ + if (!m_should_start_animation_on_client_registration) + return; + + m_should_start_animation_on_client_registration = false; + + start_animation_if_needed(); +} + +void AnimatedDecodedImageData::start_animation_if_needed() +{ + // NB: Animations should start when the first client is registered while the document is active and visible. + if (!has_clients() || !m_document_observer->document()->is_fully_active() || m_document_observer->document()->hidden()) + return; + + start_animation(); +} + +} diff --git a/Libraries/LibWeb/HTML/AnimatedDecodedImageData.h b/Libraries/LibWeb/HTML/AnimatedDecodedImageData.h new file mode 100644 index 0000000000..76f485cb7c --- /dev/null +++ b/Libraries/LibWeb/HTML/AnimatedDecodedImageData.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2026, Callum Law + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::HTML { + +class AnimatedDecodedImageData : public DecodedImageData { + GC_CELL(AnimatedDecodedImageData, DecodedImageData); + GC_DECLARE_ALLOCATOR(AnimatedDecodedImageData); + +public: + virtual void visit_edges(Cell::Visitor&) override; + + virtual void restart_animation() override; + +protected: + AnimatedDecodedImageData(GC::Ref); + + virtual void start_animation() = 0; + virtual void reset_animation() = 0; + virtual void stop_animation() = 0; + + virtual void on_client_registered() override; + +private: + void start_animation_if_needed(); + + GC::Ref m_document_observer; + bool m_should_start_animation_on_client_registration { true }; +}; + +} diff --git a/Libraries/LibWeb/HTML/BitmapDecodedImageData.cpp b/Libraries/LibWeb/HTML/BitmapDecodedImageData.cpp index 46694ec05f..b74f226a28 100644 --- a/Libraries/LibWeb/HTML/BitmapDecodedImageData.cpp +++ b/Libraries/LibWeb/HTML/BitmapDecodedImageData.cpp @@ -34,7 +34,7 @@ size_t BitmapDecodedImageData::external_memory_size() const return m_frame.bitmap().data_size(); } -Optional BitmapDecodedImageData::frame(size_t, Gfx::IntSize) const +Optional BitmapDecodedImageData::current_frame(Gfx::IntSize) const { return m_frame; } @@ -59,7 +59,7 @@ Optional BitmapDecodedImageData::intrinsic_aspect_ratio() cons return CSSPixels(m_frame.width()) / CSSPixels(m_frame.height()); } -void BitmapDecodedImageData::paint(DisplayListRecordingContext& context, size_t, Gfx::IntRect dst_rect, CSS::ImageRendering image_rendering) const +void BitmapDecodedImageData::paint(DisplayListRecordingContext& context, Gfx::IntRect dst_rect, CSS::ImageRendering image_rendering) const { auto scaling_mode = CSS::to_gfx_scaling_mode(image_rendering, m_frame.size(), dst_rect.size()); diff --git a/Libraries/LibWeb/HTML/BitmapDecodedImageData.h b/Libraries/LibWeb/HTML/BitmapDecodedImageData.h index 751eeb5ccf..1300d5aec2 100644 --- a/Libraries/LibWeb/HTML/BitmapDecodedImageData.h +++ b/Libraries/LibWeb/HTML/BitmapDecodedImageData.h @@ -21,18 +21,13 @@ public: virtual ~BitmapDecodedImageData() override; virtual Optional default_frame(Gfx::IntSize = {}) const override; - virtual Optional frame(size_t frame_index, Gfx::IntSize = {}) const override; - - virtual int frame_duration(size_t) const override { return 0; } - virtual size_t frame_count() const override { return 1; } - virtual size_t loop_count() const override { return 0; } - virtual bool is_animated() const override { return false; } + virtual Optional current_frame(Gfx::IntSize = {}) const override; virtual Optional intrinsic_width() const override; virtual Optional intrinsic_height() const override; virtual Optional intrinsic_aspect_ratio() const override; - virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, CSS::ImageRendering) const override; + virtual void paint(DisplayListRecordingContext&, Gfx::IntRect dst_rect, CSS::ImageRendering) const override; private: BitmapDecodedImageData(Gfx::DecodedImageFrame&& frame); diff --git a/Libraries/LibWeb/HTML/DecodedImageData.cpp b/Libraries/LibWeb/HTML/DecodedImageData.cpp index de7f44e13b..2236268925 100644 --- a/Libraries/LibWeb/HTML/DecodedImageData.cpp +++ b/Libraries/LibWeb/HTML/DecodedImageData.cpp @@ -16,6 +16,8 @@ void DecodedImageData::Client::register_with_decoded_image_data_if_needed() return; image_data->m_clients.set(this); + + image_data->on_client_registered(); } void DecodedImageData::Client::unregister_with_decoded_image_data_if_needed() diff --git a/Libraries/LibWeb/HTML/DecodedImageData.h b/Libraries/LibWeb/HTML/DecodedImageData.h index a4cdbbde2e..b6ee56ab9a 100644 --- a/Libraries/LibWeb/HTML/DecodedImageData.h +++ b/Libraries/LibWeb/HTML/DecodedImageData.h @@ -20,6 +20,7 @@ namespace Web::HTML { // https://html.spec.whatwg.org/multipage/images.html#img-req-data class DecodedImageData : public JS::Cell { GC_CELL(DecodedImageData, JS::Cell); + friend class Web::Internals::Internals; public: class Client { @@ -37,17 +38,12 @@ public: [[nodiscard]] bool is_cors_cross_origin() const { return m_is_cors_cross_origin; } void set_is_cors_cross_origin(bool value) { m_is_cors_cross_origin = value; } - virtual void paint([[maybe_unused]] DisplayListRecordingContext&, [[maybe_unused]] size_t frame_index, [[maybe_unused]] Gfx::IntRect dst_rect, CSS::ImageRendering) const = 0; + virtual void paint([[maybe_unused]] DisplayListRecordingContext&, [[maybe_unused]] Gfx::IntRect dst_rect, CSS::ImageRendering) const = 0; virtual Optional default_frame(Gfx::IntSize = {}) const = 0; - virtual Optional frame(size_t frame_index, Gfx::IntSize = {}) const = 0; - virtual int frame_duration(size_t frame_index) const = 0; + virtual Optional current_frame(Gfx::IntSize = {}) const = 0; - virtual size_t frame_count() const = 0; - virtual size_t loop_count() const = 0; - virtual bool is_animated() const = 0; - - virtual size_t notify_frame_advanced(size_t frame_index) { return frame_index; } + virtual void restart_animation() { } virtual Optional intrinsic_width() const = 0; virtual Optional intrinsic_height() const = 0; @@ -57,6 +53,8 @@ protected: DecodedImageData(); void notify_clients_did_update(); + bool has_clients() const { return !m_clients.is_empty(); } + virtual void on_client_registered() { } private: HashTable m_clients; diff --git a/Libraries/LibWeb/HTML/HTMLImageElement.cpp b/Libraries/LibWeb/HTML/HTMLImageElement.cpp index 86a45b2d4f..7cc67a1baf 100644 --- a/Libraries/LibWeb/HTML/HTMLImageElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLImageElement.cpp @@ -148,9 +148,6 @@ GC_DEFINE_ALLOCATOR(HTMLImageElement); HTMLImageElement::HTMLImageElement(DOM::Document& document, DOM::QualifiedName qualified_name) : HTMLElement(document, move(qualified_name)) { - m_animation_timer = Core::Timer::create(); - m_animation_timer->on_timeout = [this] { animate(); }; - document.register_viewport_client(*this); } @@ -171,25 +168,12 @@ void HTMLImageElement::initialize(JS::Realm& realm) m_current_request = ImageRequest::create(realm, document().page()); // AD-HOC: Create a DocumentObserver eagerly to handle document lifecycle changes. - // The document_became_inactive callback handles the navigation case by clearing the - // load event delayer and stopping the animation timer. + // The document_became_inactive callback handles the navigation case by clearing the load event delayer. // A document_became_active callback is set lazily by update_the_image_data() when // needed to restart image loading after the document becomes active again. m_document_observer = realm.create(realm, document()); m_document_observer->set_document_became_inactive([this]() { m_load_event_delayer.clear(); - m_animation_timer->stop(); - m_animation_paused_by_visibility = false; - }); - m_document_observer->set_document_visibility_state_observer([this](HTML::VisibilityState visibility_state) { - if (visibility_state == HTML::VisibilityState::Hidden) { - m_animation_paused_by_visibility = m_animation_timer->is_active(); - m_animation_timer->stop(); - return; - } - - if (m_animation_paused_by_visibility) - start_animation_timer_if_visible(); }); } @@ -206,6 +190,8 @@ void HTMLImageElement::adopted_from(DOM::Document& old_document) if (m_load_event_delayer.has_value()) m_load_event_delayer.emplace(document()); + + // FIXME: The current and pending requests may still be pointing at the old document's SharedResourceRequests. } void HTMLImageElement::visit_edges(Cell::Visitor& visitor) @@ -699,7 +685,6 @@ void HTMLImageElement::update_the_image_data_impl(bool restart_animations, bool m_current_request = ImageRequest::create(document().realm(), document().page()); m_current_request->set_image_data(entry->image_data); m_current_request->set_state(ImageRequest::State::CompletelyAvailable); - m_current_frame_index = 0; register_with_decoded_image_data_if_needed(); // 5. Prepare the current request for presentation given the img element. @@ -985,13 +970,6 @@ void HTMLImageElement::add_callbacks_to_image_request(GC::Ref imag if (!maybe_omit_events || previous_url != url_string) dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load)); - m_current_frame_index = 0; - m_animation_timer->stop(); - if (image_data->is_animated() && image_data->frame_count() > 1) { - m_animation_timer->set_interval(image_data->frame_duration(0)); - start_animation_timer_if_visible(); - } - m_load_event_delayer.clear(); })); }, @@ -1230,38 +1208,8 @@ void HTMLImageElement::handle_failed_fetch() // https://html.spec.whatwg.org/multipage/rendering.html#restart-the-animation void HTMLImageElement::restart_the_animation() { - m_current_frame_index = 0; - - if (current_request_has_running_animation()) { - start_animation_timer_if_visible(); - } else { - m_animation_timer->stop(); - m_animation_paused_by_visibility = false; - } -} - -bool HTMLImageElement::current_request_has_running_animation() const -{ - auto image_data = m_current_request->image_data(); - return image_data && image_data->is_animated() && image_data->frame_count() > 1; -} - -void HTMLImageElement::start_animation_timer_if_visible() -{ - if (!current_request_has_running_animation()) { - m_animation_timer->stop(); - m_animation_paused_by_visibility = false; - return; - } - - if (document().visibility_state_value() == VisibilityState::Hidden) { - m_animation_timer->stop(); - m_animation_paused_by_visibility = true; - return; - } - - m_animation_paused_by_visibility = false; - m_animation_timer->start(); + if (auto image_data = m_current_request->image_data()) + image_data->restart_animation(); } // https://html.spec.whatwg.org/multipage/images.html#update-the-source-set @@ -1424,38 +1372,6 @@ void HTMLImageElement::set_source_set(SourceSet source_set) m_source_set = move(source_set); } -void HTMLImageElement::animate() -{ - if (document().visibility_state_value() == VisibilityState::Hidden) { - m_animation_timer->stop(); - m_animation_paused_by_visibility = true; - return; - } - - auto image_data = m_current_request->image_data(); - if (!image_data) { - return; - } - - m_current_frame_index = (m_current_frame_index + 1) % image_data->frame_count(); - m_current_frame_index = image_data->notify_frame_advanced(m_current_frame_index); - auto current_frame_duration = image_data->frame_duration(m_current_frame_index); - - if (current_frame_duration != m_animation_timer->interval()) { - m_animation_timer->restart(current_frame_duration); - } - - if (m_current_frame_index == image_data->frame_count() - 1) { - ++m_loops_completed; - if (m_loops_completed > 0 && m_loops_completed == image_data->loop_count()) { - m_animation_timer->stop(); - m_animation_paused_by_visibility = false; - } - } - - set_needs_repaint(); -} - bool HTMLImageElement::allows_auto_sizes() const { // An img element allows auto-sizes if: diff --git a/Libraries/LibWeb/HTML/HTMLImageElement.h b/Libraries/LibWeb/HTML/HTMLImageElement.h index 792917dca2..fc3aa464f0 100644 --- a/Libraries/LibWeb/HTML/HTMLImageElement.h +++ b/Libraries/LibWeb/HTML/HTMLImageElement.h @@ -98,8 +98,6 @@ public: ImageRequest& current_request() { return *m_current_request; } ImageRequest const& current_request() const { return *m_current_request; } - virtual size_t current_frame_index() const override { return m_current_frame_index; } - // https://html.spec.whatwg.org/multipage/images.html#upgrade-the-pending-request-to-the-current-request void upgrade_pending_request_to_current_request(); @@ -140,15 +138,6 @@ private: virtual void decoded_image_data_did_update() override { set_needs_repaint(); } - bool current_request_has_running_animation() const; - void start_animation_timer_if_visible(); - void animate(); - - RefPtr m_animation_timer; - size_t m_current_frame_index { 0 }; - size_t m_loops_completed { 0 }; - bool m_animation_paused_by_visibility { false }; - Optional m_load_event_delayer; GC::Ptr m_document_observer; diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.h b/Libraries/LibWeb/HTML/HTMLInputElement.h index 679cb9f867..dd36cfffc0 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -287,7 +287,6 @@ private: virtual bool supports_dimension_attributes() const override { return type_state() == TypeAttributeState::ImageButton; } // ^Layout::ImageProvider - virtual size_t current_frame_index() const override { return 0; } virtual GC::Ptr decoded_image_data() const override { return image_data(); } virtual void initialize(JS::Realm&) override; diff --git a/Libraries/LibWeb/HTML/HTMLObjectElement.h b/Libraries/LibWeb/HTML/HTMLObjectElement.h index 786bc81856..c141031a89 100644 --- a/Libraries/LibWeb/HTML/HTMLObjectElement.h +++ b/Libraries/LibWeb/HTML/HTMLObjectElement.h @@ -83,7 +83,6 @@ private: virtual i32 default_tab_index_value() const override; // ^Layout::ImageProvider - virtual size_t current_frame_index() const override { return 0; } virtual GC::Ptr decoded_image_data() const override { return image_data(); } GC::Ptr image_data() const; diff --git a/Libraries/LibWeb/HTML/SharedResourceRequest.cpp b/Libraries/LibWeb/HTML/SharedResourceRequest.cpp index ebe8e09aec..d0a63875a3 100644 --- a/Libraries/LibWeb/HTML/SharedResourceRequest.cpp +++ b/Libraries/LibWeb/HTML/SharedResourceRequest.cpp @@ -221,6 +221,7 @@ void SharedResourceRequest::handle_successful_fetch(URL::URL const& url_string, strong_this->m_image_data = AnimatedBitmapDecodedImageData::create( strong_this->m_document->realm(), + *strong_this->m_document, result.session_id, result.frame_count, result.loop_count, diff --git a/Libraries/LibWeb/Internals/Internals.cpp b/Libraries/LibWeb/Internals/Internals.cpp index 329817742b..8835d57400 100644 --- a/Libraries/LibWeb/Internals/Internals.cpp +++ b/Libraries/LibWeb/Internals/Internals.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -35,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,7 @@ #include #include #include +#include #include namespace Web::Internals { @@ -856,9 +858,38 @@ bool Internals::style_sheet_may_have_has_selectors(CSS::CSSStyleSheet& style_she return style_sheet.selector_insights().has_has_selectors; } -WebIDL::UnsignedLongLong Internals::active_image_style_value_animation_count() +WebIDL::ExceptionOr Internals::image_animation_state_for_url(String const& url) { - return CSS::ImageStyleValue::active_animation_timer_count(window().associated_document()); + auto& document = window().associated_document(); + auto parsed_url = document.encoding_parse_url(url); + if (!parsed_url.has_value()) + return WebIDL::SimpleException { .type = WebIDL::SimpleExceptionType::TypeError, .message = MUST(String::formatted("Invalid URL: '{}'", url)) }; + + auto it = document.shared_resource_requests().find(*parsed_url); + if (it == document.shared_resource_requests().end()) + return WebIDL::SimpleException { .type = WebIDL::SimpleExceptionType::TypeError, .message = MUST(String::formatted("URL doesn't have any associated shared resource requests: '{}'", url)) }; + + auto image_data = it->value->image_data(); + + if (!image_data) + return WebIDL::SimpleException { .type = WebIDL::SimpleExceptionType::TypeError, .message = MUST(String::formatted("URL's shared resource request doesn't have any associated image data: '{}'", url)) }; + + auto const* animated_bitmap_data = as_if(*image_data); + + if (!animated_bitmap_data) + return WebIDL::SimpleException { .type = WebIDL::SimpleExceptionType::TypeError, .message = MUST(String::formatted("URL's associated image is not an animated bitmap: '{}'", url)) }; + + auto object = JS::Object::create(realm(), nullptr); + + object->define_direct_property("timerActive"_utf16_fly_string, JS::Value(animated_bitmap_data->m_animation_timer->is_active()), JS::default_attributes); + object->define_direct_property("sessionID"_utf16_fly_string, JS::Value(static_cast(animated_bitmap_data->m_session_id)), JS::default_attributes); + object->define_direct_property("frameIndex"_utf16_fly_string, JS::Value(animated_bitmap_data->m_current_frame_index), JS::default_attributes); + object->define_direct_property("frameCount"_utf16_fly_string, JS::Value(animated_bitmap_data->m_frame_count), JS::default_attributes); + object->define_direct_property("loopsCompleted"_utf16_fly_string, JS::Value(animated_bitmap_data->m_loops_completed), JS::default_attributes); + object->define_direct_property("loopCount"_utf16_fly_string, JS::Value(animated_bitmap_data->m_loop_count), JS::default_attributes); + object->define_direct_property("clientCount"_utf16_fly_string, JS::Value(image_data->m_clients.size()), JS::default_attributes); + + return object.ptr(); } struct AsyncScrollingStateSnapshot { diff --git a/Libraries/LibWeb/Internals/Internals.h b/Libraries/LibWeb/Internals/Internals.h index ac8ddbb747..d1dd29d6f0 100644 --- a/Libraries/LibWeb/Internals/Internals.h +++ b/Libraries/LibWeb/Internals/Internals.h @@ -136,7 +136,7 @@ public: void set_preferred_color_scheme(StringView color_scheme); String canvas_color_scheme(); bool style_sheet_may_have_has_selectors(CSS::CSSStyleSheet&); - WebIDL::UnsignedLongLong active_image_style_value_animation_count(); + WebIDL::ExceptionOr image_animation_state_for_url(String const& url); JS::Object* async_scrolling_state(); bool async_scrolling_state_blocks_wheel_event_at(double x, double y); bool async_scrolling_state_can_wheel_scroll_at(double x, double y, double delta_x, double delta_y, bool force_stale_wheel_event_regions); diff --git a/Libraries/LibWeb/Internals/Internals.idl b/Libraries/LibWeb/Internals/Internals.idl index f5b3b2e26a..d021a4c7c2 100644 --- a/Libraries/LibWeb/Internals/Internals.idl +++ b/Libraries/LibWeb/Internals/Internals.idl @@ -132,7 +132,7 @@ interface Internals { DOMString canvasColorScheme(); // Returns the selector-insight cache state for stylesheet invalidation tests. boolean styleSheetMayHaveHasSelectors(CSSStyleSheet sheet); - unsigned long long activeImageStyleValueAnimationCount(); + object imageAnimationStateForURL(USVString url); object asyncScrollingState(); boolean asyncScrollingStateBlocksWheelEventAt(double x, double y); diff --git a/Libraries/LibWeb/Layout/ImageProvider.cpp b/Libraries/LibWeb/Layout/ImageProvider.cpp index f190f3afe2..3cf6b7269a 100644 --- a/Libraries/LibWeb/Layout/ImageProvider.cpp +++ b/Libraries/LibWeb/Layout/ImageProvider.cpp @@ -50,7 +50,7 @@ Optional ImageProvider::intrinsic_size() const Optional ImageProvider::current_image_frame(Optional size) const { if (auto const& data = decoded_image_data()) - return data->frame(current_frame_index(), size.value_or(intrinsic_size().value_or({}).to_type())); + return data->current_frame(size.value_or(intrinsic_size().value_or({}).to_type())); return {}; } diff --git a/Libraries/LibWeb/Layout/ImageProvider.h b/Libraries/LibWeb/Layout/ImageProvider.h index c985591f65..f1a59537d4 100644 --- a/Libraries/LibWeb/Layout/ImageProvider.h +++ b/Libraries/LibWeb/Layout/ImageProvider.h @@ -22,8 +22,6 @@ public: bool is_image_available() const { return decoded_image_data() != nullptr; } - virtual size_t current_frame_index() const = 0; - virtual GC::Ptr decoded_image_data() const = 0; Optional intrinsic_width() const; diff --git a/Libraries/LibWeb/Layout/TreeBuilder.cpp b/Libraries/LibWeb/Layout/TreeBuilder.cpp index d17fdb414d..db77d9816b 100644 --- a/Libraries/LibWeb/Layout/TreeBuilder.cpp +++ b/Libraries/LibWeb/Layout/TreeBuilder.cpp @@ -219,13 +219,6 @@ public: m_layout_node = layout_node; } - virtual size_t current_frame_index() const override - { - if (auto document = this->document()) - return m_image->current_frame_index(*document); - return 0; - } - virtual GC::Ptr decoded_image_data() const override { if (auto document = this->document()) diff --git a/Libraries/LibWeb/Painting/ImagePaintable.cpp b/Libraries/LibWeb/Painting/ImagePaintable.cpp index 43875b50b0..0b2a559ce1 100644 --- a/Libraries/LibWeb/Painting/ImagePaintable.cpp +++ b/Libraries/LibWeb/Painting/ImagePaintable.cpp @@ -85,7 +85,7 @@ void ImagePaintable::paint(DisplayListRecordingContext& context, PaintPhase phas context.display_list_recorder().save(); context.display_list_recorder().add_clip_rect(image_int_rect_device_pixels); } - decoded_image_data->paint(context, m_image_provider.current_frame_index(), draw_rect, computed_values().image_rendering()); + decoded_image_data->paint(context, draw_rect, computed_values().image_rendering()); if (draw_rect_needs_clip) context.display_list_recorder().restore(); } diff --git a/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp b/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp index a9da29e147..7a4d34297a 100644 --- a/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp +++ b/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp @@ -221,7 +221,7 @@ RefPtr SVGDecodedImageData::render_to_surface(Gfx::IntSize return surface; } -Optional SVGDecodedImageData::frame(size_t, Gfx::IntSize size) const +Optional SVGDecodedImageData::current_frame(Gfx::IntSize size) const { if (size.is_empty()) return {}; @@ -243,7 +243,7 @@ Optional SVGDecodedImageData::default_frame(Gfx::IntSize { // FIXME: Implement this properly once we support animated SVGs, potentially by creating a temporary internal // document which has animations disabled. - return frame(0, size); + return current_frame(size); } Optional SVGDecodedImageData::intrinsic_width() const @@ -300,7 +300,7 @@ void SVGDecodedImageData::SVGPageClient::visit_edges(Visitor& visitor) visitor.visit(m_svg_page); } -void SVGDecodedImageData::paint(DisplayListRecordingContext& context, size_t, Gfx::IntRect dst_rect, CSS::ImageRendering) const +void SVGDecodedImageData::paint(DisplayListRecordingContext& context, Gfx::IntRect dst_rect, CSS::ImageRendering) const { auto display_list = record_display_list(dst_rect.size(), context.display_list_recorder().resource_storage()); if (!display_list.has_value()) diff --git a/Libraries/LibWeb/SVG/SVGDecodedImageData.h b/Libraries/LibWeb/SVG/SVGDecodedImageData.h index deb9e334e4..c077191eff 100644 --- a/Libraries/LibWeb/SVG/SVGDecodedImageData.h +++ b/Libraries/LibWeb/SVG/SVGDecodedImageData.h @@ -25,24 +25,19 @@ public: virtual ~SVGDecodedImageData() override; virtual Optional default_frame(Gfx::IntSize = {}) const override; - virtual Optional frame(size_t frame_index, Gfx::IntSize) const override; + virtual Optional current_frame(Gfx::IntSize = {}) const override; virtual Optional intrinsic_width() const override; virtual Optional intrinsic_height() const override; virtual Optional intrinsic_aspect_ratio() const override; // FIXME: Support SVG animations. :^) - virtual int frame_duration(size_t) const override { return 0; } - virtual size_t frame_count() const override { return 1; } - virtual size_t loop_count() const override { return 0; } - virtual bool is_animated() const override { return false; } - DOM::Document const& svg_document() const { return *m_document; } virtual void visit_edges(Cell::Visitor& visitor) override; virtual size_t external_memory_size() const override; - virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, CSS::ImageRendering) const override; + virtual void paint(DisplayListRecordingContext&, Gfx::IntRect dst_rect, CSS::ImageRendering) const override; private: SVGDecodedImageData(GC::Ref, GC::Ref, GC::Ref, GC::Ref); diff --git a/Libraries/LibWeb/SVG/SVGImageElement.cpp b/Libraries/LibWeb/SVG/SVGImageElement.cpp index 20569dc229..efa65704a2 100644 --- a/Libraries/LibWeb/SVG/SVGImageElement.cpp +++ b/Libraries/LibWeb/SVG/SVGImageElement.cpp @@ -5,7 +5,6 @@ */ #include "SVGImageElement.h" -#include #include #include #include @@ -26,8 +25,6 @@ GC_DEFINE_ALLOCATOR(SVGImageElement); SVGImageElement::SVGImageElement(DOM::Document& document, DOM::QualifiedName qualified_name) : SVGGraphicsElement(document, move(qualified_name)) { - m_animation_timer = Core::Timer::create(); - m_animation_timer->on_timeout = [this] { animate(); }; } SVGImageElement::~SVGImageElement() = default; @@ -196,12 +193,6 @@ void SVGImageElement::fetch_the_document(URL::URL const& url) m_resource_request->add_callbacks( [this, resource_request = GC::Root { m_resource_request }] { m_load_event_delayer.clear(); - auto image_data = resource_request->image_data(); - 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(); - } register_with_decoded_image_data_if_needed(); set_needs_style_update(true); set_needs_layout_update(DOM::SetNeedsLayoutReason::SVGImageElementFetchTheDocument); @@ -226,31 +217,6 @@ RefPtr SVGImageElement::create_layout_node(CSS::ComputedProperties return make_ref_counted(document(), *this, style); } -void SVGImageElement::animate() -{ - auto image_data = m_resource_request->image_data(); - if (!image_data) { - return; - } - - m_current_frame_index = (m_current_frame_index + 1) % image_data->frame_count(); - auto current_frame_duration = image_data->frame_duration(m_current_frame_index); - - if (current_frame_duration != m_animation_timer->interval()) { - m_animation_timer->restart(current_frame_duration); - } - - if (m_current_frame_index == image_data->frame_count() - 1) { - ++m_loops_completed; - if (m_loops_completed > 0 && m_loops_completed == image_data->loop_count()) { - m_animation_timer->stop(); - } - } - - if (paintable()) - paintable()->set_needs_repaint(); -} - GC::Ptr SVGImageElement::decoded_image_data() const { if (!m_resource_request) diff --git a/Libraries/LibWeb/SVG/SVGImageElement.h b/Libraries/LibWeb/SVG/SVGImageElement.h index 63ad5b031d..824e9c1fad 100644 --- a/Libraries/LibWeb/SVG/SVGImageElement.h +++ b/Libraries/LibWeb/SVG/SVGImageElement.h @@ -38,7 +38,6 @@ public: Gfx::FloatRect bounding_box() const; // ^Layout::ImageProvider - virtual size_t current_frame_index() const override { return m_current_frame_index; } virtual GC::Ptr decoded_image_data() const override; protected: @@ -57,17 +56,11 @@ private: virtual RefPtr create_layout_node(CSS::ComputedProperties const&) override; virtual void decoded_image_data_did_update() override { set_needs_repaint(); } - void animate(); - GC::Ptr m_x; GC::Ptr m_y; GC::Ptr m_width; GC::Ptr m_height; - RefPtr m_animation_timer; - size_t m_current_frame_index { 0 }; - size_t m_loops_completed { 0 }; - Optional m_href; GC::Ptr m_resource_request; diff --git a/Tests/LibWeb/Screenshot/expected/animated-background-image-late-consumer-uses-first-consumer-timing.png b/Tests/LibWeb/Screenshot/expected/animated-background-image-late-consumer-uses-first-consumer-timing.png new file mode 100644 index 0000000000..689c22f986 Binary files /dev/null and b/Tests/LibWeb/Screenshot/expected/animated-background-image-late-consumer-uses-first-consumer-timing.png differ diff --git a/Tests/LibWeb/Screenshot/input/animated-background-image-late-consumer-uses-first-consumer-timing.html b/Tests/LibWeb/Screenshot/input/animated-background-image-late-consumer-uses-first-consumer-timing.html new file mode 100644 index 0000000000..4fd05fe3e4 --- /dev/null +++ b/Tests/LibWeb/Screenshot/input/animated-background-image-late-consumer-uses-first-consumer-timing.html @@ -0,0 +1,44 @@ + + + +
+
+
+ + + diff --git a/Tests/LibWeb/Screenshot/input/animated-background-image-repaints-on-advance.html b/Tests/LibWeb/Screenshot/input/animated-background-image-repaints-on-advance.html index 7d48fcfa7b..6ea09301db 100644 --- a/Tests/LibWeb/Screenshot/input/animated-background-image-repaints-on-advance.html +++ b/Tests/LibWeb/Screenshot/input/animated-background-image-repaints-on-advance.html @@ -13,27 +13,16 @@ }
+ + +
+ diff --git a/Tests/LibWeb/Text/input/css/animated-background-image-pauses-for-inactive-document.html b/Tests/LibWeb/Text/input/css/animated-background-image-pauses-for-inactive-document.html new file mode 100644 index 0000000000..dd7210ae56 --- /dev/null +++ b/Tests/LibWeb/Text/input/css/animated-background-image-pauses-for-inactive-document.html @@ -0,0 +1,52 @@ + + + diff --git a/Tests/LibWeb/Text/input/css/animated-background-image-shared-resource-count.html b/Tests/LibWeb/Text/input/css/animated-background-image-shared-resource-count.html new file mode 100644 index 0000000000..85abdb7e42 --- /dev/null +++ b/Tests/LibWeb/Text/input/css/animated-background-image-shared-resource-count.html @@ -0,0 +1,24 @@ + + + +
+
+ diff --git a/Tests/LibWeb/Text/input/css/animated-background-image-timer-count-is-document-scoped.html b/Tests/LibWeb/Text/input/css/animated-background-image-timer-count-is-document-scoped.html index 28969f02cf..06fcb8f30e 100644 --- a/Tests/LibWeb/Text/input/css/animated-background-image-timer-count-is-document-scoped.html +++ b/Tests/LibWeb/Text/input/css/animated-background-image-timer-count-is-document-scoped.html @@ -1,6 +1,8 @@ diff --git a/Tests/LibWeb/Text/input/css/animated-background-image-timer-stops-after-full-layout-tree-teardown.html b/Tests/LibWeb/Text/input/css/animated-background-image-timer-doesnt-stop-after-full-layout-tree-teardown.html similarity index 61% rename from Tests/LibWeb/Text/input/css/animated-background-image-timer-stops-after-full-layout-tree-teardown.html rename to Tests/LibWeb/Text/input/css/animated-background-image-timer-doesnt-stop-after-full-layout-tree-teardown.html index fc132b0d7a..0577c54a84 100644 --- a/Tests/LibWeb/Text/input/css/animated-background-image-timer-stops-after-full-layout-tree-teardown.html +++ b/Tests/LibWeb/Text/input/css/animated-background-image-timer-doesnt-stop-after-full-layout-tree-teardown.html @@ -10,19 +10,21 @@
Hello diff --git a/Tests/LibWeb/Text/input/css/animated-background-image-timer-stops-after-layout-node-replacement.html b/Tests/LibWeb/Text/input/css/animated-background-image-timer-doesnt-stop-after-layout-node-replacement.html similarity index 60% rename from Tests/LibWeb/Text/input/css/animated-background-image-timer-stops-after-layout-node-replacement.html rename to Tests/LibWeb/Text/input/css/animated-background-image-timer-doesnt-stop-after-layout-node-replacement.html index 002372f98c..be973f6ac6 100644 --- a/Tests/LibWeb/Text/input/css/animated-background-image-timer-stops-after-layout-node-replacement.html +++ b/Tests/LibWeb/Text/input/css/animated-background-image-timer-doesnt-stop-after-layout-node-replacement.html @@ -9,19 +9,21 @@
diff --git a/Tests/LibWeb/Text/input/css/animated-background-image-timer-stops-when-hidden.html b/Tests/LibWeb/Text/input/css/animated-background-image-timer-doesnt-stop-when-hidden.html similarity index 64% rename from Tests/LibWeb/Text/input/css/animated-background-image-timer-stops-when-hidden.html rename to Tests/LibWeb/Text/input/css/animated-background-image-timer-doesnt-stop-when-hidden.html index b14afd0679..fa338c6c67 100644 --- a/Tests/LibWeb/Text/input/css/animated-background-image-timer-stops-when-hidden.html +++ b/Tests/LibWeb/Text/input/css/animated-background-image-timer-doesnt-stop-when-hidden.html @@ -9,16 +9,18 @@
diff --git a/Tests/LibWeb/Text/input/css/animated-generated-content-image-timer-stops-when-hidden.html b/Tests/LibWeb/Text/input/css/animated-generated-content-image-timer-doesnt-stop-when-hidden.html similarity index 62% rename from Tests/LibWeb/Text/input/css/animated-generated-content-image-timer-stops-when-hidden.html rename to Tests/LibWeb/Text/input/css/animated-generated-content-image-timer-doesnt-stop-when-hidden.html index 07192312a0..d71f52ea4f 100644 --- a/Tests/LibWeb/Text/input/css/animated-generated-content-image-timer-stops-when-hidden.html +++ b/Tests/LibWeb/Text/input/css/animated-generated-content-image-timer-doesnt-stop-when-hidden.html @@ -7,16 +7,18 @@
diff --git a/Tests/LibWeb/Text/input/css/animated-img-restart-shared-resource.html b/Tests/LibWeb/Text/input/css/animated-img-restart-shared-resource.html new file mode 100644 index 0000000000..0730fd893d --- /dev/null +++ b/Tests/LibWeb/Text/input/css/animated-img-restart-shared-resource.html @@ -0,0 +1,28 @@ + + + + + diff --git a/Tests/LibWeb/Text/input/css/inline-relative-css-image-base-url.html b/Tests/LibWeb/Text/input/css/inline-relative-css-image-base-url.html index 9f100d9cd9..71aa817b10 100644 --- a/Tests/LibWeb/Text/input/css/inline-relative-css-image-base-url.html +++ b/Tests/LibWeb/Text/input/css/inline-relative-css-image-base-url.html @@ -9,11 +9,13 @@ " >