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};
12
13use crate::dom::bindings::callback::{ExceptionHandling, RootedCallback, TracedCallback};
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::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)]
43#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
44struct PendingStringCallback {
45    id: usize,
46    callback: TracedCallback<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(
67            cx,
68            Box::new(DataTransferItem::new_inherited(data_store, id)),
69            global,
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_static("string"),
96                Kind::File { .. } => DOMString::from_static("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<RootedCallback<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            self.pending_callbacks
122                .borrow_mut()
123                .push(PendingStringCallback {
124                    id,
125                    callback: callback.to_traced(),
126                });
127
128            self.next_callback.set(id + 1);
129            let this = Trusted::new(self);
130
131            // Step 4 Otherwise, queue a task to invoke callback,
132            // passing the actual data of the item represented by the DataTransferItem object as the argument.
133            self.global()
134                .task_manager()
135                .dom_manipulation_task_source()
136                .queue(task!(invoke_callback: move |cx| {
137                    let this = this.root();
138                    let maybe_index = this.pending_callbacks.borrow().iter().position(|val| val.id == id);
139                    if let Some(index) = maybe_index {
140                        rooted!(&in(cx) let callback = this
141                            .pending_callbacks
142                            .safe_borrow_mut(cx.no_gc())
143                            .swap_remove(index)
144                            .callback);
145                        let _ = callback.Call__(cx, DOMString::from(string), ExceptionHandling::Report);
146                    }
147                }));
148        }
149    }
150
151    /// <https://html.spec.whatwg.org/multipage/#dom-datatransferitem-getasfile>
152    fn GetAsFile(&self, cx: &mut JSContext) -> Option<DomRoot<File>> {
153        // Step 1 If the DataTransferItem object is not in the read/write mode or the read-only mode, then return null.
154        if !self.can_read() {
155            return None;
156        }
157
158        // Step 2 If the drag data item kind is not File, then return null.
159        // Step 3 Return a new File object representing the actual data
160        // of the item represented by the DataTransferItem object.
161        self.item_kind()?.as_file(cx, &self.global())
162    }
163
164    /// <https://wicg.github.io/entries-api/#dom-datatransferitem-webkitgetasentry>
165    fn WebkitGetAsEntry(&self, cx: &mut JSContext) -> Option<DomRoot<FileSystemEntry>> {
166        // Step 1. Let store be this’s DataTransfer object’s drag data store.
167        // Step 2. If store’s drag data store mode is not read/write mode or read-only mode,
168        // return null and abort these steps.
169        if !self.can_read() {
170            return None;
171        }
172        let global = self.global();
173        // Step 3. Let item be the item in store’s drag data store item list that this represents.
174        // Step 4. If item’s kind is not `File`, then return null and abort these steps.
175        let file = self.item_kind()?.as_file(cx, &global)?;
176
177        // Step 5: Return a new FileSystemEntry object representing the entry.
178        let name = file.Name().to_string();
179
180        let file_entry = FileSystemFileEntry::new(
181            cx,
182            &global,
183            USVString::from(name.clone()),
184            USVString::from(format!("/{}", name)),
185            &file,
186        );
187
188        let root = FileSystemDirectoryEntry::new(
189            cx,
190            &global,
191            USVString::default(),
192            USVString::from(String::from("/")),
193        );
194
195        root.push_child(file_entry.upcast::<FileSystemEntry>());
196
197        let fs = FileSystem::new(
198            cx,
199            &global,
200            USVString::from(String::from("filesystem")),
201            &root,
202        );
203
204        root.set_filesystem(&fs);
205        file_entry.set_filesystem(&fs);
206
207        Some(DomRoot::upcast::<FileSystemEntry>(file_entry))
208    }
209}