LibWeb: Account text and image storage as external memory

Report DOM character data, decoded image frames, ImageBitmap pixel
buffers, and 2D canvas surfaces through the GC external memory hook.
This lets image and text-heavy pages participate in GC threshold
calculations through their retained backing storage.
This commit is contained in:
Andreas Kling 2026-05-06 00:34:52 +02:00 committed by Andreas Kling
parent 68fa684f76
commit a414136d7c
10 changed files with 67 additions and 0 deletions

View file

@ -5,6 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/ExternalMemory.h>
#include <LibUnicode/Segmenter.h>
#include <LibWeb/Bindings/CharacterData.h>
#include <LibWeb/CSS/Invalidation/LanguageInvalidator.h>
@ -32,6 +33,11 @@ void CharacterData::initialize(JS::Realm& realm)
Base::initialize(realm);
}
size_t CharacterData::external_memory_size() const
{
return Node::external_memory_size() + JS::utf16_string_external_memory_size(m_data);
}
// https://dom.spec.whatwg.org/#dom-characterdata-data
void CharacterData::set_data(Utf16String const& data)
{

View file

@ -48,6 +48,8 @@ protected:
virtual void initialize(JS::Realm&) override;
private:
virtual size_t external_memory_size() const override;
Utf16String m_data;
mutable OwnPtr<Unicode::Segmenter> m_grapheme_segmenter;

View file

@ -6,6 +6,7 @@
#include <LibGC/Heap.h>
#include <LibGfx/Bitmap.h>
#include <LibJS/Runtime/ExternalMemory.h>
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/HTML/AnimatedDecodedImageData.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
@ -100,6 +101,16 @@ AnimatedDecodedImageData::AnimatedDecodedImageData(
AnimatedDecodedImageData::~AnimatedDecodedImageData() = default;
size_t AnimatedDecodedImageData::external_memory_size() const
{
size_t size = JS::vector_external_memory_size(m_durations);
for (auto const& slot : m_buffer_slots) {
if (slot.frame)
size = JS::saturating_add_external_memory_size(size, slot.frame->bitmap().data_size());
}
return size;
}
void AnimatedDecodedImageData::finalize()
{
Base::finalize();

View file

@ -79,6 +79,8 @@ private:
Gfx::ColorSpace,
Vector<u32> durations);
virtual size_t external_memory_size() const override;
BufferSlot const* find_slot(u32 frame_index) const;
BufferSlot& evict_oldest_slot();
void maybe_request_more_frames(size_t current_frame_index);

View file

@ -5,6 +5,8 @@
*/
#include <LibGC/Heap.h>
#include <LibGfx/Bitmap.h>
#include <LibJS/Runtime/ExternalMemory.h>
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/HTML/BitmapDecodedImageData.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
@ -28,6 +30,16 @@ BitmapDecodedImageData::BitmapDecodedImageData(Vector<Frame>&& frames, size_t lo
BitmapDecodedImageData::~BitmapDecodedImageData() = default;
size_t BitmapDecodedImageData::external_memory_size() const
{
size_t size = JS::vector_external_memory_size(m_frames);
for (auto const& frame : m_frames) {
if (frame.frame)
size = JS::saturating_add_external_memory_size(size, frame.frame->bitmap().data_size());
}
return size;
}
RefPtr<Gfx::DecodedImageFrame> BitmapDecodedImageData::frame(size_t frame_index, Gfx::IntSize) const
{
if (frame_index >= m_frames.size())

View file

@ -42,6 +42,8 @@ public:
private:
BitmapDecodedImageData(Vector<Frame>&&, size_t loop_count, bool animated);
virtual size_t external_memory_size() const override;
Vector<Frame> m_frames;
size_t m_loop_count { 0 };
bool m_animated { false };

View file

@ -9,12 +9,15 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Checked.h>
#include <AK/NumericLimits.h>
#include <AK/OwnPtr.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/CompositingAndBlendingOperator.h>
#include <LibGfx/DecodedImageFrame.h>
#include <LibGfx/PainterSkia.h>
#include <LibGfx/Rect.h>
#include <LibJS/Runtime/ExternalMemory.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibUnicode/Segmenter.h>
@ -74,6 +77,24 @@ void CanvasRenderingContext2D::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_element);
}
size_t CanvasRenderingContext2D::external_memory_size() const
{
auto size = Base::external_memory_size();
if (!m_surface)
return size;
auto surface_size = m_surface->size();
if (surface_size.is_empty())
return size;
Checked<size_t> pixel_size = static_cast<size_t>(surface_size.width());
pixel_size *= static_cast<size_t>(surface_size.height());
pixel_size *= sizeof(u32);
if (pixel_size.has_overflow())
return NumericLimits<size_t>::max();
return JS::saturating_add_external_memory_size(size, pixel_size.value());
}
HTMLCanvasElement& CanvasRenderingContext2D::canvas_element()
{
return *m_element;

View file

@ -138,6 +138,7 @@ private:
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
virtual size_t external_memory_size() const override;
virtual Gfx::Painter* painter_for_canvas_state() override { return painter(); }
virtual Gfx::Path& path_for_canvas_state() override { return path(); }

View file

@ -5,6 +5,7 @@
*/
#include <LibGfx/Bitmap.h>
#include <LibJS/Runtime/ExternalMemory.h>
#include <LibWeb/Bindings/ImageBitmap.h>
#include <LibWeb/HTML/ImageBitmap.h>
#include <LibWeb/HTML/StructuredSerialize.h>
@ -72,6 +73,14 @@ void ImageBitmap::visit_edges(Cell::Visitor& visitor)
Base::visit_edges(visitor);
}
size_t ImageBitmap::external_memory_size() const
{
auto size = Base::external_memory_size();
if (m_bitmap)
size = JS::saturating_add_external_memory_size(size, m_bitmap->data_size());
return size;
}
// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#the-imagebitmap-interface:serialization-steps
WebIDL::ExceptionOr<void> ImageBitmap::serialization_steps(HTML::TransferDataEncoder& serialized, bool, HTML::SerializationMemory&)
{

View file

@ -65,6 +65,7 @@ private:
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
virtual size_t external_memory_size() const override;
WebIDL::UnsignedLong m_width = 0;
WebIDL::UnsignedLong m_height = 0;