Clipboard.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /*
  2. * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibJS/Runtime/Realm.h>
  7. #include <LibTextCodec/Decoder.h>
  8. #include <LibWeb/Bindings/ClipboardPrototype.h>
  9. #include <LibWeb/Bindings/HostDefined.h>
  10. #include <LibWeb/Clipboard/Clipboard.h>
  11. #include <LibWeb/FileAPI/Blob.h>
  12. #include <LibWeb/HTML/Scripting/Environments.h>
  13. #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
  14. #include <LibWeb/HTML/Window.h>
  15. #include <LibWeb/MimeSniff/MimeType.h>
  16. #include <LibWeb/Page/Page.h>
  17. #include <LibWeb/Platform/EventLoopPlugin.h>
  18. #include <LibWeb/WebIDL/Promise.h>
  19. namespace Web::Clipboard {
  20. JS_DEFINE_ALLOCATOR(Clipboard);
  21. WebIDL::ExceptionOr<JS::NonnullGCPtr<Clipboard>> Clipboard::construct_impl(JS::Realm& realm)
  22. {
  23. return realm.heap().allocate<Clipboard>(realm, realm);
  24. }
  25. Clipboard::Clipboard(JS::Realm& realm)
  26. : DOM::EventTarget(realm)
  27. {
  28. }
  29. Clipboard::~Clipboard() = default;
  30. void Clipboard::initialize(JS::Realm& realm)
  31. {
  32. Base::initialize(realm);
  33. WEB_SET_PROTOTYPE_FOR_INTERFACE(Clipboard);
  34. }
  35. // https://w3c.github.io/clipboard-apis/#os-specific-well-known-format
  36. static String os_specific_well_known_format(StringView mime_type_string)
  37. {
  38. // NOTE: Here we always takes the Linux case, and defer to the chrome layer to handle OS specific implementations.
  39. auto mime_type = MimeSniff::MimeType::parse(mime_type_string);
  40. // 1. Let wellKnownFormat be an empty string.
  41. String well_known_format {};
  42. // 2. If mimeType’s essence is "text/plain", then
  43. if (mime_type->essence() == "text/plain"sv) {
  44. // On Windows, follow the convention described below:
  45. // Assign CF_UNICODETEXT to wellKnownFormat.
  46. // On MacOS, follow the convention described below:
  47. // Assign NSPasteboardTypeString to wellKnownFormat.
  48. // On Linux, ChromeOS, and Android, follow the convention described below:
  49. // Assign "text/plain" to wellKnownFormat.
  50. well_known_format = "text/plain"_string;
  51. }
  52. // 3. Else, if mimeType’s essence is "text/html", then
  53. if (mime_type->essence() == "text/html"sv) {
  54. // On Windows, follow the convention described below:
  55. // Assign CF_HTML to wellKnownFormat.
  56. // On MacOS, follow the convention described below:
  57. // Assign NSHTMLPboardType to wellKnownFormat.
  58. // On Linux, ChromeOS, and Android, follow the convention described below:
  59. // Assign "text/html" to wellKnownFormat.
  60. well_known_format = "text/html"_string;
  61. }
  62. // 4. Else, if mimeType’s essence is "image/png", then
  63. if (mime_type->essence() == "image/png"sv) {
  64. // On Windows, follow the convention described below:
  65. // Assign "PNG" to wellKnownFormat.
  66. // On MacOS, follow the convention described below:
  67. // Assign NSPasteboardTypePNG to wellKnownFormat.
  68. // On Linux, ChromeOS, and Android, follow the convention described below:
  69. // Assign "image/png" to wellKnownFormat.
  70. well_known_format = "image/png"_string;
  71. }
  72. // 5. Return wellKnownFormat.
  73. return well_known_format;
  74. }
  75. // https://w3c.github.io/clipboard-apis/#write-blobs-and-option-to-the-clipboard
  76. static void write_blobs_and_option_to_clipboard(JS::Realm& realm, ReadonlySpan<JS::NonnullGCPtr<FileAPI::Blob>> items, String presentation_style)
  77. {
  78. auto& window = verify_cast<HTML::Window>(realm.global_object());
  79. // FIXME: 1. Let webCustomFormats be a sequence<Blob>.
  80. // 2. For each item in items:
  81. for (auto const& item : items) {
  82. // 1. Let formatString be the result of running os specific well-known format given item’s type.
  83. auto format_string = os_specific_well_known_format(item->type());
  84. // 2. If formatString is empty then follow the below steps:
  85. if (format_string.is_empty()) {
  86. // FIXME: 1. Let webCustomFormatString be the item’s type.
  87. // FIXME: 2. Let webCustomFormat be an empty type.
  88. // FIXME: 3. If webCustomFormatString starts with `"web "` prefix, then remove the `"web "` prefix and store the
  89. // FIXME: remaining string in webMimeTypeString.
  90. // FIXME: 4. Let webMimeType be the result of parsing a MIME type given webMimeTypeString.
  91. // FIXME: 5. If webMimeType is failure, then abort all steps.
  92. // FIXME: 6. Let webCustomFormat’s type's essence equal to webMimeType.
  93. // FIXME: 7. Set item’s type to webCustomFormat.
  94. // FIXME: 8. Append webCustomFormat to webCustomFormats.
  95. }
  96. // 3. Let payload be the result of UTF-8 decoding item’s underlying byte sequence.
  97. auto decoder = TextCodec::decoder_for("UTF-8"sv);
  98. auto payload = MUST(TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, item->raw_bytes()));
  99. // 4. Insert payload and presentationStyle into the system clipboard using formatString as the native clipboard format.
  100. window.page().client().page_did_insert_clipboard_entry(move(payload), move(presentation_style), move(format_string));
  101. }
  102. // FIXME: 3. Write web custom formats given webCustomFormats.
  103. }
  104. // https://w3c.github.io/clipboard-apis/#check-clipboard-write-permission
  105. static bool check_clipboard_write_permission(JS::Realm& realm)
  106. {
  107. // NOTE: The clipboard permission is undergoing a refactor because the clipboard-write permission was removed from
  108. // the Permissions spec. So this partially implements the proposed update:
  109. // https://pr-preview.s3.amazonaws.com/w3c/clipboard-apis/pull/164.html#write-permission
  110. // 1. Let hasGesture be true if the relevant global object of this has transient activation, false otherwise.
  111. auto has_gesture = verify_cast<HTML::Window>(realm.global_object()).has_transient_activation();
  112. // 2. If hasGesture then,
  113. if (has_gesture) {
  114. // FIXME: 1. Return true if the current script is running as a result of user interaction with a "cut" or "copy"
  115. // element created by the user agent or operating system.
  116. return true;
  117. }
  118. // 3. Otherwise, return false.
  119. return false;
  120. }
  121. // https://w3c.github.io/clipboard-apis/#dom-clipboard-writetext
  122. JS::NonnullGCPtr<WebIDL::Promise> Clipboard::write_text(String data)
  123. {
  124. // 1. Let realm be this's relevant realm.
  125. auto& realm = HTML::relevant_realm(*this);
  126. // 2. Let p be a new promise in realm.
  127. auto promise = WebIDL::create_promise(realm);
  128. // 3. Run the following steps in parallel:
  129. Platform::EventLoopPlugin::the().deferred_invoke(JS::create_heap_function(realm.heap(), [&realm, promise, data = move(data)]() mutable {
  130. // 1. Let r be the result of running check clipboard write permission.
  131. auto result = check_clipboard_write_permission(realm);
  132. // 2. If r is false, then:
  133. if (!result) {
  134. // 1. Queue a global task on the permission task source, given realm’s global object, to reject p with
  135. // "NotAllowedError" DOMException in realm.
  136. queue_global_task(HTML::Task::Source::Permissions, realm.global_object(), JS::create_heap_function(realm.heap(), [&realm, promise]() mutable {
  137. HTML::TemporaryExecutionContext execution_context { realm };
  138. WebIDL::reject_promise(realm, promise, WebIDL::NotAllowedError::create(realm, "Clipboard writing is only allowed through user activation"_string));
  139. }));
  140. // 2. Abort these steps.
  141. return;
  142. }
  143. // 1. Queue a global task on the clipboard task source, given realm’s global object, to perform the below steps:
  144. queue_global_task(HTML::Task::Source::Clipboard, realm.global_object(), JS::create_heap_function(realm.heap(), [&realm, promise, data = move(data)]() mutable {
  145. // 1. Let itemList be an empty sequence<Blob>.
  146. Vector<JS::NonnullGCPtr<FileAPI::Blob>> item_list;
  147. // 2. Let textBlob be a new Blob created with: type attribute set to "text/plain;charset=utf-8", and its
  148. // underlying byte sequence set to the UTF-8 encoding of data.
  149. // Note: On Windows replace `\n` characters with `\r\n` in data before creating textBlob.
  150. auto text_blob = FileAPI::Blob::create(realm, MUST(ByteBuffer::copy(data.bytes())), "text/plain;charset=utf-8"_string);
  151. // 3. Add textBlob to itemList.
  152. item_list.append(text_blob);
  153. // 4. Let option be set to "unspecified".
  154. auto option = "unspecified"_string;
  155. // 5. Write blobs and option to the clipboard with itemList and option.
  156. write_blobs_and_option_to_clipboard(realm, item_list, move(option));
  157. // 6. Resolve p.
  158. HTML::TemporaryExecutionContext execution_context { realm };
  159. WebIDL::resolve_promise(realm, promise, JS::js_undefined());
  160. }));
  161. }));
  162. // 4. Return p.
  163. return promise;
  164. }
  165. }