浏览代码

headless-browser: Support creating child web views

This is used when a page calls window.open, and is relied upon heavily
by WPT.
Timothy Flynn 19 小时之前
父节点
当前提交
b73f9fef5a

+ 11 - 3
Ladybird/Headless/Application.cpp

@@ -68,12 +68,20 @@ ErrorOr<void> Application::launch_services()
     return {};
     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));
     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()
 void Application::destroy_web_views()

+ 2 - 1
Ladybird/Headless/Application.h

@@ -37,7 +37,8 @@ public:
     static Requests::RequestClient& request_client() { return *the().m_request_client; }
     static Requests::RequestClient& request_client() { return *the().m_request_client; }
     static ImageDecoderClient::Client& image_decoder_client() { return *the().m_image_decoder_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();
     void destroy_web_views();
 
 
     template<typename Callback>
     template<typename Callback>

+ 54 - 20
Ladybird/Headless/HeadlessWebView.cpp

@@ -10,13 +10,25 @@
 #include <Ladybird/Utilities.h>
 #include <Ladybird/Utilities.h>
 #include <LibGfx/Bitmap.h>
 #include <LibGfx/Bitmap.h>
 #include <LibGfx/ShareableBitmap.h>
 #include <LibGfx/ShareableBitmap.h>
+#include <LibWeb/Crypto/Crypto.h>
 
 
 namespace Ladybird {
 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())
     , 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 = []() {
     on_request_worker_agent = []() {
         auto web_worker_paths = MUST(get_paths_for_helper_process("WebWorker"sv));
         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()));
         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 view = adopt_own(*new HeadlessWebView(move(theme), window_size));
+    view->initialize_client(CreateNewClient::Yes);
 
 
-    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()));
+    return view;
+}
 
 
-    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)));
+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->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());
+    view->m_client_state.client = parent.client();
+    view->m_client_state.page_index = page_index;
+    view->initialize_client(CreateNewClient::No);
 
 
-    if (WebView::Application::chrome_options().allow_popups == WebView::AllowPopups::Yes)
-        view->client().async_debug_request(0, "block-pop-ups"sv, "off"sv);
+    return view;
+}
 
 
-    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);
+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();
 
 
-    view->m_client_state.client->on_web_content_process_crash = [&view = *view] {
+        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("\033[31;1mWebContent Crashed!!\033[0m");
-        warnln("    Last page loaded: {}", view.url());
+        warnln("    Last page loaded: {}", url());
         VERIFY_NOT_REACHED();
         VERIFY_NOT_REACHED();
     };
     };
-
-    return view;
 }
 }
 
 
 void HeadlessWebView::clear_content_filters()
 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()
 NonnullRefPtr<Core::Promise<RefPtr<Gfx::Bitmap>>> HeadlessWebView::take_screenshot()

+ 6 - 3
Ladybird/Headless/HeadlessWebView.h

@@ -20,7 +20,8 @@ namespace Ladybird {
 
 
 class HeadlessWebView final : public WebView::ViewImplementation {
 class HeadlessWebView final : public WebView::ViewImplementation {
 public:
 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();
     void clear_content_filters();
 
 
@@ -30,10 +31,10 @@ public:
     void on_test_complete(TestCompletion);
     void on_test_complete(TestCompletion);
 
 
 private:
 private:
-    explicit HeadlessWebView(Gfx::IntSize viewport_size);
+    HeadlessWebView(Core::AnonymousBuffer theme, Gfx::IntSize viewport_size);
 
 
     void update_zoom() override { }
     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 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; }
     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;
     virtual void did_receive_screenshot(Badge<WebView::WebContentClient>, Gfx::ShareableBitmap const& screenshot) override;
 
 
+    Core::AnonymousBuffer m_theme;
     Gfx::IntSize m_viewport_size;
     Gfx::IntSize m_viewport_size;
+
     RefPtr<Core::Promise<RefPtr<Gfx::Bitmap>>> m_pending_screenshot;
     RefPtr<Core::Promise<RefPtr<Gfx::Bitmap>>> m_pending_screenshot;
 
 
     NonnullRefPtr<TestPromise> m_test_promise;
     NonnullRefPtr<TestPromise> m_test_promise;

+ 1 - 1
Ladybird/Headless/Test.cpp

@@ -402,7 +402,7 @@ ErrorOr<void> run_tests(Core::AnonymousBuffer const& theme, Gfx::IntSize window_
     size_t loaded_web_views = 0;
     size_t loaded_web_views = 0;
 
 
     for (size_t i = 0; i < concurrency; ++i) {
     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.on_load_finish = [&](auto const&) { ++loaded_web_views; };
     }
     }
 
 

+ 1 - 1
Ladybird/Headless/main.cpp

@@ -81,7 +81,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
         return 0;
         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());
     VERIFY(!WebView::Application::chrome_options().urls.is_empty());
     auto const& url = WebView::Application::chrome_options().urls.first();
     auto const& url = WebView::Application::chrome_options().urls.first();