headless-browser: Support creating child web views

This is used when a page calls window.open, and is relied upon heavily
by WPT.
This commit is contained in:
Timothy Flynn 2024-10-21 18:54:25 -04:00 committed by Tim Ledbetter
commit b73f9fef5a
Notes: github-actions[bot] 2024-10-22 03:25:46 +00:00
6 changed files with 82 additions and 36 deletions

View file

@ -68,12 +68,20 @@ ErrorOr<void> Application::launch_services()
return {};
}
ErrorOr<HeadlessWebView*> Application::create_web_view(Core::AnonymousBuffer theme, Gfx::IntSize window_size)
HeadlessWebView& Application::create_web_view(Core::AnonymousBuffer theme, Gfx::IntSize window_size)
{
auto web_view = TRY(HeadlessWebView::create(move(theme), window_size));
auto web_view = HeadlessWebView::create(move(theme), window_size);
m_web_views.append(move(web_view));
return m_web_views.last().ptr();
return *m_web_views.last();
}
HeadlessWebView& Application::create_child_web_view(HeadlessWebView const& parent, u64 page_index)
{
auto web_view = HeadlessWebView::create_child(parent, page_index);
m_web_views.append(move(web_view));
return *m_web_views.last();
}
void Application::destroy_web_views()

View file

@ -37,7 +37,8 @@ public:
static Requests::RequestClient& request_client() { return *the().m_request_client; }
static ImageDecoderClient::Client& image_decoder_client() { return *the().m_image_decoder_client; }
ErrorOr<HeadlessWebView*> create_web_view(Core::AnonymousBuffer theme, Gfx::IntSize window_size);
HeadlessWebView& create_web_view(Core::AnonymousBuffer theme, Gfx::IntSize window_size);
HeadlessWebView& create_child_web_view(HeadlessWebView const&, u64 page_index);
void destroy_web_views();
template<typename Callback>

View file

@ -10,13 +10,25 @@
#include <Ladybird/Utilities.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ShareableBitmap.h>
#include <LibWeb/Crypto/Crypto.h>
namespace Ladybird {
HeadlessWebView::HeadlessWebView(Gfx::IntSize viewport_size)
: m_viewport_size(viewport_size)
HeadlessWebView::HeadlessWebView(Core::AnonymousBuffer theme, Gfx::IntSize viewport_size)
: m_theme(move(theme))
, m_viewport_size(viewport_size)
, m_test_promise(TestPromise::construct())
{
on_new_web_view = [this](auto, auto, Optional<u64> page_index) {
if (page_index.has_value()) {
auto& web_view = Application::the().create_child_web_view(*this, *page_index);
return web_view.handle();
}
auto& web_view = Application::the().create_web_view(m_theme, m_viewport_size);
return web_view.handle();
};
on_request_worker_agent = []() {
auto web_worker_paths = MUST(get_paths_for_helper_process("WebWorker"sv));
auto worker_client = MUST(launch_web_worker_process(web_worker_paths, Application::request_client()));
@ -25,38 +37,60 @@ HeadlessWebView::HeadlessWebView(Gfx::IntSize viewport_size)
};
}
ErrorOr<NonnullOwnPtr<HeadlessWebView>> HeadlessWebView::create(Core::AnonymousBuffer theme, Gfx::IntSize window_size)
NonnullOwnPtr<HeadlessWebView> HeadlessWebView::create(Core::AnonymousBuffer theme, Gfx::IntSize window_size)
{
auto view = TRY(adopt_nonnull_own_or_enomem(new (nothrow) HeadlessWebView(window_size)));
auto request_server_socket = TRY(connect_new_request_server_client(Application::request_client()));
auto image_decoder_socket = TRY(connect_new_image_decoder_client(Application::image_decoder_client()));
auto candidate_web_content_paths = TRY(get_paths_for_helper_process("WebContent"sv));
view->m_client_state.client = TRY(launch_web_content_process(*view, candidate_web_content_paths, move(image_decoder_socket), move(request_server_socket)));
view->client().async_update_system_theme(0, move(theme));
view->client().async_set_viewport_size(0, view->viewport_size());
view->client().async_set_window_size(0, view->viewport_size());
if (WebView::Application::chrome_options().allow_popups == WebView::AllowPopups::Yes)
view->client().async_debug_request(0, "block-pop-ups"sv, "off"sv);
if (auto web_driver_ipc_path = WebView::Application::chrome_options().webdriver_content_ipc_path; web_driver_ipc_path.has_value())
view->client().async_connect_to_webdriver(0, *web_driver_ipc_path);
view->m_client_state.client->on_web_content_process_crash = [&view = *view] {
warnln("\033[31;1mWebContent Crashed!!\033[0m");
warnln(" Last page loaded: {}", view.url());
VERIFY_NOT_REACHED();
};
auto view = adopt_own(*new HeadlessWebView(move(theme), window_size));
view->initialize_client(CreateNewClient::Yes);
return view;
}
NonnullOwnPtr<HeadlessWebView> HeadlessWebView::create_child(HeadlessWebView const& parent, u64 page_index)
{
auto view = adopt_own(*new HeadlessWebView(parent.m_theme, parent.m_viewport_size));
view->m_client_state.client = parent.client();
view->m_client_state.page_index = page_index;
view->initialize_client(CreateNewClient::No);
return view;
}
void HeadlessWebView::initialize_client(CreateNewClient create_new_client)
{
if (create_new_client == CreateNewClient::Yes) {
auto request_server_socket = connect_new_request_server_client(Application::request_client()).release_value_but_fixme_should_propagate_errors();
auto image_decoder_socket = connect_new_image_decoder_client(Application::image_decoder_client()).release_value_but_fixme_should_propagate_errors();
auto web_content_paths = get_paths_for_helper_process("WebContent"sv).release_value_but_fixme_should_propagate_errors();
m_client_state.client = launch_web_content_process(*this, web_content_paths, move(image_decoder_socket), move(request_server_socket)).release_value_but_fixme_should_propagate_errors();
} else {
m_client_state.client->register_view(m_client_state.page_index, *this);
}
m_client_state.client_handle = MUST(Web::Crypto::generate_random_uuid());
client().async_set_window_handle(m_client_state.page_index, m_client_state.client_handle);
client().async_update_system_theme(m_client_state.page_index, m_theme);
client().async_set_viewport_size(m_client_state.page_index, viewport_size());
client().async_set_window_size(m_client_state.page_index, viewport_size());
if (Application::chrome_options().allow_popups == WebView::AllowPopups::Yes)
client().async_debug_request(m_client_state.page_index, "block-pop-ups"sv, "off"sv);
if (auto const& web_driver_ipc_path = Application::chrome_options().webdriver_content_ipc_path; web_driver_ipc_path.has_value())
client().async_connect_to_webdriver(m_client_state.page_index, *web_driver_ipc_path);
m_client_state.client->on_web_content_process_crash = [this] {
warnln("\033[31;1mWebContent Crashed!!\033[0m");
warnln(" Last page loaded: {}", url());
VERIFY_NOT_REACHED();
};
}
void HeadlessWebView::clear_content_filters()
{
client().async_set_content_filters(0, {});
client().async_set_content_filters(m_client_state.page_index, {});
}
NonnullRefPtr<Core::Promise<RefPtr<Gfx::Bitmap>>> HeadlessWebView::take_screenshot()

View file

@ -20,7 +20,8 @@ namespace Ladybird {
class HeadlessWebView final : public WebView::ViewImplementation {
public:
static ErrorOr<NonnullOwnPtr<HeadlessWebView>> create(Core::AnonymousBuffer theme, Gfx::IntSize window_size);
static NonnullOwnPtr<HeadlessWebView> create(Core::AnonymousBuffer theme, Gfx::IntSize window_size);
static NonnullOwnPtr<HeadlessWebView> create_child(HeadlessWebView const&, u64 page_index);
void clear_content_filters();
@ -30,10 +31,10 @@ public:
void on_test_complete(TestCompletion);
private:
explicit HeadlessWebView(Gfx::IntSize viewport_size);
HeadlessWebView(Core::AnonymousBuffer theme, Gfx::IntSize viewport_size);
void update_zoom() override { }
void initialize_client(CreateNewClient) override { }
void initialize_client(CreateNewClient) override;
virtual Web::DevicePixelSize viewport_size() const override { return m_viewport_size.to_type<Web::DevicePixels>(); }
virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const override { return widget_position; }
@ -41,7 +42,9 @@ private:
virtual void did_receive_screenshot(Badge<WebView::WebContentClient>, Gfx::ShareableBitmap const& screenshot) override;
Core::AnonymousBuffer m_theme;
Gfx::IntSize m_viewport_size;
RefPtr<Core::Promise<RefPtr<Gfx::Bitmap>>> m_pending_screenshot;
NonnullRefPtr<TestPromise> m_test_promise;

View file

@ -402,7 +402,7 @@ ErrorOr<void> run_tests(Core::AnonymousBuffer const& theme, Gfx::IntSize window_
size_t loaded_web_views = 0;
for (size_t i = 0; i < concurrency; ++i) {
auto& view = *TRY(app.create_web_view(theme, window_size));
auto& view = app.create_web_view(theme, window_size);
view.on_load_finish = [&](auto const&) { ++loaded_web_views; };
}

View file

@ -81,7 +81,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
return 0;
}
auto& view = *TRY(app->create_web_view(move(theme), window_size));
auto& view = app->create_web_view(move(theme), window_size);
VERIFY(!WebView::Application::chrome_options().urls.is_empty());
auto const& url = WebView::Application::chrome_options().urls.first();