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::codegen::Bindings::FileBinding::FileMethods;
18use crate::dom::bindings::inheritance::Castable;
19use crate::dom::bindings::refcounted::Trusted;
20use crate::dom::bindings::reflector::DomGlobal;
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::bindings::str::{DOMString, USVString};
23use crate::dom::file::File;
24use crate::dom::filesystem::FileSystem;
25use crate::dom::filesystemdirectoryentry::FileSystemDirectoryEntry;
26use crate::dom::filesystementry::FileSystemEntry;
27use crate::dom::filesystemfileentry::FileSystemFileEntry;
28use crate::dom::globalscope::GlobalScope;
29use crate::drag_data_store::{DragDataStore, Kind, Mode};
30
31#[dom_struct]
32pub(crate) struct DataTransferItem {
33    reflector_: Reflector,
34    #[conditional_malloc_size_of]
35    #[no_trace]
36    data_store: Rc<RefCell<Option<DragDataStore>>>,
37    id: u16,
38    pending_callbacks: DomRefCell<Vec<PendingStringCallback>>,
39    next_callback: Cell<usize>,
40}
41
42#[derive(JSTraceable, MallocSizeOf)]
43struct PendingStringCallback {
44    id: usize,
45    #[conditional_malloc_size_of]
46    callback: Rc<FunctionStringCallback>,
47}
48
49impl DataTransferItem {
50    fn new_inherited(data_store: Rc<RefCell<Option<DragDataStore>>>, id: u16) -> DataTransferItem {
51        DataTransferItem {
52            reflector_: Reflector::new(),
53            data_store,
54            id,
55            pending_callbacks: Default::default(),
56            next_callback: Cell::new(0),
57        }
58    }
59
60    pub(crate) fn new(
61        cx: &mut JSContext,
62        global: &GlobalScope,
63        data_store: Rc<RefCell<Option<DragDataStore>>>,
64        id: u16,
65    ) -> DomRoot<DataTransferItem> {
66        reflect_dom_object_with_cx(
67            Box::new(DataTransferItem::new_inherited(data_store, id)),
68            global,
69            cx,
70        )
71    }
72
73    fn item_kind(&self) -> Option<Ref<'_, Kind>> {
74        Ref::filter_map(self.data_store.borrow(), |data_store| {
75            data_store
76                .as_ref()
77                .and_then(|data_store| data_store.get_by_id(&self.id))
78        })
79        .ok()
80    }
81
82    fn can_read(&self) -> bool {
83        self.data_store
84            .borrow()
85            .as_ref()
86            .is_some_and(|data_store| data_store.mode() != Mode::Protected)
87    }
88}
89
90impl DataTransferItemMethods<crate::DomTypeHolder> for DataTransferItem {
91    /// <https://html.spec.whatwg.org/multipage/#dom-datatransferitem-kind>
92    fn Kind(&self) -> DOMString {
93        self.item_kind()
94            .map_or(DOMString::new(), |item| match *item {
95                Kind::Text { .. } => DOMString::from("string"),
96                Kind::File { .. } => DOMString::from("file"),
97            })
98    }
99
100    /// <https://html.spec.whatwg.org/multipage/#dom-datatransferitem-type>
101    fn Type(&self) -> DOMString {
102        self.item_kind()
103            .map_or(DOMString::new(), |item| item.type_())
104    }
105
106    /// <https://html.spec.whatwg.org/multipage/#dom-datatransferitem-getasstring>
107    fn GetAsString(&self, callback: Option<Rc<FunctionStringCallback>>) {
108        // Step 1 If the callback is null, return.
109        let Some(callback) = callback else {
110            return;
111        };
112
113        // Step 2 If the DataTransferItem object is not in the read/write mode or the read-only mode, return.
114        if !self.can_read() {
115            return;
116        }
117
118        // Step 3 If the drag data item kind is not text, then return.
119        if let Some(string) = self.item_kind().and_then(|item| item.as_string()) {
120            let id = self.next_callback.get();
121            let pending_callback = PendingStringCallback { id, callback };
122            self.pending_callbacks.borrow_mut().push(pending_callback);
123
124            self.next_callback.set(id + 1);
125            let this = Trusted::new(self);
126
127            // Step 4 Otherwise, queue a task to invoke callback,
128            // passing the actual data of the item represented by the DataTransferItem object as the argument.
129            self.global()
130                .task_manager()
131                .dom_manipulation_task_source()
132                .queue(task!(invoke_callback: move |cx| {
133                    let this = this.root();
134                    let maybe_index = this.pending_callbacks.borrow().iter().position(|val| val.id == id);
135                    if let Some(index) = maybe_index {
136                        let callback = this.pending_callbacks.safe_borrow_mut(cx.no_gc()).swap_remove(index).callback;
137                        let _ = callback.Call__(cx, DOMString::from(string), ExceptionHandling::Report);
138                    }
139                }));
140        }
141    }
142
143    /// <https://html.spec.whatwg.org/multipage/#dom-datatransferitem-getasfile>
144    fn GetAsFile(&self, cx: &mut JSContext) -> Option<DomRoot<File>> {
145        // Step 1 If the DataTransferItem object is not in the read/write mode or the read-only mode, then return null.
146        if !self.can_read() {
147            return None;
148        }
149
150        // Step 2 If the drag data item kind is not File, then return null.
151        // Step 3 Return a new File object representing the actual data
152        // of the item represented by the DataTransferItem object.
153        self.item_kind()?.as_file(cx, &self.global())
154    }
155
156    /// <https://wicg.github.io/entries-api/#dom-datatransferitem-webkitgetasentry>
157    fn WebkitGetAsEntry(&self, cx: &mut JSContext) -> Option<DomRoot<FileSystemEntry>> {
158        // Step 1. Let store be this’s DataTransfer object’s drag data store.
159        // Step 2. If store’s drag data store mode is not read/write mode or read-only mode,
160        // return null and abort these steps.
161        if !self.can_read() {
162            return None;
163        }
164        let global = self.global();
165        // Step 3. Let item be the item in store’s drag data store item list that this represents.
166        // Step 4. If item’s kind is not `File`, then return null and abort these steps.
167        let file = self.item_kind()?.as_file(cx, &global)?;
168
169        // Step 5: Return a new FileSystemEntry object representing the entry.
170        let name = file.Name().to_string();
171
172        let file_entry = FileSystemFileEntry::new(
173            cx,
174            &global,
175            USVString::from(name.clone()),
176            USVString::from(format!("/{}", name)),
177            &file,
178        );
179
180        let root = FileSystemDirectoryEntry::new(
181            cx,
182            &global,
183            USVString::default(),
184            USVString::from(String::from("/")),
185        );
186
187        root.push_child(file_entry.upcast::<FileSystemEntry>());
188
189        let fs = FileSystem::new(
190            cx,
191            &global,
192            USVString::from(String::from("filesystem")),
193            &root,
194        );
195
196        root.set_filesystem(&fs);
197        file_entry.set_filesystem(&fs);
198
199        Some(DomRoot::upcast::<FileSystemEntry>(file_entry))
200    }
201}