Coverage Report

Created: 2025-11-02 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}