mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-11 18:50:50 +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
|
@ -33,6 +33,7 @@ set(SOURCES
|
||||||
Bindings/SyntheticHostDefined.cpp
|
Bindings/SyntheticHostDefined.cpp
|
||||||
Clipboard/Clipboard.cpp
|
Clipboard/Clipboard.cpp
|
||||||
Clipboard/ClipboardEvent.cpp
|
Clipboard/ClipboardEvent.cpp
|
||||||
|
Clipboard/ClipboardItem.cpp
|
||||||
Compression/CompressionStream.cpp
|
Compression/CompressionStream.cpp
|
||||||
Compression/DecompressionStream.cpp
|
Compression/DecompressionStream.cpp
|
||||||
Crypto/Crypto.cpp
|
Crypto/Crypto.cpp
|
||||||
|
|
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";
|
||||||
|
};
|
|
@ -84,6 +84,7 @@ enum class XMLHttpRequestResponseType;
|
||||||
|
|
||||||
namespace Web::Clipboard {
|
namespace Web::Clipboard {
|
||||||
class Clipboard;
|
class Clipboard;
|
||||||
|
class ClipboardItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Web::Compression {
|
namespace Web::Compression {
|
||||||
|
|
|
@ -9,6 +9,7 @@ libweb_js_bindings(Animations/DocumentTimeline)
|
||||||
libweb_js_bindings(Animations/KeyframeEffect)
|
libweb_js_bindings(Animations/KeyframeEffect)
|
||||||
libweb_js_bindings(Clipboard/Clipboard)
|
libweb_js_bindings(Clipboard/Clipboard)
|
||||||
libweb_js_bindings(Clipboard/ClipboardEvent)
|
libweb_js_bindings(Clipboard/ClipboardEvent)
|
||||||
|
libweb_js_bindings(Clipboard/ClipboardItem)
|
||||||
libweb_js_bindings(Compression/CompressionStream)
|
libweb_js_bindings(Compression/CompressionStream)
|
||||||
libweb_js_bindings(Compression/DecompressionStream)
|
libweb_js_bindings(Compression/DecompressionStream)
|
||||||
libweb_js_bindings(Crypto/Crypto)
|
libweb_js_bindings(Crypto/Crypto)
|
||||||
|
|
|
@ -45,6 +45,7 @@ static bool is_platform_object(Type const& type)
|
||||||
"CanvasGradient"sv,
|
"CanvasGradient"sv,
|
||||||
"CanvasPattern"sv,
|
"CanvasPattern"sv,
|
||||||
"CanvasRenderingContext2D"sv,
|
"CanvasRenderingContext2D"sv,
|
||||||
|
"ClipboardItem"sv,
|
||||||
"CloseWatcher"sv,
|
"CloseWatcher"sv,
|
||||||
"CryptoKey"sv,
|
"CryptoKey"sv,
|
||||||
"DataTransfer"sv,
|
"DataTransfer"sv,
|
||||||
|
@ -247,6 +248,9 @@ CppType idl_type_name_to_cpp_type(Type const& type, Interface const& interface)
|
||||||
if (type.name() == "Function")
|
if (type.name() == "Function")
|
||||||
return { .name = "GC::Ref<WebIDL::CallbackType>", .sequence_storage_type = SequenceStorageType::MarkedVector };
|
return { .name = "GC::Ref<WebIDL::CallbackType>", .sequence_storage_type = SequenceStorageType::MarkedVector };
|
||||||
|
|
||||||
|
if (type.name() == "Promise")
|
||||||
|
return { .name = "GC::Root<WebIDL::Promise>", .sequence_storage_type = SequenceStorageType::MarkedVector };
|
||||||
|
|
||||||
if (type.name() == "sequence") {
|
if (type.name() == "sequence") {
|
||||||
auto& parameterized_type = verify_cast<ParameterizedType>(type);
|
auto& parameterized_type = verify_cast<ParameterizedType>(type);
|
||||||
auto& sequence_type = parameterized_type.parameters().first();
|
auto& sequence_type = parameterized_type.parameters().first();
|
||||||
|
@ -4663,6 +4667,7 @@ void generate_constructor_implementation(IDL::Interface const& interface, String
|
||||||
#include <LibJS/Runtime/DataView.h>
|
#include <LibJS/Runtime/DataView.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
#include <LibJS/Runtime/Iterator.h>
|
#include <LibJS/Runtime/Iterator.h>
|
||||||
|
#include <LibJS/Runtime/PromiseConstructor.h>
|
||||||
#include <LibJS/Runtime/ValueInlines.h>
|
#include <LibJS/Runtime/ValueInlines.h>
|
||||||
#include <LibJS/Runtime/TypedArray.h>
|
#include <LibJS/Runtime/TypedArray.h>
|
||||||
#include <LibWeb/Bindings/@constructor_class@.h>
|
#include <LibWeb/Bindings/@constructor_class@.h>
|
||||||
|
|
3
Tests/LibWeb/Text/expected/Clipboard/clipboarditem.txt
Normal file
3
Tests/LibWeb/Text/expected/Clipboard/clipboarditem.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
types: text/plain,text/uri-list
|
||||||
|
getType('text/plain'): hello
|
||||||
|
getType('text/uri-list'): https://example.com
|
|
@ -60,6 +60,7 @@ ChannelMergerNode
|
||||||
CharacterData
|
CharacterData
|
||||||
Clipboard
|
Clipboard
|
||||||
ClipboardEvent
|
ClipboardEvent
|
||||||
|
ClipboardItem
|
||||||
CloseEvent
|
CloseEvent
|
||||||
CloseWatcher
|
CloseWatcher
|
||||||
Comment
|
Comment
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 28 tests
|
||||||
|
|
||||||
|
17 Pass
|
||||||
|
11 Fail
|
||||||
|
Fail ClipboardItem({string, Blob}) succeeds with different types
|
||||||
|
Pass ClipboardItem() succeeds with empty options
|
||||||
|
Pass ClipboardItem({}) fails with empty dictionary input
|
||||||
|
Pass ClipboardItem(Blob) fails
|
||||||
|
Pass ClipboardItem() fails with null input
|
||||||
|
Pass ClipboardItem() fails with no input
|
||||||
|
Fail types() returns correct values
|
||||||
|
Fail getType(DOMString valid type) succeeds with correct output
|
||||||
|
Fail getType(DOMString invalid type) succeeds with correct output
|
||||||
|
Fail getType(DOMString type) rejects correctly when querying for missing type
|
||||||
|
Fail getType(DOMString valid type) converts DOMString to Blob
|
||||||
|
Fail getType(DOMString invalid type) converts DOMString to Blob
|
||||||
|
Pass supports(text/plain) returns true
|
||||||
|
Pass supports(text/html) returns true
|
||||||
|
Pass supports(image/png) returns true
|
||||||
|
Fail supports(text/uri-list) returns true
|
||||||
|
Fail supports(image/svg+xml) returns true
|
||||||
|
Fail supports(web foo/bar) returns true
|
||||||
|
Fail supports(web text/html) returns true
|
||||||
|
Pass supports(web ) returns false
|
||||||
|
Pass supports(web) returns false
|
||||||
|
Pass supports(web foo) returns false
|
||||||
|
Pass supports(foo/bar) returns false
|
||||||
|
Pass supports(weB text/html) returns false
|
||||||
|
Pass supports( web text/html) returns false
|
||||||
|
Pass supports(not a/real type) returns false
|
||||||
|
Pass supports() returns false
|
||||||
|
Pass supports( ) returns false
|
20
Tests/LibWeb/Text/input/Clipboard/clipboarditem.html
Normal file
20
Tests/LibWeb/Text/input/Clipboard/clipboarditem.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
asyncTest(async (done) => {
|
||||||
|
const blob = new Blob(['https://example.com'], {type: 'text/uri-list'});
|
||||||
|
|
||||||
|
const item = new ClipboardItem({'text/plain': 'hello', 'text/uri-list': blob});
|
||||||
|
|
||||||
|
println(`types: ${item.types}`);
|
||||||
|
|
||||||
|
const blobOutput = await item.getType('text/plain');
|
||||||
|
const text = await (new Response(blobOutput)).text();
|
||||||
|
println(`getType('text/plain'): ${text}`);
|
||||||
|
|
||||||
|
const blobOutput2 = await item.getType('text/uri-list');
|
||||||
|
const text2 = await (new Response(blobOutput2)).text();
|
||||||
|
println(`getType('text/uri-list'): ${text2}`);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,124 @@
|
||||||
|
<!doctype html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>ClipboardItem tests</title>
|
||||||
|
<link rel="help" href="https://w3c.github.io/clipboard-apis/#async-clipboard-api">
|
||||||
|
<script src="../resources/testharness.js"></script>
|
||||||
|
<script src="../resources/testharnessreport.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const blob = new Blob(['hello'], {type: 'text/plain'});
|
||||||
|
const blob2 = new Blob(['this should work'], {type: 'not a/real type'});
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
new ClipboardItem({'text/plain': blob});
|
||||||
|
new ClipboardItem({'text/plain': blob, 'not a/real type': blob2});
|
||||||
|
}, "ClipboardItem({string, Blob}) succeeds with different types");
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
new ClipboardItem({'text/plain': blob}, {});
|
||||||
|
}, "ClipboardItem() succeeds with empty options");
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
assert_throws_js(TypeError, () => {new ClipboardItem({});});
|
||||||
|
}, "ClipboardItem({}) fails with empty dictionary input");
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
assert_throws_js(TypeError, () => {new ClipboardItem(blob);});
|
||||||
|
}, "ClipboardItem(Blob) fails");
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
assert_throws_js(TypeError, () => {new ClipboardItem(null);});
|
||||||
|
}, "ClipboardItem() fails with null input");
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
assert_throws_js(TypeError, () => {new ClipboardItem();});
|
||||||
|
}, "ClipboardItem() fails with no input");
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
const item = new ClipboardItem({'text/plain': blob});
|
||||||
|
const types = item.types;
|
||||||
|
assert_equals(types.length, 1);
|
||||||
|
assert_equals(types[0], 'text/plain');
|
||||||
|
const item2 =
|
||||||
|
new ClipboardItem({'text/plain': blob, 'not a/real type': blob2});
|
||||||
|
const types2 = item2.types;
|
||||||
|
assert_equals(types2.length, 2);
|
||||||
|
assert_equals(types2[0], 'text/plain');
|
||||||
|
assert_equals(types2[1], 'not a/real type');
|
||||||
|
}, "types() returns correct values");
|
||||||
|
|
||||||
|
promise_test(async () => {
|
||||||
|
const item =
|
||||||
|
new ClipboardItem({'text/plain': blob, 'not a/real type': blob2});
|
||||||
|
|
||||||
|
const blobOutput = await item.getType('text/plain');
|
||||||
|
assert_true(blobOutput.type.includes('text/plain'));
|
||||||
|
const text = await (new Response(blobOutput)).text();
|
||||||
|
|
||||||
|
assert_equals('hello', text);
|
||||||
|
}, "getType(DOMString valid type) succeeds with correct output");
|
||||||
|
|
||||||
|
promise_test(async () => {
|
||||||
|
const item =
|
||||||
|
new ClipboardItem({'text/plain': blob, 'not a/real type': blob2});
|
||||||
|
|
||||||
|
const blobOutput = await item.getType('not a/real type');
|
||||||
|
assert_true(blobOutput.type.includes('not a/real type'));
|
||||||
|
const text = await (new Response(blobOutput)).text();
|
||||||
|
|
||||||
|
assert_equals('this should work', text);
|
||||||
|
}, "getType(DOMString invalid type) succeeds with correct output");
|
||||||
|
|
||||||
|
promise_test(async t => {
|
||||||
|
const item =
|
||||||
|
new ClipboardItem({'text/plain': blob, 'not a/real type': blob2});
|
||||||
|
promise_rejects_dom(t, "NotFoundError", item.getType('type not in item'));
|
||||||
|
promise_rejects_dom(t, "NotFoundError", item.getType('text/plain:subtype'));
|
||||||
|
}, "getType(DOMString type) rejects correctly when querying for missing type");
|
||||||
|
|
||||||
|
promise_test(async () => {
|
||||||
|
const item =
|
||||||
|
new ClipboardItem({'text/plain': 'abc', 'not a/real type': 'xxx'});
|
||||||
|
const blob = await item.getType('text/plain');
|
||||||
|
assert_equals(blob.type, 'text/plain');
|
||||||
|
|
||||||
|
const text = await (new Response(blob)).text();
|
||||||
|
assert_equals(text, 'abc');
|
||||||
|
}, "getType(DOMString valid type) converts DOMString to Blob");
|
||||||
|
|
||||||
|
promise_test(async () => {
|
||||||
|
const item =
|
||||||
|
new ClipboardItem({'text/plain': 'abc', 'not a/real type': 'xxx'});
|
||||||
|
const blob = await item.getType('not a/real type');
|
||||||
|
assert_equals(blob.type, 'not a/real type');
|
||||||
|
|
||||||
|
const text = await (new Response(blob)).text();
|
||||||
|
assert_equals(text, 'xxx');
|
||||||
|
}, "getType(DOMString invalid type) converts DOMString to Blob");
|
||||||
|
|
||||||
|
[
|
||||||
|
// mandatory data types
|
||||||
|
['text/plain', true],
|
||||||
|
['text/html', true],
|
||||||
|
['image/png', true],
|
||||||
|
// optional data types
|
||||||
|
['text/uri-list', true],
|
||||||
|
['image/svg+xml', true],
|
||||||
|
['web foo/bar', true],
|
||||||
|
['web text/html', true],
|
||||||
|
// invalid types
|
||||||
|
['web ', false],
|
||||||
|
['web', false],
|
||||||
|
['web foo', false],
|
||||||
|
['foo/bar', false],
|
||||||
|
['weB text/html', false],
|
||||||
|
[' web text/html', false],
|
||||||
|
['not a/real type', false],
|
||||||
|
['', false],
|
||||||
|
[' ', false],
|
||||||
|
].forEach(([type, result]) => {
|
||||||
|
promise_test(async () => {
|
||||||
|
assert_equals(ClipboardItem.supports(type), result);
|
||||||
|
}, `supports(${type}) returns ${result ? "true" : "false"}`);
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue