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::record::Record;
15use servo_constellation_traits::BlobImpl;
16
17use crate::dom::bindings::cell::DomRefCell;
18use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
19 ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
20};
21use crate::dom::bindings::conversions::{
22 ConversionResult, SafeFromJSValConvertible, StringificationBehavior,
23};
24use crate::dom::bindings::error::{Error, Fallible};
25use crate::dom::bindings::frozenarray::CachedFrozenArray;
26use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto_and_cx};
27use crate::dom::bindings::root::DomRoot;
28use crate::dom::bindings::str::DOMString;
29use crate::dom::blob::Blob;
30use crate::dom::promise::Promise;
31use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
32use crate::dom::window::Window;
33use crate::realms::InRealm;
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 let can_gc = CanGc::from_cx(cx);
49 if v.get().is_string() {
51 let data_as_bytes = match DOMString::safe_from_jsval(
53 cx.into(),
54 v,
55 StringificationBehavior::Default,
56 can_gc,
57 ) {
58 Ok(ConversionResult::Success(s)) => s.as_bytes().to_owned(),
59 _ => return,
60 };
61
62 let blob_data = Blob::new(
64 &self.promise.global(),
65 BlobImpl::new_from_bytes(data_as_bytes, self.type_.clone()),
66 can_gc,
67 );
68
69 self.promise.resolve_native(&blob_data, can_gc);
71 }
72 else if DomRoot::<Blob>::safe_from_jsval(cx.into(), v, (), can_gc)
74 .is_ok_and(|result| result.get_success_value().is_some())
75 {
76 self.promise.resolve(cx.into(), v, can_gc);
78 }
79 }
80}
81
82#[derive(Clone, JSTraceable, MallocSizeOf)]
85struct RepresentationDataPromiseRejectionHandler {
86 #[conditional_malloc_size_of]
87 promise: Rc<Promise>,
88}
89
90impl Callback for RepresentationDataPromiseRejectionHandler {
91 fn callback(&self, cx: &mut CurrentRealm, _v: SafeHandleValue) {
93 self.promise
95 .reject_error(Error::NotFound(None), CanGc::from_cx(cx));
96 }
97}
98
99const CUSTOM_FORMAT_PREFIX: &str = "web ";
101
102#[derive(JSTraceable, MallocSizeOf)]
104pub(super) struct Representation {
105 #[no_trace]
106 #[ignore_malloc_size_of = "Extern type"]
107 pub mime_type: Mime,
108 pub is_custom: bool,
109 #[conditional_malloc_size_of]
110 pub data: Rc<Promise>,
111}
112
113#[dom_struct]
114pub(crate) struct ClipboardItem {
115 reflector_: Reflector,
116 representations: DomRefCell<Vec<Representation>>,
117 presentation_style: DomRefCell<PresentationStyle>,
118 #[ignore_malloc_size_of = "mozjs"]
119 frozen_types: CachedFrozenArray,
120}
121
122impl ClipboardItem {
123 fn new_inherited() -> ClipboardItem {
124 ClipboardItem {
125 reflector_: Reflector::new(),
126 representations: Default::default(),
127 presentation_style: Default::default(),
128 frozen_types: CachedFrozenArray::new(),
129 }
130 }
131
132 fn new(
133 cx: &mut JSContext,
134 window: &Window,
135 proto: Option<HandleObject>,
136 ) -> DomRoot<ClipboardItem> {
137 reflect_dom_object_with_proto_and_cx(
138 Box::new(ClipboardItem::new_inherited()),
139 window,
140 proto,
141 cx,
142 )
143 }
144}
145
146impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
147 fn Constructor(
149 cx: &mut JSContext,
150 global: &Window,
151 proto: Option<HandleObject>,
152 items: Record<DOMString, Rc<Promise>>,
153 options: &ClipboardItemOptions,
154 ) -> Fallible<DomRoot<ClipboardItem>> {
155 if items.is_empty() {
157 return Err(Error::Type(c"No item provided".to_owned()));
158 }
159
160 let clipboard_item = ClipboardItem::new(cx, global, proto);
165
166 *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
168
169 for (key, value) in items.deref() {
171 let key = key.str();
176 let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
177 None => (&*key, false),
178 Some(stripped) => (stripped, true),
180 };
181
182 let mime_type =
185 Mime::from_str(key).map_err(|_| Error::Type(c"Invalid mime type".to_owned()))?;
186
187 if clipboard_item
190 .representations
191 .borrow()
192 .iter()
193 .any(|representation| {
194 representation.mime_type == mime_type && representation.is_custom == is_custom
195 })
196 {
197 return Err(Error::Type(c"Tried to add a duplicate mime".to_owned()));
198 }
199
200 let representation = Representation {
205 mime_type,
206 is_custom,
207 data: value.clone(),
208 };
209
210 clipboard_item
212 .representations
213 .borrow_mut()
214 .push(representation);
215 }
216
217 Ok(clipboard_item)
220 }
221
222 fn PresentationStyle(&self) -> PresentationStyle {
224 *self.presentation_style.borrow()
225 }
226
227 fn Types(&self, cx: &mut JSContext, retval: MutableHandleValue) {
229 self.frozen_types.get_or_init(
230 || {
231 let mut types = Vec::new();
233
234 self.representations
235 .borrow()
236 .iter()
237 .for_each(|representation| {
238 let mime_type_string = representation.mime_type.to_string();
240
241 let mime_type_string = if representation.is_custom {
243 format!("{}{}", CUSTOM_FORMAT_PREFIX, mime_type_string)
244 } else {
245 mime_type_string
246 };
247
248 types.push(DOMString::from(mime_type_string));
250 });
251 types
252 },
253 cx.into(),
254 retval,
255 CanGc::from_cx(cx),
256 );
257 }
258
259 fn GetType(&self, realm: &mut CurrentRealm, type_: DOMString) -> Fallible<Rc<Promise>> {
261 let global = self.global();
263
264 let type_ = type_.str();
269 let (type_, is_custom) = match type_.strip_prefix(CUSTOM_FORMAT_PREFIX) {
270 None => (&*type_, false),
271 Some(stripped) => (stripped, true),
273 };
274
275 let mime_type =
278 Mime::from_str(type_).map_err(|_| Error::Type(c"Invalid mime type".to_owned()))?;
279
280 let item_type_list = self.representations.borrow();
282
283 let p = Promise::new_in_realm(realm);
285
286 for representation in item_type_list.iter() {
288 if representation.mime_type == mime_type && representation.is_custom == is_custom {
290 let representation_data_promise = &representation.data;
292
293 let fulfillment_handler = Box::new(RepresentationDataPromiseFulfillmentHandler {
295 promise: p.clone(),
296 type_: representation.mime_type.to_string(),
297 });
298 let rejection_handler =
299 Box::new(RepresentationDataPromiseRejectionHandler { promise: p.clone() });
300 let in_realm_proof = realm.into();
301 let comp = InRealm::Already(&in_realm_proof);
302 let handler = PromiseNativeHandler::new(
303 &global,
304 Some(fulfillment_handler),
305 Some(rejection_handler),
306 CanGc::from_cx(realm),
307 );
308
309 representation_data_promise.append_native_handler(
310 &handler,
311 comp,
312 CanGc::from_cx(realm),
313 );
314
315 return Ok(p);
317 }
318 }
319
320 p.reject_error(Error::NotFound(None), CanGc::from_cx(realm));
322
323 Ok(p)
325 }
326
327 fn Supports(_: &Window, type_: DOMString) -> bool {
329 type_ == "text/plain"
333 }
334}