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, SafeFromJSValConvertible, 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 = match DOMString::safe_from_jsval(
52 cx.into(),
53 v,
54 StringificationBehavior::Default,
55 CanGc::from_cx(cx),
56 ) {
57 Ok(ConversionResult::Success(s)) => s.as_bytes().to_owned(),
58 _ => return,
59 };
60
61 let blob_data = Blob::new(
63 cx,
64 &self.promise.global(),
65 BlobImpl::new_from_bytes(data_as_bytes, self.type_.clone()),
66 );
67
68 self.promise.resolve_native_with_cx(cx, &blob_data);
70 }
71 else if DomRoot::<Blob>::safe_from_jsval(cx.into(), v, (), CanGc::from_cx(cx))
73 .is_ok_and(|result| result.get_success_value().is_some())
74 {
75 self.promise.resolve_with_cx(cx, v);
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 self.promise.reject_error_with_cx(cx, Error::NotFound(None));
94 }
95}
96
97const CUSTOM_FORMAT_PREFIX: &str = "web ";
99
100#[derive(JSTraceable, MallocSizeOf)]
102pub(super) struct Representation {
103 #[no_trace]
104 #[ignore_malloc_size_of = "Extern type"]
105 pub mime_type: Mime,
106 pub is_custom: bool,
107 #[conditional_malloc_size_of]
108 pub data: Rc<Promise>,
109}
110
111#[dom_struct]
112pub(crate) struct ClipboardItem {
113 reflector_: Reflector,
114 representations: DomRefCell<Vec<Representation>>,
115 presentation_style: DomRefCell<PresentationStyle>,
116 #[ignore_malloc_size_of = "mozjs"]
117 frozen_types: CachedFrozenArray,
118}
119
120impl ClipboardItem {
121 fn new_inherited() -> ClipboardItem {
122 ClipboardItem {
123 reflector_: Reflector::new(),
124 representations: Default::default(),
125 presentation_style: Default::default(),
126 frozen_types: CachedFrozenArray::new(),
127 }
128 }
129
130 fn new(
131 cx: &mut JSContext,
132 window: &Window,
133 proto: Option<HandleObject>,
134 ) -> DomRoot<ClipboardItem> {
135 reflect_dom_object_with_proto_and_cx(
136 Box::new(ClipboardItem::new_inherited()),
137 window,
138 proto,
139 cx,
140 )
141 }
142}
143
144impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
145 fn Constructor(
147 cx: &mut JSContext,
148 global: &Window,
149 proto: Option<HandleObject>,
150 items: Record<DOMString, Rc<Promise>>,
151 options: &ClipboardItemOptions,
152 ) -> Fallible<DomRoot<ClipboardItem>> {
153 if items.is_empty() {
155 return Err(Error::Type(c"No item provided".to_owned()));
156 }
157
158 let clipboard_item = ClipboardItem::new(cx, global, proto);
163
164 *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
166
167 for (key, value) in items.deref() {
169 let key = key.str();
174 let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
175 None => (&*key, false),
176 Some(stripped) => (stripped, true),
178 };
179
180 let mime_type =
183 Mime::from_str(key).map_err(|_| Error::Type(c"Invalid mime type".to_owned()))?;
184
185 if clipboard_item
188 .representations
189 .borrow()
190 .iter()
191 .any(|representation| {
192 representation.mime_type == mime_type && representation.is_custom == is_custom
193 })
194 {
195 return Err(Error::Type(c"Tried to add a duplicate mime".to_owned()));
196 }
197
198 let representation = Representation {
203 mime_type,
204 is_custom,
205 data: value.clone(),
206 };
207
208 clipboard_item
210 .representations
211 .borrow_mut()
212 .push(representation);
213 }
214
215 Ok(clipboard_item)
218 }
219
220 fn PresentationStyle(&self) -> PresentationStyle {
222 *self.presentation_style.borrow()
223 }
224
225 fn Types(&self, cx: &mut JSContext, retval: MutableHandleValue) {
227 self.frozen_types.get_or_init(
228 || {
229 let mut types = Vec::new();
231
232 self.representations
233 .borrow()
234 .iter()
235 .for_each(|representation| {
236 let mime_type_string = representation.mime_type.to_string();
238
239 let mime_type_string = if representation.is_custom {
241 format!("{}{}", CUSTOM_FORMAT_PREFIX, mime_type_string)
242 } else {
243 mime_type_string
244 };
245
246 types.push(DOMString::from(mime_type_string));
248 });
249 types
250 },
251 cx.into(),
252 retval,
253 CanGc::from_cx(cx),
254 );
255 }
256
257 fn GetType(&self, realm: &mut CurrentRealm, type_: DOMString) -> Fallible<Rc<Promise>> {
259 let global = self.global();
261
262 let type_ = type_.str();
267 let (type_, is_custom) = match type_.strip_prefix(CUSTOM_FORMAT_PREFIX) {
268 None => (&*type_, false),
269 Some(stripped) => (stripped, true),
271 };
272
273 let mime_type =
276 Mime::from_str(type_).map_err(|_| Error::Type(c"Invalid mime type".to_owned()))?;
277
278 let item_type_list = self.representations.borrow();
280
281 let p = Promise::new_in_realm(realm);
283
284 for representation in item_type_list.iter() {
286 if representation.mime_type == mime_type && representation.is_custom == is_custom {
288 let representation_data_promise = &representation.data;
290
291 let fulfillment_handler = Box::new(RepresentationDataPromiseFulfillmentHandler {
293 promise: p.clone(),
294 type_: representation.mime_type.to_string(),
295 });
296 let rejection_handler =
297 Box::new(RepresentationDataPromiseRejectionHandler { promise: p.clone() });
298
299 let handler = PromiseNativeHandler::new(
300 realm,
301 &global,
302 Some(fulfillment_handler),
303 Some(rejection_handler),
304 );
305 representation_data_promise.append_native_handler(realm, &handler);
306
307 return Ok(p);
309 }
310 }
311
312 p.reject_error(Error::NotFound(None), CanGc::from_cx(realm));
314
315 Ok(p)
317 }
318
319 fn Supports(_: &Window, type_: DOMString) -> bool {
321 type_ == "text/plain"
325 }
326}