script/dom/
clipboarditem.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::ops::Deref;
6use std::rc::Rc;
7use std::str::FromStr;
8
9use data_url::mime::Mime;
10use dom_struct::dom_struct;
11use js::rust::{HandleObject, MutableHandleValue};
12use script_bindings::record::Record;
13
14use crate::dom::bindings::cell::DomRefCell;
15use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
16    ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
17};
18use crate::dom::bindings::error::{Error, Fallible};
19use crate::dom::bindings::frozenarray::CachedFrozenArray;
20use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::bindings::str::DOMString;
23use crate::dom::promise::Promise;
24use crate::dom::window::Window;
25use crate::script_runtime::{CanGc, JSContext};
26
27/// <https://w3c.github.io/clipboard-apis/#web-custom-format>
28const CUSTOM_FORMAT_PREFIX: &str = "web ";
29
30/// <https://w3c.github.io/clipboard-apis/#representation>
31#[derive(JSTraceable, MallocSizeOf)]
32pub(super) struct Representation {
33    #[no_trace]
34    #[ignore_malloc_size_of = "Extern type"]
35    pub mime_type: Mime,
36    pub is_custom: bool,
37    #[ignore_malloc_size_of = "Rc is hard"]
38    pub data: Rc<Promise>,
39}
40
41#[dom_struct]
42pub(crate) struct ClipboardItem {
43    reflector_: Reflector,
44    representations: DomRefCell<Vec<Representation>>,
45    presentation_style: DomRefCell<PresentationStyle>,
46    #[ignore_malloc_size_of = "mozjs"]
47    frozen_types: CachedFrozenArray,
48}
49
50impl ClipboardItem {
51    fn new_inherited() -> ClipboardItem {
52        ClipboardItem {
53            reflector_: Reflector::new(),
54            representations: Default::default(),
55            presentation_style: Default::default(),
56            frozen_types: CachedFrozenArray::new(),
57        }
58    }
59
60    fn new(window: &Window, proto: Option<HandleObject>, can_gc: CanGc) -> DomRoot<ClipboardItem> {
61        reflect_dom_object_with_proto(
62            Box::new(ClipboardItem::new_inherited()),
63            window,
64            proto,
65            can_gc,
66        )
67    }
68}
69
70impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
71    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-clipboarditem>
72    fn Constructor(
73        global: &Window,
74        proto: Option<HandleObject>,
75        can_gc: CanGc,
76        items: Record<DOMString, Rc<Promise>>,
77        options: &ClipboardItemOptions,
78    ) -> Fallible<DomRoot<ClipboardItem>> {
79        // Step 1 If items is empty, then throw a TypeError.
80        if items.is_empty() {
81            return Err(Error::Type(String::from("No item provided")));
82        }
83
84        // Step 2 If options is empty, then set options["presentationStyle"] = "unspecified".
85        // NOTE: This is done inside bindings
86
87        // Step 3 Set this's clipboard item to a new clipboard item.
88        let clipboard_item = ClipboardItem::new(global, proto, can_gc);
89
90        // Step 4 Set this's clipboard item's presentation style to options["presentationStyle"].
91        *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
92
93        // Step 6 For each (key, value) in items:
94        for (key, value) in items.deref() {
95            // Step 6.2 Let isCustom be false.
96
97            // Step 6.3 If key starts with `"web "` prefix, then
98            // Step 6.3.1 Remove `"web "` prefix and assign the remaining string to key.
99            let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
100                None => (key.str(), false),
101                // Step 6.3.2 Set isCustom true
102                Some(stripped) => (stripped, true),
103            };
104
105            // Step 6.5 Let mimeType be the result of parsing a MIME type given key.
106            // Step 6.6 If mimeType is failure, then throw a TypeError.
107            let mime_type =
108                Mime::from_str(key).map_err(|_| Error::Type(String::from("Invalid mime type")))?;
109
110            // Step 6.7 If this's clipboard item's list of representations contains a representation
111            // whose MIME type is mimeType and whose [representation/isCustom] is isCustom, then throw a TypeError.
112            if clipboard_item
113                .representations
114                .borrow()
115                .iter()
116                .any(|representation| {
117                    representation.mime_type == mime_type && representation.is_custom == is_custom
118                })
119            {
120                return Err(Error::Type(String::from("Tried to add a duplicate mime")));
121            }
122
123            // Step 6.1 Let representation be a new representation.
124            // Step 6.4 Set representation’s isCustom flag to isCustom.
125            // Step 6.8 Set representation’s MIME type to mimeType.
126            // Step 6.9 Set representation’s data to value.
127            let representation = Representation {
128                mime_type,
129                is_custom,
130                data: value.clone(),
131            };
132
133            // Step 6.10 Append representation to this's clipboard item's list of representations.
134            clipboard_item
135                .representations
136                .borrow_mut()
137                .push(representation);
138        }
139
140        // NOTE: The steps for creating a frozen array from the list of mimeType are done in the Types() method
141
142        Ok(clipboard_item)
143    }
144
145    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-presentationstyle>
146    fn PresentationStyle(&self) -> PresentationStyle {
147        *self.presentation_style.borrow()
148    }
149
150    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-types>
151    fn Types(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
152        self.frozen_types.get_or_init(
153            || {
154                // Step 5 Let types be a list of DOMString.
155                let mut types = Vec::new();
156
157                self.representations
158                    .borrow()
159                    .iter()
160                    .for_each(|representation| {
161                        // Step 6.11 Let mimeTypeString be the result of serializing a MIME type with mimeType.
162                        let mime_type_string = representation.mime_type.to_string();
163
164                        // Step 6.12 If isCustom is true, prefix mimeTypeString with `"web "`.
165                        let mime_type_string = if representation.is_custom {
166                            format!("{}{}", CUSTOM_FORMAT_PREFIX, mime_type_string)
167                        } else {
168                            mime_type_string
169                        };
170
171                        // Step 6.13 Add mimeTypeString to types.
172                        types.push(DOMString::from(mime_type_string));
173                    });
174                types
175            },
176            cx,
177            retval,
178            can_gc,
179        );
180    }
181}