LibWebView+UI: Generate application context menus

This migrates all duplicated context menus from the UIs to LibWebView.
The context menu actions are now largely handled directly in LibWebView,
with some UI-specific callbacks added to display e.g. confirmation
dialogs.

Actions that only ever apply to a specific web view are stored on the
ViewImplementation itself. Actions that need to be dynamically applied
to the active web view are stored on the Application.
This commit is contained in:
Timothy Flynn 2025-09-01 08:20:14 -04:00 committed by Tim Flynn
commit 5d8d9b337a
Notes: github-actions[bot] 2025-09-11 18:25:14 +00:00
18 changed files with 553 additions and 1172 deletions

View file

@ -19,6 +19,7 @@
#include <LibWebView/Database.h> #include <LibWebView/Database.h>
#include <LibWebView/HeadlessWebView.h> #include <LibWebView/HeadlessWebView.h>
#include <LibWebView/HelperProcess.h> #include <LibWebView/HelperProcess.h>
#include <LibWebView/Menu.h>
#include <LibWebView/URL.h> #include <LibWebView/URL.h>
#include <LibWebView/UserAgent.h> #include <LibWebView/UserAgent.h>
#include <LibWebView/Utilities.h> #include <LibWebView/Utilities.h>
@ -268,6 +269,7 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
}; };
create_platform_options(m_browser_options, m_web_content_options); create_platform_options(m_browser_options, m_web_content_options);
initialize_actions();
m_event_loop = create_platform_event_loop(); m_event_loop = create_platform_event_loop();
TRY(launch_services()); TRY(launch_services());
@ -609,6 +611,32 @@ void Application::display_error_dialog(StringView error_message) const
warnln("{}", error_message); warnln("{}", error_message);
} }
void Application::initialize_actions()
{
m_reload_action = Action::create("Reload"sv, ActionID::Reload, [this]() {
if (auto view = active_web_view(); view.has_value())
view->reload();
});
m_copy_selection_action = Action::create("Copy"sv, ActionID::CopySelection, [this]() {
if (auto view = active_web_view(); view.has_value())
view->insert_text_into_clipboard(view->selected_text());
});
m_paste_action = Action::create("Paste"sv, ActionID::Paste, [this]() {
if (auto view = active_web_view(); view.has_value())
view->paste_text_from_clipboard();
});
m_select_all_action = Action::create("Select All"sv, ActionID::SelectAll, [this]() {
if (auto view = active_web_view(); view.has_value())
view->select_all();
});
m_view_source_action = Action::create("View Source"sv, ActionID::ViewSource, [this]() {
if (auto view = active_web_view(); view.has_value())
view->get_source();
});
}
ErrorOr<Application::DevtoolsState> Application::toggle_devtools_enabled() ErrorOr<Application::DevtoolsState> Application::toggle_devtools_enabled()
{ {
if (m_devtools) { if (m_devtools) {

View file

@ -68,6 +68,12 @@ public:
virtual void display_download_confirmation_dialog(StringView download_name, LexicalPath const& path) const; virtual void display_download_confirmation_dialog(StringView download_name, LexicalPath const& path) const;
virtual void display_error_dialog(StringView error_message) const; virtual void display_error_dialog(StringView error_message) const;
Action& reload_action() { return *m_reload_action; }
Action& copy_selection_action() { return *m_copy_selection_action; }
Action& paste_action() { return *m_paste_action; }
Action& select_all_action() { return *m_select_all_action; }
Action& view_source_action() { return *m_view_source_action; }
enum class DevtoolsState { enum class DevtoolsState {
Disabled, Disabled,
Enabled, Enabled,
@ -97,6 +103,8 @@ private:
ErrorOr<void> launch_image_decoder_server(); ErrorOr<void> launch_image_decoder_server();
ErrorOr<void> launch_devtools_server(); ErrorOr<void> launch_devtools_server();
void initialize_actions();
virtual Vector<DevTools::TabDescription> tab_list() const override; virtual Vector<DevTools::TabDescription> tab_list() const override;
virtual Vector<DevTools::CSSProperty> css_property_list() const override; virtual Vector<DevTools::CSSProperty> css_property_list() const override;
virtual void inspect_tab(DevTools::TabDescription const&, OnTabInspectionComplete) const override; virtual void inspect_tab(DevTools::TabDescription const&, OnTabInspectionComplete) const override;
@ -152,6 +160,12 @@ private:
OwnPtr<Core::EventLoop> m_event_loop; OwnPtr<Core::EventLoop> m_event_loop;
OwnPtr<ProcessManager> m_process_manager; OwnPtr<ProcessManager> m_process_manager;
RefPtr<Action> m_reload_action;
RefPtr<Action> m_copy_selection_action;
RefPtr<Action> m_paste_action;
RefPtr<Action> m_select_all_action;
RefPtr<Action> m_view_source_action;
#if defined(AK_OS_MACOS) #if defined(AK_OS_MACOS)
OwnPtr<MachPortServer> m_mach_port_server; OwnPtr<MachPortServer> m_mach_port_server;
#endif #endif

View file

@ -23,6 +23,35 @@
namespace WebView { namespace WebView {
enum class ActionID { enum class ActionID {
NavigateBack,
NavigateForward,
Reload,
CopySelection,
Paste,
SelectAll,
SearchSelectedText,
TakeVisibleScreenshot,
TakeFullScreenshot,
ViewSource,
OpenInNewTab,
CopyURL,
OpenImage,
CopyImage,
OpenAudio,
OpenVideo,
PlayMedia,
PauseMedia,
MuteMedia,
UnmuteMedia,
ToggleMediaControlsState,
ToggleMediaLoopState,
}; };
class WEBVIEW_API Action class WEBVIEW_API Action

View file

@ -6,16 +6,20 @@
#include <AK/Error.h> #include <AK/Error.h>
#include <AK/String.h> #include <AK/String.h>
#include <AK/TemporaryChange.h>
#include <AK/Time.h> #include <AK/Time.h>
#include <LibCore/StandardPaths.h> #include <LibCore/StandardPaths.h>
#include <LibCore/Timer.h> #include <LibCore/Timer.h>
#include <LibGfx/ImageFormats/PNGWriter.h> #include <LibGfx/ImageFormats/PNGWriter.h>
#include <LibURL/Parser.h>
#include <LibWeb/Clipboard/SystemClipboard.h> #include <LibWeb/Clipboard/SystemClipboard.h>
#include <LibWeb/Crypto/Crypto.h> #include <LibWeb/Crypto/Crypto.h>
#include <LibWeb/Infra/Strings.h> #include <LibWeb/Infra/Strings.h>
#include <LibWebView/Application.h> #include <LibWebView/Application.h>
#include <LibWebView/HelperProcess.h> #include <LibWebView/HelperProcess.h>
#include <LibWebView/Menu.h>
#include <LibWebView/SiteIsolation.h> #include <LibWebView/SiteIsolation.h>
#include <LibWebView/URL.h>
#include <LibWebView/UserAgent.h> #include <LibWebView/UserAgent.h>
#include <LibWebView/ViewImplementation.h> #include <LibWebView/ViewImplementation.h>
@ -49,6 +53,8 @@ ViewImplementation::ViewImplementation()
{ {
s_all_views.set(m_view_id, this); s_all_views.set(m_view_id, this);
initialize_context_menus();
m_repeated_crash_timer = Core::Timer::create_single_shot(1000, [this] { m_repeated_crash_timer = Core::Timer::create_single_shot(1000, [this] {
// Reset the "crashing a lot" counter after 1 second in case we just // Reset the "crashing a lot" counter after 1 second in case we just
// happen to be visiting crashy websites a lot. // happen to be visiting crashy websites a lot.
@ -271,11 +277,6 @@ void ViewImplementation::select_all()
client().async_select_all(page_id()); client().async_select_all(page_id());
} }
void ViewImplementation::paste(String const& text)
{
client().async_paste(page_id(), text);
}
void ViewImplementation::find_in_page(String const& query, CaseSensitivity case_sensitivity) void ViewImplementation::find_in_page(String const& query, CaseSensitivity case_sensitivity)
{ {
client().async_find_in_page(page_id(), query, case_sensitivity); client().async_find_in_page(page_id(), query, case_sensitivity);
@ -456,31 +457,23 @@ void ViewImplementation::select_dropdown_closed(Optional<u32> const& selected_it
client().async_select_dropdown_closed(page_id(), selected_item_id); client().async_select_dropdown_closed(page_id(), selected_item_id);
} }
void ViewImplementation::insert_text_into_clipboard(ByteString text) const
{
if (on_insert_clipboard_entry)
on_insert_clipboard_entry({ move(text), "text/plain"_string }, {});
}
void ViewImplementation::paste_text_from_clipboard()
{
if (on_request_clipboard_text)
client().async_paste(page_id(), on_request_clipboard_text());
}
void ViewImplementation::retrieved_clipboard_entries(u64 request_id, ReadonlySpan<Web::Clipboard::SystemClipboardItem> items) void ViewImplementation::retrieved_clipboard_entries(u64 request_id, ReadonlySpan<Web::Clipboard::SystemClipboardItem> items)
{ {
client().async_retrieved_clipboard_entries(page_id(), request_id, items); client().async_retrieved_clipboard_entries(page_id(), request_id, items);
} }
void ViewImplementation::toggle_media_play_state()
{
client().async_toggle_media_play_state(page_id());
}
void ViewImplementation::toggle_media_mute_state()
{
client().async_toggle_media_mute_state(page_id());
}
void ViewImplementation::toggle_media_loop_state()
{
client().async_toggle_media_loop_state(page_id());
}
void ViewImplementation::toggle_media_controls_state()
{
client().async_toggle_media_controls_state(page_id());
}
void ViewImplementation::toggle_page_mute_state() void ViewImplementation::toggle_page_mute_state()
{ {
m_mute_state = Web::HTML::invert_mute_state(m_mute_state); m_mute_state = Web::HTML::invert_mute_state(m_mute_state);
@ -513,8 +506,8 @@ void ViewImplementation::did_change_audio_play_state(Badge<WebContentClient>, We
void ViewImplementation::did_update_navigation_buttons_state(Badge<WebContentClient>, bool back_enabled, bool forward_enabled) const void ViewImplementation::did_update_navigation_buttons_state(Badge<WebContentClient>, bool back_enabled, bool forward_enabled) const
{ {
if (on_navigation_buttons_state_changed) m_navigate_back_action->set_enabled(back_enabled);
on_navigation_buttons_state_changed(back_enabled, forward_enabled); m_navigate_forward_action->set_enabled(forward_enabled);
} }
void ViewImplementation::did_allocate_backing_stores(Badge<WebContentClient>, i32 front_bitmap_id, Gfx::ShareableBitmap const& front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap const& back_bitmap) void ViewImplementation::did_allocate_backing_stores(Badge<WebContentClient>, i32 front_bitmap_id, Gfx::ShareableBitmap const& front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap const& back_bitmap)
@ -805,4 +798,226 @@ void ViewImplementation::use_native_user_style_sheet()
set_user_style_sheet(native_stylesheet_source); set_user_style_sheet(native_stylesheet_source);
} }
void ViewImplementation::initialize_context_menus()
{
auto& application = Application::the();
m_navigate_back_action = Action::create("Go Back"sv, ActionID::NavigateBack, [this]() {
traverse_the_history_by_delta(-1);
});
m_navigate_forward_action = Action::create("Go Forward"sv, ActionID::NavigateForward, [this]() {
traverse_the_history_by_delta(+1);
});
m_navigate_back_action->set_enabled(false);
m_navigate_forward_action->set_enabled(false);
m_search_selected_text_action = Action::create("Search Selected Text"sv, ActionID::SearchSelectedText, [this]() {
auto const& search_engine = Application::settings().search_engine();
if (!search_engine.has_value())
return;
auto url_string = search_engine->format_search_query_for_navigation(*m_search_text);
auto url = URL::Parser::basic_parse(url_string);
VERIFY(url.has_value());
if (on_link_click)
on_link_click(*url, "_blank"sv, 0);
});
m_search_selected_text_action->set_visible(false);
auto take_and_save_screenshot = [this](auto type) {
take_screenshot(type)
->when_resolved([](auto const& path) {
Application::the().display_download_confirmation_dialog("Screenshot"sv, path);
})
.when_rejected([](auto const& error) {
if (error.is_errno() && error.code() == ECANCELED)
return;
auto error_message = MUST(String::formatted("{}", error));
Application::the().display_error_dialog(error_message);
});
};
m_take_visible_screenshot_action = Action::create("Take Visible Screenshot"sv, ActionID::TakeVisibleScreenshot, [take_and_save_screenshot]() {
take_and_save_screenshot(ScreenshotType::Visible);
});
m_take_full_screenshot_action = Action::create("Take Full Screenshot"sv, ActionID::TakeFullScreenshot, [take_and_save_screenshot]() {
take_and_save_screenshot(ScreenshotType::Full);
});
m_open_in_new_tab_action = Action::create("Open in New Tab"sv, ActionID::OpenInNewTab, [this]() {
if (on_link_click)
on_link_click(m_context_menu_url, {}, Web::UIEvents::Mod_PlatformCtrl);
});
m_copy_url_action = Action::create("Copy URL"sv, ActionID::CopyURL, [this]() {
insert_text_into_clipboard(url_text_to_copy(m_context_menu_url));
});
m_open_image_action = Action::create("Open Image"sv, ActionID::OpenImage, [this]() {
if (on_link_click)
on_link_click(m_context_menu_url, {}, {});
});
m_copy_image_action = Action::create("Copy Image"sv, ActionID::CopyImage, [this]() {
if (!m_image_context_menu_bitmap.has_value())
return;
auto bitmap = m_image_context_menu_bitmap.release_value();
if (!bitmap.is_valid())
return;
auto encoded = Gfx::PNGWriter::encode(*bitmap.bitmap());
if (encoded.is_error())
return;
if (on_insert_clipboard_entry)
on_insert_clipboard_entry({ ByteString { encoded.value().bytes() }, "image/png"_string }, {});
});
m_open_audio_action = Action::create("Open Audio"sv, ActionID::OpenAudio, [this]() {
if (on_link_click)
on_link_click(m_context_menu_url, {}, {});
});
m_open_video_action = Action::create("Open Video"sv, ActionID::OpenVideo, [this]() {
if (on_link_click)
on_link_click(m_context_menu_url, {}, {});
});
m_media_play_action = Action::create("Play"sv, ActionID::PlayMedia, [this]() {
client().async_toggle_media_play_state(page_id());
});
m_media_pause_action = Action::create("Pause"sv, ActionID::PauseMedia, [this]() {
client().async_toggle_media_play_state(page_id());
});
m_media_mute_action = Action::create("Mute"sv, ActionID::MuteMedia, [this]() {
client().async_toggle_media_mute_state(page_id());
});
m_media_unmute_action = Action::create("Unmute"sv, ActionID::UnmuteMedia, [this]() {
client().async_toggle_media_mute_state(page_id());
});
m_media_controls_action = Action::create_checkable("Show Controls"sv, ActionID::ToggleMediaControlsState, [this]() {
client().async_toggle_media_controls_state(page_id());
});
m_media_loop_action = Action::create_checkable("Loop"sv, ActionID::ToggleMediaLoopState, [this]() {
client().async_toggle_media_loop_state(page_id());
});
m_page_context_menu = Menu::create("Page Context Menu"sv);
m_page_context_menu->add_action(*m_navigate_back_action);
m_page_context_menu->add_action(*m_navigate_forward_action);
m_page_context_menu->add_action(application.reload_action());
m_page_context_menu->add_separator();
m_page_context_menu->add_action(application.copy_selection_action());
m_page_context_menu->add_action(application.paste_action());
m_page_context_menu->add_action(application.select_all_action());
m_page_context_menu->add_separator();
m_page_context_menu->add_action(*m_search_selected_text_action);
m_page_context_menu->add_separator();
m_page_context_menu->add_action(*m_take_visible_screenshot_action);
m_page_context_menu->add_action(*m_take_full_screenshot_action);
m_page_context_menu->add_separator();
m_page_context_menu->add_action(application.view_source_action());
m_link_context_menu = Menu::create("Link Context Menu"sv);
m_link_context_menu->add_action(*m_open_in_new_tab_action);
m_link_context_menu->add_action(*m_copy_url_action);
m_image_context_menu = Menu::create("Image Context Menu"sv);
m_image_context_menu->add_action(*m_open_image_action);
m_image_context_menu->add_action(*m_open_in_new_tab_action);
m_image_context_menu->add_separator();
m_image_context_menu->add_action(*m_copy_image_action);
m_image_context_menu->add_action(*m_copy_url_action);
m_media_context_menu = Menu::create("Media Context Menu"sv);
m_media_context_menu->add_action(*m_media_play_action);
m_media_context_menu->add_action(*m_media_pause_action);
m_media_context_menu->add_action(*m_media_mute_action);
m_media_context_menu->add_action(*m_media_unmute_action);
m_media_context_menu->add_action(*m_media_controls_action);
m_media_context_menu->add_action(*m_media_loop_action);
m_media_context_menu->add_separator();
m_media_context_menu->add_action(*m_open_audio_action);
m_media_context_menu->add_action(*m_open_video_action);
m_media_context_menu->add_action(*m_open_in_new_tab_action);
m_media_context_menu->add_separator();
m_media_context_menu->add_action(*m_copy_url_action);
}
void ViewImplementation::did_request_page_context_menu(Badge<WebContentClient>, Gfx::IntPoint content_position)
{
auto const& search_engine = Application::settings().search_engine();
auto selected_text = search_engine.has_value() ? selected_text_with_whitespace_collapsed() : OptionalNone {};
TemporaryChange change_url { m_search_text, move(selected_text) };
if (m_search_text.has_value()) {
m_search_selected_text_action->set_text(search_engine->format_search_query_for_display(*m_search_text));
m_search_selected_text_action->set_visible(true);
} else {
m_search_selected_text_action->set_visible(false);
}
if (m_page_context_menu->on_activation)
m_page_context_menu->on_activation(to_widget_position(content_position));
}
void ViewImplementation::did_request_link_context_menu(Badge<WebContentClient>, Gfx::IntPoint content_position, URL::URL url)
{
m_context_menu_url = move(url);
m_open_in_new_tab_action->set_text("Open in New Tab"sv);
switch (url_type(m_context_menu_url)) {
case URLType::Email:
m_copy_url_action->set_text("Copy Email Address"sv);
break;
case URLType::Telephone:
m_copy_url_action->set_text("Copy Phone Number"sv);
break;
case URLType::Other:
m_copy_url_action->set_text("Copy Link Address"sv);
break;
}
if (m_link_context_menu->on_activation)
m_link_context_menu->on_activation(to_widget_position(content_position));
}
void ViewImplementation::did_request_image_context_menu(Badge<WebContentClient>, Gfx::IntPoint content_position, URL::URL url, Optional<Gfx::ShareableBitmap> bitmap)
{
m_context_menu_url = move(url);
m_image_context_menu_bitmap = move(bitmap);
m_open_in_new_tab_action->set_text("Open Image in New Tab"sv);
m_copy_url_action->set_text("Copy Image URL"sv);
m_copy_image_action->set_enabled(m_image_context_menu_bitmap.has_value());
if (m_image_context_menu->on_activation)
m_image_context_menu->on_activation(to_widget_position(content_position));
}
void ViewImplementation::did_request_media_context_menu(Badge<WebContentClient>, Gfx::IntPoint content_position, Web::Page::MediaContextMenu menu)
{
m_context_menu_url = move(menu.media_url);
m_open_in_new_tab_action->set_text(menu.is_video ? "Open Video in New Tab"sv : "Open Audio in new Tab"sv);
m_copy_url_action->set_text(menu.is_video ? "Copy Video URL"sv : "Copy Audio URL"sv);
m_open_audio_action->set_visible(!menu.is_video);
m_open_video_action->set_visible(menu.is_video);
m_media_play_action->set_visible(!menu.is_playing);
m_media_pause_action->set_visible(menu.is_playing);
m_media_mute_action->set_visible(!menu.is_muted);
m_media_unmute_action->set_visible(menu.is_muted);
m_media_controls_action->set_checked(menu.has_user_agent_controls);
m_media_loop_action->set_checked(menu.is_looping);
if (m_media_context_menu->on_activation)
m_media_context_menu->on_activation(to_widget_position(content_position));
}
} }

View file

@ -88,7 +88,6 @@ public:
void find_in_page(String const& query, CaseSensitivity = CaseSensitivity::CaseInsensitive); void find_in_page(String const& query, CaseSensitivity = CaseSensitivity::CaseInsensitive);
void find_in_page_next_match(); void find_in_page_next_match();
void find_in_page_previous_match(); void find_in_page_previous_match();
void paste(String const&);
void get_source(); void get_source();
@ -132,13 +131,10 @@ public:
void file_picker_closed(Vector<Web::HTML::SelectedFile> selected_files); void file_picker_closed(Vector<Web::HTML::SelectedFile> selected_files);
void select_dropdown_closed(Optional<u32> const& selected_item_id); void select_dropdown_closed(Optional<u32> const& selected_item_id);
void insert_text_into_clipboard(ByteString) const;
void paste_text_from_clipboard();
void retrieved_clipboard_entries(u64 request_id, ReadonlySpan<Web::Clipboard::SystemClipboardItem>); void retrieved_clipboard_entries(u64 request_id, ReadonlySpan<Web::Clipboard::SystemClipboardItem>);
void toggle_media_play_state();
void toggle_media_mute_state();
void toggle_media_loop_state();
void toggle_media_controls_state();
Web::HTML::MuteState page_mute_state() const { return m_mute_state; } Web::HTML::MuteState page_mute_state() const { return m_mute_state; }
void toggle_page_mute_state(); void toggle_page_mute_state();
@ -174,10 +170,6 @@ public:
Function<String(Web::HTML::ActivateTab, Web::HTML::WebViewHints, Optional<u64>)> on_new_web_view; Function<String(Web::HTML::ActivateTab, Web::HTML::WebViewHints, Optional<u64>)> on_new_web_view;
Function<void()> on_activate_tab; Function<void()> on_activate_tab;
Function<void()> on_close; Function<void()> on_close;
Function<void(Gfx::IntPoint screen_position)> on_context_menu_request;
Function<void(URL::URL const&, Gfx::IntPoint screen_position)> on_link_context_menu_request;
Function<void(URL::URL const&, Gfx::IntPoint screen_position, Optional<Gfx::ShareableBitmap> const&)> on_image_context_menu_request;
Function<void(Gfx::IntPoint screen_position, Web::Page::MediaContextMenu const&)> on_media_context_menu_request;
Function<void(URL::URL const&)> on_link_hover; Function<void(URL::URL const&)> on_link_hover;
Function<void()> on_link_unhover; Function<void()> on_link_unhover;
Function<void(URL::URL const&, ByteString const& target, unsigned modifiers)> on_link_click; Function<void(URL::URL const&, ByteString const& target, unsigned modifiers)> on_link_click;
@ -234,9 +226,21 @@ public:
Function<String()> on_request_clipboard_text; Function<String()> on_request_clipboard_text;
Function<void(u64 request_id)> on_request_clipboard_entries; Function<void(u64 request_id)> on_request_clipboard_entries;
Function<void(Web::HTML::AudioPlayState)> on_audio_play_state_changed; Function<void(Web::HTML::AudioPlayState)> on_audio_play_state_changed;
Function<void(bool, bool)> on_navigation_buttons_state_changed;
Function<void()> on_web_content_crashed; Function<void()> on_web_content_crashed;
Menu& page_context_menu() { return *m_page_context_menu; }
Menu& link_context_menu() { return *m_link_context_menu; }
Menu& image_context_menu() { return *m_image_context_menu; }
Menu& media_context_menu() { return *m_media_context_menu; }
void did_request_page_context_menu(Badge<WebContentClient>, Gfx::IntPoint content_position);
void did_request_link_context_menu(Badge<WebContentClient>, Gfx::IntPoint content_position, URL::URL url);
void did_request_image_context_menu(Badge<WebContentClient>, Gfx::IntPoint content_position, URL::URL url, Optional<Gfx::ShareableBitmap> bitmap);
void did_request_media_context_menu(Badge<WebContentClient>, Gfx::IntPoint content_position, Web::Page::MediaContextMenu menu);
Action& navigate_back_action() { return *m_navigate_back_action; }
Action& navigate_forward_action() { return *m_navigate_forward_action; }
virtual Web::DevicePixelSize viewport_size() const = 0; virtual Web::DevicePixelSize viewport_size() const = 0;
virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const = 0; virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const = 0;
virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const = 0; virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const = 0;
@ -272,6 +276,8 @@ protected:
virtual void autoplay_settings_changed() override; virtual void autoplay_settings_changed() override;
virtual void do_not_track_changed() override; virtual void do_not_track_changed() override;
void initialize_context_menus();
struct SharedBitmap { struct SharedBitmap {
i32 id { -1 }; i32 id { -1 };
Web::DevicePixelSize last_painted_size; Web::DevicePixelSize last_painted_size;
@ -294,6 +300,37 @@ protected:
float m_device_pixel_ratio { 1.0 }; float m_device_pixel_ratio { 1.0 };
double m_maximum_frames_per_second { 60.0 }; double m_maximum_frames_per_second { 60.0 };
RefPtr<Menu> m_page_context_menu;
RefPtr<Menu> m_link_context_menu;
RefPtr<Menu> m_image_context_menu;
RefPtr<Menu> m_media_context_menu;
RefPtr<Action> m_navigate_back_action;
RefPtr<Action> m_navigate_forward_action;
RefPtr<Action> m_search_selected_text_action;
Optional<String> m_search_text;
RefPtr<Action> m_take_visible_screenshot_action;
RefPtr<Action> m_take_full_screenshot_action;
RefPtr<Action> m_open_in_new_tab_action;
RefPtr<Action> m_copy_url_action;
URL::URL m_context_menu_url;
RefPtr<Action> m_open_image_action;
RefPtr<Action> m_copy_image_action;
Optional<Gfx::ShareableBitmap> m_image_context_menu_bitmap;
RefPtr<Action> m_open_audio_action;
RefPtr<Action> m_open_video_action;
RefPtr<Action> m_media_play_action;
RefPtr<Action> m_media_pause_action;
RefPtr<Action> m_media_mute_action;
RefPtr<Action> m_media_unmute_action;
RefPtr<Action> m_media_controls_action;
RefPtr<Action> m_media_loop_action;
Queue<Web::InputEvent> m_pending_input_events; Queue<Web::InputEvent> m_pending_input_events;
RefPtr<Core::Timer> m_backing_store_shrink_timer; RefPtr<Core::Timer> m_backing_store_shrink_timer;

View file

@ -262,34 +262,26 @@ void WebContentClient::did_middle_click_link(u64 page_id, URL::URL url, ByteStri
void WebContentClient::did_request_context_menu(u64 page_id, Gfx::IntPoint content_position) void WebContentClient::did_request_context_menu(u64 page_id, Gfx::IntPoint content_position)
{ {
if (auto view = view_for_page_id(page_id); view.has_value()) { if (auto view = view_for_page_id(page_id); view.has_value())
if (view->on_context_menu_request) view->did_request_page_context_menu({}, content_position);
view->on_context_menu_request(view->to_widget_position(content_position));
}
} }
void WebContentClient::did_request_link_context_menu(u64 page_id, Gfx::IntPoint content_position, URL::URL url, ByteString, unsigned) void WebContentClient::did_request_link_context_menu(u64 page_id, Gfx::IntPoint content_position, URL::URL url, ByteString, unsigned)
{ {
if (auto view = view_for_page_id(page_id); view.has_value()) { if (auto view = view_for_page_id(page_id); view.has_value())
if (view->on_link_context_menu_request) view->did_request_link_context_menu({}, content_position, move(url));
view->on_link_context_menu_request(url, view->to_widget_position(content_position));
}
} }
void WebContentClient::did_request_image_context_menu(u64 page_id, Gfx::IntPoint content_position, URL::URL url, ByteString, unsigned, Optional<Gfx::ShareableBitmap> bitmap) void WebContentClient::did_request_image_context_menu(u64 page_id, Gfx::IntPoint content_position, URL::URL url, ByteString, unsigned, Optional<Gfx::ShareableBitmap> bitmap)
{ {
if (auto view = view_for_page_id(page_id); view.has_value()) { if (auto view = view_for_page_id(page_id); view.has_value())
if (view->on_image_context_menu_request) view->did_request_image_context_menu({}, content_position, move(url), move(bitmap));
view->on_image_context_menu_request(url, view->to_widget_position(content_position), bitmap);
}
} }
void WebContentClient::did_request_media_context_menu(u64 page_id, Gfx::IntPoint content_position, ByteString, unsigned, Web::Page::MediaContextMenu menu) void WebContentClient::did_request_media_context_menu(u64 page_id, Gfx::IntPoint content_position, ByteString, unsigned, Web::Page::MediaContextMenu menu)
{ {
if (auto view = view_for_page_id(page_id); view.has_value()) { if (auto view = view_for_page_id(page_id); view.has_value())
if (view->on_media_context_menu_request) view->did_request_media_context_menu({}, content_position, move(menu));
view->on_media_context_menu_request(view->to_widget_position(content_position), menu);
}
} }
void WebContentClient::did_get_source(u64 page_id, URL::URL url, URL::URL base_url, String source) void WebContentClient::did_get_source(u64 page_id, URL::URL url, URL::URL base_url, String source)

View file

@ -10,10 +10,10 @@
#import <Application/ApplicationDelegate.h> #import <Application/ApplicationDelegate.h>
#import <Interface/InfoBar.h> #import <Interface/InfoBar.h>
#import <Interface/LadybirdWebView.h> #import <Interface/LadybirdWebView.h>
#import <Interface/Menu.h>
#import <Interface/Tab.h> #import <Interface/Tab.h>
#import <Interface/TabController.h> #import <Interface/TabController.h>
#import <LibWebView/UserAgent.h> #import <LibWebView/UserAgent.h>
#import <Utilities/Conversions.h> #import <Utilities/Conversions.h>
#if !__has_feature(objc_arc) #if !__has_feature(objc_arc)
@ -471,17 +471,12 @@
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Cut" [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Cut"
action:@selector(cut:) action:@selector(cut:)
keyEquivalent:@"x"]]; keyEquivalent:@"x"]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy"
action:@selector(copy:) [submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().copy_selection_action())];
keyEquivalent:@"c"]]; [submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().paste_action())];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Paste"
action:@selector(paste:)
keyEquivalent:@"v"]];
[submenu addItem:[NSMenuItem separatorItem]]; [submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Select All" [submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().select_all_action())];
action:@selector(selectAll:)
keyEquivalent:@"a"]];
[submenu addItem:[NSMenuItem separatorItem]]; [submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Find..." [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Find..."
@ -586,19 +581,11 @@
- (NSMenuItem*)createHistoryMenu - (NSMenuItem*)createHistoryMenu
{ {
auto* menu = [[NSMenuItem alloc] init]; auto* menu = [[NSMenuItem alloc] init];
auto* submenu = [[NSMenu alloc] initWithTitle:@"History"]; auto* submenu = [[NSMenu alloc] initWithTitle:@"History"];
[submenu setAutoenablesItems:NO];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Reload Page" [submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().reload_action())];
action:@selector(reload:)
keyEquivalent:@"r"]];
[submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Navigate Back"
action:@selector(navigateBack:)
keyEquivalent:@"["]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Navigate Forward"
action:@selector(navigateForward:)
keyEquivalent:@"]"]];
[submenu addItem:[NSMenuItem separatorItem]]; [submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Clear History" [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Clear History"
@ -614,9 +601,7 @@
auto* menu = [[NSMenuItem alloc] init]; auto* menu = [[NSMenuItem alloc] init];
auto* submenu = [[NSMenu alloc] initWithTitle:@"Inspect"]; auto* submenu = [[NSMenu alloc] initWithTitle:@"Inspect"];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"View Source" [submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().view_source_action())];
action:@selector(viewSource:)
keyEquivalent:@"u"]];
self.toggle_devtools_menu_item = [[NSMenuItem alloc] initWithTitle:@"Enable DevTools" self.toggle_devtools_menu_item = [[NSMenuItem alloc] initWithTitle:@"Enable DevTools"
action:@selector(toggleDevToolsEnabled:) action:@selector(toggleDevToolsEnabled:)

View file

@ -36,9 +36,6 @@
- (void)onLoadFinish:(URL::URL const&)url; - (void)onLoadFinish:(URL::URL const&)url;
- (void)onURLChange:(URL::URL const&)url; - (void)onURLChange:(URL::URL const&)url;
- (void)onBackNavigationEnabled:(BOOL)back_enabled
forwardNavigationEnabled:(BOOL)forward_enabled;
- (void)onTitleChange:(Utf16String const&)title; - (void)onTitleChange:(Utf16String const&)title;
- (void)onFaviconChange:(Gfx::Bitmap const&)bitmap; - (void)onFaviconChange:(Gfx::Bitmap const&)bitmap;
- (void)onAudioPlayStateChange:(Web::HTML::AudioPlayState)play_state; - (void)onAudioPlayStateChange:(Web::HTML::AudioPlayState)play_state;
@ -58,10 +55,6 @@
- (void)loadURL:(URL::URL const&)url; - (void)loadURL:(URL::URL const&)url;
- (void)loadHTML:(StringView)html; - (void)loadHTML:(StringView)html;
- (void)navigateBack;
- (void)navigateForward;
- (void)reload;
- (WebView::ViewImplementation&)view; - (WebView::ViewImplementation&)view;
- (String const&)handle; - (String const&)handle;
@ -89,6 +82,4 @@
- (void)debugRequest:(ByteString const&)request argument:(ByteString const&)argument; - (void)debugRequest:(ByteString const&)request argument:(ByteString const&)argument;
- (void)viewSource;
@end @end

View file

@ -5,22 +5,15 @@
*/ */
#include <AK/Optional.h> #include <AK/Optional.h>
#include <AK/TemporaryChange.h>
#include <Interface/LadybirdWebViewBridge.h> #include <Interface/LadybirdWebViewBridge.h>
#include <LibGfx/ImageFormats/PNGWriter.h>
#include <LibGfx/ShareableBitmap.h>
#include <LibURL/Parser.h>
#include <LibURL/URL.h> #include <LibURL/URL.h>
#include <LibWeb/HTML/SelectedFile.h> #include <LibWeb/HTML/SelectedFile.h>
#include <LibWebView/Application.h>
#include <LibWebView/SearchEngine.h>
#include <LibWebView/SourceHighlighter.h> #include <LibWebView/SourceHighlighter.h>
#include <LibWebView/URL.h>
#import <Application/Application.h>
#import <Application/ApplicationDelegate.h> #import <Application/ApplicationDelegate.h>
#import <Interface/Event.h> #import <Interface/Event.h>
#import <Interface/LadybirdWebView.h> #import <Interface/LadybirdWebView.h>
#import <Interface/Menu.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h> #import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import <Utilities/Conversions.h> #import <Utilities/Conversions.h>
@ -28,14 +21,6 @@
# error "This project requires ARC" # error "This project requires ARC"
#endif #endif
static constexpr NSInteger CONTEXT_MENU_PLAY_PAUSE_TAG = 1;
static constexpr NSInteger CONTEXT_MENU_MUTE_UNMUTE_TAG = 2;
static constexpr NSInteger CONTEXT_MENU_CONTROLS_TAG = 3;
static constexpr NSInteger CONTEXT_MENU_LOOP_TAG = 4;
static constexpr NSInteger CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG = 5;
static constexpr NSInteger CONTEXT_MENU_COPY_LINK_TAG = 6;
static constexpr NSInteger CONTEXT_MENU_COPY_IMAGE_TAG = 7;
// Calls to [NSCursor hide] and [NSCursor unhide] must be balanced. We use this struct to ensure // Calls to [NSCursor hide] and [NSCursor unhide] must be balanced. We use this struct to ensure
// we only call [NSCursor hide] once and to ensure that we do call [NSCursor unhide]. // we only call [NSCursor hide] once and to ensure that we do call [NSCursor unhide].
// https://developer.apple.com/documentation/appkit/nscursor#1651301 // https://developer.apple.com/documentation/appkit/nscursor#1651301
@ -55,10 +40,6 @@ struct HideCursor {
{ {
OwnPtr<Ladybird::WebViewBridge> m_web_view_bridge; OwnPtr<Ladybird::WebViewBridge> m_web_view_bridge;
URL::URL m_context_menu_url;
Optional<Gfx::ShareableBitmap> m_context_menu_bitmap;
Optional<String> m_context_menu_search_text;
Optional<HideCursor> m_hidden_cursor; Optional<HideCursor> m_hidden_cursor;
// We have to send key events for modifer keys, but AppKit does not generate key down/up events when only a modifier // We have to send key events for modifer keys, but AppKit does not generate key down/up events when only a modifier
@ -71,8 +52,7 @@ struct HideCursor {
@property (nonatomic, strong) NSMenu* page_context_menu; @property (nonatomic, strong) NSMenu* page_context_menu;
@property (nonatomic, strong) NSMenu* link_context_menu; @property (nonatomic, strong) NSMenu* link_context_menu;
@property (nonatomic, strong) NSMenu* image_context_menu; @property (nonatomic, strong) NSMenu* image_context_menu;
@property (nonatomic, strong) NSMenu* audio_context_menu; @property (nonatomic, strong) NSMenu* media_context_menu;
@property (nonatomic, strong) NSMenu* video_context_menu;
@property (nonatomic, strong) NSMenu* select_dropdown; @property (nonatomic, strong) NSMenu* select_dropdown;
@property (nonatomic, strong) NSTextField* status_label; @property (nonatomic, strong) NSTextField* status_label;
@property (nonatomic, strong) NSAlert* dialog; @property (nonatomic, strong) NSAlert* dialog;
@ -86,11 +66,6 @@ struct HideCursor {
@implementation LadybirdWebView @implementation LadybirdWebView
@synthesize page_context_menu = _page_context_menu;
@synthesize link_context_menu = _link_context_menu;
@synthesize image_context_menu = _image_context_menu;
@synthesize audio_context_menu = _audio_context_menu;
@synthesize video_context_menu = _video_context_menu;
@synthesize status_label = _status_label; @synthesize status_label = _status_label;
- (instancetype)init:(id<LadybirdWebViewObserver>)observer - (instancetype)init:(id<LadybirdWebViewObserver>)observer
@ -136,6 +111,11 @@ struct HideCursor {
m_web_view_bridge = MUST(Ladybird::WebViewBridge::create(move(screen_rects), device_pixel_ratio, maximum_frames_per_second, [delegate preferredColorScheme], [delegate preferredContrast], [delegate preferredMotion])); m_web_view_bridge = MUST(Ladybird::WebViewBridge::create(move(screen_rects), device_pixel_ratio, maximum_frames_per_second, [delegate preferredColorScheme], [delegate preferredContrast], [delegate preferredMotion]));
[self setWebViewCallbacks]; [self setWebViewCallbacks];
self.page_context_menu = Ladybird::create_context_menu(self, [self view].page_context_menu());
self.link_context_menu = Ladybird::create_context_menu(self, [self view].link_context_menu());
self.image_context_menu = Ladybird::create_context_menu(self, [self view].image_context_menu());
self.media_context_menu = Ladybird::create_context_menu(self, [self view].media_context_menu());
auto* area = [[NSTrackingArea alloc] initWithRect:[self bounds] auto* area = [[NSTrackingArea alloc] initWithRect:[self bounds]
options:NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved options:NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved
owner:self owner:self
@ -162,21 +142,6 @@ struct HideCursor {
m_web_view_bridge->load_html(html); m_web_view_bridge->load_html(html);
} }
- (void)navigateBack
{
m_web_view_bridge->traverse_the_history_by_delta(-1);
}
- (void)navigateForward
{
m_web_view_bridge->traverse_the_history_by_delta(1);
}
- (void)reload
{
m_web_view_bridge->reload();
}
- (WebView::ViewImplementation&)view - (WebView::ViewImplementation&)view
{ {
return *m_web_view_bridge; return *m_web_view_bridge;
@ -281,11 +246,6 @@ struct HideCursor {
m_web_view_bridge->debug_request(request, argument); m_web_view_bridge->debug_request(request, argument);
} }
- (void)viewSource
{
m_web_view_bridge->get_source();
}
#pragma mark - Private methods #pragma mark - Private methods
static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_type) static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_type)
@ -391,15 +351,6 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
[self.observer onURLChange:url]; [self.observer onURLChange:url];
}; };
m_web_view_bridge->on_navigation_buttons_state_changed = [weak_self](auto back_enabled, auto forward_enabled) {
LadybirdWebView* self = weak_self;
if (self == nil) {
return;
}
[self.observer onBackNavigationEnabled:back_enabled
forwardNavigationEnabled:forward_enabled];
};
m_web_view_bridge->on_title_change = [weak_self](auto const& title) { m_web_view_bridge->on_title_change = [weak_self](auto const& title) {
LadybirdWebView* self = weak_self; LadybirdWebView* self = weak_self;
if (self == nil) { if (self == nil) {
@ -622,107 +573,6 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
[self.observer onCreateNewTab:url activateTab:Web::HTML::ActivateTab::No]; [self.observer onCreateNewTab:url activateTab:Web::HTML::ActivateTab::No];
}; };
m_web_view_bridge->on_context_menu_request = [weak_self](auto position) {
LadybirdWebView* self = weak_self;
if (self == nil) {
return;
}
auto* search_selected_text_menu_item = [self.page_context_menu itemWithTag:CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG];
auto const& search_engine = WebView::Application::settings().search_engine();
auto selected_text = self.observer && search_engine.has_value()
? m_web_view_bridge->selected_text_with_whitespace_collapsed()
: OptionalNone {};
TemporaryChange change_url { m_context_menu_search_text, move(selected_text) };
if (m_context_menu_search_text.has_value()) {
auto action_text = search_engine->format_search_query_for_display(*m_context_menu_search_text);
[search_selected_text_menu_item setTitle:Ladybird::string_to_ns_string(action_text)];
[search_selected_text_menu_item setHidden:NO];
} else {
[search_selected_text_menu_item setHidden:YES];
}
auto* event = Ladybird::create_context_menu_mouse_event(self, position);
[NSMenu popUpContextMenu:self.page_context_menu withEvent:event forView:self];
};
m_web_view_bridge->on_link_context_menu_request = [weak_self](auto const& url, auto position) {
LadybirdWebView* self = weak_self;
if (self == nil) {
return;
}
TemporaryChange change_url { m_context_menu_url, url };
auto* copy_link_menu_item = [self.link_context_menu itemWithTag:CONTEXT_MENU_COPY_LINK_TAG];
switch (WebView::url_type(url)) {
case WebView::URLType::Email:
[copy_link_menu_item setTitle:@"Copy Email Address"];
break;
case WebView::URLType::Telephone:
[copy_link_menu_item setTitle:@"Copy Phone Number"];
break;
case WebView::URLType::Other:
[copy_link_menu_item setTitle:@"Copy URL"];
break;
}
auto* event = Ladybird::create_context_menu_mouse_event(self, position);
[NSMenu popUpContextMenu:self.link_context_menu withEvent:event forView:self];
};
m_web_view_bridge->on_image_context_menu_request = [weak_self](auto const& url, auto position, auto const& bitmap) {
LadybirdWebView* self = weak_self;
if (self == nil) {
return;
}
TemporaryChange change_url { m_context_menu_url, url };
TemporaryChange change_bitmap { m_context_menu_bitmap, bitmap };
auto* copy_image_menu_item = [self.image_context_menu itemWithTag:CONTEXT_MENU_COPY_IMAGE_TAG];
[copy_image_menu_item setEnabled:bitmap.has_value()];
auto* event = Ladybird::create_context_menu_mouse_event(self, position);
[NSMenu popUpContextMenu:self.image_context_menu withEvent:event forView:self];
};
m_web_view_bridge->on_media_context_menu_request = [weak_self](auto position, auto const& menu) {
LadybirdWebView* self = weak_self;
if (self == nil) {
return;
}
TemporaryChange change_url { m_context_menu_url, menu.media_url };
auto* context_menu = menu.is_video ? self.video_context_menu : self.audio_context_menu;
auto* play_pause_menu_item = [context_menu itemWithTag:CONTEXT_MENU_PLAY_PAUSE_TAG];
auto* mute_unmute_menu_item = [context_menu itemWithTag:CONTEXT_MENU_MUTE_UNMUTE_TAG];
auto* controls_menu_item = [context_menu itemWithTag:CONTEXT_MENU_CONTROLS_TAG];
auto* loop_menu_item = [context_menu itemWithTag:CONTEXT_MENU_LOOP_TAG];
if (menu.is_playing) {
[play_pause_menu_item setTitle:@"Pause"];
} else {
[play_pause_menu_item setTitle:@"Play"];
}
if (menu.is_muted) {
[mute_unmute_menu_item setTitle:@"Unmute"];
} else {
[mute_unmute_menu_item setTitle:@"Mute"];
}
auto controls_state = menu.has_user_agent_controls ? NSControlStateValueOn : NSControlStateValueOff;
[controls_menu_item setState:controls_state];
auto loop_state = menu.is_looping ? NSControlStateValueOn : NSControlStateValueOff;
[loop_menu_item setState:loop_state];
auto* event = Ladybird::create_context_menu_mouse_event(self, position);
[NSMenu popUpContextMenu:context_menu withEvent:event forView:self];
};
m_web_view_bridge->on_request_alert = [weak_self](auto const& message) { m_web_view_bridge->on_request_alert = [weak_self](auto const& message) {
LadybirdWebView* self = weak_self; LadybirdWebView* self = weak_self;
if (self == nil) { if (self == nil) {
@ -1155,314 +1005,8 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
m_web_view_bridge->color_picker_update(Ladybird::ns_color_to_gfx_color([NSColorPanel sharedColorPanel].color), Web::HTML::ColorPickerUpdateState::Closed); m_web_view_bridge->color_picker_update(Ladybird::ns_color_to_gfx_color([NSColorPanel sharedColorPanel].color), Web::HTML::ColorPickerUpdateState::Closed);
} }
- (void)copy:(id)sender
{
copy_data_to_clipboard(m_web_view_bridge->selected_text(), NSPasteboardTypeString);
}
- (void)paste:(id)sender
{
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
{
m_web_view_bridge->select_all();
}
- (void)searchSelectedText:(id)sender
{
auto const& search_engine = WebView::Application::settings().search_engine();
if (!search_engine.has_value())
return;
auto url_string = search_engine->format_search_query_for_navigation(*m_context_menu_search_text);
auto url = URL::Parser::basic_parse(url_string);
VERIFY(url.has_value());
[self.observer onCreateNewTab:url.release_value() activateTab:Web::HTML::ActivateTab::Yes];
}
- (void)takeVisibleScreenshot:(id)sender
{
[self takeScreenshot:WebView::ViewImplementation::ScreenshotType::Visible];
}
- (void)takeFullScreenshot:(id)sender
{
[self takeScreenshot:WebView::ViewImplementation::ScreenshotType::Full];
}
- (void)takeScreenshot:(WebView::ViewImplementation::ScreenshotType)type
{
m_web_view_bridge->take_screenshot(type)
->when_resolved([](auto const& path) {
WebView::Application::the().display_download_confirmation_dialog("Screenshot"sv, path);
})
.when_rejected([](auto const& error) {
if (error.is_errno() && error.code() == ECANCELED)
return;
auto error_message = MUST(String::formatted("{}", error));
WebView::Application::the().display_error_dialog(error_message);
});
}
- (void)openLink:(id)sender
{
m_web_view_bridge->on_link_click(m_context_menu_url, {}, 0);
}
- (void)openLinkInNewTab:(id)sender
{
m_web_view_bridge->on_link_middle_click(m_context_menu_url, {}, 0);
}
- (void)copyLink:(id)sender
{
auto link = WebView::url_text_to_copy(m_context_menu_url);
copy_data_to_clipboard(link, NSPasteboardTypeString);
}
- (void)copyImage:(id)sender
{
if (!m_context_menu_bitmap.has_value()) {
return;
}
auto* bitmap = m_context_menu_bitmap.value().bitmap();
if (bitmap == nullptr) {
return;
}
auto png = Gfx::PNGWriter::encode(*bitmap);
if (png.is_error()) {
return;
}
auto* data = [NSData dataWithBytes:png.value().data() length:png.value().size()];
auto* pasteBoard = [NSPasteboard generalPasteboard];
[pasteBoard clearContents];
[pasteBoard setData:data forType:NSPasteboardTypePNG];
}
- (void)toggleMediaPlayState:(id)sender
{
m_web_view_bridge->toggle_media_play_state();
}
- (void)toggleMediaMuteState:(id)sender
{
m_web_view_bridge->toggle_media_mute_state();
}
- (void)toggleMediaControlsState:(id)sender
{
m_web_view_bridge->toggle_media_controls_state();
}
- (void)toggleMediaLoopState:(id)sender
{
m_web_view_bridge->toggle_media_loop_state();
}
#pragma mark - Properties #pragma mark - Properties
- (NSMenu*)page_context_menu
{
if (!_page_context_menu) {
_page_context_menu = [[NSMenu alloc] initWithTitle:@"Page Context Menu"];
[_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Go Back"
action:@selector(navigateBack:)
keyEquivalent:@""]];
[_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Go Forward"
action:@selector(navigateForward:)
keyEquivalent:@""]];
[_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Reload"
action:@selector(reload:)
keyEquivalent:@""]];
[_page_context_menu addItem:[NSMenuItem separatorItem]];
[_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy"
action:@selector(copy:)
keyEquivalent:@""]];
[_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Paste"
action:@selector(paste:)
keyEquivalent:@""]];
[_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Select All"
action:@selector(selectAll:)
keyEquivalent:@""]];
[_page_context_menu addItem:[NSMenuItem separatorItem]];
auto* search_selected_text_menu_item = [[NSMenuItem alloc] initWithTitle:@"Search for <query>"
action:@selector(searchSelectedText:)
keyEquivalent:@""];
[search_selected_text_menu_item setTag:CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG];
[_page_context_menu addItem:search_selected_text_menu_item];
[_page_context_menu addItem:[NSMenuItem separatorItem]];
[_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Take Visible Screenshot"
action:@selector(takeVisibleScreenshot:)
keyEquivalent:@""]];
[_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Take Full Screenshot"
action:@selector(takeFullScreenshot:)
keyEquivalent:@""]];
[_page_context_menu addItem:[NSMenuItem separatorItem]];
[_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"View Source"
action:@selector(viewSource:)
keyEquivalent:@""]];
}
return _page_context_menu;
}
- (NSMenu*)link_context_menu
{
if (!_link_context_menu) {
_link_context_menu = [[NSMenu alloc] initWithTitle:@"Link Context Menu"];
[_link_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open"
action:@selector(openLink:)
keyEquivalent:@""]];
[_link_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open in New Tab"
action:@selector(openLinkInNewTab:)
keyEquivalent:@""]];
[_link_context_menu addItem:[NSMenuItem separatorItem]];
auto* copy_link_menu_item = [[NSMenuItem alloc] initWithTitle:@"Copy URL"
action:@selector(copyLink:)
keyEquivalent:@""];
[copy_link_menu_item setTag:CONTEXT_MENU_COPY_LINK_TAG];
[_link_context_menu addItem:copy_link_menu_item];
}
return _link_context_menu;
}
- (NSMenu*)image_context_menu
{
if (!_image_context_menu) {
_image_context_menu = [[NSMenu alloc] initWithTitle:@"Image Context Menu"];
[_image_context_menu setAutoenablesItems:NO];
[_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Image"
action:@selector(openLink:)
keyEquivalent:@""]];
[_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Image in New Tab"
action:@selector(openLinkInNewTab:)
keyEquivalent:@""]];
[_image_context_menu addItem:[NSMenuItem separatorItem]];
auto* copy_image_menu_item = [[NSMenuItem alloc] initWithTitle:@"Copy Image"
action:@selector(copyImage:)
keyEquivalent:@""];
[copy_image_menu_item setTag:CONTEXT_MENU_COPY_IMAGE_TAG];
[_image_context_menu addItem:copy_image_menu_item];
[_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Image URL"
action:@selector(copyLink:)
keyEquivalent:@""]];
}
return _image_context_menu;
}
- (NSMenu*)audio_context_menu
{
if (!_audio_context_menu) {
_audio_context_menu = [[NSMenu alloc] initWithTitle:@"Audio Context Menu"];
auto* play_pause_menu_item = [[NSMenuItem alloc] initWithTitle:@"Play"
action:@selector(toggleMediaPlayState:)
keyEquivalent:@""];
[play_pause_menu_item setTag:CONTEXT_MENU_PLAY_PAUSE_TAG];
auto* mute_unmute_menu_item = [[NSMenuItem alloc] initWithTitle:@"Mute"
action:@selector(toggleMediaMuteState:)
keyEquivalent:@""];
[mute_unmute_menu_item setTag:CONTEXT_MENU_MUTE_UNMUTE_TAG];
auto* controls_menu_item = [[NSMenuItem alloc] initWithTitle:@"Controls"
action:@selector(toggleMediaControlsState:)
keyEquivalent:@""];
[controls_menu_item setTag:CONTEXT_MENU_CONTROLS_TAG];
auto* loop_menu_item = [[NSMenuItem alloc] initWithTitle:@"Loop"
action:@selector(toggleMediaLoopState:)
keyEquivalent:@""];
[loop_menu_item setTag:CONTEXT_MENU_LOOP_TAG];
[_audio_context_menu addItem:play_pause_menu_item];
[_audio_context_menu addItem:mute_unmute_menu_item];
[_audio_context_menu addItem:controls_menu_item];
[_audio_context_menu addItem:loop_menu_item];
[_audio_context_menu addItem:[NSMenuItem separatorItem]];
[_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Audio"
action:@selector(openLink:)
keyEquivalent:@""]];
[_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Audio in New Tab"
action:@selector(openLinkInNewTab:)
keyEquivalent:@""]];
[_audio_context_menu addItem:[NSMenuItem separatorItem]];
[_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Audio URL"
action:@selector(copyLink:)
keyEquivalent:@""]];
}
return _audio_context_menu;
}
- (NSMenu*)video_context_menu
{
if (!_video_context_menu) {
_video_context_menu = [[NSMenu alloc] initWithTitle:@"Video Context Menu"];
auto* play_pause_menu_item = [[NSMenuItem alloc] initWithTitle:@"Play"
action:@selector(toggleMediaPlayState:)
keyEquivalent:@""];
[play_pause_menu_item setTag:CONTEXT_MENU_PLAY_PAUSE_TAG];
auto* mute_unmute_menu_item = [[NSMenuItem alloc] initWithTitle:@"Mute"
action:@selector(toggleMediaMuteState:)
keyEquivalent:@""];
[mute_unmute_menu_item setTag:CONTEXT_MENU_MUTE_UNMUTE_TAG];
auto* controls_menu_item = [[NSMenuItem alloc] initWithTitle:@"Controls"
action:@selector(toggleMediaControlsState:)
keyEquivalent:@""];
[controls_menu_item setTag:CONTEXT_MENU_CONTROLS_TAG];
auto* loop_menu_item = [[NSMenuItem alloc] initWithTitle:@"Loop"
action:@selector(toggleMediaLoopState:)
keyEquivalent:@""];
[loop_menu_item setTag:CONTEXT_MENU_LOOP_TAG];
[_video_context_menu addItem:play_pause_menu_item];
[_video_context_menu addItem:mute_unmute_menu_item];
[_video_context_menu addItem:controls_menu_item];
[_video_context_menu addItem:loop_menu_item];
[_video_context_menu addItem:[NSMenuItem separatorItem]];
[_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Video"
action:@selector(openLink:)
keyEquivalent:@""]];
[_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Video in New Tab"
action:@selector(openLinkInNewTab:)
keyEquivalent:@""]];
[_video_context_menu addItem:[NSMenuItem separatorItem]];
[_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Video URL"
action:@selector(copyLink:)
keyEquivalent:@""]];
}
return _video_context_menu;
}
- (NSTextField*)status_label - (NSTextField*)status_label
{ {
if (!_status_label) { if (!_status_label) {

View file

@ -38,6 +38,22 @@
if (!action) if (!action)
return; return;
if (![[[NSApp keyWindow] firstResponder] isKindOfClass:[LadybirdWebView class]]) {
switch (action->id()) {
case WebView::ActionID::CopySelection:
[NSApp sendAction:@selector(copy:) to:nil from:sender];
return;
case WebView::ActionID::Paste:
[NSApp sendAction:@selector(paste:) to:nil from:sender];
return;
case WebView::ActionID::SelectAll:
[NSApp sendAction:@selector(selectAll:) to:nil from:sender];
return;
default:
break;
}
}
if (action->is_checkable()) if (action->is_checkable())
action->set_checked(!action->checked()); action->set_checked(!action->checked());
action->activate(); action->activate();
@ -94,6 +110,35 @@ private:
static void initialize_native_control(WebView::Action& action, id control) static void initialize_native_control(WebView::Action& action, id control)
{ {
switch (action.id()) {
case WebView::ActionID::NavigateBack:
[control setKeyEquivalent:@"["];
break;
case WebView::ActionID::NavigateForward:
[control setKeyEquivalent:@"]"];
break;
case WebView::ActionID::Reload:
[control setKeyEquivalent:@"r"];
break;
case WebView::ActionID::CopySelection:
[control setKeyEquivalent:@"c"];
break;
case WebView::ActionID::Paste:
[control setKeyEquivalent:@"v"];
break;
case WebView::ActionID::SelectAll:
[control setKeyEquivalent:@"a"];
break;
case WebView::ActionID::ViewSource:
[control setKeyEquivalent:@"u"];
break;
default:
break;
}
action.add_observer(ActionObserver::create(action, control)); action.add_observer(ActionObserver::create(action, control));
} }

View file

@ -6,7 +6,6 @@
#include <AK/String.h> #include <AK/String.h>
#include <LibCore/Resource.h> #include <LibCore/Resource.h>
#include <LibGfx/ShareableBitmap.h>
#include <LibURL/URL.h> #include <LibURL/URL.h>
#include <LibWebView/ViewImplementation.h> #include <LibWebView/ViewImplementation.h>
@ -285,13 +284,6 @@ static constexpr CGFloat const WINDOW_HEIGHT = 800;
[[self tabController] onURLChange:url]; [[self tabController] onURLChange:url];
} }
- (void)onBackNavigationEnabled:(BOOL)back_enabled
forwardNavigationEnabled:(BOOL)forward_enabled
{
[[self tabController] onBackNavigationEnabled:back_enabled
forwardNavigationEnabled:forward_enabled];
}
- (void)onTitleChange:(Utf16String const&)title - (void)onTitleChange:(Utf16String const&)title
{ {
self.title = Ladybird::utf16_string_to_ns_string(title); self.title = Ladybird::utf16_string_to_ns_string(title);

View file

@ -33,14 +33,9 @@ struct TabSettings {
- (void)onLoadStart:(URL::URL const&)url isRedirect:(BOOL)isRedirect; - (void)onLoadStart:(URL::URL const&)url isRedirect:(BOOL)isRedirect;
- (void)onURLChange:(URL::URL const&)url; - (void)onURLChange:(URL::URL const&)url;
- (void)onBackNavigationEnabled:(BOOL)back_enabled
forwardNavigationEnabled:(BOOL)forward_enabled;
- (void)onCreateNewTab; - (void)onCreateNewTab;
- (void)navigateBack:(id)sender;
- (void)navigateForward:(id)sender;
- (void)reload:(id)sender;
- (void)clearHistory; - (void)clearHistory;
- (void)setPopupBlocking:(BOOL)block_popups; - (void)setPopupBlocking:(BOOL)block_popups;

View file

@ -7,15 +7,14 @@
#include <LibWeb/Loader/UserAgent.h> #include <LibWeb/Loader/UserAgent.h>
#include <LibWebView/Application.h> #include <LibWebView/Application.h>
#include <LibWebView/Autocomplete.h> #include <LibWebView/Autocomplete.h>
#include <LibWebView/SearchEngine.h>
#include <LibWebView/URL.h> #include <LibWebView/URL.h>
#include <LibWebView/UserAgent.h> #include <LibWebView/UserAgent.h>
#include <LibWebView/ViewImplementation.h> #include <LibWebView/ViewImplementation.h>
#import <Application/ApplicationDelegate.h> #import <Application/ApplicationDelegate.h>
#import <Interface/Autocomplete.h> #import <Interface/Autocomplete.h>
#import <Interface/Event.h>
#import <Interface/LadybirdWebView.h> #import <Interface/LadybirdWebView.h>
#import <Interface/Menu.h>
#import <Interface/Tab.h> #import <Interface/Tab.h>
#import <Interface/TabController.h> #import <Interface/TabController.h>
#import <Utilities/Conversions.h> #import <Utilities/Conversions.h>
@ -58,9 +57,6 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
TabSettings m_settings; TabSettings m_settings;
OwnPtr<WebView::Autocomplete> m_autocomplete; OwnPtr<WebView::Autocomplete> m_autocomplete;
bool m_can_navigate_back;
bool m_can_navigate_forward;
} }
@property (nonatomic, strong) Tab* parent; @property (nonatomic, strong) Tab* parent;
@ -128,9 +124,6 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
[self.autocomplete showWithSuggestions:move(suggestions)]; [self.autocomplete showWithSuggestions:move(suggestions)];
}; };
m_can_navigate_back = false;
m_can_navigate_forward = false;
} }
return self; return self;
@ -170,14 +163,6 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
[self.window makeFirstResponder:[self tab].web_view]; [self.window makeFirstResponder:[self tab].web_view];
} }
- (void)onBackNavigationEnabled:(BOOL)back_enabled
forwardNavigationEnabled:(BOOL)forward_enabled
{
m_can_navigate_back = back_enabled;
m_can_navigate_forward = forward_enabled;
[self updateNavigationButtonStates];
}
- (void)onCreateNewTab - (void)onCreateNewTab
{ {
[self setPopupBlocking:m_settings.block_popups]; [self setPopupBlocking:m_settings.block_popups];
@ -202,21 +187,6 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
[self updateZoomButton]; [self updateZoomButton];
} }
- (void)navigateBack:(id)sender
{
[[[self tab] web_view] navigateBack];
}
- (void)navigateForward:(id)sender
{
[[[self tab] web_view] navigateForward];
}
- (void)reload:(id)sender
{
[[[self tab] web_view] reload];
}
- (void)clearHistory - (void)clearHistory
{ {
// FIXME: Reimplement clearing history using WebContent's history. // FIXME: Reimplement clearing history using WebContent's history.
@ -227,11 +197,6 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
[[[self tab] web_view] debugRequest:request argument:argument]; [[[self tab] web_view] debugRequest:request argument:argument];
} }
- (void)viewSource:(id)sender
{
[[[self tab] web_view] viewSource];
}
- (void)focusLocationToolbarItem - (void)focusLocationToolbarItem
{ {
[self.window makeFirstResponder:self.location_toolbar_item.view]; [self.window makeFirstResponder:self.location_toolbar_item.view];
@ -308,15 +273,6 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
return YES; return YES;
} }
- (void)updateNavigationButtonStates
{
auto* navigate_back_button = (NSButton*)[[self navigate_back_toolbar_item] view];
[navigate_back_button setEnabled:m_can_navigate_back];
auto* navigate_forward_button = (NSButton*)[[self navigate_forward_toolbar_item] view];
[navigate_forward_button setEnabled:m_can_navigate_forward];
}
- (void)showTabOverview:(id)sender - (void)showTabOverview:(id)sender
{ {
self.tab.titlebarAppearsTransparent = NO; self.tab.titlebarAppearsTransparent = NO;
@ -474,10 +430,7 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
- (NSToolbarItem*)navigate_back_toolbar_item - (NSToolbarItem*)navigate_back_toolbar_item
{ {
if (!_navigate_back_toolbar_item) { if (!_navigate_back_toolbar_item) {
auto* button = [self create_button:NSImageNameGoBackTemplate auto* button = Ladybird::create_application_button([[[self tab] web_view] view].navigate_back_action(), NSImageNameGoBackTemplate);
with_action:@selector(navigateBack:)
with_tooltip:@"Navigate back"];
[button setEnabled:NO];
_navigate_back_toolbar_item = [[NSToolbarItem alloc] initWithItemIdentifier:TOOLBAR_NAVIGATE_BACK_IDENTIFIER]; _navigate_back_toolbar_item = [[NSToolbarItem alloc] initWithItemIdentifier:TOOLBAR_NAVIGATE_BACK_IDENTIFIER];
[_navigate_back_toolbar_item setView:button]; [_navigate_back_toolbar_item setView:button];
@ -489,10 +442,7 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
- (NSToolbarItem*)navigate_forward_toolbar_item - (NSToolbarItem*)navigate_forward_toolbar_item
{ {
if (!_navigate_forward_toolbar_item) { if (!_navigate_forward_toolbar_item) {
auto* button = [self create_button:NSImageNameGoForwardTemplate auto* button = Ladybird::create_application_button([[[self tab] web_view] view].navigate_forward_action(), NSImageNameGoForwardTemplate);
with_action:@selector(navigateForward:)
with_tooltip:@"Navigate forward"];
[button setEnabled:NO];
_navigate_forward_toolbar_item = [[NSToolbarItem alloc] initWithItemIdentifier:TOOLBAR_NAVIGATE_FORWARD_IDENTIFIER]; _navigate_forward_toolbar_item = [[NSToolbarItem alloc] initWithItemIdentifier:TOOLBAR_NAVIGATE_FORWARD_IDENTIFIER];
[_navigate_forward_toolbar_item setView:button]; [_navigate_forward_toolbar_item setView:button];
@ -504,10 +454,7 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
- (NSToolbarItem*)reload_toolbar_item - (NSToolbarItem*)reload_toolbar_item
{ {
if (!_reload_toolbar_item) { if (!_reload_toolbar_item) {
auto* button = [self create_button:NSImageNameRefreshTemplate auto* button = Ladybird::create_application_button(WebView::Application::the().reload_action(), NSImageNameRefreshTemplate);
with_action:@selector(reload:)
with_tooltip:@"Reload page"];
[button setEnabled:YES];
_reload_toolbar_item = [[NSToolbarItem alloc] initWithItemIdentifier:TOOLBAR_RELOAD_IDENTIFIER]; _reload_toolbar_item = [[NSToolbarItem alloc] initWithItemIdentifier:TOOLBAR_RELOAD_IDENTIFIER];
[_reload_toolbar_item setView:button]; [_reload_toolbar_item setView:button];

View file

@ -19,6 +19,7 @@
#include <UI/Qt/Application.h> #include <UI/Qt/Application.h>
#include <UI/Qt/BrowserWindow.h> #include <UI/Qt/BrowserWindow.h>
#include <UI/Qt/Icon.h> #include <UI/Qt/Icon.h>
#include <UI/Qt/Menu.h>
#include <UI/Qt/Settings.h> #include <UI/Qt/Settings.h>
#include <UI/Qt/StringUtils.h> #include <UI/Qt/StringUtils.h>
#include <UI/Qt/TabBar.h> #include <UI/Qt/TabBar.h>
@ -26,13 +27,14 @@
#include <QAction> #include <QAction>
#include <QActionGroup> #include <QActionGroup>
#include <QClipboard>
#include <QGuiApplication> #include <QGuiApplication>
#include <QInputDialog> #include <QInputDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QPlainTextEdit> #include <QMouseEvent>
#include <QScreen>
#include <QShortcut> #include <QShortcut>
#include <QStatusBar> #include <QStatusBar>
#include <QWheelEvent>
#include <QWindow> #include <QWindow>
namespace Ladybird { namespace Ladybird {
@ -143,24 +145,9 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
auto* edit_menu = m_hamburger_menu->addMenu("&Edit"); auto* edit_menu = m_hamburger_menu->addMenu("&Edit");
menuBar()->addMenu(edit_menu); menuBar()->addMenu(edit_menu);
m_copy_selection_action = new QAction("&Copy", this); edit_menu->addAction(create_application_action(*this, Application::the().copy_selection_action()));
m_copy_selection_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv)); edit_menu->addAction(create_application_action(*this, Application::the().paste_action()));
m_copy_selection_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Copy)); edit_menu->addAction(create_application_action(*this, Application::the().select_all_action()));
edit_menu->addAction(m_copy_selection_action);
QObject::connect(m_copy_selection_action, &QAction::triggered, this, &BrowserWindow::copy_selected_text);
m_paste_action = new QAction("&Paste", this);
m_paste_action->setIcon(load_icon_from_uri("resource://icons/16x16/paste.png"sv));
m_paste_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Paste));
edit_menu->addAction(m_paste_action);
QObject::connect(m_paste_action, &QAction::triggered, this, &BrowserWindow::paste);
m_select_all_action = new QAction("Select &All", this);
m_select_all_action->setIcon(load_icon_from_uri("resource://icons/16x16/select-all.png"sv));
m_select_all_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::SelectAll));
edit_menu->addAction(m_select_all_action);
QObject::connect(m_select_all_action, &QAction::triggered, this, &BrowserWindow::select_all);
edit_menu->addSeparator(); edit_menu->addSeparator();
m_find_in_page_action = new QAction("&Find in Page...", this); m_find_in_page_action = new QAction("&Find in Page...", this);
@ -330,15 +317,7 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
auto* inspect_menu = m_hamburger_menu->addMenu("&Inspect"); auto* inspect_menu = m_hamburger_menu->addMenu("&Inspect");
menuBar()->addMenu(inspect_menu); menuBar()->addMenu(inspect_menu);
m_view_source_action = new QAction("View &Source", this); edit_menu->addAction(create_application_action(*this, Application::the().view_source_action()));
m_view_source_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-html.png"sv));
m_view_source_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_U));
inspect_menu->addAction(m_view_source_action);
QObject::connect(m_view_source_action, &QAction::triggered, this, [this] {
if (m_current_tab) {
m_current_tab->view().get_source();
}
});
m_enable_devtools_action = new QAction("Enable &DevTools", this); 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->setIcon(load_icon_from_uri("resource://icons/browser/dom-tree.png"sv));
@ -641,30 +620,6 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
QObject::connect(m_tabs_container, &QTabWidget::tabCloseRequested, this, &BrowserWindow::close_tab); QObject::connect(m_tabs_container, &QTabWidget::tabCloseRequested, this, &BrowserWindow::close_tab);
QObject::connect(close_current_tab_action, &QAction::triggered, this, &BrowserWindow::close_current_tab); QObject::connect(close_current_tab_action, &QAction::triggered, this, &BrowserWindow::close_current_tab);
m_go_back_action = new QAction("Go Back", this);
connect(m_go_back_action, &QAction::triggered, this, [this] {
if (m_current_tab)
m_current_tab->back();
});
m_go_forward_action = new QAction("Go Forward", this);
connect(m_go_forward_action, &QAction::triggered, this, [this] {
if (m_current_tab)
m_current_tab->forward();
});
m_reload_action = new QAction("&Reload", this);
connect(m_reload_action, &QAction::triggered, this, [this] {
if (m_current_tab)
m_current_tab->reload();
});
m_go_back_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Back));
m_go_forward_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Forward));
m_reload_action->setShortcuts({ QKeySequence(Qt::CTRL | Qt::Key_R), QKeySequence(Qt::Key_F5) });
m_go_back_action->setEnabled(false);
m_go_forward_action->setEnabled(false);
m_reload_action->setEnabled(true);
for (int i = 0; i <= 7; ++i) { for (int i = 0; i <= 7; ++i) {
new QShortcut(QKeySequence(Qt::CTRL | static_cast<Qt::Key>(Qt::Key_1 + i)), this, [this, i] { new QShortcut(QKeySequence(Qt::CTRL | static_cast<Qt::Key>(Qt::Key_1 + i)), this, [this, i] {
if (m_tabs_container->count() <= 1) if (m_tabs_container->count() <= 1)
@ -727,10 +682,8 @@ void BrowserWindow::devtools_enabled()
void BrowserWindow::set_current_tab(Tab* tab) void BrowserWindow::set_current_tab(Tab* tab)
{ {
m_current_tab = tab; m_current_tab = tab;
if (tab) { if (tab)
update_displayed_zoom_level(); update_displayed_zoom_level();
tab->update_navigation_buttons_state();
}
} }
void BrowserWindow::debug_request(ByteString const& request, ByteString const& argument) void BrowserWindow::debug_request(ByteString const& request, ByteString const& argument)
@ -801,7 +754,6 @@ void BrowserWindow::initialize_tab(Tab* tab)
QObject::connect(tab, &Tab::title_changed, this, &BrowserWindow::tab_title_changed); QObject::connect(tab, &Tab::title_changed, this, &BrowserWindow::tab_title_changed);
QObject::connect(tab, &Tab::favicon_changed, this, &BrowserWindow::tab_favicon_changed); QObject::connect(tab, &Tab::favicon_changed, this, &BrowserWindow::tab_favicon_changed);
QObject::connect(tab, &Tab::audio_play_state_changed, this, &BrowserWindow::tab_audio_play_state_changed); QObject::connect(tab, &Tab::audio_play_state_changed, this, &BrowserWindow::tab_audio_play_state_changed);
QObject::connect(tab, &Tab::navigation_buttons_state_changed, this, &BrowserWindow::tab_navigation_buttons_state_changed);
QObject::connect(&tab->view(), &WebContentView::urls_dropped, this, [this](auto& urls) { QObject::connect(&tab->view(), &WebContentView::urls_dropped, this, [this](auto& urls) {
VERIFY(urls.size()); VERIFY(urls.size());
@ -976,12 +928,6 @@ void BrowserWindow::tab_audio_play_state_changed(int index, Web::HTML::AudioPlay
} }
} }
void BrowserWindow::tab_navigation_buttons_state_changed(int index)
{
auto* tab = as<Tab>(m_tabs_container->widget(index));
tab->update_navigation_buttons_state();
}
QIcon BrowserWindow::icon_for_page_mute_state(Tab& tab) const QIcon BrowserWindow::icon_for_page_mute_state(Tab& tab) const
{ {
switch (tab.view().page_mute_state()) { switch (tab.view().page_mute_state()) {
@ -1118,14 +1064,6 @@ void BrowserWindow::update_zoom_menu()
m_zoom_menu->setTitle(qstring_from_ak_string(zoom_level_text)); m_zoom_menu->setTitle(qstring_from_ak_string(zoom_level_text));
} }
void BrowserWindow::select_all()
{
if (!m_current_tab)
return;
m_current_tab->view().select_all();
}
void BrowserWindow::show_find_in_page() void BrowserWindow::show_find_in_page()
{ {
if (!m_current_tab) if (!m_current_tab)
@ -1134,15 +1072,6 @@ void BrowserWindow::show_find_in_page()
m_current_tab->show_find_in_page(); m_current_tab->show_find_in_page();
} }
void BrowserWindow::paste()
{
if (!m_current_tab)
return;
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() void BrowserWindow::update_displayed_zoom_level()
{ {
VERIFY(m_current_tab); VERIFY(m_current_tab);
@ -1170,17 +1099,6 @@ void BrowserWindow::set_preferred_color_scheme(Web::CSS::PreferredColorScheme co
}); });
} }
void BrowserWindow::copy_selected_text()
{
if (!m_current_tab)
return;
auto text = m_current_tab->view().selected_text();
auto* clipboard = QGuiApplication::clipboard();
clipboard->setText(qstring_from_ak_string(text));
}
bool BrowserWindow::event(QEvent* event) bool BrowserWindow::event(QEvent* event)
{ {
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)

View file

@ -7,15 +7,12 @@
#pragma once #pragma once
#include <LibCore/Forward.h>
#include <LibWeb/HTML/ActivateTab.h> #include <LibWeb/HTML/ActivateTab.h>
#include <LibWeb/HTML/AudioPlayState.h> #include <LibWeb/HTML/AudioPlayState.h>
#include <LibWebView/Forward.h> #include <LibWebView/Forward.h>
#include <UI/Qt/FindInPageWidget.h>
#include <UI/Qt/Tab.h> #include <UI/Qt/Tab.h>
#include <QIcon> #include <QIcon>
#include <QLineEdit>
#include <QMainWindow> #include <QMainWindow>
#include <QMenuBar> #include <QMenuBar>
#include <QTabBar> #include <QTabBar>
@ -42,65 +39,16 @@ public:
int tab_count() { return m_tabs_container->count(); } int tab_count() { return m_tabs_container->count(); }
int tab_index(Tab*); int tab_index(Tab*);
Tab& create_new_tab(Web::HTML::ActivateTab activate_tab); Tab& create_new_tab(Web::HTML::ActivateTab activate_tab);
QMenu& hamburger_menu()
{
return *m_hamburger_menu;
}
QAction& go_back_action()
{
return *m_go_back_action;
}
QAction& go_forward_action()
{
return *m_go_forward_action;
}
QAction& reload_action()
{
return *m_reload_action;
}
QAction& new_tab_action()
{
return *m_new_tab_action;
}
QAction& new_window_action()
{
return *m_new_window_action;
}
QAction& copy_selection_action()
{
return *m_copy_selection_action;
}
QAction& select_all_action()
{
return *m_select_all_action;
}
QAction& find_action()
{
return *m_find_in_page_action;
}
QAction& paste_action()
{
return *m_paste_action;
}
QAction& view_source_action()
{
return *m_view_source_action;
}
Tab* current_tab() const { return m_current_tab; } Tab* current_tab() const { return m_current_tab; }
QMenu& hamburger_menu() const { return *m_hamburger_menu; }
QAction& new_tab_action() const { return *m_new_tab_action; }
QAction& new_window_action() const { return *m_new_window_action; }
QAction& find_action() const { return *m_find_in_page_action; }
double refresh_rate() const { return m_refresh_rate; } double refresh_rate() const { return m_refresh_rate; }
public slots: public slots:
@ -109,7 +57,6 @@ public slots:
void tab_title_changed(int index, QString const&); void tab_title_changed(int index, QString const&);
void tab_favicon_changed(int index, QIcon const& icon); void tab_favicon_changed(int index, QIcon const& icon);
void tab_audio_play_state_changed(int index, Web::HTML::AudioPlayState); void tab_audio_play_state_changed(int index, Web::HTML::AudioPlayState);
void tab_navigation_buttons_state_changed(int index);
Tab& new_tab_from_url(URL::URL const&, Web::HTML::ActivateTab); Tab& new_tab_from_url(URL::URL const&, Web::HTML::ActivateTab);
Tab& new_tab_from_content(StringView html, Web::HTML::ActivateTab); Tab& new_tab_from_content(StringView html, Web::HTML::ActivateTab);
Tab& new_child_tab(Web::HTML::ActivateTab, Tab& parent, Optional<u64> page_index); Tab& new_child_tab(Web::HTML::ActivateTab, Tab& parent, Optional<u64> page_index);
@ -132,10 +79,7 @@ public slots:
void reset_zoom(); void reset_zoom();
void update_zoom_menu(); void update_zoom_menu();
void update_displayed_zoom_level(); void update_displayed_zoom_level();
void select_all();
void show_find_in_page(); void show_find_in_page();
void paste();
void copy_selected_text();
protected: protected:
bool eventFilter(QObject* obj, QEvent* event) override; bool eventFilter(QObject* obj, QEvent* event) override;
@ -194,16 +138,9 @@ private:
QMenu* m_hamburger_menu { nullptr }; QMenu* m_hamburger_menu { nullptr };
QAction* m_go_back_action { nullptr };
QAction* m_go_forward_action { nullptr };
QAction* m_reload_action { nullptr };
QAction* m_new_tab_action { nullptr }; QAction* m_new_tab_action { nullptr };
QAction* m_new_window_action { nullptr }; QAction* m_new_window_action { nullptr };
QAction* m_copy_selection_action { nullptr };
QAction* m_paste_action { nullptr };
QAction* m_select_all_action { nullptr };
QAction* m_find_in_page_action { nullptr }; QAction* m_find_in_page_action { nullptr };
QAction* m_view_source_action { nullptr };
QAction* m_enable_devtools_action { nullptr }; QAction* m_enable_devtools_action { nullptr };
QAction* m_show_line_box_borders_action { nullptr }; QAction* m_show_line_box_borders_action { nullptr };
QAction* m_enable_scripting_action { nullptr }; QAction* m_enable_scripting_action { nullptr };

View file

@ -73,8 +73,86 @@ private:
QPointer<QAction> m_action; QPointer<QAction> m_action;
}; };
static void initialize_native_control(WebView::Action& action, QAction& qaction) static void initialize_native_control(WebView::Action& action, QAction& qaction, QPalette const& palette)
{ {
switch (action.id()) {
case WebView::ActionID::NavigateBack:
qaction.setIcon(create_tvg_icon_with_theme_colors("back", palette));
qaction.setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Back));
break;
case WebView::ActionID::NavigateForward:
qaction.setIcon(create_tvg_icon_with_theme_colors("forward", palette));
qaction.setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Forward));
break;
case WebView::ActionID::Reload:
qaction.setIcon(create_tvg_icon_with_theme_colors("reload", palette));
qaction.setShortcuts({ QKeySequence(Qt::CTRL | Qt::Key_R), QKeySequence(Qt::Key_F5) });
break;
case WebView::ActionID::CopySelection:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv));
qaction.setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Copy));
break;
case WebView::ActionID::Paste:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/paste.png"sv));
qaction.setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Paste));
break;
case WebView::ActionID::SelectAll:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/select-all.png"sv));
qaction.setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::SelectAll));
break;
case WebView::ActionID::SearchSelectedText:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/find.png"sv));
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));
break;
case WebView::ActionID::TakeVisibleScreenshot:
case WebView::ActionID::TakeFullScreenshot:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/filetype-image.png"sv));
break;
case WebView::ActionID::OpenInNewTab:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/new-tab.png"sv));
break;
case WebView::ActionID::CopyURL:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv));
break;
case WebView::ActionID::OpenImage:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/filetype-image.png"sv));
break;
case WebView::ActionID::CopyImage:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv));
break;
case WebView::ActionID::OpenAudio:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/filetype-sound.png"sv));
break;
case WebView::ActionID::OpenVideo:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/filetype-video.png"sv));
break;
case WebView::ActionID::PlayMedia:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/play.png"sv));
break;
case WebView::ActionID::PauseMedia:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/pause.png"sv));
break;
case WebView::ActionID::MuteMedia:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/audio-volume-muted.png"sv));
break;
case WebView::ActionID::UnmuteMedia:
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/audio-volume-high.png"sv));
break;
default:
break;
}
if (action.is_checkable()) if (action.is_checkable())
qaction.setCheckable(true); qaction.setCheckable(true);
@ -123,7 +201,7 @@ QMenu* create_context_menu(QWidget& parent, WebContentView& view, WebView::Menu&
QAction* create_application_action(QWidget& parent, WebView::Action& action) QAction* create_application_action(QWidget& parent, WebView::Action& action)
{ {
auto* qaction = new QAction(&parent); auto* qaction = new QAction(&parent);
initialize_native_control(action, *qaction); initialize_native_control(action, *qaction, parent.palette());
return qaction; return qaction;
} }

View file

@ -6,24 +6,18 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/TemporaryChange.h> #include <LibURL/URL.h>
#include <LibGfx/ImageFormats/BMPWriter.h>
#include <LibURL/Parser.h>
#include <LibWeb/HTML/SelectedFile.h> #include <LibWeb/HTML/SelectedFile.h>
#include <LibWebView/Application.h> #include <LibWebView/Application.h>
#include <LibWebView/SearchEngine.h>
#include <LibWebView/SourceHighlighter.h> #include <LibWebView/SourceHighlighter.h>
#include <LibWebView/URL.h>
#include <UI/Qt/BrowserWindow.h> #include <UI/Qt/BrowserWindow.h>
#include <UI/Qt/Icon.h> #include <UI/Qt/Icon.h>
#include <UI/Qt/Menu.h>
#include <UI/Qt/Settings.h> #include <UI/Qt/Settings.h>
#include <UI/Qt/StringUtils.h> #include <UI/Qt/StringUtils.h>
#include <QClipboard> #include <QClipboard>
#include <QColorDialog> #include <QColorDialog>
#include <QCoreApplication>
#include <QCursor>
#include <QDesktopServices>
#include <QFileDialog> #include <QFileDialog>
#include <QFont> #include <QFont>
#include <QFontMetrics> #include <QFontMetrics>
@ -35,9 +29,6 @@
#include <QMimeData> #include <QMimeData>
#include <QMimeDatabase> #include <QMimeDatabase>
#include <QMimeType> #include <QMimeType>
#include <QPainter>
#include <QPoint>
#include <QPushButton>
#include <QResizeEvent> #include <QResizeEvent>
namespace Ladybird { namespace Ladybird {
@ -91,13 +82,22 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
m_hamburger_button->setMenu(&m_window->hamburger_menu()); m_hamburger_button->setMenu(&m_window->hamburger_menu());
m_hamburger_button->setStyleSheet(":menu-indicator {image: none}"); m_hamburger_button->setStyleSheet(":menu-indicator {image: none}");
m_navigate_back_action = create_application_action(*this, view().navigate_back_action());
m_navigate_forward_action = create_application_action(*this, view().navigate_forward_action());
m_reload_action = create_application_action(*this, WebView::Application::the().reload_action());
recreate_toolbar_icons(); recreate_toolbar_icons();
m_favicon = default_favicon(); m_favicon = default_favicon();
m_toolbar->addAction(&m_window->go_back_action()); m_page_context_menu = create_context_menu(*this, view(), view().page_context_menu());
m_toolbar->addAction(&m_window->go_forward_action()); m_link_context_menu = create_context_menu(*this, view(), view().link_context_menu());
m_toolbar->addAction(&m_window->reload_action()); m_image_context_menu = create_context_menu(*this, view(), view().image_context_menu());
m_media_context_menu = create_context_menu(*this, view(), view().media_context_menu());
m_toolbar->addAction(m_navigate_back_action);
m_toolbar->addAction(m_navigate_forward_action);
m_toolbar->addAction(m_reload_action);
m_toolbar->addWidget(m_location_edit); m_toolbar->addWidget(m_location_edit);
m_toolbar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); m_toolbar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
m_hamburger_button_action = m_toolbar->addWidget(m_hamburger_button); m_hamburger_button_action = m_toolbar->addWidget(m_hamburger_button);
@ -414,17 +414,6 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
emit audio_play_state_changed(tab_index(), play_state); emit audio_play_state_changed(tab_index(), play_state);
}; };
view().on_navigation_buttons_state_changed = [this](auto back_enabled, auto forward_enabled) {
m_can_navigate_back = back_enabled;
m_can_navigate_forward = forward_enabled;
emit navigation_buttons_state_changed(tab_index());
};
auto* reload_tab_action = new QAction("&Reload Tab", this);
QObject::connect(reload_tab_action, &QAction::triggered, this, [this]() {
reload();
});
auto* duplicate_tab_action = new QAction("&Duplicate Tab", this); auto* duplicate_tab_action = new QAction("&Duplicate Tab", this);
QObject::connect(duplicate_tab_action, &QAction::triggered, this, [this]() { QObject::connect(duplicate_tab_action, &QAction::triggered, this, [this]() {
m_window->new_tab_from_url(view().url(), Web::HTML::ActivateTab::Yes); m_window->new_tab_from_url(view().url(), Web::HTML::ActivateTab::Yes);
@ -470,7 +459,7 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
}); });
m_context_menu = new QMenu("Context menu", this); m_context_menu = new QMenu("Context menu", this);
m_context_menu->addAction(reload_tab_action); m_context_menu->addAction(create_application_action(*this, WebView::Application::the().reload_action()));
m_context_menu->addAction(duplicate_tab_action); m_context_menu->addAction(duplicate_tab_action);
m_context_menu->addSeparator(); m_context_menu->addSeparator();
auto* move_tab_menu = m_context_menu->addMenu("Mo&ve Tab"); auto* move_tab_menu = m_context_menu->addMenu("Mo&ve Tab");
@ -482,288 +471,6 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
close_multiple_tabs_menu->addAction(close_tabs_to_left_action); close_multiple_tabs_menu->addAction(close_tabs_to_left_action);
close_multiple_tabs_menu->addAction(close_tabs_to_right_action); close_multiple_tabs_menu->addAction(close_tabs_to_right_action);
close_multiple_tabs_menu->addAction(close_other_tabs_action); close_multiple_tabs_menu->addAction(close_other_tabs_action);
auto* search_selected_text_action = new QAction("&Search for <query>", this);
search_selected_text_action->setIcon(load_icon_from_uri("resource://icons/16x16/find.png"sv));
QObject::connect(search_selected_text_action, &QAction::triggered, this, [this]() {
auto const& search_engine = WebView::Application::settings().search_engine();
if (!search_engine.has_value())
return;
auto url_string = search_engine->format_search_query_for_navigation(*m_page_context_menu_search_text);
auto url = URL::Parser::basic_parse(url_string);
VERIFY(url.has_value());
m_window->new_tab_from_url(url.release_value(), Web::HTML::ActivateTab::Yes);
});
auto take_screenshot = [this](auto type) {
auto& view = this->view();
view.take_screenshot(type)
->when_resolved([](auto const& path) {
WebView::Application::the().display_download_confirmation_dialog("Screenshot"sv, path);
})
.when_rejected([](auto const& error) {
if (error.is_errno() && error.code() == ECANCELED)
return;
auto error_message = MUST(String::formatted("{}", error));
WebView::Application::the().display_error_dialog(error_message);
});
};
auto* take_visible_screenshot_action = new QAction("Take &Visible Screenshot", this);
take_visible_screenshot_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-image.png"sv));
QObject::connect(take_visible_screenshot_action, &QAction::triggered, this, [take_screenshot]() {
take_screenshot(WebView::ViewImplementation::ScreenshotType::Visible);
});
auto* take_full_screenshot_action = new QAction("Take &Full Screenshot", this);
take_full_screenshot_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-image.png"sv));
QObject::connect(take_full_screenshot_action, &QAction::triggered, this, [take_screenshot]() {
take_screenshot(WebView::ViewImplementation::ScreenshotType::Full);
});
m_page_context_menu = new QMenu("Context menu", this);
m_page_context_menu->addAction(&m_window->go_back_action());
m_page_context_menu->addAction(&m_window->go_forward_action());
m_page_context_menu->addAction(&m_window->reload_action());
m_page_context_menu->addSeparator();
m_page_context_menu->addAction(&m_window->copy_selection_action());
m_page_context_menu->addAction(&m_window->paste_action());
m_page_context_menu->addAction(&m_window->select_all_action());
m_page_context_menu->addSeparator();
m_page_context_menu->addAction(search_selected_text_action);
m_page_context_menu->addSeparator();
m_page_context_menu->addAction(take_visible_screenshot_action);
m_page_context_menu->addAction(take_full_screenshot_action);
m_page_context_menu->addSeparator();
m_page_context_menu->addAction(&m_window->view_source_action());
view().on_context_menu_request = [this, search_selected_text_action](Gfx::IntPoint content_position) {
auto const& search_engine = WebView::Application::settings().search_engine();
auto selected_text = search_engine.has_value()
? view().selected_text_with_whitespace_collapsed()
: OptionalNone {};
TemporaryChange change_url { m_page_context_menu_search_text, AK::move(selected_text) };
if (m_page_context_menu_search_text.has_value()) {
auto action_text = search_engine->format_search_query_for_display(*m_page_context_menu_search_text);
search_selected_text_action->setText(qstring_from_ak_string(action_text));
search_selected_text_action->setVisible(true);
} else {
search_selected_text_action->setVisible(false);
}
m_page_context_menu->exec(view().map_point_to_global_position(content_position));
};
auto* open_link_in_new_tab_action = new QAction("Open Link in New &Tab", this);
open_link_in_new_tab_action->setIcon(load_icon_from_uri("resource://icons/16x16/new-tab.png"sv));
QObject::connect(open_link_in_new_tab_action, &QAction::triggered, this, [this]() {
open_link_in_new_tab(m_link_context_menu_url);
});
m_link_context_menu_copy_url_action = new QAction("Copy &Link Address", this);
m_link_context_menu_copy_url_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv));
QObject::connect(m_link_context_menu_copy_url_action, &QAction::triggered, this, [this]() {
copy_link_url(m_link_context_menu_url);
});
m_link_context_menu = new QMenu("Link context menu", this);
m_link_context_menu->addAction(open_link_in_new_tab_action);
m_link_context_menu->addAction(m_link_context_menu_copy_url_action);
view().on_link_context_menu_request = [this](auto const& url, Gfx::IntPoint content_position) {
m_link_context_menu_url = url;
switch (WebView::url_type(url)) {
case WebView::URLType::Email:
m_link_context_menu_copy_url_action->setText("Copy &Email Address");
break;
case WebView::URLType::Telephone:
m_link_context_menu_copy_url_action->setText("Copy &Phone Number");
break;
case WebView::URLType::Other:
m_link_context_menu_copy_url_action->setText("Copy &Link Address");
break;
}
m_link_context_menu->exec(view().map_point_to_global_position(content_position));
};
auto* open_image_action = new QAction("&Open Image", this);
open_image_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-image.png"sv));
QObject::connect(open_image_action, &QAction::triggered, this, [this]() {
open_link(m_image_context_menu_url);
});
auto* open_image_in_new_tab_action = new QAction("&Open Image in New &Tab", this);
open_image_in_new_tab_action->setIcon(load_icon_from_uri("resource://icons/16x16/new-tab.png"sv));
QObject::connect(open_image_in_new_tab_action, &QAction::triggered, this, [this]() {
open_link_in_new_tab(m_image_context_menu_url);
});
m_image_context_menu_copy_image_action = new QAction("&Copy Image", this);
m_image_context_menu_copy_image_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv));
QObject::connect(m_image_context_menu_copy_image_action, &QAction::triggered, this, [this]() {
if (!m_image_context_menu_bitmap.has_value())
return;
auto* bitmap = m_image_context_menu_bitmap.value().bitmap();
if (bitmap == nullptr)
return;
auto data = Gfx::BMPWriter::encode(*bitmap);
if (data.is_error())
return;
auto image = QImage::fromData(data.value().data(), data.value().size(), "BMP");
if (image.isNull())
return;
auto* clipboard = QGuiApplication::clipboard();
clipboard->setImage(image);
});
auto* copy_image_url_action = new QAction("Copy Image &URL", this);
copy_image_url_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv));
QObject::connect(copy_image_url_action, &QAction::triggered, this, [this]() {
copy_link_url(m_image_context_menu_url);
});
m_image_context_menu = new QMenu("Image context menu", this);
m_image_context_menu->addAction(open_image_action);
m_image_context_menu->addAction(open_image_in_new_tab_action);
m_image_context_menu->addSeparator();
m_image_context_menu->addAction(m_image_context_menu_copy_image_action);
m_image_context_menu->addAction(copy_image_url_action);
view().on_image_context_menu_request = [this](auto& image_url, Gfx::IntPoint content_position, Optional<Gfx::ShareableBitmap> const& shareable_bitmap) {
m_image_context_menu_url = image_url;
m_image_context_menu_bitmap = shareable_bitmap;
m_image_context_menu_copy_image_action->setEnabled(shareable_bitmap.has_value());
m_image_context_menu->exec(view().map_point_to_global_position(content_position));
};
m_media_context_menu_play_icon = load_icon_from_uri("resource://icons/16x16/play.png"sv);
m_media_context_menu_pause_icon = load_icon_from_uri("resource://icons/16x16/pause.png"sv);
m_media_context_menu_mute_icon = load_icon_from_uri("resource://icons/16x16/audio-volume-muted.png"sv);
m_media_context_menu_unmute_icon = load_icon_from_uri("resource://icons/16x16/audio-volume-high.png"sv);
m_media_context_menu_play_pause_action = new QAction("&Play", this);
m_media_context_menu_play_pause_action->setIcon(m_media_context_menu_play_icon);
QObject::connect(m_media_context_menu_play_pause_action, &QAction::triggered, this, [this]() {
view().toggle_media_play_state();
});
m_media_context_menu_mute_unmute_action = new QAction("&Mute", this);
m_media_context_menu_mute_unmute_action->setIcon(m_media_context_menu_mute_icon);
QObject::connect(m_media_context_menu_mute_unmute_action, &QAction::triggered, this, [this]() {
view().toggle_media_mute_state();
});
m_media_context_menu_controls_action = new QAction("Show &Controls", this);
m_media_context_menu_controls_action->setCheckable(true);
QObject::connect(m_media_context_menu_controls_action, &QAction::triggered, this, [this]() {
view().toggle_media_controls_state();
});
m_media_context_menu_loop_action = new QAction("&Loop", this);
m_media_context_menu_loop_action->setCheckable(true);
QObject::connect(m_media_context_menu_loop_action, &QAction::triggered, this, [this]() {
view().toggle_media_loop_state();
});
auto* open_audio_action = new QAction("&Open Audio", this);
open_audio_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-sound.png"sv));
QObject::connect(open_audio_action, &QAction::triggered, this, [this]() {
open_link(m_media_context_menu_url);
});
auto* open_audio_in_new_tab_action = new QAction("Open Audio in New &Tab", this);
open_audio_in_new_tab_action->setIcon(load_icon_from_uri("resource://icons/16x16/new-tab.png"sv));
QObject::connect(open_audio_in_new_tab_action, &QAction::triggered, this, [this]() {
open_link_in_new_tab(m_media_context_menu_url);
});
auto* copy_audio_url_action = new QAction("Copy Audio &URL", this);
copy_audio_url_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv));
QObject::connect(copy_audio_url_action, &QAction::triggered, this, [this]() {
copy_link_url(m_media_context_menu_url);
});
m_audio_context_menu = new QMenu("Audio context menu", this);
m_audio_context_menu->addAction(m_media_context_menu_play_pause_action);
m_audio_context_menu->addAction(m_media_context_menu_mute_unmute_action);
m_audio_context_menu->addAction(m_media_context_menu_controls_action);
m_audio_context_menu->addAction(m_media_context_menu_loop_action);
m_audio_context_menu->addSeparator();
m_audio_context_menu->addAction(open_audio_action);
m_audio_context_menu->addAction(open_audio_in_new_tab_action);
m_audio_context_menu->addSeparator();
m_audio_context_menu->addAction(copy_audio_url_action);
auto* open_video_action = new QAction("&Open Video", this);
open_video_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-video.png"sv));
QObject::connect(open_video_action, &QAction::triggered, this, [this]() {
open_link(m_media_context_menu_url);
});
auto* open_video_in_new_tab_action = new QAction("Open Video in New &Tab", this);
open_video_in_new_tab_action->setIcon(load_icon_from_uri("resource://icons/16x16/new-tab.png"sv));
QObject::connect(open_video_in_new_tab_action, &QAction::triggered, this, [this]() {
open_link_in_new_tab(m_media_context_menu_url);
});
auto* copy_video_url_action = new QAction("Copy Video &URL", this);
copy_video_url_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv));
QObject::connect(copy_video_url_action, &QAction::triggered, this, [this]() {
copy_link_url(m_media_context_menu_url);
});
m_video_context_menu = new QMenu("Video context menu", this);
m_video_context_menu->addAction(m_media_context_menu_play_pause_action);
m_video_context_menu->addAction(m_media_context_menu_mute_unmute_action);
m_video_context_menu->addAction(m_media_context_menu_controls_action);
m_video_context_menu->addAction(m_media_context_menu_loop_action);
m_video_context_menu->addSeparator();
m_video_context_menu->addAction(open_video_action);
m_video_context_menu->addAction(open_video_in_new_tab_action);
m_video_context_menu->addSeparator();
m_video_context_menu->addAction(copy_video_url_action);
view().on_media_context_menu_request = [this](Gfx::IntPoint content_position, Web::Page::MediaContextMenu const& menu) {
m_media_context_menu_url = menu.media_url;
if (menu.is_playing) {
m_media_context_menu_play_pause_action->setIcon(m_media_context_menu_pause_icon);
m_media_context_menu_play_pause_action->setText("&Pause");
} else {
m_media_context_menu_play_pause_action->setIcon(m_media_context_menu_play_icon);
m_media_context_menu_play_pause_action->setText("&Play");
}
if (menu.is_muted) {
m_media_context_menu_mute_unmute_action->setIcon(m_media_context_menu_unmute_icon);
m_media_context_menu_mute_unmute_action->setText("Un&mute");
} else {
m_media_context_menu_mute_unmute_action->setIcon(m_media_context_menu_mute_icon);
m_media_context_menu_mute_unmute_action->setText("&Mute");
}
m_media_context_menu_controls_action->setChecked(menu.has_user_agent_controls);
m_media_context_menu_loop_action->setChecked(menu.is_looping);
auto screen_position = view().map_point_to_global_position(content_position);
if (menu.is_video)
m_video_context_menu->exec(screen_position);
else
m_audio_context_menu->exec(screen_position);
};
} }
Tab::~Tab() = default; Tab::~Tab() = default;
@ -796,37 +503,6 @@ void Tab::load_html(StringView html)
view().load_html(html); view().load_html(html);
} }
void Tab::back()
{
view().traverse_the_history_by_delta(-1);
}
void Tab::forward()
{
view().traverse_the_history_by_delta(1);
}
void Tab::reload()
{
view().reload();
}
void Tab::open_link(URL::URL const& url)
{
view().on_link_click(url, "", 0);
}
void Tab::open_link_in_new_tab(URL::URL const& url)
{
view().on_link_click(url, "_blank", Web::UIEvents::Mod_Ctrl);
}
void Tab::copy_link_url(URL::URL const& url)
{
auto* clipboard = QGuiApplication::clipboard();
clipboard->setText(qstring_from_ak_string(WebView::url_text_to_copy(url)));
}
void Tab::location_edit_return_pressed() void Tab::location_edit_return_pressed()
{ {
if (m_location_edit->text().isEmpty()) if (m_location_edit->text().isEmpty())
@ -876,15 +552,6 @@ void Tab::update_hover_label()
m_hover_label->raise(); m_hover_label->raise();
} }
void Tab::update_navigation_buttons_state()
{
if (m_window->current_tab() != this)
return;
m_window->go_back_action().setEnabled(m_can_navigate_back);
m_window->go_forward_action().setEnabled(m_can_navigate_forward);
}
bool Tab::event(QEvent* event) bool Tab::event(QEvent* event)
{ {
if (event->type() == QEvent::PaletteChange) { if (event->type() == QEvent::PaletteChange) {
@ -897,9 +564,9 @@ bool Tab::event(QEvent* event)
void Tab::recreate_toolbar_icons() void Tab::recreate_toolbar_icons()
{ {
m_window->go_back_action().setIcon(create_tvg_icon_with_theme_colors("back", palette())); m_navigate_back_action->setIcon(create_tvg_icon_with_theme_colors("back", palette()));
m_window->go_forward_action().setIcon(create_tvg_icon_with_theme_colors("forward", palette())); m_navigate_forward_action->setIcon(create_tvg_icon_with_theme_colors("forward", palette()));
m_window->reload_action().setIcon(create_tvg_icon_with_theme_colors("reload", palette())); m_reload_action->setIcon(create_tvg_icon_with_theme_colors("reload", palette()));
m_window->new_tab_action().setIcon(create_tvg_icon_with_theme_colors("new_tab", palette())); m_window->new_tab_action().setIcon(create_tvg_icon_with_theme_colors("new_tab", palette()));
m_hamburger_button->setIcon(create_tvg_icon_with_theme_colors("hamburger", palette())); m_hamburger_button->setIcon(create_tvg_icon_with_theme_colors("hamburger", palette()));
} }

View file

@ -14,7 +14,6 @@
#include <QBoxLayout> #include <QBoxLayout>
#include <QLabel> #include <QLabel>
#include <QLineEdit>
#include <QMenu> #include <QMenu>
#include <QPointer> #include <QPointer>
#include <QToolBar> #include <QToolBar>
@ -56,10 +55,6 @@ public:
void navigate(URL::URL const&); void navigate(URL::URL const&);
void load_html(StringView); void load_html(StringView);
void back();
void forward();
void reload();
void debug_request(ByteString const& request, ByteString const& argument = ""); void debug_request(ByteString const& request, ByteString const& argument = "");
void open_file(); void open_file();
@ -74,8 +69,6 @@ public:
QMenu* context_menu() const { return m_context_menu; } QMenu* context_menu() const { return m_context_menu; }
void update_navigation_buttons_state();
QToolButton* hamburger_button() const { return m_hamburger_button; } QToolButton* hamburger_button() const { return m_hamburger_button; }
void update_hover_label(); void update_hover_label();
@ -98,17 +91,13 @@ signals:
void title_changed(int id, QString const&); void title_changed(int id, QString const&);
void favicon_changed(int id, QIcon const&); void favicon_changed(int id, QIcon const&);
void audio_play_state_changed(int id, Web::HTML::AudioPlayState); void audio_play_state_changed(int id, Web::HTML::AudioPlayState);
void navigation_buttons_state_changed(int id);
private: private:
virtual void resizeEvent(QResizeEvent*) override; virtual void resizeEvent(QResizeEvent*) override;
virtual bool event(QEvent*) override; virtual bool event(QEvent*) override;
void recreate_toolbar_icons(); void recreate_toolbar_icons();
int tab_index();
void open_link(URL::URL const&);
void open_link_in_new_tab(URL::URL const&);
void copy_link_url(URL::URL const&);
QBoxLayout* m_layout { nullptr }; QBoxLayout* m_layout { nullptr };
QToolBar* m_toolbar { nullptr }; QToolBar* m_toolbar { nullptr };
@ -125,39 +114,17 @@ private:
QIcon m_favicon; QIcon m_favicon;
QMenu* m_context_menu { nullptr }; QMenu* m_context_menu { nullptr };
QMenu* m_page_context_menu { nullptr }; QMenu* m_page_context_menu { nullptr };
Optional<String> m_page_context_menu_search_text;
QMenu* m_link_context_menu { nullptr }; QMenu* m_link_context_menu { nullptr };
QAction* m_link_context_menu_copy_url_action { nullptr };
URL::URL m_link_context_menu_url;
QMenu* m_image_context_menu { nullptr }; QMenu* m_image_context_menu { nullptr };
QAction* m_image_context_menu_copy_image_action { nullptr }; QMenu* m_media_context_menu { nullptr };
Optional<Gfx::ShareableBitmap> m_image_context_menu_bitmap;
URL::URL m_image_context_menu_url;
QMenu* m_audio_context_menu { nullptr };
QMenu* m_video_context_menu { nullptr };
QIcon m_media_context_menu_play_icon;
QIcon m_media_context_menu_pause_icon;
QIcon m_media_context_menu_mute_icon;
QIcon m_media_context_menu_unmute_icon;
QAction* m_media_context_menu_play_pause_action { nullptr };
QAction* m_media_context_menu_mute_unmute_action { nullptr };
QAction* m_media_context_menu_controls_action { nullptr };
QAction* m_media_context_menu_loop_action { nullptr };
URL::URL m_media_context_menu_url;
QMenu* m_select_dropdown { nullptr }; QMenu* m_select_dropdown { nullptr };
int tab_index(); QAction* m_navigate_back_action { nullptr };
QAction* m_navigate_forward_action { nullptr };
QAction* m_reload_action { nullptr };
QPointer<QDialog> m_dialog; QPointer<QDialog> m_dialog;
bool m_can_navigate_back { false };
bool m_can_navigate_forward { false };
}; };
} }