LibWeb+WebContent+WebWorker: Move backing store allocation in Navigable

Making navigables responsible for backing store allocation will allow us
to have separate backing stores for iframes and run paint updates for
them independently, which is a step toward isolating them into separate
processes.

Another nice side effect is that now Skia backend context is ready by
the time backing stores are allocated, so we will be able to get rid of
BackingStore class in the upcoming changes and allocate PaintingSurface
directly.
This commit is contained in:
Aliaksandr Kalenik 2025-06-26 22:33:58 +02:00 committed by Jelle Raaijmakers
parent b73525ba0e
commit 082053d781
Notes: github-actions[bot] 2025-07-04 14:14:12 +00:00
23 changed files with 265 additions and 262 deletions

View file

@ -1,135 +0,0 @@
/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/Timer.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <WebContent/BackingStoreManager.h>
#include <WebContent/PageClient.h>
#ifdef AK_OS_MACOS
# include <LibCore/IOSurface.h>
# include <LibCore/MachPort.h>
# include <LibCore/Platform/MachMessageTypes.h>
#endif
namespace WebContent {
#ifdef AK_OS_MACOS
static Optional<Core::MachPort> s_browser_mach_port;
void BackingStoreManager::set_browser_mach_port(Core::MachPort&& port)
{
s_browser_mach_port = move(port);
}
#endif
BackingStoreManager::BackingStoreManager(PageClient& page_client)
: m_page_client(page_client)
{
m_backing_store_shrink_timer = Core::Timer::create_single_shot(3000, [this] {
resize_backing_stores_if_needed(WindowResizingInProgress::No);
});
}
void BackingStoreManager::restart_resize_timer()
{
m_backing_store_shrink_timer->restart();
}
void BackingStoreManager::reallocate_backing_stores(Gfx::IntSize size)
{
#ifdef AK_OS_MACOS
if (s_browser_mach_port.has_value()) {
auto back_iosurface = Core::IOSurfaceHandle::create(size.width(), size.height());
auto back_iosurface_port = back_iosurface.create_mach_port();
auto front_iosurface = Core::IOSurfaceHandle::create(size.width(), size.height());
auto front_iosurface_port = front_iosurface.create_mach_port();
m_front_bitmap_id = m_next_bitmap_id++;
m_back_bitmap_id = m_next_bitmap_id++;
Core::Platform::BackingStoreMetadata metadata;
metadata.page_id = m_page_client.m_id;
metadata.front_backing_store_id = m_front_bitmap_id;
metadata.back_backing_store_id = m_back_bitmap_id;
Core::Platform::MessageWithBackingStores message;
message.header.msgh_remote_port = s_browser_mach_port->port();
message.header.msgh_local_port = MACH_PORT_NULL;
message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
message.header.msgh_size = sizeof(message);
message.header.msgh_id = Core::Platform::BACKING_STORE_IOSURFACES_MESSAGE_ID;
message.body.msgh_descriptor_count = 2;
message.front_descriptor.name = front_iosurface_port.release();
message.front_descriptor.disposition = MACH_MSG_TYPE_MOVE_SEND;
message.front_descriptor.type = MACH_MSG_PORT_DESCRIPTOR;
message.back_descriptor.name = back_iosurface_port.release();
message.back_descriptor.disposition = MACH_MSG_TYPE_MOVE_SEND;
message.back_descriptor.type = MACH_MSG_PORT_DESCRIPTOR;
message.metadata = metadata;
mach_msg_timeout_t const timeout = 100; // milliseconds
auto const send_result = mach_msg(&message.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, message.header.msgh_size, 0, MACH_PORT_NULL, timeout, MACH_PORT_NULL);
if (send_result != KERN_SUCCESS) {
dbgln("Failed to send message to server: {}", mach_error_string(send_result));
VERIFY_NOT_REACHED();
}
m_front_store = Web::Painting::IOSurfaceBackingStore::create(move(front_iosurface));
m_back_store = Web::Painting::IOSurfaceBackingStore::create(move(back_iosurface));
return;
}
#endif
m_front_bitmap_id = m_next_bitmap_id++;
m_back_bitmap_id = m_next_bitmap_id++;
auto front_bitmap = Gfx::Bitmap::create_shareable(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, size).release_value();
auto back_bitmap = Gfx::Bitmap::create_shareable(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, size).release_value();
m_front_store = Web::Painting::BitmapBackingStore::create(front_bitmap);
m_back_store = Web::Painting::BitmapBackingStore::create(back_bitmap);
m_page_client.page_did_allocate_backing_stores(m_front_bitmap_id, front_bitmap->to_shareable_bitmap(), m_back_bitmap_id, back_bitmap->to_shareable_bitmap());
}
void BackingStoreManager::resize_backing_stores_if_needed(WindowResizingInProgress window_resize_in_progress)
{
auto css_pixels_viewpor_rect = m_page_client.page().top_level_traversable()->viewport_rect();
auto viewport_size = m_page_client.page().css_to_device_rect(css_pixels_viewpor_rect).size();
if (viewport_size.is_empty())
return;
Web::DevicePixelSize minimum_needed_size;
if (window_resize_in_progress == WindowResizingInProgress::Yes) {
// Pad the minimum needed size so that we don't have to keep reallocating backing stores while the window is being resized.
minimum_needed_size = { viewport_size.width() + 256, viewport_size.height() + 256 };
} else {
// If we're not in the middle of a resize, we can shrink the backing store size to match the viewport size.
minimum_needed_size = viewport_size;
m_front_store.clear();
m_back_store.clear();
}
if (!m_front_store || !m_back_store || !m_front_store->size().contains(minimum_needed_size.to_type<int>())) {
reallocate_backing_stores(minimum_needed_size.to_type<int>());
}
}
void BackingStoreManager::swap_back_and_front()
{
swap(m_front_store, m_back_store);
swap(m_front_bitmap_id, m_back_bitmap_id);
}
}

View file

@ -1,59 +0,0 @@
/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/Painting/BackingStore.h>
#include <WebContent/Forward.h>
namespace WebContent {
class BackingStoreManager {
public:
#ifdef AK_OS_MACOS
static void set_browser_mach_port(Core::MachPort&&);
#endif
enum class WindowResizingInProgress {
No,
Yes
};
void resize_backing_stores_if_needed(WindowResizingInProgress window_resize_in_progress);
void reallocate_backing_stores(Gfx::IntSize);
void restart_resize_timer();
struct BackingStore {
i32 bitmap_id { -1 };
Web::Painting::BackingStore* store { nullptr };
};
BackingStore acquire_store_for_next_frame()
{
BackingStore backing_store;
backing_store.bitmap_id = m_back_bitmap_id;
backing_store.store = m_back_store.ptr();
swap_back_and_front();
return backing_store;
}
BackingStoreManager(PageClient&);
private:
void swap_back_and_front();
// FIXME: We should come up with an ownership model for this class that makes the GC-checker happy
IGNORE_GC PageClient& m_page_client;
i32 m_front_bitmap_id { -1 };
i32 m_back_bitmap_id { -1 };
RefPtr<Web::Painting::BackingStore> m_front_store;
RefPtr<Web::Painting::BackingStore> m_back_store;
int m_next_bitmap_id { 0 };
RefPtr<Core::Timer> m_backing_store_shrink_timer;
};
}

View file

@ -1,7 +1,6 @@
include(audio)
set(SOURCES
BackingStoreManager.cpp
ConnectionFromClient.cpp
ConsoleGlobalEnvironmentExtensions.cpp
DevToolsConsoleClient.cpp

View file

@ -334,8 +334,9 @@ void ConnectionFromClient::debug_request(u64 page_id, ByteString request, ByteSt
if (request == "set-line-box-borders") {
bool state = argument == "on";
page->set_should_show_line_box_borders(state);
page->page().top_level_traversable()->set_needs_repaint();
auto traversable = page->page().top_level_traversable();
traversable->set_should_show_line_box_borders(state);
traversable->set_needs_repaint();
return;
}

View file

@ -65,7 +65,6 @@ PageClient::PageClient(PageHost& owner, u64 id)
: m_owner(owner)
, m_page(Web::Page::create(Web::Bindings::main_thread_vm(), *this))
, m_id(id)
, m_backing_store_manager(*this)
{
setup_palette();
@ -79,11 +78,6 @@ PageClient::PageClient(PageHost& owner, u64 id)
PageClient::~PageClient() = default;
bool PageClient::is_ready_to_paint() const
{
return m_number_of_queued_rasterization_tasks <= 1;
}
void PageClient::visit_edges(JS::Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
@ -188,64 +182,9 @@ Web::Layout::Viewport* PageClient::layout_root()
return document->layout_node();
}
void PageClient::process_screenshot_requests()
{
while (!m_screenshot_tasks.is_empty()) {
auto task = m_screenshot_tasks.dequeue();
if (task.node_id.has_value()) {
auto* dom_node = Web::DOM::Node::from_unique_id(*task.node_id);
if (!dom_node || !dom_node->paintable_box()) {
client().async_did_take_screenshot(m_id, {});
continue;
}
auto rect = page().enclosing_device_rect(dom_node->paintable_box()->absolute_border_box_rect());
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, rect.size().to_type<int>()).release_value_but_fixme_should_propagate_errors();
auto backing_store = Web::Painting::BitmapBackingStore::create(*bitmap);
start_display_list_rendering(rect, backing_store, { .paint_overlay = Web::PaintOptions::PaintOverlay::No }, [this, backing_store] {
client().async_did_take_screenshot(m_id, backing_store->bitmap().to_shareable_bitmap());
});
} else {
Web::DevicePixelRect rect { { 0, 0 }, content_size() };
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, rect.size().to_type<int>()).release_value_but_fixme_should_propagate_errors();
auto backing_store = Web::Painting::BitmapBackingStore::create(*bitmap);
start_display_list_rendering(rect, backing_store, {}, [this, backing_store] {
client().async_did_take_screenshot(m_id, backing_store->bitmap().to_shareable_bitmap());
});
}
}
}
void PageClient::ready_to_paint()
{
m_number_of_queued_rasterization_tasks--;
VERIFY(m_number_of_queued_rasterization_tasks >= 0 && m_number_of_queued_rasterization_tasks < 2);
}
void PageClient::paint_next_frame()
{
auto [backing_store_id, back_store] = m_backing_store_manager.acquire_store_for_next_frame();
if (!back_store)
return;
VERIFY(m_number_of_queued_rasterization_tasks <= 1);
m_number_of_queued_rasterization_tasks++;
auto viewport_rect = page().css_to_device_rect(page().top_level_traversable()->viewport_rect());
start_display_list_rendering(viewport_rect, *back_store, {}, [this, viewport_rect, backing_store_id] {
client().async_did_paint(m_id, viewport_rect.to_type<int>(), backing_store_id);
});
}
void PageClient::start_display_list_rendering(Web::DevicePixelRect const& content_rect, Web::Painting::BackingStore& target, Web::PaintOptions paint_options, Function<void()>&& callback)
{
paint_options.should_show_line_box_borders = m_should_show_line_box_borders;
auto& traversable = *page().top_level_traversable();
auto display_list = traversable.record_display_list(content_rect, paint_options);
if (!display_list) {
callback();
return;
}
traversable.start_display_list_rendering(*display_list, target, move(callback));
page().top_level_traversable()->ready_to_paint();
}
Queue<Web::QueuedInputEvent>& PageClient::input_event_queue()
@ -261,10 +200,6 @@ void PageClient::report_finished_handling_input_event(u64 page_id, Web::EventRes
void PageClient::set_viewport_size(Web::DevicePixelSize const& size)
{
page().top_level_traversable()->set_viewport_size(page().device_to_css_size(size));
m_backing_store_manager.restart_resize_timer();
m_backing_store_manager.resize_backing_stores_if_needed(BackingStoreManager::WindowResizingInProgress::Yes);
m_pending_set_browser_zoom_request = false;
}
void PageClient::page_did_request_cursor_change(Gfx::Cursor const& cursor)
@ -411,12 +346,12 @@ void PageClient::page_did_set_test_timeout(double milliseconds)
void PageClient::page_did_set_browser_zoom(double factor)
{
m_pending_set_browser_zoom_request = true;
auto traversable = page().top_level_traversable();
traversable->set_pending_set_browser_zoom_request(true);
client().async_did_set_browser_zoom(m_id, factor);
auto& event_loop = Web::HTML::main_thread_event_loop();
event_loop.spin_until(GC::create_function(event_loop.heap(), [&]() {
return !m_pending_set_browser_zoom_request || !is_connection_open();
event_loop.spin_until(GC::create_function(event_loop.heap(), [this, traversable]() {
return !traversable->pending_set_browser_zoom_request() || !is_connection_open();
}));
}
@ -765,6 +700,16 @@ 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);
}
ErrorOr<void> PageClient::connect_to_webdriver(ByteString const& webdriver_ipc_path)
{
VERIFY(!m_webdriver);
@ -961,8 +906,7 @@ Web::DisplayListPlayerType PageClient::display_list_player_type() const
void PageClient::queue_screenshot_task(Optional<Web::UniqueNodeID> node_id)
{
m_screenshot_tasks.enqueue({ node_id });
page().top_level_traversable()->set_needs_repaint();
page().top_level_traversable()->queue_screenshot_task(node_id);
}
}

View file

@ -13,11 +13,11 @@
#include <LibWeb/HTML/AudioPlayState.h>
#include <LibWeb/HTML/FileFilter.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/BackingStoreManager.h>
#include <LibWeb/PixelUnits.h>
#include <LibWeb/StorageAPI/StorageEndpoint.h>
#include <LibWebView/Forward.h>
#include <LibWebView/StorageOperationError.h>
#include <WebContent/BackingStoreManager.h>
#include <WebContent/Forward.h>
namespace WebContent {
@ -31,6 +31,8 @@ public:
virtual ~PageClient() override;
virtual u64 id() const override { return m_id; }
enum class UseSkiaPainter {
CPUBackend,
GPUBackendIfAvailable,
@ -40,18 +42,12 @@ public:
virtual bool is_headless() const override;
static void set_is_headless(bool);
virtual bool is_ready_to_paint() const override;
virtual Web::Page& page() override { return *m_page; }
virtual Web::Page const& page() const override { return *m_page; }
ErrorOr<void> connect_to_webdriver(ByteString const& webdriver_ipc_path);
ErrorOr<void> connect_to_web_ui(IPC::File);
virtual void paint_next_frame() override;
virtual void process_screenshot_requests() override;
virtual void start_display_list_rendering(Web::DevicePixelRect const& content_rect, Web::Painting::BackingStore&, Web::PaintOptions, Function<void()>&& callback) override;
virtual Queue<Web::QueuedInputEvent>& input_event_queue() override;
virtual void report_finished_handling_input_event(u64 page_id, Web::EventResult event_was_handled) override;
@ -62,7 +58,6 @@ public:
void set_preferred_color_scheme(Web::CSS::PreferredColorScheme);
void set_preferred_contrast(Web::CSS::PreferredContrast);
void set_preferred_motion(Web::CSS::PreferredMotion);
void set_should_show_line_box_borders(bool b) { m_should_show_line_box_borders = b; }
void set_has_focus(bool);
void set_is_scripting_enabled(bool);
void set_window_position(Web::DevicePixelPoint);
@ -102,8 +97,6 @@ public:
void queue_screenshot_task(Optional<Web::UniqueNodeID> node_id);
friend class BackingStoreManager;
private:
PageClient(PageHost&, u64 id);
@ -182,6 +175,8 @@ private:
virtual void page_did_allocate_backing_stores(i32 front_bitmap_id, Gfx::ShareableBitmap front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap back_bitmap) override;
virtual IPC::File 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;
Web::Layout::Viewport* layout_root();
@ -195,16 +190,8 @@ private:
Web::DevicePixelSize m_content_size;
float m_device_pixels_per_css_pixel { 1.0f };
u64 m_id { 0 };
bool m_should_show_line_box_borders { false };
bool m_has_focus { false };
i32 m_number_of_queued_rasterization_tasks { 0 };
struct ScreenshotTask {
Optional<Web::UniqueNodeID> node_id;
};
Queue<ScreenshotTask> m_screenshot_tasks;
Web::CSS::PreferredColorScheme m_preferred_color_scheme { Web::CSS::PreferredColorScheme::Auto };
Web::CSS::PreferredContrast m_preferred_contrast { Web::CSS::PreferredContrast::NoPreference };
Web::CSS::PreferredMotion m_preferred_motion { Web::CSS::PreferredMotion::NoPreference };
@ -212,15 +199,11 @@ private:
RefPtr<WebDriverConnection> m_webdriver;
RefPtr<WebUIConnection> m_web_ui;
BackingStoreManager m_backing_store_manager;
WeakPtr<WebContentConsoleClient> m_top_level_document_console_client;
GC::Root<JS::GlobalObject> m_console_global_object;
RefPtr<Core::Timer> m_paint_refresh_timer;
bool m_pending_set_browser_zoom_request = false;
};
}

View file

@ -24,6 +24,7 @@
#include <LibWeb/Loader/ContentFilter.h>
#include <LibWeb/Loader/GeneratedPagesLoader.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/Painting/BackingStoreManager.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Platform/AudioCodecPluginAgnostic.h>
#include <LibWeb/Platform/EventLoopPluginSerenity.h>
@ -186,7 +187,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
// FIXME: For some reason, our implementation of IOSurface does not work on Intel macOS. Remove this conditional
// compilation when that is resolved.
# if ARCH(AARCH64)
WebContent::BackingStoreManager::set_browser_mach_port(move(server_port));
Web::Painting::BackingStoreManager::set_browser_mach_port(move(server_port));
# endif
}
#endif