Coverage Report

Created: 2026-07-25 07:46

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