diff --git a/Ladybird/Qt/BrowserWindow.cpp b/Ladybird/Qt/BrowserWindow.cpp index 7c8c04c3c6f..b00a5395548 100644 --- a/Ladybird/Qt/BrowserWindow.cpp +++ b/Ladybird/Qt/BrowserWindow.cpp @@ -110,6 +110,12 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, WebView::Cook 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)); @@ -721,6 +727,15 @@ void BrowserWindow::select_all() m_current_tab->view().select_all(); } +void BrowserWindow::paste() +{ + if (!m_current_tab) + return; + + auto* clipboard = QGuiApplication::clipboard(); + m_current_tab->view().paste(ak_string_from_qstring(clipboard->text())); +} + void BrowserWindow::update_displayed_zoom_level() { VERIFY(m_current_tab); diff --git a/Ladybird/Qt/BrowserWindow.h b/Ladybird/Qt/BrowserWindow.h index bf2d83831e7..c1721f0d0ba 100644 --- a/Ladybird/Qt/BrowserWindow.h +++ b/Ladybird/Qt/BrowserWindow.h @@ -59,6 +59,11 @@ public: return *m_select_all_action; } + QAction& paste_action() + { + return *m_paste_action; + } + QAction& view_source_action() { return *m_view_source_action; @@ -90,6 +95,7 @@ public slots: void reset_zoom(); void update_zoom_menu(); void select_all(); + void paste(); void copy_selected_text(); protected: @@ -131,6 +137,7 @@ private: QAction* m_go_forward_action { nullptr }; QAction* m_reload_action { nullptr }; QAction* m_copy_selection_action { nullptr }; + QAction* m_paste_action { nullptr }; QAction* m_select_all_action { nullptr }; QAction* m_view_source_action { nullptr }; QAction* m_inspect_dom_node_action { nullptr }; diff --git a/Ladybird/Qt/Tab.cpp b/Ladybird/Qt/Tab.cpp index b67a4fc7465..b0a30662497 100644 --- a/Ladybird/Qt/Tab.cpp +++ b/Ladybird/Qt/Tab.cpp @@ -430,6 +430,7 @@ Tab::Tab(BrowserWindow* window, WebContentOptions const& web_content_options, St 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); diff --git a/Ladybird/cmake/ResourceFiles.cmake b/Ladybird/cmake/ResourceFiles.cmake index d627818d4fb..2c8a8951553 100644 --- a/Ladybird/cmake/ResourceFiles.cmake +++ b/Ladybird/cmake/ResourceFiles.cmake @@ -32,6 +32,7 @@ set(16x16_ICONS layout.png new-tab.png open-parent-directory.png + paste.png pause.png play.png select-all.png diff --git a/Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp b/Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp index 703a0ae9f05..8d690c92389 100644 --- a/Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp +++ b/Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp @@ -441,6 +441,15 @@ void BrowsingContext::select_all() (void)selection->select_all_children(*document->body()); } +void BrowsingContext::paste(String const& text) +{ + auto* document = active_document(); + if (!document) + return; + + m_event_handler.handle_paste(text); +} + bool BrowsingContext::increment_cursor_position_offset() { if (!m_cursor_position->increment_offset()) diff --git a/Userland/Libraries/LibWeb/HTML/BrowsingContext.h b/Userland/Libraries/LibWeb/HTML/BrowsingContext.h index 9a765b7a397..1d0cce56ccb 100644 --- a/Userland/Libraries/LibWeb/HTML/BrowsingContext.h +++ b/Userland/Libraries/LibWeb/HTML/BrowsingContext.h @@ -130,6 +130,7 @@ public: String selected_text() const; void select_all(); + void paste(String const&); void did_edit(Badge); diff --git a/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp b/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp index 542efbe2311..07e7c9ab4b1 100644 --- a/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp +++ b/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp @@ -93,13 +93,20 @@ void EditEventHandler::handle_delete(DOM::Range& range) } void EditEventHandler::handle_insert(JS::NonnullGCPtr position, u32 code_point) +{ + StringBuilder builder; + builder.append_code_point(code_point); + handle_insert(position, MUST(builder.to_string())); +} + +void EditEventHandler::handle_insert(JS::NonnullGCPtr position, String data) { if (is(*position->node())) { auto& node = verify_cast(*position->node()); StringBuilder builder; builder.append(node.data().bytes_as_string_view().substring_view(0, position->offset())); - builder.append_code_point(code_point); + builder.append(data); builder.append(node.data().bytes_as_string_view().substring_view(position->offset())); // Cut string by max length @@ -113,9 +120,7 @@ void EditEventHandler::handle_insert(JS::NonnullGCPtr position, u } else { auto& node = *position->node(); auto& realm = node.realm(); - StringBuilder builder; - builder.append_code_point(code_point); - auto text = realm.heap().allocate(realm, node.document(), MUST(builder.to_string())); + auto text = realm.heap().allocate(realm, node.document(), data); MUST(node.append_child(*text)); position->set_node(text); position->set_offset(1); diff --git a/Userland/Libraries/LibWeb/Page/EditEventHandler.h b/Userland/Libraries/LibWeb/Page/EditEventHandler.h index eb17462eaeb..a3083b42a34 100644 --- a/Userland/Libraries/LibWeb/Page/EditEventHandler.h +++ b/Userland/Libraries/LibWeb/Page/EditEventHandler.h @@ -23,6 +23,7 @@ public: virtual void handle_delete_character_after(JS::NonnullGCPtr); virtual void handle_delete(DOM::Range&); virtual void handle_insert(JS::NonnullGCPtr, u32 code_point); + virtual void handle_insert(JS::NonnullGCPtr, String); private: JS::NonnullGCPtr m_browsing_context; diff --git a/Userland/Libraries/LibWeb/Page/EventHandler.cpp b/Userland/Libraries/LibWeb/Page/EventHandler.cpp index ca23a201007..9d9cc83fcaf 100644 --- a/Userland/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Userland/Libraries/LibWeb/Page/EventHandler.cpp @@ -868,6 +868,21 @@ bool EventHandler::handle_keyup(KeyCode key, u32 modifiers, u32 code_point) return !fire_keyboard_event(UIEvents::EventNames::keyup, m_browsing_context, key, modifiers, code_point); } +void EventHandler::handle_paste(String const& text) +{ + auto* active_document = m_browsing_context->active_document(); + if (!active_document) + return; + if (!active_document->is_fully_active()) + return; + + if (auto cursor_position = m_browsing_context->cursor_position()) { + active_document->update_layout(); + m_edit_event_handler->handle_insert(*cursor_position, text); + cursor_position->set_offset(cursor_position->offset() + text.code_points().length()); + } +} + void EventHandler::set_mouse_event_tracking_paintable(Painting::Paintable* paintable) { m_mouse_event_tracking_paintable = paintable; diff --git a/Userland/Libraries/LibWeb/Page/EventHandler.h b/Userland/Libraries/LibWeb/Page/EventHandler.h index 33467fdab73..06980b78851 100644 --- a/Userland/Libraries/LibWeb/Page/EventHandler.h +++ b/Userland/Libraries/LibWeb/Page/EventHandler.h @@ -38,6 +38,8 @@ public: void set_edit_event_handler(NonnullOwnPtr value) { m_edit_event_handler = move(value); } + void handle_paste(String const& text); + void visit_edges(JS::Cell::Visitor& visitor) const; private: diff --git a/Userland/Libraries/LibWebView/ViewImplementation.cpp b/Userland/Libraries/LibWebView/ViewImplementation.cpp index 168f6238722..20703761a5b 100644 --- a/Userland/Libraries/LibWebView/ViewImplementation.cpp +++ b/Userland/Libraries/LibWebView/ViewImplementation.cpp @@ -165,6 +165,11 @@ void ViewImplementation::select_all() client().async_select_all(page_id()); } +void ViewImplementation::paste(String const& text) +{ + client().async_paste(page_id(), text); +} + void ViewImplementation::get_source() { client().async_get_source(page_id()); diff --git a/Userland/Libraries/LibWebView/ViewImplementation.h b/Userland/Libraries/LibWebView/ViewImplementation.h index 164f89341d1..901176cdbae 100644 --- a/Userland/Libraries/LibWebView/ViewImplementation.h +++ b/Userland/Libraries/LibWebView/ViewImplementation.h @@ -63,6 +63,7 @@ public: ByteString selected_text(); Optional selected_text_with_whitespace_collapsed(); void select_all(); + void paste(String const&); void get_source(); diff --git a/Userland/Services/WebContent/ConnectionFromClient.cpp b/Userland/Services/WebContent/ConnectionFromClient.cpp index 1b9dbfbd31b..6ba8c75c8a8 100644 --- a/Userland/Services/WebContent/ConnectionFromClient.cpp +++ b/Userland/Services/WebContent/ConnectionFromClient.cpp @@ -910,6 +910,18 @@ void ConnectionFromClient::select_all(u64 page_id) page.page().focused_context().select_all(); } +void ConnectionFromClient::paste(u64 page_id, String const& text) +{ + auto maybe_page = page(page_id); + if (!maybe_page.has_value()) { + dbgln("ConnectionFromClient::paste: No page with ID {}", page_id); + return; + } + auto& page = maybe_page.release_value(); + + page.page().focused_context().paste(text); +} + Messages::WebContentServer::DumpLayoutTreeResponse ConnectionFromClient::dump_layout_tree(u64 page_id) { auto maybe_page = page(page_id); diff --git a/Userland/Services/WebContent/ConnectionFromClient.h b/Userland/Services/WebContent/ConnectionFromClient.h index f7f83a928ab..5613f489013 100644 --- a/Userland/Services/WebContent/ConnectionFromClient.h +++ b/Userland/Services/WebContent/ConnectionFromClient.h @@ -125,6 +125,8 @@ private: virtual Messages::WebContentServer::GetSelectedTextResponse get_selected_text(u64 page_id) override; virtual void select_all(u64 page_id) override; + virtual void paste(u64 page_id, String const& text) override; + void report_finished_handling_input_event(u64 page_id, bool event_was_handled); NonnullOwnPtr m_page_host; diff --git a/Userland/Services/WebContent/WebContentServer.ipc b/Userland/Services/WebContent/WebContentServer.ipc index 94bf06292fa..b741f6d6a36 100644 --- a/Userland/Services/WebContent/WebContentServer.ipc +++ b/Userland/Services/WebContent/WebContentServer.ipc @@ -65,6 +65,7 @@ endpoint WebContentServer get_selected_text(u64 page_id) => (ByteString selection) select_all(u64 page_id) =| + paste(u64 page_id, String text) =| set_content_filters(u64 page_id, Vector filters) =| set_autoplay_allowed_on_all_websites(u64 page_id) =|