mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-25 03:36:36 +00:00
Everywhere: Move the Ladybird folder to UI
This commit is contained in:
parent
93712b24bf
commit
db47cc41f8
Notes:
github-actions[bot]
2024-11-10 11:51:45 +00:00
Author: https://github.com/trflynn89
Commit: db47cc41f8
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2256
Reviewed-by: https://github.com/sideshowbarker
203 changed files with 266 additions and 244 deletions
31
UI/AppKit/Application/Application.h
Normal file
31
UI/AppKit/Application/Application.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Error.h>
|
||||
#include <LibIPC/Forward.h>
|
||||
#include <LibMain/Main.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
namespace Ladybird {
|
||||
class WebViewBridge;
|
||||
}
|
||||
|
||||
@interface Application : NSApplication
|
||||
|
||||
- (void)setupWebViewApplication:(Main::Arguments&)arguments
|
||||
newTabPageURL:(URL::URL)new_tab_page_url;
|
||||
|
||||
- (ErrorOr<void>)launchRequestServer;
|
||||
- (ErrorOr<void>)launchImageDecoder;
|
||||
- (ErrorOr<NonnullRefPtr<WebView::WebContentClient>>)launchWebContent:(Ladybird::WebViewBridge&)web_view_bridge;
|
||||
- (ErrorOr<IPC::File>)launchWebWorker;
|
||||
|
||||
@end
|
156
UI/AppKit/Application/Application.mm
Normal file
156
UI/AppKit/Application/Application.mm
Normal file
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <Interface/LadybirdWebViewBridge.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/ThreadEventQueue.h>
|
||||
#include <LibImageDecoderClient/Client.h>
|
||||
#include <LibRequests/RequestClient.h>
|
||||
#include <LibWebView/Application.h>
|
||||
#include <LibWebView/WebContentClient.h>
|
||||
#include <UI/HelperProcess.h>
|
||||
#include <UI/Utilities.h>
|
||||
#include <Utilities/Conversions.h>
|
||||
|
||||
#import <Application/Application.h>
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
# error "This project requires ARC"
|
||||
#endif
|
||||
|
||||
namespace Ladybird {
|
||||
|
||||
class ApplicationBridge : public WebView::Application {
|
||||
WEB_VIEW_APPLICATION(ApplicationBridge)
|
||||
|
||||
private:
|
||||
virtual Optional<ByteString> ask_user_for_download_folder() const override
|
||||
{
|
||||
auto* panel = [NSOpenPanel openPanel];
|
||||
[panel setAllowsMultipleSelection:NO];
|
||||
[panel setCanChooseDirectories:YES];
|
||||
[panel setCanChooseFiles:NO];
|
||||
[panel setMessage:@"Select download directory"];
|
||||
|
||||
if ([panel runModal] != NSModalResponseOK)
|
||||
return {};
|
||||
|
||||
return Ladybird::ns_string_to_byte_string([[panel URL] path]);
|
||||
}
|
||||
};
|
||||
|
||||
ApplicationBridge::ApplicationBridge(Badge<WebView::Application>, Main::Arguments&)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@interface Application ()
|
||||
{
|
||||
OwnPtr<Ladybird::ApplicationBridge> m_application_bridge;
|
||||
|
||||
RefPtr<Requests::RequestClient> m_request_server_client;
|
||||
RefPtr<ImageDecoderClient::Client> m_image_decoder_client;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation Application
|
||||
|
||||
#pragma mark - Public methods
|
||||
|
||||
- (void)setupWebViewApplication:(Main::Arguments&)arguments
|
||||
newTabPageURL:(URL::URL)new_tab_page_url
|
||||
{
|
||||
m_application_bridge = Ladybird::ApplicationBridge::create(arguments, move(new_tab_page_url));
|
||||
}
|
||||
|
||||
- (ErrorOr<void>)launchRequestServer
|
||||
{
|
||||
auto request_server_paths = TRY(get_paths_for_helper_process("RequestServer"sv));
|
||||
m_request_server_client = TRY(launch_request_server_process(request_server_paths, s_ladybird_resource_root));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_new_image_decoder()
|
||||
{
|
||||
auto image_decoder_paths = TRY(get_paths_for_helper_process("ImageDecoder"sv));
|
||||
return launch_image_decoder_process(image_decoder_paths);
|
||||
}
|
||||
|
||||
- (ErrorOr<void>)launchImageDecoder
|
||||
{
|
||||
m_image_decoder_client = TRY(launch_new_image_decoder());
|
||||
|
||||
__weak Application* weak_self = self;
|
||||
|
||||
m_image_decoder_client->on_death = [weak_self]() {
|
||||
Application* self = weak_self;
|
||||
if (self == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_image_decoder_client = nullptr;
|
||||
|
||||
if (auto err = [self launchImageDecoder]; err.is_error()) {
|
||||
dbgln("Failed to restart image decoder: {}", err.error());
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
auto num_clients = WebView::WebContentClient::client_count();
|
||||
auto new_sockets = m_image_decoder_client->send_sync_but_allow_failure<Messages::ImageDecoderServer::ConnectNewClients>(num_clients);
|
||||
if (!new_sockets || new_sockets->sockets().size() == 0) {
|
||||
dbgln("Failed to connect {} new clients to ImageDecoder", num_clients);
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
WebView::WebContentClient::for_each_client([sockets = new_sockets->take_sockets()](WebView::WebContentClient& client) mutable {
|
||||
client.async_connect_to_image_decoder(sockets.take_last());
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
- (ErrorOr<NonnullRefPtr<WebView::WebContentClient>>)launchWebContent:(Ladybird::WebViewBridge&)web_view_bridge
|
||||
{
|
||||
// FIXME: Fail to open the tab, rather than crashing the whole application if this fails
|
||||
auto request_server_socket = TRY(connect_new_request_server_client(*m_request_server_client));
|
||||
auto image_decoder_socket = TRY(connect_new_image_decoder_client(*m_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, move(image_decoder_socket), move(request_server_socket)));
|
||||
|
||||
return web_content;
|
||||
}
|
||||
|
||||
- (ErrorOr<IPC::File>)launchWebWorker
|
||||
{
|
||||
auto web_worker_paths = TRY(get_paths_for_helper_process("WebWorker"sv));
|
||||
auto worker_client = TRY(launch_web_worker_process(web_worker_paths, *m_request_server_client));
|
||||
|
||||
return worker_client->clone_transport();
|
||||
}
|
||||
|
||||
#pragma mark - NSApplication
|
||||
|
||||
- (void)terminate:(id)sender
|
||||
{
|
||||
Core::EventLoop::current().quit(0);
|
||||
}
|
||||
|
||||
- (void)sendEvent:(NSEvent*)event
|
||||
{
|
||||
if ([event type] == NSEventTypeApplicationDefined) {
|
||||
Core::ThreadEventQueue::current().process();
|
||||
} else {
|
||||
[super sendEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
51
UI/AppKit/Application/ApplicationDelegate.h
Normal file
51
UI/AppKit/Application/ApplicationDelegate.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/CSS/PreferredColorScheme.h>
|
||||
#include <LibWeb/CSS/PreferredContrast.h>
|
||||
#include <LibWeb/CSS/PreferredMotion.h>
|
||||
#include <LibWeb/HTML/ActivateTab.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class Tab;
|
||||
@class TabController;
|
||||
|
||||
@interface ApplicationDelegate : NSObject <NSApplicationDelegate>
|
||||
|
||||
- (nullable instancetype)init;
|
||||
|
||||
- (nonnull TabController*)createNewTab:(Optional<URL::URL> const&)url
|
||||
fromTab:(nullable Tab*)tab
|
||||
activateTab:(Web::HTML::ActivateTab)activate_tab;
|
||||
|
||||
- (nonnull TabController*)createNewTab:(StringView)html
|
||||
url:(URL::URL const&)url
|
||||
fromTab:(nullable Tab*)tab
|
||||
activateTab:(Web::HTML::ActivateTab)activate_tab;
|
||||
|
||||
- (nonnull TabController*)createChildTab:(Optional<URL::URL> const&)url
|
||||
fromTab:(nonnull Tab*)tab
|
||||
activateTab:(Web::HTML::ActivateTab)activate_tab
|
||||
pageIndex:(u64)page_index;
|
||||
|
||||
- (void)setActiveTab:(nonnull Tab*)tab;
|
||||
- (nullable Tab*)activeTab;
|
||||
|
||||
- (void)removeTab:(nonnull TabController*)controller;
|
||||
|
||||
- (Web::CSS::PreferredColorScheme)preferredColorScheme;
|
||||
- (Web::CSS::PreferredContrast)preferredContrast;
|
||||
- (Web::CSS::PreferredMotion)preferredMotion;
|
||||
- (WebView::SearchEngine const&)searchEngine;
|
||||
|
||||
@end
|
802
UI/AppKit/Application/ApplicationDelegate.mm
Normal file
802
UI/AppKit/Application/ApplicationDelegate.mm
Normal file
|
@ -0,0 +1,802 @@
|
|||
/*
|
||||
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWebView/Application.h>
|
||||
#include <LibWebView/CookieJar.h>
|
||||
#include <LibWebView/SearchEngine.h>
|
||||
|
||||
#import <Application/ApplicationDelegate.h>
|
||||
#import <Interface/LadybirdWebView.h>
|
||||
#import <Interface/Tab.h>
|
||||
#import <Interface/TabController.h>
|
||||
#import <LibWebView/UserAgent.h>
|
||||
|
||||
#if defined(LADYBIRD_USE_SWIFT)
|
||||
// FIXME: Report this codegen error to Apple
|
||||
# define StyleMask NSWindowStyleMask
|
||||
# import <Ladybird-Swift.h>
|
||||
# undef StyleMask
|
||||
#else
|
||||
# import <Interface/TaskManagerController.h>
|
||||
#endif
|
||||
|
||||
#import <Utilities/Conversions.h>
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
# error "This project requires ARC"
|
||||
#endif
|
||||
|
||||
@interface ApplicationDelegate () <TaskManagerDelegate>
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray<TabController*>* managed_tabs;
|
||||
@property (nonatomic, weak) Tab* active_tab;
|
||||
|
||||
@property (nonatomic, strong) TaskManagerController* task_manager_controller;
|
||||
|
||||
- (NSMenuItem*)createApplicationMenu;
|
||||
- (NSMenuItem*)createFileMenu;
|
||||
- (NSMenuItem*)createEditMenu;
|
||||
- (NSMenuItem*)createViewMenu;
|
||||
- (NSMenuItem*)createSettingsMenu;
|
||||
- (NSMenuItem*)createHistoryMenu;
|
||||
- (NSMenuItem*)createInspectMenu;
|
||||
- (NSMenuItem*)createDebugMenu;
|
||||
- (NSMenuItem*)createWindowMenu;
|
||||
- (NSMenuItem*)createHelpMenu;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ApplicationDelegate
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
[NSApp setMainMenu:[[NSMenu alloc] init]];
|
||||
|
||||
[[NSApp mainMenu] addItem:[self createApplicationMenu]];
|
||||
[[NSApp mainMenu] addItem:[self createFileMenu]];
|
||||
[[NSApp mainMenu] addItem:[self createEditMenu]];
|
||||
[[NSApp mainMenu] addItem:[self createViewMenu]];
|
||||
[[NSApp mainMenu] addItem:[self createSettingsMenu]];
|
||||
[[NSApp mainMenu] addItem:[self createHistoryMenu]];
|
||||
[[NSApp mainMenu] addItem:[self createInspectMenu]];
|
||||
[[NSApp mainMenu] addItem:[self createDebugMenu]];
|
||||
[[NSApp mainMenu] addItem:[self createWindowMenu]];
|
||||
[[NSApp mainMenu] addItem:[self createHelpMenu]];
|
||||
|
||||
self.managed_tabs = [[NSMutableArray alloc] init];
|
||||
|
||||
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();
|
||||
|
||||
// Reduce the tooltip delay, as the default delay feels quite long.
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@100 forKey:@"NSInitialToolTipDelay"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Public methods
|
||||
|
||||
- (TabController*)createNewTab:(Optional<URL::URL> const&)url
|
||||
fromTab:(Tab*)tab
|
||||
activateTab:(Web::HTML::ActivateTab)activate_tab
|
||||
{
|
||||
auto* controller = [self createNewTab:activate_tab fromTab:tab];
|
||||
|
||||
if (url.has_value()) {
|
||||
[controller loadURL:*url];
|
||||
}
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
- (nonnull TabController*)createNewTab:(StringView)html
|
||||
url:(URL::URL const&)url
|
||||
fromTab:(nullable Tab*)tab
|
||||
activateTab:(Web::HTML::ActivateTab)activate_tab
|
||||
{
|
||||
auto* controller = [self createNewTab:activate_tab fromTab:tab];
|
||||
[controller loadHTML:html url:url];
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
- (nonnull TabController*)createChildTab:(Optional<URL::URL> const&)url
|
||||
fromTab:(nonnull Tab*)tab
|
||||
activateTab:(Web::HTML::ActivateTab)activate_tab
|
||||
pageIndex:(u64)page_index
|
||||
{
|
||||
auto* controller = [self createChildTab:activate_tab fromTab:tab pageIndex:page_index];
|
||||
|
||||
if (url.has_value()) {
|
||||
[controller loadURL:*url];
|
||||
}
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
- (void)setActiveTab:(Tab*)tab
|
||||
{
|
||||
self.active_tab = tab;
|
||||
}
|
||||
|
||||
- (Tab*)activeTab
|
||||
{
|
||||
return self.active_tab;
|
||||
}
|
||||
|
||||
- (void)removeTab:(TabController*)controller
|
||||
{
|
||||
[self.managed_tabs removeObject:controller];
|
||||
|
||||
if ([self.managed_tabs count] == 0u) {
|
||||
if (self.task_manager_controller != nil) {
|
||||
[self.task_manager_controller.window close];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (Web::CSS::PreferredColorScheme)preferredColorScheme
|
||||
{
|
||||
return m_preferred_color_scheme;
|
||||
}
|
||||
|
||||
- (Web::CSS::PreferredContrast)preferredContrast
|
||||
{
|
||||
return m_preferred_contrast;
|
||||
}
|
||||
|
||||
- (Web::CSS::PreferredMotion)preferredMotion
|
||||
{
|
||||
return m_preferred_motion;
|
||||
}
|
||||
|
||||
- (WebView::SearchEngine const&)searchEngine
|
||||
{
|
||||
return m_search_engine;
|
||||
}
|
||||
|
||||
#pragma mark - Private methods
|
||||
|
||||
- (void)openAboutVersionPage:(id)sender
|
||||
{
|
||||
auto* current_tab = [NSApp keyWindow];
|
||||
if (![current_tab isKindOfClass:[Tab class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self createNewTab:URL::URL("about:version"sv)
|
||||
fromTab:(Tab*)current_tab
|
||||
activateTab:Web::HTML::ActivateTab::Yes];
|
||||
}
|
||||
|
||||
- (nonnull TabController*)createNewTab:(Web::HTML::ActivateTab)activate_tab
|
||||
fromTab:(nullable Tab*)tab
|
||||
{
|
||||
auto* controller = [[TabController alloc] init];
|
||||
[self initializeTabController:controller
|
||||
activateTab:activate_tab
|
||||
fromTab:tab];
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
- (nonnull TabController*)createChildTab:(Web::HTML::ActivateTab)activate_tab
|
||||
fromTab:(nonnull Tab*)tab
|
||||
pageIndex:(u64)page_index
|
||||
{
|
||||
auto* controller = [[TabController alloc] initAsChild:tab pageIndex:page_index];
|
||||
[self initializeTabController:controller
|
||||
activateTab:activate_tab
|
||||
fromTab:tab];
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
- (void)initializeTabController:(TabController*)controller
|
||||
activateTab:(Web::HTML::ActivateTab)activate_tab
|
||||
fromTab:(nullable Tab*)tab
|
||||
{
|
||||
[controller showWindow:nil];
|
||||
|
||||
if (tab) {
|
||||
[[tab tabGroup] addWindow:controller.window];
|
||||
|
||||
// FIXME: Can we create the tabbed window above without it becoming active in the first place?
|
||||
if (activate_tab == Web::HTML::ActivateTab::No) {
|
||||
[tab orderFront:nil];
|
||||
}
|
||||
}
|
||||
|
||||
if (activate_tab == Web::HTML::ActivateTab::Yes) {
|
||||
[[controller window] orderFrontRegardless];
|
||||
}
|
||||
|
||||
[self.managed_tabs addObject:controller];
|
||||
[controller onCreateNewTab];
|
||||
}
|
||||
|
||||
- (void)closeCurrentTab:(id)sender
|
||||
{
|
||||
auto* current_window = [NSApp keyWindow];
|
||||
[current_window close];
|
||||
}
|
||||
|
||||
- (void)openTaskManager:(id)sender
|
||||
{
|
||||
if (self.task_manager_controller != nil) {
|
||||
[self.task_manager_controller.window makeKeyAndOrderFront:sender];
|
||||
return;
|
||||
}
|
||||
|
||||
self.task_manager_controller = [[TaskManagerController alloc] initWithDelegate:self];
|
||||
[self.task_manager_controller showWindow:nil];
|
||||
}
|
||||
|
||||
- (void)openLocation:(id)sender
|
||||
{
|
||||
auto* current_tab = [NSApp keyWindow];
|
||||
|
||||
if (![current_tab isKindOfClass:[Tab class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* controller = (TabController*)[current_tab windowController];
|
||||
[controller focusLocationToolbarItem];
|
||||
}
|
||||
|
||||
- (void)setAutoPreferredColorScheme:(id)sender
|
||||
{
|
||||
m_preferred_color_scheme = Web::CSS::PreferredColorScheme::Auto;
|
||||
[self broadcastPreferredColorSchemeUpdate];
|
||||
}
|
||||
|
||||
- (void)setDarkPreferredColorScheme:(id)sender
|
||||
{
|
||||
m_preferred_color_scheme = Web::CSS::PreferredColorScheme::Dark;
|
||||
[self broadcastPreferredColorSchemeUpdate];
|
||||
}
|
||||
|
||||
- (void)setLightPreferredColorScheme:(id)sender
|
||||
{
|
||||
m_preferred_color_scheme = Web::CSS::PreferredColorScheme::Light;
|
||||
[self broadcastPreferredColorSchemeUpdate];
|
||||
}
|
||||
|
||||
- (void)broadcastPreferredColorSchemeUpdate
|
||||
{
|
||||
for (TabController* controller in self.managed_tabs) {
|
||||
auto* tab = (Tab*)[controller window];
|
||||
[[tab web_view] setPreferredColorScheme:m_preferred_color_scheme];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAutoPreferredContrast:(id)sender
|
||||
{
|
||||
m_preferred_contrast = Web::CSS::PreferredContrast::Auto;
|
||||
[self broadcastPreferredContrastUpdate];
|
||||
}
|
||||
|
||||
- (void)setLessPreferredContrast:(id)sender
|
||||
{
|
||||
m_preferred_contrast = Web::CSS::PreferredContrast::Less;
|
||||
[self broadcastPreferredContrastUpdate];
|
||||
}
|
||||
|
||||
- (void)setMorePreferredContrast:(id)sender
|
||||
{
|
||||
m_preferred_contrast = Web::CSS::PreferredContrast::More;
|
||||
[self broadcastPreferredContrastUpdate];
|
||||
}
|
||||
|
||||
- (void)setNoPreferencePreferredContrast:(id)sender
|
||||
{
|
||||
m_preferred_contrast = Web::CSS::PreferredContrast::NoPreference;
|
||||
[self broadcastPreferredContrastUpdate];
|
||||
}
|
||||
|
||||
- (void)broadcastPreferredContrastUpdate
|
||||
{
|
||||
for (TabController* controller in self.managed_tabs) {
|
||||
auto* tab = (Tab*)[controller window];
|
||||
[[tab web_view] setPreferredContrast:m_preferred_contrast];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAutoPreferredMotion:(id)sender
|
||||
{
|
||||
m_preferred_motion = Web::CSS::PreferredMotion::Auto;
|
||||
[self broadcastPreferredMotionUpdate];
|
||||
}
|
||||
|
||||
- (void)setNoPreferencePreferredMotion:(id)sender
|
||||
{
|
||||
m_preferred_motion = Web::CSS::PreferredMotion::NoPreference;
|
||||
[self broadcastPreferredMotionUpdate];
|
||||
}
|
||||
|
||||
- (void)setReducePreferredMotion:(id)sender
|
||||
{
|
||||
m_preferred_motion = Web::CSS::PreferredMotion::Reduce;
|
||||
[self broadcastPreferredMotionUpdate];
|
||||
}
|
||||
|
||||
- (void)broadcastPreferredMotionUpdate
|
||||
{
|
||||
for (TabController* controller in self.managed_tabs) {
|
||||
auto* tab = (Tab*)[controller window];
|
||||
[[tab web_view] setPreferredMotion:m_preferred_motion];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSearchEngine:(id)sender
|
||||
{
|
||||
auto* item = (NSMenuItem*)sender;
|
||||
auto title = Ladybird::ns_string_to_string([item title]);
|
||||
|
||||
if (auto search_engine = WebView::find_search_engine_by_name(title); search_engine.has_value())
|
||||
m_search_engine = search_engine.release_value();
|
||||
else
|
||||
m_search_engine = WebView::default_search_engine();
|
||||
}
|
||||
|
||||
- (void)clearHistory:(id)sender
|
||||
{
|
||||
for (TabController* controller in self.managed_tabs) {
|
||||
[controller clearHistory];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dumpCookies:(id)sender
|
||||
{
|
||||
WebView::Application::cookie_jar().dump_cookies();
|
||||
}
|
||||
|
||||
- (NSMenuItem*)createApplicationMenu
|
||||
{
|
||||
auto* menu = [[NSMenuItem alloc] init];
|
||||
|
||||
auto* process_name = [[NSProcessInfo processInfo] processName];
|
||||
auto* submenu = [[NSMenu alloc] initWithTitle:process_name];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"About %@", process_name]
|
||||
action:@selector(openAboutVersionPage:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"Hide %@", process_name]
|
||||
action:@selector(hide:)
|
||||
keyEquivalent:@"h"]];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"Quit %@", process_name]
|
||||
action:@selector(terminate:)
|
||||
keyEquivalent:@"q"]];
|
||||
|
||||
[menu setSubmenu:submenu];
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (NSMenuItem*)createFileMenu
|
||||
{
|
||||
auto* menu = [[NSMenuItem alloc] init];
|
||||
auto* submenu = [[NSMenu alloc] initWithTitle:@"File"];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"New Tab"
|
||||
action:@selector(createNewTab:)
|
||||
keyEquivalent:@"t"]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Close Tab"
|
||||
action:@selector(closeCurrentTab:)
|
||||
keyEquivalent:@"w"]];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Location"
|
||||
action:@selector(openLocation:)
|
||||
keyEquivalent:@"l"]];
|
||||
|
||||
[menu setSubmenu:submenu];
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (NSMenuItem*)createEditMenu
|
||||
{
|
||||
auto* menu = [[NSMenuItem alloc] init];
|
||||
auto* submenu = [[NSMenu alloc] initWithTitle:@"Edit"];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Undo"
|
||||
action:@selector(undo:)
|
||||
keyEquivalent:@"z"]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Redo"
|
||||
action:@selector(redo:)
|
||||
keyEquivalent:@"y"]];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Cut"
|
||||
action:@selector(cut:)
|
||||
keyEquivalent:@"x"]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy"
|
||||
action:@selector(copy:)
|
||||
keyEquivalent:@"c"]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Paste"
|
||||
action:@selector(paste:)
|
||||
keyEquivalent:@"v"]];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Select All"
|
||||
action:@selector(selectAll:)
|
||||
keyEquivalent:@"a"]];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Find..."
|
||||
action:@selector(find:)
|
||||
keyEquivalent:@"f"]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Find Next"
|
||||
action:@selector(findNextMatch:)
|
||||
keyEquivalent:@"g"]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Find Previous"
|
||||
action:@selector(findPreviousMatch:)
|
||||
keyEquivalent:@"G"]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Use Selection for Find"
|
||||
action:@selector(useSelectionForFind:)
|
||||
keyEquivalent:@"e"]];
|
||||
|
||||
[menu setSubmenu:submenu];
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (NSMenuItem*)createViewMenu
|
||||
{
|
||||
auto* menu = [[NSMenuItem alloc] init];
|
||||
auto* submenu = [[NSMenu alloc] initWithTitle:@"View"];
|
||||
|
||||
auto* color_scheme_menu = [[NSMenu alloc] init];
|
||||
[color_scheme_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Auto"
|
||||
action:@selector(setAutoPreferredColorScheme:)
|
||||
keyEquivalent:@""]];
|
||||
[color_scheme_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Dark"
|
||||
action:@selector(setDarkPreferredColorScheme:)
|
||||
keyEquivalent:@""]];
|
||||
[color_scheme_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Light"
|
||||
action:@selector(setLightPreferredColorScheme:)
|
||||
keyEquivalent:@""]];
|
||||
|
||||
auto* color_scheme_menu_item = [[NSMenuItem alloc] initWithTitle:@"Color Scheme"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[color_scheme_menu_item setSubmenu:color_scheme_menu];
|
||||
|
||||
auto* contrast_menu = [[NSMenu alloc] init];
|
||||
[contrast_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Auto"
|
||||
action:@selector(setAutoPreferredContrast:)
|
||||
keyEquivalent:@""]];
|
||||
[contrast_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Less"
|
||||
action:@selector(setLessPreferredContrast:)
|
||||
keyEquivalent:@""]];
|
||||
[contrast_menu addItem:[[NSMenuItem alloc] initWithTitle:@"More"
|
||||
action:@selector(setMorePreferredContrast:)
|
||||
keyEquivalent:@""]];
|
||||
[contrast_menu addItem:[[NSMenuItem alloc] initWithTitle:@"No Preference"
|
||||
action:@selector(setNoPreferencePreferredContrast:)
|
||||
keyEquivalent:@""]];
|
||||
|
||||
auto* contrast_menu_item = [[NSMenuItem alloc] initWithTitle:@"Contrast"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[contrast_menu_item setSubmenu:contrast_menu];
|
||||
|
||||
auto* motion_menu = [[NSMenu alloc] init];
|
||||
[motion_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Auto"
|
||||
action:@selector(setAutoPreferredMotion:)
|
||||
keyEquivalent:@""]];
|
||||
[motion_menu addItem:[[NSMenuItem alloc] initWithTitle:@"No Preference"
|
||||
action:@selector(setNoPreferencePreferredMotion:)
|
||||
keyEquivalent:@""]];
|
||||
[motion_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Reduce"
|
||||
action:@selector(setReducePreferredMotion:)
|
||||
keyEquivalent:@""]];
|
||||
|
||||
auto* motion_menu_item = [[NSMenuItem alloc] initWithTitle:@"Motion"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[motion_menu_item setSubmenu:motion_menu];
|
||||
|
||||
auto* zoom_menu = [[NSMenu alloc] init];
|
||||
[zoom_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Zoom In"
|
||||
action:@selector(zoomIn:)
|
||||
keyEquivalent:@"+"]];
|
||||
[zoom_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Zoom Out"
|
||||
action:@selector(zoomOut:)
|
||||
keyEquivalent:@"-"]];
|
||||
[zoom_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Actual Size"
|
||||
action:@selector(resetZoom:)
|
||||
keyEquivalent:@"0"]];
|
||||
|
||||
auto* zoom_menu_item = [[NSMenuItem alloc] initWithTitle:@"Zoom"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[zoom_menu_item setSubmenu:zoom_menu];
|
||||
|
||||
[submenu addItem:color_scheme_menu_item];
|
||||
[submenu addItem:contrast_menu_item];
|
||||
[submenu addItem:motion_menu_item];
|
||||
[submenu addItem:zoom_menu_item];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[menu setSubmenu:submenu];
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (NSMenuItem*)createSettingsMenu
|
||||
{
|
||||
auto* menu = [[NSMenuItem alloc] init];
|
||||
auto* submenu = [[NSMenu alloc] initWithTitle:@"Settings"];
|
||||
|
||||
auto* search_engine_menu = [[NSMenu alloc] init];
|
||||
|
||||
for (auto const& search_engine : WebView::search_engines()) {
|
||||
[search_engine_menu addItem:[[NSMenuItem alloc] initWithTitle:Ladybird::string_to_ns_string(search_engine.name)
|
||||
action:@selector(setSearchEngine:)
|
||||
keyEquivalent:@""]];
|
||||
}
|
||||
|
||||
auto* search_engine_menu_item = [[NSMenuItem alloc] initWithTitle:@"Search Engine"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[search_engine_menu_item setSubmenu:search_engine_menu];
|
||||
|
||||
[submenu addItem:search_engine_menu_item];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Enable Autoplay"
|
||||
action:@selector(toggleAutoplay:)
|
||||
keyEquivalent:@""]];
|
||||
|
||||
[menu setSubmenu:submenu];
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (NSMenuItem*)createHistoryMenu
|
||||
{
|
||||
auto* menu = [[NSMenuItem alloc] init];
|
||||
auto* submenu = [[NSMenu alloc] initWithTitle:@"History"];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Reload Page"
|
||||
action:@selector(reload:)
|
||||
keyEquivalent:@"r"]];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Navigate Back"
|
||||
action:@selector(navigateBack:)
|
||||
keyEquivalent:@"["]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Navigate Forward"
|
||||
action:@selector(navigateForward:)
|
||||
keyEquivalent:@"]"]];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Clear History"
|
||||
action:@selector(clearHistory:)
|
||||
keyEquivalent:@""]];
|
||||
|
||||
[menu setSubmenu:submenu];
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (NSMenuItem*)createInspectMenu
|
||||
{
|
||||
auto* menu = [[NSMenuItem alloc] init];
|
||||
auto* submenu = [[NSMenu alloc] initWithTitle:@"Inspect"];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"View Source"
|
||||
action:@selector(viewSource:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Inspector"
|
||||
action:@selector(openInspector:)
|
||||
keyEquivalent:@"I"]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Task Manager"
|
||||
action:@selector(openTaskManager:)
|
||||
keyEquivalent:@"M"]];
|
||||
|
||||
[menu setSubmenu:submenu];
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (NSMenuItem*)createDebugMenu
|
||||
{
|
||||
auto* menu = [[NSMenuItem alloc] init];
|
||||
auto* submenu = [[NSMenu alloc] initWithTitle:@"Debug"];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump DOM Tree"
|
||||
action:@selector(dumpDOMTree:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump Layout Tree"
|
||||
action:@selector(dumpLayoutTree:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump Paint Tree"
|
||||
action:@selector(dumpPaintTree:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump Stacking Context Tree"
|
||||
action:@selector(dumpStackingContextTree:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump Style Sheets"
|
||||
action:@selector(dumpStyleSheets:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump All Resolved Styles"
|
||||
action:@selector(dumpAllResolvedStyles:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump History"
|
||||
action:@selector(dumpHistory:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump Cookies"
|
||||
action:@selector(dumpCookies:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump Local Storage"
|
||||
action:@selector(dumpLocalStorage:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Show Line Box Borders"
|
||||
action:@selector(toggleLineBoxBorders:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Collect Garbage"
|
||||
action:@selector(collectGarbage:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump GC Graph"
|
||||
action:@selector(dumpGCGraph:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Clear Cache"
|
||||
action:@selector(clearCache:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
auto* spoof_user_agent_menu = [[NSMenu alloc] init];
|
||||
auto add_user_agent = [spoof_user_agent_menu](ByteString name) {
|
||||
[spoof_user_agent_menu addItem:[[NSMenuItem alloc] initWithTitle:Ladybird::string_to_ns_string(name)
|
||||
action:@selector(setUserAgentSpoof:)
|
||||
keyEquivalent:@""]];
|
||||
};
|
||||
|
||||
add_user_agent("Disabled");
|
||||
for (auto const& userAgent : WebView::user_agents)
|
||||
add_user_agent(userAgent.key);
|
||||
|
||||
auto* spoof_user_agent_menu_item = [[NSMenuItem alloc] initWithTitle:@"Spoof User Agent"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[spoof_user_agent_menu_item setSubmenu:spoof_user_agent_menu];
|
||||
|
||||
[submenu addItem:spoof_user_agent_menu_item];
|
||||
|
||||
auto* navigator_compatibility_mode_menu = [[NSMenu alloc] init];
|
||||
auto add_navigator_compatibility_mode = [navigator_compatibility_mode_menu](ByteString name) {
|
||||
[navigator_compatibility_mode_menu addItem:[[NSMenuItem alloc] initWithTitle:Ladybird::string_to_ns_string(name)
|
||||
action:@selector(setNavigatorCompatibilityMode:)
|
||||
keyEquivalent:@""]];
|
||||
};
|
||||
add_navigator_compatibility_mode("Chrome");
|
||||
add_navigator_compatibility_mode("Gecko");
|
||||
add_navigator_compatibility_mode("WebKit");
|
||||
|
||||
auto* navigator_compatibility_mode_menu_item = [[NSMenuItem alloc] initWithTitle:@"Navigator Compatibility Mode"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[navigator_compatibility_mode_menu_item setSubmenu:navigator_compatibility_mode_menu];
|
||||
|
||||
[submenu addItem:navigator_compatibility_mode_menu_item];
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Enable Scripting"
|
||||
action:@selector(toggleScripting:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Block Pop-ups"
|
||||
action:@selector(togglePopupBlocking:)
|
||||
keyEquivalent:@""]];
|
||||
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Enable Same-Origin Policy"
|
||||
action:@selector(toggleSameOriginPolicy:)
|
||||
keyEquivalent:@""]];
|
||||
|
||||
[menu setSubmenu:submenu];
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (NSMenuItem*)createWindowMenu
|
||||
{
|
||||
auto* menu = [[NSMenuItem alloc] init];
|
||||
auto* submenu = [[NSMenu alloc] initWithTitle:@"Window"];
|
||||
|
||||
[NSApp setWindowsMenu:submenu];
|
||||
|
||||
[menu setSubmenu:submenu];
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (NSMenuItem*)createHelpMenu
|
||||
{
|
||||
auto* menu = [[NSMenuItem alloc] init];
|
||||
auto* submenu = [[NSMenu alloc] initWithTitle:@"Help"];
|
||||
|
||||
[NSApp setHelpMenu:submenu];
|
||||
|
||||
[menu setSubmenu:submenu];
|
||||
return menu;
|
||||
}
|
||||
|
||||
#pragma mark - NSApplicationDelegate
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification*)notification
|
||||
{
|
||||
Tab* tab = nil;
|
||||
|
||||
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
|
||||
fromTab:tab
|
||||
activateTab:activate_tab];
|
||||
|
||||
tab = (Tab*)[controller window];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(NSNotification*)notification
|
||||
{
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)validateMenuItem:(NSMenuItem*)item
|
||||
{
|
||||
if ([item action] == @selector(setAutoPreferredColorScheme:)) {
|
||||
[item setState:(m_preferred_color_scheme == Web::CSS::PreferredColorScheme::Auto) ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
} else if ([item action] == @selector(setDarkPreferredColorScheme:)) {
|
||||
[item setState:(m_preferred_color_scheme == Web::CSS::PreferredColorScheme::Dark) ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
} else if ([item action] == @selector(setLightPreferredColorScheme:)) {
|
||||
[item setState:(m_preferred_color_scheme == Web::CSS::PreferredColorScheme::Light) ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
} else if ([item action] == @selector(setAutoPreferredContrast:)) {
|
||||
[item setState:(m_preferred_contrast == Web::CSS::PreferredContrast::Auto) ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
} else if ([item action] == @selector(setLessPreferredContrast:)) {
|
||||
[item setState:(m_preferred_contrast == Web::CSS::PreferredContrast::Less) ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
} else if ([item action] == @selector(setMorePreferredContrast:)) {
|
||||
[item setState:(m_preferred_contrast == Web::CSS::PreferredContrast::More) ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
} else if ([item action] == @selector(setNoPreferencePreferredContrast:)) {
|
||||
[item setState:(m_preferred_contrast == Web::CSS::PreferredContrast::NoPreference) ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
} else if ([item action] == @selector(setAutoPreferredMotion:)) {
|
||||
[item setState:(m_preferred_motion == Web::CSS::PreferredMotion::Auto) ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
} else if ([item action] == @selector(setNoPreferencePreferredMotion:)) {
|
||||
[item setState:(m_preferred_motion == Web::CSS::PreferredMotion::NoPreference) ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
} else if ([item action] == @selector(setReducePreferredMotion:)) {
|
||||
[item setState:(m_preferred_motion == Web::CSS::PreferredMotion::Reduce) ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
} else if ([item action] == @selector(setSearchEngine:)) {
|
||||
auto title = Ladybird::ns_string_to_string([item title]);
|
||||
[item setState:(m_search_engine.name == title) ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - TaskManagerDelegate
|
||||
|
||||
- (void)onTaskManagerClosed
|
||||
{
|
||||
self.task_manager_controller = nil;
|
||||
}
|
||||
|
||||
@end
|
55
UI/AppKit/Application/EventLoopImplementation.h
Normal file
55
UI/AppKit/Application/EventLoopImplementation.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <LibCore/EventLoopImplementation.h>
|
||||
|
||||
namespace Ladybird {
|
||||
|
||||
class CFEventLoopManager final : public Core::EventLoopManager {
|
||||
public:
|
||||
virtual NonnullOwnPtr<Core::EventLoopImplementation> make_implementation() override;
|
||||
|
||||
virtual intptr_t register_timer(Core::EventReceiver&, int interval_milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible) override;
|
||||
virtual void unregister_timer(intptr_t timer_id) override;
|
||||
|
||||
virtual void register_notifier(Core::Notifier&) override;
|
||||
virtual void unregister_notifier(Core::Notifier&) override;
|
||||
|
||||
virtual void did_post_event() override;
|
||||
|
||||
virtual int register_signal(int, Function<void(int)>) override;
|
||||
virtual void unregister_signal(int) override;
|
||||
};
|
||||
|
||||
class CFEventLoopImplementation final : public Core::EventLoopImplementation {
|
||||
public:
|
||||
// FIXME: This currently only manages the main NSApp event loop, as that is all we currently
|
||||
// interact with. When we need multiple event loops, or an event loop that isn't the
|
||||
// NSApp loop, we will need to create our own CFRunLoop.
|
||||
static NonnullOwnPtr<CFEventLoopImplementation> create();
|
||||
|
||||
virtual int exec() override;
|
||||
virtual size_t pump(PumpMode) override;
|
||||
virtual void quit(int) override;
|
||||
virtual void wake() override;
|
||||
virtual void post_event(Core::EventReceiver& receiver, NonnullOwnPtr<Core::Event>&&) override;
|
||||
|
||||
// FIXME: These APIs only exist for obscure use-cases inside SerenityOS. Try to get rid of them.
|
||||
virtual void unquit() override { }
|
||||
virtual bool was_exit_requested() const override { return false; }
|
||||
virtual void notify_forked_and_in_child() override { }
|
||||
|
||||
private:
|
||||
CFEventLoopImplementation() = default;
|
||||
|
||||
int m_exit_code { 0 };
|
||||
};
|
||||
|
||||
}
|
414
UI/AppKit/Application/EventLoopImplementation.mm
Normal file
414
UI/AppKit/Application/EventLoopImplementation.mm
Normal file
|
@ -0,0 +1,414 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/IDAllocator.h>
|
||||
#include <AK/Singleton.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <LibCore/Event.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/ThreadEventQueue.h>
|
||||
|
||||
#import <Application/EventLoopImplementation.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include <sys/event.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace Ladybird {
|
||||
|
||||
struct ThreadData {
|
||||
static ThreadData& the()
|
||||
{
|
||||
static thread_local ThreadData s_thread_data;
|
||||
return s_thread_data;
|
||||
}
|
||||
|
||||
Core::Notifier& notifier_by_fd(int fd)
|
||||
{
|
||||
for (auto notifier : notifiers) {
|
||||
if (notifier.key->fd() == fd)
|
||||
return *notifier.key;
|
||||
}
|
||||
|
||||
// If we didn't have a notifier for the provided FD, it should have been unregistered.
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
IDAllocator timer_id_allocator;
|
||||
HashMap<int, CFRunLoopTimerRef> timers;
|
||||
HashMap<Core::Notifier*, CFRunLoopSourceRef> notifiers;
|
||||
};
|
||||
|
||||
class SignalHandlers : public RefCounted<SignalHandlers> {
|
||||
AK_MAKE_NONCOPYABLE(SignalHandlers);
|
||||
AK_MAKE_NONMOVABLE(SignalHandlers);
|
||||
|
||||
public:
|
||||
SignalHandlers(int signal_number, CFFileDescriptorCallBack);
|
||||
~SignalHandlers();
|
||||
|
||||
void dispatch();
|
||||
int add(Function<void(int)>&& handler);
|
||||
bool remove(int handler_id);
|
||||
|
||||
bool is_empty() const
|
||||
{
|
||||
if (m_calling_handlers) {
|
||||
for (auto const& handler : m_handlers_pending) {
|
||||
if (handler.value)
|
||||
return false; // an add is pending
|
||||
}
|
||||
}
|
||||
return m_handlers.is_empty();
|
||||
}
|
||||
|
||||
bool have(int handler_id) const
|
||||
{
|
||||
if (m_calling_handlers) {
|
||||
auto it = m_handlers_pending.find(handler_id);
|
||||
if (it != m_handlers_pending.end()) {
|
||||
if (!it->value)
|
||||
return false; // a deletion is pending
|
||||
}
|
||||
}
|
||||
return m_handlers.contains(handler_id);
|
||||
}
|
||||
|
||||
int m_signal_number;
|
||||
void (*m_original_handler)(int);
|
||||
HashMap<int, Function<void(int)>> m_handlers;
|
||||
HashMap<int, Function<void(int)>> m_handlers_pending;
|
||||
bool m_calling_handlers { false };
|
||||
CFRunLoopSourceRef m_source { nullptr };
|
||||
int m_kevent_fd = { -1 };
|
||||
};
|
||||
|
||||
SignalHandlers::SignalHandlers(int signal_number, CFFileDescriptorCallBack handle_signal)
|
||||
: m_signal_number(signal_number)
|
||||
, m_original_handler(signal(signal_number, [](int) {}))
|
||||
{
|
||||
m_kevent_fd = kqueue();
|
||||
if (m_kevent_fd < 0) {
|
||||
dbgln("Unable to create kqueue to register signal {}: {}", signal_number, strerror(errno));
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
struct kevent changes = {};
|
||||
EV_SET(&changes, signal_number, EVFILT_SIGNAL, EV_ADD | EV_RECEIPT, 0, 0, nullptr);
|
||||
if (auto res = kevent(m_kevent_fd, &changes, 1, &changes, 1, NULL); res < 0) {
|
||||
dbgln("Unable to register signal {}: {}", signal_number, strerror(errno));
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
CFFileDescriptorContext context = { 0, this, nullptr, nullptr, nullptr };
|
||||
CFFileDescriptorRef kq_ref = CFFileDescriptorCreate(kCFAllocatorDefault, m_kevent_fd, FALSE, handle_signal, &context);
|
||||
|
||||
m_source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, kq_ref, 0);
|
||||
CFRunLoopAddSource(CFRunLoopGetMain(), m_source, kCFRunLoopDefaultMode);
|
||||
|
||||
CFFileDescriptorEnableCallBacks(kq_ref, kCFFileDescriptorReadCallBack);
|
||||
CFRelease(kq_ref);
|
||||
}
|
||||
|
||||
SignalHandlers::~SignalHandlers()
|
||||
{
|
||||
CFRunLoopRemoveSource(CFRunLoopGetMain(), m_source, kCFRunLoopDefaultMode);
|
||||
CFRelease(m_source);
|
||||
(void)::signal(m_signal_number, m_original_handler);
|
||||
::close(m_kevent_fd);
|
||||
}
|
||||
|
||||
struct SignalHandlersInfo {
|
||||
HashMap<int, NonnullRefPtr<SignalHandlers>> signal_handlers;
|
||||
int next_signal_id { 0 };
|
||||
};
|
||||
|
||||
static Singleton<SignalHandlersInfo> s_signals;
|
||||
static SignalHandlersInfo* signals_info()
|
||||
{
|
||||
return s_signals.ptr();
|
||||
}
|
||||
|
||||
void SignalHandlers::dispatch()
|
||||
{
|
||||
TemporaryChange change(m_calling_handlers, true);
|
||||
for (auto& handler : m_handlers)
|
||||
handler.value(m_signal_number);
|
||||
if (!m_handlers_pending.is_empty()) {
|
||||
// Apply pending adds/removes
|
||||
for (auto& handler : m_handlers_pending) {
|
||||
if (handler.value) {
|
||||
auto result = m_handlers.set(handler.key, move(handler.value));
|
||||
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
|
||||
} else {
|
||||
m_handlers.remove(handler.key);
|
||||
}
|
||||
}
|
||||
m_handlers_pending.clear();
|
||||
}
|
||||
}
|
||||
|
||||
int SignalHandlers::add(Function<void(int)>&& handler)
|
||||
{
|
||||
int id = ++signals_info()->next_signal_id; // TODO: worry about wrapping and duplicates?
|
||||
if (m_calling_handlers)
|
||||
m_handlers_pending.set(id, move(handler));
|
||||
else
|
||||
m_handlers.set(id, move(handler));
|
||||
return id;
|
||||
}
|
||||
|
||||
bool SignalHandlers::remove(int handler_id)
|
||||
{
|
||||
VERIFY(handler_id != 0);
|
||||
if (m_calling_handlers) {
|
||||
auto it = m_handlers.find(handler_id);
|
||||
if (it != m_handlers.end()) {
|
||||
// Mark pending remove
|
||||
m_handlers_pending.set(handler_id, {});
|
||||
return true;
|
||||
}
|
||||
it = m_handlers_pending.find(handler_id);
|
||||
if (it != m_handlers_pending.end()) {
|
||||
if (!it->value)
|
||||
return false; // already was marked as deleted
|
||||
it->value = nullptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return m_handlers.remove(handler_id);
|
||||
}
|
||||
|
||||
static void post_application_event()
|
||||
{
|
||||
auto* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
|
||||
location:NSMakePoint(0, 0)
|
||||
modifierFlags:0
|
||||
timestamp:0
|
||||
windowNumber:0
|
||||
context:nil
|
||||
subtype:0
|
||||
data1:0
|
||||
data2:0];
|
||||
|
||||
[NSApp postEvent:event atStart:NO];
|
||||
}
|
||||
|
||||
NonnullOwnPtr<Core::EventLoopImplementation> CFEventLoopManager::make_implementation()
|
||||
{
|
||||
return CFEventLoopImplementation::create();
|
||||
}
|
||||
|
||||
intptr_t CFEventLoopManager::register_timer(Core::EventReceiver& receiver, int interval_milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible should_fire_when_not_visible)
|
||||
{
|
||||
auto& thread_data = ThreadData::the();
|
||||
|
||||
auto timer_id = thread_data.timer_id_allocator.allocate();
|
||||
auto weak_receiver = receiver.make_weak_ptr();
|
||||
|
||||
auto interval_seconds = static_cast<double>(interval_milliseconds) / 1000.0;
|
||||
auto first_fire_time = CFAbsoluteTimeGetCurrent() + interval_seconds;
|
||||
|
||||
auto* timer = CFRunLoopTimerCreateWithHandler(
|
||||
kCFAllocatorDefault, first_fire_time, should_reload ? interval_seconds : 0, 0, 0,
|
||||
^(CFRunLoopTimerRef) {
|
||||
auto receiver = weak_receiver.strong_ref();
|
||||
if (!receiver) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (should_fire_when_not_visible == Core::TimerShouldFireWhenNotVisible::No) {
|
||||
if (!receiver->is_visible_for_timer_purposes()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Core::TimerEvent event;
|
||||
receiver->dispatch_event(event);
|
||||
});
|
||||
|
||||
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
|
||||
thread_data.timers.set(timer_id, timer);
|
||||
|
||||
return timer_id;
|
||||
}
|
||||
|
||||
void CFEventLoopManager::unregister_timer(intptr_t timer_id)
|
||||
{
|
||||
auto& thread_data = ThreadData::the();
|
||||
thread_data.timer_id_allocator.deallocate(static_cast<int>(timer_id));
|
||||
|
||||
auto timer = thread_data.timers.take(static_cast<int>(timer_id));
|
||||
VERIFY(timer.has_value());
|
||||
CFRunLoopTimerInvalidate(*timer);
|
||||
CFRelease(*timer);
|
||||
}
|
||||
|
||||
static void socket_notifier(CFSocketRef socket, CFSocketCallBackType notification_type, CFDataRef, void const*, void*)
|
||||
{
|
||||
auto& notifier = ThreadData::the().notifier_by_fd(CFSocketGetNative(socket));
|
||||
|
||||
// This socket callback is not quite re-entrant. If Core::Notifier::dispatch_event blocks, e.g.
|
||||
// to wait upon a Core::Promise, this socket will not receive any more notifications until that
|
||||
// promise is resolved or rejected. So we mark this socket as able to receive more notifications
|
||||
// before dispatching the event, which allows it to be triggered again.
|
||||
CFSocketEnableCallBacks(socket, notification_type);
|
||||
|
||||
Core::NotifierActivationEvent event(notifier.fd(), notifier.type());
|
||||
notifier.dispatch_event(event);
|
||||
|
||||
// This manual process of enabling the callbacks also seems to require waking the event loop,
|
||||
// otherwise it hangs indefinitely in any ongoing pump(PumpMode::WaitForEvents) invocation.
|
||||
post_application_event();
|
||||
}
|
||||
|
||||
void CFEventLoopManager::register_notifier(Core::Notifier& notifier)
|
||||
{
|
||||
auto notification_type = kCFSocketNoCallBack;
|
||||
|
||||
switch (notifier.type()) {
|
||||
case Core::Notifier::Type::Read:
|
||||
notification_type = kCFSocketReadCallBack;
|
||||
break;
|
||||
case Core::Notifier::Type::Write:
|
||||
notification_type = kCFSocketWriteCallBack;
|
||||
break;
|
||||
default:
|
||||
TODO();
|
||||
break;
|
||||
}
|
||||
|
||||
CFSocketContext context { .version = 0, .info = nullptr, .retain = nullptr, .release = nullptr, .copyDescription = nullptr };
|
||||
auto* socket = CFSocketCreateWithNative(kCFAllocatorDefault, notifier.fd(), notification_type, &socket_notifier, &context);
|
||||
|
||||
CFOptionFlags sockopt = CFSocketGetSocketFlags(socket);
|
||||
sockopt &= ~kCFSocketAutomaticallyReenableReadCallBack;
|
||||
sockopt &= ~kCFSocketCloseOnInvalidate;
|
||||
CFSocketSetSocketFlags(socket, sockopt);
|
||||
|
||||
auto* source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
|
||||
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
|
||||
|
||||
CFRelease(socket);
|
||||
|
||||
ThreadData::the().notifiers.set(¬ifier, source);
|
||||
}
|
||||
|
||||
void CFEventLoopManager::unregister_notifier(Core::Notifier& notifier)
|
||||
{
|
||||
if (auto source = ThreadData::the().notifiers.take(¬ifier); source.has_value()) {
|
||||
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), *source, kCFRunLoopCommonModes);
|
||||
CFRelease(*source);
|
||||
}
|
||||
}
|
||||
|
||||
void CFEventLoopManager::did_post_event()
|
||||
{
|
||||
post_application_event();
|
||||
}
|
||||
|
||||
static void handle_signal(CFFileDescriptorRef f, CFOptionFlags callback_types, void* info)
|
||||
{
|
||||
VERIFY(callback_types & kCFFileDescriptorReadCallBack);
|
||||
auto* signal_handlers = static_cast<SignalHandlers*>(info);
|
||||
|
||||
struct kevent event { };
|
||||
|
||||
// returns number of events that have occurred since last call
|
||||
(void)::kevent(CFFileDescriptorGetNativeDescriptor(f), nullptr, 0, &event, 1, nullptr);
|
||||
CFFileDescriptorEnableCallBacks(f, kCFFileDescriptorReadCallBack);
|
||||
|
||||
signal_handlers->dispatch();
|
||||
}
|
||||
|
||||
int CFEventLoopManager::register_signal(int signal_number, Function<void(int)> handler)
|
||||
{
|
||||
VERIFY(signal_number != 0);
|
||||
auto& info = *signals_info();
|
||||
auto handlers = info.signal_handlers.find(signal_number);
|
||||
if (handlers == info.signal_handlers.end()) {
|
||||
auto signal_handlers = adopt_ref(*new SignalHandlers(signal_number, &handle_signal));
|
||||
auto handler_id = signal_handlers->add(move(handler));
|
||||
info.signal_handlers.set(signal_number, move(signal_handlers));
|
||||
return handler_id;
|
||||
} else {
|
||||
return handlers->value->add(move(handler));
|
||||
}
|
||||
}
|
||||
|
||||
void CFEventLoopManager::unregister_signal(int handler_id)
|
||||
{
|
||||
VERIFY(handler_id != 0);
|
||||
int remove_signal_number = 0;
|
||||
auto& info = *signals_info();
|
||||
for (auto& h : info.signal_handlers) {
|
||||
auto& handlers = *h.value;
|
||||
if (handlers.remove(handler_id)) {
|
||||
if (handlers.is_empty())
|
||||
remove_signal_number = handlers.m_signal_number;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (remove_signal_number != 0)
|
||||
info.signal_handlers.remove(remove_signal_number);
|
||||
}
|
||||
|
||||
NonnullOwnPtr<CFEventLoopImplementation> CFEventLoopImplementation::create()
|
||||
{
|
||||
return adopt_own(*new CFEventLoopImplementation);
|
||||
}
|
||||
|
||||
int CFEventLoopImplementation::exec()
|
||||
{
|
||||
[NSApp run];
|
||||
return m_exit_code;
|
||||
}
|
||||
|
||||
size_t CFEventLoopImplementation::pump(PumpMode mode)
|
||||
{
|
||||
auto* wait_until = mode == PumpMode::WaitForEvents ? [NSDate distantFuture] : [NSDate distantPast];
|
||||
|
||||
auto* event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
untilDate:wait_until
|
||||
inMode:NSDefaultRunLoopMode
|
||||
dequeue:YES];
|
||||
|
||||
while (event) {
|
||||
[NSApp sendEvent:event];
|
||||
|
||||
event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
untilDate:nil
|
||||
inMode:NSDefaultRunLoopMode
|
||||
dequeue:YES];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CFEventLoopImplementation::quit(int exit_code)
|
||||
{
|
||||
m_exit_code = exit_code;
|
||||
[NSApp stop:nil];
|
||||
}
|
||||
|
||||
void CFEventLoopImplementation::wake()
|
||||
{
|
||||
CFRunLoopWakeUp(CFRunLoopGetCurrent());
|
||||
}
|
||||
|
||||
void CFEventLoopImplementation::post_event(Core::EventReceiver& receiver, NonnullOwnPtr<Core::Event>&& event)
|
||||
{
|
||||
m_thread_event_queue.post_event(receiver, move(event));
|
||||
|
||||
if (&m_thread_event_queue != &Core::ThreadEventQueue::current())
|
||||
wake();
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue