mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
LibWebView+WebContent+headless-browser: Make the page info IPCs async
The IPCs to request a page's text, layout tree, etc. are currently all synchronous. This can result in a deadlock when WebContent also makes a synchronous IPC call, as both ends will be waiting on each other. This replaces the page info IPCs with a single, asynchronous IPC. This new IPC is promise-based, much like our screenshot IPC.
This commit is contained in:
parent
b688bbf26c
commit
3332230cef
Notes:
github-actions[bot]
2024-09-19 16:08:09 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/3332230cef8 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1447
11 changed files with 174 additions and 90 deletions
|
@ -71,7 +71,7 @@ static bool is_primitive_type(ByteString const& type)
|
|||
static bool is_simple_type(ByteString const& type)
|
||||
{
|
||||
// Small types that it makes sense just to pass by value.
|
||||
return type.is_one_of("AK::CaseSensitivity", "Gfx::Color", "Web::DevicePixels", "Gfx::IntPoint", "Gfx::FloatPoint", "Web::DevicePixelPoint", "Gfx::IntSize", "Gfx::FloatSize", "Web::DevicePixelSize", "Core::File::OpenMode", "Web::Cookie::Source", "Web::EventResult", "Web::HTML::AllowMultipleFiles", "Web::HTML::AudioPlayState", "Web::HTML::HistoryHandlingBehavior");
|
||||
return type.is_one_of("AK::CaseSensitivity", "Gfx::Color", "Web::DevicePixels", "Gfx::IntPoint", "Gfx::FloatPoint", "Web::DevicePixelPoint", "Gfx::IntSize", "Gfx::FloatSize", "Web::DevicePixelSize", "Core::File::OpenMode", "Web::Cookie::Source", "Web::EventResult", "Web::HTML::AllowMultipleFiles", "Web::HTML::AudioPlayState", "Web::HTML::HistoryHandlingBehavior", "WebView::PageInfoType");
|
||||
}
|
||||
|
||||
static bool is_primitive_or_simple_type(ByteString const& type)
|
||||
|
|
22
Userland/Libraries/LibWebView/PageInfo.h
Normal file
22
Userland/Libraries/LibWebView/PageInfo.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/EnumBits.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
enum class PageInfoType {
|
||||
Text = 1 << 0,
|
||||
LayoutTree = 1 << 2,
|
||||
PaintTree = 1 << 3,
|
||||
GCGraph = 1 << 4,
|
||||
};
|
||||
|
||||
AK_ENUM_BITWISE_OPERATORS(PageInfoType);
|
||||
|
||||
}
|
|
@ -583,15 +583,40 @@ void ViewImplementation::did_receive_screenshot(Badge<WebContentClient>, Gfx::Sh
|
|||
m_pending_screenshot = nullptr;
|
||||
}
|
||||
|
||||
NonnullRefPtr<Core::Promise<String>> ViewImplementation::request_internal_page_info(PageInfoType type)
|
||||
{
|
||||
auto promise = Core::Promise<String>::construct();
|
||||
|
||||
if (m_pending_info_request) {
|
||||
// For simplicitly, only allow one info request at a time for now.
|
||||
promise->reject(Error::from_string_literal("A page info request is already in progress"));
|
||||
return promise;
|
||||
}
|
||||
|
||||
m_pending_info_request = promise;
|
||||
client().async_request_internal_page_info(page_id(), type);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
void ViewImplementation::did_receive_internal_page_info(Badge<WebContentClient>, PageInfoType, String const& info)
|
||||
{
|
||||
VERIFY(m_pending_info_request);
|
||||
|
||||
m_pending_info_request->resolve(String { info });
|
||||
m_pending_info_request = nullptr;
|
||||
}
|
||||
|
||||
ErrorOr<LexicalPath> ViewImplementation::dump_gc_graph()
|
||||
{
|
||||
auto gc_graph_json = client().dump_gc_graph(page_id());
|
||||
auto promise = request_internal_page_info(PageInfoType::GCGraph);
|
||||
auto gc_graph_json = TRY(promise->await());
|
||||
|
||||
LexicalPath path { Core::StandardPaths::tempfile_directory() };
|
||||
path = path.append(TRY(Core::DateTime::now().to_string("gc-graph-%Y-%m-%d-%H-%M-%S.json"sv)));
|
||||
|
||||
auto screenshot_file = TRY(Core::File::open(path.string(), Core::File::OpenMode::Write));
|
||||
TRY(screenshot_file->write_until_depleted(gc_graph_json.bytes()));
|
||||
auto dump_file = TRY(Core::File::open(path.string(), Core::File::OpenMode::Write));
|
||||
TRY(dump_file->write_until_depleted(gc_graph_json.bytes()));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <LibWeb/Page/EventResult.h>
|
||||
#include <LibWeb/Page/InputEvent.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
#include <LibWebView/PageInfo.h>
|
||||
#include <LibWebView/WebContentClient.h>
|
||||
|
||||
namespace WebView {
|
||||
|
@ -140,6 +141,9 @@ public:
|
|||
NonnullRefPtr<Core::Promise<LexicalPath>> take_dom_node_screenshot(i32);
|
||||
virtual void did_receive_screenshot(Badge<WebContentClient>, Gfx::ShareableBitmap const&);
|
||||
|
||||
NonnullRefPtr<Core::Promise<String>> request_internal_page_info(PageInfoType);
|
||||
void did_receive_internal_page_info(Badge<WebContentClient>, PageInfoType, String const&);
|
||||
|
||||
ErrorOr<LexicalPath> dump_gc_graph();
|
||||
|
||||
void set_user_style_sheet(String source);
|
||||
|
@ -286,6 +290,7 @@ protected:
|
|||
RefPtr<Core::Timer> m_repeated_crash_timer;
|
||||
|
||||
RefPtr<Core::Promise<LexicalPath>> m_pending_screenshot;
|
||||
RefPtr<Core::Promise<String>> m_pending_info_request;
|
||||
|
||||
Web::HTML::AudioPlayState m_audio_play_state { Web::HTML::AudioPlayState::Paused };
|
||||
size_t m_number_of_elements_playing_audio { 0 };
|
||||
|
|
|
@ -342,6 +342,12 @@ void WebContentClient::did_take_screenshot(u64 page_id, Gfx::ShareableBitmap con
|
|||
view->did_receive_screenshot({}, screenshot);
|
||||
}
|
||||
|
||||
void WebContentClient::did_get_internal_page_info(u64 page_id, WebView::PageInfoType type, String const& info)
|
||||
{
|
||||
if (auto view = view_for_page_id(page_id); view.has_value())
|
||||
view->did_receive_internal_page_info({}, type, info);
|
||||
}
|
||||
|
||||
void WebContentClient::did_output_js_console_message(u64 page_id, i32 message_index)
|
||||
{
|
||||
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
||||
|
|
|
@ -78,6 +78,7 @@ private:
|
|||
virtual void did_finish_editing_dom_node(u64 page_id, Optional<i32> const& node_id) override;
|
||||
virtual void did_get_dom_node_html(u64 page_id, String const& html) override;
|
||||
virtual void did_take_screenshot(u64 page_id, Gfx::ShareableBitmap const& screenshot) override;
|
||||
virtual void did_get_internal_page_info(u64 page_id, PageInfoType, String const&) override;
|
||||
virtual void did_output_js_console_message(u64 page_id, i32 message_index) override;
|
||||
virtual void did_get_js_console_messages(u64 page_id, i32 start_index, Vector<ByteString> const& message_types, Vector<ByteString> const& messages) override;
|
||||
virtual void did_change_favicon(u64 page_id, Gfx::ShareableBitmap const&) override;
|
||||
|
|
|
@ -840,10 +840,104 @@ void ConnectionFromClient::take_dom_node_screenshot(u64 page_id, i32 node_id)
|
|||
page->queue_screenshot_task(node_id);
|
||||
}
|
||||
|
||||
Messages::WebContentServer::DumpGcGraphResponse ConnectionFromClient::dump_gc_graph(u64)
|
||||
static void append_page_text(Web::Page& page, StringBuilder& builder)
|
||||
{
|
||||
auto gc_graph_json = Web::Bindings::main_thread_vm().heap().dump_graph();
|
||||
return MUST(String::from_byte_string(gc_graph_json.to_byte_string()));
|
||||
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;
|
||||
}
|
||||
|
||||
document->update_layout();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
document->update_layout();
|
||||
|
||||
auto* layout_root = document->layout_node();
|
||||
if (!layout_root) {
|
||||
builder.append("(no layout tree)"sv);
|
||||
return;
|
||||
}
|
||||
if (!layout_root->paintable()) {
|
||||
builder.append("(no paint tree)"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
Web::dump_tree(builder, *layout_root->paintable());
|
||||
}
|
||||
|
||||
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()) {
|
||||
async_did_get_internal_page_info(page_id, type, "(no page)"_string);
|
||||
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);
|
||||
}
|
||||
|
||||
if (has_flag(type, WebView::PageInfoType::GCGraph)) {
|
||||
if (!builder.is_empty())
|
||||
builder.append("\n"sv);
|
||||
append_gc_graph(builder);
|
||||
}
|
||||
|
||||
async_did_get_internal_page_info(page_id, type, MUST(builder.to_string()));
|
||||
}
|
||||
|
||||
Messages::WebContentServer::GetSelectedTextResponse ConnectionFromClient::get_selected_text(u64 page_id)
|
||||
|
@ -895,58 +989,6 @@ void ConnectionFromClient::paste(u64 page_id, String const& text)
|
|||
page->page().focused_navigable().paste(text);
|
||||
}
|
||||
|
||||
Messages::WebContentServer::DumpLayoutTreeResponse ConnectionFromClient::dump_layout_tree(u64 page_id)
|
||||
{
|
||||
auto page = this->page(page_id);
|
||||
if (!page.has_value())
|
||||
return ByteString { "(no page)" };
|
||||
|
||||
auto* document = page->page().top_level_browsing_context().active_document();
|
||||
if (!document)
|
||||
return ByteString { "(no DOM tree)" };
|
||||
document->update_layout();
|
||||
auto* layout_root = document->layout_node();
|
||||
if (!layout_root)
|
||||
return ByteString { "(no layout tree)" };
|
||||
StringBuilder builder;
|
||||
Web::dump_tree(builder, *layout_root);
|
||||
return builder.to_byte_string();
|
||||
}
|
||||
|
||||
Messages::WebContentServer::DumpPaintTreeResponse ConnectionFromClient::dump_paint_tree(u64 page_id)
|
||||
{
|
||||
auto page = this->page(page_id);
|
||||
if (!page.has_value())
|
||||
return ByteString { "(no page)" };
|
||||
|
||||
auto* document = page->page().top_level_browsing_context().active_document();
|
||||
if (!document)
|
||||
return ByteString { "(no DOM tree)" };
|
||||
document->update_layout();
|
||||
auto* layout_root = document->layout_node();
|
||||
if (!layout_root)
|
||||
return ByteString { "(no layout tree)" };
|
||||
if (!layout_root->paintable())
|
||||
return ByteString { "(no paint tree)" };
|
||||
StringBuilder builder;
|
||||
Web::dump_tree(builder, *layout_root->paintable());
|
||||
return builder.to_byte_string();
|
||||
}
|
||||
|
||||
Messages::WebContentServer::DumpTextResponse ConnectionFromClient::dump_text(u64 page_id)
|
||||
{
|
||||
auto page = this->page(page_id);
|
||||
if (!page.has_value())
|
||||
return ByteString { "(no page)" };
|
||||
|
||||
auto* document = page->page().top_level_browsing_context().active_document();
|
||||
if (!document)
|
||||
return ByteString { "(no DOM tree)" };
|
||||
if (!document->body())
|
||||
return ByteString { "(no body)" };
|
||||
return document->body()->inner_text();
|
||||
}
|
||||
|
||||
void ConnectionFromClient::set_content_filters(u64, Vector<String> const& filters)
|
||||
{
|
||||
Web::ContentFilter::the().set_patterns(filters).release_value_but_fixme_should_propagate_errors();
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <LibWeb/Page/InputEvent.h>
|
||||
#include <LibWeb/Platform/Timer.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
#include <LibWebView/PageInfo.h>
|
||||
#include <WebContent/Forward.h>
|
||||
#include <WebContent/WebContentClientEndpoint.h>
|
||||
#include <WebContent/WebContentConsoleClient.h>
|
||||
|
@ -90,9 +91,6 @@ private:
|
|||
virtual void remove_dom_node(u64 page_id, i32 node_id) override;
|
||||
virtual void get_dom_node_html(u64 page_id, i32 node_id) override;
|
||||
|
||||
virtual Messages::WebContentServer::DumpLayoutTreeResponse dump_layout_tree(u64 page_id) override;
|
||||
virtual Messages::WebContentServer::DumpPaintTreeResponse dump_paint_tree(u64 page_id) override;
|
||||
virtual Messages::WebContentServer::DumpTextResponse dump_text(u64 page_id) override;
|
||||
virtual void set_content_filters(u64 page_id, Vector<String> const&) override;
|
||||
virtual void set_autoplay_allowed_on_all_websites(u64 page_id) override;
|
||||
virtual void set_autoplay_allowlist(u64 page_id, Vector<String> const& allowlist) override;
|
||||
|
@ -135,7 +133,7 @@ private:
|
|||
virtual void take_document_screenshot(u64 page_id) override;
|
||||
virtual void take_dom_node_screenshot(u64 page_id, i32 node_id) override;
|
||||
|
||||
virtual Messages::WebContentServer::DumpGcGraphResponse dump_gc_graph(u64 page_id) override;
|
||||
virtual void request_internal_page_info(u64 page_id, WebView::PageInfoType) override;
|
||||
|
||||
virtual Messages::WebContentServer::GetLocalStorageEntriesResponse get_local_storage_entries(u64 page_id) override;
|
||||
virtual Messages::WebContentServer::GetSessionStorageEntriesResponse get_session_storage_entries(u64 page_id) override;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWebView/Attribute.h>
|
||||
#include <LibWebView/ProcessHandle.h>
|
||||
#include <LibWebView/PageInfo.h>
|
||||
|
||||
endpoint WebContentClient
|
||||
{
|
||||
|
@ -62,6 +63,8 @@ endpoint WebContentClient
|
|||
|
||||
did_take_screenshot(u64 page_id, Gfx::ShareableBitmap screenshot) =|
|
||||
|
||||
did_get_internal_page_info(u64 page_id, WebView::PageInfoType type, String info) =|
|
||||
|
||||
did_change_favicon(u64 page_id, Gfx::ShareableBitmap favicon) =|
|
||||
did_request_all_cookies(u64 page_id, URL::URL url) => (Vector<Web::Cookie::Cookie> cookies)
|
||||
did_request_named_cookie(u64 page_id, URL::URL url, String name) => (Optional<Web::Cookie::Cookie> cookie)
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <LibWeb/Page/InputEvent.h>
|
||||
#include <LibWeb/WebDriver/ExecuteScript.h>
|
||||
#include <LibWebView/Attribute.h>
|
||||
#include <LibWebView/PageInfo.h>
|
||||
|
||||
endpoint WebContentServer
|
||||
{
|
||||
|
@ -62,14 +63,10 @@ endpoint WebContentServer
|
|||
take_document_screenshot(u64 page_id) =|
|
||||
take_dom_node_screenshot(u64 page_id, i32 node_id) =|
|
||||
|
||||
dump_gc_graph(u64 page_id) => (String json)
|
||||
request_internal_page_info(u64 page_id, WebView::PageInfoType type) =|
|
||||
|
||||
run_javascript(u64 page_id, ByteString js_source) =|
|
||||
|
||||
dump_layout_tree(u64 page_id) => (ByteString dump)
|
||||
dump_paint_tree(u64 page_id) => (ByteString dump)
|
||||
dump_text(u64 page_id) => (ByteString dump)
|
||||
|
||||
get_selected_text(u64 page_id) => (ByteString selection)
|
||||
select_all(u64 page_id) =|
|
||||
paste(u64 page_id, String text) =|
|
||||
|
|
|
@ -120,21 +120,6 @@ public:
|
|||
m_pending_screenshot->resolve(screenshot.bitmap());
|
||||
}
|
||||
|
||||
ErrorOr<String> dump_layout_tree()
|
||||
{
|
||||
return String::from_byte_string(client().dump_layout_tree(0));
|
||||
}
|
||||
|
||||
ErrorOr<String> dump_paint_tree()
|
||||
{
|
||||
return String::from_byte_string(client().dump_paint_tree(0));
|
||||
}
|
||||
|
||||
ErrorOr<String> dump_text()
|
||||
{
|
||||
return String::from_byte_string(client().dump_text(0));
|
||||
}
|
||||
|
||||
void clear_content_filters()
|
||||
{
|
||||
client().async_set_content_filters(0, {});
|
||||
|
@ -256,18 +241,15 @@ static ErrorOr<TestResult> run_dump_test(HeadlessWebContentView& view, URL::URL
|
|||
// It also causes a lot more code to run, which is good for finding bugs. :^)
|
||||
(void)view.take_screenshot();
|
||||
|
||||
StringBuilder builder;
|
||||
builder.append(view.dump_layout_tree().release_value_but_fixme_should_propagate_errors());
|
||||
builder.append("\n"sv);
|
||||
builder.append(view.dump_paint_tree().release_value_but_fixme_should_propagate_errors());
|
||||
result = builder.to_string().release_value_but_fixme_should_propagate_errors();
|
||||
auto promise = view.request_internal_page_info(WebView::PageInfoType::LayoutTree | WebView::PageInfoType::PaintTree);
|
||||
result = MUST(promise->await());
|
||||
|
||||
loop.quit(0);
|
||||
}
|
||||
};
|
||||
|
||||
view.on_text_test_finish = {};
|
||||
} else if (mode == TestMode::Text) {
|
||||
|
||||
view.on_load_finish = [&](auto const& loaded_url) {
|
||||
// NOTE: We don't want subframe loads to trigger the test finish.
|
||||
if (!url.equals(loaded_url, URL::ExcludeFragment::Yes))
|
||||
|
@ -276,8 +258,11 @@ static ErrorOr<TestResult> run_dump_test(HeadlessWebContentView& view, URL::URL
|
|||
if (did_finish_test)
|
||||
loop.quit(0);
|
||||
};
|
||||
|
||||
view.on_text_test_finish = [&]() {
|
||||
result = view.dump_text().release_value_but_fixme_should_propagate_errors();
|
||||
auto promise = view.request_internal_page_info(WebView::PageInfoType::Text);
|
||||
result = MUST(promise->await());
|
||||
|
||||
did_finish_test = true;
|
||||
if (did_finish_loading)
|
||||
loop.quit(0);
|
||||
|
|
Loading…
Reference in a new issue