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.
This commit is contained in:
Timothy Flynn 2024-07-30 14:01:05 -04:00 committed by Andreas Kling
parent 0e640f6f70
commit 5f8d852dae
Notes: github-actions[bot] 2024-08-01 09:39:41 +00:00
35 changed files with 427 additions and 440 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,8 +7,9 @@
#pragma once
#include <AK/Error.h>
#include <AK/Vector.h>
#include <LibIPC/Forward.h>
#include <LibMain/Main.h>
#include <LibURL/URL.h>
#include <LibWebView/Forward.h>
#import <Cocoa/Cocoa.h>
@ -19,9 +20,10 @@ class WebViewBridge;
@interface Application : NSApplication
- (instancetype)init;
- (void)setupWebViewApplication:(Main::Arguments&)arguments
newTabPageURL:(URL::URL)new_tab_page_url;
- (ErrorOr<void>)launchRequestServer:(Vector<ByteString> const&)certificates;
- (ErrorOr<void>)launchRequestServer;
- (ErrorOr<void>)launchImageDecoder;
- (ErrorOr<NonnullRefPtr<WebView::WebContentClient>>)launchWebContent:(Ladybird::WebViewBridge&)web_view_bridge;
- (ErrorOr<IPC::File>)launchWebWorker;

View file

@ -1,10 +1,9 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <Application/ApplicationBridge.h>
#include <LibCore/EventLoop.h>
#include <LibCore/ThreadEventQueue.h>
@ -25,20 +24,17 @@
@implementation Application
- (instancetype)init
{
if (self = [super init]) {
m_application_bridge = make<Ladybird::ApplicationBridge>();
}
return self;
}
#pragma mark - Public methods
- (ErrorOr<void>)launchRequestServer:(Vector<ByteString> 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<void>)launchRequestServer
{
return m_application_bridge->launch_request_server();
}
- (ErrorOr<void>)launchImageDecoder

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <Application/ApplicationBridge.h>
#include <Ladybird/AppKit/UI/LadybirdWebViewBridge.h>
#include <Ladybird/HelperProcess.h>
@ -23,17 +22,17 @@ struct ApplicationBridgeImpl {
RefPtr<ImageDecoderClient::Client> image_decoder_client;
};
ApplicationBridge::ApplicationBridge()
ApplicationBridge::ApplicationBridge(Badge<WebView::Application>, Main::Arguments&)
: m_impl(make<ApplicationBridgeImpl>())
{
}
ApplicationBridge::~ApplicationBridge() = default;
ErrorOr<void> ApplicationBridge::launch_request_server(Vector<ByteString> const& certificates)
ErrorOr<void> 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<NonnullRefPtr<WebView::WebContentClient>> 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;
}

View file

@ -7,8 +7,8 @@
#pragma once
#include <AK/NonnullOwnPtr.h>
#include <AK/Vector.h>
#include <LibIPC/Forward.h>
#include <LibWebView/Application.h>
#include <LibWebView/Forward.h>
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<void> launch_request_server(Vector<ByteString> const& certificates);
ErrorOr<void> launch_request_server();
ErrorOr<void> launch_image_decoder();
ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content(WebViewBridge&);
ErrorOr<IPC::File> launch_web_worker();

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -8,8 +8,6 @@
#include <AK/Optional.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
#include <Ladybird/Types.h>
#include <LibURL/URL.h>
#include <LibWeb/CSS/PreferredColorScheme.h>
#include <LibWeb/CSS/PreferredContrast.h>
@ -24,12 +22,7 @@
@interface ApplicationDelegate : NSObject <NSApplicationDelegate>
- (nullable instancetype)init:(Vector<URL::URL>)initial_urls
newTabPageURL:(URL::URL)new_tab_page_url
withCookieJar:(NonnullOwnPtr<WebView::CookieJar>)cookie_jar
webContentOptions:(Ladybird::WebContentOptions const&)web_content_options
webdriverContentIPCPath:(StringView)webdriver_content_ipc_path
allowPopups:(BOOL)allow_popups;
- (nullable instancetype)initWithCookieJar:(NonnullOwnPtr<WebView::CookieJar>)cookie_jar;
- (nonnull TabController*)createNewTab:(Optional<URL::URL> const&)url
fromTab:(nullable Tab*)tab
@ -46,8 +39,6 @@
- (void)removeTab:(nonnull TabController*)controller;
- (WebView::CookieJar&)cookieJar;
- (Ladybird::WebContentOptions const&)webContentOptions;
- (Optional<StringView> const&)webdriverContentIPCPath;
- (Web::CSS::PreferredColorScheme)preferredColorScheme;
- (Web::CSS::PreferredContrast)preferredContrast;
- (Web::CSS::PreferredMotion)preferredMotion;

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWebView/Application.h>
#include <LibWebView/SearchEngine.h>
#import <Application/ApplicationDelegate.h>
@ -29,23 +30,15 @@
@interface ApplicationDelegate () <TaskManagerDelegate>
{
Vector<URL::URL> 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<WebView::CookieJar> m_cookie_jar;
Ladybird::WebContentOptions m_web_content_options;
Optional<StringView> 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<TabController*>* managed_tabs;
@ -68,12 +61,7 @@
@implementation ApplicationDelegate
- (instancetype)init:(Vector<URL::URL>)initial_urls
newTabPageURL:(URL::URL)new_tab_page_url
withCookieJar:(NonnullOwnPtr<WebView::CookieJar>)cookie_jar
webContentOptions:(Ladybird::WebContentOptions const&)web_content_options
webdriverContentIPCPath:(StringView)webdriver_content_ipc_path
allowPopups:(BOOL)allow_popups
- (instancetype)initWithCookieJar:(NonnullOwnPtr<WebView::CookieJar>)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<StringView> 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

View file

@ -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();

View file

@ -1,16 +1,16 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Ladybird/HelperProcess.h>
#include <Ladybird/Types.h>
#include <Ladybird/Utilities.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Rect.h>
#include <LibIPC/File.h>
#include <LibWeb/Crypto/Crypto.h>
#include <LibWebView/Application.h>
#include <UI/LadybirdWebViewBridge.h>
#import <UI/Palette.h>
@ -23,15 +23,13 @@ static T scale_for_device(T size, float device_pixel_ratio)
return size.template to_type<float>().scaled(device_pixel_ratio).template to_type<int>();
}
ErrorOr<NonnullOwnPtr<WebViewBridge>> WebViewBridge::create(Vector<Web::DevicePixelRect> screen_rects, float device_pixel_ratio, WebContentOptions const& web_content_options, Optional<StringView> webdriver_content_ipc_path, Web::CSS::PreferredColorScheme preferred_color_scheme, Web::CSS::PreferredContrast preferred_contrast, Web::CSS::PreferredMotion preferred_motion)
ErrorOr<NonnullOwnPtr<WebViewBridge>> WebViewBridge::create(Vector<Web::DevicePixelRect> 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<Web::DevicePixelRect> screen_rects, float device_pixel_ratio, WebContentOptions const& web_content_options, Optional<StringView> webdriver_content_ipc_path, Web::CSS::PreferredColorScheme preferred_color_scheme, Web::CSS::PreferredContrast preferred_contrast, Web::CSS::PreferredMotion preferred_motion)
WebViewBridge::WebViewBridge(Vector<Web::DevicePixelRect> 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);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,7 +7,6 @@
#pragma once
#include <AK/Vector.h>
#include <Ladybird/Types.h>
#include <LibGfx/Point.h>
#include <LibGfx/Rect.h>
#include <LibGfx/Size.h>
@ -22,13 +21,11 @@ namespace Ladybird {
class WebViewBridge final : public WebView::ViewImplementation {
public:
static ErrorOr<NonnullOwnPtr<WebViewBridge>> create(Vector<Web::DevicePixelRect> screen_rects, float device_pixel_ratio, WebContentOptions const&, Optional<StringView> webdriver_content_ipc_path, Web::CSS::PreferredColorScheme, Web::CSS::PreferredContrast, Web::CSS::PreferredMotion);
static ErrorOr<NonnullOwnPtr<WebViewBridge>> create(Vector<Web::DevicePixelRect> 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<void()> on_zoom_level_changed;
private:
WebViewBridge(Vector<Web::DevicePixelRect> screen_rects, float device_pixel_ratio, WebContentOptions const&, Optional<StringView> webdriver_content_ipc_path, Web::CSS::PreferredColorScheme, Web::CSS::PreferredContrast, Web::CSS::PreferredMotion);
WebViewBridge(Vector<Web::DevicePixelRect> 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<Web::DevicePixelRect> m_screen_rects;
Gfx::IntSize m_viewport_size;
WebContentOptions m_web_content_options;
Optional<StringView> 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 };

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -22,7 +22,7 @@ struct TabSettings {
@interface TabController : NSWindowController <NSWindowDelegate>
- (instancetype)init:(BOOL)block_popups;
- (instancetype)init;
- (void)loadURL:(URL::URL const&)url;
- (void)loadHTML:(StringView)html url:(URL::URL const&)url;

View file

@ -1,10 +1,11 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Loader/UserAgent.h>
#include <LibWebView/Application.h>
#include <LibWebView/SearchEngine.h>
#include <LibWebView/URL.h>
#include <LibWebView/UserAgent.h>
@ -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;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,9 +7,7 @@
#include <AK/Enumerate.h>
#include <Ladybird/DefaultSettings.h>
#include <Ladybird/MachPortServer.h>
#include <Ladybird/Types.h>
#include <Ladybird/Utilities.h>
#include <LibCore/ArgsParser.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibMain/Main.h>
#include <LibWebView/Application.h>
@ -30,33 +28,10 @@
# error "This project requires ARC"
#endif
static Vector<URL::URL> sanitize_urls(Vector<ByteString> const& raw_urls)
{
Vector<URL::URL> 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<ByteString> const& raw_urls, NewWindow new_window)
static void open_urls_from_client(Vector<URL::URL> 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<int> 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<ByteString> raw_urls;
Vector<ByteString> 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<Ladybird::MachPortServer>();
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<int> 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();
}

View file

@ -6,7 +6,6 @@ set(LADYBIRD_SOURCES
)
set(LADYBIRD_HEADERS
HelperProcess.h
Types.h
Utilities.h
)

View file

@ -15,10 +15,10 @@ static ErrorOr<NonnullRefPtr<ClientType>> launch_server_process(
StringView server_name,
ReadonlySpan<ByteString> candidate_server_paths,
Vector<ByteString> 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<NonnullRefPtr<ClientType>> 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<NonnullRefPtr<ClientType>> 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<NonnullRefPtr<ClientType>> launch_server_process(
ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
WebView::ViewImplementation& view,
ReadonlySpan<ByteString> candidate_web_content_paths,
Ladybird::WebContentOptions const& web_content_options,
IPC::File image_decoder_socket,
Optional<IPC::File> request_server_socket)
{
auto const& web_content_options = WebView::Application::web_content_options();
Vector<ByteString> arguments {
"--command-line"sv,
web_content_options.command_line.to_byte_string(),
@ -84,19 +85,19 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> 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<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process(
arguments.append(server.value());
}
return launch_server_process<ImageDecoderClient::Client>("ImageDecoder"sv, candidate_image_decoder_paths, arguments, Ladybird::EnableCallgrindProfiling::No);
return launch_server_process<ImageDecoderClient::Client>("ImageDecoder"sv, candidate_image_decoder_paths, arguments, WebView::EnableCallgrindProfiling::No);
}
ErrorOr<NonnullRefPtr<Web::HTML::WebWorkerClient>> launch_web_worker_process(ReadonlySpan<ByteString> candidate_web_worker_paths, RefPtr<Protocol::RequestClient> request_client)
@ -132,13 +133,13 @@ ErrorOr<NonnullRefPtr<Web::HTML::WebWorkerClient>> 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<Web::HTML::WebWorkerClient>("WebWorker"sv, candidate_web_worker_paths, move(arguments), Ladybird::EnableCallgrindProfiling::No);
return launch_server_process<Web::HTML::WebWorkerClient>("WebWorker"sv, candidate_web_worker_paths, move(arguments), WebView::EnableCallgrindProfiling::No);
}
return launch_server_process<Web::HTML::WebWorkerClient>("WebWorker"sv, candidate_web_worker_paths, move(arguments), Ladybird::EnableCallgrindProfiling::No);
return launch_server_process<Web::HTML::WebWorkerClient>("WebWorker"sv, candidate_web_worker_paths, move(arguments), WebView::EnableCallgrindProfiling::No);
}
ErrorOr<NonnullRefPtr<Protocol::RequestClient>> launch_request_server_process(ReadonlySpan<ByteString> candidate_request_server_paths, StringView serenity_resource_root, Vector<ByteString> const& certificates)
ErrorOr<NonnullRefPtr<Protocol::RequestClient>> launch_request_server_process(ReadonlySpan<ByteString> candidate_request_server_paths, StringView serenity_resource_root)
{
Vector<ByteString> arguments;
@ -147,7 +148,7 @@ ErrorOr<NonnullRefPtr<Protocol::RequestClient>> 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<NonnullRefPtr<Protocol::RequestClient>> launch_request_server_process(Re
arguments.append(server.value());
}
return launch_server_process<Protocol::RequestClient>("RequestServer"sv, candidate_request_server_paths, move(arguments), Ladybird::EnableCallgrindProfiling::No);
return launch_server_process<Protocol::RequestClient>("RequestServer"sv, candidate_request_server_paths, move(arguments), WebView::EnableCallgrindProfiling::No);
}
ErrorOr<IPC::File> connect_new_request_server_client(Protocol::RequestClient& client)

View file

@ -6,7 +6,6 @@
#pragma once
#include "Types.h"
#include <AK/Error.h>
#include <AK/Optional.h>
#include <AK/Span.h>
@ -20,13 +19,12 @@
ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
WebView::ViewImplementation& view,
ReadonlySpan<ByteString> candidate_web_content_paths,
Ladybird::WebContentOptions const&,
IPC::File image_decoder_socket,
Optional<IPC::File> request_server_socket = {});
ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process(ReadonlySpan<ByteString> candidate_image_decoder_paths);
ErrorOr<NonnullRefPtr<Web::HTML::WebWorkerClient>> launch_web_worker_process(ReadonlySpan<ByteString> candidate_web_worker_paths, RefPtr<Protocol::RequestClient>);
ErrorOr<NonnullRefPtr<Protocol::RequestClient>> launch_request_server_process(ReadonlySpan<ByteString> candidate_request_server_paths, StringView serenity_resource_root, Vector<ByteString> const& certificates);
ErrorOr<NonnullRefPtr<Protocol::RequestClient>> launch_request_server_process(ReadonlySpan<ByteString> candidate_request_server_paths, StringView serenity_resource_root);
ErrorOr<IPC::File> connect_new_request_server_client(Protocol::RequestClient&);
ErrorOr<IPC::File> connect_new_image_decoder_client(ImageDecoderClient::Client&);

View file

@ -4,21 +4,34 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Application.h"
#include "StringUtils.h"
#include "TaskManagerWindow.h"
#include <Ladybird/HelperProcess.h>
#include <Ladybird/Qt/Application.h>
#include <Ladybird/Qt/Settings.h>
#include <Ladybird/Qt/StringUtils.h>
#include <Ladybird/Qt/TaskManagerWindow.h>
#include <Ladybird/Utilities.h>
#include <LibCore/ArgsParser.h>
#include <LibWebView/URL.h>
#include <QFileOpenEvent>
namespace Ladybird {
Application::Application(int& argc, char** argv)
: QApplication(argc, argv)
Application::Application(Badge<WebView::Application>, 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<void> 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<URL::URL> 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<u64> page_index)
BrowserWindow& Application::new_window(Vector<URL::URL> const& initial_urls, WebView::CookieJar& cookie_jar, BrowserWindow::IsPopupWindow is_popup_window, Tab* parent_tab, Optional<u64> 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()) {

View file

@ -12,15 +12,18 @@
#include <LibImageDecoderClient/Client.h>
#include <LibProtocol/RequestClient.h>
#include <LibURL/URL.h>
#include <LibWebView/Application.h>
#include <QApplication>
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<ImageDecoderClient::Client> image_decoder_client() const { return *m_image_decoder_client; }
ErrorOr<void> initialize_image_decoder();
BrowserWindow& new_window(Vector<URL::URL> 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<u64> page_index = {});
BrowserWindow& new_window(Vector<URL::URL> const& initial_urls, WebView::CookieJar&, BrowserWindow::IsPopupWindow is_popup_window = BrowserWindow::IsPopupWindow::No, Tab* parent_tab = nullptr, Optional<u64> 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 };

View file

@ -23,6 +23,7 @@
#include <LibWeb/CSS/PreferredContrast.h>
#include <LibWeb/CSS/PreferredMotion.h>
#include <LibWeb/Loader/UserAgent.h>
#include <LibWebView/Application.h>
#include <LibWebView/CookieJar.h>
#include <LibWebView/UserAgent.h>
#include <QAction>
@ -71,13 +72,10 @@ public:
}
};
BrowserWindow::BrowserWindow(Vector<URL::URL> 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<u64> page_index)
BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, WebView::CookieJar& cookie_jar, IsPopupWindow is_popup_window, Tab* parent_tab, Optional<u64> 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<URL::URL> 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<Ladybird::Application*>(QApplication::instance())->show_task_manager_window(m_web_content_options);
static_cast<Ladybird::Application*>(QApplication::instance())->show_task_manager_window();
});
auto* debug_menu = m_hamburger_menu->addMenu("&Debug");
@ -556,7 +554,7 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> 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<URL::URL> const& initial_urls, WebView::Cook
tab.focus_location_editor();
});
QObject::connect(m_new_window_action, &QAction::triggered, this, [this] {
(void)static_cast<Ladybird::Application*>(QApplication::instance())->new_window({}, m_cookie_jar, m_web_content_options, m_webdriver_content_ipc_path, m_allow_popups);
(void)static_cast<Ladybird::Application*>(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<URL::URL> 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<u64> page_index) {
if (hints.popup) {
auto& window = static_cast<Ladybird::Application*>(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<Ladybird::Application*>(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();
}

View file

@ -9,7 +9,6 @@
#include "Tab.h"
#include <Ladybird/Qt/FindInPageWidget.h>
#include <Ladybird/Types.h>
#include <LibCore/Forward.h>
#include <LibWeb/HTML/ActivateTab.h>
#include <LibWeb/HTML/AudioPlayState.h>
@ -36,7 +35,7 @@ public:
Yes,
};
BrowserWindow(Vector<URL::URL> 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<u64> page_index = {});
BrowserWindow(Vector<URL::URL> const& initial_urls, WebView::CookieJar&, IsPopupWindow is_popup_window = IsPopupWindow::No, Tab* parent_tab = nullptr, Optional<u64> 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 };
};

View file

@ -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);

View file

@ -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<WebView::WebContentClient> parent_client, size_t page_index)
Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> 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);

View file

@ -29,7 +29,7 @@ class Tab final : public QWidget {
Q_OBJECT
public:
Tab(BrowserWindow* window, WebContentOptions const&, StringView webdriver_content_ipc_path, RefPtr<WebView::WebContentClient> parent_client = nullptr, size_t page_index = 0);
Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client = nullptr, size_t page_index = 0);
virtual ~Tab() override;
WebContentView& view() { return *m_view; }

View file

@ -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);

View file

@ -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;

View file

@ -30,6 +30,7 @@
#include <LibWeb/UIEvents/KeyCode.h>
#include <LibWeb/UIEvents/MouseButton.h>
#include <LibWeb/Worker/WebWorkerClient.h>
#include <LibWebView/Application.h>
#include <LibWebView/WebContentClient.h>
#include <QApplication>
#include <QCursor>
@ -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<WebView::WebContentClient> parent_client, size_t page_index)
WebContentView::WebContentView(QWidget* window, RefPtr<WebView::WebContentClient> 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<Protocol::RequestClient> 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<Ladybird::Application*>(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<IPC::File> 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<Ladybird::Application*>(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)

View file

@ -11,7 +11,6 @@
#include <AK/Function.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <Ladybird/Types.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Rect.h>
#include <LibGfx/StandardCursor.h>
@ -46,7 +45,7 @@ class WebContentView final
, public WebView::ViewImplementation {
Q_OBJECT
public:
WebContentView(QWidget* window, WebContentOptions const&, StringView webdriver_content_ipc_path, RefPtr<WebView::WebContentClient> parent_client = nullptr, size_t page_index = 0);
WebContentView(QWidget* window, RefPtr<WebView::WebContentClient> parent_client = nullptr, size_t page_index = 0);
virtual ~WebContentView() override;
Function<String(const URL::URL&, Web::HTML::ActivateTab)> 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<QUrl> 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;
};
}

View file

@ -62,72 +62,32 @@ static ErrorOr<void> handle_attached_debugger()
return {};
}
static Vector<URL::URL> sanitize_urls(Vector<ByteString> const& raw_urls)
{
Vector<URL::URL> 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<int> 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<Ladybird::EventLoopImplementationQt&>(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<Ladybird::EventLoopImplementationQt&>(Core::EventLoop::current().impl()).set_main_loop();
TRY(handle_attached_debugger());
platform_init();
Vector<ByteString> raw_urls;
StringView webdriver_content_ipc_path;
Vector<ByteString> 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<int> 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<Ladybird::MachPortServer>();
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<int> serenity_main(Main::Arguments arguments)
copy_default_config_files(Ladybird::Settings::the()->directory());
RefPtr<WebView::Database> 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<int> serenity_main(Main::Arguments arguments)
window.show();
return webview_app.exec();
return app->execute();
}

View file

@ -5,15 +5,17 @@
*/
#include <AK/Debug.h>
#include <LibCore/ArgsParser.h>
#include <LibImageDecoderClient/Client.h>
#include <LibWebView/Application.h>
#include <LibWebView/URL.h>
#include <LibWebView/WebContentClient.h>
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<ByteString> raw_urls;
Vector<ByteString> certificates;
bool new_window = false;
bool force_new_process = false;
bool allow_popups = false;
bool disable_sql_database = false;
Optional<StringView> 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;

View file

@ -6,7 +6,11 @@
#pragma once
#include <AK/Badge.h>
#include <LibCore/EventLoop.h>
#include <LibMain/Main.h>
#include <LibURL/URL.h>
#include <LibWebView/Options.h>
#include <LibWebView/Process.h>
#include <LibWebView/ProcessManager.h>
@ -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<DerivedFrom<Application> ApplicationType>
static NonnullOwnPtr<ApplicationType> 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<ApplicationType> create(Main::Arguments& arguments, URL::URL new_tab_page_url) \
{ \
return WebView::Application::create<ApplicationType>(arguments, move(new_tab_page_url)); \
} \
\
ApplicationType(Badge<WebView::Application>, Main::Arguments&);

View file

@ -9,7 +9,9 @@
#include <LibCore/StandardPaths.h>
#include <LibCore/System.h>
#include <LibIPC/ConnectionToServer.h>
#include <LibWebView/Application.h>
#include <LibWebView/ChromeProcess.h>
#include <LibWebView/URL.h>
namespace WebView {
@ -32,7 +34,7 @@ ErrorOr<ChromeProcess> ChromeProcess::create()
return ChromeProcess {};
}
ErrorOr<ChromeProcess::ProcessDisposition> ChromeProcess::connect(Vector<ByteString> const& raw_urls, bool new_window)
ErrorOr<ChromeProcess::ProcessDisposition> ChromeProcess::connect(Vector<ByteString> const& raw_urls, NewWindow new_window)
{
static constexpr auto process_name = "Ladybird"sv;
@ -52,12 +54,12 @@ ErrorOr<ChromeProcess::ProcessDisposition> ChromeProcess::connect(Vector<ByteStr
return ProcessDisposition::ContinueMainProcess;
}
ErrorOr<void> ChromeProcess::connect_as_client(ByteString const& socket_path, Vector<ByteString> const& raw_urls, bool new_window)
ErrorOr<void> ChromeProcess::connect_as_client(ByteString const& socket_path, Vector<ByteString> 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<Messages::UIProcessServer::CreateNewWindow>(raw_urls))
dbgln("Failed to send CreateNewWindow message to UIProcess");
} else {
@ -121,13 +123,13 @@ void UIProcessConnectionFromClient::die()
void UIProcessConnectionFromClient::create_new_tab(Vector<ByteString> 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<ByteString> const& urls)
{
if (on_new_window)
on_new_window(urls);
on_new_window(sanitize_urls(urls, Application::chrome_options().new_tab_page_url));
}
}

View file

@ -14,6 +14,7 @@
#include <LibIPC/ConnectionFromClient.h>
#include <LibIPC/Forward.h>
#include <LibIPC/MultiServer.h>
#include <LibWebView/Options.h>
#include <LibWebView/UIProcessClientEndpoint.h>
#include <LibWebView/UIProcessServerEndpoint.h>
@ -28,8 +29,8 @@ public:
virtual void die() override;
Function<void(Vector<ByteString> const& urls)> on_new_tab;
Function<void(Vector<ByteString> const& urls)> on_new_window;
Function<void(Vector<URL::URL> const&)> on_new_tab;
Function<void(Vector<URL::URL> const&)> on_new_window;
private:
UIProcessConnectionFromClient(NonnullOwnPtr<Core::LocalSocket>, int client_id);
@ -51,15 +52,15 @@ public:
static ErrorOr<ChromeProcess> create();
~ChromeProcess();
ErrorOr<ProcessDisposition> connect(Vector<ByteString> const& raw_urls, bool new_window);
ErrorOr<ProcessDisposition> connect(Vector<ByteString> const& raw_urls, NewWindow new_window);
Function<void(Vector<ByteString> const& raw_urls)> on_new_tab;
Function<void(Vector<ByteString> const& raw_urls)> on_new_window;
Function<void(Vector<URL::URL> const&)> on_new_tab;
Function<void(Vector<URL::URL> const&)> on_new_window;
private:
ChromeProcess() = default;
ErrorOr<void> connect_as_client(ByteString const& socket_path, Vector<ByteString> const& raw_urls, bool new_window);
ErrorOr<void> connect_as_client(ByteString const& socket_path, Vector<ByteString> const& raw_urls, NewWindow new_window);
ErrorOr<void> connect_as_server(ByteString const& socket_path);
OwnPtr<IPC::MultiServer<UIProcessConnectionFromClient>> m_server_connection;

View file

@ -6,48 +6,84 @@
#pragma once
#include <AK/ByteString.h>
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibURL/URL.h>
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<URL::URL> urls;
Vector<ByteString> raw_urls;
URL::URL new_tab_page_url;
Vector<ByteString> certificates {};
NewWindow new_window { NewWindow::No };
ForceNewProcess force_new_process { ForceNewProcess::No };
AllowPopups allow_popups { AllowPopups::No };
DisableSQLDatabase disable_sql_database { DisableSQLDatabase::No };
Optional<ByteString> 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 {

View file

@ -72,6 +72,22 @@ Optional<URL::URL> sanitize_url(StringView url, Optional<StringView> search_engi
return result;
}
Vector<URL::URL> sanitize_urls(ReadonlySpan<ByteString> raw_urls, URL::URL const& new_tab_page_url)
{
Vector<URL::URL> 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());

View file

@ -20,6 +20,7 @@ enum class AppendTLD {
Yes,
};
Optional<URL::URL> sanitize_url(StringView, Optional<StringView> search_engine = {}, AppendTLD = AppendTLD::No);
Vector<URL::URL> sanitize_urls(ReadonlySpan<ByteString> raw_urls, URL::URL const& new_tab_page_url);
struct URLParts {
StringView scheme_and_subdomain;

View file

@ -19,7 +19,6 @@
#include <AK/String.h>
#include <AK/Vector.h>
#include <Ladybird/HelperProcess.h>
#include <Ladybird/Types.h>
#include <Ladybird/Utilities.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/ConfigFile.h>
@ -64,13 +63,13 @@ static StringView s_current_test_path;
class HeadlessWebContentView final : public WebView::ViewImplementation {
public:
static ErrorOr<NonnullOwnPtr<HeadlessWebContentView>> 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<ByteString> const& certificates = {}, StringView resources_folder = {})
static ErrorOr<NonnullOwnPtr<HeadlessWebContentView>> create(Core::AnonymousBuffer theme, Gfx::IntSize const& window_size, StringView resources_folder)
{
RefPtr<Protocol::RequestClient> request_client;
RefPtr<ImageDecoderClient::Client> 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<Web::DevicePixels>());
view->client().async_set_window_size(0, window_size.to_type<Web::DevicePixels>());
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<ImageDecoderClient::Client> m_image_decoder_client;
};
static ErrorOr<NonnullRefPtr<Core::Timer>> load_page_for_screenshot_and_exit(Core::EventLoop& event_loop, HeadlessWebContentView& view, URL::URL url, int screenshot_timeout)
static ErrorOr<NonnullRefPtr<Core::Timer>> 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<TestResult> run_dump_test(HeadlessWebContentView& view, StringView input_path, StringView expectation_path, TestMode mode, int timeout_in_milliseconds = DEFAULT_TIMEOUT_MS)
static ErrorOr<TestResult> 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<TestResult> 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<TestResult> 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<TestResult> run_dump_test(HeadlessWebContentView& view, StringVie
return TestResult::Fail;
}
static ErrorOr<TestResult> run_ref_test(HeadlessWebContentView& view, StringView input_path, bool dump_failed_ref_tests, int timeout_in_milliseconds = DEFAULT_TIMEOUT_MS)
static ErrorOr<TestResult> 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<TestResult> 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<TestResult> 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<void> {
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<TestResult> 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<int> run_tests(HeadlessWebContentView& view, StringView test_root
return 1;
}
ErrorOr<int> 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<ByteString> certificates;
};
Application::Application(Badge<WebView::Application>, Main::Arguments&)
{
}
ErrorOr<int> 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<Core::ResourceImplementationFile>(MUST(String::from_utf8(resources_folder))));
Core::ResourceImplementation::install(make<Core::ResourceImplementationFile>(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;