diff --git a/components/script/dom/clipboarditem.rs b/components/script/dom/clipboarditem.rs index 129beb686c1..71df6b7abc5 100644 --- a/components/script/dom/clipboarditem.rs +++ b/components/script/dom/clipboarditem.rs @@ -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 +/// . +#[derive(Clone, JSTraceable, MallocSizeOf)] +struct RepresentationDataPromiseFulfillmentHandler { + #[ignore_malloc_size_of = "Rc are hard"] + promise: Rc, + 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::::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 +/// . +#[derive(Clone, JSTraceable, MallocSizeOf)] +struct RepresentationDataPromiseRejectionHandler { + #[ignore_malloc_size_of = "Rc are hard"] + promise: Rc, +} + +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); + } +} /// const CUSTOM_FORMAT_PREFIX: &str = "web "; @@ -148,7 +212,7 @@ impl ClipboardItemMethods for ClipboardItem { } /// - 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 for ClipboardItem { can_gc, ); } + + /// + fn GetType(&self, type_: DOMString, can_gc: CanGc) -> Fallible> { + // Step 1 Let realm be this’s 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 this’s clipboard item’s 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 representation’s MIME type is mimeType and representation’s isCustom is isCustom, then: + if representation.mime_type == mime_type && representation.is_custom == is_custom { + // Step 8.1.1 Let representationDataPromise be the representation’s 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) + } + + /// + 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" + } } diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index b27daba04fa..6a7ac7dbfb0 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -97,7 +97,7 @@ DOMInterfaces = { }, 'ClipboardItem': { - 'canGc': ['Types'] + 'canGc': ['GetType', 'Types'] }, 'CookieStore': { diff --git a/components/script_bindings/webidls/Clipboard.webidl b/components/script_bindings/webidls/Clipboard.webidl index b77e975917e..62faa0490d5 100644 --- a/components/script_bindings/webidls/Clipboard.webidl +++ b/components/script_bindings/webidls/Clipboard.webidl @@ -24,9 +24,9 @@ interface ClipboardItem { readonly attribute PresentationStyle presentationStyle; readonly attribute /* FrozenArray */ any types; - // Promise getType(DOMString type); + [Throws] Promise getType(DOMString type); - // static boolean supports(DOMString type); + static boolean supports(DOMString type); }; enum PresentationStyle { "unspecified", "inline", "attachment" }; diff --git a/tests/wpt/meta/clipboard-apis/clipboard-item.https.html.ini b/tests/wpt/meta/clipboard-apis/clipboard-item.https.html.ini index 2a5d4f9792e..79ed82de53e 100644 --- a/tests/wpt/meta/clipboard-apis/clipboard-item.https.html.ini +++ b/tests/wpt/meta/clipboard-apis/clipboard-item.https.html.ini @@ -20,9 +20,6 @@ [getType(DOMString invalid type) converts DOMString to Blob] expected: FAIL - [supports(text/plain) returns true] - expected: FAIL - [supports(text/html) returns true] expected: FAIL @@ -40,30 +37,3 @@ [supports(web text/html) returns true] expected: FAIL - - [supports(web ) returns false] - expected: FAIL - - [supports(web) returns false] - expected: FAIL - - [supports(web foo) returns false] - expected: FAIL - - [supports(foo/bar) returns false] - expected: FAIL - - [supports(weB text/html) returns false] - expected: FAIL - - [supports( web text/html) returns false] - expected: FAIL - - [supports(not a/real type) returns false] - expected: FAIL - - [supports() returns false] - expected: FAIL - - [supports( ) returns false] - expected: FAIL diff --git a/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini b/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini index e648b62a314..4ae21791309 100644 --- a/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini +++ b/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini @@ -1,10 +1,4 @@ [idlharness.https.window.html] - [ClipboardItem interface: operation getType(DOMString)] - expected: FAIL - - [ClipboardItem interface: operation supports(DOMString)] - expected: FAIL - [Clipboard interface: operation read(optional ClipboardUnsanitizedFormats)] expected: FAIL