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::rust::{HandleObject, HandleValue as SafeHandleValue, MutableHandleValue};
13use script_bindings::record::Record;
14
15use crate::dom::bindings::cell::DomRefCell;
16use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
17 ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
18};
19use crate::dom::bindings::conversions::{
20 ConversionResult, SafeFromJSValConvertible, StringificationBehavior,
21};
22use crate::dom::bindings::error::{Error, Fallible};
23use crate::dom::bindings::frozenarray::CachedFrozenArray;
24use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
25use crate::dom::bindings::root::DomRoot;
26use crate::dom::bindings::str::DOMString;
27use crate::dom::blob::Blob;
28use crate::dom::promise::Promise;
29use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
30use crate::dom::window::Window;
31use crate::realms::{InRealm, enter_realm};
32use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
33
34#[derive(Clone, JSTraceable, MallocSizeOf)]
37struct RepresentationDataPromiseFulfillmentHandler {
38 #[conditional_malloc_size_of]
39 promise: Rc<Promise>,
40 type_: String,
41}
42
43impl Callback for RepresentationDataPromiseFulfillmentHandler {
44 fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) {
46 if v.get().is_string() {
48 let data_as_bytes =
50 match DOMString::safe_from_jsval(cx, v, StringificationBehavior::Default) {
51 Ok(ConversionResult::Success(s)) => s.as_bytes().to_owned(),
52 _ => return,
53 };
54
55 let blob_data = Blob::new(
57 &self.promise.global(),
58 BlobImpl::new_from_bytes(data_as_bytes, self.type_.clone()),
59 can_gc,
60 );
61
62 self.promise.resolve_native(&blob_data, can_gc);
64 }
65 else if DomRoot::<Blob>::safe_from_jsval(cx, v, ())
67 .is_ok_and(|result| result.get_success_value().is_some())
68 {
69 self.promise.resolve(cx, v, can_gc);
71 }
72 }
73}
74
75#[derive(Clone, JSTraceable, MallocSizeOf)]
78struct RepresentationDataPromiseRejectionHandler {
79 #[conditional_malloc_size_of]
80 promise: Rc<Promise>,
81}
82
83impl Callback for RepresentationDataPromiseRejectionHandler {
84 fn callback(&self, _cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) {
86 self.promise.reject_error(Error::NotFound(None), can_gc);
88 }
89}
90
91const CUSTOM_FORMAT_PREFIX: &str = "web ";
93
94#[derive(JSTraceable, MallocSizeOf)]
96pub(super) struct Representation {
97 #[no_trace]
98 #[ignore_malloc_size_of = "Extern type"]
99 pub mime_type: Mime,
100 pub is_custom: bool,
101 #[conditional_malloc_size_of]
102 pub data: Rc<Promise>,
103}
104
105#[dom_struct]
106pub(crate) struct ClipboardItem {
107 reflector_: Reflector,
108 representations: DomRefCell<Vec<Representation>>,
109 presentation_style: DomRefCell<PresentationStyle>,
110 #[ignore_malloc_size_of = "mozjs"]
111 frozen_types: CachedFrozenArray,
112}
113
114impl ClipboardItem {
115 fn new_inherited() -> ClipboardItem {
116 ClipboardItem {
117 reflector_: Reflector::new(),
118 representations: Default::default(),
119 presentation_style: Default::default(),
120 frozen_types: CachedFrozenArray::new(),
121 }
122 }
123
124 fn new(window: &Window, proto: Option<HandleObject>, can_gc: CanGc) -> DomRoot<ClipboardItem> {
125 reflect_dom_object_with_proto(
126 Box::new(ClipboardItem::new_inherited()),
127 window,
128 proto,
129 can_gc,
130 )
131 }
132}
133
134impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
135 fn Constructor(
137 global: &Window,
138 proto: Option<HandleObject>,
139 can_gc: CanGc,
140 items: Record<DOMString, Rc<Promise>>,
141 options: &ClipboardItemOptions,
142 ) -> Fallible<DomRoot<ClipboardItem>> {
143 if items.is_empty() {
145 return Err(Error::Type(String::from("No item provided")));
146 }
147
148 let clipboard_item = ClipboardItem::new(global, proto, can_gc);
153
154 *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
156
157 for (key, value) in items.deref() {
159 let key = key.str();
164 let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
165 None => (&*key, false),
166 Some(stripped) => (stripped, true),
168 };
169
170 let mime_type =
173 Mime::from_str(key).map_err(|_| Error::Type(String::from("Invalid mime type")))?;
174
175 if clipboard_item
178 .representations
179 .borrow()
180 .iter()
181 .any(|representation| {
182 representation.mime_type == mime_type && representation.is_custom == is_custom
183 })
184 {
185 return Err(Error::Type(String::from("Tried to add a duplicate mime")));
186 }
187
188 let representation = Representation {
193 mime_type,
194 is_custom,
195 data: value.clone(),
196 };
197
198 clipboard_item
200 .representations
201 .borrow_mut()
202 .push(representation);
203 }
204
205 Ok(clipboard_item)
208 }
209
210 fn PresentationStyle(&self) -> PresentationStyle {
212 *self.presentation_style.borrow()
213 }
214
215 fn Types(&self, cx: SafeJSContext, can_gc: CanGc, retval: MutableHandleValue) {
217 self.frozen_types.get_or_init(
218 || {
219 let mut types = Vec::new();
221
222 self.representations
223 .borrow()
224 .iter()
225 .for_each(|representation| {
226 let mime_type_string = representation.mime_type.to_string();
228
229 let mime_type_string = if representation.is_custom {
231 format!("{}{}", CUSTOM_FORMAT_PREFIX, mime_type_string)
232 } else {
233 mime_type_string
234 };
235
236 types.push(DOMString::from(mime_type_string));
238 });
239 types
240 },
241 cx,
242 retval,
243 can_gc,
244 );
245 }
246
247 fn GetType(&self, type_: DOMString, can_gc: CanGc) -> Fallible<Rc<Promise>> {
249 let global = self.global();
251
252 let type_ = type_.str();
257 let (type_, is_custom) = match type_.strip_prefix(CUSTOM_FORMAT_PREFIX) {
258 None => (&*type_, false),
259 Some(stripped) => (stripped, true),
261 };
262
263 let mime_type =
266 Mime::from_str(type_).map_err(|_| Error::Type(String::from("Invalid mime type")))?;
267
268 let item_type_list = self.representations.borrow();
270
271 let p = Promise::new(&global, can_gc);
273
274 for representation in item_type_list.iter() {
276 if representation.mime_type == mime_type && representation.is_custom == is_custom {
278 let representation_data_promise = &representation.data;
280
281 let fulfillment_handler = Box::new(RepresentationDataPromiseFulfillmentHandler {
283 promise: p.clone(),
284 type_: representation.mime_type.to_string(),
285 });
286 let rejection_handler =
287 Box::new(RepresentationDataPromiseRejectionHandler { promise: p.clone() });
288 let handler = PromiseNativeHandler::new(
289 &global,
290 Some(fulfillment_handler),
291 Some(rejection_handler),
292 can_gc,
293 );
294 let realm = enter_realm(&*global);
295 let comp = InRealm::Entered(&realm);
296 representation_data_promise.append_native_handler(&handler, comp, can_gc);
297
298 return Ok(p);
300 }
301 }
302
303 p.reject_error(Error::NotFound(None), can_gc);
305
306 Ok(p)
308 }
309
310 fn Supports(_: &Window, type_: DOMString) -> bool {
312 type_ == "text/plain"
316 }
317}