2020-06-17 12:31:42 -03:00
|
|
|
/*
|
2024-10-04 08:19:50 -03:00
|
|
|
* Copyright (c) 2020-2021, Andreas Kling <andreas@ladybird.org>
|
2020-06-17 12:31:42 -03:00
|
|
|
*
|
2021-04-22 05:24:48 -03:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-06-17 12:31:42 -03:00
|
|
|
*/
|
|
|
|
|
|
2026-04-11 19:32:29 -03:00
|
|
|
#include <AK/Debug.h>
|
2026-05-22 10:42:29 -03:00
|
|
|
#include <AK/JsonArray.h>
|
|
|
|
|
#include <AK/JsonObject.h>
|
|
|
|
|
#include <AK/NeverDestroyed.h>
|
|
|
|
|
#include <AK/WeakPtr.h>
|
2026-05-11 16:45:34 -03:00
|
|
|
#include <LibCore/ElapsedTimer.h>
|
2026-05-26 13:03:07 -03:00
|
|
|
#include <LibCore/Timer.h>
|
2026-02-07 13:13:47 -03:00
|
|
|
#include <LibHTTP/Cookie/ParsedCookie.h>
|
2026-05-11 16:02:29 -03:00
|
|
|
#include <LibIPC/Transport.h>
|
2026-03-11 16:10:08 -03:00
|
|
|
#include <LibIPC/TransportHandle.h>
|
2026-05-12 16:49:42 -03:00
|
|
|
#include <LibWeb/Page/InputEvent.h>
|
2025-03-24 10:27:36 -03:00
|
|
|
#include <LibWebView/Application.h>
|
2024-09-22 14:25:18 -03:00
|
|
|
#include <LibWebView/CookieJar.h>
|
2026-03-26 14:04:33 -03:00
|
|
|
#include <LibWebView/HSTSStore.h>
|
2024-11-13 18:08:08 -03:00
|
|
|
#include <LibWebView/HelperProcess.h>
|
2026-04-11 19:32:29 -03:00
|
|
|
#include <LibWebView/HistoryStore.h>
|
2025-09-13 10:08:24 -03:00
|
|
|
#include <LibWebView/SourceHighlighter.h>
|
2025-03-24 10:27:36 -03:00
|
|
|
#include <LibWebView/ViewImplementation.h>
|
|
|
|
|
#include <LibWebView/WebContentClient.h>
|
|
|
|
|
#include <LibWebView/WebUI.h>
|
2026-05-21 10:33:23 -03:00
|
|
|
#include <LibWebView/WorkerProcessManager.h>
|
2020-06-17 12:31:42 -03:00
|
|
|
|
2022-04-30 05:46:33 -03:00
|
|
|
namespace WebView {
|
2020-08-24 08:03:18 -03:00
|
|
|
|
2026-06-04 05:40:32 -03:00
|
|
|
HashTable<WebContentClient*>& WebContentClient::clients()
|
|
|
|
|
{
|
|
|
|
|
static NeverDestroyed<HashTable<WebContentClient*>> clients;
|
|
|
|
|
return *clients;
|
|
|
|
|
}
|
2024-06-20 15:34:51 -03:00
|
|
|
|
2026-05-26 13:03:07 -03:00
|
|
|
static constexpr auto detached_page_close_timeout_ms = 1000;
|
|
|
|
|
static constexpr auto close_server_exit_timeout_ms = 5000;
|
|
|
|
|
static constexpr auto detached_page_forced_exit_timeout_ms = detached_page_close_timeout_ms + close_server_exit_timeout_ms;
|
|
|
|
|
|
2026-04-11 19:32:29 -03:00
|
|
|
static Optional<String> history_title(Utf16String const& title, URL::URL const& url)
|
|
|
|
|
{
|
|
|
|
|
if (title.is_empty())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
auto title_utf8 = title.to_utf8();
|
|
|
|
|
if (title_utf8 == url.serialize() || title_utf8 == url.serialize(URL::ExcludeFragment::Yes))
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
return title_utf8;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 11:58:13 -03:00
|
|
|
WebContentClient::WebContentClient(NonnullOwnPtr<IPC::Transport> transport, u64 initial_page_id)
|
2025-03-09 19:01:25 -03:00
|
|
|
: IPC::ConnectionToServer<WebContentClientEndpoint, WebContentServerEndpoint>(*this, move(transport))
|
2026-05-30 11:58:13 -03:00
|
|
|
, m_initial_page_id(initial_page_id)
|
2025-03-09 19:01:25 -03:00
|
|
|
{
|
2026-05-30 11:58:13 -03:00
|
|
|
VERIFY(m_initial_page_id > 0);
|
2026-06-04 05:40:32 -03:00
|
|
|
clients().set(this);
|
2025-03-09 19:01:25 -03:00
|
|
|
}
|
|
|
|
|
|
2024-06-20 15:34:51 -03:00
|
|
|
WebContentClient::~WebContentClient()
|
|
|
|
|
{
|
2026-05-21 10:33:23 -03:00
|
|
|
WorkerProcessManager::the().remove_web_content_owner(*this);
|
2026-06-04 05:40:32 -03:00
|
|
|
clients().remove(this);
|
2024-06-20 15:34:51 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-22 09:15:34 -03:00
|
|
|
Optional<WebContentClient&> WebContentClient::client_for_compositor_context_id(Web::Compositor::CompositorContextId context_id)
|
|
|
|
|
{
|
|
|
|
|
Optional<WebContentClient&> client;
|
|
|
|
|
for_each_client([&](auto& candidate) {
|
|
|
|
|
if (!candidate.page_id_for_compositor_context_id(context_id).has_value())
|
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
client = candidate;
|
|
|
|
|
return IterationDecision::Break;
|
|
|
|
|
});
|
|
|
|
|
return client;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-30 14:20:40 -03:00
|
|
|
void WebContentClient::die()
|
|
|
|
|
{
|
2024-06-30 01:24:01 -03:00
|
|
|
// Intentionally empty. Restart is handled at another level.
|
2021-01-30 14:20:40 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-22 09:15:34 -03:00
|
|
|
Web::Compositor::CompositorContextId WebContentClient::compositor_context_id_for_page(u64 page_id)
|
|
|
|
|
{
|
2026-05-30 12:36:06 -03:00
|
|
|
auto context_id = Web::Compositor::compositor_context_id_for_page(page_id);
|
|
|
|
|
if (auto registered_page_id = m_compositor_contexts.get(context_id); registered_page_id.has_value()) {
|
|
|
|
|
VERIFY(registered_page_id->has_value());
|
|
|
|
|
VERIFY(**registered_page_id == page_id);
|
|
|
|
|
return context_id;
|
|
|
|
|
}
|
2026-05-22 09:15:34 -03:00
|
|
|
|
2026-05-30 12:36:06 -03:00
|
|
|
remember_compositor_context(context_id, page_id);
|
|
|
|
|
Application::the().register_compositor_context(*this, context_id, page_id);
|
2026-05-22 09:15:34 -03:00
|
|
|
return context_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Optional<u64> WebContentClient::page_id_for_compositor_context_id(Web::Compositor::CompositorContextId context_id) const
|
|
|
|
|
{
|
2026-05-30 12:36:06 -03:00
|
|
|
auto page_id = m_compositor_contexts.get(context_id);
|
|
|
|
|
if (!page_id.has_value())
|
|
|
|
|
return {};
|
|
|
|
|
return *page_id;
|
2026-05-22 09:15:34 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Messages::WebContentClient::AllocateCompositorContextIdResponse WebContentClient::allocate_compositor_context_id(u64 page_id, Web::Compositor::PagePresentationRegistration page_presentation_registration)
|
|
|
|
|
{
|
|
|
|
|
if (page_presentation_registration == Web::Compositor::PagePresentationRegistration::Yes)
|
|
|
|
|
return compositor_context_id_for_page(page_id);
|
|
|
|
|
|
2026-05-30 12:36:06 -03:00
|
|
|
auto context_id = Application::the().allocate_compositor_context_id();
|
|
|
|
|
remember_compositor_context(context_id, {});
|
|
|
|
|
Application::the().register_compositor_context(*this, context_id, {});
|
2026-05-22 09:15:34 -03:00
|
|
|
return context_id;
|
|
|
|
|
}
|
|
|
|
|
|
LibWebView+WebContent: Add feature-gated Compositor process backend
We are moving toward an architecture where the browser owns a single
process that holds the GPU context, so Skia resources can be shared
across every renderer and WebContent processes can be sandboxed away
from direct GPU access. A dedicated Compositor helper process is the
first step. The earlier commits laid the foundation; this one turns the
helper on as a selectable backend, gated behind
--enable-compositor-process so the existing in-process path stays the
shipping default.
Default topology -- one compositor per WebContent, in-process:
+---------+
| Browser |
+---------+
/ | \
v v v
+---+ +---+ +---+ each WebContent runs its own
|WC | |WC | |WC | CompositorThread on a dedicated
|+C+| |+C+| |+C+| thread, with its own GPU context.
+---+ +---+ +---+
Opt-in topology -- one single-threaded Compositor, shared by all WCs:
+---------+ control +-------------------+
| Browser |<----------->| Compositor |
+---------+ | (single thread, |
/ | \ | single GPU ctx, |
v v v data | shared by all |
[WC] [WC] [WC] ---------->| connected WCs) |
+-------------------+
Three IPC channels carry the work in the opt-in topology. The
existing Browser<->WebContent channel gains context allocation.
A new Browser<->Compositor channel carries context lifetime,
viewport, UI input, and presentation acks plus backing-store and
frame upcalls. A new WebContent<->Compositor channel carries
display lists, scroll state, video, compositor surfaces, async
scrolling, presentation, and screenshots, with upcalls for
delegated input and compositor loss.
2026-05-22 11:17:27 -03:00
|
|
|
void WebContentClient::did_destroy_compositor_context(Web::Compositor::CompositorContextId context_id)
|
2026-05-22 09:15:34 -03:00
|
|
|
{
|
LibWebView+WebContent: Add feature-gated Compositor process backend
We are moving toward an architecture where the browser owns a single
process that holds the GPU context, so Skia resources can be shared
across every renderer and WebContent processes can be sandboxed away
from direct GPU access. A dedicated Compositor helper process is the
first step. The earlier commits laid the foundation; this one turns the
helper on as a selectable backend, gated behind
--enable-compositor-process so the existing in-process path stays the
shipping default.
Default topology -- one compositor per WebContent, in-process:
+---------+
| Browser |
+---------+
/ | \
v v v
+---+ +---+ +---+ each WebContent runs its own
|WC | |WC | |WC | CompositorThread on a dedicated
|+C+| |+C+| |+C+| thread, with its own GPU context.
+---+ +---+ +---+
Opt-in topology -- one single-threaded Compositor, shared by all WCs:
+---------+ control +-------------------+
| Browser |<----------->| Compositor |
+---------+ | (single thread, |
/ | \ | single GPU ctx, |
v v v data | shared by all |
[WC] [WC] [WC] ---------->| connected WCs) |
+-------------------+
Three IPC channels carry the work in the opt-in topology. The
existing Browser<->WebContent channel gains context allocation.
A new Browser<->Compositor channel carries context lifetime,
viewport, UI input, and presentation acks plus backing-store and
frame upcalls. A new WebContent<->Compositor channel carries
display lists, scroll state, video, compositor surfaces, async
scrolling, presentation, and screenshots, with upcalls for
delegated input and compositor loss.
2026-05-22 11:17:27 -03:00
|
|
|
forget_compositor_context(context_id);
|
2026-05-22 09:15:34 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool WebContentClient::forget_compositor_context(Web::Compositor::CompositorContextId context_id)
|
|
|
|
|
{
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
if (!m_compositor_contexts.remove(context_id))
|
2026-05-22 09:15:34 -03:00
|
|
|
return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 12:36:06 -03:00
|
|
|
void WebContentClient::remember_compositor_context(Web::Compositor::CompositorContextId context_id, Optional<u64> page_id)
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
{
|
2026-05-30 12:36:06 -03:00
|
|
|
m_compositor_contexts.set(context_id, page_id);
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-09 19:01:25 -03:00
|
|
|
void WebContentClient::assign_view(Badge<Application>, ViewImplementation& view)
|
|
|
|
|
{
|
|
|
|
|
VERIFY(m_views.is_empty());
|
2026-05-30 11:58:13 -03:00
|
|
|
view.m_client_state.page_index = m_initial_page_id;
|
|
|
|
|
m_views.set(m_initial_page_id, view);
|
2025-03-09 19:01:25 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-22 11:02:42 -03:00
|
|
|
void WebContentClient::set_compositor_connection_id(Badge<Application>, i32 compositor_connection_id)
|
|
|
|
|
{
|
|
|
|
|
m_compositor_connection_id = compositor_connection_id;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void WebContentClient::register_view(u64 page_id, ViewImplementation& view)
|
2020-06-17 12:31:42 -03:00
|
|
|
{
|
2024-02-02 22:00:48 -03:00
|
|
|
VERIFY(page_id > 0);
|
2026-05-26 13:03:07 -03:00
|
|
|
if (m_detached_page_close_timer)
|
|
|
|
|
m_detached_page_close_timer->stop();
|
|
|
|
|
Application::process_manager().cancel_forced_exit(pid());
|
2026-05-30 11:58:13 -03:00
|
|
|
view.m_client_state.page_index = page_id;
|
2026-01-30 05:08:55 -03:00
|
|
|
m_views.set(page_id, view);
|
2026-04-11 19:33:00 -03:00
|
|
|
m_history_recorded_urls_for_current_load.remove(page_id);
|
2020-06-17 12:31:42 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void WebContentClient::unregister_view(u64 page_id)
|
2023-05-17 13:37:31 -03:00
|
|
|
{
|
2026-05-30 12:36:06 -03:00
|
|
|
forget_compositor_context(Web::Compositor::compositor_context_id_for_page(page_id));
|
2026-05-26 13:03:07 -03:00
|
|
|
|
|
|
|
|
// A page that still needs a beforeunload check is not a detached
|
|
|
|
|
// background close. It is being closed without waiting for WebContent,
|
|
|
|
|
// e.g. because the user requested a forced close.
|
|
|
|
|
if (auto view = m_views.get(page_id); view.has_value() && (*view)->needs_beforeunload_check())
|
|
|
|
|
m_detached_pages_pending_close.remove(page_id);
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
m_views.remove(page_id);
|
2026-04-11 19:33:00 -03:00
|
|
|
m_history_recorded_urls_for_current_load.remove(page_id);
|
2026-05-26 13:03:07 -03:00
|
|
|
close_server_if_unused();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::prepare_for_detached_close(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
m_detached_pages_pending_close.set(page_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::request_close(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
// The frontend may destroy the view immediately after this for pages that
|
|
|
|
|
// cannot prompt during beforeunload. Keep owning the WebContent close until
|
|
|
|
|
// the page reports that its top-level traversable has been closed.
|
|
|
|
|
prepare_for_detached_close(page_id);
|
|
|
|
|
async_request_close(page_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::close_server_if_unused()
|
|
|
|
|
{
|
|
|
|
|
if (!m_views.is_empty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (m_detached_pages_pending_close.is_empty()) {
|
|
|
|
|
if (m_detached_page_close_timer)
|
|
|
|
|
m_detached_page_close_timer->stop();
|
2024-06-30 22:12:22 -03:00
|
|
|
async_close_server();
|
2026-05-26 13:03:07 -03:00
|
|
|
Application::process_manager().force_exit_after_timeout(pid(), close_server_exit_timeout_ms);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Application::process_manager().force_exit_after_timeout(pid(), detached_page_forced_exit_timeout_ms);
|
|
|
|
|
|
|
|
|
|
if (!m_detached_page_close_timer) {
|
|
|
|
|
m_detached_page_close_timer = Core::Timer::create_single_shot(detached_page_close_timeout_ms, [this] {
|
|
|
|
|
dbgln("Timed out waiting for detached WebContent page close acknowledgement");
|
|
|
|
|
m_detached_pages_pending_close.clear();
|
|
|
|
|
close_server_if_unused();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m_detached_page_close_timer->is_active())
|
|
|
|
|
m_detached_page_close_timer->start();
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2023-05-17 13:37:31 -03:00
|
|
|
|
2025-03-24 10:27:36 -03:00
|
|
|
void WebContentClient::web_ui_disconnected(Badge<WebUI>)
|
|
|
|
|
{
|
|
|
|
|
m_web_ui.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 09:15:34 -03:00
|
|
|
void WebContentClient::destroy_all_compositor_contexts()
|
|
|
|
|
{
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
m_compositor_contexts.clear();
|
2026-05-22 09:15:34 -03:00
|
|
|
}
|
|
|
|
|
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
ErrorOr<void> WebContentClient::reconnect_to_compositor_process(Badge<Application>)
|
|
|
|
|
{
|
2026-05-24 16:51:33 -03:00
|
|
|
if (!is_open())
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
m_compositor_connection_id.clear();
|
|
|
|
|
TRY(Application::the().connect_web_content_to_compositor(*this));
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ErrorOr<void> WebContentClient::recreate_compositor_contexts(Badge<Application>)
|
|
|
|
|
{
|
2026-05-24 16:51:33 -03:00
|
|
|
if (!is_open())
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
return {};
|
|
|
|
|
|
2026-05-30 12:36:06 -03:00
|
|
|
for (auto const& [context_id, page_id] : m_compositor_contexts)
|
|
|
|
|
TRY(Application::the().try_register_compositor_context(*this, context_id, page_id));
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-31 15:16:39 -03:00
|
|
|
void WebContentClient::replay_compositor_view_state_after_reconnect(Badge<Application>)
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
{
|
2026-05-24 16:51:33 -03:00
|
|
|
if (!is_open())
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (auto const& [page_id, view] : m_views) {
|
2026-05-30 12:36:06 -03:00
|
|
|
auto context_id = Web::Compositor::compositor_context_id_for_page(page_id);
|
|
|
|
|
if (!m_compositor_contexts.contains(context_id))
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
continue;
|
2026-05-30 12:36:06 -03:00
|
|
|
Application::the().update_compositor_viewport(context_id, view->viewport_size().to_type<int>());
|
2026-05-31 15:16:39 -03:00
|
|
|
Application::the().update_compositor_display_metadata(context_id, view->display_id(), view->maximum_frames_per_second());
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::notify_compositor_process_reconnected(Badge<Application>)
|
|
|
|
|
{
|
2026-05-24 16:51:33 -03:00
|
|
|
if (!is_open())
|
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-23 22:36:24 -03:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
async_compositor_process_reconnected();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 19:32:16 -03:00
|
|
|
void WebContentClient::notify_all_views_of_crash()
|
|
|
|
|
{
|
2026-05-22 09:15:34 -03:00
|
|
|
destroy_all_compositor_contexts();
|
|
|
|
|
|
2026-01-13 19:32:16 -03:00
|
|
|
// Collect view IDs first, then use deferred_invoke to handle crashes safely
|
|
|
|
|
// (avoids signal handler deadlock and allows views to be looked up by ID
|
|
|
|
|
// in case they're destroyed before the deferred_invoke runs).
|
|
|
|
|
Vector<u64> view_ids;
|
|
|
|
|
view_ids.ensure_capacity(m_views.size());
|
|
|
|
|
for (auto& [page_id, view] : m_views)
|
2026-01-30 05:08:37 -03:00
|
|
|
view_ids.unchecked_append(view->view_id());
|
2026-01-13 19:32:16 -03:00
|
|
|
|
|
|
|
|
for (auto view_id : view_ids) {
|
|
|
|
|
Core::deferred_invoke([view_id] {
|
|
|
|
|
auto view = ViewImplementation::find_view_by_id(view_id);
|
|
|
|
|
if (!view.has_value())
|
|
|
|
|
return;
|
|
|
|
|
view->handle_web_content_process_crash();
|
|
|
|
|
if (view->on_web_content_crashed)
|
|
|
|
|
view->on_web_content_crashed();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 14:38:29 -03:00
|
|
|
bool WebContentClient::send_async_scroll_to_compositor(u64 page_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels)
|
2026-05-11 16:45:34 -03:00
|
|
|
{
|
|
|
|
|
auto timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise);
|
LibWebView+WebContent: Add feature-gated Compositor process backend
We are moving toward an architecture where the browser owns a single
process that holds the GPU context, so Skia resources can be shared
across every renderer and WebContent processes can be sandboxed away
from direct GPU access. A dedicated Compositor helper process is the
first step. The earlier commits laid the foundation; this one turns the
helper on as a selectable backend, gated behind
--enable-compositor-process so the existing in-process path stays the
shipping default.
Default topology -- one compositor per WebContent, in-process:
+---------+
| Browser |
+---------+
/ | \
v v v
+---+ +---+ +---+ each WebContent runs its own
|WC | |WC | |WC | CompositorThread on a dedicated
|+C+| |+C+| |+C+| thread, with its own GPU context.
+---+ +---+ +---+
Opt-in topology -- one single-threaded Compositor, shared by all WCs:
+---------+ control +-------------------+
| Browser |<----------->| Compositor |
+---------+ | (single thread, |
/ | \ | single GPU ctx, |
v v v data | shared by all |
[WC] [WC] [WC] ---------->| connected WCs) |
+-------------------+
Three IPC channels carry the work in the opt-in topology. The
existing Browser<->WebContent channel gains context allocation.
A new Browser<->Compositor channel carries context lifetime,
viewport, UI input, and presentation acks plus backing-store and
frame upcalls. A new WebContent<->Compositor channel carries
display lists, scroll state, video, compositor surfaces, async
scrolling, presentation, and screenshots, with upcalls for
delegated input and compositor loss.
2026-05-22 11:17:27 -03:00
|
|
|
|
2026-05-24 16:51:33 -03:00
|
|
|
auto handled = Application::the().send_async_scroll_to_compositor(compositor_context_id_for_page(page_id), position, delta_in_device_pixels);
|
LibWebView+WebContent: Add feature-gated Compositor process backend
We are moving toward an architecture where the browser owns a single
process that holds the GPU context, so Skia resources can be shared
across every renderer and WebContent processes can be sandboxed away
from direct GPU access. A dedicated Compositor helper process is the
first step. The earlier commits laid the foundation; this one turns the
helper on as a selectable backend, gated behind
--enable-compositor-process so the existing in-process path stays the
shipping default.
Default topology -- one compositor per WebContent, in-process:
+---------+
| Browser |
+---------+
/ | \
v v v
+---+ +---+ +---+ each WebContent runs its own
|WC | |WC | |WC | CompositorThread on a dedicated
|+C+| |+C+| |+C+| thread, with its own GPU context.
+---+ +---+ +---+
Opt-in topology -- one single-threaded Compositor, shared by all WCs:
+---------+ control +-------------------+
| Browser |<----------->| Compositor |
+---------+ | (single thread, |
/ | \ | single GPU ctx, |
v v v data | shared by all |
[WC] [WC] [WC] ---------->| connected WCs) |
+-------------------+
Three IPC channels carry the work in the opt-in topology. The
existing Browser<->WebContent channel gains context allocation.
A new Browser<->Compositor channel carries context lifetime,
viewport, UI input, and presentation acks plus backing-store and
frame upcalls. A new WebContent<->Compositor channel carries
display lists, scroll state, video, compositor surfaces, async
scrolling, presentation, and screenshots, with upcalls for
delegated input and compositor loss.
2026-05-22 11:17:27 -03:00
|
|
|
|
2026-05-12 14:38:29 -03:00
|
|
|
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI compositor IPC async_scroll_by page {} returned {} in {} us",
|
2026-05-11 16:45:34 -03:00
|
|
|
page_id, handled, timer.elapsed_time().to_microseconds());
|
|
|
|
|
return handled;
|
|
|
|
|
}
|
|
|
|
|
|
LibWebView+WebContent: Add feature-gated Compositor process backend
We are moving toward an architecture where the browser owns a single
process that holds the GPU context, so Skia resources can be shared
across every renderer and WebContent processes can be sandboxed away
from direct GPU access. A dedicated Compositor helper process is the
first step. The earlier commits laid the foundation; this one turns the
helper on as a selectable backend, gated behind
--enable-compositor-process so the existing in-process path stays the
shipping default.
Default topology -- one compositor per WebContent, in-process:
+---------+
| Browser |
+---------+
/ | \
v v v
+---+ +---+ +---+ each WebContent runs its own
|WC | |WC | |WC | CompositorThread on a dedicated
|+C+| |+C+| |+C+| thread, with its own GPU context.
+---+ +---+ +---+
Opt-in topology -- one single-threaded Compositor, shared by all WCs:
+---------+ control +-------------------+
| Browser |<----------->| Compositor |
+---------+ | (single thread, |
/ | \ | single GPU ctx, |
v v v data | shared by all |
[WC] [WC] [WC] ---------->| connected WCs) |
+-------------------+
Three IPC channels carry the work in the opt-in topology. The
existing Browser<->WebContent channel gains context allocation.
A new Browser<->Compositor channel carries context lifetime,
viewport, UI input, and presentation acks plus backing-store and
frame upcalls. A new WebContent<->Compositor channel carries
display lists, scroll state, video, compositor surfaces, async
scrolling, presentation, and screenshots, with upcalls for
delegated input and compositor loss.
2026-05-22 11:17:27 -03:00
|
|
|
bool WebContentClient::handle_mouse_event_in_compositor(u64 page_id, Web::MouseEvent const& event)
|
2026-05-12 16:49:42 -03:00
|
|
|
{
|
|
|
|
|
auto timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise);
|
LibWebView+WebContent: Add feature-gated Compositor process backend
We are moving toward an architecture where the browser owns a single
process that holds the GPU context, so Skia resources can be shared
across every renderer and WebContent processes can be sandboxed away
from direct GPU access. A dedicated Compositor helper process is the
first step. The earlier commits laid the foundation; this one turns the
helper on as a selectable backend, gated behind
--enable-compositor-process so the existing in-process path stays the
shipping default.
Default topology -- one compositor per WebContent, in-process:
+---------+
| Browser |
+---------+
/ | \
v v v
+---+ +---+ +---+ each WebContent runs its own
|WC | |WC | |WC | CompositorThread on a dedicated
|+C+| |+C+| |+C+| thread, with its own GPU context.
+---+ +---+ +---+
Opt-in topology -- one single-threaded Compositor, shared by all WCs:
+---------+ control +-------------------+
| Browser |<----------->| Compositor |
+---------+ | (single thread, |
/ | \ | single GPU ctx, |
v v v data | shared by all |
[WC] [WC] [WC] ---------->| connected WCs) |
+-------------------+
Three IPC channels carry the work in the opt-in topology. The
existing Browser<->WebContent channel gains context allocation.
A new Browser<->Compositor channel carries context lifetime,
viewport, UI input, and presentation acks plus backing-store and
frame upcalls. A new WebContent<->Compositor channel carries
display lists, scroll state, video, compositor surfaces, async
scrolling, presentation, and screenshots, with upcalls for
delegated input and compositor loss.
2026-05-22 11:17:27 -03:00
|
|
|
|
2026-05-24 16:51:33 -03:00
|
|
|
auto handled = Application::the().handle_mouse_event_in_compositor(compositor_context_id_for_page(page_id), event);
|
LibWebView+WebContent: Add feature-gated Compositor process backend
We are moving toward an architecture where the browser owns a single
process that holds the GPU context, so Skia resources can be shared
across every renderer and WebContent processes can be sandboxed away
from direct GPU access. A dedicated Compositor helper process is the
first step. The earlier commits laid the foundation; this one turns the
helper on as a selectable backend, gated behind
--enable-compositor-process so the existing in-process path stays the
shipping default.
Default topology -- one compositor per WebContent, in-process:
+---------+
| Browser |
+---------+
/ | \
v v v
+---+ +---+ +---+ each WebContent runs its own
|WC | |WC | |WC | CompositorThread on a dedicated
|+C+| |+C+| |+C+| thread, with its own GPU context.
+---+ +---+ +---+
Opt-in topology -- one single-threaded Compositor, shared by all WCs:
+---------+ control +-------------------+
| Browser |<----------->| Compositor |
+---------+ | (single thread, |
/ | \ | single GPU ctx, |
v v v data | shared by all |
[WC] [WC] [WC] ---------->| connected WCs) |
+-------------------+
Three IPC channels carry the work in the opt-in topology. The
existing Browser<->WebContent channel gains context allocation.
A new Browser<->Compositor channel carries context lifetime,
viewport, UI input, and presentation acks plus backing-store and
frame upcalls. A new WebContent<->Compositor channel carries
display lists, scroll state, video, compositor surfaces, async
scrolling, presentation, and screenshots, with upcalls for
delegated input and compositor loss.
2026-05-22 11:17:27 -03:00
|
|
|
|
2026-05-12 16:49:42 -03:00
|
|
|
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI compositor IPC mouse_event page {} returned {} in {} us",
|
|
|
|
|
page_id, handled, timer.elapsed_time().to_microseconds());
|
|
|
|
|
return handled;
|
|
|
|
|
}
|
|
|
|
|
|
LibWebView+WebContent: Add feature-gated Compositor process backend
We are moving toward an architecture where the browser owns a single
process that holds the GPU context, so Skia resources can be shared
across every renderer and WebContent processes can be sandboxed away
from direct GPU access. A dedicated Compositor helper process is the
first step. The earlier commits laid the foundation; this one turns the
helper on as a selectable backend, gated behind
--enable-compositor-process so the existing in-process path stays the
shipping default.
Default topology -- one compositor per WebContent, in-process:
+---------+
| Browser |
+---------+
/ | \
v v v
+---+ +---+ +---+ each WebContent runs its own
|WC | |WC | |WC | CompositorThread on a dedicated
|+C+| |+C+| |+C+| thread, with its own GPU context.
+---+ +---+ +---+
Opt-in topology -- one single-threaded Compositor, shared by all WCs:
+---------+ control +-------------------+
| Browser |<----------->| Compositor |
+---------+ | (single thread, |
/ | \ | single GPU ctx, |
v v v data | shared by all |
[WC] [WC] [WC] ---------->| connected WCs) |
+-------------------+
Three IPC channels carry the work in the opt-in topology. The
existing Browser<->WebContent channel gains context allocation.
A new Browser<->Compositor channel carries context lifetime,
viewport, UI input, and presentation acks plus backing-store and
frame upcalls. A new WebContent<->Compositor channel carries
display lists, scroll state, video, compositor surfaces, async
scrolling, presentation, and screenshots, with upcalls for
delegated input and compositor loss.
2026-05-22 11:17:27 -03:00
|
|
|
void WebContentClient::dispatch_mouse_event_to_web_content(u64 page_id, Web::MouseEvent const& event)
|
|
|
|
|
{
|
2026-05-30 12:36:06 -03:00
|
|
|
auto context_id = compositor_context_id_for_page(page_id);
|
|
|
|
|
if (Application::the().dispatch_mouse_event_to_web_content(context_id, event))
|
2026-05-24 16:51:33 -03:00
|
|
|
return;
|
LibWebView+WebContent: Add feature-gated Compositor process backend
We are moving toward an architecture where the browser owns a single
process that holds the GPU context, so Skia resources can be shared
across every renderer and WebContent processes can be sandboxed away
from direct GPU access. A dedicated Compositor helper process is the
first step. The earlier commits laid the foundation; this one turns the
helper on as a selectable backend, gated behind
--enable-compositor-process so the existing in-process path stays the
shipping default.
Default topology -- one compositor per WebContent, in-process:
+---------+
| Browser |
+---------+
/ | \
v v v
+---+ +---+ +---+ each WebContent runs its own
|WC | |WC | |WC | CompositorThread on a dedicated
|+C+| |+C+| |+C+| thread, with its own GPU context.
+---+ +---+ +---+
Opt-in topology -- one single-threaded Compositor, shared by all WCs:
+---------+ control +-------------------+
| Browser |<----------->| Compositor |
+---------+ | (single thread, |
/ | \ | single GPU ctx, |
v v v data | shared by all |
[WC] [WC] [WC] ---------->| connected WCs) |
+-------------------+
Three IPC channels carry the work in the opt-in topology. The
existing Browser<->WebContent channel gains context allocation.
A new Browser<->Compositor channel carries context lifetime,
viewport, UI input, and presentation acks plus backing-store and
frame upcalls. A new WebContent<->Compositor channel carries
display lists, scroll state, video, compositor surfaces, async
scrolling, presentation, and screenshots, with upcalls for
delegated input and compositor loss.
2026-05-22 11:17:27 -03:00
|
|
|
|
|
|
|
|
async_mouse_event(page_id, event.clone_without_browser_data());
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-11 16:02:29 -03:00
|
|
|
void WebContentClient::notify_presented_bitmap_ready_to_paint(u64 page_id, i32 bitmap_id)
|
|
|
|
|
{
|
2026-05-30 12:36:06 -03:00
|
|
|
auto context_id = Web::Compositor::compositor_context_id_for_page(page_id);
|
|
|
|
|
if (!m_compositor_contexts.contains(context_id))
|
LibWebView+WebContent: Add feature-gated Compositor process backend
We are moving toward an architecture where the browser owns a single
process that holds the GPU context, so Skia resources can be shared
across every renderer and WebContent processes can be sandboxed away
from direct GPU access. A dedicated Compositor helper process is the
first step. The earlier commits laid the foundation; this one turns the
helper on as a selectable backend, gated behind
--enable-compositor-process so the existing in-process path stays the
shipping default.
Default topology -- one compositor per WebContent, in-process:
+---------+
| Browser |
+---------+
/ | \
v v v
+---+ +---+ +---+ each WebContent runs its own
|WC | |WC | |WC | CompositorThread on a dedicated
|+C+| |+C+| |+C+| thread, with its own GPU context.
+---+ +---+ +---+
Opt-in topology -- one single-threaded Compositor, shared by all WCs:
+---------+ control +-------------------+
| Browser |<----------->| Compositor |
+---------+ | (single thread, |
/ | \ | single GPU ctx, |
v v v data | shared by all |
[WC] [WC] [WC] ---------->| connected WCs) |
+-------------------+
Three IPC channels carry the work in the opt-in topology. The
existing Browser<->WebContent channel gains context allocation.
A new Browser<->Compositor channel carries context lifetime,
viewport, UI input, and presentation acks plus backing-store and
frame upcalls. A new WebContent<->Compositor channel carries
display lists, scroll state, video, compositor surfaces, async
scrolling, presentation, and screenshots, with upcalls for
delegated input and compositor loss.
2026-05-22 11:17:27 -03:00
|
|
|
return;
|
|
|
|
|
|
2026-05-30 12:36:06 -03:00
|
|
|
Application::the().notify_compositor_presented_bitmap_ready_to_paint(context_id, bitmap_id);
|
2026-05-11 16:02:29 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::did_present_bitmap(u64 page_id, Gfx::IntRect rect, i32 bitmap_id)
|
2024-02-02 22:00:48 -03:00
|
|
|
{
|
2026-05-11 16:48:43 -03:00
|
|
|
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI compositor IPC did_paint for page {} bitmap {} rect={}x{} at {},{}",
|
|
|
|
|
page_id, bitmap_id, rect.width(), rect.height(), rect.x(), rect.y());
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2024-03-29 10:44:49 -03:00
|
|
|
view->server_did_paint({}, bitmap_id, rect.size());
|
2026-05-11 16:02:29 -03:00
|
|
|
} else {
|
|
|
|
|
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI dropping did_paint for page {} bitmap {}: no view",
|
|
|
|
|
page_id, bitmap_id);
|
2026-05-24 16:51:33 -03:00
|
|
|
notify_presented_bitmap_ready_to_paint(page_id, bitmap_id);
|
2026-05-11 16:02:29 -03:00
|
|
|
}
|
2023-05-17 13:37:31 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-09 12:40:34 -03:00
|
|
|
void WebContentClient::did_request_new_process_for_navigation(u64 page_id, URL::URL url)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->create_new_process_for_cross_site_navigation(url);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 19:33:00 -03:00
|
|
|
void WebContentClient::maybe_record_history_visit_for_current_load(u64 page_id, URL::URL const& url, Optional<String> title, StringView reason)
|
|
|
|
|
{
|
|
|
|
|
auto normalized_url = HistoryStore::normalize_url(url);
|
|
|
|
|
if (!normalized_url.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (auto recorded_url = m_history_recorded_urls_for_current_load.get(page_id); recorded_url.has_value() && *recorded_url == *normalized_url) {
|
|
|
|
|
dbgln_if(WEBVIEW_HISTORY_DEBUG, "[History] Visit for page {} at '{}' was already recorded during this load before {}", page_id, *normalized_url, reason);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbgln_if(WEBVIEW_HISTORY_DEBUG, "[History] Recording history visit for page {} at '{}' after {}", page_id, *normalized_url, reason);
|
|
|
|
|
|
|
|
|
|
// Title and favicon updates already give us a useful history entry, so
|
|
|
|
|
// do not wait for did_finish_loading() on pages that never reach it.
|
|
|
|
|
Application::history_store().record_visit(url, move(title));
|
|
|
|
|
m_history_recorded_urls_for_current_load.set(page_id, normalized_url.release_value());
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_start_loading(u64 page_id, URL::URL url, bool is_redirect)
|
2020-06-17 12:31:42 -03:00
|
|
|
{
|
2024-06-30 01:24:01 -03:00
|
|
|
if (auto process = WebView::Application::the().find_process(m_process_handle.pid); process.has_value())
|
|
|
|
|
process->set_title(OptionalNone {});
|
2024-04-22 15:11:46 -03:00
|
|
|
|
2026-04-11 19:33:00 -03:00
|
|
|
m_history_recorded_urls_for_current_load.remove(page_id);
|
|
|
|
|
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2026-05-20 11:42:08 -03:00
|
|
|
view->m_should_suppress_history_for_current_load = view->m_should_suppress_history_for_next_load;
|
|
|
|
|
view->m_should_suppress_history_for_next_load = false;
|
|
|
|
|
|
2024-03-29 10:44:49 -03:00
|
|
|
view->set_url({}, url);
|
2023-05-17 13:37:31 -03:00
|
|
|
|
2024-03-29 10:44:49 -03:00
|
|
|
if (view->on_load_start)
|
|
|
|
|
view->on_load_start(url, is_redirect);
|
2026-01-14 15:03:16 -03:00
|
|
|
|
|
|
|
|
for (auto const& [id, listener] : view->m_navigation_listeners) {
|
|
|
|
|
if (listener.on_load_start)
|
|
|
|
|
listener.on_load_start(url, is_redirect);
|
|
|
|
|
}
|
2024-03-29 10:44:49 -03:00
|
|
|
}
|
2020-06-17 12:31:42 -03:00
|
|
|
}
|
2020-06-17 13:00:18 -03:00
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_finish_loading(u64 page_id, URL::URL url)
|
2023-09-14 14:17:32 -03:00
|
|
|
{
|
2025-03-24 10:27:36 -03:00
|
|
|
if (url.scheme() == "about"sv && url.paths().size() == 1) {
|
2026-05-30 11:58:13 -03:00
|
|
|
if (auto web_ui = WebUI::create(*this, page_id, url.paths().first()); web_ui.is_error())
|
2025-03-24 10:27:36 -03:00
|
|
|
warnln("Could not create WebUI for {}: {}", url, web_ui.error());
|
|
|
|
|
else
|
|
|
|
|
m_web_ui = web_ui.release_value();
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2026-05-20 11:42:08 -03:00
|
|
|
auto client_url = url;
|
|
|
|
|
// Browser-generated pages can finish with an internal document URL.
|
|
|
|
|
// Keep exposing the URL accepted at load start for suppressed loads.
|
|
|
|
|
if (view->m_should_suppress_history_for_current_load)
|
|
|
|
|
client_url = view->url();
|
|
|
|
|
else
|
|
|
|
|
view->set_url({}, url);
|
|
|
|
|
auto should_update_history = !view->m_should_suppress_history_for_current_load;
|
2026-04-11 19:32:29 -03:00
|
|
|
auto title = history_title(view->title(), url);
|
|
|
|
|
|
2026-05-20 11:42:08 -03:00
|
|
|
if (should_update_history) {
|
|
|
|
|
dbgln_if(WEBVIEW_HISTORY_DEBUG, "[History] Load finished for page {} at '{}' with title '{}'",
|
|
|
|
|
page_id,
|
|
|
|
|
url,
|
|
|
|
|
title.has_value() ? title->bytes_as_string_view() : "<none>"sv);
|
|
|
|
|
|
|
|
|
|
maybe_record_history_visit_for_current_load(page_id, url, title, "load finish"sv);
|
|
|
|
|
if (title.has_value())
|
|
|
|
|
Application::history_store().update_title(url, *title);
|
|
|
|
|
if (view->favicon_base64_png().has_value())
|
|
|
|
|
Application::history_store().update_favicon(url, *view->favicon_base64_png());
|
|
|
|
|
}
|
2024-03-29 10:44:49 -03:00
|
|
|
if (view->on_load_finish)
|
2026-05-20 11:42:08 -03:00
|
|
|
view->on_load_finish(client_url);
|
2026-01-14 15:03:16 -03:00
|
|
|
|
|
|
|
|
for (auto const& [id, listener] : view->m_navigation_listeners) {
|
|
|
|
|
if (listener.on_load_finish)
|
2026-05-20 11:42:08 -03:00
|
|
|
listener.on_load_finish(client_url);
|
2026-01-14 15:03:16 -03:00
|
|
|
}
|
2024-03-29 10:44:49 -03:00
|
|
|
}
|
2023-09-14 14:17:32 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-18 13:37:32 -03:00
|
|
|
void WebContentClient::did_finish_test(u64 page_id, String text)
|
2022-11-11 15:33:11 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2025-03-18 13:37:32 -03:00
|
|
|
if (view->on_test_finish)
|
|
|
|
|
view->on_test_finish(text);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-19 11:16:05 -03:00
|
|
|
void WebContentClient::did_set_test_timeout(u64 page_id, double milliseconds)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_set_test_timeout)
|
|
|
|
|
view->on_set_test_timeout(milliseconds);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 05:24:15 -03:00
|
|
|
void WebContentClient::did_receive_reference_test_metadata(u64 page_id, JsonValue metadata)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_reference_test_metadata)
|
|
|
|
|
view->on_reference_test_metadata(metadata);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 13:33:48 -03:00
|
|
|
void WebContentClient::did_receive_test_variant_metadata(u64 page_id, JsonValue metadata)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_test_variant_metadata)
|
|
|
|
|
view->on_test_variant_metadata(metadata);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-23 18:15:06 -03:00
|
|
|
void WebContentClient::did_set_browser_zoom(u64 page_id, double factor)
|
|
|
|
|
{
|
2025-09-10 08:23:15 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->set_zoom(factor);
|
2024-12-23 18:15:06 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_find_in_page(u64 page_id, size_t current_match_index, Optional<size_t> total_match_count)
|
2024-06-09 14:35:32 -03:00
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_find_in_page)
|
|
|
|
|
view->on_find_in_page(current_match_index, total_match_count);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void WebContentClient::did_request_refresh(u64 page_id)
|
2022-11-11 15:33:11 -03:00
|
|
|
{
|
2024-09-22 14:29:27 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->reload();
|
2022-11-11 15:33:11 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_cursor_change(u64 page_id, Gfx::Cursor cursor)
|
2021-02-27 18:12:12 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_cursor_change)
|
2025-02-20 09:17:29 -03:00
|
|
|
view->on_cursor_change(cursor);
|
2024-03-29 10:44:49 -03:00
|
|
|
}
|
2021-02-27 18:12:12 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-28 17:51:01 -03:00
|
|
|
void WebContentClient::did_change_title(u64 page_id, Utf16String title)
|
2020-07-04 18:40:17 -03:00
|
|
|
{
|
2024-06-30 01:24:01 -03:00
|
|
|
if (auto process = WebView::Application::the().find_process(m_process_handle.pid); process.has_value())
|
2025-07-28 17:51:01 -03:00
|
|
|
process->set_title(title);
|
2024-04-22 15:11:46 -03:00
|
|
|
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2026-05-20 11:42:08 -03:00
|
|
|
if (!title.is_empty() && !view->m_should_suppress_history_for_current_load) {
|
2026-04-11 19:32:29 -03:00
|
|
|
auto title_utf8 = title.to_utf8();
|
|
|
|
|
|
2026-04-11 19:33:00 -03:00
|
|
|
maybe_record_history_visit_for_current_load(page_id, view->url(), title_utf8, "title change"sv);
|
2026-04-11 19:32:29 -03:00
|
|
|
dbgln_if(WEBVIEW_HISTORY_DEBUG, "[History] Title changed for page {} at '{}' to '{}'",
|
|
|
|
|
page_id,
|
|
|
|
|
view->url(),
|
|
|
|
|
title_utf8);
|
|
|
|
|
|
|
|
|
|
Application::history_store().update_title(view->url(), title_utf8);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-28 17:51:01 -03:00
|
|
|
if (title.is_empty())
|
|
|
|
|
title = Utf16String::from_utf8(view->url().serialize());
|
|
|
|
|
|
|
|
|
|
view->set_title({}, title);
|
2023-06-07 17:37:30 -03:00
|
|
|
|
2025-02-17 08:43:06 -03:00
|
|
|
if (view->on_title_change)
|
2025-07-28 17:51:01 -03:00
|
|
|
view->on_title_change(title);
|
2024-03-29 10:44:49 -03:00
|
|
|
}
|
2020-07-04 18:40:17 -03:00
|
|
|
}
|
2020-07-05 10:43:43 -03:00
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_change_url(u64 page_id, URL::URL url)
|
2024-04-14 05:27:20 -03:00
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2026-04-11 20:38:54 -03:00
|
|
|
// Some navigations report the same URL more than once. Keep those
|
|
|
|
|
// duplicate updates inside LibWebView so frontends do not reset
|
|
|
|
|
// location bar state or steal focus for a no-op change.
|
|
|
|
|
if (view->url() == url)
|
|
|
|
|
return;
|
|
|
|
|
|
2024-04-14 17:59:59 -03:00
|
|
|
view->set_url({}, url);
|
|
|
|
|
|
2024-04-14 05:27:20 -03:00
|
|
|
if (view->on_url_change)
|
|
|
|
|
view->on_url_change(url);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_tooltip_override(u64 page_id, Gfx::IntPoint position, ByteString title)
|
2024-07-02 11:48:57 -03:00
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_request_tooltip_override)
|
|
|
|
|
view->on_request_tooltip_override(view->to_widget_position(position), title);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::did_stop_tooltip_override(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_stop_tooltip_override)
|
|
|
|
|
view->on_stop_tooltip_override();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_enter_tooltip_area(u64 page_id, ByteString title)
|
2021-03-30 13:10:06 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_enter_tooltip_area)
|
2024-07-02 09:16:24 -03:00
|
|
|
view->on_enter_tooltip_area(title);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2021-03-30 13:10:06 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void WebContentClient::did_leave_tooltip_area(u64 page_id)
|
2021-03-30 13:10:06 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_leave_tooltip_area)
|
|
|
|
|
view->on_leave_tooltip_area();
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2021-03-30 13:10:06 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_hover_link(u64 page_id, URL::URL url)
|
2020-07-05 11:59:20 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_link_hover)
|
|
|
|
|
view->on_link_hover(url);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2020-07-05 11:59:20 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void WebContentClient::did_unhover_link(u64 page_id)
|
2020-07-05 11:59:20 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_link_unhover)
|
|
|
|
|
view->on_link_unhover();
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2020-07-05 11:59:20 -03:00
|
|
|
}
|
2020-07-06 15:01:46 -03:00
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_click_link(u64 page_id, URL::URL url, ByteString target, unsigned modifiers)
|
2020-07-06 15:01:46 -03:00
|
|
|
{
|
2025-09-13 10:08:24 -03:00
|
|
|
if (modifiers == Web::UIEvents::Mod_PlatformCtrl)
|
|
|
|
|
Application::the().open_url_in_new_tab(url, Web::HTML::ActivateTab::No);
|
|
|
|
|
else if (target == "_blank"sv)
|
|
|
|
|
Application::the().open_url_in_new_tab(url, Web::HTML::ActivateTab::Yes);
|
|
|
|
|
else if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->load(url);
|
2020-07-06 15:01:46 -03:00
|
|
|
}
|
|
|
|
|
|
2025-09-13 10:08:24 -03:00
|
|
|
void WebContentClient::did_middle_click_link(u64, URL::URL url, ByteString, unsigned)
|
2020-07-06 15:01:46 -03:00
|
|
|
{
|
2025-09-13 10:08:24 -03:00
|
|
|
Application::the().open_url_in_new_tab(url, Web::HTML::ActivateTab::No);
|
2020-07-06 16:58:16 -03:00
|
|
|
}
|
2020-07-07 07:24:29 -03:00
|
|
|
|
2026-05-06 13:00:59 -03:00
|
|
|
void WebContentClient::did_request_context_menu(u64 page_id, Gfx::IntPoint content_position, Web::ContextMenuForInputEventsTarget for_input_events_target)
|
2020-07-07 07:24:29 -03:00
|
|
|
{
|
2025-09-01 09:20:14 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
2026-05-06 13:00:59 -03:00
|
|
|
view->did_request_page_context_menu({}, content_position, for_input_events_target);
|
2020-07-07 07:24:29 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_link_context_menu(u64 page_id, Gfx::IntPoint content_position, URL::URL url, ByteString, unsigned)
|
2020-07-07 07:24:29 -03:00
|
|
|
{
|
2025-09-01 09:20:14 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->did_request_link_context_menu({}, content_position, move(url));
|
2020-07-07 07:24:29 -03:00
|
|
|
}
|
2020-08-24 08:03:18 -03:00
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_image_context_menu(u64 page_id, Gfx::IntPoint content_position, URL::URL url, ByteString, unsigned, Optional<Gfx::ShareableBitmap> bitmap)
|
2021-04-11 11:49:25 -03:00
|
|
|
{
|
2025-09-01 09:20:14 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->did_request_image_context_menu({}, content_position, move(url), move(bitmap));
|
2021-04-11 11:49:25 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_media_context_menu(u64 page_id, Gfx::IntPoint content_position, ByteString, unsigned, Web::Page::MediaContextMenu menu)
|
2023-05-15 10:42:56 -03:00
|
|
|
{
|
2025-09-01 09:20:14 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->did_request_media_context_menu({}, content_position, move(menu));
|
2023-05-15 10:42:56 -03:00
|
|
|
}
|
|
|
|
|
|
2025-09-13 10:08:24 -03:00
|
|
|
void WebContentClient::did_get_source(u64, URL::URL url, URL::URL base_url, String source)
|
2021-02-23 09:17:23 -03:00
|
|
|
{
|
2025-09-13 10:08:24 -03:00
|
|
|
if (auto view = Application::the().open_blank_new_tab(Web::HTML::ActivateTab::Yes); view.has_value()) {
|
2026-06-05 07:39:55 -03:00
|
|
|
auto html = highlight_source(url, base_url, source, Syntax::Language::HTML);
|
2025-09-13 10:08:24 -03:00
|
|
|
view->load_html(html);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2021-02-23 09:17:23 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
static JsonObject parse_json(StringView json, StringView name)
|
2025-02-21 14:39:43 -03:00
|
|
|
{
|
|
|
|
|
auto parsed_tree = JsonValue::from_string(json);
|
|
|
|
|
if (parsed_tree.is_error()) {
|
|
|
|
|
dbgln("Unable to parse {}: {}", name, parsed_tree.error());
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
if (!parsed_tree.value().is_object()) {
|
|
|
|
|
dbgln("Expected {} to be an object: {}", name, parsed_tree.value());
|
|
|
|
|
return {};
|
2025-02-21 14:39:43 -03:00
|
|
|
}
|
2025-03-19 16:50:56 -03:00
|
|
|
|
|
|
|
|
return move(parsed_tree.release_value().as_object());
|
2025-02-21 14:39:43 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-22 10:42:29 -03:00
|
|
|
static JsonArray parse_json_array(StringView json, StringView name)
|
|
|
|
|
{
|
|
|
|
|
auto parsed_tree = JsonValue::from_string(json);
|
|
|
|
|
if (parsed_tree.is_error()) {
|
|
|
|
|
dbgln("Unable to parse {}: {}", name, parsed_tree.error());
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!parsed_tree.value().is_array()) {
|
|
|
|
|
dbgln("Expected {} to be an array: {}", name, parsed_tree.value());
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return move(parsed_tree.release_value().as_array());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Optional<JsonObject> parse_optional_json_object(StringView json, StringView name)
|
|
|
|
|
{
|
|
|
|
|
auto parsed_tree = JsonValue::from_string(json);
|
|
|
|
|
if (parsed_tree.is_error()) {
|
|
|
|
|
dbgln("Unable to parse {}: {}", name, parsed_tree.error());
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parsed_tree.value().is_null())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
if (!parsed_tree.value().is_object()) {
|
|
|
|
|
dbgln("Expected {} to be an object or null: {}", name, parsed_tree.value());
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return move(parsed_tree.release_value().as_object());
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_inspect_dom_tree(u64 page_id, String dom_tree)
|
2021-06-07 12:35:10 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_received_dom_tree)
|
2025-02-21 14:39:43 -03:00
|
|
|
view->on_received_dom_tree(parse_json(dom_tree, "DOM tree"sv));
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2021-06-07 12:35:10 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
void WebContentClient::did_inspect_dom_node(u64 page_id, DOMNodeProperties properties)
|
2023-12-30 11:30:27 -03:00
|
|
|
{
|
2025-03-19 16:50:56 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_received_dom_node_properties)
|
|
|
|
|
view->on_received_dom_node_properties(move(properties));
|
2023-12-30 11:30:27 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 10:42:29 -03:00
|
|
|
void WebContentClient::did_inspect_grid_layouts(u64 page_id, String grid_layouts)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_received_grid_layouts)
|
|
|
|
|
view->on_received_grid_layouts(parse_json_array(grid_layouts, "grid layouts"sv));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::did_inspect_current_grid(u64 page_id, String grid_layout)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_received_current_grid)
|
|
|
|
|
view->on_received_current_grid(parse_optional_json_object(grid_layout, "current grid"sv));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 12:41:13 -03:00
|
|
|
void WebContentClient::did_inspect_current_flexbox(u64 page_id, String flexbox_layout)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_received_current_flexbox)
|
|
|
|
|
view->on_received_current_flexbox(parse_optional_json_object(flexbox_layout, "current flexbox"sv));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_inspect_accessibility_tree(u64 page_id, String accessibility_tree)
|
2023-05-17 13:37:31 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_received_accessibility_tree)
|
2025-02-21 14:39:43 -03:00
|
|
|
view->on_received_accessibility_tree(parse_json(accessibility_tree, "accessibility tree"sv));
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2021-08-27 08:47:30 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_get_hovered_node_id(u64 page_id, Web::UniqueNodeID node_id)
|
2023-12-30 11:42:24 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_received_hovered_node_id)
|
|
|
|
|
view->on_received_hovered_node_id(node_id);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2023-12-30 11:42:24 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 08:10:27 -03:00
|
|
|
void WebContentClient::did_get_node_id_at_position(u64 page_id, u64 request_id, Web::UniqueNodeID node_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2026-05-28 08:14:41 -03:00
|
|
|
view->did_receive_node_picker_hit_test(request_id, node_id);
|
2026-05-28 08:10:27 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_finish_editing_dom_node(u64 page_id, Optional<Web::UniqueNodeID> node_id)
|
2023-12-30 12:08:33 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2025-04-07 07:46:22 -03:00
|
|
|
if (view->on_finished_editing_dom_node)
|
|
|
|
|
view->on_finished_editing_dom_node(node_id);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2023-12-30 12:08:33 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_mutate_dom(u64 page_id, Mutation mutation)
|
2025-03-06 19:32:43 -03:00
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_dom_mutation_received)
|
2025-03-08 14:22:39 -03:00
|
|
|
view->on_dom_mutation_received(move(mutation));
|
2025-03-06 19:32:43 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_get_dom_node_html(u64 page_id, String html)
|
2023-12-30 16:32:50 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_received_dom_node_html)
|
2025-03-10 18:36:41 -03:00
|
|
|
view->on_received_dom_node_html(move(html));
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2023-12-30 16:32:50 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-14 17:22:16 -03:00
|
|
|
void WebContentClient::did_list_style_sheets(u64 page_id, Vector<Web::CSS::StyleSheetIdentifier> stylesheets)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_received_style_sheet_list)
|
|
|
|
|
view->on_received_style_sheet_list(stylesheets);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::did_get_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier, URL::URL base_url, String source)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_received_style_sheet_source)
|
|
|
|
|
view->on_received_style_sheet_source(identifier, base_url, source);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_take_screenshot(u64 page_id, Gfx::ShareableBitmap screenshot)
|
2023-12-31 11:51:02 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->did_receive_screenshot({}, screenshot);
|
2023-12-31 11:51:02 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-01 16:18:43 -03:00
|
|
|
void WebContentClient::did_get_internal_page_info(u64 page_id, WebView::PageInfoType type, Optional<Core::AnonymousBuffer> info)
|
2024-09-19 11:40:00 -03:00
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->did_receive_internal_page_info({}, type, info);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_execute_js_console_input(u64 page_id, JsonValue result)
|
2025-02-24 13:57:33 -03:00
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_received_js_console_result)
|
2025-03-08 14:22:39 -03:00
|
|
|
view->on_received_js_console_result(move(result));
|
2025-02-24 13:57:33 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-14 16:13:24 -03:00
|
|
|
void WebContentClient::did_output_js_console_message(u64 page_id, ConsoleOutput console_output)
|
2021-09-04 07:14:25 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2026-01-14 16:13:24 -03:00
|
|
|
if (view->on_console_message)
|
|
|
|
|
view->on_console_message(move(console_output));
|
2025-03-04 10:48:20 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-14 19:24:51 -03:00
|
|
|
void WebContentClient::did_start_network_request(u64 page_id, u64 request_id, URL::URL url, ByteString method, Vector<HTTP::Header> request_headers, ByteBuffer request_body, Optional<String> initiator_type)
|
2026-01-14 14:43:48 -03:00
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_network_request_started)
|
2026-01-14 19:24:51 -03:00
|
|
|
view->on_network_request_started(request_id, url, method, request_headers, move(request_body), move(initiator_type));
|
2026-01-14 14:43:48 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::did_receive_network_response_headers(u64 page_id, u64 request_id, u32 status_code, Optional<String> reason_phrase, Vector<HTTP::Header> response_headers)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_network_response_headers_received)
|
|
|
|
|
view->on_network_response_headers_received(request_id, status_code, reason_phrase, response_headers);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-14 16:47:04 -03:00
|
|
|
void WebContentClient::did_receive_network_response_body(u64 page_id, u64 request_id, ByteBuffer data)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_network_response_body_received)
|
|
|
|
|
view->on_network_response_body_received(request_id, move(data));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-14 14:43:48 -03:00
|
|
|
void WebContentClient::did_finish_network_request(u64 page_id, u64 request_id, u64 body_size, Requests::RequestTimingInfo timing_info, Optional<Requests::NetworkError> network_error)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_network_request_finished)
|
|
|
|
|
view->on_network_request_finished(request_id, body_size, timing_info, network_error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_alert(u64 page_id, String message)
|
2020-09-12 06:56:13 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_request_alert)
|
|
|
|
|
view->on_request_alert(message);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2020-09-12 06:56:13 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_confirm(u64 page_id, String message)
|
2021-02-10 04:48:28 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_request_confirm)
|
|
|
|
|
view->on_request_confirm(message);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2021-02-10 04:48:28 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_prompt(u64 page_id, String message, String default_)
|
2021-02-20 08:05:18 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_request_prompt)
|
|
|
|
|
view->on_request_prompt(message, default_);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2021-02-20 08:05:18 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_set_prompt_text(u64 page_id, String message)
|
2022-11-16 10:57:05 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_request_set_prompt_text)
|
|
|
|
|
view->on_request_set_prompt_text(message);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2022-11-16 10:57:05 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void WebContentClient::did_request_accept_dialog(u64 page_id)
|
2022-11-16 08:58:14 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_request_accept_dialog)
|
|
|
|
|
view->on_request_accept_dialog();
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2022-11-16 08:58:14 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void WebContentClient::did_request_dismiss_dialog(u64 page_id)
|
2022-11-16 08:58:14 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_request_dismiss_dialog)
|
|
|
|
|
view->on_request_dismiss_dialog();
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2022-11-16 08:58:14 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_change_favicon(u64 page_id, Gfx::ShareableBitmap favicon)
|
2021-03-26 11:41:25 -03:00
|
|
|
{
|
2021-05-02 14:54:34 -03:00
|
|
|
if (!favicon.is_valid()) {
|
2021-03-26 11:41:25 -03:00
|
|
|
dbgln("DidChangeFavicon: Received invalid favicon");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-05-17 13:37:31 -03:00
|
|
|
|
2026-04-11 19:33:00 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2026-05-20 11:42:08 -03:00
|
|
|
if (!view->m_should_suppress_history_for_current_load)
|
|
|
|
|
maybe_record_history_visit_for_current_load(page_id, view->url(), history_title(view->title(), view->url()), "favicon change"sv);
|
2026-03-22 12:44:54 -03:00
|
|
|
view->set_favicon({}, *favicon.bitmap());
|
2026-04-11 19:33:00 -03:00
|
|
|
}
|
2021-03-26 11:41:25 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-01 15:27:19 -03:00
|
|
|
void WebContentClient::did_request_document_cookie_version_index(u64 page_id, i64 document_id, String domain)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (auto document_index = view->ensure_document_cookie_version_index({}, domain); !document_index.is_error())
|
|
|
|
|
async_set_document_cookie_version_index(page_id, document_id, document_index.value());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 17:05:45 -03:00
|
|
|
Messages::WebContentClient::DidRequestAllCookiesWebdriverResponse WebContentClient::did_request_all_cookies_webdriver(URL::URL url)
|
2022-11-11 11:23:24 -03:00
|
|
|
{
|
2025-08-05 17:05:45 -03:00
|
|
|
return Application::cookie_jar().get_all_cookies_webdriver(url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Messages::WebContentClient::DidRequestAllCookiesCookiestoreResponse WebContentClient::did_request_all_cookies_cookiestore(URL::URL url)
|
|
|
|
|
{
|
|
|
|
|
return Application::cookie_jar().get_all_cookies_cookiestore(url);
|
2022-11-11 11:23:24 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
Messages::WebContentClient::DidRequestNamedCookieResponse WebContentClient::did_request_named_cookie(URL::URL url, String name)
|
2022-11-11 11:46:56 -03:00
|
|
|
{
|
2024-09-22 14:25:18 -03:00
|
|
|
return Application::cookie_jar().get_named_cookie(url, name);
|
2022-11-11 11:46:56 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-07 13:13:47 -03:00
|
|
|
Messages::WebContentClient::DidRequestCookieResponse WebContentClient::did_request_cookie(u64 page_id, URL::URL url, HTTP::Cookie::Source source)
|
2021-04-11 11:54:11 -03:00
|
|
|
{
|
2026-02-07 13:13:47 -03:00
|
|
|
HTTP::Cookie::VersionedCookie cookie;
|
2026-02-01 15:27:19 -03:00
|
|
|
cookie.cookie = Application::cookie_jar().get_cookie(url, source);
|
|
|
|
|
|
2026-02-07 13:13:47 -03:00
|
|
|
if (source == HTTP::Cookie::Source::NonHttp) {
|
2026-02-01 15:27:19 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
cookie.cookie_version = view->document_cookie_version(url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cookie;
|
2021-04-11 11:54:11 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-07 13:13:47 -03:00
|
|
|
void WebContentClient::did_set_cookie(URL::URL url, HTTP::Cookie::ParsedCookie cookie, HTTP::Cookie::Source source)
|
2021-04-11 11:54:11 -03:00
|
|
|
{
|
2024-09-22 14:25:18 -03:00
|
|
|
Application::cookie_jar().set_cookie(url, cookie, source);
|
2021-04-11 11:54:11 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-07 13:13:47 -03:00
|
|
|
void WebContentClient::did_update_cookie(HTTP::Cookie::Cookie cookie)
|
2022-11-11 13:18:40 -03:00
|
|
|
{
|
2024-09-22 14:25:18 -03:00
|
|
|
Application::cookie_jar().update_cookie(cookie);
|
2022-11-11 13:18:40 -03:00
|
|
|
}
|
|
|
|
|
|
2024-10-13 09:56:46 -03:00
|
|
|
void WebContentClient::did_expire_cookies_with_time_offset(AK::Duration offset)
|
|
|
|
|
{
|
|
|
|
|
Application::cookie_jar().expire_cookies_with_time_offset(offset);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 14:04:33 -03:00
|
|
|
void WebContentClient::did_store_hsts_policy(String domain, HTTP::HSTS::ParsedHSTSPolicy policy)
|
|
|
|
|
{
|
|
|
|
|
Application::hsts_store().store_policy(domain, policy);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Messages::WebContentClient::DidIsKnownHstsHostResponse WebContentClient::did_is_known_hsts_host(String domain)
|
|
|
|
|
{
|
|
|
|
|
return Application::hsts_store().is_known_hsts_host(domain);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-08 18:35:46 -03:00
|
|
|
Messages::WebContentClient::DidRequestStorageItemResponse WebContentClient::did_request_storage_item(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key, String bottle_key)
|
|
|
|
|
{
|
|
|
|
|
return Application::storage_jar().get_item(storage_endpoint, storage_key, bottle_key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Messages::WebContentClient::DidSetStorageItemResponse WebContentClient::did_set_storage_item(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key, String bottle_key, String value)
|
|
|
|
|
{
|
|
|
|
|
return Application::storage_jar().set_item(storage_endpoint, storage_key, bottle_key, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::did_remove_storage_item(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key, String bottle_key)
|
|
|
|
|
{
|
|
|
|
|
Application::storage_jar().remove_item(storage_endpoint, storage_key, bottle_key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Messages::WebContentClient::DidRequestStorageKeysResponse WebContentClient::did_request_storage_keys(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key)
|
|
|
|
|
{
|
|
|
|
|
return Application::storage_jar().get_all_keys(storage_endpoint, storage_key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::did_clear_storage(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key)
|
|
|
|
|
{
|
|
|
|
|
Application::storage_jar().clear_storage_key(storage_endpoint, storage_key);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 16:18:00 -03:00
|
|
|
void WebContentClient::did_post_broadcast_channel_message(u64, Web::HTML::BroadcastChannelMessage message)
|
|
|
|
|
{
|
|
|
|
|
WebContentClient::for_each_client([&](auto& client) {
|
|
|
|
|
if (client.pid() == message.source_process_id)
|
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
client.async_broadcast_channel_message(message);
|
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
2026-05-21 10:33:23 -03:00
|
|
|
WorkerProcessManager::the().broadcast_channel_message_from_web_content(message);
|
2026-04-10 16:18:00 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-30 11:58:13 -03:00
|
|
|
Messages::WebContentClient::DidRequestNewWebViewResponse WebContentClient::did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab activate_tab, Web::HTML::WebViewHints hints)
|
2023-03-14 11:12:09 -03:00
|
|
|
{
|
2026-05-30 11:58:13 -03:00
|
|
|
auto new_page_id = Application::the().allocate_page_id();
|
|
|
|
|
String handle;
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_new_web_view)
|
2026-05-30 11:58:13 -03:00
|
|
|
handle = view->on_new_web_view(activate_tab, hints, new_page_id);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-30 11:58:13 -03:00
|
|
|
return { new_page_id, move(handle) };
|
2023-03-14 11:12:09 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void WebContentClient::did_request_activate_tab(u64 page_id)
|
2023-03-20 20:52:00 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_activate_tab)
|
|
|
|
|
view->on_activate_tab();
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2023-03-20 20:52:00 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void WebContentClient::did_close_browsing_context(u64 page_id)
|
2023-03-07 00:10:26 -03:00
|
|
|
{
|
2026-05-26 13:03:07 -03:00
|
|
|
m_detached_pages_pending_close.remove(page_id);
|
|
|
|
|
|
|
|
|
|
if (auto view = m_views.get(page_id); view.has_value()) {
|
|
|
|
|
if ((*view)->on_close)
|
|
|
|
|
(*view)->on_close();
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2026-05-26 13:03:07 -03:00
|
|
|
|
|
|
|
|
close_server_if_unused();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::did_change_needs_beforeunload_check(u64 page_id, bool needs_beforeunload_check)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->did_change_needs_beforeunload_check({}, needs_beforeunload_check);
|
2023-03-07 00:10:26 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void WebContentClient::did_update_resource_count(u64 page_id, i32 count_waiting)
|
2022-02-20 19:03:39 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_resource_status_change)
|
|
|
|
|
view->on_resource_status_change(count_waiting);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2022-02-20 19:03:39 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void WebContentClient::did_request_restore_window(u64 page_id)
|
2022-11-09 11:51:39 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_restore_window)
|
|
|
|
|
view->on_restore_window();
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2022-11-09 11:51:39 -03:00
|
|
|
}
|
|
|
|
|
|
2024-10-29 00:37:11 -03:00
|
|
|
void WebContentClient::did_request_reposition_window(u64 page_id, Gfx::IntPoint position)
|
2022-11-09 11:51:39 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_reposition_window)
|
2024-10-29 00:37:11 -03:00
|
|
|
view->on_reposition_window(position);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2022-11-09 11:51:39 -03:00
|
|
|
}
|
|
|
|
|
|
2024-10-29 00:37:11 -03:00
|
|
|
void WebContentClient::did_request_resize_window(u64 page_id, Gfx::IntSize size)
|
2022-11-09 11:51:39 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_resize_window)
|
2024-10-29 00:37:11 -03:00
|
|
|
view->on_resize_window(size);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2022-11-09 11:51:39 -03:00
|
|
|
}
|
|
|
|
|
|
2024-10-29 00:45:18 -03:00
|
|
|
void WebContentClient::did_request_maximize_window(u64 page_id)
|
2022-11-09 12:04:09 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_maximize_window)
|
2024-10-29 00:45:18 -03:00
|
|
|
view->on_maximize_window();
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2022-11-09 12:04:09 -03:00
|
|
|
}
|
|
|
|
|
|
2024-10-29 00:45:18 -03:00
|
|
|
void WebContentClient::did_request_minimize_window(u64 page_id)
|
2022-11-09 12:04:09 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_minimize_window)
|
2024-10-29 00:45:18 -03:00
|
|
|
view->on_minimize_window();
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2022-11-09 12:04:09 -03:00
|
|
|
}
|
|
|
|
|
|
2026-03-16 19:38:31 -03:00
|
|
|
void WebContentClient::did_request_fullscreen_window(u64 page_id)
|
2022-11-09 15:09:17 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_fullscreen_window)
|
2024-10-29 00:45:18 -03:00
|
|
|
view->on_fullscreen_window();
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2025-03-27 11:59:17 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::did_request_exit_fullscreen(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_exit_fullscreen_window)
|
|
|
|
|
view->on_exit_fullscreen_window();
|
|
|
|
|
}
|
2022-11-09 15:09:17 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_file(u64 page_id, ByteString path, i32 request_id)
|
2022-02-26 13:50:31 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_request_file)
|
|
|
|
|
view->on_request_file(path, request_id);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2022-02-26 13:50:31 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_color_picker(u64 page_id, Color current_color)
|
2023-09-04 06:32:40 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_request_color_picker)
|
|
|
|
|
view->on_request_color_picker(current_color);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2023-09-04 06:32:40 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_file_picker(u64 page_id, Web::HTML::FileFilter accepted_file_types, Web::HTML::AllowMultipleFiles allow_multiple_files)
|
2024-02-25 15:02:47 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_request_file_picker)
|
|
|
|
|
view->on_request_file_picker(accepted_file_types, allow_multiple_files);
|
2024-02-25 15:02:47 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void WebContentClient::did_request_select_dropdown(u64 page_id, Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items)
|
2023-12-07 11:53:49 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_request_select_dropdown)
|
2025-03-21 11:53:59 -03:00
|
|
|
view->on_request_select_dropdown(view->to_widget_position(content_position), minimum_width / view->device_pixel_ratio(), items);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2023-12-07 11:53:49 -03:00
|
|
|
}
|
|
|
|
|
|
2024-09-12 13:56:31 -03:00
|
|
|
void WebContentClient::did_finish_handling_input_event(u64 page_id, Web::EventResult event_result)
|
2022-11-21 13:07:47 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
2024-09-12 13:56:31 -03:00
|
|
|
view->did_finish_handling_input_event({}, event_result);
|
2022-11-21 13:07:47 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 11:48:50 -03:00
|
|
|
void WebContentClient::did_update_input_caret_rect(u64 page_id, Optional<Web::DevicePixelRect> rect)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->set_input_caret_rect({}, rect);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void WebContentClient::did_change_theme_color(u64 page_id, Gfx::Color color)
|
2023-09-18 20:51:48 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
if (view->on_theme_color_change)
|
|
|
|
|
view->on_theme_color_change(color);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2023-09-18 20:51:48 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-24 15:44:30 -03:00
|
|
|
void WebContentClient::did_change_background_color(u64 page_id, Gfx::Color color)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->did_change_background_color({}, color);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-18 09:16:36 -03:00
|
|
|
void WebContentClient::did_insert_clipboard_entry(u64, Web::Clipboard::SystemClipboardRepresentation entry, String)
|
2023-11-10 15:35:41 -03:00
|
|
|
{
|
2025-09-18 09:16:36 -03:00
|
|
|
Application::the().insert_clipboard_entry(move(entry));
|
2025-05-01 12:38:31 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::did_request_clipboard_entries(u64 page_id, u64 request_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2025-09-18 09:16:36 -03:00
|
|
|
Vector<Web::Clipboard::SystemClipboardItem> items;
|
|
|
|
|
if (auto entries = Application::the().clipboard_entries(); !entries.is_empty())
|
|
|
|
|
items.empend(move(entries));
|
|
|
|
|
|
|
|
|
|
view->retrieved_clipboard_entries(request_id, items);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
2023-11-10 15:35:41 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-18 08:18:50 -03:00
|
|
|
void WebContentClient::did_request_primary_paste(u64 page_id)
|
2026-05-15 08:02:20 -03:00
|
|
|
{
|
2026-05-18 08:18:50 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
|
|
|
|
auto text = Application::the().clipboard_text(Application::ClipboardType::Selection);
|
|
|
|
|
view->client().async_paste(page_id, text);
|
|
|
|
|
}
|
2026-05-15 08:02:20 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-27 12:43:35 -03:00
|
|
|
void WebContentClient::did_update_primary_selection(u64 page_id, String text)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
Application::the().set_clipboard_text(move(text), Application::ClipboardType::Selection);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 20:27:06 -03:00
|
|
|
void WebContentClient::did_change_audio_play_state(u64 page_id, Web::HTML::AudioPlayState play_state)
|
|
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->did_change_audio_play_state({}, play_state);
|
2024-03-26 20:27:06 -03:00
|
|
|
}
|
|
|
|
|
|
2024-04-13 18:12:55 -03:00
|
|
|
void WebContentClient::did_update_navigation_buttons_state(u64 page_id, bool back_enabled, bool forward_enabled)
|
|
|
|
|
{
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value())
|
|
|
|
|
view->did_update_navigation_buttons_state({}, back_enabled, forward_enabled);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-11 16:02:29 -03:00
|
|
|
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)
|
2024-06-17 12:50:57 -03:00
|
|
|
{
|
2026-05-11 16:48:43 -03:00
|
|
|
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI received backing stores for page {} front={} back={}",
|
|
|
|
|
page_id, front_bitmap_id, back_bitmap_id);
|
|
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2026-03-23 17:58:03 -03:00
|
|
|
view->did_allocate_backing_stores({}, front_bitmap_id, move(front_backing_store), back_bitmap_id, move(back_backing_store));
|
2026-05-11 16:48:43 -03:00
|
|
|
} else {
|
|
|
|
|
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI dropping backing stores for page {} front={} back={}: no view",
|
|
|
|
|
page_id, front_bitmap_id, back_bitmap_id);
|
|
|
|
|
}
|
2024-06-17 12:50:57 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-21 10:33:23 -03:00
|
|
|
Messages::WebContentClient::StartWorkerAgentResponse WebContentClient::start_worker_agent(u64 page_id, Web::HTML::WorkerAgentStartRequest request)
|
2024-01-06 17:13:59 -03:00
|
|
|
{
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
2026-05-21 10:33:23 -03:00
|
|
|
auto agent_id = WorkerProcessManager::the().start_worker_agent(*this, page_id, move(request));
|
|
|
|
|
return { agent_id };
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-21 10:33:23 -03:00
|
|
|
return { 0 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebContentClient::close_worker_agent(u64, Web::HTML::WorkerAgentId agent_id, Web::HTML::WorkerAgentOwnerToken owner_token)
|
|
|
|
|
{
|
|
|
|
|
WorkerProcessManager::the().close_worker_agent(*this, agent_id, owner_token);
|
2024-03-29 10:44:49 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Optional<ViewImplementation&> WebContentClient::view_for_page_id(u64 page_id, SourceLocation location)
|
|
|
|
|
{
|
2025-03-09 19:01:25 -03:00
|
|
|
// Don't bother logging anything for the spare WebContent process. It will only receive a load notification for about:blank.
|
|
|
|
|
if (m_views.is_empty())
|
|
|
|
|
return {};
|
|
|
|
|
|
2024-03-29 10:44:49 -03:00
|
|
|
if (auto view = m_views.get(page_id); view.has_value())
|
|
|
|
|
return *view.value();
|
2024-01-06 17:13:59 -03:00
|
|
|
|
2024-03-29 10:44:49 -03:00
|
|
|
dbgln("WebContentClient::{}: Did not find a page with ID {}", location.function_name(), page_id);
|
|
|
|
|
return {};
|
2024-01-06 17:13:59 -03:00
|
|
|
}
|
|
|
|
|
|
2020-08-24 08:03:18 -03:00
|
|
|
}
|