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