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