LibWeb+LibWebVew+WebContent+UI: Add IPC to retrieve the system clipboard

We currently have a single IPC to set clipboard data. We will also need
an IPC to retrieve that data from the UI. This defines system clipboard
data in LibWeb to handle this transfer, and adds the IPC to provide it.
This commit is contained in:
Timothy Flynn 2025-05-01 11:38:31 -04:00 committed by Tim Flynn
commit 61c0f67c8c
Notes: github-actions[bot] 2025-05-02 21:47:42 +00:00
23 changed files with 255 additions and 28 deletions

View file

@ -34,6 +34,7 @@ set(SOURCES
Clipboard/Clipboard.cpp Clipboard/Clipboard.cpp
Clipboard/ClipboardEvent.cpp Clipboard/ClipboardEvent.cpp
Clipboard/ClipboardItem.cpp Clipboard/ClipboardItem.cpp
Clipboard/SystemClipboard.cpp
Compression/CompressionStream.cpp Compression/CompressionStream.cpp
Compression/DecompressionStream.cpp Compression/DecompressionStream.cpp
ContentSecurityPolicy/Directives/Directive.cpp ContentSecurityPolicy/Directives/Directive.cpp

View file

@ -8,6 +8,7 @@
#include <LibTextCodec/Decoder.h> #include <LibTextCodec/Decoder.h>
#include <LibWeb/Bindings/ClipboardPrototype.h> #include <LibWeb/Bindings/ClipboardPrototype.h>
#include <LibWeb/Clipboard/Clipboard.h> #include <LibWeb/Clipboard/Clipboard.h>
#include <LibWeb/Clipboard/SystemClipboard.h>
#include <LibWeb/FileAPI/Blob.h> #include <LibWeb/FileAPI/Blob.h>
#include <LibWeb/HTML/Scripting/Environments.h> #include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h> #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
@ -40,43 +41,43 @@ void Clipboard::initialize(JS::Realm& realm)
} }
// https://w3c.github.io/clipboard-apis/#os-specific-well-known-format // https://w3c.github.io/clipboard-apis/#os-specific-well-known-format
static StringView os_specific_well_known_format(StringView mime_type_string) static String os_specific_well_known_format(StringView mime_type_string)
{ {
// NOTE: Here we always takes the Linux case, and defer to the browser process to handle OS specific implementations. // NOTE: Here we always takes the Linux case, and defer to the browser process to handle OS specific implementations.
auto mime_type = MimeSniff::MimeType::parse(mime_type_string); auto mime_type = MimeSniff::MimeType::parse(mime_type_string);
// 1. Let wellKnownFormat be an empty string. // 1. Let wellKnownFormat be an empty string.
StringView well_known_format {}; String well_known_format {};
// 2. If mimeTypes essence is "text/plain", then // 2. If mimeTypes essence is "text/plain", then
if (mime_type->essence() == "text/plain"sv) { if (auto const& essence = mime_type->essence(); essence == "text/plain"sv) {
// On Windows, follow the convention described below: // On Windows, follow the convention described below:
// Assign CF_UNICODETEXT to wellKnownFormat. // Assign CF_UNICODETEXT to wellKnownFormat.
// On MacOS, follow the convention described below: // On MacOS, follow the convention described below:
// Assign NSPasteboardTypeString to wellKnownFormat. // Assign NSPasteboardTypeString to wellKnownFormat.
// On Linux, ChromeOS, and Android, follow the convention described below: // On Linux, ChromeOS, and Android, follow the convention described below:
// Assign "text/plain" to wellKnownFormat. // Assign "text/plain" to wellKnownFormat.
well_known_format = "text/plain"sv; well_known_format = essence;
} }
// 3. Else, if mimeTypes essence is "text/html", then // 3. Else, if mimeTypes essence is "text/html", then
if (mime_type->essence() == "text/html"sv) { else if (essence == "text/html"sv) {
// On Windows, follow the convention described below: // On Windows, follow the convention described below:
// Assign CF_HTML to wellKnownFormat. // Assign CF_HTML to wellKnownFormat.
// On MacOS, follow the convention described below: // On MacOS, follow the convention described below:
// Assign NSHTMLPboardType to wellKnownFormat. // Assign NSHTMLPboardType to wellKnownFormat.
// On Linux, ChromeOS, and Android, follow the convention described below: // On Linux, ChromeOS, and Android, follow the convention described below:
// Assign "text/html" to wellKnownFormat. // Assign "text/html" to wellKnownFormat.
well_known_format = "text/html"sv; well_known_format = essence;
} }
// 4. Else, if mimeTypes essence is "image/png", then // 4. Else, if mimeTypes essence is "image/png", then
if (mime_type->essence() == "image/png"sv) { else if (essence == "image/png"sv) {
// On Windows, follow the convention described below: // On Windows, follow the convention described below:
// Assign "PNG" to wellKnownFormat. // Assign "PNG" to wellKnownFormat.
// On MacOS, follow the convention described below: // On MacOS, follow the convention described below:
// Assign NSPasteboardTypePNG to wellKnownFormat. // Assign NSPasteboardTypePNG to wellKnownFormat.
// On Linux, ChromeOS, and Android, follow the convention described below: // On Linux, ChromeOS, and Android, follow the convention described below:
// Assign "image/png" to wellKnownFormat. // Assign "image/png" to wellKnownFormat.
well_known_format = "image/png"sv; well_known_format = essence;
} }
// 5. Return wellKnownFormat. // 5. Return wellKnownFormat.
@ -113,7 +114,7 @@ static void write_blobs_and_option_to_clipboard(JS::Realm& realm, ReadonlySpan<G
auto payload = MUST(TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, item->raw_bytes())); auto payload = MUST(TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, item->raw_bytes()));
// 4. Insert payload and presentationStyle into the system clipboard using formatString as the native clipboard format. // 4. Insert payload and presentationStyle into the system clipboard using formatString as the native clipboard format.
window.page().client().page_did_insert_clipboard_entry(payload, presentation_style, format_string); window.page().client().page_did_insert_clipboard_entry({ payload.to_byte_string(), move(format_string) }, presentation_style);
} }
// FIXME: 3. Write web custom formats given webCustomFormats. // FIXME: 3. Write web custom formats given webCustomFormats.

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#include <LibWeb/Clipboard/SystemClipboard.h>
template<>
ErrorOr<void> IPC::encode(Encoder& encoder, Web::Clipboard::SystemClipboardRepresentation const& output)
{
TRY(encoder.encode(output.data));
TRY(encoder.encode(output.mime_type));
return {};
}
template<>
ErrorOr<Web::Clipboard::SystemClipboardRepresentation> IPC::decode(Decoder& decoder)
{
auto data = TRY(decoder.decode<ByteString>());
auto mime_type = TRY(decoder.decode<String>());
return Web::Clipboard::SystemClipboardRepresentation { move(data), move(mime_type) };
}
template<>
ErrorOr<void> IPC::encode(Encoder& encoder, Web::Clipboard::SystemClipboardItem const& output)
{
TRY(encoder.encode(output.system_clipboard_representations));
return {};
}
template<>
ErrorOr<Web::Clipboard::SystemClipboardItem> IPC::decode(Decoder& decoder)
{
auto system_clipboard_representation = TRY(decoder.decode<Vector<Web::Clipboard::SystemClipboardRepresentation>>());
return Web::Clipboard::SystemClipboardItem { move(system_clipboard_representation) };
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibIPC/Forward.h>
namespace Web::Clipboard {
// https://w3c.github.io/clipboard-apis/#system-clipboard-representation
struct SystemClipboardRepresentation {
ByteString data;
String mime_type;
};
// https://w3c.github.io/clipboard-apis/#system-clipboard-item
struct SystemClipboardItem {
Vector<SystemClipboardRepresentation> system_clipboard_representations;
};
}
namespace IPC {
template<>
ErrorOr<void> encode(Encoder&, Web::Clipboard::SystemClipboardRepresentation const&);
template<>
ErrorOr<Web::Clipboard::SystemClipboardRepresentation> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Web::Clipboard::SystemClipboardItem const&);
template<>
ErrorOr<Web::Clipboard::SystemClipboardItem> decode(Decoder&);
}

View file

@ -92,6 +92,9 @@ enum class XMLHttpRequestResponseType : u8;
namespace Web::Clipboard { namespace Web::Clipboard {
class Clipboard; class Clipboard;
class ClipboardItem; class ClipboardItem;
struct SystemClipboardItem;
struct SystemClipboardRepresentation;
} }
namespace Web::Compression { namespace Web::Compression {

View file

@ -10,6 +10,7 @@
#include <LibIPC/Decoder.h> #include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h> #include <LibIPC/Encoder.h>
#include <LibWeb/CSS/StyleComputer.h> #include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/Clipboard/SystemClipboard.h>
#include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Range.h> #include <LibWeb/DOM/Range.h>
#include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/HTML/BrowsingContext.h>
@ -49,6 +50,7 @@ void Page::visit_edges(JS::Cell::Visitor& visitor)
visitor.visit(m_client); visitor.visit(m_client);
visitor.visit(m_window_rect_observer); visitor.visit(m_window_rect_observer);
visitor.visit(m_on_pending_dialog_closed); visitor.visit(m_on_pending_dialog_closed);
visitor.visit(m_pending_clipboard_requests);
} }
HTML::Navigable& Page::focused_navigable() HTML::Navigable& Page::focused_navigable()
@ -454,6 +456,20 @@ void Page::select_dropdown_closed(Optional<u32> const& selected_item_id)
} }
} }
void Page::request_clipboard_entries(ClipboardRequest request)
{
auto request_id = m_next_clipboard_request_id++;
m_pending_clipboard_requests.set(request_id, request);
client().page_did_request_clipboard_entries(request_id);
}
void Page::retrieved_clipboard_entries(u64 request_id, Vector<Clipboard::SystemClipboardItem> items)
{
if (auto request = m_pending_clipboard_requests.take(request_id); request.has_value())
(*request)->function()(move(items));
}
void Page::register_media_element(Badge<HTML::HTMLMediaElement>, UniqueNodeID media_id) void Page::register_media_element(Badge<HTML::HTMLMediaElement>, UniqueNodeID media_id)
{ {
m_media_elements.append(media_id); m_media_elements.append(media_id);

View file

@ -164,6 +164,10 @@ public:
void did_request_select_dropdown(WeakPtr<HTML::HTMLSelectElement> target, Web::CSSPixelPoint content_position, Web::CSSPixels minimum_width, Vector<Web::HTML::SelectItem> items); void did_request_select_dropdown(WeakPtr<HTML::HTMLSelectElement> target, Web::CSSPixelPoint content_position, Web::CSSPixels minimum_width, Vector<Web::HTML::SelectItem> items);
void select_dropdown_closed(Optional<u32> const& selected_item_id); void select_dropdown_closed(Optional<u32> const& selected_item_id);
using ClipboardRequest = GC::Ref<GC::Function<void(Vector<Clipboard::SystemClipboardItem>)>>;
void request_clipboard_entries(ClipboardRequest);
void retrieved_clipboard_entries(u64 request_id, Vector<Clipboard::SystemClipboardItem>);
enum class PendingNonBlockingDialog { enum class PendingNonBlockingDialog {
None, None,
ColorPicker, ColorPicker,
@ -272,6 +276,9 @@ private:
PendingNonBlockingDialog m_pending_non_blocking_dialog { PendingNonBlockingDialog::None }; PendingNonBlockingDialog m_pending_non_blocking_dialog { PendingNonBlockingDialog::None };
WeakPtr<HTML::HTMLElement> m_pending_non_blocking_dialog_target; WeakPtr<HTML::HTMLElement> m_pending_non_blocking_dialog_target;
HashMap<u64, ClipboardRequest> m_pending_clipboard_requests;
u64 m_next_clipboard_request_id { 0 };
Vector<UniqueNodeID> m_media_elements; Vector<UniqueNodeID> m_media_elements;
Optional<UniqueNodeID> m_media_context_menu_element_id; Optional<UniqueNodeID> m_media_context_menu_element_id;
@ -393,7 +400,8 @@ public:
virtual void page_did_change_theme_color(Gfx::Color) { } virtual void page_did_change_theme_color(Gfx::Color) { }
virtual void page_did_insert_clipboard_entry([[maybe_unused]] StringView data, [[maybe_unused]] StringView presentation_style, [[maybe_unused]] StringView mime_type) { } virtual void page_did_insert_clipboard_entry(Clipboard::SystemClipboardRepresentation const&, [[maybe_unused]] StringView presentation_style) { }
virtual void page_did_request_clipboard_entries([[maybe_unused]] u64 request_id) { }
virtual void page_did_change_audio_play_state(HTML::AudioPlayState) { } virtual void page_did_change_audio_play_state(HTML::AudioPlayState) { }

View file

@ -10,6 +10,7 @@
#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 <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>
@ -455,6 +456,11 @@ 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::retrieved_clipboard_entries(u64 request_id, ReadonlySpan<Web::Clipboard::SystemClipboardItem> items)
{
client().async_retrieved_clipboard_entries(page_id(), request_id, items);
}
void ViewImplementation::toggle_media_play_state() void ViewImplementation::toggle_media_play_state()
{ {
client().async_toggle_media_play_state(page_id()); client().async_toggle_media_play_state(page_id());

View file

@ -130,6 +130,8 @@ 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 retrieved_clipboard_entries(u64 request_id, ReadonlySpan<Web::Clipboard::SystemClipboardItem>);
void toggle_media_play_state(); void toggle_media_play_state();
void toggle_media_mute_state(); void toggle_media_mute_state();
void toggle_media_loop_state(); void toggle_media_loop_state();
@ -225,7 +227,8 @@ public:
Function<void(double factor)> on_set_browser_zoom; Function<void(double factor)> on_set_browser_zoom;
Function<void(size_t current_match_index, Optional<size_t> const& total_match_count)> on_find_in_page; Function<void(size_t current_match_index, Optional<size_t> const& total_match_count)> on_find_in_page;
Function<void(Gfx::Color)> on_theme_color_change; Function<void(Gfx::Color)> on_theme_color_change;
Function<void(String const&, String const&, String const&)> on_insert_clipboard_entry; Function<void(Web::Clipboard::SystemClipboardRepresentation, String const&)> on_insert_clipboard_entry;
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(bool, bool)> on_navigation_buttons_state_changed;
Function<void()> on_web_content_crashed; Function<void()> on_web_content_crashed;

View file

@ -633,11 +633,19 @@ void WebContentClient::did_change_theme_color(u64 page_id, Gfx::Color color)
} }
} }
void WebContentClient::did_insert_clipboard_entry(u64 page_id, String data, String presentation_style, String mime_type) void WebContentClient::did_insert_clipboard_entry(u64 page_id, Web::Clipboard::SystemClipboardRepresentation entry, String presentation_style)
{ {
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_insert_clipboard_entry) if (view->on_insert_clipboard_entry)
view->on_insert_clipboard_entry(data, presentation_style, mime_type); view->on_insert_clipboard_entry(move(entry), presentation_style);
}
}
void WebContentClient::did_request_clipboard_entries(u64 page_id, u64 request_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_request_clipboard_entries)
view->on_request_clipboard_entries(request_id);
} }
} }

View file

@ -126,7 +126,8 @@ private:
virtual void did_set_browser_zoom(u64 page_id, double factor) override; virtual void did_set_browser_zoom(u64 page_id, double factor) override;
virtual void did_find_in_page(u64 page_id, size_t current_match_index, Optional<size_t> total_match_count) override; virtual void did_find_in_page(u64 page_id, size_t current_match_index, Optional<size_t> total_match_count) override;
virtual void did_change_theme_color(u64 page_id, Gfx::Color color) override; virtual void did_change_theme_color(u64 page_id, Gfx::Color color) override;
virtual void did_insert_clipboard_entry(u64 page_id, String data, String presentation_style, String mime_type) override; virtual void did_insert_clipboard_entry(u64 page_id, Web::Clipboard::SystemClipboardRepresentation, String presentation_style) override;
virtual void did_request_clipboard_entries(u64 page_id, u64 request_id) override;
virtual void did_change_audio_play_state(u64 page_id, Web::HTML::AudioPlayState) override; virtual void did_change_audio_play_state(u64 page_id, Web::HTML::AudioPlayState) override;
virtual void did_update_navigation_buttons_state(u64 page_id, bool back_enabled, bool forward_enabled) override; virtual void did_update_navigation_buttons_state(u64 page_id, bool back_enabled, bool forward_enabled) override;
virtual void did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::ShareableBitmap, i32 back_bitmap_id, Gfx::ShareableBitmap) override; virtual void did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::ShareableBitmap, i32 back_bitmap_id, Gfx::ShareableBitmap) override;

View file

@ -1257,6 +1257,12 @@ void ConnectionFromClient::select_dropdown_closed(u64 page_id, Optional<u32> sel
page->page().select_dropdown_closed(selected_item_id); page->page().select_dropdown_closed(selected_item_id);
} }
void ConnectionFromClient::retrieved_clipboard_entries(u64 page_id, u64 request_id, Vector<Web::Clipboard::SystemClipboardItem> items)
{
if (auto page = this->page(page_id); page.has_value())
page->page().retrieved_clipboard_entries(request_id, move(items));
}
void ConnectionFromClient::toggle_media_play_state(u64 page_id) void ConnectionFromClient::toggle_media_play_state(u64 page_id)
{ {
if (auto page = this->page(page_id); page.has_value()) if (auto page = this->page(page_id); page.has_value())

View file

@ -129,6 +129,8 @@ private:
virtual void file_picker_closed(u64 page_id, Vector<Web::HTML::SelectedFile> selected_files) override; virtual void file_picker_closed(u64 page_id, Vector<Web::HTML::SelectedFile> selected_files) override;
virtual void select_dropdown_closed(u64 page_id, Optional<u32> selected_item_id) override; virtual void select_dropdown_closed(u64 page_id, Optional<u32> selected_item_id) override;
virtual void retrieved_clipboard_entries(u64 page_id, u64 request_id, Vector<Web::Clipboard::SystemClipboardItem>) override;
virtual void toggle_media_play_state(u64 page_id) override; virtual void toggle_media_play_state(u64 page_id) override;
virtual void toggle_media_mute_state(u64 page_id) override; virtual void toggle_media_mute_state(u64 page_id) override;
virtual void toggle_media_loop_state(u64 page_id) override; virtual void toggle_media_loop_state(u64 page_id) override;

View file

@ -649,9 +649,14 @@ void PageClient::page_did_change_theme_color(Gfx::Color color)
client().async_did_change_theme_color(m_id, color); client().async_did_change_theme_color(m_id, color);
} }
void PageClient::page_did_insert_clipboard_entry(StringView data, StringView presentation_style, StringView mime_type) void PageClient::page_did_insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation const& entry, StringView presentation_style)
{ {
client().async_did_insert_clipboard_entry(m_id, data, presentation_style, mime_type); client().async_did_insert_clipboard_entry(m_id, entry, presentation_style);
}
void PageClient::page_did_request_clipboard_entries(u64 request_id)
{
client().async_did_request_clipboard_entries(m_id, request_id);
} }
void PageClient::page_did_change_audio_play_state(Web::HTML::AudioPlayState play_state) void PageClient::page_did_change_audio_play_state(Web::HTML::AudioPlayState play_state)

View file

@ -169,7 +169,8 @@ private:
virtual void page_did_set_test_timeout(double milliseconds) override; virtual void page_did_set_test_timeout(double milliseconds) override;
virtual void page_did_set_browser_zoom(double factor) override; virtual void page_did_set_browser_zoom(double factor) override;
virtual void page_did_change_theme_color(Gfx::Color color) override; virtual void page_did_change_theme_color(Gfx::Color color) override;
virtual void page_did_insert_clipboard_entry(StringView data, StringView presentation_style, StringView mime_type) override; virtual void page_did_insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation const&, StringView presentation_style) override;
virtual void page_did_request_clipboard_entries(u64 request_id) override;
virtual void page_did_change_audio_play_state(Web::HTML::AudioPlayState) override; virtual void page_did_change_audio_play_state(Web::HTML::AudioPlayState) override;
virtual void page_did_allocate_backing_stores(i32 front_bitmap_id, Gfx::ShareableBitmap front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap back_bitmap) override; virtual void page_did_allocate_backing_stores(i32 front_bitmap_id, Gfx::ShareableBitmap front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap back_bitmap) override;
virtual IPC::File request_worker_agent(Web::Bindings::AgentType) override; virtual IPC::File request_worker_agent(Web::Bindings::AgentType) override;

View file

@ -3,6 +3,8 @@
#include <LibGfx/Cursor.h> #include <LibGfx/Cursor.h>
#include <LibGfx/ShareableBitmap.h> #include <LibGfx/ShareableBitmap.h>
#include <LibURL/URL.h> #include <LibURL/URL.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Clipboard/SystemClipboard.h>
#include <LibWeb/Cookie/Cookie.h> #include <LibWeb/Cookie/Cookie.h>
#include <LibWeb/Cookie/ParsedCookie.h> #include <LibWeb/Cookie/ParsedCookie.h>
#include <LibWeb/CSS/Selector.h> #include <LibWeb/CSS/Selector.h>
@ -21,7 +23,6 @@
#include <LibWebView/Mutation.h> #include <LibWebView/Mutation.h>
#include <LibWebView/PageInfo.h> #include <LibWebView/PageInfo.h>
#include <LibWebView/ProcessHandle.h> #include <LibWebView/ProcessHandle.h>
#include <LibWeb/Bindings/MainThreadVM.h>
endpoint WebContentClient endpoint WebContentClient
{ {
@ -91,7 +92,10 @@ endpoint WebContentClient
did_request_select_dropdown(u64 page_id, Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items) =| did_request_select_dropdown(u64 page_id, Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items) =|
did_finish_handling_input_event(u64 page_id, Web::EventResult event_result) =| did_finish_handling_input_event(u64 page_id, Web::EventResult event_result) =|
did_change_theme_color(u64 page_id, Gfx::Color color) =| did_change_theme_color(u64 page_id, Gfx::Color color) =|
did_insert_clipboard_entry(u64 page_id, String data, String presentation_style, String mime_type) =|
did_insert_clipboard_entry(u64 page_id, Web::Clipboard::SystemClipboardRepresentation entry, String presentation_style) =|
did_request_clipboard_entries(u64 page_id, u64 request_id) =|
did_update_navigation_buttons_state(u64 page_id, bool back_enabled, bool forward_enabled) =| did_update_navigation_buttons_state(u64 page_id, bool back_enabled, bool forward_enabled) =|
did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::ShareableBitmap front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap back_bitmap) =| did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::ShareableBitmap front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap back_bitmap) =|

View file

@ -1,6 +1,7 @@
#include <LibGfx/Rect.h> #include <LibGfx/Rect.h>
#include <LibIPC/File.h> #include <LibIPC/File.h>
#include <LibURL/URL.h> #include <LibURL/URL.h>
#include <LibWeb/Clipboard/SystemClipboard.h>
#include <LibWeb/CSS/PreferredColorScheme.h> #include <LibWeb/CSS/PreferredColorScheme.h>
#include <LibWeb/CSS/PreferredContrast.h> #include <LibWeb/CSS/PreferredContrast.h>
#include <LibWeb/CSS/PreferredMotion.h> #include <LibWeb/CSS/PreferredMotion.h>
@ -117,6 +118,8 @@ endpoint WebContentServer
file_picker_closed(u64 page_id, Vector<Web::HTML::SelectedFile> selected_files) =| file_picker_closed(u64 page_id, Vector<Web::HTML::SelectedFile> selected_files) =|
select_dropdown_closed(u64 page_id, Optional<u32> selected_item_id) =| select_dropdown_closed(u64 page_id, Optional<u32> selected_item_id) =|
retrieved_clipboard_entries(u64 page_id, u64 request_id, Vector<Web::Clipboard::SystemClipboardItem> items) =|
toggle_media_play_state(u64 page_id) =| toggle_media_play_state(u64 page_id) =|
toggle_media_mute_state(u64 page_id) =| toggle_media_mute_state(u64 page_id) =|
toggle_media_loop_state(u64 page_id) =| toggle_media_loop_state(u64 page_id) =|

View file

@ -1064,19 +1064,50 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
totalMatchCount:total_match_count]; totalMatchCount:total_match_count];
}; };
m_web_view_bridge->on_insert_clipboard_entry = [](auto const& data, auto const&, auto const& mime_type) { m_web_view_bridge->on_insert_clipboard_entry = [](Web::Clipboard::SystemClipboardRepresentation const& entry, auto const&) {
NSPasteboardType pasteboard_type = nil; NSPasteboardType pasteboard_type = nil;
// https://w3c.github.io/clipboard-apis/#os-specific-well-known-format // https://w3c.github.io/clipboard-apis/#os-specific-well-known-format
if (mime_type == "text/plain"sv) if (entry.mime_type == "text/plain"sv)
pasteboard_type = NSPasteboardTypeString; pasteboard_type = NSPasteboardTypeString;
else if (mime_type == "text/html"sv) else if (entry.mime_type == "text/html"sv)
pasteboard_type = NSPasteboardTypeHTML; pasteboard_type = NSPasteboardTypeHTML;
else if (mime_type == "text/png"sv) else if (entry.mime_type == "image/png"sv)
pasteboard_type = NSPasteboardTypePNG; pasteboard_type = NSPasteboardTypePNG;
if (pasteboard_type) if (pasteboard_type)
copy_data_to_clipboard(data, pasteboard_type); copy_data_to_clipboard(entry.data, pasteboard_type);
};
m_web_view_bridge->on_request_clipboard_entries = [weak_self](auto request_id) {
LadybirdWebView* self = weak_self;
if (self == nil) {
return;
}
Vector<Web::Clipboard::SystemClipboardItem> items;
Vector<Web::Clipboard::SystemClipboardRepresentation> representations;
auto* pasteBoard = [NSPasteboard generalPasteboard];
for (NSPasteboardType type : [pasteBoard types]) {
String mime_type;
if (type == NSPasteboardTypeString)
mime_type = "text/plain"_string;
else if (type == NSPasteboardTypeHTML)
mime_type = "text/html"_string;
else if (type == NSPasteboardTypePNG)
mime_type = "image/png"_string;
auto data = Ladybird::ns_data_to_string([pasteBoard dataForType:type]);
representations.empend(move(data), move(mime_type));
}
if (!representations.is_empty())
items.empend(AK::move(representations));
m_web_view_bridge->retrieved_clipboard_entries(request_id, items);
}; };
m_web_view_bridge->on_audio_play_state_changed = [weak_self](auto play_state) { m_web_view_bridge->on_audio_play_state_changed = [weak_self](auto play_state) {

View file

@ -23,6 +23,7 @@ String ns_string_to_string(NSString*);
ByteString ns_string_to_byte_string(NSString*); ByteString ns_string_to_byte_string(NSString*);
NSString* string_to_ns_string(StringView); NSString* string_to_ns_string(StringView);
ByteString ns_data_to_string(NSData*);
NSData* string_to_ns_data(StringView); NSData* string_to_ns_data(StringView);
NSDictionary* deserialize_json_to_dictionary(StringView); NSDictionary* deserialize_json_to_dictionary(StringView);

View file

@ -28,6 +28,11 @@ NSString* string_to_ns_string(StringView string)
return [[NSString alloc] initWithData:string_to_ns_data(string) encoding:NSUTF8StringEncoding]; return [[NSString alloc] initWithData:string_to_ns_data(string) encoding:NSUTF8StringEncoding];
} }
ByteString ns_data_to_string(NSData* data)
{
return { reinterpret_cast<char const*>([data bytes]), [data length] };
}
NSData* string_to_ns_data(StringView string) NSData* string_to_ns_data(StringView string)
{ {
return [NSData dataWithBytes:string.characters_without_null_termination() length:string.length()]; return [NSData dataWithBytes:string.characters_without_null_termination() length:string.length()];

View file

@ -13,6 +13,11 @@ AK::ByteString ak_byte_string_from_qstring(QString const& qstring)
return AK::ByteString(utf8_data.data(), utf8_data.size()); return AK::ByteString(utf8_data.data(), utf8_data.size());
} }
AK::ByteString ak_byte_string_from_qbytearray(QByteArray const& qbytearray)
{
return AK::ByteString(qbytearray.data(), qbytearray.size());
}
String ak_string_from_qstring(QString const& qstring) String ak_string_from_qstring(QString const& qstring)
{ {
auto utf8_data = qstring.toUtf8(); auto utf8_data = qstring.toUtf8();
@ -24,6 +29,11 @@ QString qstring_from_ak_string(StringView ak_string)
return QString::fromUtf8(ak_string.characters_without_null_termination(), static_cast<qsizetype>(ak_string.length())); return QString::fromUtf8(ak_string.characters_without_null_termination(), static_cast<qsizetype>(ak_string.length()));
} }
QByteArray qbytearray_from_ak_string(StringView ak_string)
{
return { ak_string.characters_without_null_termination(), static_cast<qsizetype>(ak_string.length()) };
}
Optional<URL::URL> ak_url_from_qstring(QString const& qstring) Optional<URL::URL> ak_url_from_qstring(QString const& qstring)
{ {
auto utf8_data = qstring.toUtf8(); auto utf8_data = qstring.toUtf8();

View file

@ -13,11 +13,16 @@
#include <AK/StringView.h> #include <AK/StringView.h>
#include <LibURL/URL.h> #include <LibURL/URL.h>
#include <QByteArray>
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
AK::ByteString ak_byte_string_from_qstring(QString const&); AK::ByteString ak_byte_string_from_qstring(QString const&);
AK::ByteString ak_byte_string_from_qbytearray(QByteArray const&);
String ak_string_from_qstring(QString const&); String ak_string_from_qstring(QString const&);
QString qstring_from_ak_string(StringView); QString qstring_from_ak_string(StringView);
QByteArray qbytearray_from_ak_string(StringView);
Optional<URL::URL> ak_url_from_qstring(QString const&); Optional<URL::URL> ak_url_from_qstring(QString const&);
URL::URL ak_url_from_qurl(QUrl const&); URL::URL ak_url_from_qurl(QUrl const&);

View file

@ -369,16 +369,39 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
view().did_update_window_rect(); view().did_update_window_rect();
}; };
view().on_insert_clipboard_entry = [](auto const& data, auto const&, auto const& mime_type) { view().on_insert_clipboard_entry = [](Web::Clipboard::SystemClipboardRepresentation const& entry, auto const&) {
QByteArray qdata { data.bytes_as_string_view().characters_without_null_termination(), static_cast<qsizetype>(data.bytes_as_string_view().length()) };
auto* mime_data = new QMimeData(); auto* mime_data = new QMimeData();
mime_data->setData(qstring_from_ak_string(mime_type), qdata); mime_data->setData(qstring_from_ak_string(entry.mime_type), qbytearray_from_ak_string(entry.data));
auto* clipboard = QGuiApplication::clipboard(); auto* clipboard = QGuiApplication::clipboard();
clipboard->setMimeData(mime_data); clipboard->setMimeData(mime_data);
}; };
view().on_request_clipboard_entries = [this](auto request_id) {
auto const* clipboard = QGuiApplication::clipboard();
auto const* mime_data = clipboard->mimeData();
if (!mime_data) {
view().retrieved_clipboard_entries(request_id, {});
return;
}
Vector<Web::Clipboard::SystemClipboardItem> items;
Vector<Web::Clipboard::SystemClipboardRepresentation> representations;
for (auto const& format : mime_data->formats()) {
auto data = ak_byte_string_from_qbytearray(mime_data->data(format));
auto mime_type = ak_string_from_qstring(format);
representations.empend(AK::move(data), AK::move(mime_type));
}
if (!representations.is_empty())
items.empend(AK::move(representations));
view().retrieved_clipboard_entries(request_id, items);
};
view().on_audio_play_state_changed = [this](auto play_state) { view().on_audio_play_state_changed = [this](auto play_state) {
emit audio_play_state_changed(tab_index(), play_state); emit audio_play_state_changed(tab_index(), play_state);
}; };