DragAndDropEventHandler.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. /*
  2. * Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/ScopeGuard.h>
  7. #include <LibWeb/HTML/DragEvent.h>
  8. #include <LibWeb/HTML/EventNames.h>
  9. #include <LibWeb/HTML/HTMLInputElement.h>
  10. #include <LibWeb/HTML/HTMLTextAreaElement.h>
  11. #include <LibWeb/HTML/SelectedFile.h>
  12. #include <LibWeb/MimeSniff/Resource.h>
  13. #include <LibWeb/Page/DragAndDropEventHandler.h>
  14. namespace Web {
  15. void DragAndDropEventHandler::visit_edges(JS::Cell::Visitor& visitor) const
  16. {
  17. visitor.visit(m_source_node);
  18. visitor.visit(m_immediate_user_selection);
  19. visitor.visit(m_current_target_element);
  20. }
  21. // https://html.spec.whatwg.org/multipage/dnd.html#drag-and-drop-processing-model
  22. EventResult DragAndDropEventHandler::handle_drag_start(
  23. JS::Realm& realm,
  24. CSSPixelPoint screen_position,
  25. CSSPixelPoint page_offset,
  26. CSSPixelPoint client_offset,
  27. CSSPixelPoint offset,
  28. unsigned button,
  29. unsigned buttons,
  30. unsigned modifiers,
  31. Vector<HTML::SelectedFile> files)
  32. {
  33. auto fire_a_drag_and_drop_event = [&](GC::Ptr<DOM::EventTarget> target, FlyString const& name, GC::Ptr<DOM::EventTarget> related_target = nullptr) {
  34. return this->fire_a_drag_and_drop_event(realm, target, name, screen_position, page_offset, client_offset, offset, button, buttons, modifiers, related_target);
  35. };
  36. // 1. Determine what is being dragged, as follows:
  37. //
  38. // FIXME: If the drag operation was invoked on a selection, then it is the selection that is being dragged.
  39. //
  40. // FIXME: Otherwise, if the drag operation was invoked on a Document, it is the first element, going up the ancestor chain,
  41. // starting at the node that the user tried to drag, that has the IDL attribute draggable set to true. If there is
  42. // no such element, then nothing is being dragged; return, the drag-and-drop operation is never started.
  43. //
  44. // Otherwise, the drag operation was invoked outside the user agent's purview. What is being dragged is defined by
  45. // the document or application where the drag was started.
  46. // 2. Create a drag data store. All the DND events fired subsequently by the steps in this section must use this drag
  47. // data store.
  48. m_drag_data_store = HTML::DragDataStore::create();
  49. // 3. Establish which DOM node is the source node, as follows:
  50. //
  51. // FIXME: If it is a selection that is being dragged, then the source node is the Text node that the user started the
  52. // drag on (typically the Text node that the user originally clicked). If the user did not specify a particular
  53. // node, for example if the user just told the user agent to begin a drag of "the selection", then the source
  54. // node is the first Text node containing a part of the selection.
  55. //
  56. // FIXME: Otherwise, if it is an element that is being dragged, then the source node is the element that is being dragged.
  57. //
  58. // Otherwise, the source node is part of another document or application. When this specification requires that
  59. // an event be dispatched at the source node in this case, the user agent must instead follow the platform-specific
  60. // conventions relevant to that situation.
  61. m_source_node = nullptr;
  62. // FIXME: 4. Determine the list of dragged nodes, as follows:
  63. //
  64. // If it is a selection that is being dragged, then the list of dragged nodes contains, in tree order, every node
  65. // that is partially or completely included in the selection (including all their ancestors).
  66. //
  67. // Otherwise, the list of dragged nodes contains only the source node, if any.
  68. // 5. If it is a selection that is being dragged, then add an item to the drag data store item list, with its
  69. // properties set as follows:
  70. //
  71. // The drag data item type string
  72. // "text/plain"
  73. // The drag data item kind
  74. // Text
  75. // The actual data
  76. // The text of the selection
  77. //
  78. // Otherwise, if any files are being dragged, then add one item per file to the drag data store item list, with
  79. // their properties set as follows:
  80. //
  81. // The drag data item type string
  82. // The MIME type of the file, if known, or "application/octet-stream" otherwise.
  83. // The drag data item kind
  84. // File
  85. // The actual data
  86. // The file's contents and name.
  87. for (auto& file : files) {
  88. auto contents = file.take_contents();
  89. auto mime_type = MimeSniff::Resource::sniff(contents);
  90. m_drag_data_store->add_item({
  91. .kind = HTML::DragDataStoreItem::Kind::File,
  92. .type_string = mime_type.essence(),
  93. .data = move(contents),
  94. .file_name = file.name(),
  95. });
  96. }
  97. // FIXME: 6. If the list of dragged nodes is not empty, then extract the microdata from those nodes into a JSON form, and
  98. // add one item to the drag data store item list, with its properties set as follows:
  99. //
  100. // The drag data item type string
  101. // application/microdata+json
  102. // The drag data item kind
  103. // Text
  104. // The actual data
  105. // The resulting JSON string.
  106. // FIXME: 7. Run the following substeps:
  107. [&]() {
  108. // 1. Let urls be « ».
  109. // 2. For each node in the list of dragged nodes:
  110. //
  111. // If the node is an a element with an href attribute
  112. // Add to urls the result of encoding-parsing-and-serializing a URL given the element's href content
  113. // attribute's value, relative to the element's node document.
  114. // If the node is an img element with a src attribute
  115. // Add to urls the result of encoding-parsing-and-serializing a URL given the element's src content
  116. // attribute's value, relative to the element's node document.
  117. // 3. If urls is still empty, then return.
  118. // 4. Let url string be the result of concatenating the strings in urls, in the order they were added, separated
  119. // by a U+000D CARRIAGE RETURN U+000A LINE FEED character pair (CRLF).
  120. // 5. Add one item to the drag data store item list, with its properties set as follows:
  121. //
  122. // The drag data item type string
  123. // text/uri-list
  124. // The drag data item kind
  125. // Text
  126. // The actual data
  127. // url string
  128. }();
  129. // FIXME: 8. Update the drag data store default feedback as appropriate for the user agent (if the user is dragging the
  130. // selection, then the selection would likely be the basis for this feedback; if the user is dragging an element,
  131. // then that element's rendering would be used; if the drag began outside the user agent, then the platform
  132. // conventions for determining the drag feedback should be used).
  133. // 9. Fire a DND event named dragstart at the source node.
  134. auto drag_event = fire_a_drag_and_drop_event(m_source_node, HTML::EventNames::dragstart);
  135. // If the event is canceled, then the drag-and-drop operation should not occur; return.
  136. if (drag_event->cancelled()) {
  137. reset();
  138. return EventResult::Cancelled;
  139. }
  140. // FIXME: 10. Fire a pointer event at the source node named pointercancel, and fire any other follow-up events as
  141. // required by Pointer Events.
  142. // 11. Initiate the drag-and-drop operation in a manner consistent with platform conventions, and as described below.
  143. //
  144. // The drag-and-drop feedback must be generated from the first of the following sources that is available:
  145. //
  146. // 1. The drag data store bitmap, if any. In this case, the drag data store hot spot coordinate should be
  147. // used as hints for where to put the cursor relative to the resulting image. The values are expressed
  148. // as distances in CSS pixels from the left side and from the top side of the image respectively.
  149. // 2. The drag data store default feedback.
  150. return EventResult::Handled;
  151. }
  152. // https://html.spec.whatwg.org/multipage/dnd.html#drag-and-drop-processing-model:queue-a-task
  153. EventResult DragAndDropEventHandler::handle_drag_move(
  154. JS::Realm& realm,
  155. GC::Ref<DOM::Document> document,
  156. GC::Ref<DOM::Node> node,
  157. CSSPixelPoint screen_position,
  158. CSSPixelPoint page_offset,
  159. CSSPixelPoint client_offset,
  160. CSSPixelPoint offset,
  161. unsigned button,
  162. unsigned buttons,
  163. unsigned modifiers)
  164. {
  165. if (!has_ongoing_drag_and_drop_operation())
  166. return EventResult::Cancelled;
  167. auto fire_a_drag_and_drop_event = [&](GC::Ptr<DOM::EventTarget> target, FlyString const& name, GC::Ptr<DOM::EventTarget> related_target = nullptr) {
  168. return this->fire_a_drag_and_drop_event(realm, target, name, screen_position, page_offset, client_offset, offset, button, buttons, modifiers, related_target);
  169. };
  170. // FIXME: 1. If the user agent is still performing the previous iteration of the sequence (if any) when the next iteration
  171. // becomes due, return for this iteration (effectively "skipping missed frames" of the drag-and-drop operation).
  172. // 2. Fire a DND event named drag at the source node. If this event is canceled, the user agent must set the current
  173. // drag operation to "none" (no drag operation).
  174. auto drag_event = fire_a_drag_and_drop_event(m_source_node, HTML::EventNames::drag);
  175. if (drag_event->cancelled())
  176. m_current_drag_operation = HTML::DataTransferEffect::none;
  177. // 3. If the drag event was not canceled and the user has not ended the drag-and-drop operation, check the state of
  178. // the drag-and-drop operation, as follows:
  179. if (!drag_event->cancelled()) {
  180. GC::Ptr<DOM::Node> previous_target_element = m_current_target_element;
  181. // 1. If the user is indicating a different immediate user selection than during the last iteration (or if this
  182. // is the first iteration), and if this immediate user selection is not the same as the current target element,
  183. // then update the current target element as follows:
  184. if (m_immediate_user_selection != node && node != m_current_target_element) {
  185. m_immediate_user_selection = node;
  186. // -> If the new immediate user selection is null
  187. if (!m_immediate_user_selection) {
  188. // Set the current target element to null also.
  189. m_current_target_element = nullptr;
  190. }
  191. // FIXME: -> If the new immediate user selection is in a non-DOM document or application
  192. else if (false) {
  193. // Set the current target element to the immediate user selection.
  194. m_current_target_element = m_immediate_user_selection;
  195. }
  196. // -> Otherwise
  197. else {
  198. // Fire a DND event named dragenter at the immediate user selection.
  199. auto drag_event = fire_a_drag_and_drop_event(m_immediate_user_selection, HTML::EventNames::dragenter);
  200. // If the event is canceled, then set the current target element to the immediate user selection.
  201. if (drag_event->cancelled()) {
  202. m_current_target_element = m_immediate_user_selection;
  203. }
  204. // Otherwise, run the appropriate step from the following list:
  205. else {
  206. // -> If the immediate user selection is a text control (e.g., textarea, or an input element whose
  207. // type attribute is in the Text state) or an editing host or editable element, and the drag data
  208. // store item list has an item with the drag data item type string "text/plain" and the drag data
  209. // item kind text
  210. if (allow_text_drop(*m_immediate_user_selection)) {
  211. // Set the current target element to the immediate user selection anyway.
  212. m_current_target_element = m_immediate_user_selection;
  213. }
  214. // -> If the immediate user selection is the body element
  215. else if (m_immediate_user_selection == document->body()) {
  216. // Leave the current target element unchanged.
  217. }
  218. // -> Otherwise
  219. else {
  220. // Fire a DND event named dragenter at the body element, if there is one, or at the Document
  221. // object, if not. Then, set the current target element to the body element, regardless of
  222. // whether that event was canceled or not.
  223. DOM::EventTarget* target = document->body();
  224. if (!target)
  225. target = document;
  226. fire_a_drag_and_drop_event(target, HTML::EventNames::dragenter);
  227. m_current_target_element = document->body();
  228. }
  229. }
  230. }
  231. }
  232. // 2. If the previous step caused the current target element to change, and if the previous target element
  233. // was not null or a part of a non-DOM document, then fire a DND event named dragleave at the previous
  234. // target element, with the new current target element as the specific related target.
  235. if (previous_target_element && previous_target_element != m_current_target_element)
  236. fire_a_drag_and_drop_event(previous_target_element, HTML::EventNames::dragleave, m_current_target_element);
  237. // 3. If the current target element is a DOM element, then fire a DND event named dragover at this current
  238. // target element.
  239. if (m_current_target_element && is<DOM::Element>(*m_current_target_element)) {
  240. auto drag_event = fire_a_drag_and_drop_event(m_current_target_element, HTML::EventNames::dragover);
  241. // If the dragover event is not canceled, run the appropriate step from the following list:
  242. if (!drag_event->cancelled()) {
  243. // -> If the current target element is a text control (e.g., textarea, or an input element whose type
  244. // attribute is in the Text state) or an editing host or editable element, and the drag data store
  245. // item list has an item with the drag data item type string "text/plain" and the drag data item kind
  246. // text.
  247. if (allow_text_drop(*m_current_target_element)) {
  248. // Set the current drag operation to either "copy" or "move", as appropriate given the platform
  249. // conventions.
  250. m_current_drag_operation = HTML::DataTransferEffect::copy;
  251. }
  252. // -> Otherwise
  253. else {
  254. // Reset the current drag operation to "none".
  255. m_current_drag_operation = HTML::DataTransferEffect::none;
  256. }
  257. }
  258. // Otherwise (if the dragover event is canceled), set the current drag operation based on the values of the
  259. // effectAllowed and dropEffect attributes of the DragEvent object's dataTransfer object as they stood after
  260. // the event dispatch finished, as per the following table:
  261. else {
  262. auto const& effect_allowed = drag_event->data_transfer()->effect_allowed();
  263. auto const& drop_effect = drag_event->data_transfer()->drop_effect();
  264. // effectAllowed | dropEffect | Drag operation
  265. // ---------------------------------------------------------------------------------------
  266. // "uninitialized", "copy", "copyLink", "copyMove", or "all" | "copy" | "copy"
  267. // "uninitialized", "link", "copyLink", "linkMove", or "all" | "link" | "link"
  268. // "uninitialized", "move", "copyMove", "linkMove", or "all" | "move" | "move"
  269. // Any other case | | "none"
  270. using namespace HTML::DataTransferEffect;
  271. if (effect_allowed.is_one_of(uninitialized, copy, copyLink, copyMove, all) && drop_effect == copy)
  272. m_current_drag_operation = copy;
  273. else if (effect_allowed.is_one_of(uninitialized, link, copyLink, linkMove, all) && drop_effect == link)
  274. m_current_drag_operation = link;
  275. else if (effect_allowed.is_one_of(uninitialized, move, copyMove, linkMove, all) && drop_effect == move)
  276. m_current_drag_operation = move;
  277. else
  278. m_current_drag_operation = none;
  279. }
  280. }
  281. }
  282. // Set 4 continues in handle_drag_end.
  283. if (drag_event->cancelled())
  284. return handle_drag_end(realm, Cancelled::Yes, screen_position, page_offset, client_offset, offset, button, buttons, modifiers);
  285. return EventResult::Handled;
  286. }
  287. EventResult DragAndDropEventHandler::handle_drag_leave(
  288. JS::Realm& realm,
  289. CSSPixelPoint screen_position,
  290. CSSPixelPoint page_offset,
  291. CSSPixelPoint client_offset,
  292. CSSPixelPoint offset,
  293. unsigned button,
  294. unsigned buttons,
  295. unsigned modifiers)
  296. {
  297. return handle_drag_end(realm, Cancelled::Yes, screen_position, page_offset, client_offset, offset, button, buttons, modifiers);
  298. }
  299. EventResult DragAndDropEventHandler::handle_drop(
  300. JS::Realm& realm,
  301. CSSPixelPoint screen_position,
  302. CSSPixelPoint page_offset,
  303. CSSPixelPoint client_offset,
  304. CSSPixelPoint offset,
  305. unsigned button,
  306. unsigned buttons,
  307. unsigned modifiers)
  308. {
  309. return handle_drag_end(realm, Cancelled::No, screen_position, page_offset, client_offset, offset, button, buttons, modifiers);
  310. }
  311. // https://html.spec.whatwg.org/multipage/dnd.html#drag-and-drop-processing-model:event-dnd-drag-3
  312. EventResult DragAndDropEventHandler::handle_drag_end(
  313. JS::Realm& realm,
  314. Cancelled cancelled,
  315. CSSPixelPoint screen_position,
  316. CSSPixelPoint page_offset,
  317. CSSPixelPoint client_offset,
  318. CSSPixelPoint offset,
  319. unsigned button,
  320. unsigned buttons,
  321. unsigned modifiers)
  322. {
  323. if (!has_ongoing_drag_and_drop_operation())
  324. return EventResult::Cancelled;
  325. auto fire_a_drag_and_drop_event = [&](GC::Ptr<DOM::EventTarget> target, FlyString const& name, GC::Ptr<DOM::EventTarget> related_target = nullptr) {
  326. return this->fire_a_drag_and_drop_event(realm, target, name, screen_position, page_offset, client_offset, offset, button, buttons, modifiers, related_target);
  327. };
  328. ScopeGuard guard { [&]() { reset(); } };
  329. // 4. Otherwise, if the user ended the drag-and-drop operation (e.g. by releasing the mouse button in a mouse-driven
  330. // drag-and-drop interface), or if the drag event was canceled, then this will be the last iteration. Run the
  331. // following steps, then stop the drag-and-drop operation:
  332. {
  333. bool dropped = false;
  334. // 1. If the current drag operation is "none" (no drag operation), or, if the user ended the drag-and-drop
  335. // operation by canceling it (e.g. by hitting the Escape key), or if the current target element is null, then
  336. // the drag operation failed. Run these substeps:
  337. if (m_current_drag_operation == HTML::DataTransferEffect::none || cancelled == Cancelled::Yes || !m_current_target_element) {
  338. // 1. Let dropped be false.
  339. dropped = false;
  340. // 2. If the current target element is a DOM element, fire a DND event named dragleave at it; otherwise, if
  341. // it is not null, use platform-specific conventions for drag cancelation.
  342. if (m_current_target_element && is<DOM::Element>(*m_current_target_element)) {
  343. fire_a_drag_and_drop_event(m_current_target_element, HTML::EventNames::dragleave);
  344. } else if (m_current_target_element) {
  345. // FIXME: "use platform-specific conventions for drag cancelation"
  346. }
  347. // 3. Set the current drag operation to "none".
  348. m_current_drag_operation = HTML::DataTransferEffect::none;
  349. }
  350. // Otherwise, the drag operation might be a success; run these substeps:
  351. else {
  352. GC::Ptr<HTML::DragEvent> drag_event;
  353. // 1. Let dropped be true.
  354. dropped = true;
  355. // 2. If the current target element is a DOM element, fire a DND event named drop at it; otherwise, use
  356. // platform-specific conventions for indicating a drop.
  357. if (is<DOM::Element>(*m_current_target_element)) {
  358. drag_event = fire_a_drag_and_drop_event(m_current_target_element, HTML::EventNames::drop);
  359. } else {
  360. // FIXME: "use platform-specific conventions for indicating a drop"
  361. }
  362. // 3. If the event is canceled, set the current drag operation to the value of the dropEffect attribute of
  363. // the DragEvent object's dataTransfer object as it stood after the event dispatch finished.
  364. if (drag_event && drag_event->cancelled()) {
  365. m_current_drag_operation = drag_event->data_transfer()->drop_effect();
  366. }
  367. // Otherwise, the event is not canceled; perform the event's default action, which depends on the exact
  368. // target as follows:
  369. else {
  370. // -> If the current target element is a text control (e.g., textarea, or an input element whose type
  371. // attribute is in the Text state) or an editing host or editable element, and the drag data store
  372. // item list has an item with the drag data item type string "text/plain" and the drag data item
  373. // kind text
  374. if (allow_text_drop(*m_current_target_element)) {
  375. // FIXME: Insert the actual data of the first item in the drag data store item list to have a drag data item
  376. // type string of "text/plain" and a drag data item kind that is text into the text control or editing
  377. // host or editable element in a manner consistent with platform-specific conventions (e.g. inserting
  378. // it at the current mouse cursor position, or inserting it at the end of the field).
  379. }
  380. // -> Otherwise
  381. else {
  382. // Reset the current drag operation to "none".
  383. m_current_drag_operation = HTML::DataTransferEffect::none;
  384. }
  385. }
  386. }
  387. // 2. Fire a DND event named dragend at the source node.
  388. fire_a_drag_and_drop_event(m_source_node, HTML::EventNames::dragend);
  389. // 3. Run the appropriate steps from the following list as the default action of the dragend event:
  390. // -> If dropped is true, the current target element is a text control (see below), the current drag operation
  391. // is "move", and the source of the drag-and-drop operation is a selection in the DOM that is entirely
  392. // contained within an editing host
  393. if (false) {
  394. // FIXME: Delete the selection.
  395. }
  396. // -> If dropped is true, the current target element is a text control (see below), the current drag operation
  397. // is "move", and the source of the drag-and-drop operation is a selection in a text control
  398. else if (false) {
  399. // FIXME: The user agent should delete the dragged selection from the relevant text control.
  400. }
  401. // -> If dropped is false or if the current drag operation is "none"
  402. else if (!dropped || m_current_drag_operation == HTML::DataTransferEffect::none) {
  403. // The drag was canceled. If the platform conventions dictate that this be represented to the user (e.g. by
  404. // animating the dragged selection going back to the source of the drag-and-drop operation), then do so.
  405. return EventResult::Cancelled;
  406. }
  407. // -> Otherwise
  408. else {
  409. // The event has no default action.
  410. }
  411. }
  412. return EventResult::Handled;
  413. }
  414. // https://html.spec.whatwg.org/multipage/dnd.html#fire-a-dnd-event
  415. GC::Ref<HTML::DragEvent> DragAndDropEventHandler::fire_a_drag_and_drop_event(
  416. JS::Realm& realm,
  417. GC::Ptr<DOM::EventTarget> target,
  418. FlyString const& name,
  419. CSSPixelPoint screen_position,
  420. CSSPixelPoint page_offset,
  421. CSSPixelPoint client_offset,
  422. CSSPixelPoint offset,
  423. unsigned button,
  424. unsigned buttons,
  425. unsigned modifiers,
  426. GC::Ptr<DOM::EventTarget> related_target)
  427. {
  428. // NOTE: When the source node is determined above, the spec indicates we must follow platform-specific conventions
  429. // for dispatching events at the source node if the source node is an out-of-document object. We currently
  430. // handle this by allowing callers to pass a null `target` node. This allows us to execute all state-change
  431. // operations in the fire-a-DND-event AO, and simply skip event dispatching for now if the target is null.
  432. // 1. Let dataDragStoreWasChanged be false.
  433. bool drag_data_store_was_changed = false;
  434. // 2. If no specific related target was provided, set related target to null.
  435. // 3. Let window be the relevant global object of the Document object of the specified target element.
  436. // NOTE: We defer this until it's needed later, to more easily handle when the target is not an element.
  437. // 4. If e is dragstart, then set the drag data store mode to the read/write mode and set dataDragStoreWasChanged to true.
  438. if (name == HTML::EventNames::dragstart) {
  439. m_drag_data_store->set_mode(HTML::DragDataStore::Mode::ReadWrite);
  440. drag_data_store_was_changed = true;
  441. }
  442. // 5. If e is drop, set the drag data store mode to the read-only mode.
  443. else if (name == HTML::EventNames::drop) {
  444. m_drag_data_store->set_mode(HTML::DragDataStore::Mode::ReadOnly);
  445. }
  446. // 6. Let dataTransfer be a newly created DataTransfer object associated with the given drag data store.
  447. auto data_transfer = HTML::DataTransfer::create(realm, *m_drag_data_store);
  448. // 7. Set the effectAllowed attribute to the drag data store's drag data store allowed effects state.
  449. data_transfer->set_effect_allowed_internal(m_drag_data_store->allowed_effects_state());
  450. // 8. Set the dropEffect attribute to "none" if e is dragstart, drag, or dragleave; to the value corresponding to the
  451. // current drag operation if e is drop or dragend; and to a value based on the effectAllowed attribute's value and
  452. // the drag-and-drop source, as given by the following table, otherwise (i.e. if e is dragenter or dragover):
  453. if (name.is_one_of(HTML::EventNames::dragstart, HTML::EventNames::drag, HTML::EventNames::dragleave)) {
  454. data_transfer->set_drop_effect(HTML::DataTransferEffect::none);
  455. } else if (name.is_one_of(HTML::EventNames::drop, HTML::EventNames::dragend)) {
  456. data_transfer->set_drop_effect(m_current_drag_operation);
  457. } else {
  458. // effectAllowed | dropEffect
  459. // ---------------------------------------------------------------------------------------------------------------------------------------
  460. // "none" | "none"
  461. // "copy" | "copy"
  462. // "copyLink" | "copy", or, if appropriate, "link"
  463. // "copyMove" | "copy", or, if appropriate, "move"
  464. // "all" | "copy", or, if appropriate, either "link" or "move"
  465. // "link" | "link"
  466. // "linkMove" | "link", or, if appropriate, "move"
  467. // "move" | "move"
  468. // "uninitialized" when what is being dragged is a selection from a text control | "move", or, if appropriate, either "copy" or "link"
  469. // "uninitialized" when what is being dragged is a selection | "copy", or, if appropriate, either "link" or "move"
  470. // "uninitialized" when what is being dragged is an a element with an href attribute | "link", or, if appropriate, either "copy" or "move"
  471. // Any other case | "copy", or, if appropriate, either "link" or "move"
  472. using namespace HTML::DataTransferEffect;
  473. // clang-format off
  474. if (data_transfer->effect_allowed() == none) data_transfer->set_drop_effect(none);
  475. else if (data_transfer->effect_allowed() == copy) data_transfer->set_drop_effect(copy);
  476. else if (data_transfer->effect_allowed() == copyLink) data_transfer->set_drop_effect(copy);
  477. else if (data_transfer->effect_allowed() == copyMove) data_transfer->set_drop_effect(copy);
  478. else if (data_transfer->effect_allowed() == all) data_transfer->set_drop_effect(copy);
  479. else if (data_transfer->effect_allowed() == link) data_transfer->set_drop_effect(link);
  480. else if (data_transfer->effect_allowed() == linkMove) data_transfer->set_drop_effect(link);
  481. else if (data_transfer->effect_allowed() == move) data_transfer->set_drop_effect(move);
  482. // FIXME: Handle "uninitialized" when element drag operations are supported.
  483. else data_transfer->set_drop_effect(copy);
  484. // clang-format on
  485. }
  486. // 9. Let event be the result of creating an event using DragEvent.
  487. // FIXME: Implement https://dom.spec.whatwg.org/#concept-event-create
  488. HTML::DragEventInit event_init {};
  489. // 10. Initialize event's type attribute to e, its bubbles attribute to true, its view attribute to window, its
  490. // relatedTarget attribute to related target, and its dataTransfer attribute to dataTransfer.
  491. event_init.bubbles = true;
  492. event_init.related_target = related_target;
  493. event_init.data_transfer = data_transfer;
  494. if (target) {
  495. auto& window = static_cast<HTML::Window&>(HTML::relevant_global_object(*target));
  496. event_init.view = window.window();
  497. }
  498. // If e is not dragleave or dragend, then initialize event's cancelable attribute to true.
  499. if (!name.is_one_of(HTML::EventNames::dragleave, HTML::EventNames::dragend))
  500. event_init.cancelable = true;
  501. // 11. Initialize event's mouse and key attributes initialized according to the state of the input devices as they
  502. // would be for user interaction events.
  503. event_init.ctrl_key = (modifiers & UIEvents::Mod_Ctrl) != 0;
  504. event_init.shift_key = (modifiers & UIEvents::Mod_Shift) != 0;
  505. event_init.alt_key = (modifiers & UIEvents::Mod_Alt) != 0;
  506. event_init.meta_key = (modifiers & UIEvents::Mod_Super) != 0;
  507. event_init.screen_x = screen_position.x().to_double();
  508. event_init.screen_y = screen_position.y().to_double();
  509. event_init.client_x = client_offset.x().to_double();
  510. event_init.client_y = client_offset.y().to_double();
  511. event_init.button = button;
  512. event_init.buttons = buttons;
  513. auto event = HTML::DragEvent::create(realm, name, event_init, page_offset.x().to_double(), page_offset.y().to_double(), offset.x().to_double(), offset.y().to_double());
  514. // The "create an event" AO in step 9 should set these.
  515. event->set_is_trusted(true);
  516. event->set_initialized(true);
  517. event->set_composed(true);
  518. // 12. Dispatch event at the specified target element.
  519. if (target)
  520. target->dispatch_event(event);
  521. // 13. Set the drag data store allowed effects state to the current value of dataTransfer's effectAllowed attribute.
  522. // (It can only have changed value if e is dragstart.)
  523. m_drag_data_store->set_allowed_effects_state(data_transfer->effect_allowed());
  524. // 14. If dataDragStoreWasChanged is true, then set the drag data store mode back to the protected mode.
  525. if (drag_data_store_was_changed)
  526. m_drag_data_store->set_mode(HTML::DragDataStore::Mode::Protected);
  527. // 15. Break the association between dataTransfer and the drag data store.
  528. data_transfer->disassociate_with_drag_data_store();
  529. return event;
  530. }
  531. bool DragAndDropEventHandler::allow_text_drop(GC::Ref<DOM::Node> node) const
  532. {
  533. if (!m_drag_data_store->has_text_item())
  534. return false;
  535. if (node->is_editable())
  536. return true;
  537. if (is<HTML::HTMLTextAreaElement>(*node))
  538. return true;
  539. if (is<HTML::HTMLInputElement>(*node)) {
  540. auto const& input = static_cast<HTML::HTMLInputElement const&>(*node);
  541. return input.type_state() == HTML::HTMLInputElement::TypeAttributeState::Text;
  542. }
  543. return false;
  544. }
  545. void DragAndDropEventHandler::reset()
  546. {
  547. // When the drag-and-drop operation has completed, we no longer need the drag data store and its related fields.
  548. // Clear them, as we currently use the existence of the drag data store to ignore other input events.
  549. m_drag_data_store.clear();
  550. m_source_node = nullptr;
  551. m_immediate_user_selection = nullptr;
  552. m_current_target_element = nullptr;
  553. m_current_drag_operation = HTML::DataTransferEffect::none;
  554. }
  555. }