1use std::ops::Deref;
6use std::rc::Rc;
7use std::str::FromStr;
8
9use data_url::mime::Mime;
10use dom_struct::dom_struct;
11use js::context::JSContext;
12use js::realm::CurrentRealm;
13use js::rust::{HandleObject, HandleValue as SafeHandleValue, MutableHandleValue};
14use script_bindings::cell::DomRefCell;
15use script_bindings::record::Record;
16use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
17use servo_constellation_traits::BlobImpl;
18
19use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
20 ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
21};
22use crate::dom::bindings::conversions::{
23 ConversionResult, FromJSValConvertible, StringificationBehavior,
24};
25use crate::dom::bindings::error::{Error, Fallible};
26use crate::dom::bindings::frozenarray::CachedFrozenArray;
27use crate::dom::bindings::reflector::DomGlobal;
28use crate::dom::bindings::root::DomRoot;
29use crate::dom::bindings::str::DOMString;
30use crate::dom::blob::Blob;
31use crate::dom::promise::Promise;
32use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
33use crate::dom::window::Window;
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 if v.get().is_string() {
49 let data_as_bytes =
51 match DOMString::safe_from_jsval(cx, v, StringificationBehavior::Default) {
52 Ok(ConversionResult::Success(s)) => s.as_bytes().to_owned(),
53 _ => return,
54 };
55
56 let blob_data = Blob::new(
58 cx,
59 &self.promise.global(),
60 BlobImpl::new_from_bytes(data_as_bytes, self.type_.clone()),
61 );
62
63 self.promise.resolve_native(cx, &blob_data);
65 }
66 else if DomRoot::<Blob>::safe_from_jsval(cx, v, ())
68 .is_ok_and(|result| result.get_success_value().is_some())
69 {
70 self.promise.resolve(cx, v);
72 }
73 }
74}
75
76#[derive(Clone, JSTraceable, MallocSizeOf)]
79struct RepresentationDataPromiseRejectionHandler {
80 #[conditional_malloc_size_of]
81 promise: Rc<Promise>,
82}
83
84impl Callback for RepresentationDataPromiseRejectionHandler {
85 fn callback(&self, cx: &mut CurrentRealm, _v: SafeHandleValue) {
87 self.promise.reject_error(cx, Error::NotFound(None));
89 }
90}
91
92const CUSTOM_FORMAT_PREFIX: &str = "web ";
94
95#[derive(JSTraceable, MallocSizeOf)]
97pub(super) struct Representation {
98 #[no_trace]
99 #[ignore_malloc_size_of = "Extern type"]
100 pub mime_type: Mime,
101 pub is_custom: bool,
102 #[conditional_malloc_size_of]
103 pub data: Rc<Promise>,
104}
105
106#[dom_struct]
107pub(crate) struct ClipboardItem {
108 reflector_: Reflector,
109 representations: DomRefCell<Vec<Representation>>,
110 presentation_style: DomRefCell<PresentationStyle>,
111 #[ignore_malloc_size_of = "mozjs"]
112 frozen_types: CachedFrozenArray,
113}
114
115impl ClipboardItem {
116 fn new_inherited() -> ClipboardItem {
117 ClipboardItem {
118 reflector_: Reflector::new(),
119 representations: Default::default(),
120 presentation_style: Default::default(),
121 frozen_types: CachedFrozenArray::new(),
122 }
123 }
124
125 fn new(
126 cx: &mut JSContext,
127 window: &Window,
128 proto: Option<HandleObject>,
129 ) -> DomRoot<ClipboardItem> {
130 reflect_dom_object_with_proto_and_cx(
131 Box::new(ClipboardItem::new_inherited()),
132 window,
133 proto,
134 cx,
135 )
136 }
137}
138
139impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
140 fn Constructor(
142 cx: &mut JSContext,
143 global: &Window,
144 proto: Option<HandleObject>,
145 items: Record<DOMString, Rc<Promise>>,
146 options: &ClipboardItemOptions,
147 ) -> Fallible<DomRoot<ClipboardItem>> {
148 if items.is_empty() {
150 return Err(Error::Type(c"No item provided".to_owned()));
151 }
152
153 let clipboard_item = ClipboardItem::new(cx, global, proto);
158
159 *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
161
162 for (key, value) in items.deref() {
164 let key = key.str();
169 let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
170 None => (&*key, false),
171 Some(stripped) => (stripped, true),
173 };
174
175 let mime_type =
178 Mime::from_str(key).map_err(|_| Error::Type(c"Invalid mime type".to_owned()))?;
179
180 if clipboard_item
183 .representations
184 .borrow()
185 .iter()
186 .any(|representation| {
187 representation.mime_type == mime_type && representation.is_custom == is_custom
188 })
189 {
190 return Err(Error::Type(c"Tried to add a duplicate mime".to_owned()));
191 }
192
193 let representation = Representation {
198 mime_type,
199 is_custom,
200 data: value.clone(),
201 };
202
203 clipboard_item
205 .representations
206 .borrow_mut()
207 .push(representation);
208 }
209
210 Ok(clipboard_item)
213 }
214
215 fn PresentationStyle(&self) -> PresentationStyle {
217 *self.presentation_style.borrow()
218 }
219
220 fn Types(&self, cx: &mut JSContext, retval: MutableHandleValue) {
222 self.frozen_types.get_or_init(
223 cx,
224 || {
225 let mut types = Vec::new();
227
228 self.representations
229 .borrow()
230 .iter()
231 .for_each(|representation| {
232 let mime_type_string = representation.mime_type.to_string();
234
235 let mime_type_string = if representation.is_custom {
237 format!("{}{}", CUSTOM_FORMAT_PREFIX, mime_type_string)
238 } else {
239 mime_type_string
240 };
241
242 types.push(DOMString::from(mime_type_string));
244 });
245 types
246 },
247 retval,
248 );
249 }
250
251 fn GetType(&self, realm: &mut CurrentRealm, type_: DOMString) -> Fallible<Rc<Promise>> {
253 let global = self.global();
255
256 let type_ = type_.str();
261 let (type_, is_custom) = match type_.strip_prefix(CUSTOM_FORMAT_PREFIX) {
262 None => (&*type_, false),
263 Some(stripped) => (stripped, true),
265 };
266
267 let mime_type =
270 Mime::from_str(type_).map_err(|_| Error::Type(c"Invalid mime type".to_owned()))?;
271
272 let item_type_list = self.representations.borrow();
274
275 let p = Promise::new_in_realm(realm);
277
278 for representation in item_type_list.iter() {
280 if representation.mime_type == mime_type && representation.is_custom == is_custom {
282 let representation_data_promise = &representation.data;
284
285 let fulfillment_handler = Box::new(RepresentationDataPromiseFulfillmentHandler {
287 promise: p.clone(),
288 type_: representation.mime_type.to_string(),
289 });
290 let rejection_handler =
291 Box::new(RepresentationDataPromiseRejectionHandler { promise: p.clone() });
292
293 let handler = PromiseNativeHandler::new(
294 realm,
295 &global,
296 Some(fulfillment_handler),
297 Some(rejection_handler),
298 );
299 representation_data_promise.append_native_handler(realm, &handler);
300
301 return Ok(p);
303 }
304 }
305
306 p.reject_error(realm, Error::NotFound(None));
308
309 Ok(p)
311 }
312
313 fn Supports(_: &Window, type_: DOMString) -> bool {
315 type_ == "text/plain"
319 }
320}