Browse Source

LibWeb+WebContent+Ladybird: Add ability to paste text from clipboard

Text can be pasted by pressing Ctrl/Cmd+V or by using button in the
context menu. For now only the Qt client is supported.
Aliaksandr Kalenik 1 year ago
parent
commit
561e011e07

+ 15 - 0
Ladybird/Qt/BrowserWindow.cpp

@@ -110,6 +110,12 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> 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);

+ 7 - 0
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 };

+ 1 - 0
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);

+ 1 - 0
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

+ 9 - 0
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())

+ 1 - 0
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<EditEventHandler>);
 

+ 9 - 4
Userland/Libraries/LibWeb/Page/EditEventHandler.cpp

@@ -93,13 +93,20 @@ void EditEventHandler::handle_delete(DOM::Range& range)
 }
 
 void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Position> 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<DOM::Position> position, String data)
 {
     if (is<DOM::Text>(*position->node())) {
         auto& node = verify_cast<DOM::Text>(*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<DOM::Position> position, u
     } else {
         auto& node = *position->node();
         auto& realm = node.realm();
-        StringBuilder builder;
-        builder.append_code_point(code_point);
-        auto text = realm.heap().allocate<DOM::Text>(realm, node.document(), MUST(builder.to_string()));
+        auto text = realm.heap().allocate<DOM::Text>(realm, node.document(), data);
         MUST(node.append_child(*text));
         position->set_node(text);
         position->set_offset(1);

+ 1 - 0
Userland/Libraries/LibWeb/Page/EditEventHandler.h

@@ -23,6 +23,7 @@ public:
     virtual void handle_delete_character_after(JS::NonnullGCPtr<DOM::Position>);
     virtual void handle_delete(DOM::Range&);
     virtual void handle_insert(JS::NonnullGCPtr<DOM::Position>, u32 code_point);
+    virtual void handle_insert(JS::NonnullGCPtr<DOM::Position>, String);
 
 private:
     JS::NonnullGCPtr<HTML::BrowsingContext> m_browsing_context;

+ 15 - 0
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;

+ 2 - 0
Userland/Libraries/LibWeb/Page/EventHandler.h

@@ -38,6 +38,8 @@ public:
 
     void set_edit_event_handler(NonnullOwnPtr<EditEventHandler> value) { m_edit_event_handler = move(value); }
 
+    void handle_paste(String const& text);
+
     void visit_edges(JS::Cell::Visitor& visitor) const;
 
 private:

+ 5 - 0
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());

+ 1 - 0
Userland/Libraries/LibWebView/ViewImplementation.h

@@ -63,6 +63,7 @@ public:
     ByteString selected_text();
     Optional<String> selected_text_with_whitespace_collapsed();
     void select_all();
+    void paste(String const&);
 
     void get_source();
 

+ 12 - 0
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);

+ 2 - 0
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<PageHost> m_page_host;

+ 1 - 0
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<String> filters) =|
     set_autoplay_allowed_on_all_websites(u64 page_id) =|