mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-12 20:42:21 +00:00
LibWeb: Implement the ClipboardItem API
Spec: https://w3c.github.io/clipboard-apis/#clipboard-item-interface
This commit is contained in:
parent
bdd6729d78
commit
b3edbd7bf2
Notes:
github-actions[bot]
2024-12-20 15:30:21 +00:00
Author: https://github.com/F3n67u
Commit: b3edbd7bf2
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2664
Reviewed-by: https://github.com/awesomekling
Reviewed-by: https://github.com/tcl3 ✅
12 changed files with 484 additions and 0 deletions
208
Libraries/LibWeb/Clipboard/ClipboardItem.cpp
Normal file
208
Libraries/LibWeb/Clipboard/ClipboardItem.cpp
Normal file
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Feng Yu <f3n67u@outlook.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Clipboard/ClipboardItem.h>
|
||||
#include <LibWeb/FileAPI/Blob.h>
|
||||
#include <LibWeb/MimeSniff/MimeType.h>
|
||||
#include <LibWeb/WebIDL/Promise.h>
|
||||
|
||||
namespace Web::Clipboard {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(ClipboardItem);
|
||||
|
||||
// https://w3c.github.io/clipboard-apis/#dom-clipboarditem-clipboarditem
|
||||
WebIDL::ExceptionOr<GC::Ref<ClipboardItem>> ClipboardItem::construct_impl(JS::Realm& realm, OrderedHashMap<String, GC::Root<WebIDL::Promise>> const& items, ClipboardItemOptions const& options)
|
||||
{
|
||||
// 1. If items is empty, then throw a TypeError.
|
||||
if (items.is_empty())
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Items cannot be empty"sv };
|
||||
|
||||
// 2. If options is empty, then set options["presentationStyle"] = "unspecified".
|
||||
// NOTE: This step is handled by presentationStyle's default value in ClipboardItemOptions.
|
||||
|
||||
// 3. Set this's clipboard item to a new clipboard item.
|
||||
auto clipboard_item = realm.create<ClipboardItem>(realm);
|
||||
|
||||
// 4. Set this's clipboard item's presentation style to options["presentationStyle"].
|
||||
clipboard_item->m_presentation_style = options.presentation_style;
|
||||
|
||||
// 5. Let types be a list of DOMString.
|
||||
Vector<String> types;
|
||||
|
||||
// 6. For each (key, value) in items:
|
||||
for (auto const& [key, value] : items) {
|
||||
// 2. Let isCustom be false.
|
||||
bool is_custom = false;
|
||||
|
||||
// 3. If key starts with `"web "` prefix, then:
|
||||
auto key_without_prefix = key;
|
||||
if (key.starts_with_bytes(WEB_CUSTOM_FORMAT_PREFIX)) {
|
||||
// 1. Remove `"web "` prefix and assign the remaining string to key.
|
||||
key_without_prefix = MUST(key.substring_from_byte_offset(WEB_CUSTOM_FORMAT_PREFIX.length()));
|
||||
|
||||
// 2. Set isCustom to true.
|
||||
is_custom = true;
|
||||
}
|
||||
|
||||
// 5. Let mimeType be the result of parsing a MIME type given key.
|
||||
auto mime_type = MimeSniff::MimeType::parse(key_without_prefix);
|
||||
|
||||
// 6. If mimeType is failure, then throw a TypeError.
|
||||
if (!mime_type.has_value()) {
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Invalid MIME type: {}", key)) };
|
||||
}
|
||||
|
||||
auto mime_type_serialized = mime_type->serialized();
|
||||
|
||||
// 7. If this's clipboard item's list of representations contains a representation whose MIME type
|
||||
// is mimeType and whose [representation/isCustom] is isCustom, then throw a TypeError.
|
||||
auto existing = clipboard_item->m_representations.find_if([&](auto const& item) {
|
||||
return item.mime_type == mime_type_serialized && item.is_custom == is_custom;
|
||||
});
|
||||
if (!existing.is_end()) {
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Duplicate MIME type: {}", key)) };
|
||||
}
|
||||
|
||||
// 11. Let mimeTypeString be the result of serializing a MIME type with mimeType.
|
||||
// 12. If isCustom is true, prefix mimeTypeString with `"web "`.
|
||||
auto mime_type_string = is_custom ? MUST(String::formatted("{}{}", WEB_CUSTOM_FORMAT_PREFIX, mime_type_serialized)) : mime_type_serialized;
|
||||
|
||||
// 13. Add mimeTypeString to types.
|
||||
types.append(move(mime_type_string));
|
||||
|
||||
// 1. Let representation be a new representation.
|
||||
// 4. Set representation’s isCustom flag to isCustom.
|
||||
// 8. Set representation’s MIME type to mimeType.
|
||||
// 9. Set representation’s data to value.
|
||||
// 10. Append representation to this's clipboard item's list of representations.
|
||||
clipboard_item->m_representations.empend(move(mime_type_serialized), is_custom, *value);
|
||||
}
|
||||
|
||||
// 7. Set this's types array to the result of running create a frozen array from types.
|
||||
clipboard_item->m_types = types;
|
||||
|
||||
return clipboard_item;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/clipboard-apis/#dom-clipboarditem-gettype
|
||||
WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> ClipboardItem::get_type(String const& type)
|
||||
{
|
||||
// 1. Let realm be this's relevant realm.
|
||||
auto& realm = HTML::relevant_realm(*this);
|
||||
|
||||
// 2. Let isCustom be false.
|
||||
bool is_custom = false;
|
||||
|
||||
// 3. If type starts with `"web "` prefix, then:
|
||||
auto type_without_prefix = type;
|
||||
if (type.starts_with_bytes(WEB_CUSTOM_FORMAT_PREFIX)) {
|
||||
// 1. Remove `"web "` prefix and assign the remaining string to type.
|
||||
type_without_prefix = MUST(type.substring_from_byte_offset(WEB_CUSTOM_FORMAT_PREFIX.length()));
|
||||
|
||||
// 2. Set isCustom to true.
|
||||
is_custom = true;
|
||||
}
|
||||
|
||||
// 4. Let mimeType be the result of parsing a MIME type given type.
|
||||
auto mime_type = MimeSniff::MimeType::parse(type_without_prefix);
|
||||
|
||||
// 5. If mimeType is failure, then throw a TypeError.
|
||||
if (!mime_type.has_value()) {
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Invalid MIME type: {}", type)) };
|
||||
}
|
||||
|
||||
auto mime_type_serialized = mime_type->serialized();
|
||||
|
||||
// 6. Let itemTypeList be this's clipboard item's list of representations.
|
||||
auto const& item_type_list = m_representations;
|
||||
|
||||
// 7. Let p be a new promise in realm.
|
||||
auto promise = WebIDL::create_promise(realm);
|
||||
|
||||
// 8. For each representation in itemTypeList:
|
||||
for (auto const& representation : item_type_list) {
|
||||
// 1. If representation’s MIME type is mimeType and representation’s isCustom is isCustom, then:
|
||||
if (representation.mime_type == mime_type_serialized && representation.is_custom == is_custom) {
|
||||
// 1. Let representationDataPromise be the representation’s data.
|
||||
auto representation_data_promise = representation.data;
|
||||
|
||||
// 2. React to representationDataPromise:
|
||||
WebIDL::react_to_promise(
|
||||
*representation_data_promise,
|
||||
GC::create_function(realm.heap(), [&realm, promise, mime_type_serialized](JS::Value value) -> WebIDL::ExceptionOr<JS::Value> {
|
||||
// 1. If v is a DOMString, then follow the below steps:
|
||||
if (value.is_string()) {
|
||||
// 1. Let dataAsBytes be the result of UTF-8 encoding v.
|
||||
auto utf8_string = value.as_string().utf8_string();
|
||||
auto data_as_bytes = MUST(ByteBuffer::copy(utf8_string.bytes()));
|
||||
|
||||
// 2. Let blobData be a Blob created using dataAsBytes with its type set to mimeType, serialized.
|
||||
auto blob_data = FileAPI::Blob::create(realm, data_as_bytes, mime_type_serialized);
|
||||
|
||||
// 3. Resolve p with blobData.
|
||||
WebIDL::resolve_promise(realm, promise, blob_data);
|
||||
}
|
||||
// 2. If v is a Blob, then follow the below steps:
|
||||
if (value.is_object() && is<FileAPI::Blob>(value.as_object())) {
|
||||
// 1. Resolve p with v.
|
||||
WebIDL::resolve_promise(realm, promise, value);
|
||||
}
|
||||
|
||||
return JS::js_undefined();
|
||||
}),
|
||||
// 2. If representationDataPromise was rejected, then:
|
||||
GC::create_function(realm.heap(), [&realm, type, promise](JS::Value) -> WebIDL::ExceptionOr<JS::Value> {
|
||||
// 1. Reject p with "NotFoundError" DOMException in realm.
|
||||
WebIDL::reject_promise(realm, promise, WebIDL::NotFoundError::create(realm, MUST(String::formatted("No data found for MIME type: {}", type))));
|
||||
|
||||
return JS::js_undefined();
|
||||
}));
|
||||
|
||||
// 3. Return p.
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
// 9. Reject p with "NotFoundError" DOMException in realm.
|
||||
WebIDL::reject_promise(realm, promise, WebIDL::NotFoundError::create(realm, MUST(String::formatted("No data found for MIME type: {}", type))));
|
||||
|
||||
// 10. Return p.
|
||||
return promise;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/clipboard-apis/#dom-clipboarditem-supports
|
||||
bool ClipboardItem::supports(JS::VM&, String const& type)
|
||||
{
|
||||
// 1. If type is in mandatory data types or optional data types, then return true.
|
||||
// 2. If not, then return false.
|
||||
// TODO: Implement optional data types, like web custom formats and image/svg+xml.
|
||||
return any_of(MANDATORY_DATA_TYPES, [&](auto supported) { return supported == type; });
|
||||
}
|
||||
|
||||
ClipboardItem::ClipboardItem(JS::Realm& realm)
|
||||
: Bindings::PlatformObject(realm)
|
||||
{
|
||||
}
|
||||
|
||||
ClipboardItem::~ClipboardItem() = default;
|
||||
|
||||
void ClipboardItem::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(ClipboardItem);
|
||||
}
|
||||
|
||||
void ClipboardItem::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
for (auto& representation : m_representations) {
|
||||
visitor.visit(representation.data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
64
Libraries/LibWeb/Clipboard/ClipboardItem.h
Normal file
64
Libraries/LibWeb/Clipboard/ClipboardItem.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Feng Yu <f3n67u@outlook.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGC/Ptr.h>
|
||||
#include <LibJS/Runtime/PromiseCapability.h>
|
||||
#include <LibWeb/Bindings/ClipboardItemPrototype.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/DataTransfer.h>
|
||||
#include <LibWeb/MimeSniff/MimeType.h>
|
||||
|
||||
namespace Web::Clipboard {
|
||||
|
||||
constexpr auto WEB_CUSTOM_FORMAT_PREFIX = "web "sv;
|
||||
|
||||
inline constexpr Array MANDATORY_DATA_TYPES = {
|
||||
"text/plain"sv, "text/html"sv, "image/png"sv
|
||||
};
|
||||
|
||||
struct ClipboardItemOptions {
|
||||
Bindings::PresentationStyle presentation_style { Bindings::PresentationStyle::Unspecified };
|
||||
};
|
||||
|
||||
// https://w3c.github.io/clipboard-apis/#clipboard-item-interface
|
||||
class ClipboardItem : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(ClipboardItem, Bindings::PlatformObject);
|
||||
GC_DECLARE_ALLOCATOR(ClipboardItem);
|
||||
|
||||
public:
|
||||
struct Representation {
|
||||
String mime_type; // The MIME type (e.g., "text/plain").
|
||||
bool is_custom; // Whether this is a web custom format.
|
||||
GC::Ref<WebIDL::Promise> data; // The actual data for this representation.
|
||||
};
|
||||
|
||||
static WebIDL::ExceptionOr<GC::Ref<ClipboardItem>> construct_impl(JS::Realm&, OrderedHashMap<String, GC::Root<WebIDL::Promise>> const& items, ClipboardItemOptions const& options = {});
|
||||
|
||||
virtual ~ClipboardItem() override;
|
||||
|
||||
Bindings::PresentationStyle presentation_style() const { return m_presentation_style; }
|
||||
|
||||
Vector<String> const& types() const { return m_types; }
|
||||
|
||||
WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> get_type(String const& type);
|
||||
|
||||
static bool supports(JS::VM&, String const& type);
|
||||
|
||||
private:
|
||||
ClipboardItem(JS::Realm&);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
Bindings::PresentationStyle m_presentation_style;
|
||||
Vector<String> m_types;
|
||||
Vector<Representation> m_representations;
|
||||
};
|
||||
|
||||
}
|
22
Libraries/LibWeb/Clipboard/ClipboardItem.idl
Normal file
22
Libraries/LibWeb/Clipboard/ClipboardItem.idl
Normal file
|
@ -0,0 +1,22 @@
|
|||
typedef Promise<(DOMString or Blob)> ClipboardItemData;
|
||||
|
||||
// https://w3c.github.io/clipboard-apis/#clipboard-item-interface
|
||||
[SecureContext, Exposed=Window]
|
||||
interface ClipboardItem {
|
||||
constructor(record<DOMString, ClipboardItemData> items,
|
||||
optional ClipboardItemOptions options = {});
|
||||
|
||||
readonly attribute PresentationStyle presentationStyle;
|
||||
// FIXME: Should be a FrozenArray<DOMString>
|
||||
readonly attribute sequence<DOMString> types;
|
||||
|
||||
Promise<Blob> getType(DOMString type);
|
||||
|
||||
static boolean supports(DOMString type);
|
||||
};
|
||||
|
||||
enum PresentationStyle { "unspecified", "inline", "attachment" };
|
||||
|
||||
dictionary ClipboardItemOptions {
|
||||
PresentationStyle presentationStyle = "unspecified";
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue