mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-06-19 00:01:59 +00:00
ImageStyleValue stored GC-managed request, stylesheet, and animation timer references as strong GC::Ptr fields even though image style values are refcounted objects. When such a style value outlives the GC object that normally visits it, those fields can keep stale pointers after GC collects the referents. On Steam this allowed a stale image resource request to be read as unrelated image data, making carousel SVG arrows render at the wrong size. Store these back references as GC::Weak instead. Reachable style values still use live requests, stylesheets, and timers normally, but detached values observe null after the GC collects the referent and can reload or skip the now-dead association instead of dereferencing reused GC memory. Keep a local timer handle while installing the timeout callback so setup does not rely on the weak member. With the image style values no longer hiding strong GC edges, remove the obsolete IGNORE_GC annotation from CSSStyleSheet's pending image list.
215 lines
7 KiB
C++
215 lines
7 KiB
C++
/*
|
|
* Copyright (c) 2026-present, the Ladybird developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibWeb/CSS/CSSStyleSheet.h>
|
|
#include <LibWeb/CSS/Resolution.h>
|
|
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
|
|
#include <LibWeb/CSS/StyleValues/ImageSetStyleValue.h>
|
|
#include <LibWeb/CSS/StyleValues/ResolutionStyleValue.h>
|
|
#include <LibWeb/DOM/AbstractElement.h>
|
|
#include <LibWeb/DOM/Document.h>
|
|
#include <LibWeb/DOM/Element.h>
|
|
#include <LibWeb/HTML/SupportedImageTypes.h>
|
|
#include <LibWeb/Layout/Node.h>
|
|
#include <LibWeb/Page/Page.h>
|
|
#include <LibWeb/Painting/DisplayListRecordingContext.h>
|
|
|
|
namespace Web::CSS {
|
|
|
|
ValueComparingNonnullRefPtr<ImageSetStyleValue const> ImageSetStyleValue::create(Vector<Option> options)
|
|
{
|
|
return adopt_ref(*new (nothrow) ImageSetStyleValue(move(options)));
|
|
}
|
|
|
|
ImageSetStyleValue::ImageSetStyleValue(Vector<Option> options)
|
|
: AbstractImageStyleValue(Type::ImageSet)
|
|
, m_options(move(options))
|
|
{
|
|
}
|
|
|
|
AbstractImageStyleValue const* ImageSetStyleValue::select_image(double device_pixels_per_css_pixel) const
|
|
{
|
|
ImageSetStyleValue::Option const* best_below_or_equal = nullptr;
|
|
Optional<double> best_below_or_equal_resolution;
|
|
ImageSetStyleValue::Option const* best_above = nullptr;
|
|
Optional<double> best_above_resolution;
|
|
|
|
for (auto const& option : m_options) {
|
|
if (option.type.has_value() && !HTML::is_supported_image_type(*option.type))
|
|
continue;
|
|
|
|
auto resolution = Resolution::from_style_value(option.resolution).to_dots_per_pixel();
|
|
|
|
if (resolution >= device_pixels_per_css_pixel) {
|
|
if (!best_above_resolution.has_value() || resolution < *best_above_resolution) {
|
|
best_above = &option;
|
|
best_above_resolution = resolution;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!best_below_or_equal_resolution.has_value() || resolution > *best_below_or_equal_resolution) {
|
|
best_below_or_equal = &option;
|
|
best_below_or_equal_resolution = resolution;
|
|
}
|
|
}
|
|
|
|
if (best_above)
|
|
return best_above->image.ptr();
|
|
if (best_below_or_equal)
|
|
return best_below_or_equal->image.ptr();
|
|
return nullptr;
|
|
}
|
|
|
|
void ImageSetStyleValue::visit_edges(JS::Cell::Visitor& visitor) const
|
|
{
|
|
Base::visit_edges(visitor);
|
|
visitor.visit(m_style_sheet);
|
|
for (auto const& option : m_options)
|
|
option.image->visit_edges(visitor);
|
|
}
|
|
|
|
void ImageSetStyleValue::serialize(StringBuilder& builder, SerializationMode mode) const
|
|
{
|
|
builder.append("image-set("sv);
|
|
for (size_t i = 0; i < m_options.size(); ++i) {
|
|
if (i > 0)
|
|
builder.append(", "sv);
|
|
auto const& option = m_options[i];
|
|
option.image->serialize(builder, mode);
|
|
builder.append(' ');
|
|
option.resolution->serialize(builder, mode);
|
|
if (option.type.has_value()) {
|
|
builder.append(" type(\""sv);
|
|
builder.append_escaped_for_json(*option.type);
|
|
builder.append("\")"sv);
|
|
}
|
|
}
|
|
builder.append(')');
|
|
}
|
|
|
|
bool ImageSetStyleValue::equals(StyleValue const& other) const
|
|
{
|
|
if (type() != other.type())
|
|
return false;
|
|
auto const& other_image_set = other.as_image_set();
|
|
if (m_options.size() != other_image_set.m_options.size())
|
|
return false;
|
|
|
|
for (size_t i = 0; i < m_options.size(); ++i) {
|
|
auto const& option = m_options[i];
|
|
auto const& other_option = other_image_set.m_options[i];
|
|
if (!option.image->equals(*other_option.image))
|
|
return false;
|
|
if (!option.resolution->equals(*other_option.resolution))
|
|
return false;
|
|
if (option.type != other_option.type)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ImageSetStyleValue::is_computationally_independent() const
|
|
{
|
|
for (auto const& option : m_options) {
|
|
if (!option.image->is_computationally_independent())
|
|
return false;
|
|
if (!option.resolution->is_computationally_independent())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ImageSetStyleValue::load_any_resources(DOM::Document& document)
|
|
{
|
|
auto dpr = document.page().client().device_pixels_per_css_pixel();
|
|
if (auto const* image = select_image(dpr); image && image != m_selected_image) {
|
|
const_cast<AbstractImageStyleValue&>(*image).set_style_sheet(m_style_sheet.ptr());
|
|
m_selected_image = image;
|
|
}
|
|
if (m_selected_image)
|
|
const_cast<AbstractImageStyleValue&>(*m_selected_image).load_any_resources(document);
|
|
}
|
|
|
|
Optional<CSSPixels> ImageSetStyleValue::natural_width() const
|
|
{
|
|
if (m_selected_image)
|
|
return m_selected_image->natural_width();
|
|
return {};
|
|
}
|
|
|
|
Optional<CSSPixels> ImageSetStyleValue::natural_height() const
|
|
{
|
|
if (m_selected_image)
|
|
return m_selected_image->natural_height();
|
|
return {};
|
|
}
|
|
|
|
Optional<CSSPixelFraction> ImageSetStyleValue::natural_aspect_ratio() const
|
|
{
|
|
if (m_selected_image)
|
|
return m_selected_image->natural_aspect_ratio();
|
|
return {};
|
|
}
|
|
|
|
void ImageSetStyleValue::resolve_for_size(Layout::NodeWithStyle const& layout_node, CSSPixelSize size) const
|
|
{
|
|
if (m_selected_image)
|
|
m_selected_image->resolve_for_size(layout_node, size);
|
|
}
|
|
|
|
bool ImageSetStyleValue::is_paintable() const
|
|
{
|
|
if (m_selected_image)
|
|
return m_selected_image->is_paintable();
|
|
return false;
|
|
}
|
|
|
|
void ImageSetStyleValue::paint(DisplayListRecordingContext& context, DevicePixelRect const& dest_rect, ImageRendering image_rendering) const
|
|
{
|
|
if (m_selected_image)
|
|
m_selected_image->paint(context, dest_rect, image_rendering);
|
|
}
|
|
|
|
Optional<Gfx::Color> ImageSetStyleValue::color_if_single_pixel_bitmap() const
|
|
{
|
|
if (m_selected_image)
|
|
return m_selected_image->color_if_single_pixel_bitmap();
|
|
return {};
|
|
}
|
|
|
|
void ImageSetStyleValue::set_style_sheet(GC::Ptr<CSSStyleSheet> style_sheet)
|
|
{
|
|
Base::set_style_sheet(style_sheet);
|
|
m_style_sheet = style_sheet;
|
|
|
|
// Propagate the style sheet to candidate images whose type() filter does not exclude them. This ensures the
|
|
// candidate images register themselves as pending image resources on the style sheet, so their fetches start when
|
|
// the style sheet is associated with the document, properly delaying the document's load event.
|
|
for (auto const& option : m_options) {
|
|
if (option.type.has_value() && !HTML::is_supported_image_type(*option.type))
|
|
continue;
|
|
const_cast<AbstractImageStyleValue&>(*option.image).set_style_sheet(style_sheet);
|
|
}
|
|
}
|
|
|
|
ValueComparingNonnullRefPtr<StyleValue const> ImageSetStyleValue::absolutized(ComputationContext const& context) const
|
|
{
|
|
Vector<Option> options;
|
|
options.ensure_capacity(m_options.size());
|
|
for (auto const& option : m_options) {
|
|
auto image = option.image->absolutized(context);
|
|
VERIFY(image->is_abstract_image());
|
|
options.unchecked_append({
|
|
.image = image->as_abstract_image(),
|
|
.resolution = option.resolution->absolutized(context),
|
|
.type = option.type,
|
|
});
|
|
}
|
|
return ImageSetStyleValue::create(move(options));
|
|
}
|
|
|
|
}
|