1use std::ops::Deref;
6use std::str::FromStr;
7
8use data_url::mime::Mime;
9use dom_struct::dom_struct;
10use js::context::JSContext;
11use js::realm::CurrentRealm;
12use js::rust::{HandleObject, HandleValue as SafeHandleValue, MutableHandleValue};
13use script_bindings::cell::DomRefCell;
14use script_bindings::record::Record;
15use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
16use servo_constellation_traits::BlobImpl;
17
18use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
19 ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
20};
21use crate::dom::bindings::conversions::{
22 ConversionResult, FromJSValConvertible, StringificationBehavior,
23};
24use crate::dom::bindings::error::{Error, Fallible};
25use crate::dom::bindings::frozenarray::CachedFrozenArray;
26use crate::dom::bindings::reflector::DomGlobal;
27use crate::dom::bindings::root::DomRoot;
28use crate::dom::bindings::str::DOMString;
29use crate::dom::blob::Blob;
30use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
31use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
32use crate::dom::window::Window;
33
34#[derive(Clone, JSTraceable, MallocSizeOf)]
37#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
38struct RepresentationDataPromiseFulfillmentHandler {
39 promise: TracedPromise,
40 type_: String,
41}
42
43impl js::gc::Rootable for RepresentationDataPromiseFulfillmentHandler {}
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::from_jsval(cx, v, StringificationBehavior::Default)
52 {
53 Ok(ConversionResult::Success(s)) => s.as_bytes(cx.no_gc()).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(cx, &blob_data);
66 }
67 else if DomRoot::<Blob>::from_jsval(cx, v, ())
69 .is_ok_and(|result| result.get_success_value().is_some())
70 {
71 self.promise.resolve(cx, v);
73 }
74 }
75}
76
77#[derive(Clone, JSTraceable, MallocSizeOf)]
80#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
81struct RepresentationDataPromiseRejectionHandler {
82 promise: TracedPromise,
83}
84
85impl js::gc::Rootable for RepresentationDataPromiseRejectionHandler {}
86
87impl Callback for RepresentationDataPromiseRejectionHandler {
88 fn callback(&self, cx: &mut CurrentRealm, _v: SafeHandleValue) {
90 self.promise.reject_error(cx, Error::NotFound(None));
92 }
93}
94
95const CUSTOM_FORMAT_PREFIX: &str = "web ";
97
98#[derive(JSTraceable, MallocSizeOf)]
100#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
101pub(super) struct Representation {
102 #[no_trace]
103 #[ignore_malloc_size_of = "Extern type"]
104 pub mime_type: Mime,
105 pub is_custom: bool,
106 pub data: TracedPromise,
107}
108
109impl js::gc::Rootable for Representation {}
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(cx, Box::new(ClipboardItem::new_inherited()), window, proto)
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, RootedPromise>,
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
161 .presentation_style
162 .safe_borrow_mut(cx.no_gc()) = 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(c"Invalid mime type".to_owned()))?;
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(c"Tried to add a duplicate mime".to_owned()));
193 }
194
195 rooted!(&in(cx) let mut representation = Some(Representation {
200 mime_type,
201 is_custom,
202 data: value.to_traced(),
203 }));
204
205 clipboard_item
207 .representations
208 .safe_borrow_mut(cx.no_gc())
209 .push(representation.take().unwrap());
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: &mut JSContext, retval: MutableHandleValue) {
224 self.frozen_types.get_or_init(
225 cx,
226 || {
227 let mut types = Vec::new();
229
230 self.representations
231 .borrow()
232 .iter()
233 .for_each(|representation| {
234 let mime_type_string = representation.mime_type.to_string();
236
237 let mime_type_string = if representation.is_custom {
239 format!("{}{}", CUSTOM_FORMAT_PREFIX, mime_type_string)
240 } else {
241 mime_type_string
242 };
243
244 types.push(DOMString::from(mime_type_string));
246 });
247 types
248 },
249 retval,
250 );
251 }
252
253 fn GetType(&self, realm: &mut CurrentRealm, type_: DOMString) -> Fallible<RootedPromise> {
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.root(realm);
286
287 rooted!(&in(realm) let mut fulfillment_handler = Some(RepresentationDataPromiseFulfillmentHandler {
289 promise: p.to_traced(),
290 type_: representation.mime_type.to_string(),
291 }));
292 rooted!(&in(realm) let mut rejection_handler = Some(RepresentationDataPromiseRejectionHandler {
293 promise: p.to_traced(),
294 }));
295
296 let handler = PromiseNativeHandler::new(
297 realm,
298 &global,
299 fulfillment_handler
300 .take()
301 .map(|handler| Box::new(handler) as Box<dyn Callback>),
302 rejection_handler
303 .take()
304 .map(|handler| Box::new(handler) as Box<dyn Callback>),
305 );
306 representation_data_promise.append_native_handler(realm, &handler);
307
308 return Ok(p);
310 }
311 }
312
313 p.reject_error(realm, Error::NotFound(None));
315
316 Ok(p)
318 }
319
320 fn Supports(_: &Window, type_: DOMString) -> bool {
322 type_ == "text/plain"
326 }
327}