LibWeb: Present frames through Compositor IPC

Introduce a dedicated Compositor IPC channel between the UI process and
WebContent. Use it for backing-store setup, presented bitmap delivery,
and bitmap-specific ready_to_paint acknowledgements.

This makes CompositorThread the single owner of frame presentation
bookkeeping before async scrolling starts producing frames without the
main thread.

Remove old paint and backing-store messages from WebContentClient and
PageClient so the UI process no longer observes two presentation
protocols.
This commit is contained in:
Andreas Kling 2026-05-11 21:02:29 +02:00 committed by Andreas Kling
parent 584b2748ee
commit 1b810a5e15
20 changed files with 414 additions and 100 deletions

View file

@ -15,6 +15,10 @@
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
#include <LibWeb/Painting/ExternalContentSource.h>
#include <AK/HashMap.h>
#include <AK/NeverDestroyed.h>
#include <AK/Queue.h>
#ifdef USE_VULKAN_DMABUF_IMAGES
# include <AK/Array.h>
# include <LibGfx/VulkanImage.h>
@ -56,7 +60,6 @@ struct UpdateBackingStoresCommand {
Gfx::IntSize size;
i32 front_bitmap_id;
i32 back_bitmap_id;
Function<void(i32, Gfx::SharedImage, i32, Gfx::SharedImage)> allocation_callback;
};
struct ScreenshotCommand {
@ -139,14 +142,23 @@ static ErrorOr<DMABufBackingStorePair> create_linear_dmabuf_backing_stores(Gfx::
class CompositorThread::ThreadData final : public AtomicRefCounted<ThreadData> {
public:
ThreadData(NonnullRefPtr<Core::WeakEventLoopReference>&& main_thread_event_loop, CompositorThread::PresentationCallback presentation_callback)
: m_main_thread_event_loop(move(main_thread_event_loop))
, m_presentation_callback(move(presentation_callback))
ThreadData(u64 page_id, NonnullRefPtr<Core::WeakEventLoopReference>&& main_thread_event_loop, CompositorThread::PagePresentationRegistration page_presentation_registration)
: m_page_id(page_id)
, m_main_thread_event_loop(move(main_thread_event_loop))
, m_presents_to_client(page_presentation_registration == CompositorThread::PagePresentationRegistration::Yes)
{
}
~ThreadData() = default;
u64 page_id() const { return m_page_id; }
bool presents_to_client() const { return m_presents_to_client; }
void stop_presenting_to_client()
{
Sync::MutexLocker const locker { m_mutex };
m_presents_to_client = false;
}
void set_presentation_mode(CompositorThread::PresentationMode mode)
{
Sync::MutexLocker const locker { m_mutex };
@ -267,10 +279,10 @@ public:
}
if (should_present) {
// Block if we already have a frame queued (back pressure)
// Block if we already have a frame queued (back pressure).
{
Sync::MutexLocker const locker { m_mutex };
while (m_queued_rasterization_tasks > 1 && !m_exit) {
while (m_queued_rasterization_tasks > 0 && !m_exit) {
m_ready_to_paint.wait();
}
if (m_exit)
@ -297,10 +309,10 @@ public:
presentation_mode.visit(
[this, viewport_rect, rendered_bitmap_id](CompositorThread::PresentToUI) {
m_queued_rasterization_tasks++;
invoke_on_main_thread([this, viewport_rect, rendered_bitmap_id]() {
m_presentation_callback(viewport_rect, rendered_bitmap_id);
});
if (m_presents_to_client) {
finish_rasterizing(rendered_bitmap_id);
VERIFY(CompositorThread::present_frame_to_client(m_page_id, viewport_rect, rendered_bitmap_id));
}
},
[this](CompositorThread::PublishToExternalContent const& mode) {
auto snapshot = Gfx::DecodedImageFrame { *m_backing_stores.front_store->snapshot_bitmap() };
@ -327,17 +339,16 @@ private:
void publish_backing_store_pair(UpdateBackingStoresCommand& cmd, Gfx::SharedImage front_shared_image, Gfx::SharedImage back_shared_image)
{
if (!cmd.allocation_callback)
if (!m_presents_to_client)
return;
invoke_on_main_thread([callback = move(cmd.allocation_callback), front_bitmap_id = cmd.front_bitmap_id, front_shared_image = move(front_shared_image), back_bitmap_id = cmd.back_bitmap_id, back_shared_image = move(back_shared_image)]() mutable {
callback(front_bitmap_id, move(front_shared_image), back_bitmap_id, move(back_shared_image));
});
VERIFY(CompositorThread::present_backing_stores_to_client(m_page_id, cmd.front_bitmap_id, move(front_shared_image), cmd.back_bitmap_id, move(back_shared_image)));
}
void allocate_backing_stores(UpdateBackingStoresCommand& cmd)
{
#ifdef USE_VULKAN_DMABUF_IMAGES
if (m_skia_backend_context && cmd.allocation_callback) {
if (m_skia_backend_context && m_presents_to_client) {
auto backing_stores = create_linear_dmabuf_backing_stores(cmd.size, *m_skia_backend_context);
if (!backing_stores.is_error()) {
auto backing_store_pair = backing_stores.release_value();
@ -372,8 +383,9 @@ private:
});
}
u64 m_page_id { 0 };
NonnullRefPtr<Core::WeakEventLoopReference> m_main_thread_event_loop;
CompositorThread::PresentationCallback m_presentation_callback;
bool m_presents_to_client { false };
mutable Sync::Mutex m_mutex;
mutable Sync::ConditionVariable m_command_ready { m_mutex };
@ -389,6 +401,7 @@ private:
CompositorThread::PresentationMode m_presentation_mode { CompositorThread::PresentToUI {} };
Atomic<i32> m_queued_rasterization_tasks { 0 };
Optional<i32> m_presented_bitmap_id_awaiting_ack;
mutable Sync::ConditionVariable m_ready_to_paint { m_mutex };
bool m_needs_present { false };
@ -399,25 +412,180 @@ private:
mutable Sync::ConditionVariable m_frame_completed { m_mutex };
public:
void decrement_queued_tasks()
void finish_rasterizing(i32 bitmap_id)
{
Sync::MutexLocker const locker { m_mutex };
VERIFY(m_queued_rasterization_tasks >= 1 && m_queued_rasterization_tasks <= 2);
VERIFY(!m_presented_bitmap_id_awaiting_ack.has_value());
m_presented_bitmap_id_awaiting_ack = bitmap_id;
m_queued_rasterization_tasks++;
VERIFY(m_queued_rasterization_tasks == 1);
}
void decrement_queued_tasks(i32 bitmap_id)
{
Sync::MutexLocker const locker { m_mutex };
if (m_presented_bitmap_id_awaiting_ack != bitmap_id)
return;
VERIFY(m_queued_rasterization_tasks == 1);
m_presented_bitmap_id_awaiting_ack.clear();
m_queued_rasterization_tasks--;
m_ready_to_paint.signal();
m_command_ready.signal();
}
};
CompositorThread::CompositorThread(PresentationCallback presentation_callback)
: m_thread_data(adopt_ref(*new ThreadData(Core::EventLoop::current_weak(), move(presentation_callback))))
struct FramePresentationState {
RefPtr<Core::WeakEventLoopReference> event_loop;
CompositorThread::BackingStorePresentationCallback backing_store_callback;
CompositorThread::FramePresentationCallback frame_callback;
};
static Sync::Mutex& compositor_presentation_state_mutex()
{
static NeverDestroyed<Sync::Mutex> mutex;
return *mutex;
}
static HashMap<u64, NonnullRefPtr<CompositorThread::ThreadData>>& page_compositors()
{
static NeverDestroyed<HashMap<u64, NonnullRefPtr<CompositorThread::ThreadData>>> compositors;
return *compositors;
}
static FramePresentationState& frame_presentation_state()
{
static NeverDestroyed<FramePresentationState> state;
return *state;
}
CompositorThread::CompositorThread(u64 page_id, PagePresentationRegistration page_presentation_registration)
: m_thread_data(adopt_ref(*new ThreadData(page_id, Core::EventLoop::current_weak(), page_presentation_registration)))
{
if (page_presentation_registration == PagePresentationRegistration::Yes)
register_page_compositor(page_id, m_thread_data);
}
CompositorThread::~CompositorThread()
{
unregister_page_compositor(m_thread_data->page_id(), *m_thread_data);
m_thread_data->exit();
}
void CompositorThread::register_page_compositor(u64 page_id, NonnullRefPtr<ThreadData> thread_data)
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
page_compositors().set(page_id, move(thread_data));
}
void CompositorThread::unregister_page_compositor(u64 page_id, ThreadData& thread_data)
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
auto compositor = page_compositors().find(page_id);
if (compositor == page_compositors().end())
return;
if (compositor->value.ptr() != &thread_data)
return;
page_compositors().remove(compositor);
}
void CompositorThread::set_frame_presentation_callbacks(NonnullRefPtr<Core::WeakEventLoopReference> event_loop, BackingStorePresentationCallback backing_store_callback, FramePresentationCallback frame_callback)
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
auto& state = frame_presentation_state();
state.event_loop = move(event_loop);
state.backing_store_callback = move(backing_store_callback);
state.frame_callback = move(frame_callback);
}
void CompositorThread::clear_frame_presentation_callbacks()
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
auto& state = frame_presentation_state();
state.event_loop = nullptr;
state.backing_store_callback = {};
state.frame_callback = {};
}
bool CompositorThread::present_backing_stores_to_client(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage&& front_shared_image, i32 back_bitmap_id, Gfx::SharedImage&& back_shared_image)
{
RefPtr<Core::WeakEventLoopReference> event_loop_reference;
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
if (!page_compositors().contains(page_id))
return false;
auto& state = frame_presentation_state();
if (!state.backing_store_callback)
return false;
event_loop_reference = state.event_loop;
}
if (!event_loop_reference)
return false;
auto event_loop = event_loop_reference->take();
if (!event_loop)
return false;
event_loop->deferred_invoke([page_id, front_bitmap_id, front_shared_image = move(front_shared_image), back_bitmap_id, back_shared_image = move(back_shared_image)]() mutable {
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
if (!page_compositors().contains(page_id)) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Dropping queued UI backing stores for page {} front={} back={}: page unregistered",
page_id, front_bitmap_id, back_bitmap_id);
return;
}
auto& state = frame_presentation_state();
if (state.backing_store_callback)
state.backing_store_callback(page_id, front_bitmap_id, move(front_shared_image), back_bitmap_id, move(back_shared_image));
});
return true;
}
bool CompositorThread::present_frame_to_client(u64 page_id, Gfx::IntRect const& viewport_rect, i32 bitmap_id)
{
RefPtr<Core::WeakEventLoopReference> event_loop_reference;
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
if (!page_compositors().contains(page_id))
return false;
auto& state = frame_presentation_state();
if (!state.frame_callback)
return false;
event_loop_reference = state.event_loop;
}
if (!event_loop_reference)
return false;
auto event_loop = event_loop_reference->take();
if (!event_loop)
return false;
event_loop->deferred_invoke([page_id, viewport_rect, bitmap_id] {
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
if (!page_compositors().contains(page_id)) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Dropping queued UI present for page {} bitmap {}: page unregistered",
page_id, bitmap_id);
return;
}
auto& state = frame_presentation_state();
if (state.frame_callback)
state.frame_callback(page_id, viewport_rect, bitmap_id);
});
return true;
}
void CompositorThread::presented_bitmap_ready_to_paint(u64 page_id, i32 bitmap_id)
{
RefPtr<ThreadData> thread_data;
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
auto compositor = page_compositors().find(page_id);
if (compositor == page_compositors().end())
return;
thread_data = compositor->value;
}
thread_data->decrement_queued_tasks(bitmap_id);
}
void CompositorThread::start(DisplayListPlayerType display_list_player_type)
{
m_thread = Threading::Thread::construct("Compositor"sv, [thread_data = m_thread_data, display_list_player_type] {
@ -433,6 +601,12 @@ void CompositorThread::set_presentation_mode(PresentationMode mode)
m_thread_data->set_presentation_mode(move(mode));
}
void CompositorThread::stop_presenting_to_client()
{
m_thread_data->stop_presenting_to_client();
unregister_page_compositor(m_thread_data->page_id(), *m_thread_data);
}
void CompositorThread::update_display_list(NonnullRefPtr<Painting::DisplayList> display_list, Painting::ScrollStateSnapshot&& scroll_state_snapshot)
{
m_thread_data->enqueue_command(UpdateDisplayListCommand { move(display_list), move(scroll_state_snapshot) });
@ -443,9 +617,9 @@ void CompositorThread::update_scroll_state(Painting::ScrollStateSnapshot&& scrol
m_thread_data->enqueue_command(UpdateScrollStateCommand { move(scroll_state_snapshot) });
}
void CompositorThread::update_backing_stores(Gfx::IntSize size, i32 front_id, i32 back_id, Function<void(i32, Gfx::SharedImage, i32, Gfx::SharedImage)>&& allocation_callback)
void CompositorThread::update_backing_stores(Gfx::IntSize size, i32 front_id, i32 back_id)
{
m_thread_data->enqueue_command(UpdateBackingStoresCommand { size, front_id, back_id, move(allocation_callback) });
m_thread_data->enqueue_command(UpdateBackingStoresCommand { size, front_id, back_id });
}
u64 CompositorThread::present_frame(Gfx::IntRect viewport_rect)
@ -463,9 +637,4 @@ void CompositorThread::request_screenshot(NonnullRefPtr<Gfx::PaintingSurface> ta
m_thread_data->enqueue_command(ScreenshotCommand { move(target_surface), move(callback) });
}
void CompositorThread::ready_to_paint()
{
m_thread_data->decrement_queued_tasks();
}
}

View file

@ -8,24 +8,32 @@
#include <AK/Noncopyable.h>
#include <AK/NonnullRefPtr.h>
#include <AK/Queue.h>
#include <AK/Variant.h>
#include <LibCore/Forward.h>
#include <LibGfx/Rect.h>
#include <LibGfx/SharedImage.h>
#include <LibSync/ConditionVariable.h>
#include <LibThreading/Forward.h>
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Page/Page.h>
namespace Web::Compositor {
class CompositorThread {
class WEB_API CompositorThread {
AK_MAKE_NONCOPYABLE(CompositorThread);
AK_MAKE_NONMOVABLE(CompositorThread);
public:
class ThreadData;
public:
using PresentationCallback = Function<void(Gfx::IntRect const&, i32)>;
enum class PagePresentationRegistration {
No,
Yes,
};
using BackingStorePresentationCallback = Function<void(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage, i32 back_bitmap_id, Gfx::SharedImage)>;
using FramePresentationCallback = Function<void(u64 page_id, Gfx::IntRect const&, i32 bitmap_id)>;
struct PresentToUI {
};
struct PublishToExternalContent {
@ -33,24 +41,32 @@ public:
};
using PresentationMode = Variant<PresentToUI, PublishToExternalContent>;
explicit CompositorThread(PresentationCallback);
CompositorThread(u64 page_id, PagePresentationRegistration);
~CompositorThread();
static void set_frame_presentation_callbacks(NonnullRefPtr<Core::WeakEventLoopReference>, BackingStorePresentationCallback, FramePresentationCallback);
static void clear_frame_presentation_callbacks();
static void presented_bitmap_ready_to_paint(u64 page_id, i32 bitmap_id);
void start(DisplayListPlayerType);
void stop_presenting_to_client();
void set_presentation_mode(PresentationMode);
void update_display_list(NonnullRefPtr<Painting::DisplayList>, Painting::ScrollStateSnapshot&&);
void update_scroll_state(Painting::ScrollStateSnapshot&&);
void update_backing_stores(Gfx::IntSize, i32 front_id, i32 back_id, Function<void(i32, Gfx::SharedImage, i32, Gfx::SharedImage)>&& = {});
void update_backing_stores(Gfx::IntSize, i32 front_id, i32 back_id);
u64 present_frame(Gfx::IntRect);
void wait_for_frame(u64 frame_id);
void request_screenshot(NonnullRefPtr<Gfx::PaintingSurface>, Function<void()>&& callback);
void ready_to_paint();
private:
NonnullRefPtr<ThreadData> m_thread_data;
RefPtr<Threading::Thread> m_thread;
static void register_page_compositor(u64 page_id, NonnullRefPtr<ThreadData>);
static void unregister_page_compositor(u64 page_id, ThreadData&);
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);
};
}

View file

@ -273,15 +273,17 @@ bool Navigable::is_ancestor_of(GC::Ref<Navigable> other) const
return false;
}
Navigable::Navigable(GC::Ref<Page> page, bool is_svg_page)
Navigable::Navigable(
GC::Ref<Page> page,
bool is_svg_page,
Compositor::CompositorThread::PagePresentationRegistration page_presentation_registration)
: m_page(page)
, m_event_handler({}, *this)
, m_is_svg_page(is_svg_page)
, m_backing_store_manager(heap().allocate<Painting::BackingStoreManager>(*this))
, m_rendering_thread([page_client = &page->client()](Gfx::IntRect const& viewport_rect, i32 bitmap_id) {
if (page_client)
page_client->page_did_paint(viewport_rect, bitmap_id);
})
, m_rendering_thread(
is_svg_page ? 0 : page->client().id(),
is_svg_page ? Compositor::CompositorThread::PagePresentationRegistration::No : page_presentation_registration)
{
all_navigables().set(*this);
@ -3106,11 +3108,6 @@ void Navigable::set_has_session_history_entry_and_ready_for_navigation()
}
}
void Navigable::ready_to_paint()
{
m_rendering_thread.ready_to_paint();
}
NonnullRefPtr<Painting::ExternalContentSource> Navigable::external_content_source() const
{
VERIFY(m_external_content_source);

View file

@ -229,7 +229,6 @@ public:
bool has_pending_navigations() const { return !m_pending_navigations.is_empty(); }
void clear_pending_navigations() { m_pending_navigations.clear(); }
void ready_to_paint();
void record_display_list_and_scroll_state(PaintConfig);
void paint_next_frame();
void render_screenshot(Gfx::PaintingSurface&, PaintConfig, Function<void()>&& callback);
@ -259,7 +258,10 @@ public:
void reset_zoom();
protected:
explicit Navigable(GC::Ref<Page>, bool is_svg_page);
explicit Navigable(
GC::Ref<Page>,
bool is_svg_page,
Compositor::CompositorThread::PagePresentationRegistration = Compositor::CompositorThread::PagePresentationRegistration::No);
virtual void visit_edges(Cell::Visitor&) override;
virtual void finalize() override;

View file

@ -35,7 +35,10 @@ namespace Web::HTML {
GC_DEFINE_ALLOCATOR(TraversableNavigable);
TraversableNavigable::TraversableNavigable(GC::Ref<Page> page)
: Navigable(page, page->client().is_svg_page_client())
: Navigable(
page,
page->client().is_svg_page_client(),
Compositor::CompositorThread::PagePresentationRegistration::Yes)
, m_storage_shed(StorageAPI::StorageShed::create(page->heap()))
, m_session_history_traversal_queue(vm().heap().allocate<SessionHistoryTraversalQueue>())
{

View file

@ -20,7 +20,6 @@
#include <LibGfx/Point.h>
#include <LibGfx/Rect.h>
#include <LibGfx/ShareableBitmap.h>
#include <LibGfx/SharedImage.h>
#include <LibGfx/Size.h>
#include <LibHTTP/Cookie/Cookie.h>
#include <LibHTTP/Forward.h>
@ -450,7 +449,6 @@ public:
virtual void page_did_request_activate_tab() { }
virtual void page_did_close_top_level_traversable() { }
virtual void page_did_update_navigation_buttons_state([[maybe_unused]] bool back_enabled, [[maybe_unused]] bool forward_enabled) { }
virtual void page_did_allocate_backing_stores([[maybe_unused]] i32 front_bitmap_id, [[maybe_unused]] Gfx::SharedImage front_backing_store, [[maybe_unused]] i32 back_bitmap_id, [[maybe_unused]] Gfx::SharedImage back_backing_store) { }
virtual void request_file(FileRequest) = 0;
@ -490,7 +488,6 @@ public:
virtual void page_did_mutate_dom([[maybe_unused]] FlyString const& type, [[maybe_unused]] DOM::Node const& target, [[maybe_unused]] DOM::NodeList& added_nodes, [[maybe_unused]] DOM::NodeList& removed_nodes, [[maybe_unused]] GC::Ptr<DOM::Node> previous_sibling, [[maybe_unused]] GC::Ptr<DOM::Node> next_sibling, [[maybe_unused]] Optional<String> const& attribute_name) { }
virtual void page_did_paint([[maybe_unused]] Gfx::IntRect const& content_rect, [[maybe_unused]] i32 bitmap_id) { }
virtual void page_did_take_screenshot(Gfx::ShareableBitmap const&) { }
virtual void received_message_from_web_ui([[maybe_unused]] String const& name, [[maybe_unused]] JS::Value data) { }

View file

@ -26,6 +26,14 @@ void BackingStoreManager::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_navigable);
}
void BackingStoreManager::finalize()
{
Base::finalize();
m_backing_store_shrink_timer->on_timeout = {};
m_backing_store_shrink_timer->stop();
m_backing_store_shrink_timer.clear();
}
void BackingStoreManager::restart_resize_timer()
{
m_backing_store_shrink_timer->restart();
@ -37,15 +45,7 @@ void BackingStoreManager::reallocate_backing_stores(Gfx::IntSize size)
m_back_bitmap_id = m_next_bitmap_id++;
m_allocated_size = size;
Function<void(i32, Gfx::SharedImage, i32, Gfx::SharedImage)> allocation_callback;
if (m_navigable->is_top_level_traversable()) {
auto* page_client = &m_navigable->top_level_traversable()->page().client();
allocation_callback = [page_client](i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store) mutable {
page_client->page_did_allocate_backing_stores(front_bitmap_id, move(front_backing_store), back_bitmap_id, move(back_backing_store));
};
}
m_navigable->rendering_thread().update_backing_stores(size, m_front_bitmap_id, m_back_bitmap_id, move(allocation_callback));
m_navigable->rendering_thread().update_backing_stores(size, m_front_bitmap_id, m_back_bitmap_id);
}
void BackingStoreManager::resize_backing_stores_if_needed(WindowResizingInProgress window_resize_in_progress)

View file

@ -15,6 +15,8 @@ class WEB_API BackingStoreManager : public JS::Cell {
GC_DECLARE_ALLOCATOR(BackingStoreManager);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
enum class WindowResizingInProgress {
No,
Yes
@ -23,6 +25,7 @@ public:
void reallocate_backing_stores(Gfx::IntSize);
void restart_resize_timer();
virtual void finalize() override;
virtual void visit_edges(Cell::Visitor& visitor) override;
BackingStoreManager(HTML::Navigable&);

View file

@ -56,6 +56,8 @@ set(GENERATED_SOURCES
${GENERATED_SOURCES}
../../Services/RequestServer/RequestClientEndpoint.h
../../Services/RequestServer/RequestServerEndpoint.h
../../Services/WebContent/CompositorClientEndpoint.h
../../Services/WebContent/CompositorServerEndpoint.h
../../Services/WebContent/WebContentClientEndpoint.h
../../Services/WebContent/WebContentServerEndpoint.h
../../Services/WebContent/WebDriverClientEndpoint.h
@ -67,8 +69,10 @@ set(GENERATED_SOURCES
UIProcessServerEndpoint.h
)
compile_ipc(${LADYBIRD_SOURCE_DIR}/Services/WebContent/WebContentServer.ipc ${CMAKE_BINARY_DIR}/Services/WebContent/WebContentServerEndpoint.h)
compile_ipc(${LADYBIRD_SOURCE_DIR}/Services/WebContent/CompositorClient.ipc ${CMAKE_BINARY_DIR}/Services/WebContent/CompositorClientEndpoint.h)
compile_ipc(${LADYBIRD_SOURCE_DIR}/Services/WebContent/CompositorServer.ipc ${CMAKE_BINARY_DIR}/Services/WebContent/CompositorServerEndpoint.h)
compile_ipc(${LADYBIRD_SOURCE_DIR}/Services/WebContent/WebContentClient.ipc ${CMAKE_BINARY_DIR}/Services/WebContent/WebContentClientEndpoint.h)
compile_ipc(${LADYBIRD_SOURCE_DIR}/Services/WebContent/WebContentServer.ipc ${CMAKE_BINARY_DIR}/Services/WebContent/WebContentServerEndpoint.h)
compile_ipc(${LADYBIRD_SOURCE_DIR}/Services/WebContent/WebDriverClient.ipc ${CMAKE_BINARY_DIR}/Services/WebContent/WebDriverClientEndpoint.h)
compile_ipc(${LADYBIRD_SOURCE_DIR}/Services/WebContent/WebDriverServer.ipc ${CMAKE_BINARY_DIR}/Services/WebContent/WebDriverServerEndpoint.h)
compile_ipc(${LADYBIRD_SOURCE_DIR}/Services/WebContent/WebUIClient.ipc ${CMAKE_BINARY_DIR}/Services/WebContent/WebUIClientEndpoint.h)

View file

@ -147,16 +147,19 @@ void ViewImplementation::create_new_process_for_cross_site_navigation(URL::URL c
void ViewImplementation::server_did_paint(Badge<WebContentClient>, i32 bitmap_id, Gfx::IntSize size)
{
bool did_swap_bitmap = false;
if (m_client_state.back_bitmap.id == bitmap_id) {
m_client_state.has_usable_bitmap = true;
m_client_state.back_bitmap.last_painted_size = size.to_type<Web::DevicePixels>();
swap(m_client_state.back_bitmap, m_client_state.front_bitmap);
m_backup_shared_image_buffer = nullptr;
if (on_ready_to_paint)
on_ready_to_paint();
did_swap_bitmap = true;
}
client().async_ready_to_paint(page_id());
client().notify_presented_bitmap_ready_to_paint(page_id(), bitmap_id);
if (did_swap_bitmap && on_ready_to_paint)
on_ready_to_paint();
}
void ViewImplementation::set_window_position(Gfx::IntPoint position)

View file

@ -5,7 +5,9 @@
*/
#include <AK/Debug.h>
#include <AK/NeverDestroyed.h>
#include <LibHTTP/Cookie/ParsedCookie.h>
#include <LibIPC/Transport.h>
#include <LibIPC/TransportHandle.h>
#include <LibWebView/Application.h>
#include <LibWebView/CookieJar.h>
@ -15,11 +17,60 @@
#include <LibWebView/ViewImplementation.h>
#include <LibWebView/WebContentClient.h>
#include <LibWebView/WebUI.h>
#include <WebContent/CompositorClientEndpoint.h>
#include <WebContent/CompositorServerEndpoint.h>
namespace WebView {
HashTable<WebContentClient*> WebContentClient::s_clients;
class CompositorConnectionToServer final
: public IPC::ConnectionToServer<CompositorClientEndpoint, CompositorServerEndpoint>
, public CompositorClientEndpoint {
C_OBJECT(CompositorConnectionToServer)
public:
CompositorConnectionToServer(WebContentClient& web_content_client, NonnullOwnPtr<IPC::Transport> transport)
: IPC::ConnectionToServer<CompositorClientEndpoint, CompositorServerEndpoint>(*this, move(transport))
, m_web_content_client(web_content_client)
{
}
private:
virtual void die() override { }
virtual void did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store) override
{
m_web_content_client.did_present_backing_stores(page_id, front_bitmap_id, move(front_backing_store), back_bitmap_id, move(back_backing_store));
}
virtual void did_paint(u64 page_id, Gfx::IntRect content_rect, i32 bitmap_id) override
{
m_web_content_client.did_present_bitmap(page_id, content_rect, bitmap_id);
}
WebContentClient& m_web_content_client;
};
static HashMap<WebContentClient*, NonnullRefPtr<CompositorConnectionToServer>>& compositor_connections()
{
static NeverDestroyed<HashMap<WebContentClient*, NonnullRefPtr<CompositorConnectionToServer>>> connections;
return *connections;
}
static void initialize_compositor_connection(WebContentClient& web_content_client)
{
auto paired_transport = IPC::Transport::create_paired();
if (paired_transport.is_error()) {
dbgln("Failed to create compositor IPC transport: {}", paired_transport.error());
return;
}
auto connection = CompositorConnectionToServer::construct(web_content_client, move(paired_transport.value().local));
compositor_connections().set(&web_content_client, connection);
web_content_client.async_connect_to_compositor(move(paired_transport.value().remote_handle));
}
static Optional<String> history_title(Utf16String const& title, URL::URL const& url)
{
if (title.is_empty())
@ -37,16 +88,19 @@ WebContentClient::WebContentClient(NonnullOwnPtr<IPC::Transport> transport, View
{
s_clients.set(this);
m_views.set(0, view);
initialize_compositor_connection(*this);
}
WebContentClient::WebContentClient(NonnullOwnPtr<IPC::Transport> transport)
: IPC::ConnectionToServer<WebContentClientEndpoint, WebContentServerEndpoint>(*this, move(transport))
{
s_clients.set(this);
initialize_compositor_connection(*this);
}
WebContentClient::~WebContentClient()
{
compositor_connections().remove(this);
s_clients.remove(this);
}
@ -103,10 +157,26 @@ void WebContentClient::notify_all_views_of_crash()
}
}
void WebContentClient::did_paint(u64 page_id, Gfx::IntRect rect, i32 bitmap_id)
void WebContentClient::notify_presented_bitmap_ready_to_paint(u64 page_id, i32 bitmap_id)
{
auto connection = compositor_connections().get(this);
if (!connection.has_value() || !connection.value()->is_open()) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI compositor IPC unavailable for ready_to_paint page {} bitmap {} (connection={}, open={})",
page_id, bitmap_id, connection.has_value(), connection.has_value() && connection.value()->is_open());
return;
}
connection.value()->async_ready_to_paint(page_id, bitmap_id);
}
void WebContentClient::did_present_bitmap(u64 page_id, Gfx::IntRect rect, i32 bitmap_id)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->server_did_paint({}, bitmap_id, rect.size());
} else {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI dropping did_paint for page {} bitmap {}: no view",
page_id, bitmap_id);
notify_presented_bitmap_ready_to_paint(page_id, bitmap_id);
}
}
void WebContentClient::did_request_new_process_for_navigation(u64 page_id, URL::URL url)
@ -846,7 +916,7 @@ void WebContentClient::did_update_navigation_buttons_state(u64 page_id, bool bac
view->did_update_navigation_buttons_state({}, back_enabled, forward_enabled);
}
void WebContentClient::did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store)
void WebContentClient::did_present_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->did_allocate_backing_stores({}, front_bitmap_id, move(front_backing_store), back_bitmap_id, move(back_backing_store));

View file

@ -58,6 +58,9 @@ public:
void web_ui_disconnected(Badge<WebUI>);
void notify_all_views_of_crash();
void notify_presented_bitmap_ready_to_paint(u64 page_id, i32 bitmap_id);
void did_present_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store);
void did_present_bitmap(u64 page_id, Gfx::IntRect, i32 bitmap_id);
pid_t pid() const { return m_process_handle.pid; }
void set_pid(pid_t pid) { m_process_handle.pid = pid; }
@ -67,7 +70,6 @@ private:
virtual void die() override;
virtual void did_paint(u64 page_id, Gfx::IntRect, i32) override;
virtual void did_request_new_process_for_navigation(u64 page_id, URL::URL url) override;
virtual void did_finish_loading(u64 page_id, URL::URL) override;
virtual void did_request_refresh(u64 page_id) override;
@ -153,7 +155,6 @@ private:
virtual void did_request_clipboard_entries(u64 page_id, u64 request_id) override;
virtual void did_change_audio_play_state(u64 page_id, Web::HTML::AudioPlayState) override;
virtual void did_update_navigation_buttons_state(u64 page_id, bool back_enabled, bool forward_enabled) override;
virtual void did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store) override;
virtual Messages::WebContentClient::RequestWorkerAgentResponse request_worker_agent(u64 page_id, Web::Bindings::AgentType worker_type) override;
Optional<ViewImplementation&> view_for_page_id(u64, SourceLocation = SourceLocation::current());

View file

@ -0,0 +1,8 @@
#include <LibGfx/Rect.h>
#include <LibGfx/SharedImage.h>
endpoint CompositorClient
{
did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store) =|
did_paint(u64 page_id, Gfx::IntRect content_rect, i32 bitmap_id) =|
}

View file

@ -0,0 +1,4 @@
endpoint CompositorServer
{
ready_to_paint(u64 page_id, i32 bitmap_id) =|
}

View file

@ -10,6 +10,7 @@
*/
#include <AK/JsonObject.h>
#include <AK/OwnPtr.h>
#include <AK/QuickSort.h>
#include <LibCore/EventLoop.h>
#include <LibCore/System.h>
@ -19,6 +20,8 @@
#include <LibGfx/SystemTheme.h>
#include <LibJS/Runtime/ConsoleObject.h>
#include <LibJS/Runtime/Date.h>
#include <LibSync/ConditionVariable.h>
#include <LibThreading/Thread.h>
#include <LibUnicode/TimeZone.h>
#include <LibWeb/ARIA/RoleType.h>
#include <LibWeb/Bindings/MainThreadVM.h>
@ -27,6 +30,7 @@
#include <LibWeb/CSS/Parser/ErrorReporter.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/CSS/StyleSheetList.h>
#include <LibWeb/Compositor/CompositorThread.h>
#include <LibWeb/CookieStore/CookieStore.h>
#include <LibWeb/DOM/Attr.h>
#include <LibWeb/DOM/CharacterData.h>
@ -59,6 +63,8 @@
#include <LibWeb/Worker/WebWorkerClient.h>
#include <LibWebView/Attribute.h>
#include <LibWebView/ViewImplementation.h>
#include <WebContent/CompositorClientEndpoint.h>
#include <WebContent/CompositorServerEndpoint.h>
#include <WebContent/ConnectionFromClient.h>
#include <WebContent/PageClient.h>
#include <WebContent/PageHost.h>
@ -66,6 +72,31 @@
namespace WebContent {
class CompositorConnectionFromClient final
: public IPC::ConnectionFromClient<CompositorClientEndpoint, CompositorServerEndpoint> {
C_OBJECT(CompositorConnectionFromClient)
public:
virtual void die() override { }
private:
explicit CompositorConnectionFromClient(NonnullOwnPtr<IPC::Transport> transport)
: IPC::ConnectionFromClient<CompositorClientEndpoint, CompositorServerEndpoint>(*this, move(transport), 1)
{
}
virtual void ready_to_paint(u64 page_id, i32 bitmap_id) override
{
Web::Compositor::CompositorThread::presented_bitmap_ready_to_paint(page_id, bitmap_id);
}
};
struct CompositorIPCStartupState {
Sync::Mutex mutex;
Sync::ConditionVariable ready { mutex };
bool did_install_presentation_callbacks { false };
};
ConnectionFromClient::ConnectionFromClient(NonnullOwnPtr<IPC::Transport> transport)
: IPC::ConnectionFromClient<WebContentClientEndpoint, WebContentServerEndpoint>(*this, move(transport), 1)
, m_page_host(PageHost::create(*this))
@ -148,6 +179,40 @@ void ConnectionFromClient::connect_to_image_decoder(IPC::TransportHandle handle)
on_image_decoder_connection(handle);
}
void ConnectionFromClient::connect_to_compositor(IPC::TransportHandle handle)
{
auto startup_state = make<CompositorIPCStartupState>();
auto thread = Threading::Thread::construct("CompositorIPC"sv, [handle = move(handle), startup_state = startup_state.ptr()]() mutable {
Core::EventLoop event_loop;
auto transport = MUST(handle.create_transport());
auto connection = CompositorConnectionFromClient::construct(move(transport));
Web::Compositor::CompositorThread::set_frame_presentation_callbacks(
Core::EventLoop::current_weak(),
[connection = connection.ptr()](u64 page_id, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store) {
connection->async_did_allocate_backing_stores(page_id, front_bitmap_id, move(front_backing_store), back_bitmap_id, move(back_backing_store));
},
[connection = connection.ptr()](u64 page_id, Gfx::IntRect const& viewport_rect, i32 bitmap_id) {
connection->async_did_paint(page_id, viewport_rect, bitmap_id);
});
{
Sync::MutexLocker const locker { startup_state->mutex };
startup_state->did_install_presentation_callbacks = true;
startup_state->ready.signal();
}
auto result = event_loop.exec();
Web::Compositor::CompositorThread::clear_frame_presentation_callbacks();
return result;
});
thread->start();
{
Sync::MutexLocker const locker { startup_state->mutex };
while (!startup_state->did_install_presentation_callbacks)
startup_state->ready.wait();
}
thread->detach();
}
void ConnectionFromClient::connect_to_request_server(IPC::TransportHandle handle)
{
if (on_request_server_connection)
@ -206,12 +271,6 @@ void ConnectionFromClient::set_viewport(u64 page_id, Web::DevicePixelSize size,
}
}
void ConnectionFromClient::ready_to_paint(u64 page_id)
{
if (auto page = this->page(page_id); page.has_value())
page->ready_to_paint();
}
void ConnectionFromClient::key_event(u64 page_id, Web::KeyEvent event)
{
enqueue_input_event({ page_id, move(event), 0 });

View file

@ -66,6 +66,7 @@ private:
virtual void connect_to_web_ui(u64 page_id, IPC::TransportHandle handle) override;
virtual void connect_to_request_server(IPC::TransportHandle handle) override;
virtual void connect_to_image_decoder(IPC::TransportHandle handle) override;
virtual void connect_to_compositor(IPC::TransportHandle handle) override;
virtual void update_system_theme(u64 page_id, Core::AnonymousBuffer) override;
virtual void update_screen_rects(u64 page_id, Vector<Web::DevicePixelRect>, u32) override;
virtual void load_url(u64 page_id, URL::URL) override;
@ -77,7 +78,6 @@ private:
virtual void mouse_event(u64 page_id, Web::MouseEvent) override;
virtual void drag_event(u64 page_id, Web::DragEvent) override;
virtual void pinch_event(u64 page_id, Web::PinchEvent) override;
virtual void ready_to_paint(u64 page_id) override;
virtual void debug_request(u64 page_id, ByteString, ByteString) override;
virtual void get_source(u64 page_id) override;
virtual void inspect_dom_tree(u64 page_id) override;

View file

@ -188,11 +188,6 @@ void PageClient::set_window_size(Web::DevicePixelSize size)
page().set_window_size(size);
}
void PageClient::ready_to_paint()
{
page().top_level_traversable()->ready_to_paint();
}
Queue<Web::QueuedInputEvent>& PageClient::input_event_queue()
{
return client().input_event_queue();
@ -681,6 +676,8 @@ void PageClient::page_did_request_activate_tab()
void PageClient::page_did_close_top_level_traversable()
{
page().top_level_traversable()->rendering_thread().stop_presenting_to_client();
// FIXME: Rename this IPC call
client().async_did_close_browsing_context(m_id);
@ -734,11 +731,6 @@ void PageClient::page_did_change_audio_play_state(Web::HTML::AudioPlayState play
client().async_did_change_audio_play_state(m_id, play_state);
}
void PageClient::page_did_allocate_backing_stores(i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store)
{
client().async_did_allocate_backing_stores(m_id, front_bitmap_id, move(front_backing_store), back_bitmap_id, move(back_backing_store));
}
Web::PageClient::WorkerAgentResponse PageClient::request_worker_agent(Web::Bindings::AgentType type)
{
auto response = client().send_sync_but_allow_failure<Messages::WebContentClient::RequestWorkerAgent>(m_id, type);
@ -788,11 +780,6 @@ void PageClient::page_did_mutate_dom(FlyString const& type, Web::DOM::Node const
client().async_did_mutate_dom(m_id, { type.to_string(), target.unique_id(), move(serialized_target), mutation.release_value() });
}
void PageClient::page_did_paint(Gfx::IntRect const& content_rect, i32 bitmap_id)
{
client().async_did_paint(m_id, content_rect, bitmap_id);
}
void PageClient::page_did_take_screenshot(Gfx::ShareableBitmap const& screenshot)
{
client().async_did_take_screenshot(m_id, screenshot);

View file

@ -9,7 +9,6 @@
#pragma once
#include <LibGfx/Rect.h>
#include <LibGfx/SharedImage.h>
#include <LibWeb/CSS/StyleSheetIdentifier.h>
#include <LibWeb/HTML/AudioPlayState.h>
#include <LibWeb/HTML/FileFilter.h>
@ -85,8 +84,6 @@ public:
void did_disconnect_devtools_client();
bool has_devtools_client() const { return m_devtools_client_count > 0; }
void ready_to_paint();
void initialize_js_console(Web::DOM::Document& document);
void js_console_input(StringView js_source);
void did_execute_js_console_input(JsonValue const&);
@ -189,10 +186,8 @@ private:
virtual void page_did_insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation const&, StringView presentation_style) override;
virtual void page_did_request_clipboard_entries(u64 request_id) override;
virtual void page_did_change_audio_play_state(Web::HTML::AudioPlayState) override;
virtual void page_did_allocate_backing_stores(i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store) override;
virtual WorkerAgentResponse request_worker_agent(Web::Bindings::AgentType) override;
virtual void page_did_mutate_dom(FlyString const& type, Web::DOM::Node const& target, Web::DOM::NodeList& added_nodes, Web::DOM::NodeList& removed_nodes, GC::Ptr<Web::DOM::Node> previous_sibling, GC::Ptr<Web::DOM::Node> next_sibling, Optional<String> const& attribute_name) override;
virtual void page_did_paint(Gfx::IntRect const& content_rect, i32 bitmap_id) override;
virtual void page_did_take_screenshot(Gfx::ShareableBitmap const& screenshot) override;
virtual void received_message_from_web_ui(String const& name, JS::Value data) override;
virtual void page_did_start_network_request(u64 request_id, URL::URL const&, ByteString const&, Vector<HTTP::Header> const&, ReadonlyBytes, Optional<String>) override;

View file

@ -2,7 +2,6 @@
#include <LibGfx/Color.h>
#include <LibIPC/TransportHandle.h>
#include <LibGfx/Cursor.h>
#include <LibGfx/SharedImage.h>
#include <LibGfx/ShareableBitmap.h>
#include <LibHTTP/Cookie/Cookie.h>
#include <LibHTTP/Cookie/ParsedCookie.h>
@ -38,7 +37,6 @@ endpoint WebContentClient
did_start_loading(u64 page_id, URL::URL url, bool is_redirect) =|
did_finish_loading(u64 page_id, URL::URL url) =|
did_request_refresh(u64 page_id) =|
did_paint(u64 page_id, Gfx::IntRect content_rect, i32 bitmap_id) =|
did_request_cursor_change(u64 page_id, Gfx::Cursor cursor) =|
did_change_title(u64 page_id, Utf16String title) =|
did_change_url(u64 page_id, URL::URL url) =|
@ -117,7 +115,6 @@ endpoint WebContentClient
did_request_clipboard_entries(u64 page_id, u64 request_id) =|
did_update_navigation_buttons_state(u64 page_id, bool back_enabled, bool forward_enabled) =|
did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store) =|
did_change_audio_play_state(u64 page_id, Web::HTML::AudioPlayState play_state) =|

View file

@ -34,6 +34,7 @@ endpoint WebContentServer
connect_to_web_ui(u64 page_id, IPC::TransportHandle handle) =|
connect_to_request_server(IPC::TransportHandle handle) =|
connect_to_image_decoder(IPC::TransportHandle handle) =|
connect_to_compositor(IPC::TransportHandle handle) =|
update_system_theme(u64 page_id, Core::AnonymousBuffer theme_buffer) =|
update_screen_rects(u64 page_id, Vector<Web::DevicePixelRect> rects, u32 main_screen_index) =|
@ -43,8 +44,6 @@ endpoint WebContentServer
reload(u64 page_id) =|
traverse_the_history_by_delta(u64 page_id, i32 delta) =|
ready_to_paint(u64 page_id) =|
set_viewport(u64 page_id, Web::DevicePixelSize size, double device_pixel_ratio, Web::ViewportIsFullscreen is_fullscreen) =|
key_event(u64 page_id, Web::KeyEvent event) =|