1use std::ops::Deref;
6use std::rc::Rc;
7use std::str::FromStr;
8
9use constellation_traits::BlobImpl;
10use data_url::mime::Mime;
11use dom_struct::dom_struct;
12use js::realm::CurrentRealm;
13use js::rust::{HandleObject, HandleValue as SafeHandleValue, MutableHandleValue};
14use script_bindings::record::Record;
15
16use crate::dom::bindings::cell::DomRefCell;
17use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
18 ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
19};
20use crate::dom::bindings::conversions::{
21 ConversionResult, SafeFromJSValConvertible, StringificationBehavior,
22};
23use crate::dom::bindings::error::{Error, Fallible};
24use crate::dom::bindings::frozenarray::CachedFrozenArray;
25use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
26use crate::dom::bindings::root::DomRoot;
27use crate::dom::bindings::str::DOMString;
28use crate::dom::blob::Blob;
29use crate::dom::promise::Promise;
30use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
31use crate::dom::window::Window;
32use crate::realms::{InRealm, enter_realm};
33use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
34
35#[derive(Clone, JSTraceable, MallocSizeOf)]
38struct RepresentationDataPromiseFulfillmentHandler {
39 #[conditional_malloc_size_of]
40 promise: Rc<Promise>,
41 type_: String,
42}
43
44impl Callback for RepresentationDataPromiseFulfillmentHandler {
45 fn callback(&self, cx: &mut CurrentRealm, v: SafeHandleValue) {
47 let can_gc = CanGc::from_cx(cx);
48 if v.get().is_string() {
50 let data_as_bytes = match DOMString::safe_from_jsval(
52 cx.into(),
53 v,
54 StringificationBehavior::Default,
55 can_gc,
56 ) {
57 Ok(ConversionResult::Success(s)) => s.as_bytes().to_owned(),
58 _ => return,
59 };
60
61 let blob_data = Blob::new(
63 &self.promise.global(),
64 BlobImpl::new_from_bytes(data_as_bytes, self.type_.clone()),
65 can_gc,
66 );
67
68 self.promise.resolve_native(&blob_data, can_gc);
70 }
71 else if DomRoot::<Blob>::safe_from_jsval(cx.into(), v, (), can_gc)
73 .is_ok_and(|result| result.get_success_value().is_some())
74 {
75 self.promise.resolve(cx.into(), v, can_gc);
77 }
78 }
79}
80
81#[derive(Clone, JSTraceable, MallocSizeOf)]
84struct RepresentationDataPromiseRejectionHandler {
85 #[conditional_malloc_size_of]
86 promise: Rc<Promise>,
87}
88
89impl Callback for RepresentationDataPromiseRejectionHandler {
90 fn callback(&self, cx: &mut CurrentRealm, _v: SafeHandleValue) {
92 let can_gc = CanGc::from_cx(cx);
93 self.promise.reject_error(Error::NotFound(None), can_gc);
95 }
96}
97
98const CUSTOM_FORMAT_PREFIX: &str = "web ";
100
101#[derive(JSTraceable, MallocSizeOf)]
103pub(super) struct Representation {
104 #[no_trace]
105 #[ignore_malloc_size_of = "Extern type"]
106 pub mime_type: Mime,
107 pub is_custom: bool,
108 #[conditional_malloc_size_of]
109 pub data: Rc<Promise>,
110}
111
112#[dom_struct]
113pub(crate) struct ClipboardItem {
114 reflector_: Reflector,
115 representations: DomRefCell<Vec<Representation>>,
116 presentation_style: DomRefCell<PresentationStyle>,
117 #[ignore_malloc_size_of = "mozjs"]
118 frozen_types: CachedFrozenArray,
119}
120
121impl ClipboardItem {
122 fn new_inherited() -> ClipboardItem {
123 ClipboardItem {
124 reflector_: Reflector::new(),
125 representations: Default::default(),
126 presentation_style: Default::default(),
127 frozen_types: CachedFrozenArray::new(),
128 }
129 }
130
131 fn new(window: &Window, proto: Option<HandleObject>, can_gc: CanGc) -> DomRoot<ClipboardItem> {
132 reflect_dom_object_with_proto(
133 Box::new(ClipboardItem::new_inherited()),
134 window,
135 proto,
136 can_gc,
137 )
138 }
139}
140
141impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
142 fn Constructor(
144 global: &Window,
145 proto: Option<HandleObject>,
146 can_gc: CanGc,
147 items: Record<DOMString, Rc<Promise>>,
148 options: &ClipboardItemOptions,
149 ) -> Fallible<DomRoot<ClipboardItem>> {
150 if items.is_empty() {
152 return Err(Error::Type(String::from("No item provided")));
153 }
154
155 let clipboard_item = ClipboardItem::new(global, proto, can_gc);
160
161 *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
163
164 for (key, value) in items.deref() {
166 let key = key.str();
171 let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
172 None => (&*key, false),
173 Some(stripped) => (stripped, true),
175 };
176
177 let mime_type =
180 Mime::from_str(key).map_err(|_| Error::Type(String::from("Invalid mime type")))?;
181
182 if clipboard_item
185 .representations
186 .borrow()
187 .iter()
188 .any(|representation| {
189 representation.mime_type == mime_type && representation.is_custom == is_custom
190 })
191 {
192 return Err(Error::Type(String::from("Tried to add a duplicate mime")));
193 }
194
195 let representation = Representation {
200 mime_type,
201 is_custom,
202 data: value.clone(),
203 };
204
205 clipboard_item
207 .representations
208 .borrow_mut()
209 .push(representation);
210 }
211
212 Ok(clipboard_item)
215 }
216
217 fn PresentationStyle(&self) -> PresentationStyle {
219 *self.presentation_style.borrow()
220 }
221
222 fn Types(&self, cx: SafeJSContext, can_gc: CanGc, retval: MutableHandleValue) {
224 self.frozen_types.get_or_init(
225 || {
226 let mut types = Vec::new();
228
229 self.representations
230 .borrow()
231 .iter()
232 .for_each(|representation| {
233 let mime_type_string = representation.mime_type.to_string();
235
236 let mime_type_string = if representation.is_custom {
238 format!("{}{}", CUSTOM_FORMAT_PREFIX, mime_type_string)
239 } else {
240 mime_type_string
241 };
242
243 types.push(DOMString::from(mime_type_string));
245 });
246 types
247 },
248 cx,
249 retval,
250 can_gc,
251 );
252 }
253
254 fn GetType(&self, type_: DOMString, can_gc: CanGc) -> Fallible<Rc<Promise>> {
256 let global = self.global();
258
259 let type_ = type_.str();
264 let (type_, is_custom) = match type_.strip_prefix(CUSTOM_FORMAT_PREFIX) {
265 None => (&*type_, false),
266 Some(stripped) => (stripped, true),
268 };
269
270 let mime_type =
273 Mime::from_str(type_).map_err(|_| Error::Type(String::from("Invalid mime type")))?;
274
275 let item_type_list = self.representations.borrow();
277
278 let p = Promise::new(&global, can_gc);
280
281 for representation in item_type_list.iter() {
283 if representation.mime_type == mime_type && representation.is_custom == is_custom {
285 let representation_data_promise = &representation.data;
287
288 let fulfillment_handler = Box::new(RepresentationDataPromiseFulfillmentHandler {
290 promise: p.clone(),
291 type_: representation.mime_type.to_string(),
292 });
293 let rejection_handler =
294 Box::new(RepresentationDataPromiseRejectionHandler { promise: p.clone() });
295 let handler = PromiseNativeHandler::new(
296 &global,
297 Some(fulfillment_handler),
298 Some(rejection_handler),
299 can_gc,
300 );
301 let realm = enter_realm(&*global);
302 let comp = InRealm::Entered(&realm);
303 representation_data_promise.append_native_handler(&handler, comp, can_gc);
304
305 return Ok(p);
307 }
308 }
309
310 p.reject_error(Error::NotFound(None), can_gc);
312
313 Ok(p)
315 }
316
317 fn Supports(_: &Window, type_: DOMString) -> bool {
319 type_ == "text/plain"
323 }
324}