Skip to main content

script/dom/file/
file.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::rc::Rc;
6use std::time::SystemTime;
7
8use dom_struct::dom_struct;
9use embedder_traits::SelectedFile;
10use js::context::{JSContext, NoGC};
11use js::rust::HandleObject;
12use script_bindings::domstring::TracedDOMString;
13use script_bindings::reflector::reflect_weak_referenceable_dom_object_with_proto;
14use script_bindings::serializable::Serializable;
15use script_bindings::structuredclone::StructuredData;
16use servo_base::id::{FileId, FileIndex};
17use servo_constellation_traits::{BlobImpl, SerializableFile};
18use time::{Duration, OffsetDateTime};
19
20use crate::dom::bindings::codegen::Bindings::FileBinding;
21use crate::dom::bindings::codegen::Bindings::FileBinding::FileMethods;
22use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlobOrString;
23use crate::dom::bindings::error::{Error, Fallible};
24use crate::dom::bindings::inheritance::Castable;
25use crate::dom::bindings::root::DomRoot;
26use crate::dom::bindings::str::{DOMString, USVString};
27use crate::dom::blob::{Blob, normalize_type_string, process_blob_parts};
28use crate::dom::globalscope::GlobalScope;
29use crate::dom::window::Window;
30
31#[dom_struct]
32pub(crate) struct File {
33    blob: Blob,
34    name: TracedDOMString,
35    modified: SystemTime,
36    // TODO: This depends on the `webkitdirectory` from `HTMLInputElement`.
37    // Then need to change `SelectedFile` in embedder,
38    // and filemanager_thread to recursively walk.
39    webkit_relative_path: USVString,
40}
41
42impl File {
43    fn new_inherited(
44        blob_impl: &BlobImpl,
45        name: DOMString,
46        modified: Option<SystemTime>,
47        webkit_relative_path: USVString,
48    ) -> File {
49        File {
50            blob: Blob::new_inherited(blob_impl),
51            name: name.traced(),
52            // https://w3c.github.io/FileAPI/#dfn-lastModified
53            modified: modified.unwrap_or_else(SystemTime::now),
54            webkit_relative_path,
55        }
56    }
57
58    pub(crate) fn new(
59        cx: &mut JSContext,
60        global: &GlobalScope,
61        blob_impl: BlobImpl,
62        name: DOMString,
63        modified: Option<SystemTime>,
64    ) -> DomRoot<File> {
65        Self::new_with_proto(
66            cx,
67            global,
68            None,
69            blob_impl,
70            name,
71            modified,
72            USVString::default(),
73        )
74    }
75
76    fn new_with_proto(
77        cx: &mut JSContext,
78        global: &GlobalScope,
79        proto: Option<HandleObject>,
80        blob_impl: BlobImpl,
81        name: DOMString,
82        modified: Option<SystemTime>,
83        webkit_relative_path: USVString,
84    ) -> DomRoot<File> {
85        let file = reflect_weak_referenceable_dom_object_with_proto(
86            cx,
87            Rc::new(File::new_inherited(
88                &blob_impl,
89                name,
90                modified,
91                webkit_relative_path,
92            )),
93            global,
94            proto,
95        );
96        global.track_file(&file, blob_impl);
97        file
98    }
99
100    // Construct from selected file message from file manager thread
101    pub(crate) fn new_from_selected(
102        cx: &mut JSContext,
103        window: &Window,
104        selected: SelectedFile,
105    ) -> DomRoot<File> {
106        let name = DOMString::from(
107            selected
108                .filename
109                .to_str()
110                .expect("File name encoding error"),
111        );
112
113        File::new(
114            cx,
115            window.upcast(),
116            BlobImpl::new_from_file(
117                selected.id,
118                selected.filename,
119                selected.size,
120                normalize_type_string(&selected.type_string.to_string()),
121            ),
122            name,
123            Some(selected.modified),
124        )
125    }
126
127    pub(crate) fn file_bytes(&self) -> Result<Vec<u8>, ()> {
128        self.blob.get_bytes()
129    }
130
131    pub(crate) fn name(&self) -> &TracedDOMString {
132        &self.name
133    }
134
135    pub(crate) fn file_type(&self) -> String {
136        self.blob.type_string()
137    }
138
139    pub(crate) fn get_modified(&self) -> SystemTime {
140        self.modified
141    }
142
143    pub(crate) fn serialized_data(&self, no_gc: &NoGC) -> Result<SerializableFile, ()> {
144        let (_, blob_impl) = self.upcast::<Blob>().serialize(no_gc)?;
145        Ok(SerializableFile {
146            blob_impl,
147            name: self.name.to_string(),
148            modified: self.LastModified(),
149            webkit_relative_path: self.webkit_relative_path.to_string(),
150        })
151    }
152}
153
154impl Serializable<crate::DomTypeHolder> for File {
155    type Index = FileIndex;
156    type Data = SerializableFile;
157
158    /// <https://html.spec.whatwg.org/multipage/#serialization-steps>
159    fn serialize(&self, no_gc: &NoGC) -> Result<(FileId, SerializableFile), ()> {
160        Ok((FileId::new(), self.serialized_data(no_gc)?))
161    }
162
163    /// <https://html.spec.whatwg.org/multipage/#deserialization-steps>
164    fn deserialize(
165        cx: &mut JSContext,
166        owner: &GlobalScope,
167        serialized: SerializableFile,
168    ) -> Result<DomRoot<Self>, ()> {
169        let modified = OffsetDateTime::UNIX_EPOCH + Duration::milliseconds(serialized.modified);
170        Ok(File::new_with_proto(
171            cx,
172            owner,
173            None,
174            serialized.blob_impl,
175            serialized.name.into(),
176            Some(modified.into()),
177            USVString::from(serialized.webkit_relative_path),
178        ))
179    }
180
181    fn serialized_storage<'a>(
182        reader: StructuredData<'a, '_>,
183    ) -> &'a mut Option<rustc_hash::FxHashMap<FileId, Self::Data>> {
184        match reader {
185            StructuredData::Reader(r) => &mut r.files,
186            StructuredData::Writer(w) => &mut w.files,
187        }
188    }
189}
190
191impl FileMethods<crate::DomTypeHolder> for File {
192    // https://w3c.github.io/FileAPI/#file-constructor
193    #[expect(non_snake_case)]
194    fn Constructor(
195        cx: &mut JSContext,
196        global: &GlobalScope,
197        proto: Option<HandleObject>,
198        fileBits: Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>,
199        filename: DOMString,
200        filePropertyBag: &FileBinding::FilePropertyBag,
201    ) -> Fallible<DomRoot<File>> {
202        let bytes: Vec<u8> =
203            match process_blob_parts(cx.no_gc(), fileBits, filePropertyBag.parent.endings) {
204                Ok(bytes) => bytes,
205                Err(_) => return Err(Error::InvalidCharacter(None)),
206            };
207
208        let blobPropertyBag = &filePropertyBag.parent;
209        let modified = filePropertyBag
210            .lastModified
211            .map(|modified| OffsetDateTime::UNIX_EPOCH + Duration::milliseconds(modified))
212            .map(Into::into);
213
214        let type_string = normalize_type_string(&blobPropertyBag.type_.str());
215        Ok(File::new_with_proto(
216            cx,
217            global,
218            proto,
219            BlobImpl::new_from_bytes(bytes, type_string),
220            filename,
221            modified,
222            USVString::default(),
223        ))
224    }
225
226    /// <https://w3c.github.io/FileAPI/#dfn-name>
227    fn Name(&self) -> DOMString {
228        self.name.clone().root()
229    }
230
231    /// <https://wicg.github.io/entries-api/#dom-file-webkitrelativepath>
232    fn WebkitRelativePath(&self) -> USVString {
233        self.webkit_relative_path.clone()
234    }
235
236    /// <https://w3c.github.io/FileAPI/#dfn-lastModified>
237    fn LastModified(&self) -> i64 {
238        // This is first converted to a `time::OffsetDateTime` because it might be from before the
239        // Unix epoch in which case we will need to return a negative duration to script.
240        (OffsetDateTime::from(self.modified) - OffsetDateTime::UNIX_EPOCH).whole_milliseconds()
241            as i64
242    }
243}