From 93bbdf1f5587f2cc63a8a876412e4b1692a601e2 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Sat, 16 May 2026 22:58:31 +0200 Subject: [PATCH] LibWeb: Use compositor surface IDs for embedded content Display lists kept canvas and nested navigable content alive through ExternalContentSource objects. That made the resource graph depend on process-local object identity instead of a stable surface handle, which blocks compositor process isolation and made teardown-sensitive embedded content harder to reason about. Allocate CompositorSurfaceId values for canvases and child navigables, publish their backing stores to the owning compositor, and paint them with DrawCompositorSurface. Child navigables now publish to parent compositors by CompositorContextId instead of raw object pointers, so the in-process path uses the same stable addressing model required by a remote compositor. Clear and skip stale child surfaces during teardown, preserve Skia canvas state while drawing compositor surfaces, and add display-list coverage for canvas and iframe compositor surfaces. The nested navigable async-scrolling baseline now expects DrawCompositorSurface. --- Libraries/LibGfx/PaintingSurface.cpp | 7 ++ Libraries/LibGfx/PaintingSurface.h | 3 + Libraries/LibWeb/CMakeLists.txt | 1 - .../LibWeb/Compositor/CompositorThread.cpp | 75 +++++++++++++++++-- .../LibWeb/Compositor/CompositorThread.h | 13 +++- Libraries/LibWeb/Compositor/Types.h | 23 ++++++ Libraries/LibWeb/Forward.h | 1 - Libraries/LibWeb/HTML/HTMLCanvasElement.cpp | 26 ++++--- Libraries/LibWeb/HTML/HTMLCanvasElement.h | 8 +- Libraries/LibWeb/HTML/Navigable.cpp | 34 +++++++-- Libraries/LibWeb/HTML/Navigable.h | 6 +- Libraries/LibWeb/Painting/CanvasPaintable.cpp | 8 +- Libraries/LibWeb/Painting/DisplayList.h | 3 +- .../LibWeb/Painting/DisplayListCommand.cpp | 2 +- .../LibWeb/Painting/DisplayListCommand.h | 10 +-- .../LibWeb/Painting/DisplayListPlayerSkia.cpp | 8 +- .../LibWeb/Painting/DisplayListPlayerSkia.h | 2 +- .../LibWeb/Painting/DisplayListRecorder.cpp | 6 +- .../LibWeb/Painting/DisplayListRecorder.h | 2 +- .../LibWeb/Painting/DisplayListResourceIds.h | 9 ++- .../Painting/DisplayListResourceStorage.cpp | 38 +++------- .../Painting/DisplayListResourceStorage.h | 12 ++- .../LibWeb/Painting/ExternalContentSource.cpp | 50 ------------- .../LibWeb/Painting/ExternalContentSource.h | 36 --------- .../NavigableContainerViewportPaintable.cpp | 7 +- .../nested-navigable-wheel-admission.txt | 2 +- .../canvas-compositor-surface.txt | 12 +++ .../iframe-compositor-surface.txt | 16 ++++ .../canvas-compositor-surface.html | 19 +++++ .../iframe-compositor-surface.html | 28 +++++++ 30 files changed, 288 insertions(+), 179 deletions(-) create mode 100644 Libraries/LibWeb/Compositor/Types.h delete mode 100644 Libraries/LibWeb/Painting/ExternalContentSource.cpp delete mode 100644 Libraries/LibWeb/Painting/ExternalContentSource.h create mode 100644 Tests/LibWeb/Text/expected/display_list/canvas-compositor-surface.txt create mode 100644 Tests/LibWeb/Text/expected/display_list/iframe-compositor-surface.txt create mode 100644 Tests/LibWeb/Text/input/display_list/canvas-compositor-surface.html create mode 100644 Tests/LibWeb/Text/input/display_list/iframe-compositor-surface.html diff --git a/Libraries/LibGfx/PaintingSurface.cpp b/Libraries/LibGfx/PaintingSurface.cpp index 2c54030ffa..95d308602b 100644 --- a/Libraries/LibGfx/PaintingSurface.cpp +++ b/Libraries/LibGfx/PaintingSurface.cpp @@ -153,6 +153,13 @@ NonnullRefPtr PaintingSurface::snapshot_bitmap() const return bitmap; } +SharedImage PaintingSurface::snapshot_into_shared_image() const +{ + auto shared_image_buffer = SharedImageBuffer::create(size()); + read_into_bitmap(*shared_image_buffer.bitmap()); + return shared_image_buffer.export_shared_image(); +} + void PaintingSurface::read_into_bitmap(Bitmap& bitmap) const { auto color_type = to_skia_color_type(bitmap.format()); diff --git a/Libraries/LibGfx/PaintingSurface.h b/Libraries/LibGfx/PaintingSurface.h index 9a25670af7..3e55a5490b 100644 --- a/Libraries/LibGfx/PaintingSurface.h +++ b/Libraries/LibGfx/PaintingSurface.h @@ -31,6 +31,8 @@ class SkSurface; namespace Gfx { +class SharedImage; + class PaintingSurface : public AtomicRefCounted { public: enum class Origin { @@ -52,6 +54,7 @@ public: #endif NonnullRefPtr snapshot_bitmap() const; + SharedImage snapshot_into_shared_image() const; void read_into_bitmap(Bitmap&) const; void write_from_bitmap(Bitmap const&); diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 52db5ec07b..a10fc35ec0 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -861,7 +861,6 @@ set(SOURCES Painting/DisplayListRecorder.cpp Painting/DisplayListRecordingContext.cpp Painting/DisplayListResourceStorage.cpp - Painting/ExternalContentSource.cpp Painting/FieldSetPaintable.cpp Painting/GradientPainting.cpp Painting/ImagePaintable.cpp diff --git a/Libraries/LibWeb/Compositor/CompositorThread.cpp b/Libraries/LibWeb/Compositor/CompositorThread.cpp index 228afdfbcb..c971417f43 100644 --- a/Libraries/LibWeb/Compositor/CompositorThread.cpp +++ b/Libraries/LibWeb/Compositor/CompositorThread.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -19,7 +18,6 @@ #include #include #include -#include #include #include @@ -67,6 +65,15 @@ struct UpdateScrollStateCommand { Painting::ScrollStateSnapshot scroll_state_snapshot; }; +struct UpdateCompositorSurfaceCommand { + Painting::CompositorSurfaceId surface_id; + Gfx::SharedImage shared_image; +}; + +struct ClearCompositorSurfaceCommand { + Painting::CompositorSurfaceId surface_id; +}; + struct ViewportSizeUpdatedCommand { Gfx::IntSize viewport_size; bool is_top_level_traversable { false }; @@ -79,7 +86,8 @@ struct ScreenshotCommand { }; using CompositorCommand = Variant; + UpdateScrollStateCommand, UpdateCompositorSurfaceCommand, ClearCompositorSurfaceCommand, ViewportSizeUpdatedCommand, + ScreenshotCommand>; static SkRect to_skia_rect(Gfx::IntRect const& rect) { @@ -714,6 +722,12 @@ public: } } }, + [this](UpdateCompositorSurfaceCommand& cmd) { + m_display_list_resource_storage.update_compositor_surface(cmd.surface_id, move(cmd.shared_image)); + }, + [this](ClearCompositorSurfaceCommand& cmd) { + m_display_list_resource_storage.clear_compositor_surface(cmd.surface_id); + }, [this](ViewportSizeUpdatedCommand& cmd) { auto allocation = m_backing_store_manager.resize_backing_stores_if_needed( cmd.viewport_size, cmd.is_top_level_traversable, cmd.window_resize_in_progress); @@ -1007,7 +1021,7 @@ private: if (m_cached_display_list && m_backing_store_manager.is_valid()) { auto should_clear_back_store = presentation_mode.visit( [](CompositorThread::PresentToUI) { return false; }, - [](CompositorThread::PublishToExternalContent const&) { return true; }); + [](CompositorThread::PublishToCompositorSurface const&) { return true; }); auto& back_store = m_backing_store_manager.back_store(); if (should_clear_back_store) { // Embedded navigables leave their PaintConfig canvas unfilled, so double-buffered back stores must be @@ -1036,15 +1050,16 @@ private: m_is_rasterizing = false; } }, - [this](CompositorThread::PublishToExternalContent const& mode) { + [this](CompositorThread::PublishToCompositorSurface const& mode) { { Sync::MutexLocker const locker { m_mutex }; m_is_rasterizing = false; } if (m_has_async_scrolling_state) - dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Publishing present to external content source"); - auto snapshot = Gfx::DecodedImageFrame { *m_backing_store_manager.front_store().snapshot_bitmap() }; - mode.source->update(move(snapshot)); + dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Publishing present to compositor surface"); + auto& front_store = m_backing_store_manager.front_store(); + VERIFY(CompositorThread::update_compositor_surface_for_context( + mode.target_context_id, mode.surface_id, front_store.snapshot_into_shared_image())); }); } else { { @@ -1186,6 +1201,12 @@ static HashMap>& page_composito return *compositors; } +static HashMap>& context_compositors() +{ + static NeverDestroyed>> compositors; + return *compositors; +} + static FramePresentationState& frame_presentation_state() { static NeverDestroyed state; @@ -1199,6 +1220,12 @@ CompositorThread::CompositorThread(u64 page_id, PagePresentationRegistration pag enqueue_viewport_size_updated(m_last_viewport_size, m_last_viewport_size_is_top_level_traversable, WindowResizingInProgress::No); }); + { + Sync::MutexLocker const locker { compositor_presentation_state_mutex() }; + VERIFY(!context_compositors().contains(m_context_id)); + context_compositors().set(m_context_id, m_thread_data); + } + if (page_presentation_registration == PagePresentationRegistration::Yes) register_page_compositor(page_id, m_thread_data); } @@ -1210,6 +1237,11 @@ CompositorThread::~CompositorThread() m_backing_store_shrink_timer.clear(); unregister_page_compositor(m_thread_data->page_id(), *m_thread_data); + { + Sync::MutexLocker const locker { compositor_presentation_state_mutex() }; + VERIFY(context_compositors().remove(m_context_id)); + } + m_thread_data->exit(); } @@ -1240,6 +1272,23 @@ void CompositorThread::unregister_page_compositor(u64 page_id, ThreadData& threa dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Unregistered page {} from compositor presentation", page_id); } +bool CompositorThread::update_compositor_surface_for_context(CompositorContextId context_id, Painting::CompositorSurfaceId surface_id, Gfx::SharedImage&& shared_image) +{ + RefPtr thread_data; + { + Sync::MutexLocker const locker { compositor_presentation_state_mutex() }; + auto compositor = context_compositors().find(context_id); + if (compositor == context_compositors().end()) { + dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Dropping compositor surface {} update for unknown context {}", + surface_id, context_id); + return false; + } + thread_data = compositor->value; + } + thread_data->enqueue_command(UpdateCompositorSurfaceCommand { surface_id, move(shared_image) }); + return true; +} + void CompositorThread::set_frame_presentation_callbacks(NonnullRefPtr event_loop, BackingStorePresentationCallback backing_store_callback, FramePresentationCallback frame_callback) { Sync::MutexLocker const locker { compositor_presentation_state_mutex() }; @@ -1443,6 +1492,16 @@ void CompositorThread::update_display_list( m_thread_data->enqueue_command(UpdateDisplayListCommand { move(display_list), move(resource_transaction), move(scroll_state_snapshot) }); } +void CompositorThread::update_compositor_surface(Painting::CompositorSurfaceId surface_id, Gfx::SharedImage&& shared_image) +{ + m_thread_data->enqueue_command(UpdateCompositorSurfaceCommand { surface_id, move(shared_image) }); +} + +void CompositorThread::clear_compositor_surface(Painting::CompositorSurfaceId surface_id) +{ + m_thread_data->enqueue_command(ClearCompositorSurfaceCommand { surface_id }); +} + void CompositorThread::invalidate_wheel_event_listener_state(u64 generation) { m_thread_data->invalidate_wheel_event_listener_state(generation); diff --git a/Libraries/LibWeb/Compositor/CompositorThread.h b/Libraries/LibWeb/Compositor/CompositorThread.h index ce6eea1fd0..82da4b04e5 100644 --- a/Libraries/LibWeb/Compositor/CompositorThread.h +++ b/Libraries/LibWeb/Compositor/CompositorThread.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -61,10 +62,11 @@ public: }; struct PresentToUI { }; - struct PublishToExternalContent { - NonnullRefPtr source; + struct PublishToCompositorSurface { + CompositorContextId target_context_id; + Painting::CompositorSurfaceId surface_id; }; - using PresentationMode = Variant; + using PresentationMode = Variant; CompositorThread(u64 page_id, PagePresentationRegistration); ~CompositorThread(); @@ -75,11 +77,14 @@ public: static bool async_scroll_by(u64 page_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels); static bool handle_mouse_event(u64 page_id, MouseEvent const&); + CompositorContextId context_id() const { return m_context_id; } void start(DisplayListPlayerType); void stop_presenting_to_client(); void set_presentation_mode(PresentationMode); void update_display_list(NonnullRefPtr, Painting::DisplayListResourceTransaction&&, Painting::ScrollStateSnapshot&&); + void update_compositor_surface(Painting::CompositorSurfaceId, Gfx::SharedImage&&); + void clear_compositor_surface(Painting::CompositorSurfaceId); void update_scroll_state(Painting::ScrollStateSnapshot&&); void invalidate_wheel_event_listener_state(u64 generation); AsyncScrollEnqueueResult async_scroll_by(UniqueNodeID expected_document_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels, @@ -95,6 +100,7 @@ public: private: void enqueue_viewport_size_updated(Gfx::IntSize, bool is_top_level_traversable, WindowResizingInProgress); + CompositorContextId m_context_id { allocate_compositor_context_id() }; NonnullRefPtr m_thread_data; RefPtr m_thread; RefPtr m_backing_store_shrink_timer; @@ -103,6 +109,7 @@ private: static void register_page_compositor(u64 page_id, NonnullRefPtr); static void unregister_page_compositor(u64 page_id, ThreadData&); + static bool update_compositor_surface_for_context(CompositorContextId, Painting::CompositorSurfaceId, Gfx::SharedImage&&); static bool present_backing_stores_to_client(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage&&, i32 back_bitmap_id, Gfx::SharedImage&&); static bool present_frame_to_client(u64 page_id, Gfx::IntRect const&, i32 bitmap_id); }; diff --git a/Libraries/LibWeb/Compositor/Types.h b/Libraries/LibWeb/Compositor/Types.h new file mode 100644 index 0000000000..c4f0915a73 --- /dev/null +++ b/Libraries/LibWeb/Compositor/Types.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2026, Ladybird contributors + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::Compositor { + +AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, CompositorContextId); + +inline CompositorContextId allocate_compositor_context_id() +{ + static Atomic s_next_id { 1 }; + return CompositorContextId { s_next_id.fetch_add(1, AK::MemoryOrder::memory_order_relaxed) }; +} + +} diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 31614c9a90..7f5dc52c33 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -50,7 +50,6 @@ class DisplayList; class DisplayListPlayerSkia; class DisplayListRecorder; class DisplayListResourceStorage; -class ExternalContentSource; class VideoFrameSource; struct GradientPaintStyle; struct PatternPaintStyle; diff --git a/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp b/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp index 6481249423..4b5387b857 100644 --- a/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -54,6 +54,7 @@ void HTMLCanvasElement::initialize(JS::Realm& realm) void HTMLCanvasElement::finalize() { + clear_compositor_surface(); Base::finalize(); document().page().unregister_canvas_element({}, unique_id()); } @@ -131,17 +132,16 @@ WebIDL::UnsignedLong HTMLCanvasElement::height() const return 150; } -Painting::ExternalContentSource& HTMLCanvasElement::ensure_external_content_source() +Painting::CompositorSurfaceId HTMLCanvasElement::ensure_compositor_surface_id() { - if (!m_external_content_source) - m_external_content_source = Painting::ExternalContentSource::create(); - return *m_external_content_source; + if (!m_compositor_surface_id.has_value()) + m_compositor_surface_id = Painting::allocate_compositor_surface_id(); + return *m_compositor_surface_id; } void HTMLCanvasElement::reset_context_to_default_state() { - if (m_external_content_source) - m_external_content_source->clear(); + clear_compositor_surface(); m_context.visit( [](GC::Ref& context) { context->reset_to_default_state(); @@ -453,11 +453,19 @@ void HTMLCanvasElement::present() if (auto surface = this->surface()) { surface->flush(); - auto snapshot = Gfx::DecodedImageFrame { *surface->snapshot_bitmap() }; - ensure_external_content_source().update(snapshot); + if (auto navigable = document().navigable()) + navigable->rendering_thread().update_compositor_surface(ensure_compositor_surface_id(), surface->snapshot_into_shared_image()); } } +void HTMLCanvasElement::clear_compositor_surface() +{ + if (!m_compositor_surface_id.has_value()) + return; + if (auto navigable = document().navigable()) + navigable->rendering_thread().clear_compositor_surface(*m_compositor_surface_id); +} + RefPtr HTMLCanvasElement::surface() const { return m_context.visit( diff --git a/Libraries/LibWeb/HTML/HTMLCanvasElement.h b/Libraries/LibWeb/HTML/HTMLCanvasElement.h index b7df50e20a..63d614a12f 100644 --- a/Libraries/LibWeb/HTML/HTMLCanvasElement.h +++ b/Libraries/LibWeb/HTML/HTMLCanvasElement.h @@ -6,10 +6,11 @@ #pragma once +#include #include #include #include -#include +#include #include namespace Web::HTML { @@ -52,7 +53,7 @@ public: RefPtr surface() const; void allocate_painting_surface_if_needed(); - Painting::ExternalContentSource& ensure_external_content_source(); + Painting::CompositorSurfaceId ensure_compositor_surface_id(); CSS::ComputationContext canvas_font_computation_context(); @@ -73,9 +74,10 @@ private: JS::ThrowCompletionOr create_webgl_context(JS::Value options); void reset_context_to_default_state(); void notify_context_about_canvas_size_change(); + void clear_compositor_surface(); Variant, GC::Ref, GC::Ref, Empty> m_context; - RefPtr m_external_content_source; + Optional m_compositor_surface_id; bool m_canvas_content_dirty { false }; }; diff --git a/Libraries/LibWeb/HTML/Navigable.cpp b/Libraries/LibWeb/HTML/Navigable.cpp index d70fd5bfe2..ea544f2af4 100644 --- a/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Libraries/LibWeb/HTML/Navigable.cpp @@ -55,7 +55,6 @@ #include #include #include -#include #include #include #include @@ -297,12 +296,14 @@ Navigable::~Navigable() = default; void Navigable::set_has_been_destroyed() { + clear_compositor_surface(); m_has_been_destroyed = true; resolve_all_pending_async_scroll_operations(); } void Navigable::remove_from_all_navigables() { + clear_compositor_surface(); resolve_all_pending_async_scroll_operations(); if (m_active_document) @@ -312,6 +313,7 @@ void Navigable::remove_from_all_navigables() void Navigable::finalize() { + clear_compositor_surface(); all_navigables().remove(*this); Base::finalize(); } @@ -430,8 +432,11 @@ void Navigable::initialize_navigable(NonnullRefPtr document_state if (parent) m_should_show_line_box_borders = parent->m_should_show_line_box_borders; if (parent && !m_is_svg_page) { - m_external_content_source = Painting::ExternalContentSource::create(); - m_rendering_thread.set_presentation_mode(Compositor::CompositorThread::PublishToExternalContent { external_content_source() }); + m_compositor_surface_id = Painting::allocate_compositor_surface_id(); + m_rendering_thread.set_presentation_mode(Compositor::CompositorThread::PublishToCompositorSurface { + .target_context_id = parent->rendering_thread().context_id(), + .surface_id = *m_compositor_surface_id, + }); } // 6. Set the initial visibility state of documentState's document to navigable's traversable navigable's system visibility state. @@ -3274,10 +3279,19 @@ void Navigable::set_has_session_history_entry_and_ready_for_navigation() } } -NonnullRefPtr Navigable::external_content_source() const +Painting::CompositorSurfaceId Navigable::compositor_surface_id() const { - VERIFY(m_external_content_source); - return *m_external_content_source; + VERIFY(m_compositor_surface_id.has_value()); + return *m_compositor_surface_id; +} + +void Navigable::clear_compositor_surface() +{ + if (!m_compositor_surface_id.has_value()) + return; + if (auto parent = this->parent()) + parent->rendering_thread().clear_compositor_surface(*m_compositor_surface_id); + m_compositor_surface_id.clear(); } void Navigable::set_should_show_line_box_borders(bool value) @@ -3332,14 +3346,18 @@ void Navigable::record_display_list_and_scroll_state(PaintConfig paint_config) void Navigable::paint_next_frame() { + if (has_been_destroyed()) + return; + auto viewport_rect = page().css_to_device_rect(this->viewport_rect()).to_type(); PaintConfig paint_config { .paint_overlay = true, .should_show_line_box_borders = m_should_show_line_box_borders }; if (is_top_level_traversable()) { paint_config.canvas_fill_rect = Gfx::IntRect { {}, viewport_rect.size() }; } else { - // Nested navigables publish transparent bitmaps to their preconfigured ExternalContentSource instead of filling + // Nested navigables publish transparent bitmaps to their preconfigured compositor surface instead of filling // the canvas for the UI process. - VERIFY(m_external_content_source); + if (!m_compositor_surface_id.has_value()) + return; } auto should_defer_main_thread_present_for_async_scroll = [&] { diff --git a/Libraries/LibWeb/HTML/Navigable.h b/Libraries/LibWeb/HTML/Navigable.h index f21b061c53..0cb5d3701d 100644 --- a/Libraries/LibWeb/HTML/Navigable.h +++ b/Libraries/LibWeb/HTML/Navigable.h @@ -241,7 +241,8 @@ public: Compositor::CompositorThread& rendering_thread() { return m_rendering_thread; } - NonnullRefPtr external_content_source() const; + Painting::CompositorSurfaceId compositor_surface_id() const; + bool has_compositor_surface_id() const { return m_compositor_surface_id.has_value(); } void set_pending_set_browser_zoom_request(bool value) { m_pending_set_browser_zoom_request = value; } bool pending_set_browser_zoom_request() const { return m_pending_set_browser_zoom_request; } @@ -277,6 +278,7 @@ private: void reset_cursor_blink_cycle(); void scroll_offset_did_change(); + void clear_compositor_surface(); void inform_the_navigation_api_about_aborting_navigation(); void resolve_async_scroll_operation(Compositor::AsyncScrollOperationID); @@ -334,7 +336,7 @@ private: Painting::DisplayListResourceStorage m_display_list_resource_storage; Painting::DisplayListResourceSet m_rendering_thread_display_list_resources; Compositor::CompositorThread m_rendering_thread; - RefPtr m_external_content_source; + Optional m_compositor_surface_id; struct PendingAsyncScrollOperation { Compositor::AsyncScrollOperationID operation_id { 0 }; diff --git a/Libraries/LibWeb/Painting/CanvasPaintable.cpp b/Libraries/LibWeb/Painting/CanvasPaintable.cpp index 5a692effb5..82261a1b97 100644 --- a/Libraries/LibWeb/Painting/CanvasPaintable.cpp +++ b/Libraries/LibWeb/Painting/CanvasPaintable.cpp @@ -32,13 +32,13 @@ void CanvasPaintable::paint(DisplayListRecordingContext& context, PaintPhase pha ScopedCornerRadiusClip corner_clip { context, canvas_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; auto& canvas_element = as(*dom_node()); - if (canvas_element.surface()) { + if (auto surface = canvas_element.surface()) { auto canvas_int_rect = canvas_rect.to_type(); auto scaling_mode = to_gfx_scaling_mode(computed_values().image_rendering(), - canvas_element.surface()->size(), canvas_int_rect.size()); + surface->size(), canvas_int_rect.size()); auto& mutable_canvas_element = const_cast(canvas_element); - context.display_list_recorder().draw_external_content(canvas_int_rect, - mutable_canvas_element.ensure_external_content_source(), scaling_mode); + context.display_list_recorder().draw_compositor_surface(canvas_int_rect, + mutable_canvas_element.ensure_compositor_surface_id(), scaling_mode); } } } diff --git a/Libraries/LibWeb/Painting/DisplayList.h b/Libraries/LibWeb/Painting/DisplayList.h index 853979bc0e..93e65faff4 100644 --- a/Libraries/LibWeb/Painting/DisplayList.h +++ b/Libraries/LibWeb/Painting/DisplayList.h @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -97,7 +96,7 @@ private: virtual void fill_rect(FillRect const&) = 0; virtual void draw_scaled_decoded_image_frame(DrawScaledDecodedImageFrame const&) = 0; virtual void draw_repeated_decoded_image_frame(DrawRepeatedDecodedImageFrame const&) = 0; - virtual void draw_external_content(DrawExternalContent const&) = 0; + virtual void draw_compositor_surface(DrawCompositorSurface const&) = 0; virtual void draw_video_frame_source(DrawVideoFrameSource const&) = 0; virtual void save(Save const&) = 0; virtual void save_layer(SaveLayer const&) = 0; diff --git a/Libraries/LibWeb/Painting/DisplayListCommand.cpp b/Libraries/LibWeb/Painting/DisplayListCommand.cpp index 4ab5a0d7ff..7c713abdec 100644 --- a/Libraries/LibWeb/Painting/DisplayListCommand.cpp +++ b/Libraries/LibWeb/Painting/DisplayListCommand.cpp @@ -40,7 +40,7 @@ void DrawRepeatedDecodedImageFrame::dump(StringBuilder& builder) const builder.appendff(" dst_rect={} clip_rect={}", dst_rect, clip_rect); } -void DrawExternalContent::dump(StringBuilder& builder) const +void DrawCompositorSurface::dump(StringBuilder& builder) const { builder.appendff(" dst_rect={}", dst_rect); } diff --git a/Libraries/LibWeb/Painting/DisplayListCommand.h b/Libraries/LibWeb/Painting/DisplayListCommand.h index d09e694060..5f3e59940e 100644 --- a/Libraries/LibWeb/Painting/DisplayListCommand.h +++ b/Libraries/LibWeb/Painting/DisplayListCommand.h @@ -38,7 +38,7 @@ class DisplayList; V(FillRect, fill_rect) \ V(DrawScaledDecodedImageFrame, draw_scaled_decoded_image_frame) \ V(DrawRepeatedDecodedImageFrame, draw_repeated_decoded_image_frame) \ - V(DrawExternalContent, draw_external_content) \ + V(DrawCompositorSurface, draw_compositor_surface) \ V(DrawVideoFrameSource, draw_video_frame_source) \ V(Save, save) \ V(SaveLayer, save_layer) \ @@ -165,12 +165,12 @@ struct DrawRepeatedDecodedImageFrame { void dump(StringBuilder&) const; }; -struct DrawExternalContent { - static constexpr StringView command_name = "DrawExternalContent"sv; - static constexpr DisplayListCommandType command_type = DisplayListCommandType::DrawExternalContent; +struct DrawCompositorSurface { + static constexpr StringView command_name = "DrawCompositorSurface"sv; + static constexpr DisplayListCommandType command_type = DisplayListCommandType::DrawCompositorSurface; Gfx::IntRect dst_rect; - ExternalContentResourceId source_id; + CompositorSurfaceId surface_id; Gfx::ScalingMode scaling_mode; [[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; } diff --git a/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp b/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp index a30438a7ea..26057d0be8 100644 --- a/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp +++ b/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp @@ -165,14 +165,16 @@ void DisplayListPlayerSkia::fill_rect(FillRect const& command) canvas.drawRect(to_skia_rect(rect), paint); } -void DisplayListPlayerSkia::draw_external_content(DrawExternalContent const& command) +void DisplayListPlayerSkia::draw_compositor_surface(DrawCompositorSurface const& command) { - auto frame = resource_storage().external_content_source(command.source_id).current_frame(); + auto frame = resource_storage().compositor_surface(command.surface_id); if (!frame.has_value()) return; - auto image = m_image_cache.image_for_frame(*frame); + + auto image = m_image_cache.image_for_frame(frame.value()); if (!image) return; + auto dst_rect = to_skia_rect(command.dst_rect); SkRect src_rect = SkRect::MakeIWH(image->width(), image->height()); auto& canvas = surface().canvas(); diff --git a/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h b/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h index 1043cc384b..1e8accea78 100644 --- a/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h +++ b/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h @@ -28,7 +28,7 @@ private: void fill_rect(FillRect const&) override; void draw_scaled_decoded_image_frame(DrawScaledDecodedImageFrame const&) override; void draw_repeated_decoded_image_frame(DrawRepeatedDecodedImageFrame const&) override; - void draw_external_content(DrawExternalContent const&) override; + void draw_compositor_surface(DrawCompositorSurface const&) override; void draw_video_frame_source(DrawVideoFrameSource const&) override; void add_clip_rect(AddClipRect const&) override; void save(Save const&) override; diff --git a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp index c825dffeac..b7839e1e5d 100644 --- a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp +++ b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp @@ -499,13 +499,13 @@ void DisplayListRecorder::draw_rect(Gfx::IntRect const& rect, Color color, bool .rough = rough }); } -void DisplayListRecorder::draw_external_content(Gfx::IntRect const& dst_rect, NonnullRefPtr source, Gfx::ScalingMode scaling_mode) +void DisplayListRecorder::draw_compositor_surface(Gfx::IntRect const& dst_rect, CompositorSurfaceId surface_id, Gfx::ScalingMode scaling_mode) { if (dst_rect.is_empty()) return; - append_command(DrawExternalContent { + append_command(DrawCompositorSurface { .dst_rect = dst_rect, - .source_id = resource_storage().add_external_content_source(move(source)), + .surface_id = surface_id, .scaling_mode = scaling_mode, }); } diff --git a/Libraries/LibWeb/Painting/DisplayListRecorder.h b/Libraries/LibWeb/Painting/DisplayListRecorder.h index 75bf021474..2273bafe52 100644 --- a/Libraries/LibWeb/Painting/DisplayListRecorder.h +++ b/Libraries/LibWeb/Painting/DisplayListRecorder.h @@ -72,7 +72,7 @@ public: void draw_rect(Gfx::IntRect const& rect, Color color, bool rough = false); void draw_scaled_decoded_image_frame(Gfx::IntRect const& dst_rect, Gfx::IntRect const& clip_rect, Gfx::DecodedImageFrame frame, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor); - void draw_external_content(Gfx::IntRect const& dst_rect, NonnullRefPtr, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor); + void draw_compositor_surface(Gfx::IntRect const& dst_rect, CompositorSurfaceId, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor); void draw_video_frame_source(Gfx::IntRect const& dst_rect, NonnullRefPtr, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor); void draw_repeated_decoded_image_frame(Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::DecodedImageFrame frame, Gfx::ScalingMode scaling_mode, bool repeat_x, bool repeat_y); diff --git a/Libraries/LibWeb/Painting/DisplayListResourceIds.h b/Libraries/LibWeb/Painting/DisplayListResourceIds.h index 18f6ad80f8..6c45cc64da 100644 --- a/Libraries/LibWeb/Painting/DisplayListResourceIds.h +++ b/Libraries/LibWeb/Painting/DisplayListResourceIds.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include @@ -13,8 +14,14 @@ namespace Web::Painting { AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, FontResourceId); AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, ImageFrameResourceId); -AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, ExternalContentResourceId); AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, VideoFrameResourceId); AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, DisplayListResourceId); +AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, CompositorSurfaceId); + +inline CompositorSurfaceId allocate_compositor_surface_id() +{ + static Atomic s_next_id { 1 }; + return CompositorSurfaceId { s_next_id.fetch_add(1, AK::MemoryOrder::memory_order_relaxed) }; +} } diff --git a/Libraries/LibWeb/Painting/DisplayListResourceStorage.cpp b/Libraries/LibWeb/Painting/DisplayListResourceStorage.cpp index 9102f998df..45af183fe0 100644 --- a/Libraries/LibWeb/Painting/DisplayListResourceStorage.cpp +++ b/Libraries/LibWeb/Painting/DisplayListResourceStorage.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -27,14 +28,6 @@ ImageFrameResourceId DisplayListResourceStorage::add_image_frame(Gfx::DecodedIma return { id }; } -ExternalContentResourceId DisplayListResourceStorage::add_external_content_source( - NonnullRefPtr source) -{ - auto id = source->id(); - m_external_content_sources.ensure(id, [&] { return move(source); }); - return { id }; -} - VideoFrameResourceId DisplayListResourceStorage::add_video_frame_source(NonnullRefPtr source) { auto id = source->id(); @@ -64,8 +57,6 @@ void DisplayListResourceStorage::append_referenced_resources_from( add_font(source.font(id)); for (auto id : referenced_resources.image_frames) add_image_frame(source.image_frame(id)); - for (auto id : referenced_resources.external_content_sources) - add_external_content_source(source.external_content_source(id)); for (auto id : referenced_resources.video_frame_sources) add_video_frame_source(source.video_frame_source(id)); for (auto id : referenced_resources.display_lists) @@ -89,8 +80,6 @@ void DisplayListResourceStorage::collect_referenced_resources( referenced_resources.fonts.set(command.font_id, AK::HashSetExistingEntryBehavior::Keep); if constexpr (requires { command.frame_id; }) referenced_resources.image_frames.set(command.frame_id, AK::HashSetExistingEntryBehavior::Keep); - if constexpr (requires { external_content_source(command.source_id); }) - referenced_resources.external_content_sources.set(command.source_id, AK::HashSetExistingEntryBehavior::Keep); if constexpr (requires { video_frame_source(command.source_id); }) referenced_resources.video_frame_sources.set(command.source_id, AK::HashSetExistingEntryBehavior::Keep); if constexpr (requires { command.paint_style; command.paint_kind; }) { @@ -150,10 +139,6 @@ DisplayListResourceTransaction DisplayListResourceStorage::create_transaction( if (!previous.image_frames.contains(id)) transaction.image_frames.append(image_frame(id)); } - for (auto id : current.external_content_sources) { - if (!previous.external_content_sources.contains(id)) - transaction.external_content_sources.append(external_content_source(id)); - } for (auto id : current.video_frame_sources) { if (!previous.video_frame_sources.contains(id)) transaction.video_frame_sources.append(video_frame_source(id)); @@ -171,10 +156,6 @@ DisplayListResourceTransaction DisplayListResourceStorage::create_transaction( if (!current.image_frames.contains(id)) transaction.image_frame_ids_to_remove.append(id); } - for (auto id : previous.external_content_sources) { - if (!current.external_content_sources.contains(id)) - transaction.external_content_source_ids_to_remove.append(id); - } for (auto id : previous.video_frame_sources) { if (!current.video_frame_sources.contains(id)) transaction.video_frame_source_ids_to_remove.append(id); @@ -183,7 +164,6 @@ DisplayListResourceTransaction DisplayListResourceStorage::create_transaction( if (!current.display_lists.contains(id)) transaction.display_list_ids_to_remove.append(id); } - return transaction; } @@ -193,8 +173,6 @@ void DisplayListResourceStorage::apply_transaction(DisplayListResourceTransactio add_font(*font); for (auto const& frame : transaction.image_frames) add_image_frame(frame); - for (auto& source : transaction.external_content_sources) - add_external_content_source(move(source)); for (auto& source : transaction.video_frame_sources) add_video_frame_source(move(source)); for (auto& display_list : transaction.display_lists) @@ -204,8 +182,6 @@ void DisplayListResourceStorage::apply_transaction(DisplayListResourceTransactio m_fonts.remove(id.value()); for (auto id : transaction.image_frame_ids_to_remove) m_image_frames.remove(id.value()); - for (auto id : transaction.external_content_source_ids_to_remove) - m_external_content_sources.remove(id.value()); for (auto id : transaction.video_frame_source_ids_to_remove) m_video_frame_sources.remove(id.value()); for (auto id : transaction.display_list_ids_to_remove) @@ -216,9 +192,19 @@ void DisplayListResourceStorage::retain_only(DisplayListResourceSet const& resou { m_fonts.remove_all_matching([&](auto id, auto const&) { return !resource_set.fonts.contains(FontResourceId { id }); }); m_image_frames.remove_all_matching([&](auto id, auto const&) { return !resource_set.image_frames.contains(ImageFrameResourceId { id }); }); - m_external_content_sources.remove_all_matching([&](auto id, auto const&) { return !resource_set.external_content_sources.contains(ExternalContentResourceId { id }); }); m_video_frame_sources.remove_all_matching([&](auto id, auto const&) { return !resource_set.video_frame_sources.contains(VideoFrameResourceId { id }); }); m_display_lists.remove_all_matching([&](auto id, auto const&) { return !resource_set.display_lists.contains(DisplayListResourceId { id }); }); } +void DisplayListResourceStorage::update_compositor_surface(CompositorSurfaceId surface_id, Gfx::SharedImage&& shared_image) +{ + auto shared_image_buffer = Gfx::SharedImageBuffer::import_from_shared_image(move(shared_image)); + m_compositor_surfaces.set(surface_id.value(), Gfx::DecodedImageFrame { *shared_image_buffer.bitmap() }); +} + +void DisplayListResourceStorage::clear_compositor_surface(CompositorSurfaceId surface_id) +{ + m_compositor_surfaces.remove(surface_id.value()); +} + } diff --git a/Libraries/LibWeb/Painting/DisplayListResourceStorage.h b/Libraries/LibWeb/Painting/DisplayListResourceStorage.h index e2f03837c6..ee230a7cea 100644 --- a/Libraries/LibWeb/Painting/DisplayListResourceStorage.h +++ b/Libraries/LibWeb/Painting/DisplayListResourceStorage.h @@ -11,13 +11,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include namespace Web::Painting { @@ -25,7 +25,6 @@ namespace Web::Painting { struct DisplayListResourceSet { HashTable fonts; HashTable image_frames; - HashTable external_content_sources; HashTable video_frame_sources; HashTable display_lists; }; @@ -33,13 +32,11 @@ struct DisplayListResourceSet { struct DisplayListResourceTransaction { Vector> fonts; Vector image_frames; - Vector> external_content_sources; Vector> video_frame_sources; Vector> display_lists; Vector font_ids_to_remove; Vector image_frame_ids_to_remove; - Vector external_content_source_ids_to_remove; Vector video_frame_source_ids_to_remove; Vector display_list_ids_to_remove; }; @@ -54,7 +51,6 @@ public: FontResourceId add_font(Gfx::Font const&); ImageFrameResourceId add_image_frame(Gfx::DecodedImageFrame const&); - ExternalContentResourceId add_external_content_source(NonnullRefPtr); VideoFrameResourceId add_video_frame_source(NonnullRefPtr); DisplayListResourceId add_display_list(NonnullRefPtr); void append_referenced_resources_from(DisplayListResourceStorage const& source, ReadonlyBytes command_bytes); @@ -63,21 +59,23 @@ public: DisplayListResourceSet collect_referenced_resources(DisplayList const&) const; DisplayListResourceSet collect_referenced_resources(ReadonlyBytes command_bytes) const; void retain_only(DisplayListResourceSet const&); + void update_compositor_surface(CompositorSurfaceId, Gfx::SharedImage&&); + void clear_compositor_surface(CompositorSurfaceId); Gfx::Font const& font(FontResourceId id) const { return *m_fonts.get(id.value()).value(); } Gfx::DecodedImageFrame const& image_frame(ImageFrameResourceId id) const { return m_image_frames.get(id.value()).value(); } - ExternalContentSource const& external_content_source(ExternalContentResourceId id) const { return *m_external_content_sources.get(id.value()).value(); } VideoFrameSource const& video_frame_source(VideoFrameResourceId id) const { return *m_video_frame_sources.get(id.value()).value(); } DisplayList const& display_list(DisplayListResourceId id) const { return *m_display_lists.get(id.value()).value(); } + Optional compositor_surface(CompositorSurfaceId id) const { return m_compositor_surfaces.get(id.value()); } private: void collect_referenced_resources(ReadonlyBytes command_bytes, DisplayListResourceSet&) const; HashMap> m_fonts; HashMap m_image_frames; - HashMap> m_external_content_sources; HashMap> m_video_frame_sources; HashMap> m_display_lists; + HashMap m_compositor_surfaces; }; } diff --git a/Libraries/LibWeb/Painting/ExternalContentSource.cpp b/Libraries/LibWeb/Painting/ExternalContentSource.cpp deleted file mode 100644 index ae8dacf4c4..0000000000 --- a/Libraries/LibWeb/Painting/ExternalContentSource.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2026, Aliaksandr Kalenik - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -namespace Web::Painting { - -static Atomic s_next_id { 1 }; - -NonnullRefPtr ExternalContentSource::create() -{ - return adopt_ref(*new ExternalContentSource()); -} - -ExternalContentSource::ExternalContentSource() - : m_id(s_next_id.fetch_add(1, AK::MemoryOrder::memory_order_relaxed)) -{ -} - -void ExternalContentSource::update(Optional frame) -{ - Optional old; - { - Sync::MutexLocker const locker { m_mutex }; - old = move(m_frame); - m_frame = move(frame); - } -} - -void ExternalContentSource::clear() -{ - Optional old; - { - Sync::MutexLocker const locker { m_mutex }; - old = move(m_frame); - } -} - -Optional ExternalContentSource::current_frame() const -{ - Sync::MutexLocker const locker { m_mutex }; - return m_frame; -} - -} diff --git a/Libraries/LibWeb/Painting/ExternalContentSource.h b/Libraries/LibWeb/Painting/ExternalContentSource.h deleted file mode 100644 index 60453a91ac..0000000000 --- a/Libraries/LibWeb/Painting/ExternalContentSource.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2026, Aliaksandr Kalenik - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace Web::Painting { - -class ExternalContentSource final : public AtomicRefCounted { -public: - static NonnullRefPtr create(); - - u64 id() const { return m_id; } - - void update(Optional); - void clear(); - Optional current_frame() const; - -private: - ExternalContentSource(); - - u64 m_id { 0 }; - mutable Sync::Mutex m_mutex; - Optional m_frame; -}; - -} diff --git a/Libraries/LibWeb/Painting/NavigableContainerViewportPaintable.cpp b/Libraries/LibWeb/Painting/NavigableContainerViewportPaintable.cpp index 6bff780797..813e44d286 100644 --- a/Libraries/LibWeb/Painting/NavigableContainerViewportPaintable.cpp +++ b/Libraries/LibWeb/Painting/NavigableContainerViewportPaintable.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include namespace Web::Painting { @@ -48,12 +47,14 @@ void NavigableContainerViewportPaintable::paint(DisplayListRecordingContext& con auto content_navigable = navigable_container.content_navigable(); VERIFY(content_navigable); + if (content_navigable->has_been_destroyed() || !content_navigable->has_compositor_surface_id()) + return; context.display_list_recorder().save(); context.display_list_recorder().add_clip_rect(clip_rect.to_type()); - context.display_list_recorder().draw_external_content( + context.display_list_recorder().draw_compositor_surface( context.enclosing_device_rect(absolute_rect).to_type(), - content_navigable->external_content_source(), + content_navigable->compositor_surface_id(), Gfx::ScalingMode::NearestNeighbor); context.display_list_recorder().restore(); diff --git a/Tests/LibWeb/Text/expected/async-scrolling/nested-navigable-wheel-admission.txt b/Tests/LibWeb/Text/expected/async-scrolling/nested-navigable-wheel-admission.txt index 8de0bcd16b..88f5227da2 100644 --- a/Tests/LibWeb/Text/expected/async-scrolling/nested-navigable-wheel-admission.txt +++ b/Tests/LibWeb/Text/expected/async-scrolling/nested-navigable-wheel-admission.txt @@ -16,6 +16,6 @@ SaveLayer@0 CompositorMainThreadWheelEventRegion@1 rect=[40,40 160x120] Save@1 AddClipRect@1 rect=[40,40 160x120] - DrawExternalContent@1 dst_rect=[40,40 160x120] + DrawCompositorSurface@1 dst_rect=[40,40 160x120] Restore@1 Restore@0 diff --git a/Tests/LibWeb/Text/expected/display_list/canvas-compositor-surface.txt b/Tests/LibWeb/Text/expected/display_list/canvas-compositor-surface.txt new file mode 100644 index 0000000000..9ab662a64b --- /dev/null +++ b/Tests/LibWeb/Text/expected/display_list/canvas-compositor-surface.txt @@ -0,0 +1,12 @@ +AccumulatedVisualContext Tree: + [1] scroll_frame_id=1 (PaintableWithLines(BlockContainer
#out))
+
+DisplayList:
+SaveLayer@0
+  CompositorWheelHitTestTarget@0 target_scroll_frame_index=0 rect=[0,0 800x600]
+  CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[0,0 800x53]
+  CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[8,8 784x32]
+  CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[8,8 784x32]
+  CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[8,8 32x32]
+  DrawCompositorSurface@1 dst_rect=[8,8 32x32]
+Restore@0
diff --git a/Tests/LibWeb/Text/expected/display_list/iframe-compositor-surface.txt b/Tests/LibWeb/Text/expected/display_list/iframe-compositor-surface.txt
new file mode 100644
index 0000000000..21208fc475
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/display_list/iframe-compositor-surface.txt
@@ -0,0 +1,16 @@
+AccumulatedVisualContext Tree:
+  [1] scroll_frame_id=1 (PaintableWithLines(BlockContainer
#out))
+
+DisplayList:
+SaveLayer@0
+  CompositorWheelHitTestTarget@0 target_scroll_frame_index=0 rect=[0,0 800x600]
+  CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[0,0 800x93]
+  CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[0,0 800x80]
+  CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[0,0 800x80]
+  CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[0,0 120x80]
+  CompositorMainThreadWheelEventRegion@1 rect=[0,0 120x80]
+  Save@1
+    AddClipRect@1 rect=[0,0 120x80]
+    DrawCompositorSurface@1 dst_rect=[0,0 120x80]
+  Restore@1
+Restore@0
diff --git a/Tests/LibWeb/Text/input/display_list/canvas-compositor-surface.html b/Tests/LibWeb/Text/input/display_list/canvas-compositor-surface.html
new file mode 100644
index 0000000000..ead0e7e858
--- /dev/null
+++ b/Tests/LibWeb/Text/input/display_list/canvas-compositor-surface.html
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/Tests/LibWeb/Text/input/display_list/iframe-compositor-surface.html b/Tests/LibWeb/Text/input/display_list/iframe-compositor-surface.html
new file mode 100644
index 0000000000..9fb50c2704
--- /dev/null
+++ b/Tests/LibWeb/Text/input/display_list/iframe-compositor-surface.html
@@ -0,0 +1,28 @@
+
+
+
+
+