2020-06-17 12:31:42 -03:00
|
|
|
/*
|
2024-10-04 08:19:50 -03:00
|
|
|
* Copyright (c) 2020-2023, Andreas Kling <andreas@ladybird.org>
|
2025-07-23 07:29:03 -03:00
|
|
|
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
|
2023-01-12 11:35:35 -03:00
|
|
|
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
2022-10-18 07:40:14 -03:00
|
|
|
* Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
|
2022-11-03 14:30:11 -03:00
|
|
|
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
|
2024-02-02 22:00:48 -03:00
|
|
|
* Copyright (c) 2023-2024, Andrew Kaster <akaster@serenityos.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-05-11 16:45:34 -03:00
|
|
|
#include <AK/Debug.h>
|
2026-05-22 10:42:29 -03:00
|
|
|
#include <AK/JsonArray.h>
|
2021-06-07 12:35:10 -03:00
|
|
|
#include <AK/JsonObject.h>
|
2026-05-11 16:02:29 -03:00
|
|
|
#include <AK/OwnPtr.h>
|
2022-04-07 18:16:47 -03:00
|
|
|
#include <AK/QuickSort.h>
|
2026-06-04 05:40:53 -03:00
|
|
|
#include <LibCore/Process.h>
|
2025-12-04 17:43:33 -03:00
|
|
|
#include <LibCore/System.h>
|
2024-11-14 12:01:23 -03:00
|
|
|
#include <LibGC/Heap.h>
|
2020-06-17 12:31:42 -03:00
|
|
|
#include <LibGfx/Bitmap.h>
|
2026-05-22 10:42:29 -03:00
|
|
|
#include <LibGfx/Color.h>
|
2022-04-09 04:28:38 -03:00
|
|
|
#include <LibGfx/Font/FontDatabase.h>
|
2020-06-17 12:31:42 -03:00
|
|
|
#include <LibGfx/SystemTheme.h>
|
2022-08-28 10:20:06 -03:00
|
|
|
#include <LibJS/Runtime/ConsoleObject.h>
|
2024-09-03 11:18:43 -03:00
|
|
|
#include <LibJS/Runtime/Date.h>
|
2024-08-24 13:15:20 -03:00
|
|
|
#include <LibUnicode/TimeZone.h>
|
2026-05-11 11:35:24 -03:00
|
|
|
#include <LibWasm/Types.h>
|
2023-05-21 10:52:03 -03:00
|
|
|
#include <LibWeb/ARIA/RoleType.h>
|
2021-02-03 06:41:07 -03:00
|
|
|
#include <LibWeb/Bindings/MainThreadVM.h>
|
2025-02-18 05:19:56 -03:00
|
|
|
#include <LibWeb/CSS/ComputedProperties.h>
|
2026-02-13 06:19:02 -03:00
|
|
|
#include <LibWeb/CSS/CustomPropertyData.h>
|
2025-07-23 07:29:03 -03:00
|
|
|
#include <LibWeb/CSS/Parser/ErrorReporter.h>
|
2023-05-08 01:37:18 -03:00
|
|
|
#include <LibWeb/CSS/StyleComputer.h>
|
2026-02-11 03:33:58 -03:00
|
|
|
#include <LibWeb/CSS/StyleSheetList.h>
|
2026-05-20 13:14:36 -03:00
|
|
|
#include <LibWeb/Compositor/CompositorHost.h>
|
2025-08-07 12:20:13 -03:00
|
|
|
#include <LibWeb/CookieStore/CookieStore.h>
|
2026-06-04 12:59:14 -03:00
|
|
|
#include <LibWeb/DOM/AbstractElement.h>
|
2023-11-19 12:42:11 -03:00
|
|
|
#include <LibWeb/DOM/Attr.h>
|
|
|
|
|
#include <LibWeb/DOM/CharacterData.h>
|
2021-01-31 05:06:25 -03:00
|
|
|
#include <LibWeb/DOM/Document.h>
|
2023-11-19 12:42:11 -03:00
|
|
|
#include <LibWeb/DOM/Element.h>
|
|
|
|
|
#include <LibWeb/DOM/ElementFactory.h>
|
2024-11-13 13:07:16 -03:00
|
|
|
#include <LibWeb/DOM/ShadowRoot.h>
|
2023-12-07 13:00:57 -03:00
|
|
|
#include <LibWeb/DOM/Text.h>
|
2021-01-31 05:06:25 -03:00
|
|
|
#include <LibWeb/Dump.h>
|
2025-11-05 11:18:16 -03:00
|
|
|
#include <LibWeb/Fetch/Fetching/Fetching.h>
|
2026-04-10 16:18:00 -03:00
|
|
|
#include <LibWeb/HTML/BroadcastChannel.h>
|
2021-11-18 11:01:28 -03:00
|
|
|
#include <LibWeb/HTML/BrowsingContext.h>
|
2026-06-13 11:06:49 -03:00
|
|
|
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
2024-02-25 15:02:47 -03:00
|
|
|
#include <LibWeb/HTML/HTMLInputElement.h>
|
2026-05-22 11:13:09 -03:00
|
|
|
#include <LibWeb/HTML/Navigable.h>
|
|
|
|
|
#include <LibWeb/HTML/NavigableContainer.h>
|
2025-04-01 09:23:04 -03:00
|
|
|
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
2024-02-25 15:02:47 -03:00
|
|
|
#include <LibWeb/HTML/SelectedFile.h>
|
2022-02-08 15:50:14 -03:00
|
|
|
#include <LibWeb/HTML/Storage.h>
|
2023-08-14 15:19:41 -03:00
|
|
|
#include <LibWeb/HTML/TraversableNavigable.h>
|
2022-03-07 19:08:26 -03:00
|
|
|
#include <LibWeb/HTML/Window.h>
|
2026-05-21 10:33:23 -03:00
|
|
|
#include <LibWeb/HTML/WorkerAgentParent.h>
|
2023-12-05 18:54:53 -03:00
|
|
|
#include <LibWeb/Infra/Strings.h>
|
2026-05-27 12:41:13 -03:00
|
|
|
#include <LibWeb/Layout/FlexLayoutData.h>
|
2026-05-22 10:42:29 -03:00
|
|
|
#include <LibWeb/Layout/GridLayoutData.h>
|
2023-02-25 07:04:29 -03:00
|
|
|
#include <LibWeb/Layout/Viewport.h>
|
2026-05-21 11:36:25 -03:00
|
|
|
#include <LibWeb/Loader/ContentBlocker.h>
|
2022-04-07 18:16:47 -03:00
|
|
|
#include <LibWeb/Loader/ProxyMappings.h>
|
2021-03-29 16:31:09 -03:00
|
|
|
#include <LibWeb/Loader/ResourceLoader.h>
|
2024-07-02 15:28:39 -03:00
|
|
|
#include <LibWeb/Loader/UserAgent.h>
|
2023-12-06 12:36:27 -03:00
|
|
|
#include <LibWeb/Namespace.h>
|
2026-05-27 13:12:14 -03:00
|
|
|
#include <LibWeb/Painting/FlexboxInspectorOverlay.h>
|
2022-03-10 20:03:28 -03:00
|
|
|
#include <LibWeb/Painting/StackingContext.h>
|
2023-08-19 04:29:04 -03:00
|
|
|
#include <LibWeb/Painting/ViewportPaintable.h>
|
2023-04-17 14:21:19 -03:00
|
|
|
#include <LibWeb/PermissionsPolicy/AutoplayAllowlist.h>
|
2022-10-08 05:54:52 -03:00
|
|
|
#include <LibWeb/Platform/EventLoopPlugin.h>
|
2023-11-19 12:42:11 -03:00
|
|
|
#include <LibWebView/Attribute.h>
|
2026-02-01 15:27:19 -03:00
|
|
|
#include <LibWebView/ViewImplementation.h>
|
2026-05-22 11:02:42 -03:00
|
|
|
#include <WebContent/CompositorConnection.h>
|
2022-02-25 07:18:30 -03:00
|
|
|
#include <WebContent/ConnectionFromClient.h>
|
2023-11-29 13:34:38 -03:00
|
|
|
#include <WebContent/PageClient.h>
|
2020-06-17 12:31:42 -03:00
|
|
|
#include <WebContent/PageHost.h>
|
|
|
|
|
#include <WebContent/WebContentClientEndpoint.h>
|
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
|
|
|
#include <WebContent/WebContentCompositorHost.h>
|
2024-02-02 22:00:48 -03:00
|
|
|
|
2020-06-17 12:31:42 -03:00
|
|
|
namespace WebContent {
|
|
|
|
|
|
2025-04-08 17:01:46 -03:00
|
|
|
ConnectionFromClient::ConnectionFromClient(NonnullOwnPtr<IPC::Transport> transport)
|
2024-10-22 18:47:33 -03:00
|
|
|
: IPC::ConnectionFromClient<WebContentClientEndpoint, WebContentServerEndpoint>(*this, move(transport), 1)
|
2020-06-17 13:00:18 -03:00
|
|
|
, m_page_host(PageHost::create(*this))
|
2020-06-17 12:31:42 -03:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-16 05:38:35 -03:00
|
|
|
ConnectionFromClient::~ConnectionFromClient() = default;
|
|
|
|
|
|
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
|
|
|
CompositorConnection* ConnectionFromClient::compositor_process_connection() const
|
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
|
|
|
{
|
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_connection || !m_compositor_connection->is_open())
|
|
|
|
|
return nullptr;
|
|
|
|
|
return m_compositor_connection.ptr();
|
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 ConnectionFromClient::did_destroy_compositor_context(Web::Compositor::CompositorContextId context_id)
|
|
|
|
|
{
|
|
|
|
|
async_did_destroy_compositor_context(context_id);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-25 07:18:30 -03:00
|
|
|
void ConnectionFromClient::die()
|
2020-06-17 12:31:42 -03:00
|
|
|
{
|
2026-06-04 05:40:53 -03:00
|
|
|
Core::Process::terminate_immediately(0);
|
2020-06-17 12:31:42 -03:00
|
|
|
}
|
|
|
|
|
|
2025-01-03 13:19:46 -03:00
|
|
|
Messages::WebContentServer::InitTransportResponse ConnectionFromClient::init_transport([[maybe_unused]] int peer_pid)
|
|
|
|
|
{
|
|
|
|
|
#ifdef AK_OS_WINDOWS
|
2025-06-28 07:32:38 -03:00
|
|
|
m_transport->set_peer_pid(peer_pid);
|
2025-01-03 13:19:46 -03:00
|
|
|
return Core::System::getpid();
|
|
|
|
|
#endif
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 11:58:13 -03:00
|
|
|
void ConnectionFromClient::initialize(u64 initial_page_id)
|
|
|
|
|
{
|
|
|
|
|
m_page_host->initialize(initial_page_id);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 11:05:05 -03:00
|
|
|
Optional<PageClient&> ConnectionFromClient::page(u64 index, SourceLocation location)
|
2020-06-17 12:31:42 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = m_page_host->page(index); page.has_value())
|
|
|
|
|
return *page;
|
|
|
|
|
|
|
|
|
|
dbgln("ConnectionFromClient::{}: Did not find a page with ID {}", location.function_name(), index);
|
|
|
|
|
return {};
|
2020-06-17 12:31:42 -03:00
|
|
|
}
|
|
|
|
|
|
2024-03-29 11:05:05 -03:00
|
|
|
Optional<PageClient const&> ConnectionFromClient::page(u64 index, SourceLocation location) const
|
2020-06-17 12:31:42 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = m_page_host->page(index); page.has_value())
|
|
|
|
|
return *page;
|
|
|
|
|
|
|
|
|
|
dbgln("ConnectionFromClient::{}: Did not find a page with ID {}", location.function_name(), index);
|
|
|
|
|
return {};
|
2020-06-17 12:31:42 -03:00
|
|
|
}
|
|
|
|
|
|
2024-06-30 22:12:22 -03:00
|
|
|
void ConnectionFromClient::close_server()
|
|
|
|
|
{
|
|
|
|
|
shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
Messages::WebContentServer::GetWindowHandleResponse ConnectionFromClient::get_window_handle(u64 page_id)
|
2023-03-14 01:05:57 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
return page->page().top_level_traversable()->window_handle();
|
|
|
|
|
return String {};
|
2023-03-14 01:05:57 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::set_window_handle(u64 page_id, String handle)
|
2023-03-16 11:36:36 -03:00
|
|
|
{
|
2026-05-26 13:03:07 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value()) {
|
2026-06-13 11:06:49 -03:00
|
|
|
page->set_window_handle(move(handle));
|
2026-05-26 13:03:07 -03:00
|
|
|
page->send_current_needs_beforeunload_check();
|
|
|
|
|
}
|
2023-03-16 11:36:36 -03:00
|
|
|
}
|
|
|
|
|
|
2026-03-22 15:59:08 -03:00
|
|
|
void ConnectionFromClient::connect_to_webdriver(u64 page_id, ByteString webdriver_endpoint)
|
2022-11-08 12:03:07 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value()) {
|
|
|
|
|
// FIXME: Propagate this error back to the browser.
|
2026-03-22 15:59:08 -03:00
|
|
|
if (auto result = page->connect_to_webdriver(webdriver_endpoint); result.is_error())
|
2024-03-29 11:05:05 -03:00
|
|
|
dbgln("Unable to connect to the WebDriver process: {}", result.error());
|
2024-02-12 21:39:20 -03:00
|
|
|
}
|
2022-11-08 12:03:07 -03:00
|
|
|
}
|
|
|
|
|
|
2026-06-13 11:06:49 -03:00
|
|
|
void ConnectionFromClient::complete_webdriver_navigation_completion(u64 page_id, u64 request_id, Web::WebDriver::Response response)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->did_complete_webdriver_navigation_completion(request_id, move(response));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::complete_webdriver_history_traversal(u64 page_id, u64 request_id, bool accepted, bool will_replace_web_content_process, bool will_change_top_level_entry)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->did_complete_webdriver_history_traversal(request_id, accepted, will_replace_web_content_process, will_change_top_level_entry);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 16:10:08 -03:00
|
|
|
void ConnectionFromClient::connect_to_web_ui(u64 page_id, IPC::TransportHandle handle)
|
2025-03-24 10:27:36 -03:00
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value()) {
|
|
|
|
|
// FIXME: Propagate this error back to the browser.
|
2026-03-11 16:10:08 -03:00
|
|
|
if (auto result = page->connect_to_web_ui(move(handle)); result.is_error())
|
2025-03-24 10:27:36 -03:00
|
|
|
dbgln("Unable to connect to the WebUI host: {}", result.error());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 16:10:08 -03:00
|
|
|
void ConnectionFromClient::connect_to_image_decoder(IPC::TransportHandle handle)
|
2024-06-26 16:44:42 -03:00
|
|
|
{
|
|
|
|
|
if (on_image_decoder_connection)
|
2026-03-11 16:10:08 -03:00
|
|
|
on_image_decoder_connection(handle);
|
2024-06-26 16:44:42 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-22 11:02:42 -03:00
|
|
|
void ConnectionFromClient::connect_to_compositor_process(IPC::TransportHandle handle)
|
|
|
|
|
{
|
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
|
|
|
auto transport = MUST(handle.create_transport());
|
|
|
|
|
m_compositor_connection = adopt_ref(*new CompositorConnection(move(transport)));
|
|
|
|
|
m_compositor_connection->on_mouse_event = [this](u64 page_id, Web::MouseEvent event) {
|
|
|
|
|
mouse_event(page_id, move(event));
|
|
|
|
|
};
|
2026-06-15 13:53:42 -03:00
|
|
|
m_compositor_connection->on_compositor_lost = [this] {
|
|
|
|
|
m_page_host->compositor_process_lost();
|
|
|
|
|
};
|
2026-06-14 19:13:19 -03:00
|
|
|
|
|
|
|
|
#ifdef AK_OS_WINDOWS
|
|
|
|
|
// Perform Windows peer PID handshake before any other IPC
|
|
|
|
|
if constexpr (requires { m_compositor_connection->transport().set_peer_pid(0); }) {
|
|
|
|
|
auto response = m_compositor_connection->send_sync<Messages::CompositorWebContentServer::InitTransport>(Core::System::getpid());
|
|
|
|
|
m_compositor_connection->transport().set_peer_pid(response->compositor_pid());
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2026-05-22 11:02:42 -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
|
|
|
void ConnectionFromClient::compositor_process_reconnected()
|
|
|
|
|
{
|
|
|
|
|
m_page_host->compositor_process_reconnected();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 16:10:08 -03:00
|
|
|
void ConnectionFromClient::connect_to_request_server(IPC::TransportHandle handle)
|
2025-08-09 14:07:57 -03:00
|
|
|
{
|
|
|
|
|
if (on_request_server_connection)
|
2026-03-11 16:10:08 -03:00
|
|
|
on_request_server_connection(handle);
|
2025-08-09 14:07:57 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::update_system_theme(u64 page_id, Core::AnonymousBuffer theme_buffer)
|
2020-06-17 12:31:42 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
2024-02-12 21:39:20 -03:00
|
|
|
return;
|
|
|
|
|
|
2021-05-02 14:54:34 -03:00
|
|
|
Gfx::set_system_theme(theme_buffer);
|
|
|
|
|
auto impl = Gfx::PaletteImpl::create_with_anonymous_buffer(theme_buffer);
|
2024-03-29 11:05:05 -03:00
|
|
|
page->set_palette_impl(*impl);
|
2020-06-17 12:31:42 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::update_screen_rects(u64 page_id, Vector<Web::DevicePixelRect> rects, u32 main_screen)
|
2021-04-03 19:12:37 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->set_screen_rects(rects, main_screen);
|
2021-04-03 19:12:37 -03:00
|
|
|
}
|
|
|
|
|
|
2026-06-13 11:06:49 -03:00
|
|
|
void ConnectionFromClient::load_url(u64 page_id, URL::URL url, Web::Bindings::NavigationHistoryBehavior history_handling)
|
2020-06-17 12:31:42 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
2024-02-12 21:39:20 -03:00
|
|
|
return;
|
2021-01-30 19:43:18 -03:00
|
|
|
|
2026-06-13 11:06:49 -03:00
|
|
|
page->page().load(url, history_handling);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::load_url_with_document_resource(u64 page_id, URL::URL url,
|
|
|
|
|
Variant<Empty, String, Web::HTML::POSTResource> document_resource,
|
|
|
|
|
Web::Bindings::NavigationHistoryBehavior history_handling)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
page->page().load(url, move(document_resource), history_handling);
|
2020-06-17 12:31:42 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::load_html(u64 page_id, ByteString html)
|
2020-10-08 17:11:01 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().load_html(html);
|
2020-10-08 17:11:01 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-20 11:42:08 -03:00
|
|
|
void ConnectionFromClient::load_html_with_url(u64 page_id, ByteString html, URL::URL url)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().load_html(html, url);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-13 13:51:27 -03:00
|
|
|
void ConnectionFromClient::reload(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().reload();
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-13 17:33:48 -03:00
|
|
|
void ConnectionFromClient::traverse_the_history_by_delta(u64 page_id, i32 delta)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
2026-06-13 11:06:49 -03:00
|
|
|
page->page().traverse_the_history_by_delta_from_ui_process(delta);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::traverse_the_history_to_step(u64 page_id, i32 step)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value()) {
|
|
|
|
|
async_did_traverse_the_history_to_step(page_id, step, false, Web::HTML::HistoryStepResult::Applied);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
page->page().top_level_traversable()->traverse_the_history_to_step(step,
|
|
|
|
|
GC::create_function(Web::HTML::main_thread_event_loop().heap(), [this, page_id, step](bool step_was_available, Web::HTML::HistoryStepResult result) {
|
|
|
|
|
async_did_traverse_the_history_to_step(page_id, step, step_was_available, result);
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::check_if_traverse_history_step_is_canceled(u64 page_id, u64 request_id, i32 step)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value()) {
|
|
|
|
|
async_did_check_if_traverse_history_step_is_canceled(page_id, request_id, step, true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto& heap = Web::HTML::main_thread_event_loop().heap();
|
|
|
|
|
page->page().top_level_traversable()->check_if_traverse_history_step_is_canceled(step,
|
|
|
|
|
GC::create_function(heap, [this, page_id, request_id, step](Web::HTML::HistoryStepResult result) {
|
|
|
|
|
async_did_check_if_traverse_history_step_is_canceled(
|
|
|
|
|
page_id, request_id, step, result != Web::HTML::HistoryStepResult::Applied);
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 06:27:47 -03:00
|
|
|
void ConnectionFromClient::set_top_level_session_history(u64 page_id, Vector<Web::HTML::SessionHistoryEntryDescriptor> entries, size_t current_top_level_entry_index, bool allow_reconstructing_current_entry)
|
2026-06-13 11:06:49 -03:00
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value()) {
|
2026-06-15 06:27:47 -03:00
|
|
|
auto accepted = page->page().top_level_traversable()->replace_top_level_session_history_entries_from_ui_process(move(entries), current_top_level_entry_index, allow_reconstructing_current_entry);
|
|
|
|
|
auto session_history_snapshot = page->page().top_level_traversable()->create_session_history_snapshot();
|
|
|
|
|
async_did_set_top_level_session_history(page_id, accepted, move(session_history_snapshot.top_level_session_history_entries), move(session_history_snapshot.used_session_history_steps), session_history_snapshot.current_used_step_index);
|
2026-06-13 11:06:49 -03:00
|
|
|
} else {
|
|
|
|
|
async_did_set_top_level_session_history(page_id, false, {}, {}, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::reset_session_history_for_testing(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value()) {
|
|
|
|
|
auto& event_loop = Web::HTML::main_thread_event_loop();
|
|
|
|
|
page->page().top_level_traversable()->reset_session_history_for_testing(
|
|
|
|
|
GC::create_function(event_loop.heap(), [this, page_id] {
|
|
|
|
|
async_did_reset_session_history_for_testing(page_id);
|
|
|
|
|
}));
|
|
|
|
|
} else {
|
|
|
|
|
async_did_reset_session_history_for_testing(page_id);
|
|
|
|
|
}
|
2024-04-13 17:33:48 -03:00
|
|
|
}
|
|
|
|
|
|
LibWeb: Use a queue to process fullscreen request completions
Instead of immediately firing fullscreenchange, defer that until
WebContent's client has confirmed that it is in fullscreen for the
content. The fullscreenchange is fired by the viewport change, so in
cases where the fullscreen transition is instantaneous (i.e. the
fullscreen state is entered at the exact moment the viewport expands),
the resize event should precede the fullscreenchange event, as the spec
requires.
This fixes the WPT element-request-fullscreen-timing.html test, which
was previously succeeding by accident because we were immediately
fullscreenchange upon requestFullscreen() being called, instead of
following spec and doing the viewport (window) resize in parallel. The
WPT test was actually initially intended to assert that the
fullscreenchange event follows the resize event, but the WPT runner
didn't actually have a different resolution for normal vs fullscreen
viewports, so the resize event doesn't actually fire in their setup. In
our headless mode, the default viewport is 800x600, and the fullscreen
viewport is 1920x1080, so we do fire a resize event when entering
fullscreen. Therefore, that imported test is reverted to assert that
the resize precedes the fullscreenchange.
2026-03-17 00:01:36 -03:00
|
|
|
void ConnectionFromClient::set_viewport(u64 page_id, Web::DevicePixelSize size, double device_pixel_ratio, Web::ViewportIsFullscreen is_fullscreen)
|
2020-06-17 12:31:42 -03:00
|
|
|
{
|
LibWeb: Use a queue to process fullscreen request completions
Instead of immediately firing fullscreenchange, defer that until
WebContent's client has confirmed that it is in fullscreen for the
content. The fullscreenchange is fired by the viewport change, so in
cases where the fullscreen transition is instantaneous (i.e. the
fullscreen state is entered at the exact moment the viewport expands),
the resize event should precede the fullscreenchange event, as the spec
requires.
This fixes the WPT element-request-fullscreen-timing.html test, which
was previously succeeding by accident because we were immediately
fullscreenchange upon requestFullscreen() being called, instead of
following spec and doing the viewport (window) resize in parallel. The
WPT test was actually initially intended to assert that the
fullscreenchange event follows the resize event, but the WPT runner
didn't actually have a different resolution for normal vs fullscreen
viewports, so the resize event doesn't actually fire in their setup. In
our headless mode, the default viewport is 800x600, and the fullscreen
viewport is 1920x1080, so we do fire a resize event when entering
fullscreen. Therefore, that imported test is reverted to assert that
the resize precedes the fullscreenchange.
2026-03-17 00:01:36 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value()) {
|
2026-02-20 18:09:39 -03:00
|
|
|
page->set_viewport(size, device_pixel_ratio);
|
LibWeb: Use a queue to process fullscreen request completions
Instead of immediately firing fullscreenchange, defer that until
WebContent's client has confirmed that it is in fullscreen for the
content. The fullscreenchange is fired by the viewport change, so in
cases where the fullscreen transition is instantaneous (i.e. the
fullscreen state is entered at the exact moment the viewport expands),
the resize event should precede the fullscreenchange event, as the spec
requires.
This fixes the WPT element-request-fullscreen-timing.html test, which
was previously succeeding by accident because we were immediately
fullscreenchange upon requestFullscreen() being called, instead of
following spec and doing the viewport (window) resize in parallel. The
WPT test was actually initially intended to assert that the
fullscreenchange event follows the resize event, but the WPT runner
didn't actually have a different resolution for normal vs fullscreen
viewports, so the resize event doesn't actually fire in their setup. In
our headless mode, the default viewport is 800x600, and the fullscreen
viewport is 1920x1080, so we do fire a resize event when entering
fullscreen. Therefore, that imported test is reverted to assert that
the resize precedes the fullscreenchange.
2026-03-17 00:01:36 -03:00
|
|
|
page->page().set_viewport_is_fullscreen(is_fullscreen);
|
|
|
|
|
}
|
2020-06-17 12:31:42 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::key_event(u64 page_id, Web::KeyEvent event)
|
2020-06-17 12:31:42 -03:00
|
|
|
{
|
2025-03-08 14:22:39 -03:00
|
|
|
enqueue_input_event({ page_id, move(event), 0 });
|
2020-06-17 12:31:42 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::mouse_event(u64 page_id, Web::MouseEvent event)
|
2020-06-17 12:31:42 -03:00
|
|
|
{
|
2026-05-25 16:13:11 -03:00
|
|
|
auto page = m_page_host->page(page_id);
|
|
|
|
|
if (!page.has_value()) {
|
|
|
|
|
async_did_finish_handling_input_event(page_id, Web::EventResult::Dropped);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-05 19:06:32 -03:00
|
|
|
// OPTIMIZATION: Coalesce consecutive unprocessed mouse move and wheel events.
|
2024-03-06 12:36:28 -03:00
|
|
|
auto event_to_coalesce = [&]() -> Web::MouseEvent const* {
|
2024-03-05 19:06:32 -03:00
|
|
|
if (m_input_event_queue.is_empty())
|
2024-03-06 12:36:28 -03:00
|
|
|
return nullptr;
|
|
|
|
|
if (m_input_event_queue.tail().page_id != page_id)
|
|
|
|
|
return nullptr;
|
2023-03-14 09:47:40 -03:00
|
|
|
|
2024-03-05 19:06:32 -03:00
|
|
|
if (event.type != Web::MouseEvent::Type::MouseMove && event.type != Web::MouseEvent::Type::MouseWheel)
|
2024-03-06 12:36:28 -03:00
|
|
|
return nullptr;
|
2020-06-17 12:31:42 -03:00
|
|
|
|
2024-03-06 12:36:28 -03:00
|
|
|
if (auto const* mouse_event = m_input_event_queue.tail().event.get_pointer<Web::MouseEvent>()) {
|
2026-06-14 16:30:28 -03:00
|
|
|
if (mouse_event->type != event.type)
|
|
|
|
|
return nullptr;
|
|
|
|
|
if (event.type == Web::MouseEvent::Type::MouseWheel
|
|
|
|
|
&& mouse_event->async_scroll_performed_default_action != event.async_scroll_performed_default_action)
|
|
|
|
|
return nullptr;
|
|
|
|
|
return mouse_event;
|
2024-03-06 12:36:28 -03:00
|
|
|
}
|
2020-06-17 12:31:42 -03:00
|
|
|
|
2024-03-06 12:36:28 -03:00
|
|
|
return nullptr;
|
2023-08-04 15:52:43 -03:00
|
|
|
};
|
|
|
|
|
|
2024-03-06 12:36:28 -03:00
|
|
|
if (auto const* last_mouse_event = event_to_coalesce()) {
|
2025-03-08 14:22:39 -03:00
|
|
|
event.wheel_delta_x += last_mouse_event->wheel_delta_x;
|
|
|
|
|
event.wheel_delta_y += last_mouse_event->wheel_delta_y;
|
2024-03-06 12:36:28 -03:00
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
m_input_event_queue.tail().event = move(event);
|
2024-03-05 19:06:32 -03:00
|
|
|
++m_input_event_queue.tail().coalesced_event_count;
|
|
|
|
|
|
2026-05-25 16:13:11 -03:00
|
|
|
page->page().client().request_frame();
|
2023-08-04 15:52:43 -03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
enqueue_input_event({ page_id, move(event), 0 });
|
2023-03-14 09:26:06 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::drag_event(u64 page_id, Web::DragEvent event)
|
2024-08-17 14:36:28 -03:00
|
|
|
{
|
2025-03-08 14:22:39 -03:00
|
|
|
enqueue_input_event({ page_id, move(event), 0 });
|
2024-08-17 14:36:28 -03:00
|
|
|
}
|
|
|
|
|
|
2025-10-08 18:59:50 -03:00
|
|
|
void ConnectionFromClient::pinch_event(u64 page_id, Web::PinchEvent event)
|
|
|
|
|
{
|
2026-06-14 16:30:28 -03:00
|
|
|
auto page = m_page_host->page(page_id);
|
|
|
|
|
if (!page.has_value()) {
|
|
|
|
|
async_did_finish_handling_input_event(page_id, Web::EventResult::Dropped);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// OPTIMIZATION: Coalesce consecutive unprocessed pinch events. Pinch scale
|
|
|
|
|
// deltas are multiplicative, so preserve the combined scale change.
|
2026-06-15 19:46:16 -03:00
|
|
|
// Only coalesce events with the same focal point, as applying
|
|
|
|
|
// one combined zoom around a different point is not equivalent
|
|
|
|
|
// to applying each zoom in sequence.
|
2026-06-14 16:30:28 -03:00
|
|
|
if (!m_input_event_queue.is_empty() && m_input_event_queue.tail().page_id == page_id) {
|
|
|
|
|
if (auto const* pinch_event = m_input_event_queue.tail().event.get_pointer<Web::PinchEvent>()) {
|
2026-06-15 19:46:16 -03:00
|
|
|
if (pinch_event->position != event.position || pinch_event->modifiers != event.modifiers)
|
|
|
|
|
return enqueue_input_event({ page_id, move(event), 0 });
|
|
|
|
|
|
2026-06-14 16:30:28 -03:00
|
|
|
event.scale_delta = (1.0 + pinch_event->scale_delta) * (1.0 + event.scale_delta) - 1.0;
|
|
|
|
|
m_input_event_queue.tail().event = move(event);
|
|
|
|
|
++m_input_event_queue.tail().coalesced_event_count;
|
|
|
|
|
|
|
|
|
|
page->page().client().request_frame();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 18:59:50 -03:00
|
|
|
enqueue_input_event({ page_id, move(event), 0 });
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-14 19:25:12 -03:00
|
|
|
void ConnectionFromClient::enqueue_input_event(Web::QueuedInputEvent event)
|
2023-03-14 09:26:06 -03:00
|
|
|
{
|
2026-04-29 10:57:40 -03:00
|
|
|
auto page_id = event.page_id;
|
2026-05-25 16:13:11 -03:00
|
|
|
auto page = m_page_host->page(page_id);
|
|
|
|
|
if (!page.has_value()) {
|
|
|
|
|
async_did_finish_handling_input_event(page_id, Web::EventResult::Dropped);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-04-29 10:57:40 -03:00
|
|
|
|
2026-05-25 16:13:11 -03:00
|
|
|
m_input_event_queue.enqueue(move(event));
|
|
|
|
|
page->page().client().request_frame();
|
2021-09-28 10:39:35 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::debug_request(u64 page_id, ByteString request, ByteString argument)
|
2021-01-31 05:06:25 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
2024-02-12 21:39:20 -03:00
|
|
|
return;
|
|
|
|
|
|
2023-08-14 15:19:41 -03:00
|
|
|
if (request == "dump-session-history") {
|
2024-03-29 11:05:05 -03:00
|
|
|
auto const& traversable = page->page().top_level_traversable();
|
2023-08-14 15:19:41 -03:00
|
|
|
Web::dump_tree(*traversable);
|
2025-07-17 18:45:04 -03:00
|
|
|
return;
|
2023-08-14 15:19:41 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 19:38:19 -03:00
|
|
|
if (request == "dump-display-list") {
|
|
|
|
|
if (auto* doc = page->page().top_level_browsing_context().active_document()) {
|
|
|
|
|
auto display_list_dump = doc->dump_display_list();
|
|
|
|
|
dbgln("{}", display_list_dump);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-02 14:54:34 -03:00
|
|
|
if (request == "dump-dom-tree") {
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto* doc = page->page().top_level_browsing_context().active_document())
|
2021-01-31 05:06:25 -03:00
|
|
|
Web::dump_tree(*doc);
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2021-01-31 05:06:25 -03:00
|
|
|
}
|
|
|
|
|
|
2021-05-02 14:54:34 -03:00
|
|
|
if (request == "dump-layout-tree") {
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto* doc = page->page().top_level_browsing_context().active_document()) {
|
2023-02-25 07:04:29 -03:00
|
|
|
if (auto* viewport = doc->layout_node())
|
|
|
|
|
Web::dump_tree(*viewport);
|
2021-01-31 05:06:25 -03:00
|
|
|
}
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2021-01-31 05:06:25 -03:00
|
|
|
}
|
|
|
|
|
|
2023-03-18 16:22:58 -03:00
|
|
|
if (request == "dump-paint-tree") {
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto* doc = page->page().top_level_browsing_context().active_document()) {
|
2026-05-07 08:38:05 -03:00
|
|
|
if (auto paintable = doc->paintable())
|
2023-03-18 16:22:58 -03:00
|
|
|
Web::dump_tree(*paintable);
|
|
|
|
|
}
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2023-03-18 16:22:58 -03:00
|
|
|
}
|
|
|
|
|
|
2022-02-05 15:01:12 -03:00
|
|
|
if (request == "dump-stacking-context-tree") {
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto* doc = page->page().top_level_browsing_context().active_document()) {
|
2023-02-25 07:04:29 -03:00
|
|
|
if (auto* viewport = doc->layout_node()) {
|
2025-07-06 09:45:04 -03:00
|
|
|
auto& viewport_paintable = static_cast<Web::Painting::ViewportPaintable&>(*viewport->paintable_box());
|
|
|
|
|
viewport_paintable.build_stacking_context_tree_if_needed();
|
2026-05-07 08:38:05 -03:00
|
|
|
if (auto stacking_context = viewport_paintable.stacking_context()) {
|
2025-07-06 09:45:04 -03:00
|
|
|
StringBuilder builder;
|
|
|
|
|
stacking_context->dump(builder);
|
|
|
|
|
dbgln("{}", builder.string_view());
|
|
|
|
|
}
|
2022-02-05 15:01:12 -03:00
|
|
|
}
|
|
|
|
|
}
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2022-02-05 15:01:12 -03:00
|
|
|
}
|
|
|
|
|
|
2021-05-02 14:54:34 -03:00
|
|
|
if (request == "dump-style-sheets") {
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto* doc = page->page().top_level_browsing_context().active_document()) {
|
2024-11-13 13:07:16 -03:00
|
|
|
dbgln("=== In document: ===");
|
2021-01-31 05:06:25 -03:00
|
|
|
for (auto& sheet : doc->style_sheets().sheets()) {
|
2024-10-15 07:35:01 -03:00
|
|
|
Web::dump_sheet(sheet);
|
2021-01-31 05:06:25 -03:00
|
|
|
}
|
2024-11-13 13:07:16 -03:00
|
|
|
|
|
|
|
|
doc->for_each_shadow_root([&](auto& shadow_root) {
|
|
|
|
|
dbgln("=== In shadow root {}: ===", shadow_root.host()->debug_description());
|
|
|
|
|
shadow_root.for_each_css_style_sheet([&](auto& sheet) {
|
|
|
|
|
Web::dump_sheet(sheet);
|
|
|
|
|
});
|
|
|
|
|
});
|
2021-01-31 05:06:25 -03:00
|
|
|
}
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2021-01-31 05:06:25 -03:00
|
|
|
}
|
|
|
|
|
|
2023-05-27 09:03:08 -03:00
|
|
|
if (request == "dump-all-resolved-styles") {
|
2026-02-13 06:19:02 -03:00
|
|
|
auto dump_style = [](String const& title, Web::CSS::ComputedProperties const& style, RefPtr<Web::CSS::CustomPropertyData const> custom_property_data) {
|
2025-07-07 10:52:05 -03:00
|
|
|
dbgln("+ {}", title);
|
2025-08-24 00:38:14 -03:00
|
|
|
for (size_t i = to_underlying(Web::CSS::first_longhand_property_id); i < to_underlying(Web::CSS::last_longhand_property_id); ++i) {
|
2025-08-25 01:55:07 -03:00
|
|
|
dbgln("| {} = {}", Web::CSS::string_from_property_id(static_cast<Web::CSS::PropertyID>(i)), style.property(static_cast<Web::CSS::PropertyID>(i)).to_string(Web::CSS::SerializationMode::Normal));
|
2025-07-07 10:52:05 -03:00
|
|
|
}
|
2026-02-13 06:19:02 -03:00
|
|
|
if (custom_property_data) {
|
2026-06-08 15:31:27 -03:00
|
|
|
custom_property_data->for_each_property([](Utf16FlyString const& name, Web::CSS::StyleProperty const& property) {
|
2026-02-13 06:19:02 -03:00
|
|
|
dbgln("| {} = {}", name, property.value->to_string(Web::CSS::SerializationMode::Normal));
|
|
|
|
|
});
|
2025-07-07 10:52:05 -03:00
|
|
|
}
|
|
|
|
|
dbgln("---");
|
|
|
|
|
};
|
|
|
|
|
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto* doc = page->page().top_level_browsing_context().active_document()) {
|
2025-07-07 10:52:05 -03:00
|
|
|
Queue<Web::DOM::Node*> nodes_to_visit;
|
|
|
|
|
nodes_to_visit.enqueue(doc->document_element());
|
|
|
|
|
while (!nodes_to_visit.is_empty()) {
|
|
|
|
|
auto node = nodes_to_visit.dequeue();
|
|
|
|
|
for (auto& child : node->children_as_vector())
|
|
|
|
|
nodes_to_visit.enqueue(child.ptr());
|
|
|
|
|
if (auto* element = as_if<Web::DOM::Element>(node)) {
|
2025-09-05 10:56:12 -03:00
|
|
|
auto styles = doc->style_computer().compute_style({ *element });
|
2026-02-13 06:19:02 -03:00
|
|
|
dump_style(MUST(String::formatted("Element {}", node->debug_description())), styles, element->custom_property_data({}));
|
2025-07-07 10:52:05 -03:00
|
|
|
|
2026-05-15 01:08:04 -03:00
|
|
|
element->for_each_synthetic_pseudo_element([&](Web::CSS::PseudoElement pseudo_element_type, Web::DOM::PseudoElement const& pseudo_element) {
|
|
|
|
|
if (!pseudo_element.computed_properties())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
dump_style(MUST(String::formatted("PseudoElement {}::{}", node->debug_description(), Web::CSS::pseudo_element_name(pseudo_element_type))), *pseudo_element.computed_properties(), pseudo_element.custom_property_data());
|
|
|
|
|
});
|
2023-05-27 09:03:08 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2023-05-27 09:03:08 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-23 07:29:03 -03:00
|
|
|
if (request == "dump-all-css-errors") {
|
|
|
|
|
Web::CSS::Parser::ErrorReporter::the().dump();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-11 11:35:24 -03:00
|
|
|
if (request == "dump-wasm-stats") {
|
|
|
|
|
Wasm::dump_module_stats();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-02 14:54:34 -03:00
|
|
|
if (request == "collect-garbage") {
|
2024-11-10 11:14:12 -03:00
|
|
|
// NOTE: We use deferred_invoke here to ensure that GC runs with as little on the stack as possible.
|
|
|
|
|
Core::deferred_invoke([] {
|
2024-11-14 12:01:23 -03:00
|
|
|
Web::Bindings::main_thread_vm().heap().collect_garbage(GC::Heap::CollectionType::CollectGarbage, true);
|
2024-11-10 11:14:12 -03:00
|
|
|
});
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2021-01-31 05:06:25 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 20:27:24 -03:00
|
|
|
if (request == "crash-current-page") {
|
|
|
|
|
Core::deferred_invoke([] {
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-02 14:54:34 -03:00
|
|
|
if (request == "set-line-box-borders") {
|
|
|
|
|
bool state = argument == "on";
|
2025-06-26 17:33:58 -03:00
|
|
|
auto traversable = page->page().top_level_traversable();
|
|
|
|
|
traversable->set_should_show_line_box_borders(state);
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2021-01-31 05:06:25 -03:00
|
|
|
}
|
2021-03-29 16:31:09 -03:00
|
|
|
|
2026-05-30 05:04:28 -03:00
|
|
|
if (request == "set-caret-hit-test-debug-overlay") {
|
|
|
|
|
bool state = argument == "on";
|
|
|
|
|
auto traversable = page->page().top_level_traversable();
|
|
|
|
|
traversable->set_should_show_caret_hit_test_debug_overlay(state);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-02 14:54:34 -03:00
|
|
|
if (request == "clear-cache") {
|
2025-12-01 17:11:27 -03:00
|
|
|
Web::Fetch::Fetching::clear_http_memory_cache();
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2021-03-29 16:31:09 -03:00
|
|
|
}
|
2021-04-14 09:13:51 -03:00
|
|
|
|
2021-05-02 14:54:34 -03:00
|
|
|
if (request == "spoof-user-agent") {
|
2023-12-16 11:19:34 -03:00
|
|
|
Web::ResourceLoader::the().set_user_agent(MUST(String::from_byte_string(argument)));
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2021-04-14 09:13:51 -03:00
|
|
|
}
|
2021-09-11 21:10:43 -03:00
|
|
|
|
2022-03-30 19:29:15 -03:00
|
|
|
if (request == "scripting") {
|
2024-03-29 11:05:05 -03:00
|
|
|
page->page().set_is_scripting_enabled(argument == "on");
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2022-03-30 19:29:15 -03:00
|
|
|
}
|
|
|
|
|
|
2022-11-14 21:01:13 -03:00
|
|
|
if (request == "block-pop-ups") {
|
2024-03-29 11:05:05 -03:00
|
|
|
page->page().set_should_block_pop_ups(argument == "on");
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2022-11-14 21:01:13 -03:00
|
|
|
}
|
|
|
|
|
|
2022-02-08 15:50:14 -03:00
|
|
|
if (request == "dump-local-storage") {
|
2025-07-17 18:45:04 -03:00
|
|
|
if (auto* document = page->page().top_level_browsing_context().active_document()) {
|
|
|
|
|
auto storage_or_error = document->window()->local_storage();
|
|
|
|
|
if (storage_or_error.is_error())
|
|
|
|
|
dbgln("Failed to retrieve local storage: {}", storage_or_error.release_error());
|
|
|
|
|
else
|
|
|
|
|
storage_or_error.release_value()->dump();
|
|
|
|
|
}
|
2023-09-12 11:12:17 -03:00
|
|
|
return;
|
2022-02-08 15:50:14 -03:00
|
|
|
}
|
2023-09-12 12:36:10 -03:00
|
|
|
|
2026-06-11 08:48:26 -03:00
|
|
|
if (request == "dump-session-storage") {
|
|
|
|
|
if (auto* document = page->page().top_level_browsing_context().active_document()) {
|
|
|
|
|
auto storage_or_error = document->window()->session_storage();
|
|
|
|
|
if (storage_or_error.is_error())
|
|
|
|
|
dbgln("Failed to retrieve session storage: {}", storage_or_error.release_error());
|
|
|
|
|
else
|
|
|
|
|
storage_or_error.release_value()->dump();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-02 15:28:39 -03:00
|
|
|
if (request == "navigator-compatibility-mode") {
|
|
|
|
|
Web::NavigatorCompatibilityMode compatibility_mode;
|
|
|
|
|
if (argument == "chrome") {
|
|
|
|
|
compatibility_mode = Web::NavigatorCompatibilityMode::Chrome;
|
|
|
|
|
} else if (argument == "gecko") {
|
|
|
|
|
compatibility_mode = Web::NavigatorCompatibilityMode::Gecko;
|
|
|
|
|
} else if (argument == "webkit") {
|
|
|
|
|
compatibility_mode = Web::NavigatorCompatibilityMode::WebKit;
|
|
|
|
|
} else {
|
|
|
|
|
dbgln("Unknown navigator compatibility mode '{}', defaulting to Chrome", argument);
|
|
|
|
|
compatibility_mode = Web::NavigatorCompatibilityMode::Chrome;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Web::ResourceLoader::the().set_navigator_compatibility_mode(compatibility_mode);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-03-06 20:54:40 -03:00
|
|
|
|
2026-05-21 11:36:25 -03:00
|
|
|
if (request == "content-blocking") {
|
2026-05-21 13:35:29 -03:00
|
|
|
page->page().set_content_blocking_enabled(argument == "on");
|
2025-03-06 20:54:40 -03:00
|
|
|
return;
|
|
|
|
|
}
|
2021-01-31 05:06:25 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::get_source(u64 page_id)
|
2021-02-23 09:14:12 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value()) {
|
|
|
|
|
if (auto* doc = page->page().top_level_browsing_context().active_document())
|
2024-10-18 18:20:15 -03:00
|
|
|
async_did_get_source(page_id, doc->url(), doc->base_url(), doc->source());
|
2021-02-23 09:14:12 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::inspect_dom_tree(u64 page_id)
|
2021-06-07 12:35:10 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value()) {
|
|
|
|
|
if (auto* doc = page->page().top_level_browsing_context().active_document())
|
2025-02-21 13:51:05 -03:00
|
|
|
async_did_inspect_dom_tree(page_id, doc->dump_dom_tree_as_json());
|
2021-06-07 12:35:10 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 12:15:21 -03:00
|
|
|
void ConnectionFromClient::inspect_storage(u64 page_id, Web::StorageAPI::StorageEndpointType storage_endpoint, u64 request_id)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value()) {
|
|
|
|
|
async_did_inspect_storage(page_id, request_id, "[]"_string);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto* document = page->page().top_level_browsing_context().active_document();
|
|
|
|
|
if (!document || !document->window()) {
|
|
|
|
|
async_did_inspect_storage(page_id, request_id, "[]"_string);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Web::WebIDL::ExceptionOr<GC::Ref<Web::HTML::Storage>> storage_or_error = [&]() -> Web::WebIDL::ExceptionOr<GC::Ref<Web::HTML::Storage>> {
|
|
|
|
|
if (storage_endpoint == Web::StorageAPI::StorageEndpointType::LocalStorage)
|
|
|
|
|
return document->window()->local_storage();
|
|
|
|
|
VERIFY(storage_endpoint == Web::StorageAPI::StorageEndpointType::SessionStorage);
|
|
|
|
|
return document->window()->session_storage();
|
|
|
|
|
}();
|
|
|
|
|
|
|
|
|
|
if (storage_or_error.is_error()) {
|
|
|
|
|
async_did_inspect_storage(page_id, request_id, "[]"_string);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto storage = storage_or_error.release_value();
|
|
|
|
|
JsonArray storage_items;
|
|
|
|
|
for (auto i = 0uz; i < storage->length(); ++i) {
|
|
|
|
|
auto name = storage->key(i);
|
|
|
|
|
if (!name.has_value())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
auto value = storage->get_item(*name);
|
|
|
|
|
if (!value.has_value())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
JsonObject item;
|
|
|
|
|
item.set("name"sv, name.release_value());
|
|
|
|
|
item.set("value"sv, value.release_value());
|
|
|
|
|
storage_items.must_append(move(item));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async_did_inspect_storage(page_id, request_id, storage_items.serialized());
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 12:27:25 -03:00
|
|
|
static Optional<GC::Ref<Web::HTML::Storage>> active_session_storage_for_page(PageClient& page)
|
|
|
|
|
{
|
|
|
|
|
auto* document = page.page().top_level_browsing_context().active_document();
|
|
|
|
|
if (!document || !document->window())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
auto storage_or_error = document->window()->session_storage();
|
|
|
|
|
if (storage_or_error.is_exception())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
return storage_or_error.release_value();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Messages::WebContentServer::SetSessionStorageItemResponse ConnectionFromClient::set_session_storage_item(u64 page_id, String key, String value)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return Optional<WebView::StorageSetResult> {};
|
|
|
|
|
|
|
|
|
|
auto storage = active_session_storage_for_page(*page);
|
|
|
|
|
if (!storage.has_value())
|
|
|
|
|
return Optional<WebView::StorageSetResult> {};
|
|
|
|
|
|
|
|
|
|
auto old_value = (*storage)->get_item(key);
|
|
|
|
|
auto result = (*storage)->set_item(key, value);
|
|
|
|
|
if (result.is_exception())
|
|
|
|
|
return WebView::StorageSetResult { WebView::StorageOperationError::QuotaExceededError };
|
|
|
|
|
|
|
|
|
|
return WebView::StorageSetResult { move(old_value) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Messages::WebContentServer::RemoveSessionStorageItemResponse ConnectionFromClient::remove_session_storage_item(u64 page_id, String key)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return Optional<String> {};
|
|
|
|
|
|
|
|
|
|
auto storage = active_session_storage_for_page(*page);
|
|
|
|
|
if (!storage.has_value())
|
|
|
|
|
return Optional<String> {};
|
|
|
|
|
|
|
|
|
|
auto old_value = (*storage)->get_item(key);
|
|
|
|
|
if (old_value.has_value())
|
|
|
|
|
(*storage)->remove_item(key);
|
|
|
|
|
return old_value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Messages::WebContentServer::ClearSessionStorageResponse ConnectionFromClient::clear_session_storage(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
auto storage = active_session_storage_for_page(*page);
|
|
|
|
|
if (!storage.has_value() || (*storage)->length() == 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
(*storage)->clear();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 11:17:17 -03:00
|
|
|
void ConnectionFromClient::inspect_dom_node(u64 page_id, WebView::DOMNodeProperties::Type property_type, Web::UniqueNodeID node_id, Optional<Web::CSS::PseudoElement> pseudo_element, JsonValue options_value)
|
2021-08-27 08:31:55 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
2024-02-12 21:39:20 -03:00
|
|
|
return;
|
|
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
clear_inspected_dom_node(page_id);
|
2021-08-27 08:31:55 -03:00
|
|
|
|
2024-10-20 05:37:44 -03:00
|
|
|
auto* node = Web::DOM::Node::from_unique_id(node_id);
|
2026-06-04 12:59:14 -03:00
|
|
|
if (!node || !node->is_element()) {
|
2025-03-19 16:50:56 -03:00
|
|
|
async_did_inspect_dom_node(page_id, { property_type, {} });
|
2023-12-30 11:30:27 -03:00
|
|
|
return;
|
2021-11-24 14:05:39 -03:00
|
|
|
}
|
2021-08-27 08:31:55 -03:00
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
auto& element = as<Web::DOM::Element>(*node);
|
2025-02-21 11:34:02 -03:00
|
|
|
node->document().set_inspected_node(node);
|
2021-08-27 08:31:55 -03:00
|
|
|
|
2026-06-04 12:59:14 -03:00
|
|
|
Web::DOM::AbstractElement abstract_element { element, pseudo_element };
|
|
|
|
|
node->document().update_style_for_element(abstract_element);
|
|
|
|
|
|
2025-07-18 14:34:58 -03:00
|
|
|
auto properties = element.computed_properties(pseudo_element);
|
2025-03-19 16:50:56 -03:00
|
|
|
|
|
|
|
|
if (!properties) {
|
|
|
|
|
async_did_inspect_dom_node(page_id, { property_type, {} });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 11:26:12 -03:00
|
|
|
node->document().update_layout(Web::DOM::UpdateLayoutReason::Debugging);
|
|
|
|
|
|
2026-06-04 12:59:14 -03:00
|
|
|
// Nodes without layout (aka non-visible nodes) do not have box metrics, but DevTools can still ask for their style
|
|
|
|
|
// rules and computed properties.
|
|
|
|
|
if (property_type == WebView::DOMNodeProperties::Type::Layout && !node->layout_node()) {
|
|
|
|
|
async_did_inspect_dom_node(page_id, { property_type, {} });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
auto serialize_computed_style = [&]() {
|
|
|
|
|
JsonObject serialized;
|
|
|
|
|
|
|
|
|
|
properties->for_each_property([&](auto property_id, auto& value) {
|
|
|
|
|
serialized.set(
|
|
|
|
|
Web::CSS::string_from_property_id(property_id),
|
2025-05-16 15:20:24 -03:00
|
|
|
value.to_string(Web::CSS::SerializationMode::Normal));
|
2025-03-19 16:50:56 -03:00
|
|
|
});
|
|
|
|
|
|
2025-09-26 10:31:57 -03:00
|
|
|
// FIXME: Custom properties are not yet included in ComputedProperties, so add them manually.
|
2026-02-13 06:19:02 -03:00
|
|
|
if (auto custom_property_data = element.custom_property_data(pseudo_element)) {
|
2026-06-08 15:31:27 -03:00
|
|
|
custom_property_data->for_each_property([&](Utf16FlyString const& name, Web::CSS::StyleProperty const& value) {
|
|
|
|
|
serialized.set(name.to_utf16_string().to_utf8_but_should_be_ported_to_utf16(), value.value->to_string(Web::CSS::SerializationMode::Normal));
|
2026-02-13 06:19:02 -03:00
|
|
|
});
|
2025-09-26 10:31:57 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
return serialized;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto serialize_layout = [&](Web::Layout::Node const* layout_node) {
|
2026-05-07 08:38:05 -03:00
|
|
|
auto first_paintable = layout_node ? layout_node->first_paintable() : nullptr;
|
|
|
|
|
if (!layout_node || !layout_node->is_box() || !first_paintable || !first_paintable->is_paintable_box()) {
|
2025-03-19 16:50:56 -03:00
|
|
|
return JsonObject {};
|
2023-12-30 11:30:27 -03:00
|
|
|
}
|
2021-08-27 08:31:55 -03:00
|
|
|
|
2026-05-07 08:38:05 -03:00
|
|
|
auto const& paintable_box = as<Web::Painting::PaintableBox>(*first_paintable);
|
2025-03-19 16:50:56 -03:00
|
|
|
auto const& box_model = paintable_box.box_model();
|
2021-08-27 08:31:55 -03:00
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
JsonObject serialized;
|
2021-12-08 08:51:26 -03:00
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
serialized.set("width"sv, paintable_box.content_width().to_double());
|
|
|
|
|
serialized.set("height"sv, paintable_box.content_height().to_double());
|
2021-12-08 08:51:26 -03:00
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
serialized.set("padding-top"sv, box_model.padding.top.to_double());
|
|
|
|
|
serialized.set("padding-right"sv, box_model.padding.right.to_double());
|
|
|
|
|
serialized.set("padding-bottom"sv, box_model.padding.bottom.to_double());
|
|
|
|
|
serialized.set("padding-left"sv, box_model.padding.left.to_double());
|
2025-03-08 12:41:16 -03:00
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
serialized.set("margin-top"sv, box_model.margin.top.to_double());
|
|
|
|
|
serialized.set("margin-right"sv, box_model.margin.right.to_double());
|
|
|
|
|
serialized.set("margin-bottom"sv, box_model.margin.bottom.to_double());
|
|
|
|
|
serialized.set("margin-left"sv, box_model.margin.left.to_double());
|
2021-12-08 08:51:26 -03:00
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
serialized.set("border-top-width"sv, box_model.border.top.to_double());
|
|
|
|
|
serialized.set("border-right-width"sv, box_model.border.right.to_double());
|
|
|
|
|
serialized.set("border-bottom-width"sv, box_model.border.bottom.to_double());
|
|
|
|
|
serialized.set("border-left-width"sv, box_model.border.left.to_double());
|
2022-03-04 13:29:05 -03:00
|
|
|
|
2025-05-16 15:20:24 -03:00
|
|
|
serialized.set("box-sizing"sv, properties->property(Web::CSS::PropertyID::BoxSizing).to_string(Web::CSS::SerializationMode::Normal));
|
|
|
|
|
serialized.set("display"sv, properties->property(Web::CSS::PropertyID::Display).to_string(Web::CSS::SerializationMode::Normal));
|
|
|
|
|
serialized.set("float"sv, properties->property(Web::CSS::PropertyID::Float).to_string(Web::CSS::SerializationMode::Normal));
|
|
|
|
|
serialized.set("line-height"sv, properties->property(Web::CSS::PropertyID::LineHeight).to_string(Web::CSS::SerializationMode::Normal));
|
|
|
|
|
serialized.set("position"sv, properties->property(Web::CSS::PropertyID::Position).to_string(Web::CSS::SerializationMode::Normal));
|
|
|
|
|
serialized.set("z-index"sv, properties->property(Web::CSS::PropertyID::ZIndex).to_string(Web::CSS::SerializationMode::Normal));
|
2022-03-04 13:29:05 -03:00
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
return serialized;
|
|
|
|
|
};
|
2025-02-10 12:18:22 -03:00
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
auto serialize_used_fonts = [&]() {
|
|
|
|
|
JsonArray serialized;
|
2025-02-10 12:18:22 -03:00
|
|
|
|
2025-11-08 08:39:08 -03:00
|
|
|
properties->computed_font_list(node->document().font_computer())->for_each_font_entry([&](Gfx::FontCascadeList::Entry const& entry) {
|
2025-03-19 16:50:56 -03:00
|
|
|
auto const& font = *entry.font;
|
2022-03-04 13:29:05 -03:00
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
JsonObject font_object;
|
|
|
|
|
font_object.set("name"sv, font.family().to_string());
|
|
|
|
|
font_object.set("size"sv, font.point_size());
|
|
|
|
|
font_object.set("weight"sv, font.weight());
|
|
|
|
|
serialized.must_append(move(font_object));
|
|
|
|
|
});
|
2023-12-30 11:30:27 -03:00
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
return serialized;
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-04 12:59:14 -03:00
|
|
|
auto serialize_applied_style_rules = [&]() {
|
|
|
|
|
JsonObject const empty_options;
|
|
|
|
|
auto const& options = options_value.is_object() ? options_value.as_object() : empty_options;
|
|
|
|
|
auto include_inherited = options.get_bool("inherited"sv).value_or(false);
|
|
|
|
|
auto include_user_agent_styles = options.get_string("filter"sv).map([](auto const& filter) { return filter == "ua"sv; }).value_or(false);
|
|
|
|
|
return node->document().style_computer().collect_devtools_applied_style_rules(abstract_element, include_inherited, include_user_agent_styles);
|
|
|
|
|
};
|
|
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
JsonValue serialized;
|
|
|
|
|
|
|
|
|
|
switch (property_type) {
|
2026-06-04 12:59:14 -03:00
|
|
|
case WebView::DOMNodeProperties::Type::AppliedStyleRules:
|
|
|
|
|
serialized = serialize_applied_style_rules();
|
|
|
|
|
break;
|
2025-03-19 16:50:56 -03:00
|
|
|
case WebView::DOMNodeProperties::Type::ComputedStyle:
|
|
|
|
|
serialized = serialize_computed_style();
|
|
|
|
|
break;
|
|
|
|
|
case WebView::DOMNodeProperties::Type::Layout:
|
|
|
|
|
serialized = serialize_layout(element.layout_node());
|
|
|
|
|
break;
|
|
|
|
|
case WebView::DOMNodeProperties::Type::UsedFonts:
|
|
|
|
|
serialized = serialize_used_fonts();
|
|
|
|
|
break;
|
2021-08-27 08:31:55 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
async_did_inspect_dom_node(page_id, { property_type, move(serialized) });
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 10:42:29 -03:00
|
|
|
static StringView grid_track_type_to_string(Web::Layout::GridTrackType type)
|
|
|
|
|
{
|
|
|
|
|
switch (type) {
|
|
|
|
|
case Web::Layout::GridTrackType::Explicit:
|
|
|
|
|
return "explicit"sv;
|
|
|
|
|
case Web::Layout::GridTrackType::Implicit:
|
|
|
|
|
return "implicit"sv;
|
|
|
|
|
}
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static StringView grid_track_state_to_string(Web::Layout::GridTrackState state)
|
|
|
|
|
{
|
|
|
|
|
switch (state) {
|
|
|
|
|
case Web::Layout::GridTrackState::Static:
|
|
|
|
|
return "static"sv;
|
|
|
|
|
case Web::Layout::GridTrackState::Repeat:
|
|
|
|
|
return "repeat"sv;
|
|
|
|
|
case Web::Layout::GridTrackState::Removed:
|
|
|
|
|
return "removed"sv;
|
|
|
|
|
}
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static JsonArray serialize_grid_line_names(Vector<String> const& names)
|
|
|
|
|
{
|
|
|
|
|
JsonArray serialized_names;
|
|
|
|
|
for (auto const& name : names)
|
|
|
|
|
serialized_names.must_append(name);
|
|
|
|
|
return serialized_names;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static JsonObject serialize_grid_line(Web::Layout::GridLayoutLine const& line)
|
|
|
|
|
{
|
|
|
|
|
JsonObject serialized_line;
|
|
|
|
|
serialized_line.set("breadth"sv, line.breadth.to_double());
|
|
|
|
|
serialized_line.set("names"sv, serialize_grid_line_names(line.names));
|
|
|
|
|
serialized_line.set("negativeNumber"sv, line.negative_number);
|
|
|
|
|
serialized_line.set("number"sv, line.number);
|
|
|
|
|
serialized_line.set("start"sv, line.start.to_double());
|
|
|
|
|
serialized_line.set("type"sv, grid_track_type_to_string(line.type));
|
|
|
|
|
return serialized_line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static JsonObject serialize_grid_track(Web::Layout::GridLayoutTrack const& track)
|
|
|
|
|
{
|
|
|
|
|
JsonObject serialized_track;
|
|
|
|
|
serialized_track.set("breadth"sv, track.breadth.to_double());
|
|
|
|
|
serialized_track.set("start"sv, track.start.to_double());
|
|
|
|
|
serialized_track.set("state"sv, grid_track_state_to_string(track.state));
|
|
|
|
|
serialized_track.set("type"sv, grid_track_type_to_string(track.type));
|
|
|
|
|
return serialized_track;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static JsonObject serialize_grid_area(Web::Layout::GridLayoutArea const& area)
|
|
|
|
|
{
|
|
|
|
|
JsonObject serialized_area;
|
|
|
|
|
serialized_area.set("columnEnd"sv, area.column_end);
|
|
|
|
|
serialized_area.set("columnStart"sv, area.column_start);
|
|
|
|
|
serialized_area.set("name"sv, area.name);
|
|
|
|
|
serialized_area.set("rowEnd"sv, area.row_end);
|
|
|
|
|
serialized_area.set("rowStart"sv, area.row_start);
|
|
|
|
|
serialized_area.set("type"sv, grid_track_type_to_string(area.type));
|
|
|
|
|
return serialized_area;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static JsonObject serialize_grid_dimension(Web::Layout::GridLayoutDimension const& dimension)
|
|
|
|
|
{
|
|
|
|
|
JsonArray lines;
|
|
|
|
|
for (auto const& line : dimension.lines)
|
|
|
|
|
lines.must_append(serialize_grid_line(line));
|
|
|
|
|
|
|
|
|
|
JsonArray tracks;
|
|
|
|
|
for (auto const& track : dimension.tracks)
|
|
|
|
|
tracks.must_append(serialize_grid_track(track));
|
|
|
|
|
|
|
|
|
|
JsonObject serialized_dimension;
|
|
|
|
|
serialized_dimension.set("lines"sv, move(lines));
|
|
|
|
|
serialized_dimension.set("tracks"sv, move(tracks));
|
|
|
|
|
return serialized_dimension;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static JsonObject serialize_grid_fragment(Web::Layout::GridLayoutFragment const& fragment)
|
|
|
|
|
{
|
|
|
|
|
JsonArray areas;
|
|
|
|
|
for (auto const& area : fragment.areas)
|
|
|
|
|
areas.must_append(serialize_grid_area(area));
|
|
|
|
|
|
|
|
|
|
JsonObject serialized_fragment;
|
|
|
|
|
serialized_fragment.set("areas"sv, move(areas));
|
|
|
|
|
serialized_fragment.set("cols"sv, serialize_grid_dimension(fragment.columns));
|
|
|
|
|
serialized_fragment.set("rows"sv, serialize_grid_dimension(fragment.rows));
|
|
|
|
|
return serialized_fragment;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static JsonArray serialize_grid_fragments(Vector<Web::Layout::GridLayoutFragment> const& fragments)
|
|
|
|
|
{
|
|
|
|
|
JsonArray serialized_fragments;
|
|
|
|
|
for (auto const& fragment : fragments)
|
|
|
|
|
serialized_fragments.must_append(serialize_grid_fragment(fragment));
|
|
|
|
|
return serialized_fragments;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 12:41:13 -03:00
|
|
|
static StringView flex_layout_growth_state_to_string(Web::Layout::FlexLayoutGrowthState state)
|
|
|
|
|
{
|
|
|
|
|
switch (state) {
|
|
|
|
|
case Web::Layout::FlexLayoutGrowthState::Growing:
|
|
|
|
|
return "growing"sv;
|
|
|
|
|
case Web::Layout::FlexLayoutGrowthState::Shrinking:
|
|
|
|
|
return "shrinking"sv;
|
|
|
|
|
}
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static StringView flex_layout_clamp_state_to_string(Web::Layout::FlexLayoutClampState state)
|
|
|
|
|
{
|
|
|
|
|
switch (state) {
|
|
|
|
|
case Web::Layout::FlexLayoutClampState::Unclamped:
|
|
|
|
|
return "unclamped"sv;
|
|
|
|
|
case Web::Layout::FlexLayoutClampState::ClampedToMin:
|
|
|
|
|
return "clamped_to_min"sv;
|
|
|
|
|
case Web::Layout::FlexLayoutClampState::ClampedToMax:
|
|
|
|
|
return "clamped_to_max"sv;
|
|
|
|
|
}
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static JsonObject serialize_flex_layout_item(Web::Layout::FlexLayoutItem const& item, Web::Layout::FlexLayoutGrowthState line_growth_state)
|
|
|
|
|
{
|
|
|
|
|
JsonObject sizing;
|
|
|
|
|
sizing.set("clampState"sv, flex_layout_clamp_state_to_string(item.clamp_state));
|
|
|
|
|
sizing.set("crossAxisDirection"sv, item.cross_axis_direction);
|
|
|
|
|
sizing.set("crossMaxSize"sv, item.cross_max_size.to_double());
|
|
|
|
|
sizing.set("crossMinSize"sv, item.cross_min_size.to_double());
|
|
|
|
|
sizing.set("lineGrowthState"sv, flex_layout_growth_state_to_string(line_growth_state));
|
|
|
|
|
sizing.set("mainAxisDirection"sv, item.main_axis_direction);
|
|
|
|
|
sizing.set("mainBaseSize"sv, item.main_base_size.to_double());
|
|
|
|
|
sizing.set("mainDeltaSize"sv, item.main_delta_size.to_double());
|
|
|
|
|
sizing.set("mainMaxSize"sv, item.main_max_size.to_double());
|
|
|
|
|
sizing.set("mainMinSize"sv, item.main_min_size.to_double());
|
|
|
|
|
|
|
|
|
|
auto main_size_property_name = item.main_axis_direction.starts_with_bytes("horizontal"sv) ? "width"sv : "height"sv;
|
|
|
|
|
|
|
|
|
|
JsonObject properties;
|
|
|
|
|
properties.set("flex-basis"sv, item.flex_basis);
|
|
|
|
|
properties.set("flex-grow"sv, item.flex_grow);
|
|
|
|
|
properties.set("flex-shrink"sv, item.flex_shrink);
|
|
|
|
|
properties.set(main_size_property_name, item.main_size_property);
|
|
|
|
|
properties.set(MUST(String::formatted("min-{}", main_size_property_name)), item.main_min_size_property);
|
|
|
|
|
properties.set(MUST(String::formatted("max-{}", main_size_property_name)), item.main_max_size_property);
|
|
|
|
|
|
|
|
|
|
JsonObject computed_style;
|
|
|
|
|
computed_style.set("flexGrow"sv, item.flex_grow);
|
|
|
|
|
computed_style.set("flexShrink"sv, item.flex_shrink);
|
|
|
|
|
|
|
|
|
|
JsonObject serialized_item;
|
|
|
|
|
serialized_item.set("nodeId"sv, item.node_id->value());
|
|
|
|
|
serialized_item.set("flexItemSizing"sv, move(sizing));
|
|
|
|
|
serialized_item.set("properties"sv, move(properties));
|
|
|
|
|
serialized_item.set("computedStyle"sv, move(computed_style));
|
|
|
|
|
return serialized_item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static JsonArray serialize_flex_layout_items(Vector<Web::Layout::FlexLayoutLine> const& lines)
|
|
|
|
|
{
|
|
|
|
|
JsonArray serialized_items;
|
|
|
|
|
for (auto const& line : lines) {
|
|
|
|
|
for (auto const& item : line.items) {
|
|
|
|
|
if (!item.node_id.has_value())
|
|
|
|
|
continue;
|
|
|
|
|
serialized_items.must_append(serialize_flex_layout_item(item, line.growth_state));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return serialized_items;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Optional<JsonObject> flex_layout_for_node(Web::DOM::Node const& node)
|
|
|
|
|
{
|
|
|
|
|
auto paintable_box = node.paintable_box();
|
|
|
|
|
if (!paintable_box)
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
auto const* flex_layout_data = paintable_box->flex_layout_data();
|
|
|
|
|
if (!flex_layout_data)
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
JsonObject properties;
|
|
|
|
|
properties.set("align-content"sv, Web::CSS::to_string(flex_layout_data->align_content));
|
|
|
|
|
properties.set("align-items"sv, Web::CSS::to_string(flex_layout_data->align_items));
|
|
|
|
|
properties.set("flex-direction"sv, Web::CSS::to_string(flex_layout_data->flex_direction));
|
|
|
|
|
properties.set("flex-wrap"sv, Web::CSS::to_string(flex_layout_data->flex_wrap));
|
|
|
|
|
properties.set("justify-content"sv, Web::CSS::to_string(flex_layout_data->justify_content));
|
|
|
|
|
|
|
|
|
|
JsonObject layout;
|
|
|
|
|
layout.set("containerNodeId"sv, node.unique_id().value());
|
|
|
|
|
layout.set("properties"sv, move(properties));
|
|
|
|
|
layout.set("items"sv, serialize_flex_layout_items(flex_layout_data->lines));
|
|
|
|
|
return layout;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 10:42:29 -03:00
|
|
|
static Optional<JsonObject> grid_layout_for_node(Web::DOM::Node const& node)
|
|
|
|
|
{
|
|
|
|
|
auto paintable_box = node.paintable_box();
|
|
|
|
|
if (!paintable_box)
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
auto const* grid_layout_data = paintable_box->grid_layout_data();
|
|
|
|
|
if (!grid_layout_data)
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
JsonObject layout;
|
|
|
|
|
layout.set("containerNodeId"sv, node.unique_id().value());
|
|
|
|
|
layout.set("direction"sv, Web::CSS::to_string(grid_layout_data->direction));
|
|
|
|
|
layout.set("gridFragments"sv, serialize_grid_fragments(grid_layout_data->fragments));
|
|
|
|
|
layout.set("isSubgrid"sv, grid_layout_data->is_subgrid);
|
|
|
|
|
layout.set("writingMode"sv, Web::CSS::to_string(grid_layout_data->writing_mode));
|
|
|
|
|
|
|
|
|
|
return layout;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 11:13:09 -03:00
|
|
|
static void append_grid_layouts_for_node_and_frame_descendants(Web::DOM::Node& root_node, JsonArray& grid_layouts)
|
|
|
|
|
{
|
|
|
|
|
root_node.for_each_in_inclusive_subtree([&](Web::DOM::Node& node) {
|
|
|
|
|
if (auto grid_layout = grid_layout_for_node(node); grid_layout.has_value())
|
|
|
|
|
grid_layouts.must_append(grid_layout.release_value());
|
|
|
|
|
|
|
|
|
|
auto* navigable_container = as_if<Web::HTML::NavigableContainer>(node);
|
|
|
|
|
if (!navigable_container)
|
|
|
|
|
return Web::TraversalDecision::Continue;
|
|
|
|
|
|
|
|
|
|
auto content_navigable = navigable_container->content_navigable();
|
|
|
|
|
if (!content_navigable)
|
|
|
|
|
return Web::TraversalDecision::Continue;
|
|
|
|
|
|
|
|
|
|
auto content_document = content_navigable->active_document();
|
|
|
|
|
if (!content_document)
|
|
|
|
|
return Web::TraversalDecision::Continue;
|
|
|
|
|
|
|
|
|
|
if (!content_document->origin().is_same_origin_domain(navigable_container->document().origin()))
|
|
|
|
|
return Web::TraversalDecision::Continue;
|
|
|
|
|
|
2026-06-01 08:27:46 -03:00
|
|
|
content_document->update_layout(Web::DOM::UpdateLayoutReason::Debugging);
|
2026-05-22 11:13:09 -03:00
|
|
|
append_grid_layouts_for_node_and_frame_descendants(*content_document, grid_layouts);
|
|
|
|
|
return Web::TraversalDecision::Continue;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 10:42:29 -03:00
|
|
|
void ConnectionFromClient::inspect_grid_layouts(u64 page_id, Web::UniqueNodeID root_node_id)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto* root_node = Web::DOM::Node::from_unique_id(root_node_id);
|
|
|
|
|
if (!root_node) {
|
|
|
|
|
async_did_inspect_grid_layouts(page_id, "[]"_string);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 08:27:46 -03:00
|
|
|
root_node->document().update_layout(Web::DOM::UpdateLayoutReason::Debugging);
|
2026-05-22 10:42:29 -03:00
|
|
|
|
|
|
|
|
JsonArray grid_layouts;
|
2026-05-22 11:13:09 -03:00
|
|
|
append_grid_layouts_for_node_and_frame_descendants(*root_node, grid_layouts);
|
2026-05-22 10:42:29 -03:00
|
|
|
|
|
|
|
|
async_did_inspect_grid_layouts(page_id, grid_layouts.serialized());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::inspect_current_grid(u64 page_id, Web::UniqueNodeID node_id)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto* node = Web::DOM::Node::from_unique_id(node_id);
|
|
|
|
|
if (!node) {
|
|
|
|
|
async_did_inspect_current_grid(page_id, "null"_string);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 08:27:46 -03:00
|
|
|
node->document().update_layout(Web::DOM::UpdateLayoutReason::Debugging);
|
2026-05-22 10:42:29 -03:00
|
|
|
|
|
|
|
|
for (auto const* current = node; current; current = current->parent_or_shadow_host_node()) {
|
|
|
|
|
if (auto grid_layout = grid_layout_for_node(*current); grid_layout.has_value()) {
|
|
|
|
|
async_did_inspect_current_grid(page_id, grid_layout->serialized());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async_did_inspect_current_grid(page_id, "null"_string);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 12:41:13 -03:00
|
|
|
void ConnectionFromClient::inspect_current_flexbox(u64 page_id, Web::UniqueNodeID node_id, bool only_look_at_parents)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto* node = Web::DOM::Node::from_unique_id(node_id);
|
|
|
|
|
if (!node) {
|
|
|
|
|
async_did_inspect_current_flexbox(page_id, "null"_string);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 08:27:46 -03:00
|
|
|
node->document().update_layout(Web::DOM::UpdateLayoutReason::Debugging);
|
2026-05-27 12:41:13 -03:00
|
|
|
|
|
|
|
|
for (auto const* current = only_look_at_parents ? node->parent_or_shadow_host_node() : node; current; current = current->parent_or_shadow_host_node()) {
|
|
|
|
|
if (auto flex_layout = flex_layout_for_node(*current); flex_layout.has_value()) {
|
|
|
|
|
async_did_inspect_current_flexbox(page_id, flex_layout->serialized());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async_did_inspect_current_flexbox(page_id, "null"_string);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-19 16:50:56 -03:00
|
|
|
void ConnectionFromClient::clear_inspected_dom_node(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (auto& navigable : Web::HTML::all_navigables()) {
|
|
|
|
|
if (navigable->active_document() != nullptr) {
|
|
|
|
|
navigable->active_document()->set_inspected_node(nullptr);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-08-27 08:31:55 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 13:56:46 -03:00
|
|
|
void ConnectionFromClient::highlight_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional<Web::CSS::PseudoElement> pseudo_element)
|
2025-02-21 11:34:02 -03:00
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (auto& navigable : Web::HTML::all_navigables()) {
|
|
|
|
|
if (navigable->active_document() != nullptr) {
|
|
|
|
|
navigable->active_document()->set_highlighted_node(nullptr, {});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto* node = Web::DOM::Node::from_unique_id(node_id);
|
2026-05-28 13:09:11 -03:00
|
|
|
if (!node || !node->is_connected())
|
2025-02-21 11:34:02 -03:00
|
|
|
return;
|
|
|
|
|
|
2026-05-28 13:09:11 -03:00
|
|
|
auto& document = node->document();
|
|
|
|
|
auto navigable = document.navigable();
|
|
|
|
|
if (!navigable || navigable->active_document() != &document)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
document.update_layout(Web::DOM::UpdateLayoutReason::Debugging);
|
|
|
|
|
if (!node->layout_node())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
document.set_highlighted_node(node, pseudo_element);
|
2025-02-21 11:34:02 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-27 13:12:14 -03:00
|
|
|
static Web::Painting::FlexboxInspectorOverlayOptions flexbox_inspector_overlay_options_from_json(JsonValue const& options)
|
|
|
|
|
{
|
|
|
|
|
Web::Painting::FlexboxInspectorOverlayOptions result;
|
|
|
|
|
|
|
|
|
|
if (options.is_object()) {
|
|
|
|
|
auto const& object = options.as_object();
|
|
|
|
|
if (auto color = object.get_string("color"sv); color.has_value()) {
|
|
|
|
|
if (auto parsed_color = Gfx::Color::from_string(*color); parsed_color.has_value())
|
|
|
|
|
result.color = *parsed_color;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 10:42:29 -03:00
|
|
|
static Web::Painting::GridInspectorOverlayOptions grid_inspector_overlay_options_from_json(JsonValue const& options)
|
|
|
|
|
{
|
|
|
|
|
Web::Painting::GridInspectorOverlayOptions result;
|
|
|
|
|
|
|
|
|
|
if (options.is_object()) {
|
|
|
|
|
auto const& object = options.as_object();
|
|
|
|
|
if (auto color = object.get_string("color"sv); color.has_value()) {
|
|
|
|
|
if (auto parsed_color = Gfx::Color::from_string(*color); parsed_color.has_value())
|
|
|
|
|
result.color = *parsed_color;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.show_area_names = object.get_bool("showGridAreasOverlay"sv).value_or(result.show_area_names);
|
|
|
|
|
result.show_line_numbers = object.get_bool("showGridLineNumbers"sv).value_or(result.show_line_numbers);
|
|
|
|
|
result.show_infinite_lines = object.get_bool("showInfiniteLines"sv).value_or(result.show_infinite_lines);
|
|
|
|
|
result.show_track_sizes = object.get_bool("showGridTrackSizes"sv)
|
|
|
|
|
.value_or(object.get_bool("showGridTrackSizeLabels"sv)
|
|
|
|
|
.value_or(object.get_bool("showTrackSizes"sv)
|
|
|
|
|
.value_or(result.show_track_sizes)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 13:12:14 -03:00
|
|
|
void ConnectionFromClient::highlight_flexbox(u64 page_id, Web::UniqueNodeID node_id, JsonValue options)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto* node = Web::DOM::Node::from_unique_id(node_id);
|
2026-06-01 08:27:46 -03:00
|
|
|
if (!node)
|
2026-05-27 13:12:14 -03:00
|
|
|
return;
|
|
|
|
|
|
2026-06-01 08:27:46 -03:00
|
|
|
auto& document = node->document();
|
|
|
|
|
document.update_layout(Web::DOM::UpdateLayoutReason::Debugging);
|
|
|
|
|
if (!node->layout_node())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
document.set_flexbox_highlighted_node(node, flexbox_inspector_overlay_options_from_json(options));
|
2026-05-27 13:12:14 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::clear_flexbox_highlight(u64 page_id, Web::UniqueNodeID node_id)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (node_id != 0) {
|
|
|
|
|
auto* node = Web::DOM::Node::from_unique_id(node_id);
|
|
|
|
|
if (node)
|
|
|
|
|
node->document().clear_flexbox_highlighted_node(node);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto& navigable : Web::HTML::all_navigables()) {
|
|
|
|
|
if (navigable->active_document())
|
|
|
|
|
navigable->active_document()->clear_flexbox_highlighted_node(nullptr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 10:42:29 -03:00
|
|
|
void ConnectionFromClient::highlight_grid(u64 page_id, Web::UniqueNodeID node_id, JsonValue options)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto* node = Web::DOM::Node::from_unique_id(node_id);
|
2026-06-01 08:27:46 -03:00
|
|
|
if (!node)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto& document = node->document();
|
|
|
|
|
document.update_layout(Web::DOM::UpdateLayoutReason::Debugging);
|
|
|
|
|
if (!node->layout_node())
|
2026-05-22 10:42:29 -03:00
|
|
|
return;
|
|
|
|
|
|
2026-06-01 08:27:46 -03:00
|
|
|
document.set_grid_highlighted_node(node, grid_inspector_overlay_options_from_json(options));
|
2026-05-22 10:42:29 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::clear_grid_highlight(u64 page_id, Web::UniqueNodeID node_id)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (node_id != 0) {
|
|
|
|
|
auto* node = Web::DOM::Node::from_unique_id(node_id);
|
|
|
|
|
if (node)
|
|
|
|
|
node->document().clear_grid_highlighted_node(node);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto& navigable : Web::HTML::all_navigables()) {
|
|
|
|
|
if (navigable->active_document())
|
|
|
|
|
navigable->active_document()->clear_grid_highlighted_node(nullptr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::inspect_accessibility_tree(u64 page_id)
|
2023-12-30 11:06:22 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value()) {
|
|
|
|
|
if (auto* doc = page->page().top_level_browsing_context().active_document())
|
2025-02-21 13:51:05 -03:00
|
|
|
async_did_inspect_accessibility_tree(page_id, doc->dump_accessibility_tree_as_json());
|
2023-12-30 11:06:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::get_hovered_node_id(u64 page_id)
|
2021-08-27 13:33:10 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
2024-02-12 21:39:20 -03:00
|
|
|
return;
|
|
|
|
|
|
2024-10-20 05:37:44 -03:00
|
|
|
Web::UniqueNodeID node_id = 0;
|
2023-12-30 11:42:24 -03:00
|
|
|
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto* document = page->page().top_level_browsing_context().active_document()) {
|
2023-12-30 11:42:24 -03:00
|
|
|
if (auto* hovered_node = document->hovered_node())
|
|
|
|
|
node_id = hovered_node->unique_id();
|
2021-08-27 13:33:10 -03:00
|
|
|
}
|
2023-12-30 11:42:24 -03:00
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_get_hovered_node_id(page_id, node_id);
|
2021-08-27 13:33:10 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 08:10:27 -03:00
|
|
|
void ConnectionFromClient::get_node_id_at_position(u64 page_id, u64 request_id, Web::DevicePixelPoint position)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value()) {
|
|
|
|
|
async_did_get_node_id_at_position(page_id, request_id, 0);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async_did_get_node_id_at_position(page_id, request_id, page->page().node_id_at_position(position));
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-23 07:18:35 -03:00
|
|
|
void ConnectionFromClient::list_style_sheets(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
2025-03-14 17:22:16 -03:00
|
|
|
async_did_list_style_sheets(page_id, page->list_style_sheets());
|
2024-08-23 07:18:35 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::request_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier)
|
2024-08-23 07:18:35 -03:00
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (auto* document = page->page().top_level_browsing_context().active_document()) {
|
2024-10-18 18:20:15 -03:00
|
|
|
if (auto stylesheet = document->get_style_sheet_source(identifier); stylesheet.has_value())
|
|
|
|
|
async_did_get_style_sheet_source(page_id, identifier, document->base_url(), stylesheet.value());
|
2024-08-23 07:18:35 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-06 19:32:43 -03:00
|
|
|
void ConnectionFromClient::set_listen_for_dom_mutations(u64 page_id, bool listen_for_dom_mutations)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
page->page().set_listen_for_dom_mutations(listen_for_dom_mutations);
|
2026-05-27 08:37:28 -03:00
|
|
|
if (!listen_for_dom_mutations)
|
|
|
|
|
page->clear_pending_dom_mutations();
|
2025-03-06 19:32:43 -03:00
|
|
|
}
|
|
|
|
|
|
2026-01-15 10:28:42 -03:00
|
|
|
void ConnectionFromClient::did_connect_devtools_client(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->did_connect_devtools_client();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::did_disconnect_devtools_client(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
page->did_disconnect_devtools_client();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-10 19:15:31 -03:00
|
|
|
void ConnectionFromClient::get_dom_node_inner_html(u64 page_id, Web::UniqueNodeID node_id)
|
|
|
|
|
{
|
|
|
|
|
auto* dom_node = Web::DOM::Node::from_unique_id(node_id);
|
|
|
|
|
if (!dom_node)
|
|
|
|
|
return;
|
|
|
|
|
|
2025-08-10 18:49:19 -03:00
|
|
|
Utf16String html;
|
2025-03-10 19:15:31 -03:00
|
|
|
|
|
|
|
|
if (dom_node->is_element()) {
|
|
|
|
|
auto const& element = static_cast<Web::DOM::Element const&>(*dom_node);
|
2025-08-10 18:49:19 -03:00
|
|
|
html = element.inner_html().release_value_but_fixme_should_propagate_errors().get<Utf16String>();
|
2025-03-10 19:15:31 -03:00
|
|
|
} else if (dom_node->is_text() || dom_node->is_comment()) {
|
|
|
|
|
auto const& character_data = static_cast<Web::DOM::CharacterData const&>(*dom_node);
|
2025-08-10 18:49:19 -03:00
|
|
|
html = character_data.data();
|
2025-03-10 19:15:31 -03:00
|
|
|
} else {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-10 18:49:19 -03:00
|
|
|
async_did_get_dom_node_html(page_id, html.to_utf8_but_should_be_ported_to_utf16());
|
2025-03-10 19:15:31 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-10 18:36:41 -03:00
|
|
|
void ConnectionFromClient::get_dom_node_outer_html(u64 page_id, Web::UniqueNodeID node_id)
|
|
|
|
|
{
|
|
|
|
|
auto* dom_node = Web::DOM::Node::from_unique_id(node_id);
|
|
|
|
|
if (!dom_node)
|
|
|
|
|
return;
|
|
|
|
|
|
2025-08-10 18:49:19 -03:00
|
|
|
Utf16String html;
|
2025-03-10 18:36:41 -03:00
|
|
|
|
|
|
|
|
if (dom_node->is_element()) {
|
|
|
|
|
auto const& element = static_cast<Web::DOM::Element const&>(*dom_node);
|
2025-08-10 18:49:19 -03:00
|
|
|
html = element.outer_html().release_value_but_fixme_should_propagate_errors().get<Utf16String>();
|
2025-03-10 18:36:41 -03:00
|
|
|
} else if (dom_node->is_text() || dom_node->is_comment()) {
|
|
|
|
|
auto const& character_data = static_cast<Web::DOM::CharacterData const&>(*dom_node);
|
2025-08-10 18:49:19 -03:00
|
|
|
html = character_data.data();
|
2025-03-10 18:36:41 -03:00
|
|
|
} else {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-10 18:49:19 -03:00
|
|
|
async_did_get_dom_node_html(page_id, html.to_utf8_but_should_be_ported_to_utf16());
|
2025-03-10 18:36:41 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::set_dom_node_outer_html(u64 page_id, Web::UniqueNodeID node_id, String html)
|
|
|
|
|
{
|
|
|
|
|
auto* dom_node = Web::DOM::Node::from_unique_id(node_id);
|
|
|
|
|
if (!dom_node) {
|
|
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dom_node->is_element()) {
|
|
|
|
|
auto& element = static_cast<Web::DOM::Element&>(*dom_node);
|
2025-08-10 18:49:19 -03:00
|
|
|
element.set_outer_html(Utf16String::from_utf8(html)).release_value_but_fixme_should_propagate_errors();
|
2025-03-10 18:36:41 -03:00
|
|
|
} else if (dom_node->is_text() || dom_node->is_comment()) {
|
|
|
|
|
auto& character_data = static_cast<Web::DOM::CharacterData&>(*dom_node);
|
2025-07-24 13:05:52 -03:00
|
|
|
character_data.set_data(Utf16String::from_utf8(html));
|
2025-03-10 18:36:41 -03:00
|
|
|
} else {
|
|
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async_did_finish_editing_dom_node(page_id, node_id);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::set_dom_node_text(u64 page_id, Web::UniqueNodeID node_id, String text)
|
2023-11-19 12:42:11 -03:00
|
|
|
{
|
|
|
|
|
auto* dom_node = Web::DOM::Node::from_unique_id(node_id);
|
2023-12-30 12:08:33 -03:00
|
|
|
if (!dom_node || (!dom_node->is_text() && !dom_node->is_comment())) {
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
2023-11-19 12:42:11 -03:00
|
|
|
return;
|
2023-12-30 12:08:33 -03:00
|
|
|
}
|
2023-11-19 12:42:11 -03:00
|
|
|
|
|
|
|
|
auto& character_data = static_cast<Web::DOM::CharacterData&>(*dom_node);
|
2025-07-24 13:05:52 -03:00
|
|
|
character_data.set_data(Utf16String::from_utf8(text));
|
2023-12-30 12:08:33 -03:00
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, character_data.unique_id());
|
2023-11-19 12:42:11 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::set_dom_node_tag(u64 page_id, Web::UniqueNodeID node_id, String name)
|
2023-11-19 12:42:11 -03:00
|
|
|
{
|
|
|
|
|
auto* dom_node = Web::DOM::Node::from_unique_id(node_id);
|
2023-12-30 12:08:33 -03:00
|
|
|
if (!dom_node || !dom_node->is_element() || !dom_node->parent()) {
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
2023-12-30 12:08:33 -03:00
|
|
|
return;
|
|
|
|
|
}
|
2023-11-19 12:42:11 -03:00
|
|
|
|
|
|
|
|
auto& element = static_cast<Web::DOM::Element&>(*dom_node);
|
|
|
|
|
auto new_element = Web::DOM::create_element(element.document(), name, element.namespace_uri(), element.prefix(), element.is_value()).release_value_but_fixme_should_propagate_errors();
|
|
|
|
|
|
|
|
|
|
element.for_each_attribute([&](auto const& attribute) {
|
2024-01-02 20:47:38 -03:00
|
|
|
new_element->set_attribute_value(attribute.local_name(), attribute.value(), attribute.prefix(), attribute.namespace_uri());
|
2023-11-19 12:42:11 -03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
while (auto* child_node = element.first_child()) {
|
|
|
|
|
MUST(element.remove_child(*child_node));
|
|
|
|
|
MUST(new_element->append_child(*child_node));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
element.parent()->replace_child(*new_element, element).release_value_but_fixme_should_propagate_errors();
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, new_element->unique_id());
|
2023-11-19 12:42:11 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::add_dom_node_attributes(u64 page_id, Web::UniqueNodeID node_id, Vector<WebView::Attribute> attributes)
|
2023-12-05 18:49:47 -03:00
|
|
|
{
|
|
|
|
|
auto* dom_node = Web::DOM::Node::from_unique_id(node_id);
|
2023-12-30 12:08:33 -03:00
|
|
|
if (!dom_node || !dom_node->is_element()) {
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
2023-12-05 18:49:47 -03:00
|
|
|
return;
|
2023-12-30 12:08:33 -03:00
|
|
|
}
|
2023-12-05 18:49:47 -03:00
|
|
|
|
|
|
|
|
auto& element = static_cast<Web::DOM::Element&>(*dom_node);
|
|
|
|
|
|
2024-07-29 18:27:43 -03:00
|
|
|
for (auto const& attribute : attributes) {
|
|
|
|
|
// NOTE: We ignore invalid attributes for now, but we may want to send feedback to the user that this failed.
|
2025-10-31 09:30:47 -03:00
|
|
|
element.set_attribute_value(attribute.name, attribute.value);
|
2024-07-29 18:27:43 -03:00
|
|
|
}
|
2023-12-30 12:08:33 -03:00
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, element.unique_id());
|
2023-12-05 18:49:47 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::replace_dom_node_attribute(u64 page_id, Web::UniqueNodeID node_id, String name, Vector<WebView::Attribute> replacement_attributes)
|
2023-11-19 12:42:11 -03:00
|
|
|
{
|
|
|
|
|
auto* dom_node = Web::DOM::Node::from_unique_id(node_id);
|
2023-12-30 12:08:33 -03:00
|
|
|
if (!dom_node || !dom_node->is_element()) {
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
2023-11-19 12:42:11 -03:00
|
|
|
return;
|
2023-12-30 12:08:33 -03:00
|
|
|
}
|
2023-11-19 12:42:11 -03:00
|
|
|
|
|
|
|
|
auto& element = static_cast<Web::DOM::Element&>(*dom_node);
|
2023-12-05 18:54:53 -03:00
|
|
|
bool should_remove_attribute = true;
|
|
|
|
|
|
|
|
|
|
for (auto const& attribute : replacement_attributes) {
|
2025-05-18 00:04:56 -03:00
|
|
|
if (should_remove_attribute && name.equals_ignoring_ascii_case(attribute.name))
|
2023-12-05 18:54:53 -03:00
|
|
|
should_remove_attribute = false;
|
2023-11-19 12:42:11 -03:00
|
|
|
|
2024-07-29 18:27:43 -03:00
|
|
|
// NOTE: We ignore invalid attributes for now, but we may want to send feedback to the user that this failed.
|
2025-10-31 09:30:47 -03:00
|
|
|
element.set_attribute_value(attribute.name, attribute.value);
|
2023-12-05 18:54:53 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (should_remove_attribute)
|
|
|
|
|
element.remove_attribute(name);
|
2023-12-30 12:08:33 -03:00
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, element.unique_id());
|
2023-11-19 12:42:11 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::create_child_element(u64 page_id, Web::UniqueNodeID node_id)
|
2023-12-07 13:00:57 -03:00
|
|
|
{
|
|
|
|
|
auto* dom_node = Web::DOM::Node::from_unique_id(node_id);
|
2023-12-30 12:08:33 -03:00
|
|
|
if (!dom_node) {
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
2023-12-30 12:08:33 -03:00
|
|
|
return;
|
|
|
|
|
}
|
2023-12-07 13:00:57 -03:00
|
|
|
|
|
|
|
|
auto element = Web::DOM::create_element(dom_node->document(), Web::HTML::TagNames::div, Web::Namespace::HTML).release_value_but_fixme_should_propagate_errors();
|
|
|
|
|
dom_node->append_child(element).release_value_but_fixme_should_propagate_errors();
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, element->unique_id());
|
2023-12-07 13:00:57 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::create_child_text_node(u64 page_id, Web::UniqueNodeID node_id)
|
2023-12-07 13:00:57 -03:00
|
|
|
{
|
|
|
|
|
auto* dom_node = Web::DOM::Node::from_unique_id(node_id);
|
2023-12-30 12:08:33 -03:00
|
|
|
if (!dom_node) {
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
2023-12-30 12:08:33 -03:00
|
|
|
return;
|
|
|
|
|
}
|
2023-12-07 13:00:57 -03:00
|
|
|
|
2025-07-24 13:05:52 -03:00
|
|
|
auto text_node = dom_node->realm().create<Web::DOM::Text>(dom_node->document(), "text"_utf16);
|
2023-12-07 13:00:57 -03:00
|
|
|
dom_node->append_child(text_node).release_value_but_fixme_should_propagate_errors();
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, text_node->unique_id());
|
2023-12-07 13:00:57 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-10 19:01:29 -03:00
|
|
|
void ConnectionFromClient::insert_dom_node_before(u64 page_id, Web::UniqueNodeID node_id, Web::UniqueNodeID parent_node_id, Optional<Web::UniqueNodeID> sibling_node_id)
|
|
|
|
|
{
|
|
|
|
|
auto* dom_node = Web::DOM::Node::from_unique_id(node_id);
|
|
|
|
|
auto* parent_dom_node = Web::DOM::Node::from_unique_id(parent_node_id);
|
|
|
|
|
|
|
|
|
|
if (!dom_node || !parent_dom_node) {
|
|
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GC::Ptr<Web::DOM::Node> sibling_dom_node;
|
|
|
|
|
if (sibling_node_id.has_value()) {
|
|
|
|
|
sibling_dom_node = Web::DOM::Node::from_unique_id(*sibling_node_id);
|
|
|
|
|
|
|
|
|
|
if (!sibling_dom_node) {
|
|
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parent_dom_node->insert_before(*dom_node, sibling_dom_node);
|
|
|
|
|
async_did_finish_editing_dom_node(page_id, dom_node->unique_id());
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::clone_dom_node(u64 page_id, Web::UniqueNodeID node_id)
|
2023-12-07 13:12:43 -03:00
|
|
|
{
|
|
|
|
|
auto* dom_node = Web::DOM::Node::from_unique_id(node_id);
|
2023-12-30 12:08:33 -03:00
|
|
|
if (!dom_node || !dom_node->parent_node()) {
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
2023-12-30 12:08:33 -03:00
|
|
|
return;
|
|
|
|
|
}
|
2023-12-07 13:12:43 -03:00
|
|
|
|
2024-06-25 06:06:41 -03:00
|
|
|
auto dom_node_clone = MUST(dom_node->clone_node(nullptr, true));
|
2023-12-07 13:12:43 -03:00
|
|
|
dom_node->parent_node()->insert_before(dom_node_clone, dom_node->next_sibling());
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, dom_node_clone->unique_id());
|
2023-12-07 13:12:43 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::remove_dom_node(u64 page_id, Web::UniqueNodeID node_id)
|
2023-12-05 16:59:54 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
2024-02-12 21:39:20 -03:00
|
|
|
return;
|
|
|
|
|
|
2024-03-29 11:05:05 -03:00
|
|
|
auto* active_document = page->page().top_level_browsing_context().active_document();
|
2023-12-30 12:08:33 -03:00
|
|
|
if (!active_document) {
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
2023-12-05 16:59:54 -03:00
|
|
|
return;
|
2023-12-30 12:08:33 -03:00
|
|
|
}
|
2023-12-05 16:59:54 -03:00
|
|
|
|
|
|
|
|
auto* dom_node = Web::DOM::Node::from_unique_id(node_id);
|
2023-12-30 12:08:33 -03:00
|
|
|
if (!dom_node) {
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, {});
|
2023-12-05 16:59:54 -03:00
|
|
|
return;
|
2023-12-30 12:08:33 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto* previous_dom_node = dom_node->previous_sibling();
|
|
|
|
|
if (!previous_dom_node)
|
|
|
|
|
previous_dom_node = dom_node->parent();
|
2023-12-05 16:59:54 -03:00
|
|
|
|
|
|
|
|
dom_node->remove();
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_finish_editing_dom_node(page_id, previous_dom_node->unique_id());
|
2023-12-05 16:59:54 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::take_document_screenshot(u64 page_id)
|
2023-09-17 11:11:16 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
2024-02-12 21:39:20 -03:00
|
|
|
return;
|
|
|
|
|
|
2024-06-21 13:15:32 -03:00
|
|
|
page->queue_screenshot_task({});
|
2022-11-05 01:09:41 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::take_dom_node_screenshot(u64 page_id, Web::UniqueNodeID node_id)
|
2023-12-06 13:51:44 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
2024-02-12 21:39:20 -03:00
|
|
|
return;
|
|
|
|
|
|
2024-06-21 13:15:32 -03:00
|
|
|
page->queue_screenshot_task(node_id);
|
2023-12-06 13:51:44 -03:00
|
|
|
}
|
|
|
|
|
|
2024-09-19 11:40:00 -03:00
|
|
|
static void append_page_text(Web::Page& page, StringBuilder& builder)
|
2023-12-12 09:25:06 -03:00
|
|
|
{
|
2024-09-19 11:40:00 -03:00
|
|
|
auto* document = page.top_level_browsing_context().active_document();
|
|
|
|
|
if (!document) {
|
|
|
|
|
builder.append("(no DOM tree)"sv);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto* body = document->body();
|
|
|
|
|
if (!body) {
|
|
|
|
|
builder.append("(no body)"sv);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
builder.append(body->inner_text());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void append_layout_tree(Web::Page& page, StringBuilder& builder)
|
|
|
|
|
{
|
|
|
|
|
auto* document = page.top_level_browsing_context().active_document();
|
|
|
|
|
if (!document) {
|
|
|
|
|
builder.append("(no DOM tree)"sv);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:50:05 -03:00
|
|
|
document->update_layout(Web::DOM::UpdateLayoutReason::Debugging);
|
2024-09-19 11:40:00 -03:00
|
|
|
|
|
|
|
|
auto* layout_root = document->layout_node();
|
|
|
|
|
if (!layout_root) {
|
|
|
|
|
builder.append("(no layout tree)"sv);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Web::dump_tree(builder, *layout_root);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void append_paint_tree(Web::Page& page, StringBuilder& builder)
|
|
|
|
|
{
|
|
|
|
|
auto* document = page.top_level_browsing_context().active_document();
|
|
|
|
|
if (!document) {
|
|
|
|
|
builder.append("(no DOM tree)"sv);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:50:05 -03:00
|
|
|
document->update_layout(Web::DOM::UpdateLayoutReason::Debugging);
|
2024-09-19 11:40:00 -03:00
|
|
|
|
|
|
|
|
auto* layout_root = document->layout_node();
|
|
|
|
|
if (!layout_root) {
|
|
|
|
|
builder.append("(no layout tree)"sv);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-10-16 10:19:32 -03:00
|
|
|
if (!layout_root->first_paintable()) {
|
2024-09-19 11:40:00 -03:00
|
|
|
builder.append("(no paint tree)"sv);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 10:19:32 -03:00
|
|
|
Web::dump_tree(builder, *layout_root->first_paintable());
|
2024-09-19 11:40:00 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-06 09:45:04 -03:00
|
|
|
static void append_stacking_context_tree(Web::Page& page, StringBuilder& builder)
|
|
|
|
|
{
|
|
|
|
|
auto* document = page.top_level_browsing_context().active_document();
|
|
|
|
|
if (!document) {
|
|
|
|
|
builder.append("(no DOM tree)"sv);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document->update_layout(Web::DOM::UpdateLayoutReason::Debugging);
|
|
|
|
|
|
|
|
|
|
auto* layout_root = document->layout_node();
|
|
|
|
|
if (!layout_root) {
|
|
|
|
|
builder.append("(no layout tree)"sv);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!layout_root->first_paintable()) {
|
|
|
|
|
builder.append("(no paint tree)"sv);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto& viewport_paintable = static_cast<Web::Painting::ViewportPaintable&>(*layout_root->paintable_box());
|
|
|
|
|
viewport_paintable.build_stacking_context_tree_if_needed();
|
2026-05-07 08:38:05 -03:00
|
|
|
if (auto stacking_context = viewport_paintable.stacking_context()) {
|
2025-07-06 09:45:04 -03:00
|
|
|
stacking_context->dump(builder);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-19 11:40:00 -03:00
|
|
|
static void append_gc_graph(StringBuilder& builder)
|
|
|
|
|
{
|
|
|
|
|
auto gc_graph = Web::Bindings::main_thread_vm().heap().dump_graph();
|
|
|
|
|
gc_graph.serialize(builder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::request_internal_page_info(u64 page_id, WebView::PageInfoType type)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value()) {
|
2026-02-01 16:18:43 -03:00
|
|
|
async_did_get_internal_page_info(page_id, type, {});
|
2024-09-19 11:40:00 -03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StringBuilder builder;
|
|
|
|
|
|
|
|
|
|
if (has_flag(type, WebView::PageInfoType::Text)) {
|
|
|
|
|
append_page_text(page->page(), builder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (has_flag(type, WebView::PageInfoType::LayoutTree)) {
|
|
|
|
|
if (!builder.is_empty())
|
|
|
|
|
builder.append("\n"sv);
|
|
|
|
|
append_layout_tree(page->page(), builder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (has_flag(type, WebView::PageInfoType::PaintTree)) {
|
|
|
|
|
if (!builder.is_empty())
|
|
|
|
|
builder.append("\n"sv);
|
|
|
|
|
append_paint_tree(page->page(), builder);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-06 09:45:04 -03:00
|
|
|
if (has_flag(type, WebView::PageInfoType::StackingContextTree)) {
|
|
|
|
|
if (!builder.is_empty())
|
|
|
|
|
builder.append("\n"sv);
|
|
|
|
|
append_stacking_context_tree(page->page(), builder);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-19 11:40:00 -03:00
|
|
|
if (has_flag(type, WebView::PageInfoType::GCGraph)) {
|
|
|
|
|
if (!builder.is_empty())
|
|
|
|
|
builder.append("\n"sv);
|
|
|
|
|
append_gc_graph(builder);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 16:18:43 -03:00
|
|
|
auto buffer = MUST(Core::AnonymousBuffer::create_with_size(builder.length()));
|
2026-04-22 06:34:21 -03:00
|
|
|
if (builder.length() > 0)
|
|
|
|
|
memcpy(buffer.data<void>(), builder.string_view().characters_without_null_termination(), builder.length());
|
2026-02-01 16:18:43 -03:00
|
|
|
async_did_get_internal_page_info(page_id, type, buffer);
|
2023-12-12 09:25:06 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
Messages::WebContentServer::GetSelectedTextResponse ConnectionFromClient::get_selected_text(u64 page_id)
|
2021-07-14 09:32:55 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
2024-04-26 11:59:04 -03:00
|
|
|
return page->page().focused_navigable().selected_text().to_byte_string();
|
2026-05-06 13:00:59 -03:00
|
|
|
return ByteString {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Messages::WebContentServer::CutSelectedTextResponse ConnectionFromClient::cut_selected_text(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
return page->page().focused_navigable().cut_selected_text().to_byte_string();
|
2024-03-29 11:05:05 -03:00
|
|
|
return ByteString {};
|
2021-07-14 09:32:55 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::select_all(u64 page_id)
|
2021-07-14 09:53:55 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
2024-04-26 11:59:04 -03:00
|
|
|
page->page().focused_navigable().select_all();
|
2021-07-14 09:53:55 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::find_in_page(u64 page_id, String query, CaseSensitivity case_sensitivity)
|
2024-05-29 16:09:33 -03:00
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
2024-06-14 12:56:38 -03:00
|
|
|
auto result = page->page().find_in_page({ .string = query, .case_sensitivity = case_sensitivity });
|
2024-06-09 14:35:32 -03:00
|
|
|
async_did_find_in_page(page_id, result.current_match_index, result.total_match_count);
|
2024-05-29 16:09:33 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::find_in_page_next_match(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
2024-06-09 14:35:32 -03:00
|
|
|
auto result = page->page().find_in_page_next_match();
|
|
|
|
|
async_did_find_in_page(page_id, result.current_match_index, result.total_match_count);
|
2024-05-29 16:09:33 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::find_in_page_previous_match(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
2024-06-09 14:35:32 -03:00
|
|
|
auto result = page->page().find_in_page_previous_match();
|
|
|
|
|
async_did_find_in_page(page_id, result.current_match_index, result.total_match_count);
|
2024-05-29 16:09:33 -03:00
|
|
|
}
|
|
|
|
|
|
2025-09-18 10:18:54 -03:00
|
|
|
void ConnectionFromClient::paste(u64 page_id, Utf16String text)
|
2024-03-22 07:56:49 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
2024-04-26 11:59:04 -03:00
|
|
|
page->page().focused_navigable().paste(text);
|
2024-03-22 07:56:49 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 11:48:50 -03:00
|
|
|
void ConnectionFromClient::set_marked_text_from_input_method(u64 page_id, Utf16String text)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().focused_navigable().set_marked_text_from_input_method(text);
|
|
|
|
|
update_input_method_caret_rect(page_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::commit_text_from_input_method(u64 page_id, Utf16String text)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().focused_navigable().commit_text_from_input_method(text);
|
|
|
|
|
update_input_method_caret_rect(page_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::unmark_text_from_input_method(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().focused_navigable().unmark_text_from_input_method();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::update_input_method_caret_rect(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Push the updated caret position to the UI — so platform input methods can place their overlays. We deliberately
|
|
|
|
|
// push this asynchronously — rather than answering a synchronous request from inside an AppKit text-input callback.
|
|
|
|
|
// Blocking there re-enters the run loop — and can deadlock input-method server <-> UI <-> WebContent message flow.
|
|
|
|
|
Optional<Web::DevicePixelRect> caret_rect;
|
|
|
|
|
if (auto document = page->page().focused_navigable().active_document()) {
|
|
|
|
|
if (auto rect = document->current_caret_rect(); rect.has_value())
|
|
|
|
|
caret_rect = page->page().enclosing_device_rect(*rect);
|
|
|
|
|
}
|
|
|
|
|
async_did_update_input_caret_rect(page_id, caret_rect);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 12:46:17 -03:00
|
|
|
void ConnectionFromClient::set_content_blockers(u64 page_id, Core::AnonymousBuffer patterns_buffer)
|
|
|
|
|
{
|
2026-05-21 13:35:29 -03:00
|
|
|
auto& blocker = Web::ContentBlocker::the();
|
|
|
|
|
auto had_cosmetic_rules = blocker.has_cosmetic_rules();
|
2026-05-23 17:15:37 -03:00
|
|
|
auto result = blocker.set_rules_from_bytes(patterns_buffer.bytes());
|
|
|
|
|
if (result.is_error()) {
|
|
|
|
|
dbgln("Failed to set content blockers: {}", result.error());
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-05-21 13:35:29 -03:00
|
|
|
|
|
|
|
|
if (had_cosmetic_rules || blocker.has_cosmetic_rules()) {
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().invalidate_user_style();
|
|
|
|
|
}
|
2021-09-27 06:39:17 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::set_autoplay_allowed_on_all_websites(u64)
|
2023-04-17 14:21:19 -03:00
|
|
|
{
|
|
|
|
|
auto& autoplay_allowlist = Web::PermissionsPolicy::AutoplayAllowlist::the();
|
|
|
|
|
autoplay_allowlist.enable_globally();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::set_autoplay_allowlist(u64, Vector<String> allowlist)
|
2023-04-17 14:21:19 -03:00
|
|
|
{
|
|
|
|
|
auto& autoplay_allowlist = Web::PermissionsPolicy::AutoplayAllowlist::the();
|
2025-03-29 09:54:51 -03:00
|
|
|
autoplay_allowlist.enable_for_origins(allowlist);
|
2023-04-17 14:21:19 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::set_proxy_mappings(u64, Vector<ByteString> proxies, HashMap<ByteString, size_t> mappings)
|
2022-04-07 18:16:47 -03:00
|
|
|
{
|
|
|
|
|
auto keys = mappings.keys();
|
|
|
|
|
quick_sort(keys, [&](auto& a, auto& b) { return a.length() < b.length(); });
|
|
|
|
|
|
2023-12-16 11:19:34 -03:00
|
|
|
OrderedHashMap<ByteString, size_t> sorted_mappings;
|
2022-04-07 18:16:47 -03:00
|
|
|
for (auto& key : keys) {
|
|
|
|
|
auto value = *mappings.get(key);
|
|
|
|
|
if (value >= proxies.size())
|
|
|
|
|
continue;
|
|
|
|
|
sorted_mappings.set(key, value);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
Web::ProxyMappings::the().set_mappings(move(proxies), move(sorted_mappings));
|
2022-04-07 18:16:47 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::set_preferred_color_scheme(u64 page_id, Web::CSS::PreferredColorScheme color_scheme)
|
2021-10-26 13:00:10 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->set_preferred_color_scheme(color_scheme);
|
2021-10-26 13:00:10 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::set_preferred_contrast(u64 page_id, Web::CSS::PreferredContrast contrast)
|
2024-06-12 20:03:56 -03:00
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->set_preferred_contrast(contrast);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::set_preferred_motion(u64 page_id, Web::CSS::PreferredMotion motion)
|
2024-06-13 11:15:59 -03:00
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->set_preferred_motion(motion);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::set_preferred_languages(u64, Vector<String> preferred_languages)
|
2024-07-23 17:10:24 -03:00
|
|
|
{
|
|
|
|
|
// FIXME: Whenever the user agent needs to make the navigator.languages attribute of a Window or WorkerGlobalScope
|
|
|
|
|
// object global return a new set of language tags, the user agent must queue a global task on the DOM manipulation
|
|
|
|
|
// task source given global to fire an event named languagechange at global, and wait until that task begins to be
|
|
|
|
|
// executed before actually returning a new value.
|
2025-03-08 14:22:39 -03:00
|
|
|
Web::ResourceLoader::the().set_preferred_languages(move(preferred_languages));
|
2024-07-23 17:10:24 -03:00
|
|
|
}
|
|
|
|
|
|
2026-04-13 11:55:24 -03:00
|
|
|
void ConnectionFromClient::set_browsing_behavior(u64 page_id, WebView::BrowsingBehavior browsing_behavior)
|
|
|
|
|
{
|
2026-05-15 08:20:23 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value()) {
|
2026-04-13 11:55:24 -03:00
|
|
|
page->page().set_enable_autoscroll(browsing_behavior.enable_autoscroll);
|
2026-05-15 08:20:23 -03:00
|
|
|
page->page().set_enable_primary_paste(browsing_behavior.enable_primary_paste);
|
|
|
|
|
}
|
2026-04-13 11:55:24 -03:00
|
|
|
}
|
|
|
|
|
|
2025-04-02 10:30:34 -03:00
|
|
|
void ConnectionFromClient::set_enable_global_privacy_control(u64, bool enable)
|
2024-07-02 16:29:43 -03:00
|
|
|
{
|
2025-04-02 10:30:34 -03:00
|
|
|
Web::ResourceLoader::the().set_enable_global_privacy_control(enable);
|
2024-07-02 16:29:43 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::set_has_focus(u64 page_id, bool has_focus)
|
2022-02-06 15:03:13 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->set_has_focus(has_focus);
|
2022-02-06 15:03:13 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::set_is_scripting_enabled(u64 page_id, bool is_scripting_enabled)
|
2022-03-30 19:23:14 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->set_is_scripting_enabled(is_scripting_enabled);
|
2022-03-30 19:23:14 -03:00
|
|
|
}
|
|
|
|
|
|
2026-01-05 22:05:10 -03:00
|
|
|
void ConnectionFromClient::set_zoom_level(u64 page_id, double zoom_level)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->set_zoom_level(zoom_level);
|
2023-01-12 11:35:35 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-25 09:10:00 -03:00
|
|
|
void ConnectionFromClient::set_maximum_frames_per_second(u64 page_id, double maximum_frames_per_second)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->set_maximum_frames_per_second(maximum_frames_per_second);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::set_window_position(u64 page_id, Web::DevicePixelPoint position)
|
2022-11-01 15:55:53 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->set_window_position(position);
|
2022-11-01 15:55:53 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::set_window_size(u64 page_id, Web::DevicePixelSize size)
|
2022-11-01 15:55:53 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->set_window_size(size);
|
2022-11-01 15:55:53 -03:00
|
|
|
}
|
|
|
|
|
|
2024-10-29 00:37:11 -03:00
|
|
|
void ConnectionFromClient::did_update_window_rect(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().did_update_window_rect();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::handle_file_return(u64, i32 error, Optional<IPC::File> file, i32 request_id)
|
2022-02-26 13:50:31 -03:00
|
|
|
{
|
2023-02-02 09:29:19 -03:00
|
|
|
auto file_request = m_requested_files.take(request_id);
|
2022-02-26 13:50:31 -03:00
|
|
|
|
2023-01-30 18:35:47 -03:00
|
|
|
VERIFY(file_request.has_value());
|
|
|
|
|
VERIFY(file_request.value().on_file_request_finish);
|
|
|
|
|
|
|
|
|
|
file_request.value().on_file_request_finish(error != 0 ? Error::from_errno(error) : ErrorOr<i32> { file->take_fd() });
|
2022-02-26 13:50:31 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::request_file(u64 page_id, Web::FileRequest file_request)
|
2022-02-26 13:50:31 -03:00
|
|
|
{
|
|
|
|
|
i32 const id = last_id++;
|
|
|
|
|
|
2023-01-30 18:35:47 -03:00
|
|
|
auto path = file_request.path();
|
|
|
|
|
m_requested_files.set(id, move(file_request));
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
async_did_request_file(page_id, path, id);
|
2022-02-26 13:50:31 -03:00
|
|
|
}
|
2022-09-19 15:50:33 -03:00
|
|
|
|
2024-11-13 13:24:16 -03:00
|
|
|
void ConnectionFromClient::set_system_visibility_state(u64 page_id, Web::HTML::VisibilityState visibility_state)
|
2022-09-19 15:50:33 -03:00
|
|
|
{
|
2024-11-13 13:24:16 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().top_level_traversable()->set_system_visibility_state(visibility_state);
|
2022-09-19 15:50:33 -03:00
|
|
|
}
|
|
|
|
|
|
2025-10-09 15:29:12 -03:00
|
|
|
void ConnectionFromClient::reset_zoom(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().top_level_traversable()->reset_zoom();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::js_console_input(u64 page_id, String js_source)
|
2024-02-02 22:00:48 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
auto page = this->page(page_id);
|
|
|
|
|
if (!page.has_value())
|
2024-02-12 21:39:20 -03:00
|
|
|
return;
|
|
|
|
|
|
2024-03-29 11:05:05 -03:00
|
|
|
page->js_console_input(js_source);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::run_javascript(u64 page_id, String js_source)
|
2024-02-02 22:00:48 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->run_javascript(js_source);
|
2024-02-02 22:00:48 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::alert_closed(u64 page_id)
|
2022-11-15 17:49:36 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().alert_closed();
|
2022-11-15 17:49:36 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::confirm_closed(u64 page_id, bool accepted)
|
2022-11-15 17:49:36 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().confirm_closed(accepted);
|
2022-11-15 17:49:36 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::prompt_closed(u64 page_id, Optional<String> response)
|
2022-11-15 17:49:36 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
2025-03-08 14:22:39 -03:00
|
|
|
page->page().prompt_closed(move(response));
|
2022-11-15 17:49:36 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::color_picker_update(u64 page_id, Optional<Color> picked_color, Web::HTML::ColorPickerUpdateState state)
|
2023-09-04 06:32:40 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().color_picker_update(picked_color, state);
|
2023-09-04 06:32:40 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::file_picker_closed(u64 page_id, Vector<Web::HTML::SelectedFile> selected_files)
|
2024-02-25 15:02:47 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
2025-03-08 14:22:39 -03:00
|
|
|
page->page().file_picker_closed(selected_files);
|
2024-02-25 15:02:47 -03:00
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::select_dropdown_closed(u64 page_id, Optional<u32> selected_item_id)
|
2023-12-07 11:53:49 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
2024-04-03 14:19:08 -03:00
|
|
|
page->page().select_dropdown_closed(selected_item_id);
|
2023-12-07 11:53:49 -03:00
|
|
|
}
|
|
|
|
|
|
2025-05-01 12:38:31 -03:00
|
|
|
void ConnectionFromClient::retrieved_clipboard_entries(u64 page_id, u64 request_id, Vector<Web::Clipboard::SystemClipboardItem> items)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().retrieved_clipboard_entries(request_id, move(items));
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 12:37:14 -03:00
|
|
|
void ConnectionFromClient::did_delete_all_cookies(u64 page_id, u64 request_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->did_delete_all_cookies(request_id);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::toggle_media_play_state(u64 page_id)
|
2023-05-15 12:17:58 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
2025-10-31 09:30:47 -03:00
|
|
|
page->page().toggle_media_play_state();
|
2023-05-15 12:17:58 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::toggle_media_mute_state(u64 page_id)
|
2023-06-16 12:29:54 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().toggle_media_mute_state();
|
2023-06-16 12:29:54 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::toggle_media_loop_state(u64 page_id)
|
2023-05-15 12:17:58 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
2025-10-31 09:30:47 -03:00
|
|
|
page->page().toggle_media_loop_state();
|
2023-05-15 12:17:58 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-28 11:36:32 -03:00
|
|
|
void ConnectionFromClient::toggle_media_fullscreen_state(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().toggle_media_fullscreen_state();
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 22:00:48 -03:00
|
|
|
void ConnectionFromClient::toggle_media_controls_state(u64 page_id)
|
2023-05-15 12:17:58 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
2025-10-31 09:30:47 -03:00
|
|
|
page->page().toggle_media_controls_state();
|
2023-05-15 12:17:58 -03:00
|
|
|
}
|
|
|
|
|
|
2024-03-30 10:41:15 -03:00
|
|
|
void ConnectionFromClient::toggle_page_mute_state(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().toggle_page_mute_state();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:22:39 -03:00
|
|
|
void ConnectionFromClient::set_user_style(u64 page_id, String source)
|
2023-08-21 11:50:31 -03:00
|
|
|
{
|
2024-03-29 11:05:05 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
2025-03-08 14:22:39 -03:00
|
|
|
page->page().set_user_style(move(source));
|
2023-08-21 11:50:31 -03:00
|
|
|
}
|
|
|
|
|
|
2024-08-24 13:15:20 -03:00
|
|
|
void ConnectionFromClient::system_time_zone_changed()
|
|
|
|
|
{
|
2024-09-03 11:18:43 -03:00
|
|
|
JS::clear_system_time_zone_cache();
|
2024-08-24 13:15:20 -03:00
|
|
|
Unicode::clear_system_time_zone_cache();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 15:27:19 -03:00
|
|
|
void ConnectionFromClient::set_document_cookie_version_buffer(u64 page_id, Core::AnonymousBuffer document_cookie_version_buffer)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().client().page_did_receive_document_cookie_version_buffer(move(document_cookie_version_buffer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::set_document_cookie_version_index(u64 page_id, i64 document_id, Core::SharedVersionIndex document_index)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().client().page_did_receive_document_cookie_version_index(document_id, document_index);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 13:13:47 -03:00
|
|
|
void ConnectionFromClient::cookies_changed(u64 page_id, Vector<HTTP::Cookie::Cookie> cookies)
|
2025-08-07 12:20:13 -03:00
|
|
|
{
|
2026-01-30 10:52:34 -03:00
|
|
|
if (auto page = this->page(page_id); page.has_value()) {
|
|
|
|
|
auto window = page->page().top_level_traversable()->active_window();
|
2025-08-07 12:20:13 -03:00
|
|
|
if (!window)
|
|
|
|
|
return;
|
2026-01-30 10:52:34 -03:00
|
|
|
|
|
|
|
|
window->cookie_store()->process_cookie_changes(move(cookies));
|
2025-08-07 12:20:13 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 16:18:00 -03:00
|
|
|
void ConnectionFromClient::broadcast_channel_message(Web::HTML::BroadcastChannelMessage message)
|
|
|
|
|
{
|
|
|
|
|
Web::HTML::BroadcastChannel::deliver_message_locally(message);
|
2026-05-21 10:33:23 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::did_worker_agent_finish_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token)
|
|
|
|
|
{
|
|
|
|
|
Web::HTML::WorkerAgentParent::did_finish_loading_worker_script(owner_token);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::did_worker_agent_fail_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token)
|
|
|
|
|
{
|
|
|
|
|
Web::HTML::WorkerAgentParent::did_fail_loading_worker_script(owner_token);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::did_worker_agent_report_exception(Web::HTML::WorkerAgentOwnerToken owner_token, String message, String filename, u32 lineno, u32 colno)
|
|
|
|
|
{
|
|
|
|
|
Web::HTML::WorkerAgentParent::did_report_worker_exception(owner_token, move(message), move(filename), lineno, colno);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConnectionFromClient::did_worker_agent_close(Web::HTML::WorkerAgentOwnerToken owner_token)
|
|
|
|
|
{
|
|
|
|
|
Web::HTML::WorkerAgentParent::did_close_worker(owner_token);
|
2026-04-10 16:18:00 -03:00
|
|
|
}
|
|
|
|
|
|
2025-11-07 12:35:05 -03:00
|
|
|
// https://html.spec.whatwg.org/multipage/speculative-loading.html#nav-traversal-ui:close-a-top-level-traversable
|
|
|
|
|
void ConnectionFromClient::request_close(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
// Browser user agents should offer users the ability to arbitrarily close any top-level traversable in their top-level traversable set.
|
|
|
|
|
// For example, by clicking a "close tab" button.
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value())
|
|
|
|
|
page->page().top_level_traversable()->close_top_level_traversable();
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-01 09:23:04 -03:00
|
|
|
void ConnectionFromClient::exit_fullscreen(u64 page_id)
|
|
|
|
|
{
|
|
|
|
|
if (auto page = this->page(page_id); page.has_value()) {
|
|
|
|
|
Web::HTML::TemporaryExecutionContext context(page->page().top_level_browsing_context().active_document()->realm(), Web::HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
|
|
|
|
|
page->page().top_level_browsing_context().active_document()->fully_exit_fullscreen();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-17 12:31:42 -03:00
|
|
|
}
|