DataTransfer.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /*
  2. * Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Enumerate.h>
  7. #include <AK/Find.h>
  8. #include <LibJS/Runtime/Realm.h>
  9. #include <LibWeb/Bindings/DataTransferPrototype.h>
  10. #include <LibWeb/Bindings/Intrinsics.h>
  11. #include <LibWeb/FileAPI/Blob.h>
  12. #include <LibWeb/FileAPI/File.h>
  13. #include <LibWeb/FileAPI/FileList.h>
  14. #include <LibWeb/HTML/DataTransfer.h>
  15. #include <LibWeb/HTML/DataTransferItem.h>
  16. #include <LibWeb/HTML/DataTransferItemList.h>
  17. #include <LibWeb/Infra/Strings.h>
  18. namespace Web::HTML {
  19. GC_DEFINE_ALLOCATOR(DataTransfer);
  20. namespace DataTransferEffect {
  21. #define __ENUMERATE_DATA_TRANSFER_EFFECT(name) FlyString name = #name##_fly_string;
  22. ENUMERATE_DATA_TRANSFER_EFFECTS
  23. #undef __ENUMERATE_DATA_TRANSFER_EFFECT
  24. }
  25. GC::Ref<DataTransfer> DataTransfer::create(JS::Realm& realm, NonnullRefPtr<DragDataStore> drag_data_store)
  26. {
  27. return realm.create<DataTransfer>(realm, move(drag_data_store));
  28. }
  29. // https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer
  30. GC::Ref<DataTransfer> DataTransfer::construct_impl(JS::Realm& realm)
  31. {
  32. // 1. Set the drag data store's item list to be an empty list.
  33. auto drag_data_store = DragDataStore::create();
  34. // 2. Set the drag data store's mode to read/write mode.
  35. drag_data_store->set_mode(DragDataStore::Mode::ReadWrite);
  36. // 3. Set the dropEffect and effectAllowed to "none".
  37. // NOTE: This is done by the default-initializers.
  38. return realm.create<DataTransfer>(realm, move(drag_data_store));
  39. }
  40. DataTransfer::DataTransfer(JS::Realm& realm, NonnullRefPtr<DragDataStore> drag_data_store)
  41. : PlatformObject(realm)
  42. , m_associated_drag_data_store(move(drag_data_store))
  43. {
  44. for (auto const& [i, item] : enumerate(m_associated_drag_data_store->item_list())) {
  45. auto data_transfer_item = DataTransferItem::create(realm, *this, i);
  46. m_item_list.append(data_transfer_item);
  47. }
  48. update_data_transfer_types_list();
  49. }
  50. DataTransfer::~DataTransfer() = default;
  51. void DataTransfer::initialize(JS::Realm& realm)
  52. {
  53. Base::initialize(realm);
  54. WEB_SET_PROTOTYPE_FOR_INTERFACE(DataTransfer);
  55. }
  56. void DataTransfer::visit_edges(JS::Cell::Visitor& visitor)
  57. {
  58. Base::visit_edges(visitor);
  59. visitor.visit(m_items);
  60. visitor.visit(m_item_list);
  61. }
  62. void DataTransfer::set_drop_effect(String const& drop_effect)
  63. {
  64. set_drop_effect(FlyString { drop_effect });
  65. }
  66. void DataTransfer::set_drop_effect(FlyString drop_effect)
  67. {
  68. using namespace DataTransferEffect;
  69. // On setting, if the new value is one of "none", "copy", "link", or "move", then the attribute's current value must
  70. // be set to the new value. Other values must be ignored.
  71. if (drop_effect.is_one_of(none, copy, link, move))
  72. m_drop_effect = AK::move(drop_effect);
  73. }
  74. void DataTransfer::set_effect_allowed(String const& effect_allowed)
  75. {
  76. set_effect_allowed(FlyString { effect_allowed });
  77. }
  78. void DataTransfer::set_effect_allowed(FlyString effect_allowed)
  79. {
  80. // On setting, if drag data store's mode is the read/write mode and the new value is one of "none", "copy", "copyLink",
  81. // "copyMove", "link", "linkMove", "move", "all", or "uninitialized", then the attribute's current value must be set
  82. // to the new value. Otherwise, it must be left unchanged.
  83. if (m_associated_drag_data_store && m_associated_drag_data_store->mode() == DragDataStore::Mode::ReadWrite)
  84. set_effect_allowed_internal(move(effect_allowed));
  85. }
  86. void DataTransfer::set_effect_allowed_internal(FlyString effect_allowed)
  87. {
  88. // AD-HOC: We need to be able to set the effectAllowed attribute internally regardless of the state of the drag data store.
  89. using namespace DataTransferEffect;
  90. if (effect_allowed.is_one_of(none, copy, copyLink, copyMove, link, linkMove, move, all, uninitialized))
  91. m_effect_allowed = AK::move(effect_allowed);
  92. }
  93. // https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-items
  94. GC::Ref<DataTransferItemList> DataTransfer::items()
  95. {
  96. // The items attribute must return a DataTransferItemList object associated with the DataTransfer object.
  97. if (!m_items)
  98. m_items = DataTransferItemList::create(realm(), *this);
  99. return *m_items;
  100. }
  101. // https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-types
  102. ReadonlySpan<String> DataTransfer::types() const
  103. {
  104. // The types attribute must return this DataTransfer object's types array.
  105. return m_types;
  106. }
  107. // https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-getdata
  108. String DataTransfer::get_data(String const& format_argument) const
  109. {
  110. // 1. If the DataTransfer object is no longer associated with a drag data store, then return the empty string.
  111. if (!m_associated_drag_data_store)
  112. return {};
  113. // 2. If the drag data store's mode is the protected mode, then return the empty string.
  114. if (m_associated_drag_data_store->mode() == DragDataStore::Mode::Protected)
  115. return {};
  116. // 3. Let format be the first argument, converted to ASCII lowercase.
  117. auto format = format_argument.to_ascii_lowercase();
  118. // 4. Let convert-to-URL be false.
  119. [[maybe_unused]] bool convert_to_url = false;
  120. // 5. If format equals "text", change it to "text/plain".
  121. if (format == "text"sv) {
  122. format = "text/plain"_string;
  123. }
  124. // 6. If format equals "url", change it to "text/uri-list" and set convert-to-URL to true.
  125. else if (format == "url"sv) {
  126. format = "text/uri-list"_string;
  127. convert_to_url = true;
  128. }
  129. // 7. If there is no item in the drag data store item list whose kind is text and whose type string is equal to
  130. // format, return the empty string.
  131. auto item_list = m_associated_drag_data_store->item_list();
  132. auto it = find_if(item_list.begin(), item_list.end(), [&](auto const& item) {
  133. return item.kind == DragDataStoreItem::Kind::Text && item.type_string == format;
  134. });
  135. if (it == item_list.end())
  136. return {};
  137. // 8. Let result be the data of the item in the drag data store item list whose kind is Plain Unicode string and
  138. // whose type string is equal to format.
  139. auto const& result = it->data;
  140. // FIXME: 9. If convert-to-URL is true, then parse result as appropriate for text/uri-list data, and then set result to
  141. // the first URL from the list, if any, or the empty string otherwise.
  142. // 10. Return result.
  143. return MUST(String::from_utf8(result));
  144. }
  145. // https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-files
  146. GC::Ref<FileAPI::FileList> DataTransfer::files() const
  147. {
  148. auto& realm = this->realm();
  149. // 1. Start with an empty list L.
  150. auto files = FileAPI::FileList::create(realm);
  151. // 2. If the DataTransfer object is no longer associated with a drag data store, the FileList is empty. Return
  152. // the empty list L.
  153. if (!m_associated_drag_data_store)
  154. return files;
  155. // 3. If the drag data store's mode is the protected mode, return the empty list L.
  156. if (m_associated_drag_data_store->mode() == DragDataStore::Mode::Protected)
  157. return files;
  158. // 4. For each item in the drag data store item list whose kind is File, add the item's data (the file, in
  159. // particular its name and contents, as well as its type) to the list L.
  160. for (auto const& item : m_associated_drag_data_store->item_list()) {
  161. if (item.kind != DragDataStoreItem::Kind::File)
  162. continue;
  163. auto blob = FileAPI::Blob::create(realm, item.data, item.type_string);
  164. // FIXME: The FileAPI should use ByteString for file names.
  165. auto file_name = MUST(String::from_byte_string(item.file_name));
  166. // FIXME: Fill in other fields (e.g. last_modified).
  167. FileAPI::FilePropertyBag options {};
  168. options.type = item.type_string;
  169. auto file = MUST(FileAPI::File::create(realm, { GC::make_root(blob) }, file_name, move(options)));
  170. files->add_file(file);
  171. }
  172. // 5. The files found by these steps are those in the list L.
  173. return files;
  174. }
  175. Optional<DragDataStore::Mode> DataTransfer::mode() const
  176. {
  177. if (m_associated_drag_data_store)
  178. return m_associated_drag_data_store->mode();
  179. return {};
  180. }
  181. void DataTransfer::disassociate_with_drag_data_store()
  182. {
  183. m_associated_drag_data_store.clear();
  184. update_data_transfer_types_list();
  185. }
  186. GC::Ref<DataTransferItem> DataTransfer::add_item(DragDataStoreItem item)
  187. {
  188. auto& realm = this->realm();
  189. VERIFY(m_associated_drag_data_store);
  190. m_associated_drag_data_store->add_item(move(item));
  191. auto data_transfer_item = DataTransferItem::create(realm, *this, m_associated_drag_data_store->size() - 1);
  192. m_item_list.append(data_transfer_item);
  193. update_data_transfer_types_list();
  194. return data_transfer_item;
  195. }
  196. bool DataTransfer::contains_item_with_type(DragDataStoreItem::Kind kind, String const& type) const
  197. {
  198. VERIFY(m_associated_drag_data_store);
  199. for (auto const& item : m_associated_drag_data_store->item_list()) {
  200. if (item.kind == kind && item.type_string.equals_ignoring_ascii_case(type))
  201. return true;
  202. }
  203. return false;
  204. }
  205. GC::Ref<DataTransferItem> DataTransfer::item(size_t index) const
  206. {
  207. VERIFY(index < m_item_list.size());
  208. return m_item_list[index];
  209. }
  210. DragDataStoreItem const& DataTransfer::drag_data(size_t index) const
  211. {
  212. VERIFY(m_associated_drag_data_store);
  213. VERIFY(index < m_item_list.size());
  214. return m_associated_drag_data_store->item_list()[index];
  215. }
  216. size_t DataTransfer::length() const
  217. {
  218. if (m_associated_drag_data_store)
  219. return m_associated_drag_data_store->size();
  220. return 0;
  221. }
  222. // https://html.spec.whatwg.org/multipage/dnd.html#concept-datatransfer-types
  223. void DataTransfer::update_data_transfer_types_list()
  224. {
  225. // 1. Let L be an empty sequence.
  226. Vector<String> types;
  227. // 2. If the DataTransfer object is still associated with a drag data store, then:
  228. if (m_associated_drag_data_store) {
  229. bool contains_file = false;
  230. // 1. For each item in the DataTransfer object's drag data store item list whose kind is text, add an entry to L
  231. // consisting of the item's type string.
  232. for (auto const& item : m_associated_drag_data_store->item_list()) {
  233. switch (item.kind) {
  234. case DragDataStoreItem::Kind::Text:
  235. types.append(item.type_string);
  236. break;
  237. case DragDataStoreItem::Kind::File:
  238. contains_file = true;
  239. break;
  240. }
  241. }
  242. // 2. If there are any items in the DataTransfer object's drag data store item list whose kind is File, then add
  243. // an entry to L consisting of the string "Files". (This value can be distinguished from the other values
  244. // because it is not lowercase.)
  245. if (contains_file)
  246. types.append("Files"_string);
  247. }
  248. // 3. Set the DataTransfer object's types array to the result of creating a frozen array from L.
  249. m_types = move(types);
  250. }
  251. }