2023-03-24 15:17:11 +00:00
|
|
|
/*
|
2024-10-04 13:19:50 +02:00
|
|
|
* Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
|
2023-03-24 15:17:11 +00:00
|
|
|
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
|
2025-04-10 16:04:34 +01:00
|
|
|
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
|
2023-03-24 15:17:11 +00:00
|
|
|
* Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
2026-05-22 19:04:46 +02:00
|
|
|
#include <AK/AnyOf.h>
|
2026-06-06 13:32:47 +02:00
|
|
|
#include <LibGC/Function.h>
|
|
|
|
|
#include <LibGC/Weak.h>
|
2026-05-05 13:52:33 +02:00
|
|
|
#include <LibGfx/DecodedImageFrame.h>
|
2026-02-11 07:33:58 +01:00
|
|
|
#include <LibWeb/CSS/CSSStyleSheet.h>
|
2023-05-08 07:51:03 +02:00
|
|
|
#include <LibWeb/CSS/ComputedValues.h>
|
2025-04-10 16:04:34 +01:00
|
|
|
#include <LibWeb/CSS/Fetch.h>
|
|
|
|
|
#include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
|
2023-03-24 15:17:11 +00:00
|
|
|
#include <LibWeb/DOM/Document.h>
|
2025-08-08 09:55:38 -04:00
|
|
|
#include <LibWeb/DOMURL/DOMURL.h>
|
2023-06-11 15:37:36 +02:00
|
|
|
#include <LibWeb/HTML/DecodedImageData.h>
|
|
|
|
|
#include <LibWeb/HTML/PotentialCORSRequest.h>
|
2026-02-11 07:33:58 +01:00
|
|
|
#include <LibWeb/HTML/Scripting/Environments.h>
|
2025-04-10 15:52:33 +01:00
|
|
|
#include <LibWeb/HTML/SharedResourceRequest.h>
|
2024-06-23 18:40:10 +02:00
|
|
|
#include <LibWeb/Painting/DisplayListRecorder.h>
|
2025-07-31 23:07:26 +02:00
|
|
|
#include <LibWeb/Painting/DisplayListRecordingContext.h>
|
2023-03-24 15:17:11 +00:00
|
|
|
#include <LibWeb/Platform/Timer.h>
|
|
|
|
|
|
|
|
|
|
namespace Web::CSS {
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
ImageStyleValueResource::ImageStyleValueResource(::URL::URL url)
|
|
|
|
|
: m_url(move(url))
|
2026-06-04 10:40:32 +02:00
|
|
|
{
|
|
|
|
|
}
|
2026-05-22 19:04:46 +02:00
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
ImageStyleValueResource::~ImageStyleValueResource()
|
2025-04-10 16:04:34 +01:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
stop_animation_timer();
|
2025-04-10 16:04:34 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
void ImageStyleValueResource::visit_edges(JS::Cell::Visitor& visitor)
|
2025-04-10 16:04:34 +01:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
visitor.visit(m_resource_request);
|
|
|
|
|
visitor.visit(m_timer);
|
2025-04-10 16:04:34 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
void ImageStyleValueResource::set_resource_request(DOM::Document& document, GC::Ref<HTML::SharedResourceRequest> resource_request)
|
2023-03-24 15:17:11 +00:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
if (m_resource_request.ptr() != resource_request.ptr()) {
|
|
|
|
|
m_resource_request = resource_request;
|
|
|
|
|
m_has_resource_request_callbacks = false;
|
|
|
|
|
m_current_frame_index = 0;
|
|
|
|
|
m_loops_completed = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
add_callbacks_if_needed(document);
|
2023-03-24 15:17:11 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
void ImageStyleValueResource::register_image_style_value(DOM::Document& document, ImageStyleValue const& image_style_value)
|
|
|
|
|
{
|
|
|
|
|
m_image_style_values.set(&image_style_value);
|
|
|
|
|
add_callbacks_if_needed(document);
|
|
|
|
|
start_animation_timer_if_needed(document);
|
|
|
|
|
}
|
2024-06-16 10:38:35 +02:00
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
void ImageStyleValueResource::unregister_image_style_value(ImageStyleValue const& image_style_value)
|
2026-05-22 19:04:46 +02:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
m_image_style_values.remove(&image_style_value);
|
|
|
|
|
if (m_image_style_values.is_empty())
|
|
|
|
|
stop_animation_timer();
|
2026-05-22 19:04:46 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
GC::Ptr<HTML::DecodedImageData> ImageStyleValueResource::image_data() const
|
2024-10-30 21:37:08 +13:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
if (!m_resource_request)
|
|
|
|
|
return nullptr;
|
|
|
|
|
return m_resource_request->image_data();
|
2024-10-30 21:37:08 +13:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
Optional<Gfx::DecodedImageFrame> ImageStyleValueResource::frame(size_t frame_index, Gfx::IntSize size) const
|
2023-03-24 15:17:11 +00:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
if (auto image_data = this->image_data())
|
|
|
|
|
return image_data->frame(frame_index, size);
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ImageStyleValueResource::has_active_animation_timer() const
|
|
|
|
|
{
|
|
|
|
|
return m_timer && m_timer->is_active();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageStyleValueResource::add_callbacks_if_needed(DOM::Document& document)
|
|
|
|
|
{
|
|
|
|
|
if (!m_resource_request || m_has_resource_request_callbacks)
|
2023-03-24 15:17:11 +00:00
|
|
|
return;
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
m_has_resource_request_callbacks = true;
|
|
|
|
|
m_resource_request->add_callbacks(
|
|
|
|
|
[weak_document = GC::Weak(document), url = m_url] {
|
|
|
|
|
if (auto document = weak_document.ptr()) {
|
|
|
|
|
if (auto* resource = document->css_image_resource(url))
|
|
|
|
|
resource->notify_image_style_values_did_update(*document);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
nullptr);
|
|
|
|
|
}
|
2025-12-28 12:12:59 +01:00
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
void ImageStyleValueResource::notify_image_style_values_did_update(DOM::Document& document)
|
|
|
|
|
{
|
|
|
|
|
for (auto const* image_style_value : m_image_style_values)
|
|
|
|
|
image_style_value->notify_clients_did_update();
|
|
|
|
|
|
|
|
|
|
start_animation_timer_if_needed(document);
|
2023-03-24 15:17:11 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
void ImageStyleValueResource::start_animation_timer_if_needed(DOM::Document& document)
|
2026-05-22 19:04:46 +02:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
if (m_image_style_values.is_empty() || !is_animatable())
|
2026-05-22 19:04:46 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (m_timer && m_timer->is_active())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!m_timer) {
|
2026-05-25 04:29:10 +02:00
|
|
|
auto timer = Platform::Timer::create(document.heap());
|
|
|
|
|
m_timer = timer;
|
2026-06-06 13:32:47 +02:00
|
|
|
timer->on_timeout = GC::create_function(document.heap(), [weak_document = GC::Weak(document), url = m_url] {
|
|
|
|
|
if (auto document = weak_document.ptr())
|
|
|
|
|
document->animate_css_image_resource(url);
|
2026-05-22 19:04:46 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_timer->set_interval(current_frame_duration());
|
|
|
|
|
m_timer->start();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
void ImageStyleValueResource::stop_animation_timer()
|
2026-05-22 19:04:46 +02:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
if (m_timer && m_timer->is_active())
|
2026-05-22 19:04:46 +02:00
|
|
|
m_timer->stop();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
bool ImageStyleValueResource::is_animatable() const
|
2026-05-22 19:04:46 +02:00
|
|
|
{
|
|
|
|
|
auto image_data = this->image_data();
|
|
|
|
|
if (!image_data || !image_data->is_animated() || image_data->frame_count() <= 1)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return !animation_has_completed();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
bool ImageStyleValueResource::animation_has_completed() const
|
2026-05-22 19:04:46 +02:00
|
|
|
{
|
|
|
|
|
auto image_data = this->image_data();
|
|
|
|
|
return image_data && image_data->loop_count() > 0 && m_loops_completed == image_data->loop_count();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
int ImageStyleValueResource::current_frame_duration() const
|
2026-05-22 19:04:46 +02:00
|
|
|
{
|
|
|
|
|
auto image_data = this->image_data();
|
|
|
|
|
if (!image_data)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
return image_data->frame_duration(m_current_frame_index);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
void ImageStyleValueResource::animate(DOM::Document&)
|
2023-03-24 15:17:11 +00:00
|
|
|
{
|
2024-08-03 15:27:08 +12:00
|
|
|
if (!m_resource_request)
|
2023-06-11 15:37:36 +02:00
|
|
|
return;
|
2024-08-03 15:27:08 +12:00
|
|
|
auto image_data = m_resource_request->image_data();
|
2023-06-11 15:37:36 +02:00
|
|
|
if (!image_data)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_current_frame_index = (m_current_frame_index + 1) % image_data->frame_count();
|
2026-02-13 15:04:08 +01:00
|
|
|
m_current_frame_index = image_data->notify_frame_advanced(m_current_frame_index);
|
2023-06-11 15:37:36 +02:00
|
|
|
auto current_frame_duration = image_data->frame_duration(m_current_frame_index);
|
2023-03-24 15:17:11 +00:00
|
|
|
|
2026-05-22 19:04:46 +02:00
|
|
|
if (m_timer && current_frame_duration != m_timer->interval())
|
2023-03-24 15:17:11 +00:00
|
|
|
m_timer->restart(current_frame_duration);
|
|
|
|
|
|
2023-06-11 15:37:36 +02:00
|
|
|
if (m_current_frame_index == image_data->frame_count() - 1) {
|
2023-03-24 15:17:11 +00:00
|
|
|
++m_loops_completed;
|
2026-05-22 19:04:46 +02:00
|
|
|
if (animation_has_completed())
|
|
|
|
|
stop_animation_timer();
|
2023-03-24 15:17:11 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
for (auto const* image_style_value : m_image_style_values)
|
|
|
|
|
image_style_value->notify_did_animate();
|
2023-03-24 15:17:11 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
ValueComparingNonnullRefPtr<ImageStyleValue const> ImageStyleValue::create(URL const& url)
|
2023-03-24 15:17:11 +00:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
return adopt_ref(*new (nothrow) ImageStyleValue(url));
|
2023-06-11 15:37:36 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
ValueComparingNonnullRefPtr<ImageStyleValue const> ImageStyleValue::create(URL const& url, Optional<::URL::URL> style_resource_base_url)
|
2023-06-11 15:37:36 +02:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
return adopt_ref(*new (nothrow) ImageStyleValue(url, move(style_resource_base_url)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ValueComparingNonnullRefPtr<ImageStyleValue const> ImageStyleValue::create(::URL::URL const& url)
|
|
|
|
|
{
|
|
|
|
|
return adopt_ref(*new (nothrow) ImageStyleValue(URL { url.to_string() }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImageStyleValue::ImageStyleValue(URL const& url, Optional<::URL::URL> style_resource_base_url)
|
|
|
|
|
: AbstractImageStyleValue(Type::Image)
|
|
|
|
|
, m_url(url)
|
|
|
|
|
, m_style_resource_base_url(move(style_resource_base_url))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImageStyleValue::~ImageStyleValue() = default;
|
|
|
|
|
|
|
|
|
|
u64 ImageStyleValue::active_animation_timer_count(DOM::Document const& document)
|
|
|
|
|
{
|
|
|
|
|
return document.active_css_image_animation_timer_count();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageStyleValue::load_any_resources(DOM::Document& document)
|
|
|
|
|
{
|
|
|
|
|
RuleOrDeclaration rule_or_declaration {
|
|
|
|
|
.environment_settings_object = document.relevant_settings_object(),
|
|
|
|
|
.value = RuleOrDeclaration::Rule {},
|
|
|
|
|
.style_resource_base_url = m_style_resource_base_url,
|
|
|
|
|
.parent_style_sheet_origin_clean = m_parent_style_sheet_origin_clean,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto resource_request = fetch_an_external_image_for_a_stylesheet(m_url, rule_or_declaration, document);
|
|
|
|
|
if (!resource_request)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (auto* resource = document.css_image_resource(resource_request->url()))
|
|
|
|
|
resource->set_resource_request(document, *resource_request);
|
2026-05-05 13:52:33 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-08 12:02:18 +00:00
|
|
|
void ImageStyleValue::serialize(StringBuilder& builder, SerializationMode) const
|
2023-03-24 15:17:11 +00:00
|
|
|
{
|
2026-01-08 12:02:18 +00:00
|
|
|
builder.append(m_url.to_string());
|
2023-03-24 15:17:11 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-08 10:11:51 +01:00
|
|
|
bool ImageStyleValue::equals(StyleValue const& other) const
|
2023-03-24 15:17:11 +00:00
|
|
|
{
|
|
|
|
|
if (type() != other.type())
|
|
|
|
|
return false;
|
|
|
|
|
return m_url == other.as_image().m_url;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
Optional<CSSPixels> ImageStyleValue::natural_width(DOM::Document const& document) const
|
2023-03-24 15:17:11 +00:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
if (auto image_data = this->image_data(document))
|
2023-06-11 15:37:36 +02:00
|
|
|
return image_data->intrinsic_width();
|
2023-03-24 15:17:11 +00:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
Optional<CSSPixels> ImageStyleValue::natural_height(DOM::Document const& document) const
|
2023-03-24 15:17:11 +00:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
if (auto image_data = this->image_data(document))
|
2023-06-11 15:37:36 +02:00
|
|
|
return image_data->intrinsic_height();
|
2023-03-24 15:17:11 +00:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
Optional<CSSPixelFraction> ImageStyleValue::natural_aspect_ratio(DOM::Document const& document) const
|
2023-12-27 23:51:00 +01:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
if (auto image_data = this->image_data(document))
|
2023-12-27 23:51:00 +01:00
|
|
|
return image_data->intrinsic_aspect_ratio();
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
bool ImageStyleValue::is_paintable(DOM::Document const& document) const
|
2023-03-24 15:17:11 +00:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
return image_data(document);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageStyleValue::paint(DisplayListRecordingContext& context, DOM::Document const& document, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering) const
|
|
|
|
|
{
|
|
|
|
|
auto image_data = this->image_data(document);
|
2025-11-04 21:22:32 +01:00
|
|
|
if (!image_data)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
auto current_frame_index = this->current_frame_index(document);
|
2025-11-04 21:22:32 +01:00
|
|
|
auto dest_int_rect = dest_rect.to_type<int>();
|
2026-06-06 13:32:47 +02:00
|
|
|
auto rect = image_data->frame_rect(current_frame_index).value_or(dest_int_rect);
|
2025-11-12 09:37:53 +01:00
|
|
|
auto scaling_mode = to_gfx_scaling_mode(image_rendering, rect.size(), dest_int_rect.size());
|
2026-06-06 13:32:47 +02:00
|
|
|
image_data->paint(context, current_frame_index, dest_int_rect, scaling_mode);
|
2023-03-24 15:17:11 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
Optional<Gfx::DecodedImageFrame> ImageStyleValue::current_frame(DOM::Document const& document, DevicePixelRect const& dest_rect) const
|
2026-05-05 13:52:33 +02:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
return frame(document, current_frame_index(document), dest_rect.size().to_type<int>());
|
2026-05-05 13:52:33 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
size_t ImageStyleValue::current_frame_index(DOM::Document const& document) const
|
2023-06-11 15:37:36 +02:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
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<HTML::DecodedImageData> ImageStyleValue::image_data(DOM::Document const& document) const
|
|
|
|
|
{
|
|
|
|
|
auto resolved_url = this->resolved_url(document);
|
|
|
|
|
if (!resolved_url.has_value())
|
2023-06-11 15:37:36 +02:00
|
|
|
return nullptr;
|
2026-06-06 13:32:47 +02:00
|
|
|
|
|
|
|
|
if (auto const* resource = document.css_image_resource(*resolved_url)) {
|
|
|
|
|
if (auto image_data = resource->image_data())
|
|
|
|
|
return image_data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto const& shared_resource_requests = document.shared_resource_requests();
|
|
|
|
|
auto it = shared_resource_requests.find(*resolved_url);
|
|
|
|
|
if (it == shared_resource_requests.end())
|
|
|
|
|
return nullptr;
|
|
|
|
|
return it->value->image_data();
|
2023-06-11 15:37:36 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
Optional<Gfx::Color> ImageStyleValue::color_if_single_pixel_bitmap(DOM::Document const& document) const
|
2024-01-01 13:48:53 +01:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
if (auto decoded_frame = frame(document, current_frame_index(document)); decoded_frame.has_value()) {
|
2026-05-05 13:52:33 +02:00
|
|
|
auto const& bitmap = decoded_frame->bitmap();
|
|
|
|
|
if (bitmap.width() == 1 && bitmap.height() == 1)
|
|
|
|
|
return bitmap.get_pixel(0, 0);
|
2024-01-01 13:48:53 +01:00
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-10 16:04:34 +01:00
|
|
|
void ImageStyleValue::set_style_sheet(GC::Ptr<CSSStyleSheet> style_sheet)
|
|
|
|
|
{
|
|
|
|
|
Base::set_style_sheet(style_sheet);
|
2026-03-19 11:22:18 +01:00
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
m_style_resource_base_url.clear();
|
|
|
|
|
m_parent_style_sheet_origin_clean.clear();
|
|
|
|
|
m_should_absolutize_url_for_computed_value = false;
|
|
|
|
|
|
|
|
|
|
if (style_sheet) {
|
|
|
|
|
update_style_sheet_resource_context(*style_sheet);
|
|
|
|
|
style_sheet->register_pending_image_value(*this);
|
|
|
|
|
}
|
2025-04-10 16:04:34 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
void ImageStyleValue::update_style_sheet_resource_context(CSSStyleSheet const& style_sheet)
|
|
|
|
|
{
|
|
|
|
|
m_style_resource_base_url = style_sheet.base_url()
|
|
|
|
|
.value_or_lazy_evaluated_optional([&]() { return style_sheet.location(); })
|
|
|
|
|
.value_or_lazy_evaluated_optional([&]() { return HTML::relevant_settings_object(style_sheet).api_base_url(); });
|
|
|
|
|
m_parent_style_sheet_origin_clean = style_sheet.is_origin_clean();
|
|
|
|
|
m_should_absolutize_url_for_computed_value = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ValueComparingNonnullRefPtr<StyleValue const> ImageStyleValue::absolutized(ComputationContext const& context) const
|
2025-06-03 07:22:28 -04:00
|
|
|
{
|
|
|
|
|
if (m_url.url().is_empty())
|
|
|
|
|
return *this;
|
|
|
|
|
|
|
|
|
|
// FIXME: The spec has been updated to handle this better. The computation of the base URL here is roughly based on:
|
|
|
|
|
// https://drafts.csswg.org/css-values-4/#style-resource-base-url
|
|
|
|
|
// https://github.com/w3c/csswg-drafts/pull/12261
|
2026-06-06 13:32:47 +02:00
|
|
|
auto base_url = m_style_resource_base_url;
|
|
|
|
|
if (!base_url.has_value() && context.abstract_element.has_value())
|
|
|
|
|
base_url = context.abstract_element->document().base_url();
|
2025-06-03 07:22:28 -04:00
|
|
|
|
|
|
|
|
if (base_url.has_value()) {
|
2026-06-06 13:32:47 +02:00
|
|
|
if (m_should_absolutize_url_for_computed_value) {
|
|
|
|
|
if (DOMURL::parse(m_url.url()).has_value()) {
|
|
|
|
|
auto absolutized_image = adopt_ref(*new (nothrow) ImageStyleValue(m_url, *base_url));
|
|
|
|
|
absolutized_image->m_parent_style_sheet_origin_clean = m_parent_style_sheet_origin_clean;
|
|
|
|
|
absolutized_image->m_should_absolutize_url_for_computed_value = true;
|
|
|
|
|
return absolutized_image;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (auto resolved_url = DOMURL::parse(m_url.url(), *base_url); resolved_url.has_value()) {
|
|
|
|
|
auto absolutized_image = adopt_ref(*new (nothrow) ImageStyleValue(URL { resolved_url->to_string(), m_url.type(), m_url.request_url_modifiers() }, *base_url));
|
|
|
|
|
absolutized_image->m_parent_style_sheet_origin_clean = m_parent_style_sheet_origin_clean;
|
|
|
|
|
absolutized_image->m_should_absolutize_url_for_computed_value = true;
|
|
|
|
|
return absolutized_image;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto absolutized_image = adopt_ref(*new (nothrow) ImageStyleValue(m_url, *base_url));
|
|
|
|
|
absolutized_image->m_parent_style_sheet_origin_clean = m_parent_style_sheet_origin_clean;
|
|
|
|
|
return absolutized_image;
|
2025-06-03 07:22:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 16:07:55 +01:00
|
|
|
void ImageStyleValue::register_client(Client& client) const
|
2025-07-27 15:55:16 +02:00
|
|
|
{
|
|
|
|
|
auto result = m_clients.set(&client);
|
|
|
|
|
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
|
2026-06-06 13:32:47 +02:00
|
|
|
auto document = client.document();
|
|
|
|
|
if (!document)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto resolved_url = this->resolved_url(*document);
|
|
|
|
|
if (!resolved_url.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
client.m_registered_url = *resolved_url;
|
|
|
|
|
auto& resource = document->ensure_css_image_resource(*resolved_url);
|
|
|
|
|
auto& shared_resource_requests = document->shared_resource_requests();
|
|
|
|
|
auto it = shared_resource_requests.find(*resolved_url);
|
|
|
|
|
if (it != shared_resource_requests.end())
|
|
|
|
|
resource.set_resource_request(*document, *it->value);
|
|
|
|
|
resource.register_image_style_value(*document, *this);
|
2025-07-27 15:55:16 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 16:07:55 +01:00
|
|
|
void ImageStyleValue::unregister_client(Client& client) const
|
2025-07-27 15:55:16 +02:00
|
|
|
{
|
2026-06-06 13:32:47 +02:00
|
|
|
auto document = client.document();
|
|
|
|
|
auto registered_url = move(client.m_registered_url);
|
|
|
|
|
client.m_registered_url.clear();
|
|
|
|
|
|
2025-07-27 15:55:16 +02:00
|
|
|
auto did_remove = m_clients.remove(&client);
|
|
|
|
|
VERIFY(did_remove);
|
2026-05-22 19:04:46 +02:00
|
|
|
|
2026-06-06 13:32:47 +02:00
|
|
|
if (!document || !registered_url.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (any_of(m_clients, [&](auto const* remaining_client) {
|
|
|
|
|
return remaining_client->document() == document
|
|
|
|
|
&& remaining_client->m_registered_url.has_value()
|
|
|
|
|
&& *remaining_client->m_registered_url == *registered_url;
|
|
|
|
|
}))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (auto* resource = document->css_image_resource(*registered_url))
|
|
|
|
|
resource->unregister_image_style_value(*this);
|
|
|
|
|
document->remove_css_image_resource_if_unused(*registered_url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageStyleValue::notify_clients_did_update() const
|
|
|
|
|
{
|
|
|
|
|
for (auto* client : m_clients)
|
|
|
|
|
client->image_style_value_did_update(const_cast<ImageStyleValue&>(*this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageStyleValue::notify_did_animate() const
|
|
|
|
|
{
|
|
|
|
|
if (on_animate)
|
|
|
|
|
on_animate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Optional<::URL::URL> ImageStyleValue::resolved_url(DOM::Document const& document) const
|
|
|
|
|
{
|
|
|
|
|
if (m_url.url().is_empty())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
return DOMURL::parse(m_url.url(), style_resource_base_url(document));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
::URL::URL ImageStyleValue::style_resource_base_url(DOM::Document const& document) const
|
|
|
|
|
{
|
|
|
|
|
return m_style_resource_base_url.value_or_lazy_evaluated([&] {
|
|
|
|
|
return document.relevant_settings_object().api_base_url();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Optional<Gfx::DecodedImageFrame> ImageStyleValue::frame(DOM::Document const& document, size_t frame_index, Gfx::IntSize size) const
|
|
|
|
|
{
|
|
|
|
|
auto resolved_url = this->resolved_url(document);
|
|
|
|
|
if (resolved_url.has_value()) {
|
|
|
|
|
if (auto const* resource = document.css_image_resource(*resolved_url))
|
|
|
|
|
return resource->frame(frame_index, size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (auto image_data = this->image_data(document))
|
|
|
|
|
return image_data->frame(frame_index, size);
|
|
|
|
|
return {};
|
2025-07-27 15:55:16 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-22 19:04:46 +02:00
|
|
|
ImageStyleValue::Client::Client(DOM::Document& document, ImageStyleValue const& image_style_value)
|
2025-07-27 15:55:16 +02:00
|
|
|
: m_image_style_value(image_style_value)
|
2026-05-22 19:04:46 +02:00
|
|
|
, m_document(document)
|
2025-07-27 15:55:16 +02:00
|
|
|
{
|
|
|
|
|
m_image_style_value.register_client(*this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImageStyleValue::Client::~Client()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageStyleValue::Client::image_style_value_finalize()
|
|
|
|
|
{
|
|
|
|
|
m_image_style_value.unregister_client(*this);
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-24 15:17:11 +00:00
|
|
|
}
|