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;
34use crate::script_runtime::CanGc;
35
36#[derive(Clone, JSTraceable, MallocSizeOf)]
39struct RepresentationDataPromiseFulfillmentHandler {
40 #[conditional_malloc_size_of]
41 promise: Rc<Promise>,
42 type_: String,
43}
44
45impl Callback for RepresentationDataPromiseFulfillmentHandler {
46 fn callback(&self, cx: &mut CurrentRealm, v: SafeHandleValue) {
48 if v.get().is_string() {
50 let data_as_bytes =
52 match DOMString::safe_from_jsval(cx, v, StringificationBehavior::Default) {
53 Ok(ConversionResult::Success(s)) => s.as_bytes().to_owned(),
54 _ => return,
55 };
56
57 let blob_data = Blob::new(
59 cx,
60 &self.promise.global(),
61 BlobImpl::new_from_bytes(data_as_bytes, self.type_.clone()),
62 );
63
64 self.promise.resolve_native_with_cx(cx, &blob_data);
66 }
67 else if DomRoot::<Blob>::safe_from_jsval(cx, v, ())
69 .is_ok_and(|result| result.get_success_value().is_some())
70 {
71 self.promise.resolve_with_cx(cx, v);
73 }
74 }
75}
76
77#[derive(Clone, JSTraceable, MallocSizeOf)]
80struct RepresentationDataPromiseRejectionHandler {
81 #[conditional_malloc_size_of]
82 promise: Rc<Promise>,
83}
84
85impl Callback for RepresentationDataPromiseRejectionHandler {
86 fn callback(&self, cx: &mut CurrentRealm, _v: SafeHandleValue) {
88 self.promise.reject_error_with_cx(cx, Error::NotFound(None));
90 }
91}
92
93const CUSTOM_FORMAT_PREFIX: &str = "web ";
95
96#[derive(JSTraceable, MallocSizeOf)]
98pub(super) struct Representation {
99 #[no_trace]
100 #[ignore_malloc_size_of = "Extern type"]
101 pub mime_type: Mime,
102 pub is_custom: bool,
103 #[conditional_malloc_size_of]
104 pub data: Rc<Promise>,
105}
106
107#[dom_struct]
108pub(crate) struct ClipboardItem {
109 reflector_: Reflector,
110 representations: DomRefCell<Vec<Representation>>,
111 presentation_style: DomRefCell<PresentationStyle>,
112 #[ignore_malloc_size_of = "mozjs"]
113 frozen_types: CachedFrozenArray,
114}
115
116impl ClipboardItem {
117 fn new_inherited() -> ClipboardItem {
118 ClipboardItem {
119 reflector_: Reflector::new(),
120 representations: Default::default(),
121 presentation_style: Default::default(),
122 frozen_types: CachedFrozenArray::new(),
123 }
124 }
125
126 fn new(
127 cx: &mut JSContext,
128 window: &Window,
129 proto: Option<HandleObject>,
130 ) -> DomRoot<ClipboardItem> {
131 reflect_dom_object_with_proto_and_cx(
132 Box::new(ClipboardItem::new_inherited()),
133 window,
134 proto,
135 cx,
136 )
137 }
138}
139
140impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
141 fn Constructor(
143 cx: &mut JSContext,
144 global: &Window,
145 proto: Option<HandleObject>,
146 items: Record<DOMString, Rc<Promise>>,
147 options: &ClipboardItemOptions,
148 ) -> Fallible<DomRoot<ClipboardItem>> {
149 if items.is_empty() {
151 return Err(Error::Type(c"No item provided".to_owned()));
152 }
153
154 let clipboard_item = ClipboardItem::new(cx, global, proto);
159
160 *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
162
163 for (key, value) in items.deref() {
165 let key = key.str();
170 let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
171 None => (&*key, false),
172 Some(stripped) => (stripped, true),
174 };
175
176 let mime_type =
179 Mime::from_str(key).map_err(|_| Error::Type(c"Invalid mime type".to_owned()))?;
180
181 if clipboard_item
184 .representations
185 .borrow()
186 .iter()
187 .any(|representation| {
188 representation.mime_type == mime_type && representation.is_custom == is_custom
189 })
190 {
191 return Err(Error::Type(c"Tried to add a duplicate mime".to_owned()));
192 }
193
194 let representation = Representation {
199 mime_type,
200 is_custom,
201 data: value.clone(),
202 };
203
204 clipboard_item
206 .representations
207 .borrow_mut()
208 .push(representation);
209 }
210
211 Ok(clipboard_item)
214 }
215
216 fn PresentationStyle(&self) -> PresentationStyle {
218 *self.presentation_style.borrow()
219 }
220
221 fn Types(&self, cx: &mut JSContext, retval: MutableHandleValue) {
223 self.frozen_types.get_or_init(
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 cx.into(),
248 retval,
249 CanGc::from_cx(cx),
250 );
251 }
252
253 fn GetType(&self, realm: &mut CurrentRealm, type_: DOMString) -> Fallible<Rc<Promise>> {
255 let global = self.global();
257
258 let type_ = type_.str();
263 let (type_, is_custom) = match type_.strip_prefix(CUSTOM_FORMAT_PREFIX) {
264 None => (&*type_, false),
265 Some(stripped) => (stripped, true),
267 };
268
269 let mime_type =
272 Mime::from_str(type_).map_err(|_| Error::Type(c"Invalid mime type".to_owned()))?;
273
274 let item_type_list = self.representations.borrow();
276
277 let p = Promise::new_in_realm(realm);
279
280 for representation in item_type_list.iter() {
282 if representation.mime_type == mime_type && representation.is_custom == is_custom {
284 let representation_data_promise = &representation.data;
286
287 let fulfillment_handler = Box::new(RepresentationDataPromiseFulfillmentHandler {
289 promise: p.clone(),
290 type_: representation.mime_type.to_string(),
291 });
292 let rejection_handler =
293 Box::new(RepresentationDataPromiseRejectionHandler { promise: p.clone() });
294
295 let handler = PromiseNativeHandler::new(
296 realm,
297 &global,
298 Some(fulfillment_handler),
299 Some(rejection_handler),
300 );
301 representation_data_promise.append_native_handler(realm, &handler);
302
303 return Ok(p);
305 }
306 }
307
308 p.reject_error_with_cx(realm, Error::NotFound(None));
310
311 Ok(p)
313 }
314
315 fn Supports(_: &Window, type_: DOMString) -> bool {
317 type_ == "text/plain"
321 }
322}