ladybird/Libraries/LibWebView/WebContentClient.cpp
Sam Atkins 11b053b154 LibWebView: Add DevTools cookie mutation helpers
DevTools needs to edit and delete cookies from the browser process. Add
a small mutation surface to CookieJar and expose it through the DevTools
delegate while preserving the existing cookie notification path.
2026-06-11 15:03:47 +01:00

1239 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/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/NeverDestroyed.h>
#include <AK/WeakPtr.h>
#include <LibCore/ElapsedTimer.h>
#include <LibCore/Timer.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/HSTSStore.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 <LibWebView/WorkerProcessManager.h>
namespace WebView {
HashTable<WebContentClient*>& WebContentClient::clients()
{
static NeverDestroyed<HashTable<WebContentClient*>> clients;
return *clients;
}
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;
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, u64 initial_page_id)
: IPC::ConnectionToServer<WebContentClientEndpoint, WebContentServerEndpoint>(*this, move(transport))
, m_initial_page_id(initial_page_id)
{
VERIFY(m_initial_page_id > 0);
clients().set(this);
}
WebContentClient::~WebContentClient()
{
WorkerProcessManager::the().remove_web_content_owner(*this);
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)
{
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;
}
remember_compositor_context(context_id, page_id);
Application::the().register_compositor_context(*this, context_id, page_id);
return context_id;
}
Optional<u64> WebContentClient::page_id_for_compositor_context_id(Web::Compositor::CompositorContextId context_id) const
{
auto page_id = m_compositor_contexts.get(context_id);
if (!page_id.has_value())
return {};
return *page_id;
}
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 = Application::the().allocate_compositor_context_id();
remember_compositor_context(context_id, {});
Application::the().register_compositor_context(*this, context_id, {});
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;
return true;
}
void WebContentClient::remember_compositor_context(Web::Compositor::CompositorContextId context_id, Optional<u64> page_id)
{
m_compositor_contexts.set(context_id, page_id);
}
void WebContentClient::assign_view(Badge<Application>, ViewImplementation& view)
{
VERIFY(m_views.is_empty());
view.m_client_state.page_index = m_initial_page_id;
m_views.set(m_initial_page_id, 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);
if (m_detached_page_close_timer)
m_detached_page_close_timer->stop();
Application::process_manager().cancel_forced_exit(pid());
view.m_client_state.page_index = page_id;
m_views.set(page_id, view);
m_history_recorded_urls_for_current_load.remove(page_id);
}
void WebContentClient::unregister_view(u64 page_id)
{
forget_compositor_context(Web::Compositor::compositor_context_id_for_page(page_id));
// 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);
m_views.remove(page_id);
m_history_recorded_urls_for_current_load.remove(page_id);
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();
async_close_server();
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();
}
void WebContentClient::web_ui_disconnected(Badge<WebUI>)
{
m_web_ui.clear();
}
void WebContentClient::destroy_all_compositor_contexts()
{
m_compositor_contexts.clear();
}
ErrorOr<void> WebContentClient::reconnect_to_compositor_process(Badge<Application>)
{
if (!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 (!is_open())
return {};
for (auto const& [context_id, page_id] : m_compositor_contexts)
TRY(Application::the().try_register_compositor_context(*this, context_id, page_id));
return {};
}
void WebContentClient::replay_compositor_view_state_after_reconnect(Badge<Application>)
{
if (!is_open())
return;
for (auto const& [page_id, view] : m_views) {
auto context_id = Web::Compositor::compositor_context_id_for_page(page_id);
if (!m_compositor_contexts.contains(context_id))
continue;
Application::the().update_compositor_viewport(context_id, view->viewport_size().to_type<int>());
Application::the().update_compositor_display_metadata(context_id, view->display_id(), view->maximum_frames_per_second());
}
}
void WebContentClient::notify_compositor_process_reconnected(Badge<Application>)
{
if (!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);
auto handled = Application::the().send_async_scroll_to_compositor(compositor_context_id_for_page(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);
auto handled = Application::the().handle_mouse_event_in_compositor(compositor_context_id_for_page(page_id), event);
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)
{
auto context_id = compositor_context_id_for_page(page_id);
if (Application::the().dispatch_mouse_event_to_web_content(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)
{
auto context_id = Web::Compositor::compositor_context_id_for_page(page_id);
if (!m_compositor_contexts.contains(context_id))
return;
Application::the().notify_compositor_presented_bitmap_ready_to_paint(context_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);
notify_presented_bitmap_ready_to_paint(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, page_id, 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_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);
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());
}
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());
}
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_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));
}
}
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));
}
}
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_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()) {
view->did_receive_node_picker_hit_test(request_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)
{
(void)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);
}
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);
}
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;
});
WorkerProcessManager::the().broadcast_channel_message_from_web_content(message);
}
Messages::WebContentClient::DidRequestNewWebViewResponse WebContentClient::did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab activate_tab, Web::HTML::WebViewHints hints)
{
auto new_page_id = Application::the().allocate_page_id();
String handle;
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_new_web_view)
handle = view->on_new_web_view(activate_tab, hints, new_page_id);
}
return { new_page_id, move(handle) };
}
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)
{
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();
}
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);
}
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_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);
}
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_primary_paste(u64 page_id)
{
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);
}
}
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);
}
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::StartWorkerAgentResponse WebContentClient::start_worker_agent(u64 page_id, Web::HTML::WorkerAgentStartRequest request)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
auto agent_id = WorkerProcessManager::the().start_worker_agent(*this, page_id, move(request));
return { agent_id };
}
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);
}
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 {};
}
}