mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 01:35:08 +02:00
Previously, the LibWeb bindings generator would output multiple per interface files like Prototype/Constructor/Namespace/GlobalMixin depending on the contents of that IDL file. This complicates the build system as it means that it does not know what files will be generated without knowledge of the contents of that IDL file. Instead, for each IDL file only generate a single Bindings/<IDLFile>.h and Bindings/<IDLFile>.cpp.
425 lines
15 KiB
C++
425 lines
15 KiB
C++
/*
|
|
* Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Enumerate.h>
|
|
#include <AK/Find.h>
|
|
#include <LibJS/Runtime/Realm.h>
|
|
#include <LibWeb/Bindings/DataTransfer.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/DataTransferItem.h>
|
|
#include <LibWeb/HTML/DataTransferItemList.h>
|
|
#include <LibWeb/Infra/Strings.h>
|
|
|
|
namespace Web::HTML {
|
|
|
|
GC_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
|
|
|
|
}
|
|
|
|
GC::Ref<DataTransfer> DataTransfer::create(JS::Realm& realm, NonnullRefPtr<DragDataStore> drag_data_store)
|
|
{
|
|
return realm.create<DataTransfer>(realm, move(drag_data_store));
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer
|
|
GC::Ref<DataTransfer> DataTransfer::construct_impl(JS::Realm& realm)
|
|
{
|
|
// 1. Set the drag data store's item list to be an empty list.
|
|
auto drag_data_store = DragDataStore::create();
|
|
|
|
// 2. Set the drag data store's mode to read/write mode.
|
|
drag_data_store->set_mode(DragDataStore::Mode::ReadWrite);
|
|
|
|
// 3. Set the dropEffect and effectAllowed to "none".
|
|
// NOTE: This is done by the default-initializers.
|
|
|
|
return realm.create<DataTransfer>(realm, move(drag_data_store));
|
|
}
|
|
|
|
DataTransfer::DataTransfer(JS::Realm& realm, NonnullRefPtr<DragDataStore> drag_data_store)
|
|
: PlatformObject(realm)
|
|
, m_associated_drag_data_store(move(drag_data_store))
|
|
{
|
|
for (auto const& [i, item] : enumerate(m_associated_drag_data_store->item_list())) {
|
|
auto data_transfer_item = DataTransferItem::create(realm, *this, i);
|
|
m_item_list.append(data_transfer_item);
|
|
}
|
|
|
|
update_data_transfer_types_list();
|
|
}
|
|
|
|
DataTransfer::~DataTransfer() = default;
|
|
|
|
void DataTransfer::initialize(JS::Realm& realm)
|
|
{
|
|
WEB_SET_PROTOTYPE_FOR_INTERFACE(DataTransfer);
|
|
Base::initialize(realm);
|
|
}
|
|
|
|
void DataTransfer::visit_edges(JS::Cell::Visitor& visitor)
|
|
{
|
|
Base::visit_edges(visitor);
|
|
visitor.visit(m_items);
|
|
visitor.visit(m_item_list);
|
|
|
|
VISIT_CACHED_ATTRIBUTE(types);
|
|
}
|
|
|
|
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
|
|
GC::Ref<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 = format_argument.to_ascii_lowercase();
|
|
|
|
// 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-setdata
|
|
void DataTransfer::set_data(String const& format_argument, String const& value)
|
|
{
|
|
// 1. If the DataTransfer object is no longer associated with a drag data store, then return.
|
|
if (!m_associated_drag_data_store)
|
|
return;
|
|
|
|
// 2. If the drag data store's mode is not the read/write mode, return. Nothing happens.
|
|
if (m_associated_drag_data_store->mode() != DragDataStore::Mode::ReadWrite)
|
|
return;
|
|
|
|
// 3. Let format be the first argument, converted to ASCII lowercase.
|
|
auto format = format_argument.to_ascii_lowercase();
|
|
|
|
// 4. If format equals "text", change it to "text/plain".
|
|
// If format equals "url", change it to "text/uri-list".
|
|
if (format == "text"sv) {
|
|
format = "text/plain"_string;
|
|
} else if (format == "url"sv) {
|
|
format = "text/uri-list"_string;
|
|
}
|
|
|
|
// 5. Remove the item in the drag data store item list whose kind is text and whose type string is equal to format, if there is one.
|
|
for (auto const& [i, item] : enumerate(m_associated_drag_data_store->item_list())) {
|
|
if (item.kind == DragDataStoreItem::Kind::Text && item.type_string == format) {
|
|
remove_item(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 6. Add an item to the drag data store item list
|
|
// whose kind is text, whose type string is equal to format, and whose data is the string given by the method's second argument.
|
|
DragDataStoreItem item {};
|
|
item.kind = DragDataStoreItem::Kind::Text;
|
|
item.type_string = format;
|
|
item.data = MUST(ByteBuffer::copy(value.bytes()));
|
|
add_item(move(item));
|
|
|
|
update_data_transfer_types_list();
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-files
|
|
GC::Ref<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, { GC::make_root(blob) }, file_name, move(options)));
|
|
files->add_file(file);
|
|
}
|
|
|
|
// 5. The files found by these steps are those in the list L.
|
|
return files;
|
|
}
|
|
|
|
Optional<DragDataStore::Mode> DataTransfer::mode() const
|
|
{
|
|
if (m_associated_drag_data_store)
|
|
return m_associated_drag_data_store->mode();
|
|
return {};
|
|
}
|
|
|
|
void DataTransfer::disassociate_with_drag_data_store()
|
|
{
|
|
m_associated_drag_data_store.clear();
|
|
update_data_transfer_types_list();
|
|
}
|
|
|
|
GC::Ref<DataTransferItem> DataTransfer::add_item(DragDataStoreItem item)
|
|
{
|
|
auto& realm = this->realm();
|
|
|
|
VERIFY(m_associated_drag_data_store);
|
|
m_associated_drag_data_store->add_item(move(item));
|
|
|
|
auto data_transfer_item = DataTransferItem::create(realm, *this, m_associated_drag_data_store->size() - 1);
|
|
m_item_list.append(data_transfer_item);
|
|
|
|
update_data_transfer_types_list();
|
|
|
|
return data_transfer_item;
|
|
}
|
|
|
|
void DataTransfer::remove_item(size_t index)
|
|
{
|
|
VERIFY(m_associated_drag_data_store);
|
|
VERIFY(index < m_item_list.size());
|
|
|
|
m_associated_drag_data_store->remove_item_at(index);
|
|
auto& item = m_item_list.at(index);
|
|
item->set_item_index({}, OptionalNone {});
|
|
m_item_list.remove(index);
|
|
for (size_t i = index; i < m_item_list.size(); ++i) {
|
|
m_item_list.at(i)->set_item_index({}, i);
|
|
}
|
|
|
|
update_data_transfer_types_list();
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-cleardata
|
|
void DataTransfer::clear_data(Optional<String> maybe_format)
|
|
{
|
|
// 1. If the DataTransfer object is no longer associated with a drag data store, return. Nothing happens.
|
|
if (!m_associated_drag_data_store)
|
|
return;
|
|
|
|
// 2. If the drag data store's mode is not the read/write mode, return. Nothing happens.
|
|
if (m_associated_drag_data_store->mode() != DragDataStore::Mode::ReadWrite)
|
|
return;
|
|
|
|
auto remove_items_from_drag_data_store = [&](Optional<String> const& format = {}) {
|
|
auto did_remove_item = false;
|
|
for (size_t i = m_associated_drag_data_store->item_list().size(); i > 0; --i) {
|
|
auto const& item = m_associated_drag_data_store->item_list().at(i - 1);
|
|
if (item.kind == DragDataStoreItem::Kind::Text && (!format.has_value() || item.type_string == *format)) {
|
|
m_associated_drag_data_store->remove_item_at(i - 1);
|
|
did_remove_item = true;
|
|
}
|
|
}
|
|
if (did_remove_item)
|
|
update_data_transfer_types_list();
|
|
};
|
|
|
|
// 3. If the method was called with no arguments, remove each item in the drag data store item list whose kind is
|
|
// Plain Unicode string, and return.
|
|
if (!maybe_format.has_value()) {
|
|
remove_items_from_drag_data_store();
|
|
return;
|
|
}
|
|
|
|
// 4. Set format to format, converted to ASCII lowercase.
|
|
auto format = maybe_format->to_ascii_lowercase();
|
|
|
|
// 5. If format equals "text", change it to "text/plain".
|
|
// If format equals "url", change it to "text/uri-list".
|
|
if (format == "text"sv) {
|
|
format = "text/plain"_string;
|
|
} else if (format == "url"sv) {
|
|
format = "text/uri-list"_string;
|
|
}
|
|
|
|
// 6. Remove each item in the drag data store item list whose kind is Plain Unicode string and whose type string is equal to format.
|
|
remove_items_from_drag_data_store(format);
|
|
}
|
|
|
|
bool DataTransfer::contains_item_with_type(DragDataStoreItem::Kind kind, String const& type) const
|
|
{
|
|
VERIFY(m_associated_drag_data_store);
|
|
|
|
for (auto const& item : m_associated_drag_data_store->item_list()) {
|
|
if (item.kind == kind && item.type_string.equals_ignoring_ascii_case(type))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
GC::Ref<DataTransferItem> DataTransfer::item(size_t index) const
|
|
{
|
|
VERIFY(index < m_item_list.size());
|
|
return m_item_list[index];
|
|
}
|
|
|
|
DragDataStoreItem const& DataTransfer::drag_data(size_t index) const
|
|
{
|
|
VERIFY(m_associated_drag_data_store);
|
|
VERIFY(index < m_item_list.size());
|
|
|
|
return m_associated_drag_data_store->item_list()[index];
|
|
}
|
|
|
|
size_t DataTransfer::length() const
|
|
{
|
|
if (m_associated_drag_data_store)
|
|
return m_associated_drag_data_store->size();
|
|
return 0;
|
|
}
|
|
|
|
// 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.
|
|
set_cached_types(nullptr);
|
|
m_types = move(types);
|
|
}
|
|
|
|
}
|