script: implement ClipboardItem getType and supports (#39296)

Implement ClipboardItem's getType and supports

Testing: covered by existing tests

---------

Signed-off-by: Gae24 <96017547+Gae24@users.noreply.github.com>
This commit is contained in:
Gae24 2025-09-25 10:05:01 +02:00 committed by GitHub
parent 454d6052b4
commit 75e32ba5a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 141 additions and 43 deletions

View file

@ -6,23 +6,87 @@ use std::ops::Deref;
use std::rc::Rc;
use std::str::FromStr;
use constellation_traits::BlobImpl;
use data_url::mime::Mime;
use dom_struct::dom_struct;
use js::rust::{HandleObject, MutableHandleValue};
use js::rust::{HandleObject, HandleValue as SafeHandleValue, MutableHandleValue};
use script_bindings::record::Record;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
};
use crate::dom::bindings::conversions::{
ConversionResult, SafeFromJSValConvertible, StringificationBehavior,
};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::frozenarray::CachedFrozenArray;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::blob::Blob;
use crate::dom::promise::Promise;
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
use crate::dom::window::Window;
use crate::script_runtime::{CanGc, JSContext};
use crate::realms::{InRealm, enter_realm};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
/// The fulfillment handler for the reacting to representationDataPromise part of
/// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-gettype>.
#[derive(Clone, JSTraceable, MallocSizeOf)]
struct RepresentationDataPromiseFulfillmentHandler {
#[ignore_malloc_size_of = "Rc are hard"]
promise: Rc<Promise>,
type_: String,
}
impl Callback for RepresentationDataPromiseFulfillmentHandler {
/// Substeps of 8.1.2.1 If representationDataPromise was fulfilled with value v, then:
fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) {
// 1. If v is a DOMString, then follow the below steps:
if v.get().is_string() {
// 1.1 Let dataAsBytes be the result of UTF-8 encoding v.
let data_as_bytes =
match DOMString::safe_from_jsval(cx, v, StringificationBehavior::Default) {
Ok(ConversionResult::Success(s)) => s.as_bytes().to_owned(),
_ => return,
};
// 1.2 Let blobData be a Blob created using dataAsBytes with its type set to mimeType, serialized.
let blob_data = Blob::new(
&self.promise.global(),
BlobImpl::new_from_bytes(data_as_bytes, self.type_.clone()),
can_gc,
);
// 1.3 Resolve p with blobData.
self.promise.resolve_native(&blob_data, can_gc);
}
// 2. If v is a Blob, then follow the below steps:
else if DomRoot::<Blob>::safe_from_jsval(cx, v, ())
.is_ok_and(|result| result.get_success_value().is_some())
{
// 2.1 Resolve p with v.
self.promise.resolve(cx, v, can_gc);
}
}
}
/// The rejection handler for the reacting to representationDataPromise part of
/// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-gettype>.
#[derive(Clone, JSTraceable, MallocSizeOf)]
struct RepresentationDataPromiseRejectionHandler {
#[ignore_malloc_size_of = "Rc are hard"]
promise: Rc<Promise>,
}
impl Callback for RepresentationDataPromiseRejectionHandler {
/// Substeps of 8.1.2.2 If representationDataPromise was rejected, then:
fn callback(&self, _cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) {
// 1. Reject p with "NotFoundError" DOMException in realm.
self.promise.reject_error(Error::NotFound, can_gc);
}
}
/// <https://w3c.github.io/clipboard-apis/#web-custom-format>
const CUSTOM_FORMAT_PREFIX: &str = "web ";
@ -148,7 +212,7 @@ impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
}
/// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-types>
fn Types(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
fn Types(&self, cx: SafeJSContext, can_gc: CanGc, retval: MutableHandleValue) {
self.frozen_types.get_or_init(
|| {
// Step 5 Let types be a list of DOMString.
@ -178,4 +242,74 @@ impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
can_gc,
);
}
/// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-gettype>
fn GetType(&self, type_: DOMString, can_gc: CanGc) -> Fallible<Rc<Promise>> {
// Step 1 Let realm be thiss relevant realm.
let global = self.global();
// Step 2 Let isCustom be false.
// Step 3 If type starts with `"web "` prefix, then:
// Step 3.1 Remove `"web "` prefix and assign the remaining string to type.
let (type_, is_custom) = match type_.strip_prefix(CUSTOM_FORMAT_PREFIX) {
None => (type_.str(), false),
// Step 3.2 Set isCustom to true.
Some(stripped) => (stripped, true),
};
// Step 4 Let mimeType be the result of parsing a MIME type given type.
// Step 5 If mimeType is failure, then throw a TypeError.
let mime_type =
Mime::from_str(type_).map_err(|_| Error::Type(String::from("Invalid mime type")))?;
// Step 6 Let itemTypeList be thiss clipboard items list of representations.
let item_type_list = self.representations.borrow();
// Step 7 Let p be a new promise in realm.
let p = Promise::new(&global, can_gc);
// Step 8 For each representation in itemTypeList
for representation in item_type_list.iter() {
// Step 8.1 If representations MIME type is mimeType and representations isCustom is isCustom, then:
if representation.mime_type == mime_type && representation.is_custom == is_custom {
// Step 8.1.1 Let representationDataPromise be the representations data.
let representation_data_promise = &representation.data;
// Step 8.1.2 React to representationDataPromise:
let fulfillment_handler = Box::new(RepresentationDataPromiseFulfillmentHandler {
promise: p.clone(),
type_: representation.mime_type.to_string(),
});
let rejection_handler =
Box::new(RepresentationDataPromiseRejectionHandler { promise: p.clone() });
let handler = PromiseNativeHandler::new(
&global,
Some(fulfillment_handler),
Some(rejection_handler),
can_gc,
);
let realm = enter_realm(&*global);
let comp = InRealm::Entered(&realm);
representation_data_promise.append_native_handler(&handler, comp, can_gc);
// Step 8.1.3 Return p.
return Ok(p);
}
}
// Step 9 Reject p with "NotFoundError" DOMException in realm.
p.reject_error(Error::NotFound, can_gc);
// Step 10 Return p.
Ok(p)
}
/// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-supports>
fn Supports(_: &Window, type_: DOMString) -> bool {
// TODO Step 1 If type is in mandatory data types or optional data types, then return true.
// Step 2 If not, then return false.
// NOTE: We only supports text/plain
type_ == "text/plain"
}
}

View file

@ -97,7 +97,7 @@ DOMInterfaces = {
},
'ClipboardItem': {
'canGc': ['Types']
'canGc': ['GetType', 'Types']
},
'CookieStore': {

View file

@ -24,9 +24,9 @@ interface ClipboardItem {
readonly attribute PresentationStyle presentationStyle;
readonly attribute /* FrozenArray<DOMString> */ any types;
// Promise<Blob> getType(DOMString type);
[Throws] Promise<Blob> getType(DOMString type);
// static boolean supports(DOMString type);
static boolean supports(DOMString type);
};
enum PresentationStyle { "unspecified", "inline", "attachment" };