ladybird/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.h
Callum Law 8ebdaeab69 LibWeb: Transfer animation ownership to AnimatedBitmapDecodedImageData
Previously animation ownership was a messy split between
`AnimatedBitmapDecodedImageData` and the consumers (i.e.
`ImageStyleValueResource`, `HTMLImageElement`, and `SVGImageElement`)
with `AnimatedBitmapDecodedImageData` owning the frames and a current
frame index, and the consumers owning the rest of the state (e.g. loop
count, timers to drive the animation forward, their own current index).

This had a couple of main issues:
 - While `AnimatedDecodedImageData` partially synchronized animations by
   dropping unexpected advancement notifications, this didn't apply to
   other animation state which meant, for instance, that a later started
   consumer could drive the animation of an earlier one past the max
   loop count (albeit without invalidating the earlier consumer).
 - Multiple consumers didn't share frame timings, meaning animations
   could be up to a full frame out of sync visually.
 - Animations were paused depending on whether there were any consumers,
   this is different to the behavior in other browsers (where they
   continue regardless of whether there are any consumers).
 - It was an overgeneralization of how animations need to work - only
   `AnimatedBitmapDecodedImageData` works with an indexed frame model,
   with animated SVGs (although not yet implemented) relying on their
   internal event loop to be driven forward.

Given the above the new approach implemented in this commit is:
 - The API for `DecodedImageData` is animation system agnostic, only
   exposing `default_frame`, `current_frame`, and `restart_animation`
   methods not reliant on providing a specific frame index.
 - `AnimatedBitmapDecodedImageData` owns its own timer, loop count,
   etc. The animation starts when the first consumer registers and ends
   when the document is hidden or becomes inactive (or completes in the
   case of finite animations).
 - Consumers are invalidated by `AnimatedBitmapDecodedImageData` when
   required.

Tests have been added for:
 - Animations being paused when the document becomes inactive and
   restarted when it becomes active again.
 - Frame timings being synchronized across consumers.
 - Restarts triggered by `HTMLImageElement` applying to all consumers.
 - Processing ending once a non-infinite animation plays to completion.

The tests to ensure animations are cancelled when consumers are removed
(e.g. `animated-background-image-timer-stops-when-hidden.html`) have
been updated to assert the inverse since animation state is now per
resource not per consumer.
2026-06-18 10:44:25 +02:00

125 lines
4.6 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/HashTable.h>
#include <AK/Optional.h>
#include <LibGC/Weak.h>
#include <LibGfx/DecodedImageFrame.h>
#include <LibGfx/Forward.h>
#include <LibJS/Heap/Cell.h>
#include <LibURL/URL.h>
#include <LibWeb/CSS/StyleValues/AbstractImageStyleValue.h>
#include <LibWeb/CSS/URL.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/DecodedImageData.h>
namespace Web::CSS {
class ImageStyleValue;
class ImageStyleValueResource final : public HTML::DecodedImageData::Client {
public:
explicit ImageStyleValueResource(GC::Ref<HTML::SharedResourceRequest>, GC::Ref<DOM::Document> const&);
~ImageStyleValueResource();
void visit_edges(JS::Cell::Visitor&);
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<HTML::DecodedImageData> decoded_image_data() const override;
private:
virtual void decoded_image_data_did_update() override { notify_image_style_values_did_update(); }
void on_decoded_image_data_loaded();
void notify_image_style_values_did_update();
GC::Ref<HTML::SharedResourceRequest> m_resource_request;
HashTable<ImageStyleValue const*> m_image_style_values;
};
class ImageStyleValue final
: public AbstractImageStyleValue
, public Weakable<ImageStyleValue> {
using Base = AbstractImageStyleValue;
public:
class Client {
friend class ImageStyleValue;
public:
Client(DOM::Document&, ImageStyleValue const&);
virtual ~Client();
virtual void image_style_value_did_update(ImageStyleValue&) = 0;
protected:
void image_style_value_finalize();
GC::Ptr<DOM::Document> document() const { return m_document.ptr(); }
ImageStyleValue const& m_image_style_value;
GC::Weak<DOM::Document> m_document;
Optional<::URL::URL> m_registered_url;
};
static ValueComparingNonnullRefPtr<ImageStyleValue const> create(URL const&);
static ValueComparingNonnullRefPtr<ImageStyleValue const> create(URL const&, Optional<::URL::URL> style_resource_base_url);
static ValueComparingNonnullRefPtr<ImageStyleValue const> create(::URL::URL const&);
virtual ~ImageStyleValue() override;
virtual void serialize(StringBuilder&, SerializationMode) const override;
virtual bool equals(StyleValue const& other) const override;
virtual bool is_computationally_independent() const override { return true; }
virtual void load_any_resources(DOM::Document&) override;
Optional<CSSPixels> natural_width(DOM::Document const&) const override;
Optional<CSSPixels> natural_height(DOM::Document const&) const override;
Optional<CSSPixelFraction> natural_aspect_ratio(DOM::Document const&) const override;
virtual bool is_paintable(DOM::Document const&) const override;
void paint(DisplayListRecordingContext& context, DOM::Document const&, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering) const override;
virtual Optional<Gfx::Color> color_if_single_pixel_bitmap(DOM::Document const&) const override;
Optional<Gfx::DecodedImageFrame> current_frame(DOM::Document const&, DevicePixelRect const& dest_rect = {}) const;
GC::Ptr<HTML::DecodedImageData> image_data(DOM::Document const&) const;
private:
friend class ImageStyleValueResource;
friend class Client;
friend class CSSStyleSheet;
ImageStyleValue(URL const&, Optional<::URL::URL> style_resource_base_url = {});
void register_client(Client&) const;
void unregister_client(Client&) const;
void notify_clients_did_update() const;
void update_style_sheet_resource_context(CSSStyleSheet const&);
GC::Ptr<HTML::SharedResourceRequest> fetch_image(DOM::Document&) const;
Optional<::URL::URL> resolved_url(DOM::Document const&) const;
::URL::URL style_resource_base_url(DOM::Document const&) const;
virtual void set_style_sheet(GC::Ptr<CSSStyleSheet>) override;
virtual ValueComparingNonnullRefPtr<StyleValue const> absolutized(ComputationContext const&) const override;
URL m_url;
Optional<::URL::URL> m_style_resource_base_url;
Optional<bool> m_parent_style_sheet_origin_clean;
bool m_should_absolutize_url_for_computed_value { false };
mutable HashTable<Client*> m_clients;
};
}