/* * Copyright (c) 2024, Feng Yu * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include namespace Web::Clipboard { GC_DEFINE_ALLOCATOR(ClipboardItem); // https://w3c.github.io/clipboard-apis/#dom-clipboarditem-clipboarditem WebIDL::ExceptionOr> ClipboardItem::construct_impl(JS::Realm& realm, OrderedHashMap> 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(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 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> 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 { // 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(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 { // 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); } } }