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