Skip to main content

script/dom/datatransfer/
datatransferitem.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::{Cell, Ref, RefCell};
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use script_bindings::cell::DomRefCell;
11use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
12
13use crate::dom::bindings::callback::ExceptionHandling;
14use crate::dom::bindings::codegen::Bindings::DataTransferItemBinding::{
15    DataTransferItemMethods, FunctionStringCallback,
16};
17use crate::dom::bindings::refcounted::Trusted;
18use crate::dom::bindings::reflector::DomGlobal;
19use crate::dom::bindings::root::DomRoot;
20use crate::dom::bindings::str::DOMString;
21use crate::dom::file::File;
22use crate::dom::globalscope::GlobalScope;
23use crate::drag_data_store::{DragDataStore, Kind, Mode};
24
25#[dom_struct]
26pub(crate) struct DataTransferItem {
27    reflector_: Reflector,
28    #[conditional_malloc_size_of]
29    #[no_trace]
30    data_store: Rc<RefCell<Option<DragDataStore>>>,
31    id: u16,
32    pending_callbacks: DomRefCell<Vec<PendingStringCallback>>,
33    next_callback: Cell<usize>,
34}
35
36#[derive(JSTraceable, MallocSizeOf)]
37struct PendingStringCallback {
38    id: usize,
39    #[conditional_malloc_size_of]
40    callback: Rc<FunctionStringCallback>,
41}
42
43impl DataTransferItem {
44    fn new_inherited(data_store: Rc<RefCell<Option<DragDataStore>>>, id: u16) -> DataTransferItem {
45        DataTransferItem {
46            reflector_: Reflector::new(),
47            data_store,
48            id,
49            pending_callbacks: Default::default(),
50            next_callback: Cell::new(0),
51        }
52    }
53
54    pub(crate) fn new(
55        cx: &mut JSContext,
56        global: &GlobalScope,
57        data_store: Rc<RefCell<Option<DragDataStore>>>,
58        id: u16,
59    ) -> DomRoot<DataTransferItem> {
60        reflect_dom_object_with_cx(
61            Box::new(DataTransferItem::new_inherited(data_store, id)),
62            global,
63            cx,
64        )
65    }
66
67    fn item_kind(&self) -> Option<Ref<'_, Kind>> {
68        Ref::filter_map(self.data_store.borrow(), |data_store| {
69            data_store
70                .as_ref()
71                .and_then(|data_store| data_store.get_by_id(&self.id))
72        })
73        .ok()
74    }
75
76    fn can_read(&self) -> bool {
77        self.data_store
78            .borrow()
79            .as_ref()
80            .is_some_and(|data_store| data_store.mode() != Mode::Protected)
81    }
82}
83
84impl DataTransferItemMethods<crate::DomTypeHolder> for DataTransferItem {
85    /// <https://html.spec.whatwg.org/multipage/#dom-datatransferitem-kind>
86    fn Kind(&self) -> DOMString {
87        self.item_kind()
88            .map_or(DOMString::new(), |item| match *item {
89                Kind::Text { .. } => DOMString::from("string"),
90                Kind::File { .. } => DOMString::from("file"),
91            })
92    }
93
94    /// <https://html.spec.whatwg.org/multipage/#dom-datatransferitem-type>
95    fn Type(&self) -> DOMString {
96        self.item_kind()
97            .map_or(DOMString::new(), |item| item.type_())
98    }
99
100    /// <https://html.spec.whatwg.org/multipage/#dom-datatransferitem-getasstring>
101    fn GetAsString(&self, callback: Option<Rc<FunctionStringCallback>>) {
102        // Step 1 If the callback is null, return.
103        let Some(callback) = callback else {
104            return;
105        };
106
107        // Step 2 If the DataTransferItem object is not in the read/write mode or the read-only mode, return.
108        if !self.can_read() {
109            return;
110        }
111
112        // Step 3 If the drag data item kind is not text, then return.
113        if let Some(string) = self.item_kind().and_then(|item| item.as_string()) {
114            let id = self.next_callback.get();
115            let pending_callback = PendingStringCallback { id, callback };
116            self.pending_callbacks.borrow_mut().push(pending_callback);
117
118            self.next_callback.set(id + 1);
119            let this = Trusted::new(self);
120
121            // Step 4 Otherwise, queue a task to invoke callback,
122            // passing the actual data of the item represented by the DataTransferItem object as the argument.
123            self.global()
124                .task_manager()
125                .dom_manipulation_task_source()
126                .queue(task!(invoke_callback: move |cx| {
127                    let maybe_index = this.root().pending_callbacks.borrow().iter().position(|val| val.id == id);
128                    if let Some(index) = maybe_index {
129                        let callback = this.root().pending_callbacks.borrow_mut().swap_remove(index).callback;
130                        let _ = callback.Call__(cx, DOMString::from(string), ExceptionHandling::Report);
131                    }
132                }));
133        }
134    }
135
136    /// <https://html.spec.whatwg.org/multipage/#dom-datatransferitem-getasfile>
137    fn GetAsFile(&self, cx: &mut JSContext) -> Option<DomRoot<File>> {
138        // Step 1 If the DataTransferItem object is not in the read/write mode or the read-only mode, then return null.
139        if !self.can_read() {
140            return None;
141        }
142
143        // Step 2 If the drag data item kind is not File, then return null.
144        // Step 3 Return a new File object representing the actual data
145        // of the item represented by the DataTransferItem object.
146        self.item_kind()
147            .and_then(|item| item.as_file(cx, &self.global()))
148    }
149}