diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h
index f615e9a603..9c17220c29 100644
--- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h
+++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h
@@ -14,6 +14,7 @@ namespace IDL {
static constexpr Array libweb_interface_namespaces = {
"CSS"sv,
+ "Clipboard"sv,
"Crypto"sv,
"DOM"sv,
"DOMParsing"sv,
diff --git a/Tests/LibWeb/Text/expected/clipboard.txt b/Tests/LibWeb/Text/expected/clipboard.txt
new file mode 100644
index 0000000000..9c380cba5b
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/clipboard.txt
@@ -0,0 +1,2 @@
+ Failure
+Success
diff --git a/Tests/LibWeb/Text/input/clipboard.html b/Tests/LibWeb/Text/input/clipboard.html
new file mode 100644
index 0000000000..dfc6d391db
--- /dev/null
+++ b/Tests/LibWeb/Text/input/clipboard.html
@@ -0,0 +1,26 @@
+
+
+
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt
index aa16ed53ee..18bc55b29c 100644
--- a/Userland/Libraries/LibWeb/CMakeLists.txt
+++ b/Userland/Libraries/LibWeb/CMakeLists.txt
@@ -20,6 +20,7 @@ set(SOURCES
Bindings/MainThreadVM.cpp
Bindings/OptionConstructor.cpp
Bindings/PlatformObject.cpp
+ Clipboard/Clipboard.cpp
Crypto/Crypto.cpp
Crypto/SubtleCrypto.cpp
CSS/Angle.cpp
diff --git a/Userland/Libraries/LibWeb/Clipboard/Clipboard.cpp b/Userland/Libraries/LibWeb/Clipboard/Clipboard.cpp
new file mode 100644
index 0000000000..8a07fbeb4f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Clipboard/Clipboard.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2023, Tim Flynn
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Web::Clipboard {
+
+WebIDL::ExceptionOr> Clipboard::construct_impl(JS::Realm& realm)
+{
+ return realm.heap().allocate(realm, realm);
+}
+
+Clipboard::Clipboard(JS::Realm& realm)
+ : DOM::EventTarget(realm)
+{
+}
+
+Clipboard::~Clipboard() = default;
+
+void Clipboard::initialize(JS::Realm& realm)
+{
+ Base::initialize(realm);
+ set_prototype(&Bindings::ensure_web_prototype(realm, "Clipboard"));
+}
+
+// https://w3c.github.io/clipboard-apis/#os-specific-well-known-format
+static String os_specific_well_known_format(StringView mime_type_string)
+{
+ // NOTE: Here we always takes the Linux case, and defer to the chrome layer to handle OS specific implementations.
+ auto mime_type = MUST(MimeSniff::MimeType::parse(mime_type_string));
+
+ // 1. Let wellKnownFormat be an empty string.
+ String well_known_format {};
+
+ // 2. If mimeType’s essence is "text/plain", then
+ if (mime_type->essence() == "text/plain"sv) {
+ // On Windows, follow the convention described below:
+ // Assign CF_UNICODETEXT to wellKnownFormat.
+ // On MacOS, follow the convention described below:
+ // Assign NSPasteboardTypeString to wellKnownFormat.
+ // On Linux, ChromeOS, and Android, follow the convention described below:
+ // Assign "text/plain" to wellKnownFormat.
+ well_known_format = "text/plain"_string;
+ }
+ // 3. Else, if mimeType’s essence is "text/html", then
+ if (mime_type->essence() == "text/html"sv) {
+ // On Windows, follow the convention described below:
+ // Assign CF_HTML to wellKnownFormat.
+ // On MacOS, follow the convention described below:
+ // Assign NSHTMLPboardType to wellKnownFormat.
+ // On Linux, ChromeOS, and Android, follow the convention described below:
+ // Assign "text/html" to wellKnownFormat.
+ well_known_format = "text/html"_string;
+ }
+ // 4. Else, if mimeType’s essence is "image/png", then
+ if (mime_type->essence() == "image/png"sv) {
+ // On Windows, follow the convention described below:
+ // Assign "PNG" to wellKnownFormat.
+ // On MacOS, follow the convention described below:
+ // Assign NSPasteboardTypePNG to wellKnownFormat.
+ // On Linux, ChromeOS, and Android, follow the convention described below:
+ // Assign "image/png" to wellKnownFormat.
+ well_known_format = "image/png"_string;
+ }
+
+ // 5. Return wellKnownFormat.
+ return well_known_format;
+}
+
+// https://w3c.github.io/clipboard-apis/#write-blobs-and-option-to-the-clipboard
+static void write_blobs_and_option_to_clipboard(JS::Realm& realm, ReadonlySpan> items, String presentation_style)
+{
+ auto& window = verify_cast(realm.global_object());
+
+ // FIXME: 1. Let webCustomFormats be a sequence.
+
+ // 2. For each item in items:
+ for (auto const& item : items) {
+ // 1. Let formatString be the result of running os specific well-known format given item’s type.
+ auto format_string = os_specific_well_known_format(item->type());
+
+ // 2. If formatString is empty then follow the below steps:
+ if (format_string.is_empty()) {
+ // FIXME: 1. Let webCustomFormatString be the item’s type.
+ // FIXME: 2. Let webCustomFormat be an empty type.
+ // FIXME: 3. If webCustomFormatString starts with `"web "` prefix, then remove the `"web "` prefix and store the
+ // FIXME: remaining string in webMimeTypeString.
+ // FIXME: 4. Let webMimeType be the result of parsing a MIME type given webMimeTypeString.
+ // FIXME: 5. If webMimeType is failure, then abort all steps.
+ // FIXME: 6. Let webCustomFormat’s type's essence equal to webMimeType.
+ // FIXME: 7. Set item’s type to webCustomFormat.
+ // FIXME: 8. Append webCustomFormat to webCustomFormats.
+ }
+
+ // 3. Let payload be the result of UTF-8 decoding item’s underlying byte sequence.
+ auto decoder = TextCodec::decoder_for("UTF-8"sv);
+ auto payload = MUST(TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, item->bytes()));
+
+ // 4. Insert payload and presentationStyle into the system clipboard using formatString as the native clipboard format.
+ if (auto* page = window.page())
+ page->client().page_did_insert_clipboard_entry(move(payload), move(presentation_style), move(format_string));
+ }
+
+ // FIXME: 3. Write web custom formats given webCustomFormats.
+}
+
+// https://w3c.github.io/clipboard-apis/#check-clipboard-write-permission
+static bool check_clipboard_write_permission(JS::Realm& realm)
+{
+ // NOTE: The clipboard permission is undergoing a refactor because the clipboard-write permission was removed from
+ // the Permissions spec. So this partially implements the proposed update:
+ // https://pr-preview.s3.amazonaws.com/w3c/clipboard-apis/pull/164.html#write-permission
+
+ // 1. Let hasGesture be true if the relevant global object of this has transient activation, false otherwise.
+ auto has_gesture = verify_cast(realm.global_object()).has_transient_activation();
+
+ // 2. If hasGesture then,
+ if (has_gesture) {
+ // FIXME: 1. Return true if the current script is running as a result of user interaction with a "cut" or "copy"
+ // element created by the user agent or operating system.
+ return true;
+ }
+
+ // 3. Otherwise, return false.
+ return false;
+}
+
+// https://w3c.github.io/clipboard-apis/#dom-clipboard-writetext
+JS::NonnullGCPtr Clipboard::write_text(String data)
+{
+ // 1. Let realm be this's relevant realm.
+ auto& realm = HTML::relevant_realm(*this);
+
+ // 2. Let p be a new promise in realm.
+ auto promise = WebIDL::create_promise(realm);
+
+ // 3. Run the following steps in parallel:
+ Platform::EventLoopPlugin::the().deferred_invoke([&realm, promise, data = move(data)]() mutable {
+ // 1. Let r be the result of running check clipboard write permission.
+ auto result = check_clipboard_write_permission(realm);
+
+ // 2. If r is false, then:
+ if (!result) {
+ // 1. Queue a global task on the permission task source, given realm’s global object, to reject p with
+ // "NotAllowedError" DOMException in realm.
+ queue_global_task(HTML::Task::Source::Permissions, realm.global_object(), [&realm, promise]() mutable {
+ HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm) };
+ WebIDL::reject_promise(realm, promise, WebIDL::NotAllowedError::create(realm, "Clipboard writing is only allowed through user activation"_fly_string));
+ });
+
+ // 2. Abort these steps.
+ return;
+ }
+
+ // 1. Queue a global task on the clipboard task source, given realm’s global object, to perform the below steps:
+ queue_global_task(HTML::Task::Source::Clipboard, realm.global_object(), [&realm, promise, data = move(data)]() mutable {
+ // 1. Let itemList be an empty sequence.
+ Vector> item_list;
+
+ // 2. Let textBlob be a new Blob created with: type attribute set to "text/plain;charset=utf-8", and its
+ // underlying byte sequence set to the UTF-8 encoding of data.
+ // Note: On Windows replace `\n` characters with `\r\n` in data before creating textBlob.
+ auto text_blob = FileAPI::Blob::create(realm, MUST(ByteBuffer::copy(data.bytes())), "text/plain;charset=utf-8"_string);
+
+ // 3. Add textBlob to itemList.
+ item_list.append(text_blob);
+
+ // 4. Let option be set to "unspecified".
+ auto option = "unspecified"_string;
+
+ // 5. Write blobs and option to the clipboard with itemList and option.
+ write_blobs_and_option_to_clipboard(realm, item_list, move(option));
+
+ // 6. Resolve p.
+ HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm) };
+ WebIDL::resolve_promise(realm, promise, JS::js_undefined());
+ });
+ });
+
+ // 4. Return p.
+ return JS::NonnullGCPtr { verify_cast(*promise->promise()) };
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/Clipboard/Clipboard.h b/Userland/Libraries/LibWeb/Clipboard/Clipboard.h
new file mode 100644
index 0000000000..21553d61b3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Clipboard/Clipboard.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2023, Tim Flynn
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Web::Clipboard {
+
+class Clipboard final : public DOM::EventTarget {
+ WEB_PLATFORM_OBJECT(Clipboard, DOM::EventTarget);
+
+public:
+ static WebIDL::ExceptionOr> construct_impl(JS::Realm&);
+ virtual ~Clipboard() override;
+
+ JS::NonnullGCPtr write_text(String);
+
+private:
+ Clipboard(JS::Realm&);
+
+ virtual void initialize(JS::Realm&) override;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Clipboard/Clipboard.idl b/Userland/Libraries/LibWeb/Clipboard/Clipboard.idl
new file mode 100644
index 0000000000..f902736c13
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Clipboard/Clipboard.idl
@@ -0,0 +1,12 @@
+#import
+
+// FIXME: typedef sequence ClipboardItems;
+
+// https://w3c.github.io/clipboard-apis/#clipboard
+[SecureContext, Exposed=Window]
+interface Clipboard : EventTarget {
+ // FIXME: Promise read();
+ // FIXME: Promise readText();
+ // FIXME: Promise write(ClipboardItems data);
+ Promise writeText(DOMString data);
+};
diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h
index 42b33c64e7..6cfb0ddf5e 100644
--- a/Userland/Libraries/LibWeb/Forward.h
+++ b/Userland/Libraries/LibWeb/Forward.h
@@ -65,6 +65,10 @@ enum class ResponseType;
enum class XMLHttpRequestResponseType;
}
+namespace Web::Clipboard {
+class Clipboard;
+}
+
namespace Web::Cookie {
struct Cookie;
struct ParsedCookie;
diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h b/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h
index 2bfb7f4fdc..5a7e88264c 100644
--- a/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h
+++ b/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h
@@ -47,6 +47,12 @@ public:
// https://html.spec.whatwg.org/multipage/canvas.html#canvas-blob-serialisation-task-source
CanvasBlobSerializationTask,
+ // https://w3c.github.io/clipboard-apis/#clipboard-task-source
+ Clipboard,
+
+ // https://w3c.github.io/permissions/#permissions-task-source
+ Permissions,
+
// !!! IMPORTANT: Keep this field last!
// This serves as the base value of all unique task sources.
// Some elements, such as the HTMLMediaElement, must have a unique task source per instance.
diff --git a/Userland/Libraries/LibWeb/HTML/Navigator.cpp b/Userland/Libraries/LibWeb/HTML/Navigator.cpp
index f24b3dfb32..5c5029a854 100644
--- a/Userland/Libraries/LibWeb/HTML/Navigator.cpp
+++ b/Userland/Libraries/LibWeb/HTML/Navigator.cpp
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -57,6 +58,7 @@ void Navigator::visit_edges(Cell::Visitor& visitor)
Base::visit_edges(visitor);
visitor.visit(m_mime_type_array);
visitor.visit(m_plugin_array);
+ visitor.visit(m_clipboard);
}
JS::NonnullGCPtr Navigator::mime_types()
@@ -73,4 +75,11 @@ JS::NonnullGCPtr Navigator::plugins()
return *m_plugin_array;
}
+JS::NonnullGCPtr Navigator::clipboard()
+{
+ if (!m_clipboard)
+ m_clipboard = heap().allocate(realm(), realm());
+ return *m_clipboard;
+}
+
}
diff --git a/Userland/Libraries/LibWeb/HTML/Navigator.h b/Userland/Libraries/LibWeb/HTML/Navigator.h
index 4d2722ae0d..a89539d068 100644
--- a/Userland/Libraries/LibWeb/HTML/Navigator.h
+++ b/Userland/Libraries/LibWeb/HTML/Navigator.h
@@ -45,6 +45,7 @@ public:
[[nodiscard]] JS::NonnullGCPtr mime_types();
[[nodiscard]] JS::NonnullGCPtr plugins();
+ [[nodiscard]] JS::NonnullGCPtr clipboard();
virtual ~Navigator() override;
@@ -58,6 +59,9 @@ private:
JS::GCPtr m_plugin_array;
JS::GCPtr m_mime_type_array;
+
+ // https://w3c.github.io/clipboard-apis/#dom-navigator-clipboard
+ JS::GCPtr m_clipboard;
};
}
diff --git a/Userland/Libraries/LibWeb/HTML/Navigator.idl b/Userland/Libraries/LibWeb/HTML/Navigator.idl
index 6ea3dd23a1..fcf5f3ab2a 100644
--- a/Userland/Libraries/LibWeb/HTML/Navigator.idl
+++ b/Userland/Libraries/LibWeb/HTML/Navigator.idl
@@ -1,3 +1,4 @@
+#import
#import
#import
#import
@@ -10,6 +11,9 @@
[Exposed=Window]
interface Navigator {
// objects implementing this interface also implement the interfaces given below
+
+ // https://w3c.github.io/clipboard-apis/#navigator-interface
+ [SecureContext, SameObject] readonly attribute Clipboard clipboard;
};
// NOTE: As NavigatorContentUtils, NavigatorCookies, NavigatorPlugins, and NavigatorAutomationInformation
diff --git a/Userland/Libraries/LibWeb/Page/Page.h b/Userland/Libraries/LibWeb/Page/Page.h
index 701c3493f1..a68c7a5c77 100644
--- a/Userland/Libraries/LibWeb/Page/Page.h
+++ b/Userland/Libraries/LibWeb/Page/Page.h
@@ -258,6 +258,8 @@ public:
virtual void page_did_change_theme_color(Gfx::Color) { }
+ virtual void page_did_insert_clipboard_entry([[maybe_unused]] String data, [[maybe_unused]] String presentation_style, [[maybe_unused]] String mime_type) { }
+
protected:
virtual ~PageClient() = default;
};
diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake
index c3293ec239..2be955df56 100644
--- a/Userland/Libraries/LibWeb/idl_files.cmake
+++ b/Userland/Libraries/LibWeb/idl_files.cmake
@@ -5,6 +5,7 @@ libweb_js_bindings(Animations/Animation)
libweb_js_bindings(Animations/AnimationEffect)
libweb_js_bindings(Animations/AnimationTimeline)
libweb_js_bindings(Animations/DocumentTimeline)
+libweb_js_bindings(Clipboard/Clipboard)
libweb_js_bindings(Crypto/Crypto)
libweb_js_bindings(Crypto/SubtleCrypto)
libweb_js_bindings(CSS/CSSConditionRule)