ladybird/Libraries/LibWebView/WebContentClient.cpp
Andreas Kling 8be96396d9 LibWebView: Use page background color while resizing
Notify the UI process with the solid canvas background color recorded
for the top-level document. This is the Canvas system color with the
effective document background composited over it, matching the color
used before normal painting.

Store that color on the view and use it when AppKit, Qt, and Gtk need
to fill areas exposed while an older bitmap is still on screen during a
window resize.
2026-05-24 23:41:41 +02:00

1186 lines
44 KiB
C++

/*
* Copyright (c) 2020-2021, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/NeverDestroyed.h>
#include <AK/WeakPtr.h>
#include <LibCore/ElapsedTimer.h>
#include <LibHTTP/Cookie/ParsedCookie.h>
#include <LibIPC/Transport.h>
#include <LibIPC/TransportHandle.h>
#include <LibWeb/Page/InputEvent.h>
#include <LibWebView/Application.h>
#include <LibWebView/CookieJar.h>
#include <LibWebView/HelperProcess.h>
#include <LibWebView/HistoryStore.h>
#include <LibWebView/SourceHighlighter.h>
#include <LibWebView/ViewImplementation.h>
#include <LibWebView/WebContentClient.h>
#include <LibWebView/WebUI.h>
#include <WebContent/CompositorClientEndpoint.h>
#include <WebContent/CompositorServerEndpoint.h>
namespace WebView {
HashTable<WebContentClient*> WebContentClient::s_clients;
class CompositorConnectionToServer final
: public IPC::ConnectionToServer<CompositorClientEndpoint, CompositorServerEndpoint>
, public CompositorClientEndpoint {
C_OBJECT(CompositorConnectionToServer)
public:
CompositorConnectionToServer(WebContentClient& web_content_client, NonnullOwnPtr<IPC::Transport> transport)
: IPC::ConnectionToServer<CompositorClientEndpoint, CompositorServerEndpoint>(*this, move(transport))
, m_web_content_client(web_content_client.make_weak_ptr<WebContentClient>())
{
}
private:
virtual void die() override { }
virtual void did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store) override
{
if (auto web_content_client = m_web_content_client.strong_ref())
web_content_client->did_present_backing_stores(page_id, front_bitmap_id, move(front_backing_store), back_bitmap_id, move(back_backing_store));
}
virtual void did_paint(u64 page_id, Gfx::IntRect content_rect, i32 bitmap_id) override
{
if (auto web_content_client = m_web_content_client.strong_ref())
web_content_client->did_present_bitmap(page_id, content_rect, bitmap_id);
}
WeakPtr<WebContentClient> m_web_content_client;
};
static HashMap<WebContentClient*, NonnullRefPtr<CompositorConnectionToServer>>& compositor_connections()
{
static NeverDestroyed<HashMap<WebContentClient*, NonnullRefPtr<CompositorConnectionToServer>>> connections;
return *connections;
}
static bool try_notify_presented_bitmap_ready_to_paint(WebContentClient& web_content_client, u64 page_id, i32 bitmap_id)
{
auto connection = compositor_connections().get(&web_content_client);
if (!connection.has_value() || !connection.value()->is_open())
return false;
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI sending compositor ready_to_paint for page {} bitmap {}",
page_id, bitmap_id);
connection.value()->async_ready_to_paint(page_id, bitmap_id);
return true;
}
static void initialize_compositor_connection(WebContentClient& web_content_client)
{
auto paired_transport = MUST(IPC::Transport::create_paired());
auto connection = CompositorConnectionToServer::construct(web_content_client, move(paired_transport.local));
compositor_connections().set(&web_content_client, connection);
web_content_client.async_connect_to_compositor(move(paired_transport.remote_handle));
}
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;
}
WebContentClient::WebContentClient(NonnullOwnPtr<IPC::Transport> transport, ViewImplementation& view)
: IPC::ConnectionToServer<WebContentClientEndpoint, WebContentServerEndpoint>(*this, move(transport))
{
s_clients.set(this);
m_views.set(0, view);
if (!Application::the().should_use_compositor_process())
initialize_compositor_connection(*this);
}
WebContentClient::WebContentClient(NonnullOwnPtr<IPC::Transport> transport)
: IPC::ConnectionToServer<WebContentClientEndpoint, WebContentServerEndpoint>(*this, move(transport))
{
s_clients.set(this);
if (!Application::the().should_use_compositor_process())
initialize_compositor_connection(*this);
}
WebContentClient::~WebContentClient()
{
compositor_connections().remove(this);
s_clients.remove(this);
}
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;
}
void WebContentClient::die()
{
// Intentionally empty. Restart is handled at another level.
}
Web::Compositor::CompositorContextId WebContentClient::compositor_context_id_for_page(u64 page_id)
{
if (auto context_id = m_page_compositor_context_ids.get(page_id); context_id.has_value())
return *context_id;
auto context_id = Web::Compositor::allocate_compositor_context_id();
VERIFY(!Web::Compositor::is_page_presenting_compositor_context_id(context_id));
remember_compositor_context(context_id, page_id, Web::Compositor::PagePresentationRegistration::Yes);
Application::the().register_compositor_context(*this, context_id, page_id, Web::Compositor::PagePresentationRegistration::Yes);
return context_id;
}
Optional<u64> WebContentClient::page_id_for_compositor_context_id(Web::Compositor::CompositorContextId context_id) const
{
if (auto page_id = m_page_ids_for_compositor_context_ids.get(context_id); page_id.has_value())
return *page_id;
return {};
}
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);
auto context_id = Web::Compositor::allocate_compositor_context_id();
VERIFY(!Web::Compositor::is_page_presenting_compositor_context_id(context_id));
remember_compositor_context(context_id, {}, page_presentation_registration);
Application::the().register_compositor_context(*this, context_id, {}, page_presentation_registration);
return context_id;
}
void WebContentClient::did_destroy_compositor_context(Web::Compositor::CompositorContextId context_id)
{
forget_compositor_context(context_id);
}
bool WebContentClient::forget_compositor_context(Web::Compositor::CompositorContextId context_id)
{
if (!m_compositor_contexts.remove(context_id))
return false;
if (auto page_id = m_page_ids_for_compositor_context_ids.take(context_id); page_id.has_value())
m_page_compositor_context_ids.remove(*page_id);
return true;
}
void WebContentClient::remember_compositor_context(Web::Compositor::CompositorContextId context_id, Optional<u64> page_id, Web::Compositor::PagePresentationRegistration page_presentation_registration)
{
m_compositor_contexts.set(context_id, CompositorContextRegistration {
.page_id = page_id,
.page_presentation_registration = page_presentation_registration,
});
if (page_id.has_value()) {
m_page_compositor_context_ids.set(*page_id, context_id);
m_page_ids_for_compositor_context_ids.set(context_id, *page_id);
}
}
void WebContentClient::assign_view(Badge<Application>, ViewImplementation& view)
{
VERIFY(m_views.is_empty());
m_views.set(0, view);
}
void WebContentClient::set_compositor_connection_id(Badge<Application>, i32 compositor_connection_id)
{
m_compositor_connection_id = compositor_connection_id;
}
void WebContentClient::register_view(u64 page_id, ViewImplementation& view)
{
VERIFY(page_id > 0);
m_views.set(page_id, view);
m_history_recorded_urls_for_current_load.remove(page_id);
}
void WebContentClient::unregister_view(u64 page_id)
{
if (auto context_id = m_page_compositor_context_ids.get(page_id); context_id.has_value())
forget_compositor_context(*context_id);
m_views.remove(page_id);
m_history_recorded_urls_for_current_load.remove(page_id);
if (m_views.is_empty())
async_close_server();
}
void WebContentClient::web_ui_disconnected(Badge<WebUI>)
{
m_web_ui.clear();
}
void WebContentClient::destroy_all_compositor_contexts()
{
m_compositor_contexts.clear();
m_page_compositor_context_ids.clear();
m_page_ids_for_compositor_context_ids.clear();
}
ErrorOr<void> WebContentClient::reconnect_to_compositor_process(Badge<Application>)
{
if (!Application::the().should_use_compositor_process() || !is_open())
return {};
m_compositor_connection_id.clear();
TRY(Application::the().connect_web_content_to_compositor(*this));
return {};
}
ErrorOr<void> WebContentClient::recreate_compositor_contexts(Badge<Application>)
{
if (!Application::the().should_use_compositor_process() || !is_open())
return {};
for (auto const& [context_id, registration] : m_compositor_contexts)
TRY(Application::the().try_register_compositor_context(*this, context_id, registration.page_id, registration.page_presentation_registration));
return {};
}
void WebContentClient::update_compositor_viewports_after_reconnect(Badge<Application>)
{
if (!Application::the().should_use_compositor_process() || !is_open())
return;
for (auto const& [page_id, view] : m_views) {
auto context_id = m_page_compositor_context_ids.get(page_id);
if (!context_id.has_value())
continue;
Application::the().update_compositor_viewport(*context_id, view->viewport_size().to_type<int>());
}
}
void WebContentClient::notify_compositor_process_reconnected(Badge<Application>)
{
if (!Application::the().should_use_compositor_process() || !is_open())
return;
async_compositor_process_reconnected();
}
void WebContentClient::notify_all_views_of_crash()
{
destroy_all_compositor_contexts();
// 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)
view_ids.unchecked_append(view->view_id());
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();
});
}
}
bool WebContentClient::send_async_scroll_to_compositor(u64 page_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels)
{
auto timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise);
bool handled = false;
if (Application::the().should_use_compositor_process()) {
handled = Application::the().send_async_scroll_to_compositor(compositor_context_id_for_page(page_id), position, delta_in_device_pixels);
} else {
auto connection = compositor_connections().get(this);
VERIFY(connection.has_value());
VERIFY(connection.value()->is_open());
handled = connection.value()->async_scroll_by(page_id, position, delta_in_device_pixels);
}
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI compositor IPC async_scroll_by page {} returned {} in {} us",
page_id, handled, timer.elapsed_time().to_microseconds());
return handled;
}
bool WebContentClient::handle_mouse_event_in_compositor(u64 page_id, Web::MouseEvent const& event)
{
auto timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise);
bool handled = false;
if (Application::the().should_use_compositor_process()) {
handled = Application::the().handle_mouse_event_in_compositor(compositor_context_id_for_page(page_id), event);
} else {
auto connection = compositor_connections().get(this);
VERIFY(connection.has_value());
VERIFY(connection.value()->is_open());
handled = connection.value()->mouse_event(page_id, event.clone_without_browser_data());
}
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI compositor IPC mouse_event page {} returned {} in {} us",
page_id, handled, timer.elapsed_time().to_microseconds());
return handled;
}
void WebContentClient::dispatch_mouse_event_to_web_content(u64 page_id, Web::MouseEvent const& event)
{
if (Application::the().should_use_compositor_process()) {
auto context_id = m_page_compositor_context_ids.get(page_id);
if (context_id.has_value() && Application::the().dispatch_mouse_event_to_web_content(*context_id, event))
return;
if (!context_id.has_value()) {
auto new_context_id = compositor_context_id_for_page(page_id);
if (Application::the().dispatch_mouse_event_to_web_content(new_context_id, event))
return;
}
}
async_mouse_event(page_id, event.clone_without_browser_data());
}
void WebContentClient::notify_presented_bitmap_ready_to_paint(u64 page_id, i32 bitmap_id)
{
if (Application::the().should_use_compositor_process()) {
auto context_id = m_page_compositor_context_ids.get(page_id);
if (!context_id.has_value())
return;
Application::the().notify_compositor_presented_bitmap_ready_to_paint(*context_id, bitmap_id);
return;
}
VERIFY(try_notify_presented_bitmap_ready_to_paint(*this, page_id, bitmap_id));
}
void WebContentClient::did_present_bitmap(u64 page_id, Gfx::IntRect rect, i32 bitmap_id)
{
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()) {
view->server_did_paint({}, bitmap_id, rect.size());
} else {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI dropping did_paint for page {} bitmap {}: no view",
page_id, bitmap_id);
if (Application::the().should_use_compositor_process())
notify_presented_bitmap_ready_to_paint(page_id, bitmap_id);
else
try_notify_presented_bitmap_ready_to_paint(*this, page_id, bitmap_id);
}
}
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);
}
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());
}
void WebContentClient::did_start_loading(u64 page_id, URL::URL url, bool is_redirect)
{
if (auto process = WebView::Application::the().find_process(m_process_handle.pid); process.has_value())
process->set_title(OptionalNone {});
m_history_recorded_urls_for_current_load.remove(page_id);
if (auto view = view_for_page_id(page_id); view.has_value()) {
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;
view->set_url({}, url);
if (view->on_load_start)
view->on_load_start(url, is_redirect);
for (auto const& [id, listener] : view->m_navigation_listeners) {
if (listener.on_load_start)
listener.on_load_start(url, is_redirect);
}
}
}
void WebContentClient::did_finish_loading(u64 page_id, URL::URL url)
{
if (url.scheme() == "about"sv && url.paths().size() == 1) {
if (auto web_ui = WebUI::create(*this, url.paths().first()); web_ui.is_error())
warnln("Could not create WebUI for {}: {}", url, web_ui.error());
else
m_web_ui = web_ui.release_value();
}
if (auto view = view_for_page_id(page_id); view.has_value()) {
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;
auto title = history_title(view->title(), url);
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());
}
if (view->on_load_finish)
view->on_load_finish(client_url);
for (auto const& [id, listener] : view->m_navigation_listeners) {
if (listener.on_load_finish)
listener.on_load_finish(client_url);
}
}
}
void WebContentClient::did_finish_test(u64 page_id, String text)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_test_finish)
view->on_test_finish(text);
}
}
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);
}
}
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);
}
}
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);
}
}
void WebContentClient::did_set_browser_zoom(u64 page_id, double factor)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->set_zoom(factor);
}
void WebContentClient::did_find_in_page(u64 page_id, size_t current_match_index, Optional<size_t> total_match_count)
{
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);
}
}
void WebContentClient::did_request_refresh(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->reload();
}
void WebContentClient::did_request_cursor_change(u64 page_id, Gfx::Cursor cursor)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_cursor_change)
view->on_cursor_change(cursor);
}
}
void WebContentClient::did_change_title(u64 page_id, Utf16String title)
{
if (auto process = WebView::Application::the().find_process(m_process_handle.pid); process.has_value())
process->set_title(title);
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (!title.is_empty() && !view->m_should_suppress_history_for_current_load) {
auto title_utf8 = title.to_utf8();
maybe_record_history_visit_for_current_load(page_id, view->url(), title_utf8, "title change"sv);
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);
}
if (title.is_empty())
title = Utf16String::from_utf8(view->url().serialize());
view->set_title({}, title);
if (view->on_title_change)
view->on_title_change(title);
}
}
void WebContentClient::did_change_url(u64 page_id, URL::URL url)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
// 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;
view->set_url({}, url);
if (view->on_url_change)
view->on_url_change(url);
}
}
void WebContentClient::did_request_tooltip_override(u64 page_id, Gfx::IntPoint position, ByteString title)
{
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();
}
}
void WebContentClient::did_enter_tooltip_area(u64 page_id, ByteString title)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_enter_tooltip_area)
view->on_enter_tooltip_area(title);
}
}
void WebContentClient::did_leave_tooltip_area(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_leave_tooltip_area)
view->on_leave_tooltip_area();
}
}
void WebContentClient::did_hover_link(u64 page_id, URL::URL url)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_link_hover)
view->on_link_hover(url);
}
}
void WebContentClient::did_unhover_link(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_link_unhover)
view->on_link_unhover();
}
}
void WebContentClient::did_click_link(u64 page_id, URL::URL url, ByteString target, unsigned modifiers)
{
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);
}
void WebContentClient::did_middle_click_link(u64, URL::URL url, ByteString, unsigned)
{
Application::the().open_url_in_new_tab(url, Web::HTML::ActivateTab::No);
}
void WebContentClient::did_request_context_menu(u64 page_id, Gfx::IntPoint content_position, Web::ContextMenuForInputEventsTarget for_input_events_target)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->did_request_page_context_menu({}, content_position, for_input_events_target);
}
void WebContentClient::did_request_link_context_menu(u64 page_id, Gfx::IntPoint content_position, URL::URL url, ByteString, unsigned)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->did_request_link_context_menu({}, content_position, move(url));
}
void WebContentClient::did_request_image_context_menu(u64 page_id, Gfx::IntPoint content_position, URL::URL url, ByteString, unsigned, Optional<Gfx::ShareableBitmap> bitmap)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->did_request_image_context_menu({}, content_position, move(url), move(bitmap));
}
void WebContentClient::did_request_media_context_menu(u64 page_id, Gfx::IntPoint content_position, ByteString, unsigned, Web::Page::MediaContextMenu menu)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->did_request_media_context_menu({}, content_position, move(menu));
}
void WebContentClient::did_get_source(u64, URL::URL url, URL::URL base_url, String source)
{
if (auto view = Application::the().open_blank_new_tab(Web::HTML::ActivateTab::Yes); view.has_value()) {
auto html = highlight_source(url, base_url, source, Syntax::Language::HTML, WebView::HighlightOutputMode::FullDocument);
view->load_html(html);
}
}
static JsonObject parse_json(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_object()) {
dbgln("Expected {} to be an object: {}", name, parsed_tree.value());
return {};
}
return move(parsed_tree.release_value().as_object());
}
void WebContentClient::did_inspect_dom_tree(u64 page_id, String dom_tree)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_received_dom_tree)
view->on_received_dom_tree(parse_json(dom_tree, "DOM tree"sv));
}
}
void WebContentClient::did_inspect_dom_node(u64 page_id, DOMNodeProperties properties)
{
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));
}
}
void WebContentClient::did_inspect_accessibility_tree(u64 page_id, String accessibility_tree)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_received_accessibility_tree)
view->on_received_accessibility_tree(parse_json(accessibility_tree, "accessibility tree"sv));
}
}
void WebContentClient::did_get_hovered_node_id(u64 page_id, Web::UniqueNodeID node_id)
{
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);
}
}
void WebContentClient::did_finish_editing_dom_node(u64 page_id, Optional<Web::UniqueNodeID> node_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_finished_editing_dom_node)
view->on_finished_editing_dom_node(node_id);
}
}
void WebContentClient::did_mutate_dom(u64 page_id, Mutation mutation)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_dom_mutation_received)
view->on_dom_mutation_received(move(mutation));
}
}
void WebContentClient::did_get_dom_node_html(u64 page_id, String html)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_received_dom_node_html)
view->on_received_dom_node_html(move(html));
}
}
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);
}
}
void WebContentClient::did_take_screenshot(u64 page_id, Gfx::ShareableBitmap screenshot)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->did_receive_screenshot({}, screenshot);
}
void WebContentClient::did_get_internal_page_info(u64 page_id, WebView::PageInfoType type, Optional<Core::AnonymousBuffer> info)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->did_receive_internal_page_info({}, type, info);
}
void WebContentClient::did_execute_js_console_input(u64 page_id, JsonValue result)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_received_js_console_result)
view->on_received_js_console_result(move(result));
}
}
void WebContentClient::did_output_js_console_message(u64 page_id, ConsoleOutput console_output)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_console_message)
view->on_console_message(move(console_output));
}
}
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)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_network_request_started)
view->on_network_request_started(request_id, url, method, request_headers, move(request_body), move(initiator_type));
}
}
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);
}
}
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));
}
}
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);
}
}
void WebContentClient::did_request_alert(u64 page_id, String message)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_request_alert)
view->on_request_alert(message);
}
}
void WebContentClient::did_request_confirm(u64 page_id, String message)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_request_confirm)
view->on_request_confirm(message);
}
}
void WebContentClient::did_request_prompt(u64 page_id, String message, String default_)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_request_prompt)
view->on_request_prompt(message, default_);
}
}
void WebContentClient::did_request_set_prompt_text(u64 page_id, String message)
{
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);
}
}
void WebContentClient::did_request_accept_dialog(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_request_accept_dialog)
view->on_request_accept_dialog();
}
}
void WebContentClient::did_request_dismiss_dialog(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_request_dismiss_dialog)
view->on_request_dismiss_dialog();
}
}
void WebContentClient::did_change_favicon(u64 page_id, Gfx::ShareableBitmap favicon)
{
if (!favicon.is_valid()) {
dbgln("DidChangeFavicon: Received invalid favicon");
return;
}
if (auto view = view_for_page_id(page_id); view.has_value()) {
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);
view->set_favicon({}, *favicon.bitmap());
}
}
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());
}
}
Messages::WebContentClient::DidRequestAllCookiesWebdriverResponse WebContentClient::did_request_all_cookies_webdriver(URL::URL url)
{
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);
}
Messages::WebContentClient::DidRequestNamedCookieResponse WebContentClient::did_request_named_cookie(URL::URL url, String name)
{
return Application::cookie_jar().get_named_cookie(url, name);
}
Messages::WebContentClient::DidRequestCookieResponse WebContentClient::did_request_cookie(u64 page_id, URL::URL url, HTTP::Cookie::Source source)
{
HTTP::Cookie::VersionedCookie cookie;
cookie.cookie = Application::cookie_jar().get_cookie(url, source);
if (source == HTTP::Cookie::Source::NonHttp) {
if (auto view = view_for_page_id(page_id); view.has_value())
cookie.cookie_version = view->document_cookie_version(url);
}
return cookie;
}
void WebContentClient::did_set_cookie(URL::URL url, HTTP::Cookie::ParsedCookie cookie, HTTP::Cookie::Source source)
{
Application::cookie_jar().set_cookie(url, cookie, source);
}
void WebContentClient::did_update_cookie(HTTP::Cookie::Cookie cookie)
{
Application::cookie_jar().update_cookie(cookie);
}
void WebContentClient::did_expire_cookies_with_time_offset(AK::Duration offset)
{
Application::cookie_jar().expire_cookies_with_time_offset(offset);
}
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);
}
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;
});
}
Messages::WebContentClient::DidRequestNewWebViewResponse WebContentClient::did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab activate_tab, Web::HTML::WebViewHints hints, Optional<u64> page_index)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_new_web_view)
return view->on_new_web_view(activate_tab, hints, page_index);
}
return String {};
}
void WebContentClient::did_request_activate_tab(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_activate_tab)
view->on_activate_tab();
}
}
void WebContentClient::did_close_browsing_context(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_close)
view->on_close();
}
}
void WebContentClient::did_update_resource_count(u64 page_id, i32 count_waiting)
{
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);
}
}
void WebContentClient::did_request_restore_window(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_restore_window)
view->on_restore_window();
}
}
void WebContentClient::did_request_reposition_window(u64 page_id, Gfx::IntPoint position)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_reposition_window)
view->on_reposition_window(position);
}
}
void WebContentClient::did_request_resize_window(u64 page_id, Gfx::IntSize size)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_resize_window)
view->on_resize_window(size);
}
}
void WebContentClient::did_request_maximize_window(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_maximize_window)
view->on_maximize_window();
}
}
void WebContentClient::did_request_minimize_window(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_minimize_window)
view->on_minimize_window();
}
}
void WebContentClient::did_request_fullscreen_window(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_fullscreen_window)
view->on_fullscreen_window();
}
}
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();
}
}
void WebContentClient::did_request_file(u64 page_id, ByteString path, i32 request_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_request_file)
view->on_request_file(path, request_id);
}
}
void WebContentClient::did_request_color_picker(u64 page_id, Color current_color)
{
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);
}
}
void WebContentClient::did_request_file_picker(u64 page_id, Web::HTML::FileFilter accepted_file_types, Web::HTML::AllowMultipleFiles allow_multiple_files)
{
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);
}
}
void WebContentClient::did_request_select_dropdown(u64 page_id, Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_request_select_dropdown)
view->on_request_select_dropdown(view->to_widget_position(content_position), minimum_width / view->device_pixel_ratio(), items);
}
}
void WebContentClient::did_finish_handling_input_event(u64 page_id, Web::EventResult event_result)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->did_finish_handling_input_event({}, event_result);
}
void WebContentClient::did_change_theme_color(u64 page_id, Gfx::Color color)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_theme_color_change)
view->on_theme_color_change(color);
}
}
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);
}
void WebContentClient::did_insert_clipboard_entry(u64, Web::Clipboard::SystemClipboardRepresentation entry, String)
{
Application::the().insert_clipboard_entry(move(entry));
}
void WebContentClient::did_request_clipboard_entries(u64 page_id, u64 request_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
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);
}
}
void WebContentClient::did_request_paste(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->paste_text_from_clipboard();
}
void WebContentClient::did_change_audio_play_state(u64 page_id, Web::HTML::AudioPlayState play_state)
{
if (auto view = view_for_page_id(page_id); view.has_value())
view->did_change_audio_play_state({}, play_state);
}
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);
}
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)
{
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()) {
view->did_allocate_backing_stores({}, front_bitmap_id, move(front_backing_store), back_bitmap_id, move(back_backing_store));
} else {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] UI dropping backing stores for page {} front={} back={}: no view",
page_id, front_bitmap_id, back_bitmap_id);
}
}
Messages::WebContentClient::RequestWorkerAgentResponse WebContentClient::request_worker_agent(u64 page_id, Web::Bindings::AgentType worker_type)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
auto request_server_handle = MUST(connect_new_request_server_client());
auto image_decoder_handle = MUST(connect_new_image_decoder_client());
auto worker_client = MUST(WebView::launch_web_worker_process(worker_type));
auto worker_handle = MUST(worker_client->transport().release_for_transfer());
return { move(worker_handle), move(request_server_handle), move(image_decoder_handle) };
}
return { IPC::TransportHandle {}, IPC::TransportHandle {}, IPC::TransportHandle {} };
}
Optional<ViewImplementation&> WebContentClient::view_for_page_id(u64 page_id, SourceLocation location)
{
// 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 {};
if (auto view = m_views.get(page_id); view.has_value())
return *view.value();
dbgln("WebContentClient::{}: Did not find a page with ID {}", location.function_name(), page_id);
return {};
}
}