Skip to main content

script/dom/datatransfer/
datatransfer.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::cell::{Ref, RefCell};
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use js::rust::{HandleObject, MutableHandleValue};
10use net_traits::image_cache::Image;
11use script_bindings::cell::DomRefCell;
12use script_bindings::match_domstring_ascii;
13use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
14
15use crate::dom::bindings::codegen::Bindings::DataTransferBinding::DataTransferMethods;
16use crate::dom::bindings::inheritance::Castable;
17use crate::dom::bindings::reflector::DomGlobal;
18use crate::dom::bindings::root::{Dom, DomRoot};
19use crate::dom::bindings::str::DOMString;
20use crate::dom::datatransferitemlist::DataTransferItemList;
21use crate::dom::element::Element;
22use crate::dom::filelist::FileList;
23use crate::dom::html::htmlimageelement::HTMLImageElement;
24use crate::dom::window::Window;
25use crate::drag_data_store::{DragDataStore, Mode};
26use crate::script_runtime::CanGc;
27
28const VALID_DROP_EFFECTS: [&str; 4] = ["none", "copy", "link", "move"];
29const VALID_EFFECTS_ALLOWED: [&str; 9] = [
30    "none",
31    "copy",
32    "copyLink",
33    "copyMove",
34    "link",
35    "linkMove",
36    "move",
37    "all",
38    "uninitialized",
39];
40
41#[dom_struct]
42pub(crate) struct DataTransfer {
43    reflector_: Reflector,
44    drop_effect: DomRefCell<DOMString>,
45    effect_allowed: DomRefCell<DOMString>,
46    items: Dom<DataTransferItemList>,
47    #[conditional_malloc_size_of]
48    #[no_trace]
49    data_store: Rc<RefCell<Option<DragDataStore>>>,
50}
51
52impl DataTransfer {
53    fn new_inherited(
54        data_store: Rc<RefCell<Option<DragDataStore>>>,
55        item_list: &DataTransferItemList,
56    ) -> DataTransfer {
57        DataTransfer {
58            reflector_: Reflector::new(),
59            drop_effect: DomRefCell::new(DOMString::from("none")),
60            effect_allowed: DomRefCell::new(DOMString::from("none")),
61            items: Dom::from_ref(item_list),
62            data_store,
63        }
64    }
65
66    pub(crate) fn new_with_proto(
67        window: &Window,
68        proto: Option<HandleObject>,
69        can_gc: CanGc,
70        data_store: Rc<RefCell<Option<DragDataStore>>>,
71    ) -> DomRoot<DataTransfer> {
72        let item_list = DataTransferItemList::new(window, Rc::clone(&data_store), can_gc);
73
74        reflect_dom_object_with_proto(
75            Box::new(DataTransfer::new_inherited(data_store, &item_list)),
76            window,
77            proto,
78            can_gc,
79        )
80    }
81
82    pub(crate) fn new(
83        window: &Window,
84        data_store: Rc<RefCell<Option<DragDataStore>>>,
85        can_gc: CanGc,
86    ) -> DomRoot<DataTransfer> {
87        Self::new_with_proto(window, None, can_gc, data_store)
88    }
89
90    pub(crate) fn data_store(&self) -> Option<Ref<'_, DragDataStore>> {
91        Ref::filter_map(self.data_store.borrow(), |data_store| data_store.as_ref()).ok()
92    }
93}
94
95impl DataTransferMethods<crate::DomTypeHolder> for DataTransfer {
96    /// <https://html.spec.whatwg.org/multipage/#dom-datatransfer>
97    fn Constructor(
98        window: &Window,
99        proto: Option<HandleObject>,
100        can_gc: CanGc,
101    ) -> DomRoot<DataTransfer> {
102        let mut drag_data_store = DragDataStore::new();
103        drag_data_store.set_mode(Mode::ReadWrite);
104
105        let data_store = Rc::new(RefCell::new(Some(drag_data_store)));
106
107        DataTransfer::new_with_proto(window, proto, can_gc, data_store)
108    }
109
110    /// <https://html.spec.whatwg.org/multipage/#dom-datatransfer-dropeffect>
111    fn DropEffect(&self) -> DOMString {
112        self.drop_effect.borrow().clone()
113    }
114
115    /// <https://html.spec.whatwg.org/multipage/#dom-datatransfer-dropeffect>
116    fn SetDropEffect(&self, value: DOMString) {
117        if VALID_DROP_EFFECTS.contains(&&*value.str()) {
118            *self.drop_effect.borrow_mut() = value;
119        }
120    }
121
122    /// <https://html.spec.whatwg.org/multipage/#dom-datatransfer-effectallowed>
123    fn EffectAllowed(&self) -> DOMString {
124        self.effect_allowed.borrow().clone()
125    }
126
127    /// <https://html.spec.whatwg.org/multipage/#dom-datatransfer-effectallowed>
128    fn SetEffectAllowed(&self, value: DOMString) {
129        if self
130            .data_store
131            .borrow()
132            .as_ref()
133            .is_some_and(|data_store| data_store.mode() == Mode::ReadWrite) &&
134            VALID_EFFECTS_ALLOWED.contains(&&*value.str())
135        {
136            *self.drop_effect.borrow_mut() = value;
137        }
138    }
139
140    /// <https://html.spec.whatwg.org/multipage/#dom-datatransfer-items>
141    fn Items(&self) -> DomRoot<DataTransferItemList> {
142        DomRoot::from_ref(&self.items)
143    }
144
145    /// <https://html.spec.whatwg.org/multipage/#dom-datatransfer-setdragimage>
146    fn SetDragImage(&self, image: &Element, x: i32, y: i32) {
147        // Step 1 If the DataTransfer is no longer associated with a data store, return.
148        let mut option = self.data_store.borrow_mut();
149        let data_store = match option.as_mut() {
150            Some(value) => value,
151            None => return,
152        };
153
154        // Step 2 If the data store's mode is not the read/write mode, return.
155        if data_store.mode() != Mode::ReadWrite {
156            return;
157        }
158
159        // Step 3
160        if let Some(image) = image.downcast::<HTMLImageElement>() {
161            match image.image_data().as_ref().and_then(Image::as_raster_image) {
162                Some(image) => data_store.set_bitmap(Some(image), x, y),
163                None => warn!("Vector images are not yet supported in setDragImage"),
164            }
165        }
166    }
167
168    /// <https://html.spec.whatwg.org/multipage/#dom-datatransfer-types>
169    fn Types(&self, cx: &mut js::context::JSContext, retval: MutableHandleValue) {
170        self.items.frozen_types(cx, retval);
171    }
172
173    /// <https://html.spec.whatwg.org/multipage/#dom-datatransfer-getdata>
174    fn GetData(&self, mut format: DOMString) -> DOMString {
175        // Step 1 If the DataTransfer object is not associated with a drag data store, then return the empty string.
176        let option = self.data_store.borrow();
177        let data_store = match option.as_ref() {
178            Some(value) => value,
179            None => return DOMString::new(),
180        };
181
182        // Step 2 If the drag data store's mode is the protected mode, then return the empty string.
183        if data_store.mode() == Mode::Protected {
184            return DOMString::new();
185        }
186
187        // Step 3 Let format be the first argument, converted to ASCII lowercase.
188        format.make_ascii_lowercase();
189        // Step 4 Let convert-to-URL be false.
190        let mut convert_to_url = false;
191
192        let type_override = match_domstring_ascii!(format,
193            // Step 5 If format equals "text", change it to "text/plain".
194            "text" => Some(DOMString::from("text/plain")),
195            // Step 6 If format equals "url", change it to "text/uri-list" and set convert-to-URL to true.
196            "url" => {
197                convert_to_url = true;
198                Some(DOMString::from("text/uri-list"))
199            },
200            _ => None,
201        );
202        // Clone outside of `match_domstring_ascii!` to avoid "RefCell already borrowed" panic
203        let type_ = type_override.unwrap_or_else(|| format.clone());
204
205        let data = data_store.find_matching_text(&type_);
206
207        // Step 8
208        if let Some(result) = data {
209            // Step 9 If convert-to-URL is true, then parse result as appropriate for text/uri-list data,
210            // and then set result to the first URL from the list, if any, or the empty string otherwise.
211            if convert_to_url {
212                // TODO parse uri-list as [RFC2483]
213            }
214
215            // Step 10 Return result.
216            result
217        } else {
218            // Step 7 If there is no item in the drag data store item list
219            // whose kind is text and whose type string is equal to format, return the empty string.
220            DOMString::new()
221        }
222    }
223
224    /// <https://html.spec.whatwg.org/multipage/#dom-datatransfer-setdata>
225    fn SetData(&self, format: DOMString, data: DOMString) {
226        // Step 1 If the DataTransfer object is no longer associated with a drag data store, return. Nothing happens.
227        let mut option = self.data_store.borrow_mut();
228        let data_store = match option.as_mut() {
229            Some(value) => value,
230            None => return,
231        };
232
233        // Step 2 If the drag data store's mode is not the read/write mode, return. Nothing happens.
234        if data_store.mode() != Mode::ReadWrite {
235            return;
236        }
237
238        data_store.set_data(format, data);
239        self.items.invalidate_frozen_types();
240    }
241
242    /// <https://html.spec.whatwg.org/multipage/#dom-datatransfer-cleardata>
243    fn ClearData(&self, format: Option<DOMString>) {
244        // Step 1 If the DataTransfer is not associated with a data store, return.
245        let mut option = self.data_store.borrow_mut();
246        let data_store = match option.as_mut() {
247            Some(value) => value,
248            None => return,
249        };
250
251        // Step 2 If the data store is not in the read/write mode, return.
252        if data_store.mode() != Mode::ReadWrite {
253            return;
254        }
255
256        if data_store.clear_data(format) {
257            self.items.invalidate_frozen_types();
258        }
259    }
260
261    /// <https://html.spec.whatwg.org/multipage/#dom-datatransfer-files>
262    fn Files(&self, cx: &mut js::context::JSContext) -> DomRoot<FileList> {
263        // Step 1 Start with an empty list.
264        let mut files = Vec::new();
265
266        // Step 2 If the DataTransfer is not associated with a data store return the empty list.
267        if let Some(data_store) = self.data_store.borrow().as_ref() {
268            data_store.files(cx, &self.global(), &mut files);
269        }
270
271        // Step 5
272        FileList::new(self.global().as_window(), files, CanGc::from_cx(cx))
273    }
274}