LibWebView+UI: Generate action to enable/disable DevTools

This commit is contained in:
Timothy Flynn 2025-09-17 10:03:01 -04:00 committed by Tim Flynn
parent 6d30b0f4d4
commit 14d49d5a3a
Notes: github-actions[bot] 2025-09-18 11:28:44 +00:00
13 changed files with 121 additions and 112 deletions

View file

@ -453,6 +453,8 @@ ErrorOr<void> Application::launch_devtools_server()
m_browser_options.devtools_port = WebView::default_devtools_port;
m_devtools = TRY(DevTools::DevToolsServer::create(*this, *m_browser_options.devtools_port));
on_devtools_enabled();
return {};
}
@ -683,6 +685,10 @@ void Application::initialize_actions()
m_open_settings_page_action = Action::create("Settings"sv, ActionID::OpenSettingsPage, [this]() {
open_url_in_new_tab(URL::about_settings(), Web::HTML::ActivateTab::Yes);
});
m_toggle_devtools_action = Action::create("Enable DevTools"sv, ActionID::ToggleDevTools, [this]() {
if (auto result = toggle_devtools_enabled(); result.is_error())
display_error_dialog(MUST(String::formatted("Unable to start DevTools: {}", result.error())));
});
m_view_source_action = Action::create("View Source"sv, ActionID::ViewSource, [this]() {
if (auto view = active_web_view(); view.has_value())
view->get_source();
@ -833,15 +839,26 @@ void Application::apply_view_options(Badge<ViewImplementation>, ViewImplementati
view.debug_request("navigator-compatibility-mode"sv, m_navigator_compatibility_mode);
}
ErrorOr<Application::DevtoolsState> Application::toggle_devtools_enabled()
ErrorOr<void> Application::toggle_devtools_enabled()
{
if (m_devtools) {
m_devtools.clear();
return DevtoolsState::Disabled;
on_devtools_disabled();
} else {
TRY(launch_devtools_server());
}
TRY(launch_devtools_server());
return DevtoolsState::Enabled;
return {};
}
void Application::on_devtools_enabled() const
{
m_toggle_devtools_action->set_text("Disable DevTools"sv);
}
void Application::on_devtools_disabled() const
{
m_toggle_devtools_action->set_text("Enable DevTools"sv);
}
void Application::refresh_tab_list()

View file

@ -83,6 +83,7 @@ public:
Action& open_about_page_action() { return *m_open_about_page_action; }
Action& open_processes_page_action() { return *m_open_processes_page_action; }
Action& open_settings_page_action() { return *m_open_settings_page_action; }
Action& toggle_devtools_action() { return *m_toggle_devtools_action; }
Action& view_source_action() { return *m_view_source_action; }
Menu& zoom_menu() { return *m_zoom_menu; }
@ -95,11 +96,7 @@ public:
void apply_view_options(Badge<ViewImplementation>, ViewImplementation&);
enum class DevtoolsState {
Disabled,
Enabled,
};
ErrorOr<DevtoolsState> toggle_devtools_enabled();
ErrorOr<void> toggle_devtools_enabled();
void refresh_tab_list();
protected:
@ -115,6 +112,9 @@ protected:
virtual Optional<ByteString> ask_user_for_download_folder() const { return {}; }
virtual void on_devtools_enabled() const;
virtual void on_devtools_disabled() const;
Main::Arguments& arguments() { return m_arguments; }
private:
@ -189,6 +189,7 @@ private:
RefPtr<Action> m_open_about_page_action;
RefPtr<Action> m_open_processes_page_action;
RefPtr<Action> m_open_settings_page_action;
RefPtr<Action> m_toggle_devtools_action;
RefPtr<Action> m_view_source_action;
RefPtr<Menu> m_zoom_menu;

View file

@ -39,6 +39,7 @@ enum class ActionID {
OpenAboutPage,
OpenProcessesPage,
OpenSettingsPage,
ToggleDevTools,
ViewSource,
OpenInNewTab,

View file

@ -26,6 +26,9 @@ private:
virtual Optional<ByteString> ask_user_for_download_folder() const override;
virtual void display_download_confirmation_dialog(StringView download_name, LexicalPath const& path) const override;
virtual void display_error_dialog(StringView error_message) const override;
virtual void on_devtools_enabled() const override;
virtual void on_devtools_disabled() const override;
};
}

View file

@ -98,6 +98,22 @@ void Application::display_error_dialog(StringView error_message) const
completionHandler:nil];
}
void Application::on_devtools_enabled() const
{
WebView::Application::on_devtools_enabled();
ApplicationDelegate* delegate = [NSApp delegate];
[delegate onDevtoolsEnabled];
}
void Application::on_devtools_disabled() const
{
WebView::Application::on_devtools_disabled();
ApplicationDelegate* delegate = [NSApp delegate];
[delegate onDevtoolsDisabled];
}
}
@interface Application ()

View file

@ -37,4 +37,7 @@
- (void)removeTab:(nonnull TabController*)controller;
- (void)onDevtoolsEnabled;
- (void)onDevtoolsDisabled;
@end

View file

@ -25,8 +25,6 @@
@property (nonatomic, strong) InfoBar* info_bar;
@property (nonatomic, strong) NSMenuItem* toggle_devtools_menu_item;
- (NSMenuItem*)createApplicationMenu;
- (NSMenuItem*)createFileMenu;
- (NSMenuItem*)createEditMenu;
@ -124,6 +122,30 @@
[self.managed_tabs removeObject:controller];
}
- (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
@ -178,57 +200,6 @@
[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::browser_options().devtools_port));
[self.info_bar showWithMessage:Ladybird::string_to_ns_string(message)
dismissButtonTitle:@"Disable"
dismissButtonClicked:^{
MUST(WebView::Application::the().toggle_devtools_enabled());
[self devtoolsDisabled];
}
activeTab:self.active_tab];
}
- (void)clearHistory:(id)sender
{
for (TabController* controller in self.managed_tabs) {
@ -388,12 +359,7 @@
auto* submenu = [[NSMenu alloc] initWithTitle:@"Inspect"];
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().view_source_action())];
self.toggle_devtools_menu_item = [[NSMenuItem alloc] initWithTitle:@"Enable DevTools"
action:@selector(toggleDevToolsEnabled:)
keyEquivalent:@"I"];
[submenu addItem:self.toggle_devtools_menu_item];
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().toggle_devtools_action())];
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().open_processes_page_action())];
[menu setSubmenu:submenu];
@ -439,7 +405,7 @@
auto const& browser_options = WebView::Application::browser_options();
if (browser_options.devtools_port.has_value())
[self devtoolsEnabled];
[self onDevtoolsEnabled];
Tab* tab = nil;

View file

@ -155,6 +155,10 @@ static void initialize_native_control(WebView::Action& action, id control)
set_control_image(control, @"gearshape");
[control setKeyEquivalent:@","];
break;
case WebView::ActionID::ToggleDevTools:
set_control_image(control, @"chevron.left.chevron.right");
[control setKeyEquivalent:@"I"];
break;
case WebView::ActionID::ViewSource:
set_control_image(control, @"text.document");
[control setKeyEquivalent:@"u"];

View file

@ -134,4 +134,20 @@ void Application::display_error_dialog(StringView error_message) const
QMessageBox::warning(active_tab(), "Ladybird", qstring_from_ak_string(error_message));
}
void Application::on_devtools_enabled() const
{
WebView::Application::on_devtools_enabled();
if (m_active_window)
m_active_window->on_devtools_enabled();
}
void Application::on_devtools_disabled() const
{
WebView::Application::on_devtools_disabled();
if (m_active_window)
m_active_window->on_devtools_disabled();
}
}

View file

@ -43,6 +43,9 @@ private:
virtual void display_download_confirmation_dialog(StringView download_name, LexicalPath const& path) const override;
virtual void display_error_dialog(StringView error_message) const override;
virtual void on_devtools_enabled() const override;
virtual void on_devtools_disabled() const override;
OwnPtr<QApplication> m_application;
BrowserWindow* m_active_window { nullptr };
};

View file

@ -200,35 +200,10 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
});
auto* inspect_menu = m_hamburger_menu->addMenu("&Inspect");
menuBar()->addMenu(inspect_menu);
inspect_menu->addAction(create_application_action(*this, Application::the().view_source_action()));
m_enable_devtools_action = new QAction("Enable &DevTools", this);
m_enable_devtools_action->setIcon(load_icon_from_uri("resource://icons/browser/dom-tree.png"sv));
m_enable_devtools_action->setShortcuts({
QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_I),
QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_C),
QKeySequence(Qt::Key_F12),
});
inspect_menu->addAction(m_enable_devtools_action);
QObject::connect(m_enable_devtools_action, &QAction::triggered, this, [this] {
if (auto result = WebView::Application::the().toggle_devtools_enabled(); result.is_error()) {
auto error_message = MUST(String::formatted("Unable to start DevTools: {}", result.error()));
QMessageBox::warning(this, "Ladybird", qstring_from_ak_string(error_message));
} else {
switch (result.value()) {
case WebView::Application::DevtoolsState::Disabled:
devtools_disabled();
break;
case WebView::Application::DevtoolsState::Enabled:
devtools_enabled();
break;
}
}
});
inspect_menu->addAction(create_application_action(*inspect_menu, Application::the().view_source_action()));
inspect_menu->addAction(create_application_action(*inspect_menu, Application::the().toggle_devtools_action()));
inspect_menu->addAction(create_application_action(*inspect_menu, Application::the().open_processes_page_action()));
menuBar()->addMenu(inspect_menu);
auto* debug_menu = create_application_menu(*m_hamburger_menu, Application::the().debug_menu());
m_hamburger_menu->addMenu(debug_menu);
@ -301,31 +276,28 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
setContextMenuPolicy(Qt::PreventContextMenu);
if (browser_options.devtools_port.has_value())
devtools_enabled();
on_devtools_enabled();
}
void BrowserWindow::devtools_disabled()
{
m_enable_devtools_action->setText("Enable &DevTools");
setStatusBar(nullptr);
}
void BrowserWindow::devtools_enabled()
void BrowserWindow::on_devtools_enabled()
{
auto* disable_button = new QPushButton("Disable", this);
connect(disable_button, &QPushButton::clicked, this, [this]() {
connect(disable_button, &QPushButton::clicked, this, []() {
MUST(WebView::Application::the().toggle_devtools_enabled());
devtools_disabled();
});
m_enable_devtools_action->setText("Disable &DevTools");
statusBar()->addPermanentWidget(disable_button);
auto message = MUST(String::formatted("DevTools is enabled on port {}", WebView::Application::browser_options().devtools_port));
statusBar()->showMessage(qstring_from_ak_string(message));
}
void BrowserWindow::on_devtools_disabled()
{
setStatusBar(nullptr);
}
Tab& BrowserWindow::new_tab_from_url(URL::URL const& url, Web::HTML::ActivateTab activate_tab)
{
auto& tab = create_new_tab(activate_tab);

View file

@ -51,6 +51,9 @@ public:
double refresh_rate() const { return m_refresh_rate; }
void on_devtools_enabled();
void on_devtools_disabled();
public slots:
void device_pixel_ratio_changed(qreal dpi);
void refresh_rate_changed(qreal refresh_rate);
@ -104,9 +107,6 @@ private:
double m_device_pixel_ratio { 0 };
double m_refresh_rate { 60.0 };
void devtools_disabled();
void devtools_enabled();
QTabWidget* m_tabs_container { nullptr };
Tab* m_current_tab { nullptr };
@ -117,7 +117,6 @@ private:
QAction* m_new_tab_action { nullptr };
QAction* m_new_window_action { nullptr };
QAction* m_find_in_page_action { nullptr };
QAction* m_enable_devtools_action { nullptr };
IsPopupWindow m_is_popup_window { IsPopupWindow::No };
};

View file

@ -114,6 +114,14 @@ static void initialize_native_control(WebView::Action& action, QAction& qaction,
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/settings.png"sv));
qaction.setShortcut(QKeySequence::StandardKey::Preferences);
break;
case WebView::ActionID::ToggleDevTools:
qaction.setIcon(load_icon_from_uri("resource://icons/browser/dom-tree.png"sv));
qaction.setShortcuts({
QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_I),
QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_C),
QKeySequence(Qt::Key_F12),
});
break;
case WebView::ActionID::ViewSource:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/filetype-html.png"sv));
qaction.setShortcut(QKeySequence(Qt::CTRL | Qt::Key_U));