LibWebView+UI: Extract some UI-specific displays to Application helpers

This is preparation for moving application menus to LibWebView. We will
need a way to display these dialogs from outside of the UI layer.
This commit is contained in:
Timothy Flynn 2025-09-04 08:53:57 -04:00 committed by Tim Flynn
commit 2632b1375b
Notes: github-actions[bot] 2025-09-11 18:25:30 +00:00
10 changed files with 134 additions and 54 deletions

View file

@ -599,6 +599,16 @@ ErrorOr<LexicalPath> Application::path_for_downloaded_file(StringView file) cons
return LexicalPath::join(downloads_directory, file);
}
void Application::display_download_confirmation_dialog(StringView download_name, LexicalPath const& path) const
{
outln("{} saved to: {}", download_name, path);
}
void Application::display_error_dialog(StringView error_message) const
{
warnln("{}", error_message);
}
ErrorOr<Application::DevtoolsState> Application::toggle_devtools_enabled()
{
if (m_devtools) {

View file

@ -53,6 +53,7 @@ public:
static ProcessManager& process_manager() { return *the().m_process_manager; }
ErrorOr<NonnullRefPtr<WebContentClient>> launch_web_content_process(ViewImplementation&);
virtual Optional<ViewImplementation&> active_web_view() const { return {}; }
void add_child_process(Process&&);
@ -64,6 +65,9 @@ public:
ErrorOr<LexicalPath> path_for_downloaded_file(StringView file) const;
virtual void display_download_confirmation_dialog(StringView download_name, LexicalPath const& path) const;
virtual void display_error_dialog(StringView error_message) const;
enum class DevtoolsState {
Disabled,
Enabled,

View file

@ -231,6 +231,7 @@ public:
Function<void(size_t current_match_index, Optional<size_t> const& total_match_count)> on_find_in_page;
Function<void(Gfx::Color)> on_theme_color_change;
Function<void(Web::Clipboard::SystemClipboardRepresentation, String const&)> on_insert_clipboard_entry;
Function<String()> on_request_clipboard_text;
Function<void(u64 request_id)> on_request_clipboard_entries;
Function<void(Web::HTML::AudioPlayState)> on_audio_play_state_changed;
Function<void(bool, bool)> on_navigation_buttons_state_changed;

View file

@ -18,8 +18,13 @@ class Application final : public WebView::Application {
private:
explicit Application();
virtual Optional<ByteString> ask_user_for_download_folder() const override;
virtual NonnullOwnPtr<Core::EventLoop> create_platform_event_loop() override;
virtual Optional<WebView::ViewImplementation&> active_web_view() const override;
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;
};
}

View file

@ -10,6 +10,9 @@
#include <Utilities/Conversions.h>
#import <Application/Application.h>
#import <Application/ApplicationDelegate.h>
#import <Interface/LadybirdWebView.h>
#import <Interface/Tab.h>
#if !__has_feature(objc_arc)
# error "This project requires ARC"
@ -19,6 +22,25 @@ namespace Ladybird {
Application::Application() = default;
NonnullOwnPtr<Core::EventLoop> Application::create_platform_event_loop()
{
if (!browser_options().headless_mode.has_value()) {
Core::EventLoopManager::install(*new WebView::EventLoopManagerMacOS);
[::Application sharedApplication];
}
return WebView::Application::create_platform_event_loop();
}
Optional<WebView::ViewImplementation&> Application::active_web_view() const
{
ApplicationDelegate* delegate = [NSApp delegate];
if (auto* tab = [delegate activeTab])
return [[tab web_view] view];
return {};
}
Optional<ByteString> Application::ask_user_for_download_folder() const
{
auto* panel = [NSOpenPanel openPanel];
@ -33,14 +55,36 @@ Optional<ByteString> Application::ask_user_for_download_folder() const
return Ladybird::ns_string_to_byte_string([[panel URL] path]);
}
NonnullOwnPtr<Core::EventLoop> Application::create_platform_event_loop()
void Application::display_download_confirmation_dialog(StringView download_name, LexicalPath const& path) const
{
if (!browser_options().headless_mode.has_value()) {
Core::EventLoopManager::install(*new WebView::EventLoopManagerMacOS);
[::Application sharedApplication];
}
ApplicationDelegate* delegate = [NSApp delegate];
return WebView::Application::create_platform_event_loop();
auto message = MUST(String::formatted("{} saved to: {}", download_name, path));
auto* dialog = [[NSAlert alloc] init];
[dialog setMessageText:Ladybird::string_to_ns_string(message)];
[[dialog addButtonWithTitle:@"OK"] setTag:NSModalResponseOK];
[[dialog addButtonWithTitle:@"Open folder"] setTag:NSModalResponseContinue];
__block auto* ns_path = Ladybird::string_to_ns_string(path.string());
[dialog beginSheetModalForWindow:[delegate activeTab]
completionHandler:^(NSModalResponse response) {
if (response == NSModalResponseContinue) {
[[NSWorkspace sharedWorkspace] selectFile:ns_path inFileViewerRootedAtPath:@""];
}
}];
}
void Application::display_error_dialog(StringView error_message) const
{
ApplicationDelegate* delegate = [NSApp delegate];
auto* dialog = [[NSAlert alloc] init];
[dialog setMessageText:Ladybird::string_to_ns_string(error_message)];
[dialog beginSheetModalForWindow:[delegate activeTab]
completionHandler:nil];
}
}

View file

@ -1085,6 +1085,14 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
copy_data_to_clipboard(entry.data, pasteboard_type);
};
m_web_view_bridge->on_request_clipboard_text = []() {
auto* paste_board = [NSPasteboard generalPasteboard];
if (auto* contents = [paste_board stringForType:NSPasteboardTypeString])
return Ladybird::ns_string_to_string(contents);
return String {};
};
m_web_view_bridge->on_request_clipboard_entries = [weak_self](auto request_id) {
LadybirdWebView* self = weak_self;
if (self == nil) {
@ -1154,11 +1162,8 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
- (void)paste:(id)sender
{
auto* paste_board = [NSPasteboard generalPasteboard];
if (auto* contents = [paste_board stringForType:NSPasteboardTypeString]) {
m_web_view_bridge->paste(Ladybird::ns_string_to_string(contents));
}
if (m_web_view_bridge->on_request_clipboard_text)
m_web_view_bridge->paste(m_web_view_bridge->on_request_clipboard_text());
}
- (void)selectAll:(id)sender
@ -1191,34 +1196,15 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
- (void)takeScreenshot:(WebView::ViewImplementation::ScreenshotType)type
{
m_web_view_bridge->take_screenshot(type)
->when_resolved([self](auto const& path) {
auto message = MUST(String::formatted("Screenshot saved to: {}", path));
auto* dialog = [[NSAlert alloc] init];
[dialog setMessageText:Ladybird::string_to_ns_string(message)];
[[dialog addButtonWithTitle:@"OK"] setTag:NSModalResponseOK];
[[dialog addButtonWithTitle:@"Open folder"] setTag:NSModalResponseContinue];
__block auto* ns_path = Ladybird::string_to_ns_string(path.string());
[dialog beginSheetModalForWindow:[self window]
completionHandler:^(NSModalResponse response) {
if (response == NSModalResponseContinue) {
[[NSWorkspace sharedWorkspace] selectFile:ns_path inFileViewerRootedAtPath:@""];
}
}];
->when_resolved([](auto const& path) {
WebView::Application::the().display_download_confirmation_dialog("Screenshot"sv, path);
})
.when_rejected([self](auto const& error) {
.when_rejected([](auto const& error) {
if (error.is_errno() && error.code() == ECANCELED)
return;
auto error_message = MUST(String::formatted("{}", error));
auto* dialog = [[NSAlert alloc] init];
[dialog setMessageText:Ladybird::string_to_ns_string(error_message)];
[dialog beginSheetModalForWindow:[self window]
completionHandler:nil];
WebView::Application::the().display_error_dialog(error_message);
});
}

View file

@ -11,8 +11,10 @@
#include <UI/Qt/Settings.h>
#include <UI/Qt/StringUtils.h>
#include <QDesktopServices>
#include <QFileDialog>
#include <QFileOpenEvent>
#include <QMessageBox>
namespace Ladybird {
@ -88,6 +90,13 @@ BrowserWindow& Application::new_window(Vector<URL::URL> const& initial_urls, Bro
return *window;
}
Optional<WebView::ViewImplementation&> Application::active_web_view() const
{
if (auto* active_tab = this->active_tab())
return active_tab->view();
return {};
}
Optional<ByteString> Application::ask_user_for_download_folder() const
{
auto path = QFileDialog::getExistingDirectory(nullptr, "Select download directory", QDir::homePath());
@ -97,4 +106,26 @@ Optional<ByteString> Application::ask_user_for_download_folder() const
return ak_byte_string_from_qstring(path);
}
void Application::display_download_confirmation_dialog(StringView download_name, LexicalPath const& path) const
{
auto message = MUST(String::formatted("{} saved to: {}", download_name, path));
QMessageBox dialog(active_tab());
dialog.setWindowTitle("Ladybird");
dialog.setIcon(QMessageBox::Information);
dialog.setText(qstring_from_ak_string(message));
dialog.addButton(QMessageBox::Ok);
dialog.addButton(QMessageBox::Open)->setText("Open folder");
if (dialog.exec() == QMessageBox::Open) {
auto path_url = QUrl::fromLocalFile(qstring_from_ak_string(path.dirname()));
QDesktopServices::openUrl(path_url);
}
}
void Application::display_error_dialog(StringView error_message) const
{
QMessageBox::warning(active_tab(), "Ladybird", qstring_from_ak_string(error_message));
}
}

View file

@ -25,16 +25,22 @@ public:
BrowserWindow& new_window(Vector<URL::URL> const& initial_urls, BrowserWindow::IsPopupWindow is_popup_window = BrowserWindow::IsPopupWindow::No, Tab* parent_tab = nullptr, Optional<u64> page_index = {});
BrowserWindow& active_window() { return *m_active_window; }
BrowserWindow& active_window() const { return *m_active_window; }
void set_active_window(BrowserWindow& w) { m_active_window = &w; }
Tab* active_tab() const { return m_active_window ? m_active_window->current_tab() : nullptr; }
private:
explicit Application();
virtual void create_platform_options(WebView::BrowserOptions&, WebView::WebContentOptions&) override;
virtual NonnullOwnPtr<Core::EventLoop> create_platform_event_loop() override;
virtual Optional<WebView::ViewImplementation&> active_web_view() const override;
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;
OwnPtr<QApplication> m_application;
BrowserWindow* m_active_window { nullptr };

View file

@ -1139,8 +1139,8 @@ void BrowserWindow::paste()
if (!m_current_tab)
return;
auto* clipboard = QGuiApplication::clipboard();
m_current_tab->view().paste(ak_string_from_qstring(clipboard->text()));
if (m_current_tab->view().on_request_clipboard_text)
m_current_tab->view().paste(m_current_tab->view().on_request_clipboard_text());
}
void BrowserWindow::update_displayed_zoom_level()

View file

@ -380,6 +380,11 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
clipboard->setMimeData(mime_data);
};
view().on_request_clipboard_text = []() {
auto const* clipboard = QGuiApplication::clipboard();
return ak_string_from_qstring(clipboard->text());
};
view().on_request_clipboard_entries = [this](auto request_id) {
auto const* clipboard = QGuiApplication::clipboard();
@ -496,27 +501,15 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
auto& view = this->view();
view.take_screenshot(type)
->when_resolved([this](auto const& path) {
auto message = MUST(String::formatted("Screenshot saved to: {}", path));
QMessageBox dialog(this);
dialog.setWindowTitle("Ladybird");
dialog.setIcon(QMessageBox::Information);
dialog.setText(qstring_from_ak_string(message));
dialog.addButton(QMessageBox::Ok);
dialog.addButton(QMessageBox::Open)->setText("Open folder");
if (dialog.exec() == QMessageBox::Open) {
auto path_url = QUrl::fromLocalFile(qstring_from_ak_string(path.dirname()));
QDesktopServices::openUrl(path_url);
}
->when_resolved([](auto const& path) {
WebView::Application::the().display_download_confirmation_dialog("Screenshot"sv, path);
})
.when_rejected([this](auto const& error) {
.when_rejected([](auto const& error) {
if (error.is_errno() && error.code() == ECANCELED)
return;
auto error_message = MUST(String::formatted("{}", error));
QMessageBox::warning(this, "Ladybird", qstring_from_ak_string(error_message));
WebView::Application::the().display_error_dialog(error_message);
});
};