UI/AppKit: Add UI components to enable DevTools at runtime

This commit is contained in:
Timothy Flynn 2025-03-15 10:10:17 -04:00 committed by Alexander Kalenik
commit 28574e2812
Notes: github-actions[bot] 2025-03-15 18:10:45 +00:00
4 changed files with 188 additions and 1 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023-2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -9,6 +9,7 @@
#include <LibWebView/SearchEngine.h>
#import <Application/ApplicationDelegate.h>
#import <Interface/InfoBar.h>
#import <Interface/LadybirdWebView.h>
#import <Interface/Tab.h>
#import <Interface/TabController.h>
@ -42,8 +43,12 @@
@property (nonatomic, strong) NSMutableArray<TabController*>* managed_tabs;
@property (nonatomic, weak) Tab* active_tab;
@property (nonatomic, strong) InfoBar* info_bar;
@property (nonatomic, strong) TaskManagerController* task_manager_controller;
@property (nonatomic, strong) NSMenuItem* toggle_devtools_menu_item;
- (NSMenuItem*)createApplicationMenu;
- (NSMenuItem*)createFileMenu;
- (NSMenuItem*)createEditMenu;
@ -133,6 +138,10 @@
- (void)setActiveTab:(Tab*)tab
{
self.active_tab = tab;
if (self.info_bar) {
[self.info_bar tabBecameActive:self.active_tab];
}
}
- (Tab*)activeTab
@ -237,6 +246,57 @@
[current_window close];
}
- (void)toggleDevToolsEnabled:(id)sender
{
if (auto result = WebView::Application::the().toggle_devtools_enabled(); result.is_error()) {
auto error_message = MUST(String::formatted("Unable to start DevTools: {}", result.error()));
auto* dialog = [[NSAlert alloc] init];
[dialog setMessageText:Ladybird::string_to_ns_string(error_message)];
[dialog beginSheetModalForWindow:self.active_tab
completionHandler:nil];
} else {
switch (result.value()) {
case WebView::Application::DevtoolsState::Disabled:
[self devtoolsDisabled];
break;
case WebView::Application::DevtoolsState::Enabled:
[self devtoolsEnabled];
break;
}
}
}
- (void)devtoolsDisabled
{
[self.toggle_devtools_menu_item setTitle:@"Enable DevTools"];
if (self.info_bar) {
[self.info_bar hide];
self.info_bar = nil;
}
}
- (void)devtoolsEnabled
{
[self.toggle_devtools_menu_item setTitle:@"Disable DevTools"];
if (!self.info_bar) {
self.info_bar = [[InfoBar alloc] init];
}
auto message = MUST(String::formatted("DevTools is enabled on port {}", WebView::Application::chrome_options().devtools_port));
[self.info_bar showWithMessage:Ladybird::string_to_ns_string(message)
dismissButtonTooltip:@"Disable DevTools"
dismissButtonClicked:^{
MUST(WebView::Application::the().toggle_devtools_enabled());
[self devtoolsDisabled];
}
activeTab:self.active_tab];
}
- (void)openTaskManager:(id)sender
{
if (self.task_manager_controller != nil) {
@ -608,6 +668,12 @@
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"View Source"
action:@selector(viewSource:)
keyEquivalent:@""]];
self.toggle_devtools_menu_item = [[NSMenuItem alloc] initWithTitle:@"Enable DevTools"
action:@selector(toggleDevToolsEnabled:)
keyEquivalent:@"I"];
[submenu addItem:self.toggle_devtools_menu_item];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Task Manager"
action:@selector(openTaskManager:)
keyEquivalent:@"M"]];

View file

@ -3,6 +3,7 @@ add_library(ladybird_impl STATIC
Application/Application.mm
Application/ApplicationDelegate.mm
Interface/Event.mm
Interface/InfoBar.mm
Interface/LadybirdWebView.mm
Interface/LadybirdWebViewBridge.cpp
Interface/LadybirdWebViewWindow.mm

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#import <Cocoa/Cocoa.h>
@class Tab;
using InfoBarDismissed = void (^)(void);
@interface InfoBar : NSStackView
- (void)showWithMessage:(NSString*)message
dismissButtonTooltip:(NSString*)tooltip
dismissButtonClicked:(InfoBarDismissed)on_dimissed
activeTab:(Tab*)tab;
- (void)hide;
- (void)tabBecameActive:(Tab*)tab;
@end

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#import <Interface/InfoBar.h>
#import <Interface/Tab.h>
#if !__has_feature(objc_arc)
# error "This project requires ARC"
#endif
static constexpr CGFloat const INFO_BAR_HEIGHT = 40;
@interface InfoBar ()
@property (nonatomic, strong) NSTextField* text_label;
@property (nonatomic, strong) NSButton* dismiss_button;
@property (nonatomic, copy) InfoBarDismissed on_dimissed;
@end
@implementation InfoBar
- (instancetype)init
{
if (self = [super init]) {
self.text_label = [NSTextField labelWithString:@""];
self.dismiss_button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameStopProgressTemplate]
target:self
action:@selector(dismiss:)];
[self.dismiss_button setBezelStyle:NSBezelStyleAccessoryBarAction];
[self addView:self.text_label inGravity:NSStackViewGravityLeading];
[self addView:self.dismiss_button inGravity:NSStackViewGravityTrailing];
[self setOrientation:NSUserInterfaceLayoutOrientationHorizontal];
[self setEdgeInsets:NSEdgeInsets { 0, 8, 0, 8 }];
[[self heightAnchor] constraintEqualToConstant:INFO_BAR_HEIGHT].active = YES;
}
return self;
}
- (void)showWithMessage:(NSString*)message
dismissButtonTooltip:(NSString*)tooltip
dismissButtonClicked:(InfoBarDismissed)on_dimissed
activeTab:(Tab*)tab
{
[self.text_label setStringValue:message];
[self.dismiss_button setToolTip:tooltip];
self.on_dimissed = on_dimissed;
if (tab) {
[self attachToTab:tab];
}
[self setHidden:NO];
}
- (void)dismiss:(id)sender
{
if (self.on_dimissed) {
self.on_dimissed();
}
[self hide];
}
- (void)hide
{
[self removeFromSuperview];
[self setHidden:YES];
}
- (void)tabBecameActive:(Tab*)tab
{
if (![self isHidden]) {
[self attachToTab:tab];
}
}
- (void)attachToTab:(Tab*)tab
{
[self removeFromSuperview];
[tab.contentView addView:self inGravity:NSStackViewGravityTrailing];
[[self leadingAnchor] constraintEqualToAnchor:[tab.contentView leadingAnchor]].active = YES;
}
@end