mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-05-07 11:42:51 +00:00
Ownership of the drag data store is a bit weird. In a normal drag-and- drop operation, the DragAndDropEventHandler owns the store. When events are fired for the operation, the DataTransfer object assigned to those events are "associated" with the store. We currently represent that with an Optional<DragDataStore&>. However, it's also possible to create DataTransfer objects from scripts. Those objects create their own drag data store. This puts DataTransfer in a weird situation where it may own a store or just reference one. Rather than coming up with something like Variant<DDS, DDS&> or using MaybeOwned<DDS> here, we can get by with just making the store reference counted.
244 lines
8.6 KiB
C++
244 lines
8.6 KiB
C++
/*
|
|
* Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Find.h>
|
|
#include <LibJS/Runtime/Realm.h>
|
|
#include <LibWeb/Bindings/DataTransferPrototype.h>
|
|
#include <LibWeb/Bindings/Intrinsics.h>
|
|
#include <LibWeb/FileAPI/Blob.h>
|
|
#include <LibWeb/FileAPI/File.h>
|
|
#include <LibWeb/FileAPI/FileList.h>
|
|
#include <LibWeb/HTML/DataTransfer.h>
|
|
#include <LibWeb/HTML/DataTransferItemList.h>
|
|
#include <LibWeb/Infra/Strings.h>
|
|
|
|
namespace Web::HTML {
|
|
|
|
JS_DEFINE_ALLOCATOR(DataTransfer);
|
|
|
|
namespace DataTransferEffect {
|
|
|
|
#define __ENUMERATE_DATA_TRANSFER_EFFECT(name) FlyString name = #name##_fly_string;
|
|
ENUMERATE_DATA_TRANSFER_EFFECTS
|
|
#undef __ENUMERATE_DATA_TRANSFER_EFFECT
|
|
|
|
}
|
|
|
|
JS::NonnullGCPtr<DataTransfer> DataTransfer::construct_impl(JS::Realm& realm)
|
|
{
|
|
return realm.heap().allocate<DataTransfer>(realm, realm);
|
|
}
|
|
|
|
DataTransfer::DataTransfer(JS::Realm& realm)
|
|
: PlatformObject(realm)
|
|
{
|
|
}
|
|
|
|
DataTransfer::~DataTransfer() = default;
|
|
|
|
void DataTransfer::initialize(JS::Realm& realm)
|
|
{
|
|
Base::initialize(realm);
|
|
WEB_SET_PROTOTYPE_FOR_INTERFACE(DataTransfer);
|
|
}
|
|
|
|
void DataTransfer::visit_edges(JS::Cell::Visitor& visitor)
|
|
{
|
|
Base::visit_edges(visitor);
|
|
visitor.visit(m_items);
|
|
}
|
|
|
|
void DataTransfer::set_drop_effect(String const& drop_effect)
|
|
{
|
|
set_drop_effect(FlyString { drop_effect });
|
|
}
|
|
|
|
void DataTransfer::set_drop_effect(FlyString drop_effect)
|
|
{
|
|
using namespace DataTransferEffect;
|
|
|
|
// On setting, if the new value is one of "none", "copy", "link", or "move", then the attribute's current value must
|
|
// be set to the new value. Other values must be ignored.
|
|
if (drop_effect.is_one_of(none, copy, link, move))
|
|
m_drop_effect = AK::move(drop_effect);
|
|
}
|
|
|
|
void DataTransfer::set_effect_allowed(String const& effect_allowed)
|
|
{
|
|
set_effect_allowed(FlyString { effect_allowed });
|
|
}
|
|
|
|
void DataTransfer::set_effect_allowed(FlyString effect_allowed)
|
|
{
|
|
// On setting, if drag data store's mode is the read/write mode and the new value is one of "none", "copy", "copyLink",
|
|
// "copyMove", "link", "linkMove", "move", "all", or "uninitialized", then the attribute's current value must be set
|
|
// to the new value. Otherwise, it must be left unchanged.
|
|
if (m_associated_drag_data_store && m_associated_drag_data_store->mode() == DragDataStore::Mode::ReadWrite)
|
|
set_effect_allowed_internal(move(effect_allowed));
|
|
}
|
|
|
|
void DataTransfer::set_effect_allowed_internal(FlyString effect_allowed)
|
|
{
|
|
// AD-HOC: We need to be able to set the effectAllowed attribute internally regardless of the state of the drag data store.
|
|
using namespace DataTransferEffect;
|
|
|
|
if (effect_allowed.is_one_of(none, copy, copyLink, copyMove, link, linkMove, move, all, uninitialized))
|
|
m_effect_allowed = AK::move(effect_allowed);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-items
|
|
JS::NonnullGCPtr<DataTransferItemList> DataTransfer::items()
|
|
{
|
|
// The items attribute must return a DataTransferItemList object associated with the DataTransfer object.
|
|
if (!m_items)
|
|
m_items = DataTransferItemList::create(realm(), *this);
|
|
return *m_items;
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-types
|
|
ReadonlySpan<String> DataTransfer::types() const
|
|
{
|
|
// The types attribute must return this DataTransfer object's types array.
|
|
return m_types;
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-getdata
|
|
String DataTransfer::get_data(String const& format_argument) const
|
|
{
|
|
// 1. If the DataTransfer object is no longer associated with a drag data store, then return the empty string.
|
|
if (!m_associated_drag_data_store)
|
|
return {};
|
|
|
|
// 2. If the drag data store's mode is the protected mode, then return the empty string.
|
|
if (m_associated_drag_data_store->mode() == DragDataStore::Mode::Protected)
|
|
return {};
|
|
|
|
// 3. Let format be the first argument, converted to ASCII lowercase.
|
|
auto format = MUST(Infra::to_ascii_lowercase(format_argument));
|
|
|
|
// 4. Let convert-to-URL be false.
|
|
[[maybe_unused]] bool convert_to_url = false;
|
|
|
|
// 5. If format equals "text", change it to "text/plain".
|
|
if (format == "text"sv) {
|
|
format = "text/plain"_string;
|
|
}
|
|
|
|
// 6. If format equals "url", change it to "text/uri-list" and set convert-to-URL to true.
|
|
else if (format == "url"sv) {
|
|
format = "text/uri-list"_string;
|
|
convert_to_url = true;
|
|
}
|
|
|
|
// 7. If there is no item in the drag data store item list whose kind is text and whose type string is equal to
|
|
// format, return the empty string.
|
|
auto item_list = m_associated_drag_data_store->item_list();
|
|
|
|
auto it = find_if(item_list.begin(), item_list.end(), [&](auto const& item) {
|
|
return item.kind == DragDataStoreItem::Kind::Text && item.type_string == format;
|
|
});
|
|
|
|
if (it == item_list.end())
|
|
return {};
|
|
|
|
// 8. Let result be the data of the item in the drag data store item list whose kind is Plain Unicode string and
|
|
// whose type string is equal to format.
|
|
auto const& result = it->data;
|
|
|
|
// FIXME: 9. If convert-to-URL is true, then parse result as appropriate for text/uri-list data, and then set result to
|
|
// the first URL from the list, if any, or the empty string otherwise.
|
|
|
|
// 10. Return result.
|
|
return MUST(String::from_utf8(result));
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-files
|
|
JS::NonnullGCPtr<FileAPI::FileList> DataTransfer::files() const
|
|
{
|
|
auto& realm = this->realm();
|
|
|
|
// 1. Start with an empty list L.
|
|
auto files = FileAPI::FileList::create(realm);
|
|
|
|
// 2. If the DataTransfer object is no longer associated with a drag data store, the FileList is empty. Return
|
|
// the empty list L.
|
|
if (!m_associated_drag_data_store)
|
|
return files;
|
|
|
|
// 3. If the drag data store's mode is the protected mode, return the empty list L.
|
|
if (m_associated_drag_data_store->mode() == DragDataStore::Mode::Protected)
|
|
return files;
|
|
|
|
// 4. For each item in the drag data store item list whose kind is File, add the item's data (the file, in
|
|
// particular its name and contents, as well as its type) to the list L.
|
|
for (auto const& item : m_associated_drag_data_store->item_list()) {
|
|
if (item.kind != DragDataStoreItem::Kind::File)
|
|
continue;
|
|
|
|
auto blob = FileAPI::Blob::create(realm, item.data, item.type_string);
|
|
|
|
// FIXME: The FileAPI should use ByteString for file names.
|
|
auto file_name = MUST(String::from_byte_string(item.file_name));
|
|
|
|
// FIXME: Fill in other fields (e.g. last_modified).
|
|
FileAPI::FilePropertyBag options {};
|
|
options.type = item.type_string;
|
|
|
|
auto file = MUST(FileAPI::File::create(realm, { JS::make_handle(blob) }, file_name, move(options)));
|
|
files->add_file(file);
|
|
}
|
|
|
|
// 5. The files found by these steps are those in the list L.
|
|
return files;
|
|
}
|
|
|
|
void DataTransfer::associate_with_drag_data_store(NonnullRefPtr<DragDataStore> drag_data_store)
|
|
{
|
|
m_associated_drag_data_store = move(drag_data_store);
|
|
update_data_transfer_types_list();
|
|
}
|
|
|
|
void DataTransfer::disassociate_with_drag_data_store()
|
|
{
|
|
m_associated_drag_data_store.clear();
|
|
update_data_transfer_types_list();
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/dnd.html#concept-datatransfer-types
|
|
void DataTransfer::update_data_transfer_types_list()
|
|
{
|
|
// 1. Let L be an empty sequence.
|
|
Vector<String> types;
|
|
|
|
// 2. If the DataTransfer object is still associated with a drag data store, then:
|
|
if (m_associated_drag_data_store) {
|
|
bool contains_file = false;
|
|
|
|
// 1. For each item in the DataTransfer object's drag data store item list whose kind is text, add an entry to L
|
|
// consisting of the item's type string.
|
|
for (auto const& item : m_associated_drag_data_store->item_list()) {
|
|
switch (item.kind) {
|
|
case DragDataStoreItem::Kind::Text:
|
|
types.append(item.type_string);
|
|
break;
|
|
case DragDataStoreItem::Kind::File:
|
|
contains_file = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 2. If there are any items in the DataTransfer object's drag data store item list whose kind is File, then add
|
|
// an entry to L consisting of the string "Files". (This value can be distinguished from the other values
|
|
// because it is not lowercase.)
|
|
if (contains_file)
|
|
types.append("Files"_string);
|
|
}
|
|
|
|
// 3. Set the DataTransfer object's types array to the result of creating a frozen array from L.
|
|
m_types = move(types);
|
|
}
|
|
|
|
}
|