Files
ladybird/UI/AppKit/Application/ApplicationDelegate.mm
Andreas Kling fe2cab9270 LibWebView: Add history-backed location autocomplete
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.
2026-04-16 21:01:28 +02:00

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