mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
Teach LibWebView autocomplete to query HistoryStore before falling back to remote engines and move the wiring out of the AppKit frontend. Refine matching so scheme and www. boilerplate do not dominate results, short title and substring queries stay quiet, and history tracing can explain what the ranking code is doing.
486 lines
15 KiB
Plaintext
486 lines
15 KiB
Plaintext
/*
|
|
* Copyright (c) 2023-2026, Tim Flynn <trflynn89@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibWebView/Application.h>
|
|
|
|
#import <Application/ApplicationDelegate.h>
|
|
#import <Interface/InfoBar.h>
|
|
#import <Interface/LadybirdWebView.h>
|
|
#import <Interface/Menu.h>
|
|
#import <Interface/Tab.h>
|
|
#import <Interface/TabController.h>
|
|
#import <Utilities/Conversions.h>
|
|
|
|
#if !__has_feature(objc_arc)
|
|
# error "This project requires ARC"
|
|
#endif
|
|
|
|
@interface ApplicationDelegate ()
|
|
|
|
@property (nonatomic, strong) NSMutableArray<TabController*>* managed_tabs;
|
|
@property (nonatomic, weak) Tab* active_tab;
|
|
|
|
@property (nonatomic, strong) NSMenu* bookmarks_menu;
|
|
|
|
@property (nonatomic, strong) InfoBar* info_bar;
|
|
|
|
- (NSMenuItem*)createApplicationMenu;
|
|
- (NSMenuItem*)createFileMenu;
|
|
- (NSMenuItem*)createEditMenu;
|
|
- (NSMenuItem*)createViewMenu;
|
|
- (NSMenuItem*)createHistoryMenu;
|
|
- (NSMenuItem*)createBookmarksMenu;
|
|
- (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 createHistoryMenu]];
|
|
[[NSApp mainMenu] addItem:[self createBookmarksMenu]];
|
|
[[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];
|
|
|
|
// Reduce the tooltip delay, as the default delay feels quite long.
|
|
[[NSUserDefaults standardUserDefaults] setObject:@100 forKey:@"NSInitialToolTipDelay"];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - Public methods
|
|
|
|
- (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;
|
|
}
|
|
|
|
- (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*)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
|
|
{
|
|
if (tab == self.activeTab)
|
|
return;
|
|
|
|
self.active_tab = tab;
|
|
|
|
if (self.info_bar) {
|
|
[self.info_bar tabBecameActive:self.active_tab];
|
|
}
|
|
|
|
WebView::Application::the().update_bookmark_action_for_current_web_view();
|
|
}
|
|
|
|
- (Tab*)activeTab
|
|
{
|
|
return self.active_tab;
|
|
}
|
|
|
|
- (void)removeTab:(TabController*)controller
|
|
{
|
|
[self.managed_tabs removeObject:controller];
|
|
}
|
|
|
|
- (void)rebuildBookmarksMenu
|
|
{
|
|
Ladybird::repopulate_application_menu(self.bookmarks_menu, WebView::Application::the().bookmarks_menu());
|
|
|
|
for (TabController* controller in self.managed_tabs) {
|
|
auto* tab = (Tab*)[controller window];
|
|
[tab rebuildBookmarksBar];
|
|
}
|
|
}
|
|
|
|
- (void)updateBookmarksBarDisplay:(bool)show_bookmarks_bar
|
|
{
|
|
for (TabController* controller in self.managed_tabs) {
|
|
if (auto* tab = (Tab*)[controller window]; ([tab styleMask] & NSWindowStyleMaskFullScreen) == 0) {
|
|
[tab updateBookmarksBarDisplay:show_bookmarks_bar];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)onDevtoolsEnabled
|
|
{
|
|
if (!self.info_bar) {
|
|
self.info_bar = [[InfoBar alloc] init];
|
|
}
|
|
|
|
auto message = MUST(String::formatted("DevTools is enabled on port {}", WebView::Application::browser_options().devtools_port));
|
|
|
|
[self.info_bar showWithMessage:Ladybird::string_to_ns_string(message)
|
|
dismissButtonTitle:@"Disable"
|
|
dismissButtonClicked:^{
|
|
MUST(WebView::Application::the().toggle_devtools_enabled());
|
|
}
|
|
activeTab:self.active_tab];
|
|
}
|
|
|
|
- (void)onDevtoolsDisabled
|
|
{
|
|
if (self.info_bar) {
|
|
[self.info_bar hide];
|
|
self.info_bar = nil;
|
|
}
|
|
}
|
|
|
|
#pragma mark - Private methods
|
|
|
|
- (void)openLocation:(id)sender
|
|
{
|
|
auto* current_tab = [NSApp keyWindow];
|
|
|
|
if (![current_tab isKindOfClass:[Tab class]]) {
|
|
return;
|
|
}
|
|
|
|
auto* controller = (TabController*)[current_tab windowController];
|
|
[controller focusLocationToolbarItem];
|
|
}
|
|
|
|
- (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];
|
|
[controller focusLocationToolbarItem];
|
|
}
|
|
|
|
[self.managed_tabs addObject:controller];
|
|
}
|
|
|
|
- (void)closeCurrentTab:(id)sender
|
|
{
|
|
auto* current_window = [NSApp keyWindow];
|
|
[current_window performClose:self];
|
|
}
|
|
|
|
- (void)clearHistory:(id)sender
|
|
{
|
|
WebView::Application::the().clear_history();
|
|
}
|
|
|
|
- (NSMenuItem*)createApplicationMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
|
|
auto* process_name = [[NSProcessInfo processInfo] processName];
|
|
auto* submenu = [[NSMenu alloc] initWithTitle:process_name];
|
|
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().open_about_page_action())];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().open_settings_page_action())];
|
|
[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:Ladybird::create_application_menu_item(WebView::Application::the().copy_selection_action())];
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().paste_action())];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().select_all_action())];
|
|
[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* zoom_menu = Ladybird::create_application_menu(WebView::Application::the().zoom_menu());
|
|
auto* zoom_menu_item = [[NSMenuItem alloc] initWithTitle:[zoom_menu title]
|
|
action:nil
|
|
keyEquivalent:@""];
|
|
[zoom_menu_item setSubmenu:zoom_menu];
|
|
|
|
auto* color_scheme_menu = Ladybird::create_application_menu(WebView::Application::the().color_scheme_menu());
|
|
auto* color_scheme_menu_item = [[NSMenuItem alloc] initWithTitle:[color_scheme_menu title]
|
|
action:nil
|
|
keyEquivalent:@""];
|
|
[color_scheme_menu_item setSubmenu:color_scheme_menu];
|
|
|
|
auto* contrast_menu = Ladybird::create_application_menu(WebView::Application::the().contrast_menu());
|
|
auto* contrast_menu_item = [[NSMenuItem alloc] initWithTitle:[contrast_menu title]
|
|
action:nil
|
|
keyEquivalent:@""];
|
|
[contrast_menu_item setSubmenu:contrast_menu];
|
|
|
|
auto* motion_menu = Ladybird::create_application_menu(WebView::Application::the().motion_menu());
|
|
auto* motion_menu_item = [[NSMenuItem alloc] initWithTitle:[motion_menu title]
|
|
action:nil
|
|
keyEquivalent:@""];
|
|
[motion_menu_item setSubmenu:motion_menu];
|
|
|
|
[submenu addItem:zoom_menu_item];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
[submenu addItem:color_scheme_menu_item];
|
|
[submenu addItem:contrast_menu_item];
|
|
[submenu addItem:motion_menu_item];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[menu setSubmenu:submenu];
|
|
return menu;
|
|
}
|
|
|
|
- (NSMenuItem*)createHistoryMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
|
|
auto* submenu = [[NSMenu alloc] initWithTitle:@"History"];
|
|
[submenu setAutoenablesItems:NO];
|
|
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().reload_action())];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Clear History"
|
|
action:@selector(clearHistory:)
|
|
keyEquivalent:@""]];
|
|
|
|
[menu setSubmenu:submenu];
|
|
return menu;
|
|
}
|
|
|
|
- (NSMenuItem*)createBookmarksMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
|
|
self.bookmarks_menu = Ladybird::create_application_menu(WebView::Application::the().bookmarks_menu());
|
|
[menu setSubmenu:self.bookmarks_menu];
|
|
|
|
return menu;
|
|
}
|
|
|
|
- (NSMenuItem*)createInspectMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
|
|
auto* submenu = Ladybird::create_application_menu(WebView::Application::the().inspect_menu());
|
|
[menu setSubmenu:submenu];
|
|
|
|
return menu;
|
|
}
|
|
|
|
- (NSMenuItem*)createDebugMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
|
|
auto* submenu = Ladybird::create_application_menu(WebView::Application::the().debug_menu());
|
|
[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
|
|
{
|
|
auto const& browser_options = WebView::Application::browser_options();
|
|
|
|
if (browser_options.devtools_port.has_value())
|
|
[self onDevtoolsEnabled];
|
|
|
|
Tab* tab = nil;
|
|
|
|
for (auto const& url : browser_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;
|
|
}
|
|
|
|
- (void)applicationDidChangeScreenParameters:(NSNotification*)notification
|
|
{
|
|
for (TabController* controller in self.managed_tabs) {
|
|
auto* tab = (Tab*)[controller window];
|
|
[[tab web_view] handleDisplayRefreshRateChange];
|
|
}
|
|
}
|
|
|
|
- (BOOL)validateMenuItem:(NSMenuItem*)menu
|
|
{
|
|
SEL action = [menu action];
|
|
|
|
if (action == @selector(closeCurrentTab:)) {
|
|
return [[NSApp keyWindow] isKindOfClass:[Tab class]];
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
@end
|