From 5f8d852daeae9129d2873b4fce306be3f0599a06 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 30 Jul 2024 14:01:05 -0400 Subject: [PATCH] LibWebView+UI: Migrate Ladybird's command line flags to LibWebView Currently, if we want to add a new e.g. WebContent command line option, we have to add it to all of Qt, AppKit, and headless-browser. (Or worse, we only add it to one of these, and we have feature disparity). To prevent this, this moves command line flags to WebView::Application. The flags are assigned to ChromeOptions and WebContentOptions structs. Each chrome can still add its platform-specific options; for example, the Qt chrome has a flag to enable Qt networking. There should be no behavior change here, other than that AppKit will now support command line flags that were previously only supported by Qt. --- Ladybird/AppKit/Application/Application.h | 10 +- Ladybird/AppKit/Application/Application.mm | 22 +-- .../AppKit/Application/ApplicationBridge.cpp | 9 +- .../AppKit/Application/ApplicationBridge.h | 9 +- .../AppKit/Application/ApplicationDelegate.h | 13 +- .../AppKit/Application/ApplicationDelegate.mm | 45 +---- Ladybird/AppKit/UI/LadybirdWebView.mm | 2 +- Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp | 16 +- Ladybird/AppKit/UI/LadybirdWebViewBridge.h | 12 +- Ladybird/AppKit/UI/TabController.h | 4 +- Ladybird/AppKit/UI/TabController.mm | 7 +- Ladybird/AppKit/main.mm | 97 +++-------- Ladybird/CMakeLists.txt | 1 - Ladybird/HelperProcess.cpp | 37 +++-- Ladybird/HelperProcess.h | 4 +- Ladybird/Qt/Application.cpp | 31 +++- Ladybird/Qt/Application.h | 16 +- Ladybird/Qt/BrowserWindow.cpp | 26 ++- Ladybird/Qt/BrowserWindow.h | 7 +- Ladybird/Qt/InspectorWidget.cpp | 2 +- Ladybird/Qt/Tab.cpp | 4 +- Ladybird/Qt/Tab.h | 2 +- Ladybird/Qt/TaskManagerWindow.cpp | 4 +- Ladybird/Qt/TaskManagerWindow.h | 2 +- Ladybird/Qt/WebContentView.cpp | 15 +- Ladybird/Qt/WebContentView.h | 8 +- Ladybird/Qt/main.cpp | 105 +++--------- Userland/Libraries/LibWebView/Application.cpp | 69 +++++++- Userland/Libraries/LibWebView/Application.h | 38 ++++- .../Libraries/LibWebView/ChromeProcess.cpp | 12 +- Userland/Libraries/LibWebView/ChromeProcess.h | 13 +- .../Libraries/LibWebView/Options.h | 54 +++++- Userland/Libraries/LibWebView/URL.cpp | 16 ++ Userland/Libraries/LibWebView/URL.h | 1 + Userland/Utilities/headless-browser.cpp | 154 +++++++++--------- 35 files changed, 427 insertions(+), 440 deletions(-) rename Ladybird/Types.h => Userland/Libraries/LibWebView/Options.h (59%) diff --git a/Ladybird/AppKit/Application/Application.h b/Ladybird/AppKit/Application/Application.h index 1ce7a446653..7e81c5c812a 100644 --- a/Ladybird/AppKit/Application/Application.h +++ b/Ladybird/AppKit/Application/Application.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,8 +7,9 @@ #pragma once #include -#include #include +#include +#include #include #import @@ -19,9 +20,10 @@ class WebViewBridge; @interface Application : NSApplication -- (instancetype)init; +- (void)setupWebViewApplication:(Main::Arguments&)arguments + newTabPageURL:(URL::URL)new_tab_page_url; -- (ErrorOr)launchRequestServer:(Vector const&)certificates; +- (ErrorOr)launchRequestServer; - (ErrorOr)launchImageDecoder; - (ErrorOr>)launchWebContent:(Ladybird::WebViewBridge&)web_view_bridge; - (ErrorOr)launchWebWorker; diff --git a/Ladybird/AppKit/Application/Application.mm b/Ladybird/AppKit/Application/Application.mm index 6a0d378daa3..dfcfbda8429 100644 --- a/Ladybird/AppKit/Application/Application.mm +++ b/Ladybird/AppKit/Application/Application.mm @@ -1,10 +1,9 @@ /* - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include #include @@ -25,20 +24,17 @@ @implementation Application -- (instancetype)init -{ - if (self = [super init]) { - m_application_bridge = make(); - } - - return self; -} - #pragma mark - Public methods -- (ErrorOr)launchRequestServer:(Vector const&)certificates +- (void)setupWebViewApplication:(Main::Arguments&)arguments + newTabPageURL:(URL::URL)new_tab_page_url { - return m_application_bridge->launch_request_server(certificates); + m_application_bridge = Ladybird::ApplicationBridge::create(arguments, move(new_tab_page_url)); +} + +- (ErrorOr)launchRequestServer +{ + return m_application_bridge->launch_request_server(); } - (ErrorOr)launchImageDecoder diff --git a/Ladybird/AppKit/Application/ApplicationBridge.cpp b/Ladybird/AppKit/Application/ApplicationBridge.cpp index 9bb2fb10fe2..73a2910d829 100644 --- a/Ladybird/AppKit/Application/ApplicationBridge.cpp +++ b/Ladybird/AppKit/Application/ApplicationBridge.cpp @@ -4,7 +4,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include #include @@ -23,17 +22,17 @@ struct ApplicationBridgeImpl { RefPtr image_decoder_client; }; -ApplicationBridge::ApplicationBridge() +ApplicationBridge::ApplicationBridge(Badge, Main::Arguments&) : m_impl(make()) { } ApplicationBridge::~ApplicationBridge() = default; -ErrorOr ApplicationBridge::launch_request_server(Vector const& certificates) +ErrorOr ApplicationBridge::launch_request_server() { auto request_server_paths = TRY(get_paths_for_helper_process("RequestServer"sv)); - auto protocol_client = TRY(launch_request_server_process(request_server_paths, s_ladybird_resource_root, certificates)); + auto protocol_client = TRY(launch_request_server_process(request_server_paths, s_ladybird_resource_root)); m_impl->request_server_client = move(protocol_client); return {}; @@ -79,7 +78,7 @@ ErrorOr> ApplicationBridge::launch_web_ auto image_decoder_socket = TRY(connect_new_image_decoder_client(*m_impl->image_decoder_client)); auto web_content_paths = TRY(get_paths_for_helper_process("WebContent"sv)); - auto web_content = TRY(launch_web_content_process(web_view_bridge, web_content_paths, web_view_bridge.web_content_options(), move(image_decoder_socket), move(request_server_socket))); + auto web_content = TRY(launch_web_content_process(web_view_bridge, web_content_paths, move(image_decoder_socket), move(request_server_socket))); return web_content; } diff --git a/Ladybird/AppKit/Application/ApplicationBridge.h b/Ladybird/AppKit/Application/ApplicationBridge.h index ae83e4e8100..b56ad72ffdd 100644 --- a/Ladybird/AppKit/Application/ApplicationBridge.h +++ b/Ladybird/AppKit/Application/ApplicationBridge.h @@ -7,8 +7,8 @@ #pragma once #include -#include #include +#include #include namespace Ladybird { @@ -16,12 +16,13 @@ namespace Ladybird { struct ApplicationBridgeImpl; class WebViewBridge; -class ApplicationBridge { +class ApplicationBridge : public WebView::Application { + WEB_VIEW_APPLICATION(ApplicationBridge) + public: - ApplicationBridge(); ~ApplicationBridge(); - ErrorOr launch_request_server(Vector const& certificates); + ErrorOr launch_request_server(); ErrorOr launch_image_decoder(); ErrorOr> launch_web_content(WebViewBridge&); ErrorOr launch_web_worker(); diff --git a/Ladybird/AppKit/Application/ApplicationDelegate.h b/Ladybird/AppKit/Application/ApplicationDelegate.h index 961ed65a017..b2d304344bc 100644 --- a/Ladybird/AppKit/Application/ApplicationDelegate.h +++ b/Ladybird/AppKit/Application/ApplicationDelegate.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,8 +8,6 @@ #include #include -#include -#include #include #include #include @@ -24,12 +22,7 @@ @interface ApplicationDelegate : NSObject -- (nullable instancetype)init:(Vector)initial_urls - newTabPageURL:(URL::URL)new_tab_page_url - withCookieJar:(NonnullOwnPtr)cookie_jar - webContentOptions:(Ladybird::WebContentOptions const&)web_content_options - webdriverContentIPCPath:(StringView)webdriver_content_ipc_path - allowPopups:(BOOL)allow_popups; +- (nullable instancetype)initWithCookieJar:(NonnullOwnPtr)cookie_jar; - (nonnull TabController*)createNewTab:(Optional const&)url fromTab:(nullable Tab*)tab @@ -46,8 +39,6 @@ - (void)removeTab:(nonnull TabController*)controller; - (WebView::CookieJar&)cookieJar; -- (Ladybird::WebContentOptions const&)webContentOptions; -- (Optional const&)webdriverContentIPCPath; - (Web::CSS::PreferredColorScheme)preferredColorScheme; - (Web::CSS::PreferredContrast)preferredContrast; - (Web::CSS::PreferredMotion)preferredMotion; diff --git a/Ladybird/AppKit/Application/ApplicationDelegate.mm b/Ladybird/AppKit/Application/ApplicationDelegate.mm index 14d94e98c5d..76cce90ed18 100644 --- a/Ladybird/AppKit/Application/ApplicationDelegate.mm +++ b/Ladybird/AppKit/Application/ApplicationDelegate.mm @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #import @@ -29,23 +30,15 @@ @interface ApplicationDelegate () { - Vector m_initial_urls; - URL::URL m_new_tab_page_url; - // This will always be populated, but we cannot have a non-default constructible instance variable. OwnPtr m_cookie_jar; - Ladybird::WebContentOptions m_web_content_options; - Optional m_webdriver_content_ipc_path; - Web::CSS::PreferredColorScheme m_preferred_color_scheme; Web::CSS::PreferredContrast m_preferred_contrast; Web::CSS::PreferredMotion m_preferred_motion; ByteString m_navigator_compatibility_mode; WebView::SearchEngine m_search_engine; - - BOOL m_allow_popups; } @property (nonatomic, strong) NSMutableArray* managed_tabs; @@ -68,12 +61,7 @@ @implementation ApplicationDelegate -- (instancetype)init:(Vector)initial_urls - newTabPageURL:(URL::URL)new_tab_page_url - withCookieJar:(NonnullOwnPtr)cookie_jar - webContentOptions:(Ladybird::WebContentOptions const&)web_content_options - webdriverContentIPCPath:(StringView)webdriver_content_ipc_path - allowPopups:(BOOL)allow_popups +- (instancetype)initWithCookieJar:(NonnullOwnPtr)cookie_jar { if (self = [super init]) { [NSApp setMainMenu:[[NSMenu alloc] init]]; @@ -91,25 +79,14 @@ self.managed_tabs = [[NSMutableArray alloc] init]; - m_initial_urls = move(initial_urls); - m_new_tab_page_url = move(new_tab_page_url); - m_cookie_jar = move(cookie_jar); - m_web_content_options = web_content_options; - - if (!webdriver_content_ipc_path.is_empty()) { - m_webdriver_content_ipc_path = webdriver_content_ipc_path; - } - m_preferred_color_scheme = Web::CSS::PreferredColorScheme::Auto; m_preferred_contrast = Web::CSS::PreferredContrast::Auto; m_preferred_motion = Web::CSS::PreferredMotion::Auto; m_navigator_compatibility_mode = "chrome"; m_search_engine = WebView::default_search_engine(); - m_allow_popups = allow_popups; - // Reduce the tooltip delay, as the default delay feels quite long. [[NSUserDefaults standardUserDefaults] setObject:@100 forKey:@"NSInitialToolTipDelay"]; } @@ -124,7 +101,7 @@ activateTab:(Web::HTML::ActivateTab)activate_tab { auto* controller = [self createNewTab:activate_tab fromTab:tab]; - [controller loadURL:url.value_or(m_new_tab_page_url)]; + [controller loadURL:url.value_or(WebView::Application::chrome_options().new_tab_page_url)]; return controller; } @@ -166,16 +143,6 @@ return *m_cookie_jar; } -- (Ladybird::WebContentOptions const&)webContentOptions -{ - return m_web_content_options; -} - -- (Optional const&)webdriverContentIPCPath -{ - return m_webdriver_content_ipc_path; -} - - (Web::CSS::PreferredColorScheme)preferredColorScheme { return m_preferred_color_scheme; @@ -213,7 +180,7 @@ - (nonnull TabController*)createNewTab:(Web::HTML::ActivateTab)activate_tab fromTab:(nullable Tab*)tab { - auto* controller = [[TabController alloc] init:!m_allow_popups]; + auto* controller = [[TabController alloc] init]; [controller showWindow:nil]; if (tab) { @@ -740,7 +707,7 @@ { Tab* tab = nil; - for (auto const& url : m_initial_urls) { + for (auto const& url : WebView::Application::chrome_options().urls) { auto activate_tab = tab == nil ? Web::HTML::ActivateTab::Yes : Web::HTML::ActivateTab::No; auto* controller = [self createNewTab:url @@ -749,8 +716,6 @@ tab = (Tab*)[controller window]; } - - m_initial_urls.clear(); } - (void)applicationWillTerminate:(NSNotification*)notification diff --git a/Ladybird/AppKit/UI/LadybirdWebView.mm b/Ladybird/AppKit/UI/LadybirdWebView.mm index ae648d4300b..da617cc03fe 100644 --- a/Ladybird/AppKit/UI/LadybirdWebView.mm +++ b/Ladybird/AppKit/UI/LadybirdWebView.mm @@ -104,7 +104,7 @@ struct HideCursor { // This returns device pixel ratio of the screen the window is opened in auto device_pixel_ratio = [[NSScreen mainScreen] backingScaleFactor]; - m_web_view_bridge = MUST(Ladybird::WebViewBridge::create(move(screen_rects), device_pixel_ratio, [delegate webContentOptions], [delegate webdriverContentIPCPath], [delegate preferredColorScheme], [delegate preferredContrast], [delegate preferredMotion])); + m_web_view_bridge = MUST(Ladybird::WebViewBridge::create(move(screen_rects), device_pixel_ratio, [delegate preferredColorScheme], [delegate preferredContrast], [delegate preferredMotion])); [self setWebViewCallbacks]; m_web_view_bridge->initialize_client(); diff --git a/Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp b/Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp index 5f7f874df0f..bd7f75b9b0c 100644 --- a/Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp +++ b/Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp @@ -1,16 +1,16 @@ /* - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include -#include #include #include #include #include #include +#include #include #import @@ -23,15 +23,13 @@ static T scale_for_device(T size, float device_pixel_ratio) return size.template to_type().scaled(device_pixel_ratio).template to_type(); } -ErrorOr> WebViewBridge::create(Vector screen_rects, float device_pixel_ratio, WebContentOptions const& web_content_options, Optional webdriver_content_ipc_path, Web::CSS::PreferredColorScheme preferred_color_scheme, Web::CSS::PreferredContrast preferred_contrast, Web::CSS::PreferredMotion preferred_motion) +ErrorOr> WebViewBridge::create(Vector screen_rects, float device_pixel_ratio, Web::CSS::PreferredColorScheme preferred_color_scheme, Web::CSS::PreferredContrast preferred_contrast, Web::CSS::PreferredMotion preferred_motion) { - return adopt_nonnull_own_or_enomem(new (nothrow) WebViewBridge(move(screen_rects), device_pixel_ratio, web_content_options, move(webdriver_content_ipc_path), preferred_color_scheme, preferred_contrast, preferred_motion)); + return adopt_nonnull_own_or_enomem(new (nothrow) WebViewBridge(move(screen_rects), device_pixel_ratio, preferred_color_scheme, preferred_contrast, preferred_motion)); } -WebViewBridge::WebViewBridge(Vector screen_rects, float device_pixel_ratio, WebContentOptions const& web_content_options, Optional webdriver_content_ipc_path, Web::CSS::PreferredColorScheme preferred_color_scheme, Web::CSS::PreferredContrast preferred_contrast, Web::CSS::PreferredMotion preferred_motion) +WebViewBridge::WebViewBridge(Vector screen_rects, float device_pixel_ratio, Web::CSS::PreferredColorScheme preferred_color_scheme, Web::CSS::PreferredContrast preferred_contrast, Web::CSS::PreferredMotion preferred_motion) : m_screen_rects(move(screen_rects)) - , m_web_content_options(web_content_options) - , m_webdriver_content_ipc_path(move(webdriver_content_ipc_path)) , m_preferred_color_scheme(preferred_color_scheme) , m_preferred_contrast(preferred_contrast) , m_preferred_motion(preferred_motion) @@ -168,8 +166,8 @@ void WebViewBridge::initialize_client(CreateNewClient) client().async_update_screen_rects(m_client_state.page_index, m_screen_rects, 0); } - if (m_webdriver_content_ipc_path.has_value()) { - client().async_connect_to_webdriver(m_client_state.page_index, *m_webdriver_content_ipc_path); + if (auto const& webdriver_content_ipc_path = WebView::Application::chrome_options().webdriver_content_ipc_path; webdriver_content_ipc_path.has_value()) { + client().async_connect_to_webdriver(m_client_state.page_index, *webdriver_content_ipc_path); } } diff --git a/Ladybird/AppKit/UI/LadybirdWebViewBridge.h b/Ladybird/AppKit/UI/LadybirdWebViewBridge.h index fc33fb2eef7..6a383ff37d0 100644 --- a/Ladybird/AppKit/UI/LadybirdWebViewBridge.h +++ b/Ladybird/AppKit/UI/LadybirdWebViewBridge.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,7 +7,6 @@ #pragma once #include -#include #include #include #include @@ -22,13 +21,11 @@ namespace Ladybird { class WebViewBridge final : public WebView::ViewImplementation { public: - static ErrorOr> create(Vector screen_rects, float device_pixel_ratio, WebContentOptions const&, Optional webdriver_content_ipc_path, Web::CSS::PreferredColorScheme, Web::CSS::PreferredContrast, Web::CSS::PreferredMotion); + static ErrorOr> create(Vector screen_rects, float device_pixel_ratio, Web::CSS::PreferredColorScheme, Web::CSS::PreferredContrast, Web::CSS::PreferredMotion); virtual ~WebViewBridge() override; virtual void initialize_client(CreateNewClient = CreateNewClient::Yes) override; - WebContentOptions const& web_content_options() const { return m_web_content_options; } - float device_pixel_ratio() const { return m_device_pixel_ratio; } void set_device_pixel_ratio(float device_pixel_ratio); float inverse_device_pixel_ratio() const { return 1.0f / m_device_pixel_ratio; } @@ -59,7 +56,7 @@ public: Function on_zoom_level_changed; private: - WebViewBridge(Vector screen_rects, float device_pixel_ratio, WebContentOptions const&, Optional webdriver_content_ipc_path, Web::CSS::PreferredColorScheme, Web::CSS::PreferredContrast, Web::CSS::PreferredMotion); + WebViewBridge(Vector screen_rects, float device_pixel_ratio, Web::CSS::PreferredColorScheme, Web::CSS::PreferredContrast, Web::CSS::PreferredMotion); virtual void update_zoom() override; virtual Web::DevicePixelSize viewport_size() const override; @@ -69,9 +66,6 @@ private: Vector m_screen_rects; Gfx::IntSize m_viewport_size; - WebContentOptions m_web_content_options; - Optional m_webdriver_content_ipc_path; - Web::CSS::PreferredColorScheme m_preferred_color_scheme { Web::CSS::PreferredColorScheme::Auto }; Web::CSS::PreferredContrast m_preferred_contrast { Web::CSS::PreferredContrast::Auto }; Web::CSS::PreferredMotion m_preferred_motion { Web::CSS::PreferredMotion::Auto }; diff --git a/Ladybird/AppKit/UI/TabController.h b/Ladybird/AppKit/UI/TabController.h index ee810c0ae68..4c4602f5c25 100644 --- a/Ladybird/AppKit/UI/TabController.h +++ b/Ladybird/AppKit/UI/TabController.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -22,7 +22,7 @@ struct TabSettings { @interface TabController : NSWindowController -- (instancetype)init:(BOOL)block_popups; +- (instancetype)init; - (void)loadURL:(URL::URL const&)url; - (void)loadHTML:(StringView)html url:(URL::URL const&)url; diff --git a/Ladybird/AppKit/UI/TabController.mm b/Ladybird/AppKit/UI/TabController.mm index 19140f8ce9b..11e20a52a29 100644 --- a/Ladybird/AppKit/UI/TabController.mm +++ b/Ladybird/AppKit/UI/TabController.mm @@ -1,10 +1,11 @@ /* - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include +#include #include #include #include @@ -82,7 +83,7 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde @synthesize new_tab_toolbar_item = _new_tab_toolbar_item; @synthesize tab_overview_toolbar_item = _tab_overview_toolbar_item; -- (instancetype)init:(BOOL)block_popups +- (instancetype)init { if (self = [super init]) { self.toolbar = [[NSToolbar alloc] initWithIdentifier:TOOLBAR_IDENTIFIER]; @@ -91,7 +92,7 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde [self.toolbar setAllowsUserCustomization:NO]; [self.toolbar setSizeMode:NSToolbarSizeModeRegular]; - m_settings = { .block_popups = block_popups }; + m_settings = { .block_popups = WebView::Application::chrome_options().allow_popups == WebView::AllowPopups::Yes ? NO : YES }; m_can_navigate_back = false; m_can_navigate_forward = false; } diff --git a/Ladybird/AppKit/main.mm b/Ladybird/AppKit/main.mm index 6e50f0fcf6f..b2501db86eb 100644 --- a/Ladybird/AppKit/main.mm +++ b/Ladybird/AppKit/main.mm @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,9 +7,7 @@ #include #include #include -#include #include -#include #include #include #include @@ -30,33 +28,10 @@ # error "This project requires ARC" #endif -static Vector sanitize_urls(Vector const& raw_urls) -{ - Vector sanitized_urls; - for (auto const& raw_url : raw_urls) { - if (auto url = WebView::sanitize_url(raw_url); url.has_value()) - sanitized_urls.append(url.release_value()); - } - - if (sanitized_urls.is_empty()) { - URL::URL new_tab_page_url = Browser::default_new_tab_url; - sanitized_urls.append(move(new_tab_page_url)); - } - - return sanitized_urls; -} - -enum class NewWindow { - No, - Yes, -}; - -static void open_urls_from_client(Vector const& raw_urls, NewWindow new_window) +static void open_urls_from_client(Vector const& urls, WebView::NewWindow new_window) { ApplicationDelegate* delegate = [NSApp delegate]; - Tab* tab = new_window == NewWindow::Yes ? nil : [delegate activeTab]; - - auto urls = sanitize_urls(raw_urls); + Tab* tab = new_window == WebView::NewWindow::Yes ? nil : [delegate activeTab]; for (auto [i, url] : enumerate(urls)) { auto activate_tab = i == 0 ? Web::HTML::ActivateTab::Yes : Web::HTML::ActivateTab::No; @@ -76,51 +51,33 @@ ErrorOr serenity_main(Main::Arguments arguments) Application* application = [Application sharedApplication]; Core::EventLoopManager::install(*new Ladybird::CFEventLoopManager); - WebView::Application web_view_app(arguments.argc, arguments.argv); + [application setupWebViewApplication:arguments newTabPageURL:Browser::default_new_tab_url]; platform_init(); - Vector raw_urls; - Vector certificates; - StringView webdriver_content_ipc_path; - bool debug_web_content = false; - bool log_all_js_exceptions = false; - bool enable_http_cache = false; - bool new_window = false; - bool force_new_process = false; - bool allow_popups = false; - - Core::ArgsParser args_parser; - args_parser.set_general_help("The Ladybird web browser"); - args_parser.add_positional_argument(raw_urls, "URLs to open", "url", Core::ArgsParser::Required::No); - args_parser.add_option(webdriver_content_ipc_path, "Path to WebDriver IPC for WebContent", "webdriver-content-path", 0, "path", Core::ArgsParser::OptionHideMode::CommandLineAndMarkdown); - args_parser.add_option(debug_web_content, "Wait for debugger to attach to WebContent", "debug-web-content"); - args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate"); - args_parser.add_option(log_all_js_exceptions, "Log all JavaScript exceptions", "log-all-js-exceptions"); - args_parser.add_option(enable_http_cache, "Enable HTTP cache", "enable-http-cache"); - args_parser.add_option(new_window, "Force opening in a new window", "new-window", 'n'); - args_parser.add_option(force_new_process, "Force creation of new browser/chrome process", "force-new-process"); - args_parser.add_option(allow_popups, "Disable popup blocking by default", "allow-popups"); - args_parser.parse(arguments); - auto chrome_process = TRY(WebView::ChromeProcess::create()); - if (!force_new_process && TRY(chrome_process.connect(raw_urls, new_window)) == WebView::ChromeProcess::ProcessDisposition::ExitProcess) { - outln("Opening in existing process"); - return 0; + + if (auto const& chrome_options = WebView::Application::chrome_options(); chrome_options.force_new_process == WebView::ForceNewProcess::No) { + auto disposition = TRY(chrome_process.connect(chrome_options.raw_urls, chrome_options.new_window)); + + if (disposition == WebView::ChromeProcess::ProcessDisposition::ExitProcess) { + outln("Opening in existing process"); + return 0; + } } chrome_process.on_new_tab = [&](auto const& raw_urls) { - open_urls_from_client(raw_urls, NewWindow::No); + open_urls_from_client(raw_urls, WebView::NewWindow::No); }; chrome_process.on_new_window = [&](auto const& raw_urls) { - open_urls_from_client(raw_urls, NewWindow::Yes); + open_urls_from_client(raw_urls, WebView::NewWindow::Yes); }; auto mach_port_server = make(); set_mach_server_name(mach_port_server->server_port_name()); - mach_port_server->on_receive_child_mach_port = [&web_view_app](auto pid, auto port) { - web_view_app.set_process_mach_port(pid, move(port)); + mach_port_server->on_receive_child_mach_port = [&](auto pid, auto port) { + WebView::Application::the().set_process_mach_port(pid, move(port)); }; mach_port_server->on_receive_backing_stores = [](Ladybird::MachPortServer::BackingStoresMessage message) { if (auto view = WebView::WebContentClient::view_for_pid_and_page_id(message.pid, message.page_id); view.has_value()) @@ -131,28 +88,12 @@ ErrorOr serenity_main(Main::Arguments arguments) auto cookie_jar = TRY(WebView::CookieJar::create(*database)); // FIXME: Create an abstraction to re-spawn the RequestServer and re-hook up its client hooks to each tab on crash - TRY([application launchRequestServer:certificates]); + TRY([application launchRequestServer]); TRY([application launchImageDecoder]); - StringBuilder command_line_builder; - command_line_builder.join(' ', arguments.strings); - Ladybird::WebContentOptions web_content_options { - .command_line = MUST(command_line_builder.to_string()), - .executable_path = MUST(String::from_byte_string(MUST(Core::System::current_executable_path()))), - .wait_for_debugger = debug_web_content ? Ladybird::WaitForDebugger::Yes : Ladybird::WaitForDebugger::No, - .log_all_js_exceptions = log_all_js_exceptions ? Ladybird::LogAllJSExceptions::Yes : Ladybird::LogAllJSExceptions::No, - .enable_http_cache = enable_http_cache ? Ladybird::EnableHTTPCache::Yes : Ladybird::EnableHTTPCache::No, - }; - - auto* delegate = [[ApplicationDelegate alloc] init:sanitize_urls(raw_urls) - newTabPageURL:URL::URL { Browser::default_new_tab_url } - withCookieJar:move(cookie_jar) - webContentOptions:web_content_options - webdriverContentIPCPath:webdriver_content_ipc_path - allowPopups:allow_popups]; - + auto* delegate = [[ApplicationDelegate alloc] initWithCookieJar:move(cookie_jar)]; [NSApp setDelegate:delegate]; - return web_view_app.exec(); + return WebView::Application::the().execute(); } diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index 36f7c37f515..d46e7ae59c8 100644 --- a/Ladybird/CMakeLists.txt +++ b/Ladybird/CMakeLists.txt @@ -6,7 +6,6 @@ set(LADYBIRD_SOURCES ) set(LADYBIRD_HEADERS HelperProcess.h - Types.h Utilities.h ) diff --git a/Ladybird/HelperProcess.cpp b/Ladybird/HelperProcess.cpp index ed499d461df..212c961ea2d 100644 --- a/Ladybird/HelperProcess.cpp +++ b/Ladybird/HelperProcess.cpp @@ -15,10 +15,10 @@ static ErrorOr> launch_server_process( StringView server_name, ReadonlySpan candidate_server_paths, Vector arguments, - Ladybird::EnableCallgrindProfiling enable_callgrind_profiling, + WebView::EnableCallgrindProfiling enable_callgrind_profiling, ClientArguments&&... client_arguments) { - if (enable_callgrind_profiling == Ladybird::EnableCallgrindProfiling::Yes) { + if (enable_callgrind_profiling == WebView::EnableCallgrindProfiling::Yes) { arguments.prepend({ "--tool=callgrind"sv, "--instr-atstart=no"sv, @@ -29,7 +29,7 @@ static ErrorOr> launch_server_process( for (auto [i, path] : enumerate(candidate_server_paths)) { Core::ProcessSpawnOptions options { .name = server_name, .arguments = arguments }; - if (enable_callgrind_profiling == Ladybird::EnableCallgrindProfiling::Yes) { + if (enable_callgrind_profiling == WebView::EnableCallgrindProfiling::Yes) { options.executable = "valgrind"sv; options.search_for_executable_in_path = true; arguments[2] = path; @@ -47,7 +47,7 @@ static ErrorOr> launch_server_process( WebView::Application::the().add_child_process(WebView::Process { WebView::process_type_from_name(server_name), process.client, move(process.process) }); - if (enable_callgrind_profiling == Ladybird::EnableCallgrindProfiling::Yes) { + if (enable_callgrind_profiling == WebView::EnableCallgrindProfiling::Yes) { dbgln(); dbgln("\033[1;45mLaunched {} process under callgrind!\033[0m", server_name); dbgln("\033[100mRun `\033[4mcallgrind_control -i on\033[24m` to start instrumentation and `\033[4mcallgrind_control -i off\033[24m` stop it again.\033[0m"); @@ -69,10 +69,11 @@ static ErrorOr> launch_server_process( ErrorOr> launch_web_content_process( WebView::ViewImplementation& view, ReadonlySpan candidate_web_content_paths, - Ladybird::WebContentOptions const& web_content_options, IPC::File image_decoder_socket, Optional request_server_socket) { + auto const& web_content_options = WebView::Application::web_content_options(); + Vector arguments { "--command-line"sv, web_content_options.command_line.to_byte_string(), @@ -84,19 +85,19 @@ ErrorOr> launch_web_content_process( arguments.append("--config-path"sv); arguments.append(web_content_options.config_path.value()); } - if (web_content_options.is_layout_test_mode == Ladybird::IsLayoutTestMode::Yes) + if (web_content_options.is_layout_test_mode == WebView::IsLayoutTestMode::Yes) arguments.append("--layout-test-mode"sv); - if (web_content_options.use_lagom_networking == Ladybird::UseLagomNetworking::Yes) + if (web_content_options.use_lagom_networking == WebView::UseLagomNetworking::Yes) arguments.append("--use-lagom-networking"sv); - if (web_content_options.wait_for_debugger == Ladybird::WaitForDebugger::Yes) + if (web_content_options.wait_for_debugger == WebView::WaitForDebugger::Yes) arguments.append("--wait-for-debugger"sv); - if (web_content_options.log_all_js_exceptions == Ladybird::LogAllJSExceptions::Yes) + if (web_content_options.log_all_js_exceptions == WebView::LogAllJSExceptions::Yes) arguments.append("--log-all-js-exceptions"sv); - if (web_content_options.enable_idl_tracing == Ladybird::EnableIDLTracing::Yes) + if (web_content_options.enable_idl_tracing == WebView::EnableIDLTracing::Yes) arguments.append("--enable-idl-tracing"sv); - if (web_content_options.enable_http_cache == Ladybird::EnableHTTPCache::Yes) + if (web_content_options.enable_http_cache == WebView::EnableHTTPCache::Yes) arguments.append("--enable-http-cache"sv); - if (web_content_options.expose_internals_object == Ladybird::ExposeInternalsObject::Yes) + if (web_content_options.expose_internals_object == WebView::ExposeInternalsObject::Yes) arguments.append("--expose-internals-object"sv); if (auto server = mach_server_name(); server.has_value()) { arguments.append("--mach-server-name"sv); @@ -121,7 +122,7 @@ ErrorOr> launch_image_decoder_process( arguments.append(server.value()); } - return launch_server_process("ImageDecoder"sv, candidate_image_decoder_paths, arguments, Ladybird::EnableCallgrindProfiling::No); + return launch_server_process("ImageDecoder"sv, candidate_image_decoder_paths, arguments, WebView::EnableCallgrindProfiling::No); } ErrorOr> launch_web_worker_process(ReadonlySpan candidate_web_worker_paths, RefPtr request_client) @@ -132,13 +133,13 @@ ErrorOr> launch_web_worker_process(Rea arguments.append("--request-server-socket"sv); arguments.append(ByteString::number(socket.fd())); arguments.append("--use-lagom-networking"sv); - return launch_server_process("WebWorker"sv, candidate_web_worker_paths, move(arguments), Ladybird::EnableCallgrindProfiling::No); + return launch_server_process("WebWorker"sv, candidate_web_worker_paths, move(arguments), WebView::EnableCallgrindProfiling::No); } - return launch_server_process("WebWorker"sv, candidate_web_worker_paths, move(arguments), Ladybird::EnableCallgrindProfiling::No); + return launch_server_process("WebWorker"sv, candidate_web_worker_paths, move(arguments), WebView::EnableCallgrindProfiling::No); } -ErrorOr> launch_request_server_process(ReadonlySpan candidate_request_server_paths, StringView serenity_resource_root, Vector const& certificates) +ErrorOr> launch_request_server_process(ReadonlySpan candidate_request_server_paths, StringView serenity_resource_root) { Vector arguments; @@ -147,7 +148,7 @@ ErrorOr> launch_request_server_process(Re arguments.append(serenity_resource_root); } - for (auto const& certificate : certificates) + for (auto const& certificate : WebView::Application::chrome_options().certificates) arguments.append(ByteString::formatted("--certificate={}", certificate)); if (auto server = mach_server_name(); server.has_value()) { @@ -155,7 +156,7 @@ ErrorOr> launch_request_server_process(Re arguments.append(server.value()); } - return launch_server_process("RequestServer"sv, candidate_request_server_paths, move(arguments), Ladybird::EnableCallgrindProfiling::No); + return launch_server_process("RequestServer"sv, candidate_request_server_paths, move(arguments), WebView::EnableCallgrindProfiling::No); } ErrorOr connect_new_request_server_client(Protocol::RequestClient& client) diff --git a/Ladybird/HelperProcess.h b/Ladybird/HelperProcess.h index 635629e1b4a..548226801cc 100644 --- a/Ladybird/HelperProcess.h +++ b/Ladybird/HelperProcess.h @@ -6,7 +6,6 @@ #pragma once -#include "Types.h" #include #include #include @@ -20,13 +19,12 @@ ErrorOr> launch_web_content_process( WebView::ViewImplementation& view, ReadonlySpan candidate_web_content_paths, - Ladybird::WebContentOptions const&, IPC::File image_decoder_socket, Optional request_server_socket = {}); ErrorOr> launch_image_decoder_process(ReadonlySpan candidate_image_decoder_paths); ErrorOr> launch_web_worker_process(ReadonlySpan candidate_web_worker_paths, RefPtr); -ErrorOr> launch_request_server_process(ReadonlySpan candidate_request_server_paths, StringView serenity_resource_root, Vector const& certificates); +ErrorOr> launch_request_server_process(ReadonlySpan candidate_request_server_paths, StringView serenity_resource_root); ErrorOr connect_new_request_server_client(Protocol::RequestClient&); ErrorOr connect_new_image_decoder_client(ImageDecoderClient::Client&); diff --git a/Ladybird/Qt/Application.cpp b/Ladybird/Qt/Application.cpp index 4cab11cf1f8..da72c69e479 100644 --- a/Ladybird/Qt/Application.cpp +++ b/Ladybird/Qt/Application.cpp @@ -4,21 +4,34 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include "Application.h" -#include "StringUtils.h" -#include "TaskManagerWindow.h" #include +#include +#include +#include +#include #include +#include #include #include namespace Ladybird { -Application::Application(int& argc, char** argv) - : QApplication(argc, argv) +Application::Application(Badge, Main::Arguments& arguments) + : QApplication(arguments.argc, arguments.argv) { } +void Application::create_platform_arguments(Core::ArgsParser& args_parser) +{ + args_parser.add_option(m_enable_qt_networking, "Enable Qt as the backend networking service", "enable-qt-networking"); +} + +void Application::create_platform_options(WebView::ChromeOptions&, WebView::WebContentOptions& web_content_options) +{ + web_content_options.config_path = Settings::the()->directory(); + web_content_options.use_lagom_networking = m_enable_qt_networking ? WebView::UseLagomNetworking::No : WebView::UseLagomNetworking::Yes; +} + Application::~Application() { close_task_manager_window(); @@ -77,10 +90,10 @@ ErrorOr Application::initialize_image_decoder() return {}; } -void Application::show_task_manager_window(WebContentOptions const& web_content_options) +void Application::show_task_manager_window() { if (!m_task_manager_window) { - m_task_manager_window = new TaskManagerWindow(nullptr, web_content_options); + m_task_manager_window = new TaskManagerWindow(nullptr); } m_task_manager_window->show(); m_task_manager_window->activateWindow(); @@ -96,9 +109,9 @@ void Application::close_task_manager_window() } } -BrowserWindow& Application::new_window(Vector const& initial_urls, WebView::CookieJar& cookie_jar, WebContentOptions const& web_content_options, StringView webdriver_content_ipc_path, bool allow_popups, BrowserWindow::IsPopupWindow is_popup_window, Tab* parent_tab, Optional page_index) +BrowserWindow& Application::new_window(Vector const& initial_urls, WebView::CookieJar& cookie_jar, BrowserWindow::IsPopupWindow is_popup_window, Tab* parent_tab, Optional page_index) { - auto* window = new BrowserWindow(initial_urls, cookie_jar, web_content_options, webdriver_content_ipc_path, allow_popups, is_popup_window, parent_tab, move(page_index)); + auto* window = new BrowserWindow(initial_urls, cookie_jar, is_popup_window, parent_tab, move(page_index)); set_active_window(*window); window->show(); if (initial_urls.is_empty()) { diff --git a/Ladybird/Qt/Application.h b/Ladybird/Qt/Application.h index 7689b55df33..33091b01ae5 100644 --- a/Ladybird/Qt/Application.h +++ b/Ladybird/Qt/Application.h @@ -12,15 +12,18 @@ #include #include #include +#include #include namespace Ladybird { -class Application : public QApplication { +class Application + : public QApplication + , public WebView::Application { Q_OBJECT + WEB_VIEW_APPLICATION(Application) public: - Application(int& argc, char** argv); virtual ~Application() override; virtual bool event(QEvent* event) override; @@ -31,15 +34,20 @@ public: NonnullRefPtr image_decoder_client() const { return *m_image_decoder_client; } ErrorOr initialize_image_decoder(); - BrowserWindow& new_window(Vector const& initial_urls, WebView::CookieJar&, WebContentOptions const&, StringView webdriver_content_ipc_path, bool allow_popups, BrowserWindow::IsPopupWindow is_popup_window = BrowserWindow::IsPopupWindow::No, Tab* parent_tab = nullptr, Optional page_index = {}); + BrowserWindow& new_window(Vector const& initial_urls, WebView::CookieJar&, BrowserWindow::IsPopupWindow is_popup_window = BrowserWindow::IsPopupWindow::No, Tab* parent_tab = nullptr, Optional page_index = {}); - void show_task_manager_window(WebContentOptions const&); + void show_task_manager_window(); void close_task_manager_window(); BrowserWindow& active_window() { return *m_active_window; } void set_active_window(BrowserWindow& w) { m_active_window = &w; } private: + virtual void create_platform_arguments(Core::ArgsParser&) override; + virtual void create_platform_options(WebView::ChromeOptions&, WebView::WebContentOptions&) override; + + bool m_enable_qt_networking { false }; + TaskManagerWindow* m_task_manager_window { nullptr }; BrowserWindow* m_active_window { nullptr }; diff --git a/Ladybird/Qt/BrowserWindow.cpp b/Ladybird/Qt/BrowserWindow.cpp index a2a097e49bb..0b010b7cb88 100644 --- a/Ladybird/Qt/BrowserWindow.cpp +++ b/Ladybird/Qt/BrowserWindow.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -71,13 +72,10 @@ public: } }; -BrowserWindow::BrowserWindow(Vector const& initial_urls, WebView::CookieJar& cookie_jar, WebContentOptions const& web_content_options, StringView webdriver_content_ipc_path, bool allow_popups, IsPopupWindow is_popup_window, Tab* parent_tab, Optional page_index) +BrowserWindow::BrowserWindow(Vector const& initial_urls, WebView::CookieJar& cookie_jar, IsPopupWindow is_popup_window, Tab* parent_tab, Optional page_index) : m_tabs_container(new TabWidget(this)) , m_new_tab_button_toolbar(new QToolBar("New Tab", m_tabs_container)) , m_cookie_jar(cookie_jar) - , m_web_content_options(web_content_options) - , m_webdriver_content_ipc_path(webdriver_content_ipc_path) - , m_allow_popups(allow_popups) , m_is_popup_window(is_popup_window) { setWindowIcon(app_icon()); @@ -365,7 +363,7 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, WebView::Cook task_manager_action->setShortcuts({ QKeySequence("Ctrl+Shift+M") }); inspect_menu->addAction(task_manager_action); QObject::connect(task_manager_action, &QAction::triggered, this, [&] { - static_cast(QApplication::instance())->show_task_manager_window(m_web_content_options); + static_cast(QApplication::instance())->show_task_manager_window(); }); auto* debug_menu = m_hamburger_menu->addMenu("&Debug"); @@ -556,7 +554,7 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, WebView::Cook m_block_pop_ups_action = new QAction("Block Pop-ups", this); m_block_pop_ups_action->setCheckable(true); - m_block_pop_ups_action->setChecked(!allow_popups); + m_block_pop_ups_action->setChecked(WebView::Application::chrome_options().allow_popups == WebView::AllowPopups::No); debug_menu->addAction(m_block_pop_ups_action); QObject::connect(m_block_pop_ups_action, &QAction::triggered, this, [this] { bool state = m_block_pop_ups_action->isChecked(); @@ -599,7 +597,7 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, WebView::Cook tab.focus_location_editor(); }); QObject::connect(m_new_window_action, &QAction::triggered, this, [this] { - (void)static_cast(QApplication::instance())->new_window({}, m_cookie_jar, m_web_content_options, m_webdriver_content_ipc_path, m_allow_popups); + (void)static_cast(QApplication::instance())->new_window({}, m_cookie_jar); }); QObject::connect(open_file_action, &QAction::triggered, this, &BrowserWindow::open_file); QObject::connect(settings_action, &QAction::triggered, this, [this] { @@ -667,12 +665,8 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, WebView::Cook if (parent_tab) { new_child_tab(Web::HTML::ActivateTab::Yes, *parent_tab, AK::move(page_index)); } else { - if (initial_urls.is_empty()) { - new_tab_from_url(ak_url_from_qstring(Settings::the()->new_tab_page()), Web::HTML::ActivateTab::Yes); - } else { - for (size_t i = 0; i < initial_urls.size(); ++i) { - new_tab_from_url(initial_urls[i], (i == 0) ? Web::HTML::ActivateTab::Yes : Web::HTML::ActivateTab::No); - } + for (size_t i = 0; i < initial_urls.size(); ++i) { + new_tab_from_url(initial_urls[i], (i == 0) ? Web::HTML::ActivateTab::Yes : Web::HTML::ActivateTab::No); } } @@ -726,7 +720,7 @@ Tab& BrowserWindow::create_new_tab(Web::HTML::ActivateTab activate_tab, Tab& par if (!page_index.has_value()) return create_new_tab(activate_tab); - auto* tab = new Tab(this, m_web_content_options, m_webdriver_content_ipc_path, parent.view().client(), page_index.value()); + auto* tab = new Tab(this, parent.view().client(), page_index.value()); // FIXME: Merge with other overload if (m_current_tab == nullptr) { @@ -743,7 +737,7 @@ Tab& BrowserWindow::create_new_tab(Web::HTML::ActivateTab activate_tab, Tab& par Tab& BrowserWindow::create_new_tab(Web::HTML::ActivateTab activate_tab) { - auto* tab = new Tab(this, m_web_content_options, m_webdriver_content_ipc_path); + auto* tab = new Tab(this); if (m_current_tab == nullptr) { set_current_tab(tab); @@ -775,7 +769,7 @@ void BrowserWindow::initialize_tab(Tab* tab) tab->view().on_new_web_view = [this, tab](auto activate_tab, Web::HTML::WebViewHints hints, Optional page_index) { if (hints.popup) { - auto& window = static_cast(QApplication::instance())->new_window({}, m_cookie_jar, m_web_content_options, m_webdriver_content_ipc_path, m_allow_popups, IsPopupWindow::Yes, tab, AK::move(page_index)); + auto& window = static_cast(QApplication::instance())->new_window({}, m_cookie_jar, IsPopupWindow::Yes, tab, AK::move(page_index)); window.set_window_rect(hints.screen_x, hints.screen_y, hints.width, hints.height); return window.current_tab()->view().handle(); } diff --git a/Ladybird/Qt/BrowserWindow.h b/Ladybird/Qt/BrowserWindow.h index 2678b674030..86ab2d09daa 100644 --- a/Ladybird/Qt/BrowserWindow.h +++ b/Ladybird/Qt/BrowserWindow.h @@ -9,7 +9,6 @@ #include "Tab.h" #include -#include #include #include #include @@ -36,7 +35,7 @@ public: Yes, }; - BrowserWindow(Vector const& initial_urls, WebView::CookieJar&, WebContentOptions const&, StringView webdriver_content_ipc_path, bool allow_popups, IsPopupWindow is_popup_window = IsPopupWindow::No, Tab* parent_tab = nullptr, Optional page_index = {}); + BrowserWindow(Vector const& initial_urls, WebView::CookieJar&, IsPopupWindow is_popup_window = IsPopupWindow::No, Tab* parent_tab = nullptr, Optional page_index = {}); WebContentView& view() const { return m_current_tab->view(); } @@ -215,10 +214,6 @@ private: WebView::CookieJar& m_cookie_jar; - WebContentOptions m_web_content_options; - StringView m_webdriver_content_ipc_path; - - bool m_allow_popups { false }; IsPopupWindow m_is_popup_window { IsPopupWindow::No }; }; diff --git a/Ladybird/Qt/InspectorWidget.cpp b/Ladybird/Qt/InspectorWidget.cpp index 9bffe15084d..63a697efd2c 100644 --- a/Ladybird/Qt/InspectorWidget.cpp +++ b/Ladybird/Qt/InspectorWidget.cpp @@ -22,7 +22,7 @@ extern bool is_using_dark_system_theme(QWidget&); InspectorWidget::InspectorWidget(QWidget* tab, WebContentView& content_view) : QWidget(tab, Qt::Window) { - m_inspector_view = new WebContentView(this, content_view.web_content_options(), {}); + m_inspector_view = new WebContentView(this); if (is_using_dark_system_theme(*this)) m_inspector_view->update_palette(WebContentView::PaletteMode::Dark); diff --git a/Ladybird/Qt/Tab.cpp b/Ladybird/Qt/Tab.cpp index 201b4c41387..5e8632e081f 100644 --- a/Ladybird/Qt/Tab.cpp +++ b/Ladybird/Qt/Tab.cpp @@ -47,7 +47,7 @@ static QIcon default_favicon() return icon; } -Tab::Tab(BrowserWindow* window, WebContentOptions const& web_content_options, StringView webdriver_content_ipc_path, RefPtr parent_client, size_t page_index) +Tab::Tab(BrowserWindow* window, RefPtr parent_client, size_t page_index) : QWidget(window) , m_window(window) { @@ -55,7 +55,7 @@ Tab::Tab(BrowserWindow* window, WebContentOptions const& web_content_options, St m_layout->setSpacing(0); m_layout->setContentsMargins(0, 0, 0, 0); - m_view = new WebContentView(this, web_content_options, webdriver_content_ipc_path, parent_client, page_index); + m_view = new WebContentView(this, parent_client, page_index); m_find_in_page = new FindInPageWidget(this, m_view); m_find_in_page->setVisible(false); m_toolbar = new QToolBar(this); diff --git a/Ladybird/Qt/Tab.h b/Ladybird/Qt/Tab.h index cb8a8030373..624f7a72fbe 100644 --- a/Ladybird/Qt/Tab.h +++ b/Ladybird/Qt/Tab.h @@ -29,7 +29,7 @@ class Tab final : public QWidget { Q_OBJECT public: - Tab(BrowserWindow* window, WebContentOptions const&, StringView webdriver_content_ipc_path, RefPtr parent_client = nullptr, size_t page_index = 0); + Tab(BrowserWindow* window, RefPtr parent_client = nullptr, size_t page_index = 0); virtual ~Tab() override; WebContentView& view() { return *m_view; } diff --git a/Ladybird/Qt/TaskManagerWindow.cpp b/Ladybird/Qt/TaskManagerWindow.cpp index 29a91802b22..5ce0e9eb1d4 100644 --- a/Ladybird/Qt/TaskManagerWindow.cpp +++ b/Ladybird/Qt/TaskManagerWindow.cpp @@ -10,9 +10,9 @@ namespace Ladybird { -TaskManagerWindow::TaskManagerWindow(QWidget* parent, WebContentOptions const& web_content_options) +TaskManagerWindow::TaskManagerWindow(QWidget* parent) : QWidget(parent, Qt::WindowFlags(Qt::WindowType::Window)) - , m_web_view(new WebContentView(this, web_content_options, {})) + , m_web_view(new WebContentView(this)) { setLayout(new QVBoxLayout); layout()->addWidget(m_web_view); diff --git a/Ladybird/Qt/TaskManagerWindow.h b/Ladybird/Qt/TaskManagerWindow.h index d4d5cd3aef4..efe7863e360 100644 --- a/Ladybird/Qt/TaskManagerWindow.h +++ b/Ladybird/Qt/TaskManagerWindow.h @@ -16,7 +16,7 @@ class TaskManagerWindow : public QWidget { Q_OBJECT public: - TaskManagerWindow(QWidget* parent, WebContentOptions const&); + explicit TaskManagerWindow(QWidget* parent); private: virtual void showEvent(QShowEvent*) override; diff --git a/Ladybird/Qt/WebContentView.cpp b/Ladybird/Qt/WebContentView.cpp index d6babfa949b..dbd25e0ed7f 100644 --- a/Ladybird/Qt/WebContentView.cpp +++ b/Ladybird/Qt/WebContentView.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -50,10 +51,8 @@ namespace Ladybird { bool is_using_dark_system_theme(QWidget&); -WebContentView::WebContentView(QWidget* window, WebContentOptions const& web_content_options, StringView webdriver_content_ipc_path, RefPtr parent_client, size_t page_index) +WebContentView::WebContentView(QWidget* window, RefPtr parent_client, size_t page_index) : QAbstractScrollArea(window) - , m_web_content_options(web_content_options) - , m_webdriver_content_ipc_path(webdriver_content_ipc_path) { setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -133,7 +132,7 @@ WebContentView::WebContentView(QWidget* window, WebContentOptions const& web_con on_request_worker_agent = [&]() { RefPtr request_server_client {}; - if (m_web_content_options.use_lagom_networking == Ladybird::UseLagomNetworking::Yes) + if (WebView::Application::web_content_options().use_lagom_networking == WebView::UseLagomNetworking::Yes) request_server_client = static_cast(QApplication::instance())->request_server_client; auto worker_client = MUST(launch_web_worker_process(MUST(get_paths_for_helper_process("WebWorker"sv)), request_server_client)); @@ -574,7 +573,7 @@ void WebContentView::initialize_client(WebView::ViewImplementation::CreateNewCli m_client_state = {}; Optional request_server_socket; - if (m_web_content_options.use_lagom_networking == UseLagomNetworking::Yes) { + if (WebView::Application::web_content_options().use_lagom_networking == WebView::UseLagomNetworking::Yes) { auto& protocol = static_cast(QApplication::instance())->request_server_client; // FIXME: Fail to open the tab, rather than crashing the whole application if this fails @@ -586,7 +585,7 @@ void WebContentView::initialize_client(WebView::ViewImplementation::CreateNewCli auto image_decoder_socket = connect_new_image_decoder_client(*image_decoder).release_value_but_fixme_should_propagate_errors(); auto candidate_web_content_paths = get_paths_for_helper_process("WebContent"sv).release_value_but_fixme_should_propagate_errors(); - auto new_client = launch_web_content_process(*this, candidate_web_content_paths, m_web_content_options, AK::move(image_decoder_socket), AK::move(request_server_socket)).release_value_but_fixme_should_propagate_errors(); + auto new_client = launch_web_content_process(*this, candidate_web_content_paths, AK::move(image_decoder_socket), AK::move(request_server_socket)).release_value_but_fixme_should_propagate_errors(); m_client_state.client = new_client; } else { @@ -607,8 +606,8 @@ void WebContentView::initialize_client(WebView::ViewImplementation::CreateNewCli update_screen_rects(); - if (!m_webdriver_content_ipc_path.is_empty()) - client().async_connect_to_webdriver(m_client_state.page_index, m_webdriver_content_ipc_path); + if (auto webdriver_content_ipc_path = WebView::Application::chrome_options().webdriver_content_ipc_path; webdriver_content_ipc_path.has_value()) + client().async_connect_to_webdriver(m_client_state.page_index, *webdriver_content_ipc_path); } void WebContentView::update_cursor(Gfx::StandardCursor cursor) diff --git a/Ladybird/Qt/WebContentView.h b/Ladybird/Qt/WebContentView.h index 3310cd2c0a4..d7d9010c19d 100644 --- a/Ladybird/Qt/WebContentView.h +++ b/Ladybird/Qt/WebContentView.h @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -46,7 +45,7 @@ class WebContentView final , public WebView::ViewImplementation { Q_OBJECT public: - WebContentView(QWidget* window, WebContentOptions const&, StringView webdriver_content_ipc_path, RefPtr parent_client = nullptr, size_t page_index = 0); + WebContentView(QWidget* window, RefPtr parent_client = nullptr, size_t page_index = 0); virtual ~WebContentView() override; Function on_tab_open_request; @@ -85,8 +84,6 @@ public: QPoint map_point_to_global_position(Gfx::IntPoint) const; - WebContentOptions const& web_content_options() const { return m_web_content_options; } - signals: void urls_dropped(QList const&); @@ -113,9 +110,6 @@ private: bool m_should_show_line_box_borders { false }; Gfx::IntSize m_viewport_size; - - WebContentOptions m_web_content_options; - StringView m_webdriver_content_ipc_path; }; } diff --git a/Ladybird/Qt/main.cpp b/Ladybird/Qt/main.cpp index 9897cdbe30e..3ff17aa0271 100644 --- a/Ladybird/Qt/main.cpp +++ b/Ladybird/Qt/main.cpp @@ -62,72 +62,32 @@ static ErrorOr handle_attached_debugger() return {}; } -static Vector sanitize_urls(Vector const& raw_urls) -{ - Vector sanitized_urls; - for (auto const& raw_url : raw_urls) { - if (auto url = WebView::sanitize_url(raw_url); url.has_value()) - sanitized_urls.append(url.release_value()); - } - return sanitized_urls; -} - ErrorOr serenity_main(Main::Arguments arguments) { AK::set_rich_debug_enabled(true); - Ladybird::Application app(arguments.argc, arguments.argv); - Core::EventLoopManager::install(*new Ladybird::EventLoopManagerQt); - WebView::Application webview_app(arguments.argc, arguments.argv); - static_cast(Core::EventLoop::current().impl()).set_main_loop(); + auto app = Ladybird::Application::create(arguments, ak_url_from_qstring(Ladybird::Settings::the()->new_tab_page())); + + static_cast(Core::EventLoop::current().impl()).set_main_loop(); TRY(handle_attached_debugger()); platform_init(); - Vector raw_urls; - StringView webdriver_content_ipc_path; - Vector certificates; - bool enable_callgrind_profiling = false; - bool disable_sql_database = false; - bool enable_qt_networking = false; - bool expose_internals_object = false; - bool debug_web_content = false; - bool log_all_js_exceptions = false; - bool enable_idl_tracing = false; - bool enable_http_cache = false; - bool new_window = false; - bool force_new_process = false; - bool allow_popups = false; - - Core::ArgsParser args_parser; - args_parser.set_general_help("The Ladybird web browser :^)"); - args_parser.add_positional_argument(raw_urls, "URLs to open", "url", Core::ArgsParser::Required::No); - args_parser.add_option(webdriver_content_ipc_path, "Path to WebDriver IPC for WebContent", "webdriver-content-path", 0, "path", Core::ArgsParser::OptionHideMode::CommandLineAndMarkdown); - args_parser.add_option(enable_callgrind_profiling, "Enable Callgrind profiling", "enable-callgrind-profiling", 'P'); - args_parser.add_option(disable_sql_database, "Disable SQL database", "disable-sql-database"); - args_parser.add_option(enable_qt_networking, "Enable Qt as the backend networking service", "enable-qt-networking"); - args_parser.add_option(debug_web_content, "Wait for debugger to attach to WebContent", "debug-web-content"); - args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate"); - args_parser.add_option(log_all_js_exceptions, "Log all JavaScript exceptions", "log-all-js-exceptions"); - args_parser.add_option(enable_idl_tracing, "Enable IDL tracing", "enable-idl-tracing"); - args_parser.add_option(enable_http_cache, "Enable HTTP cache", "enable-http-cache"); - args_parser.add_option(expose_internals_object, "Expose internals object", "expose-internals-object"); - args_parser.add_option(new_window, "Force opening in a new window", "new-window", 'n'); - args_parser.add_option(force_new_process, "Force creation of new browser/chrome process", "force-new-process"); - args_parser.add_option(allow_popups, "Disable popup blocking by default", "allow-popups"); - args_parser.parse(arguments); - auto chrome_process = TRY(WebView::ChromeProcess::create()); - if (!force_new_process && TRY(chrome_process.connect(raw_urls, new_window)) == WebView::ChromeProcess::ProcessDisposition::ExitProcess) { - outln("Opening in existing process"); - return 0; + + if (app->chrome_options().force_new_process == WebView::ForceNewProcess::No) { + auto disposition = TRY(chrome_process.connect(app->chrome_options().raw_urls, app->chrome_options().new_window)); + + if (disposition == WebView::ChromeProcess::ProcessDisposition::ExitProcess) { + outln("Opening in existing process"); + return 0; + } } - chrome_process.on_new_tab = [&](auto const& raw_urls) { - auto& window = app.active_window(); - auto urls = sanitize_urls(raw_urls); + chrome_process.on_new_tab = [&](auto const& urls) { + auto& window = app->active_window(); for (size_t i = 0; i < urls.size(); ++i) { window.new_tab_from_url(urls[i], (i == 0) ? Web::HTML::ActivateTab::Yes : Web::HTML::ActivateTab::No); } @@ -136,16 +96,16 @@ ErrorOr serenity_main(Main::Arguments arguments) window.raise(); }; - app.on_open_file = [&](auto file_url) { - auto& window = app.active_window(); + app->on_open_file = [&](auto file_url) { + auto& window = app->active_window(); window.view().load(file_url); }; #if defined(AK_OS_MACOS) auto mach_port_server = make(); set_mach_server_name(mach_port_server->server_port_name()); - mach_port_server->on_receive_child_mach_port = [&webview_app](auto pid, auto port) { - webview_app.set_process_mach_port(pid, move(port)); + mach_port_server->on_receive_child_mach_port = [&app](auto pid, auto port) { + app->set_process_mach_port(pid, move(port)); }; mach_port_server->on_receive_backing_stores = [](Ladybird::MachPortServer::BackingStoresMessage message) { if (auto view = WebView::WebContentClient::view_for_pid_and_page_id(message.pid, message.page_id); view.has_value()) @@ -156,40 +116,25 @@ ErrorOr serenity_main(Main::Arguments arguments) copy_default_config_files(Ladybird::Settings::the()->directory()); RefPtr database; - if (!disable_sql_database) + if (app->chrome_options().disable_sql_database == WebView::DisableSQLDatabase::No) database = TRY(WebView::Database::create()); auto cookie_jar = database ? TRY(WebView::CookieJar::create(*database)) : WebView::CookieJar::create(); // FIXME: Create an abstraction to re-spawn the RequestServer and re-hook up its client hooks to each tab on crash - if (!enable_qt_networking) { + if (app->web_content_options().use_lagom_networking == WebView::UseLagomNetworking::Yes) { auto request_server_paths = TRY(get_paths_for_helper_process("RequestServer"sv)); - auto protocol_client = TRY(launch_request_server_process(request_server_paths, s_ladybird_resource_root, certificates)); - app.request_server_client = move(protocol_client); + auto protocol_client = TRY(launch_request_server_process(request_server_paths, s_ladybird_resource_root)); + app->request_server_client = move(protocol_client); } - TRY(app.initialize_image_decoder()); - - StringBuilder command_line_builder; - command_line_builder.join(' ', arguments.strings); - Ladybird::WebContentOptions web_content_options { - .command_line = MUST(command_line_builder.to_string()), - .executable_path = MUST(String::from_byte_string(MUST(Core::System::current_executable_path()))), - .config_path = Ladybird::Settings::the()->directory(), - .enable_callgrind_profiling = enable_callgrind_profiling ? Ladybird::EnableCallgrindProfiling::Yes : Ladybird::EnableCallgrindProfiling::No, - .use_lagom_networking = enable_qt_networking ? Ladybird::UseLagomNetworking::No : Ladybird::UseLagomNetworking::Yes, - .wait_for_debugger = debug_web_content ? Ladybird::WaitForDebugger::Yes : Ladybird::WaitForDebugger::No, - .log_all_js_exceptions = log_all_js_exceptions ? Ladybird::LogAllJSExceptions::Yes : Ladybird::LogAllJSExceptions::No, - .enable_idl_tracing = enable_idl_tracing ? Ladybird::EnableIDLTracing::Yes : Ladybird::EnableIDLTracing::No, - .enable_http_cache = enable_http_cache ? Ladybird::EnableHTTPCache::Yes : Ladybird::EnableHTTPCache::No, - .expose_internals_object = expose_internals_object ? Ladybird::ExposeInternalsObject::Yes : Ladybird::ExposeInternalsObject::No, - }; + TRY(app->initialize_image_decoder()); chrome_process.on_new_window = [&](auto const& urls) { - app.new_window(sanitize_urls(urls), *cookie_jar, web_content_options, webdriver_content_ipc_path, allow_popups); + app->new_window(urls, *cookie_jar); }; - auto& window = app.new_window(sanitize_urls(raw_urls), *cookie_jar, web_content_options, webdriver_content_ipc_path, allow_popups); + auto& window = app->new_window(app->chrome_options().urls, *cookie_jar); window.setWindowTitle("Ladybird"); if (Ladybird::Settings::the()->is_maximized()) { @@ -203,5 +148,5 @@ ErrorOr serenity_main(Main::Arguments arguments) window.show(); - return webview_app.exec(); + return app->execute(); } diff --git a/Userland/Libraries/LibWebView/Application.cpp b/Userland/Libraries/LibWebView/Application.cpp index 7e243b90986..3abe73b73e8 100644 --- a/Userland/Libraries/LibWebView/Application.cpp +++ b/Userland/Libraries/LibWebView/Application.cpp @@ -5,15 +5,17 @@ */ #include +#include #include #include +#include #include namespace WebView { Application* Application::s_the = nullptr; -Application::Application(int, char**) +Application::Application() { VERIFY(!s_the); s_the = this; @@ -28,7 +30,70 @@ Application::~Application() s_the = nullptr; } -int Application::exec() +void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_page_url) +{ + Vector raw_urls; + Vector certificates; + bool new_window = false; + bool force_new_process = false; + bool allow_popups = false; + bool disable_sql_database = false; + Optional webdriver_content_ipc_path; + bool enable_callgrind_profiling = false; + bool debug_web_content = false; + bool log_all_js_exceptions = false; + bool enable_idl_tracing = false; + bool enable_http_cache = false; + bool expose_internals_object = false; + + Core::ArgsParser args_parser; + args_parser.set_general_help("The Ladybird web browser :^)"); + args_parser.add_positional_argument(raw_urls, "URLs to open", "url", Core::ArgsParser::Required::No); + args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate"); + args_parser.add_option(new_window, "Force opening in a new window", "new-window", 'n'); + args_parser.add_option(force_new_process, "Force creation of new browser/chrome process", "force-new-process"); + args_parser.add_option(allow_popups, "Disable popup blocking by default", "allow-popups"); + args_parser.add_option(disable_sql_database, "Disable SQL database", "disable-sql-database"); + args_parser.add_option(webdriver_content_ipc_path, "Path to WebDriver IPC for WebContent", "webdriver-content-path", 0, "path", Core::ArgsParser::OptionHideMode::CommandLineAndMarkdown); + args_parser.add_option(enable_callgrind_profiling, "Enable Callgrind profiling", "enable-callgrind-profiling", 'P'); + args_parser.add_option(debug_web_content, "Wait for debugger to attach to WebContent", "debug-web-content"); + args_parser.add_option(log_all_js_exceptions, "Log all JavaScript exceptions", "log-all-js-exceptions"); + args_parser.add_option(enable_idl_tracing, "Enable IDL tracing", "enable-idl-tracing"); + args_parser.add_option(enable_http_cache, "Enable HTTP cache", "enable-http-cache"); + args_parser.add_option(expose_internals_object, "Expose internals object", "expose-internals-object"); + + create_platform_arguments(args_parser); + args_parser.parse(arguments); + + m_chrome_options = { + .urls = sanitize_urls(raw_urls, new_tab_page_url), + .raw_urls = move(raw_urls), + .new_tab_page_url = move(new_tab_page_url), + .certificates = move(certificates), + .new_window = new_window ? NewWindow::Yes : NewWindow::No, + .force_new_process = force_new_process ? ForceNewProcess::Yes : ForceNewProcess::No, + .allow_popups = allow_popups ? AllowPopups::Yes : AllowPopups::No, + .disable_sql_database = disable_sql_database ? DisableSQLDatabase::Yes : DisableSQLDatabase::No, + }; + + if (webdriver_content_ipc_path.has_value()) + m_chrome_options.webdriver_content_ipc_path = *webdriver_content_ipc_path; + + m_web_content_options = { + .command_line = MUST(String::join(' ', arguments.strings)), + .executable_path = MUST(String::from_byte_string(MUST(Core::System::current_executable_path()))), + .enable_callgrind_profiling = enable_callgrind_profiling ? EnableCallgrindProfiling::Yes : EnableCallgrindProfiling::No, + .wait_for_debugger = debug_web_content ? WaitForDebugger::Yes : WaitForDebugger::No, + .log_all_js_exceptions = log_all_js_exceptions ? LogAllJSExceptions::Yes : LogAllJSExceptions::No, + .enable_idl_tracing = enable_idl_tracing ? EnableIDLTracing::Yes : EnableIDLTracing::No, + .enable_http_cache = enable_http_cache ? EnableHTTPCache::Yes : EnableHTTPCache::No, + .expose_internals_object = expose_internals_object ? ExposeInternalsObject::Yes : ExposeInternalsObject::No, + }; + + create_platform_options(m_chrome_options, m_web_content_options); +} + +int Application::execute() { int ret = m_event_loop.exec(); m_in_shutdown = true; diff --git a/Userland/Libraries/LibWebView/Application.h b/Userland/Libraries/LibWebView/Application.h index 36a3c05f1a9..7f261568ca2 100644 --- a/Userland/Libraries/LibWebView/Application.h +++ b/Userland/Libraries/LibWebView/Application.h @@ -6,7 +6,11 @@ #pragma once +#include #include +#include +#include +#include #include #include @@ -22,13 +26,15 @@ class Application { AK_MAKE_NONCOPYABLE(Application); public: - Application(int argc, char** argv); virtual ~Application(); - int exec(); + int execute(); static Application& the() { return *s_the; } + static ChromeOptions const& chrome_options() { return the().m_chrome_options; } + static WebContentOptions const& web_content_options() { return the().m_web_content_options; } + Core::EventLoop& event_loop() { return m_event_loop; } void add_child_process(Process&&); @@ -44,14 +50,42 @@ public: String generate_process_statistics_html(); protected: + template ApplicationType> + static NonnullOwnPtr create(Main::Arguments& arguments, URL::URL new_tab_page_url) + { + auto app = adopt_own(*new ApplicationType { {}, arguments }); + app->initialize(arguments, move(new_tab_page_url)); + + return app; + } + + Application(); + virtual void process_did_exit(Process&&); + virtual void create_platform_arguments(Core::ArgsParser&) { } + virtual void create_platform_options(ChromeOptions&, WebContentOptions&) { } + private: + void initialize(Main::Arguments const& arguments, URL::URL new_tab_page_url); + static Application* s_the; + ChromeOptions m_chrome_options; + WebContentOptions m_web_content_options; + Core::EventLoop m_event_loop; ProcessManager m_process_manager; bool m_in_shutdown { false }; } SWIFT_IMMORTAL_REFERENCE; } + +#define WEB_VIEW_APPLICATION(ApplicationType) \ +public: \ + static NonnullOwnPtr create(Main::Arguments& arguments, URL::URL new_tab_page_url) \ + { \ + return WebView::Application::create(arguments, move(new_tab_page_url)); \ + } \ + \ + ApplicationType(Badge, Main::Arguments&); diff --git a/Userland/Libraries/LibWebView/ChromeProcess.cpp b/Userland/Libraries/LibWebView/ChromeProcess.cpp index 7f4819f441f..6d7276ba9c1 100644 --- a/Userland/Libraries/LibWebView/ChromeProcess.cpp +++ b/Userland/Libraries/LibWebView/ChromeProcess.cpp @@ -9,7 +9,9 @@ #include #include #include +#include #include +#include namespace WebView { @@ -32,7 +34,7 @@ ErrorOr ChromeProcess::create() return ChromeProcess {}; } -ErrorOr ChromeProcess::connect(Vector const& raw_urls, bool new_window) +ErrorOr ChromeProcess::connect(Vector const& raw_urls, NewWindow new_window) { static constexpr auto process_name = "Ladybird"sv; @@ -52,12 +54,12 @@ ErrorOr ChromeProcess::connect(Vector ChromeProcess::connect_as_client(ByteString const& socket_path, Vector const& raw_urls, bool new_window) +ErrorOr ChromeProcess::connect_as_client(ByteString const& socket_path, Vector const& raw_urls, NewWindow new_window) { auto socket = TRY(Core::LocalSocket::connect(socket_path)); auto client = UIProcessClient::construct(move(socket)); - if (new_window) { + if (new_window == NewWindow::Yes) { if (!client->send_sync_but_allow_failure(raw_urls)) dbgln("Failed to send CreateNewWindow message to UIProcess"); } else { @@ -121,13 +123,13 @@ void UIProcessConnectionFromClient::die() void UIProcessConnectionFromClient::create_new_tab(Vector const& urls) { if (on_new_tab) - on_new_tab(urls); + on_new_tab(sanitize_urls(urls, Application::chrome_options().new_tab_page_url)); } void UIProcessConnectionFromClient::create_new_window(Vector const& urls) { if (on_new_window) - on_new_window(urls); + on_new_window(sanitize_urls(urls, Application::chrome_options().new_tab_page_url)); } } diff --git a/Userland/Libraries/LibWebView/ChromeProcess.h b/Userland/Libraries/LibWebView/ChromeProcess.h index fe469f0b561..825bad23d3d 100644 --- a/Userland/Libraries/LibWebView/ChromeProcess.h +++ b/Userland/Libraries/LibWebView/ChromeProcess.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -28,8 +29,8 @@ public: virtual void die() override; - Function const& urls)> on_new_tab; - Function const& urls)> on_new_window; + Function const&)> on_new_tab; + Function const&)> on_new_window; private: UIProcessConnectionFromClient(NonnullOwnPtr, int client_id); @@ -51,15 +52,15 @@ public: static ErrorOr create(); ~ChromeProcess(); - ErrorOr connect(Vector const& raw_urls, bool new_window); + ErrorOr connect(Vector const& raw_urls, NewWindow new_window); - Function const& raw_urls)> on_new_tab; - Function const& raw_urls)> on_new_window; + Function const&)> on_new_tab; + Function const&)> on_new_window; private: ChromeProcess() = default; - ErrorOr connect_as_client(ByteString const& socket_path, Vector const& raw_urls, bool new_window); + ErrorOr connect_as_client(ByteString const& socket_path, Vector const& raw_urls, NewWindow new_window); ErrorOr connect_as_server(ByteString const& socket_path); OwnPtr> m_server_connection; diff --git a/Ladybird/Types.h b/Userland/Libraries/LibWebView/Options.h similarity index 59% rename from Ladybird/Types.h rename to Userland/Libraries/LibWebView/Options.h index 735a82879e8..768589a99c6 100644 --- a/Ladybird/Types.h +++ b/Userland/Libraries/LibWebView/Options.h @@ -6,48 +6,84 @@ #pragma once +#include +#include #include +#include +#include -namespace Ladybird { +namespace WebView { + +enum class NewWindow { + No, + Yes, +}; + +enum class ForceNewProcess { + No, + Yes, +}; + +enum class AllowPopups { + No, + Yes, +}; + +enum class DisableSQLDatabase { + No, + Yes, +}; + +struct ChromeOptions { + Vector urls; + Vector raw_urls; + URL::URL new_tab_page_url; + Vector certificates {}; + NewWindow new_window { NewWindow::No }; + ForceNewProcess force_new_process { ForceNewProcess::No }; + AllowPopups allow_popups { AllowPopups::No }; + DisableSQLDatabase disable_sql_database { DisableSQLDatabase::No }; + Optional webdriver_content_ipc_path {}; +}; enum class EnableCallgrindProfiling { No, - Yes + Yes, }; enum class IsLayoutTestMode { No, - Yes + Yes, }; enum class UseLagomNetworking { No, - Yes + Yes, }; enum class WaitForDebugger { No, - Yes + Yes, }; enum class LogAllJSExceptions { No, - Yes + Yes, }; enum class EnableIDLTracing { No, - Yes + Yes, }; enum class EnableHTTPCache { No, - Yes + Yes, }; enum class ExposeInternalsObject { No, - Yes + Yes, }; struct WebContentOptions { diff --git a/Userland/Libraries/LibWebView/URL.cpp b/Userland/Libraries/LibWebView/URL.cpp index 2374fe7bfef..9968ebeaffc 100644 --- a/Userland/Libraries/LibWebView/URL.cpp +++ b/Userland/Libraries/LibWebView/URL.cpp @@ -72,6 +72,22 @@ Optional sanitize_url(StringView url, Optional search_engi return result; } +Vector sanitize_urls(ReadonlySpan raw_urls, URL::URL const& new_tab_page_url) +{ + Vector sanitized_urls; + sanitized_urls.ensure_capacity(raw_urls.size()); + + for (auto const& raw_url : raw_urls) { + if (auto url = sanitize_url(raw_url); url.has_value()) + sanitized_urls.unchecked_append(url.release_value()); + } + + if (sanitized_urls.is_empty()) + sanitized_urls.append(new_tab_page_url); + + return sanitized_urls; +} + static URLParts break_file_url_into_parts(URL::URL const& url, StringView url_string) { auto scheme = url_string.substring_view(0, url.scheme().bytes_as_string_view().length() + "://"sv.length()); diff --git a/Userland/Libraries/LibWebView/URL.h b/Userland/Libraries/LibWebView/URL.h index 580512a201c..0145c63a667 100644 --- a/Userland/Libraries/LibWebView/URL.h +++ b/Userland/Libraries/LibWebView/URL.h @@ -20,6 +20,7 @@ enum class AppendTLD { Yes, }; Optional sanitize_url(StringView, Optional search_engine = {}, AppendTLD = AppendTLD::No); +Vector sanitize_urls(ReadonlySpan raw_urls, URL::URL const& new_tab_page_url); struct URLParts { StringView scheme_and_subdomain; diff --git a/Userland/Utilities/headless-browser.cpp b/Userland/Utilities/headless-browser.cpp index ea115997b43..060eac3929c 100644 --- a/Userland/Utilities/headless-browser.cpp +++ b/Userland/Utilities/headless-browser.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -64,13 +63,13 @@ static StringView s_current_test_path; class HeadlessWebContentView final : public WebView::ViewImplementation { public: - static ErrorOr> create(Core::AnonymousBuffer theme, Gfx::IntSize const& window_size, String const& command_line, StringView web_driver_ipc_path, Ladybird::IsLayoutTestMode is_layout_test_mode = Ladybird::IsLayoutTestMode::No, Vector const& certificates = {}, StringView resources_folder = {}) + static ErrorOr> create(Core::AnonymousBuffer theme, Gfx::IntSize const& window_size, StringView resources_folder) { RefPtr request_client; RefPtr image_decoder_client; auto request_server_paths = TRY(get_paths_for_helper_process("RequestServer"sv)); - request_client = TRY(launch_request_server_process(request_server_paths, resources_folder, certificates)); + request_client = TRY(launch_request_server_process(request_server_paths, resources_folder)); auto image_decoder_paths = TRY(get_paths_for_helper_process("ImageDecoder"sv)); image_decoder_client = TRY(launch_image_decoder_process(image_decoder_paths)); @@ -80,17 +79,11 @@ public: auto view = TRY(adopt_nonnull_own_or_enomem(new (nothrow) HeadlessWebContentView(move(database), move(cookie_jar), image_decoder_client, request_client))); - Ladybird::WebContentOptions web_content_options { - .command_line = command_line, - .executable_path = MUST(String::from_byte_string(MUST(Core::System::current_executable_path()))), - .is_layout_test_mode = is_layout_test_mode, - }; - auto request_server_socket = TRY(connect_new_request_server_client(*request_client)); auto image_decoder_socket = TRY(connect_new_image_decoder_client(*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, web_content_options, move(image_decoder_socket), move(request_server_socket))); + 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)); @@ -98,8 +91,8 @@ public: view->client().async_set_viewport_size(0, view->m_viewport_size.to_type()); view->client().async_set_window_size(0, window_size.to_type()); - if (!web_driver_ipc_path.is_empty()) - view->client().async_connect_to_webdriver(0, web_driver_ipc_path); + 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 = [] { warnln("\033[31;1mWebContent Crashed!!\033[0m"); @@ -189,7 +182,7 @@ private: RefPtr m_image_decoder_client; }; -static ErrorOr> load_page_for_screenshot_and_exit(Core::EventLoop& event_loop, HeadlessWebContentView& view, URL::URL url, int screenshot_timeout) +static ErrorOr> load_page_for_screenshot_and_exit(Core::EventLoop& event_loop, HeadlessWebContentView& view, URL::URL const& url, int screenshot_timeout) { // FIXME: Allow passing the output path as an argument. static constexpr auto output_file_path = "output.png"sv; @@ -248,7 +241,7 @@ static StringView test_result_to_string(TestResult result) VERIFY_NOT_REACHED(); } -static ErrorOr run_dump_test(HeadlessWebContentView& view, StringView input_path, StringView expectation_path, TestMode mode, int timeout_in_milliseconds = DEFAULT_TIMEOUT_MS) +static ErrorOr run_dump_test(HeadlessWebContentView& view, URL::URL const& url, StringView expectation_path, TestMode mode, int timeout_in_milliseconds = DEFAULT_TIMEOUT_MS) { Core::EventLoop loop; bool did_timeout = false; @@ -258,8 +251,6 @@ static ErrorOr run_dump_test(HeadlessWebContentView& view, StringVie loop.quit(0); }); - auto url = URL::create_with_file_scheme(TRY(FileSystem::real_path(input_path))); - String result; auto did_finish_test = false; auto did_finish_loading = false; @@ -335,9 +326,9 @@ static ErrorOr run_dump_test(HeadlessWebContentView& view, StringVie auto const color_output = isatty(STDOUT_FILENO) ? Diff::ColorOutput::Yes : Diff::ColorOutput::No; if (color_output == Diff::ColorOutput::Yes) - outln("\n\033[33;1mTest failed\033[0m: {}", input_path); + outln("\n\033[33;1mTest failed\033[0m: {}", url); else - outln("\nTest failed: {}", input_path); + outln("\nTest failed: {}", url); auto hunks = TRY(Diff::from_text(expectation, actual, 3)); auto out = TRY(Core::File::standard_output()); @@ -349,7 +340,7 @@ static ErrorOr run_dump_test(HeadlessWebContentView& view, StringVie return TestResult::Fail; } -static ErrorOr run_ref_test(HeadlessWebContentView& view, StringView input_path, bool dump_failed_ref_tests, int timeout_in_milliseconds = DEFAULT_TIMEOUT_MS) +static ErrorOr run_ref_test(HeadlessWebContentView& view, URL::URL const& url, bool dump_failed_ref_tests, int timeout_in_milliseconds = DEFAULT_TIMEOUT_MS) { Core::EventLoop loop; bool did_timeout = false; @@ -370,10 +361,10 @@ static ErrorOr run_ref_test(HeadlessWebContentView& view, StringView } }; view.on_text_test_finish = [&] { - dbgln("Unexpected text test finished during ref test for {}", input_path); + dbgln("Unexpected text test finished during ref test for {}", url); }; - view.load(URL::create_with_file_scheme(TRY(FileSystem::real_path(input_path)))); + view.load(url); timeout_timer->start(); loop.exec(); @@ -388,8 +379,8 @@ static ErrorOr run_ref_test(HeadlessWebContentView& view, StringView return TestResult::Pass; if (dump_failed_ref_tests) { - warnln("\033[33;1mRef test {} failed; dumping screenshots\033[0m", input_path); - auto title = LexicalPath::title(input_path); + warnln("\033[33;1mRef test {} failed; dumping screenshots\033[0m", url); + auto title = LexicalPath::title(url.serialize_path()); auto dump_screenshot = [&](Gfx::Bitmap& bitmap, StringView path) -> ErrorOr { auto screenshot_file = TRY(Core::File::open(path, Core::File::OpenMode::Write)); auto encoded_data = TRY(Gfx::PNGWriter::encode(bitmap)); @@ -462,13 +453,15 @@ static ErrorOr run_test(HeadlessWebContentView& view, StringView inp view.load(URL::URL("about:blank"sv)); MUST(promise->await()); + auto url = URL::create_with_file_scheme(TRY(FileSystem::real_path(input_path))); s_current_test_path = input_path; + switch (mode) { case TestMode::Text: case TestMode::Layout: - return run_dump_test(view, input_path, expectation_path, mode); + return run_dump_test(view, url, expectation_path, mode); case TestMode::Ref: - return run_ref_test(view, input_path, dump_failed_ref_tests); + return run_ref_test(view, url, dump_failed_ref_tests); default: VERIFY_NOT_REACHED(); } @@ -633,83 +626,88 @@ static ErrorOr run_tests(HeadlessWebContentView& view, StringView test_root return 1; } -ErrorOr serenity_main(Main::Arguments arguments) -{ - WebView::Application app(arguments.argc, arguments.argv); +struct Application : public WebView::Application { + WEB_VIEW_APPLICATION(Application) - int screenshot_timeout = 1; - StringView raw_url; - auto resources_folder = "/res"sv; - StringView web_driver_ipc_path; - bool dump_failed_ref_tests = false; - bool dump_layout_tree = false; - bool dump_text = false; - bool dump_gc_graph = false; - bool is_layout_test_mode = false; + virtual void create_platform_arguments(Core::ArgsParser& args_parser) override + { + args_parser.add_option(screenshot_timeout, "Take a screenshot after [n] seconds (default: 1)", "screenshot", 's', "n"); + args_parser.add_option(dump_layout_tree, "Dump layout tree and exit", "dump-layout-tree", 'd'); + args_parser.add_option(dump_text, "Dump text and exit", "dump-text", 'T'); + args_parser.add_option(test_root_path, "Run tests in path", "run-tests", 'R', "test-root-path"); + args_parser.add_option(test_glob, "Only run tests matching the given glob", "filter", 'f', "glob"); + args_parser.add_option(dump_failed_ref_tests, "Dump screenshots of failing ref tests", "dump-failed-ref-tests", 'D'); + args_parser.add_option(dump_gc_graph, "Dump GC graph", "dump-gc-graph", 'G'); + args_parser.add_option(resources_folder, "Path of the base resources folder (defaults to /res)", "resources", 'r', "resources-root-path"); + args_parser.add_option(is_layout_test_mode, "Enable layout test mode", "layout-test-mode"); + } + + virtual void create_platform_options(WebView::ChromeOptions&, WebView::WebContentOptions& web_content_options) override + { + if (!test_root_path.is_empty()) { + // --run-tests implies --layout-test-mode. + is_layout_test_mode = true; + } + + web_content_options.is_layout_test_mode = is_layout_test_mode ? WebView::IsLayoutTestMode::Yes : WebView::IsLayoutTestMode::No; + } + + int screenshot_timeout { 1 }; + ByteString resources_folder { s_ladybird_resource_root }; + bool dump_failed_ref_tests { false }; + bool dump_layout_tree { false }; + bool dump_text { false }; + bool dump_gc_graph { false }; + bool is_layout_test_mode { false }; StringView test_root_path; ByteString test_glob; - Vector certificates; +}; +Application::Application(Badge, Main::Arguments&) +{ +} + +ErrorOr serenity_main(Main::Arguments arguments) +{ platform_init(); - resources_folder = s_ladybird_resource_root; - Core::ArgsParser args_parser; - args_parser.set_general_help("This utility runs the Browser in headless mode."); - args_parser.add_option(screenshot_timeout, "Take a screenshot after [n] seconds (default: 1)", "screenshot", 's', "n"); - args_parser.add_option(dump_layout_tree, "Dump layout tree and exit", "dump-layout-tree", 'd'); - args_parser.add_option(dump_text, "Dump text and exit", "dump-text", 'T'); - args_parser.add_option(test_root_path, "Run tests in path", "run-tests", 'R', "test-root-path"); - args_parser.add_option(test_glob, "Only run tests matching the given glob", "filter", 'f', "glob"); - args_parser.add_option(dump_failed_ref_tests, "Dump screenshots of failing ref tests", "dump-failed-ref-tests", 'D'); - args_parser.add_option(dump_gc_graph, "Dump GC graph", "dump-gc-graph", 'G'); - args_parser.add_option(resources_folder, "Path of the base resources folder (defaults to /res)", "resources", 'r', "resources-root-path"); - args_parser.add_option(web_driver_ipc_path, "Path to the WebDriver IPC socket", "webdriver-ipc-path", 0, "path"); - args_parser.add_option(is_layout_test_mode, "Enable layout test mode", "layout-test-mode"); - args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate"); - args_parser.add_positional_argument(raw_url, "URL to open", "url", Core::ArgsParser::Required::No); - args_parser.parse(arguments); + auto app = Application::create(arguments, "about:newtab"sv); - Core::ResourceImplementation::install(make(MUST(String::from_utf8(resources_folder)))); + Core::ResourceImplementation::install(make(MUST(String::from_byte_string(app->resources_folder)))); - auto theme_path = LexicalPath::join(resources_folder, "themes"sv, "Default.ini"sv); + auto theme_path = LexicalPath::join(app->resources_folder, "themes"sv, "Default.ini"sv); auto theme = TRY(Gfx::load_system_theme(theme_path.string())); // FIXME: Allow passing the window size as an argument. static constexpr Gfx::IntSize window_size { 800, 600 }; - if (!test_root_path.is_empty()) { - // --run-tests implies --layout-test-mode. - is_layout_test_mode = true; + auto view = TRY(HeadlessWebContentView::create(move(theme), window_size, app->resources_folder)); + + if (!app->test_root_path.is_empty()) { + auto test_glob = ByteString::formatted("*{}*", app->test_glob); + return run_tests(*view, app->test_root_path, test_glob, app->dump_failed_ref_tests, app->dump_gc_graph); } - StringBuilder command_line_builder; - command_line_builder.join(' ', arguments.strings); - auto view = TRY(HeadlessWebContentView::create(move(theme), window_size, MUST(command_line_builder.to_string()), web_driver_ipc_path, is_layout_test_mode ? Ladybird::IsLayoutTestMode::Yes : Ladybird::IsLayoutTestMode::No, certificates, resources_folder)); - - if (!test_root_path.is_empty()) { - test_glob = ByteString::formatted("*{}*", test_glob); - return run_tests(*view, test_root_path, test_glob, dump_failed_ref_tests, dump_gc_graph); - } - - auto url = WebView::sanitize_url(raw_url); - if (!url.has_value()) { - warnln("Invalid URL: \"{}\"", raw_url); + VERIFY(!WebView::Application::chrome_options().urls.is_empty()); + auto const& url = WebView::Application::chrome_options().urls.first(); + if (!url.is_valid()) { + warnln("Invalid URL: \"{}\"", url); return Error::from_string_literal("Invalid URL"); } - if (dump_layout_tree) { - TRY(run_dump_test(*view, raw_url, ""sv, TestMode::Layout)); + if (app->dump_layout_tree) { + TRY(run_dump_test(*view, url, ""sv, TestMode::Layout)); return 0; } - if (dump_text) { - TRY(run_dump_test(*view, raw_url, ""sv, TestMode::Text)); + if (app->dump_text) { + TRY(run_dump_test(*view, url, ""sv, TestMode::Text)); return 0; } - if (web_driver_ipc_path.is_empty()) { - auto timer = TRY(load_page_for_screenshot_and_exit(Core::EventLoop::current(), *view, url.value(), screenshot_timeout)); - return app.exec(); + if (!WebView::Application::chrome_options().webdriver_content_ipc_path.has_value()) { + auto timer = TRY(load_page_for_screenshot_and_exit(Core::EventLoop::current(), *view, url, app->screenshot_timeout)); + return app->execute(); } return 0;