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::reflector::reflect_weak_referenceable_dom_object_with_proto;
13use servo_base::id::{FileId, FileIndex};
14use servo_constellation_traits::{BlobImpl, SerializableFile};
15use time::{Duration, OffsetDateTime};
16
17use crate::dom::bindings::codegen::Bindings::FileBinding;
18use crate::dom::bindings::codegen::Bindings::FileBinding::FileMethods;
19use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlobOrString;
20use crate::dom::bindings::error::{Error, Fallible};
21use crate::dom::bindings::inheritance::Castable;
22use crate::dom::bindings::root::DomRoot;
23use crate::dom::bindings::serializable::Serializable;
24use crate::dom::bindings::str::{DOMString, USVString};
25use crate::dom::bindings::structuredclone::StructuredData;
26use crate::dom::blob::{Blob, normalize_type_string, process_blob_parts};
27use crate::dom::globalscope::GlobalScope;
28use crate::dom::window::Window;
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        cx: &mut JSContext,
59        global: &GlobalScope,
60        blob_impl: BlobImpl,
61        name: DOMString,
62        modified: Option<SystemTime>,
63    ) -> DomRoot<File> {
64        Self::new_with_proto(
65            cx,
66            global,
67            None,
68            blob_impl,
69            name,
70            modified,
71            USVString::default(),
72        )
73    }
74
75    fn new_with_proto(
76        cx: &mut JSContext,
77        global: &GlobalScope,
78        proto: Option<HandleObject>,
79        blob_impl: BlobImpl,
80        name: DOMString,
81        modified: Option<SystemTime>,
82        webkit_relative_path: USVString,
83    ) -> DomRoot<File> {
84        let file = reflect_weak_referenceable_dom_object_with_proto(
85            cx,
86            Rc::new(File::new_inherited(
87                &blob_impl,
88                name,
89                modified,
90                webkit_relative_path,
91            )),
92            global,
93            proto,
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        cx: &mut JSContext,
102        window: &Window,
103        selected: SelectedFile,
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            cx,
114            window.upcast(),
115            BlobImpl::new_from_file(
116                selected.id,
117                selected.filename,
118                selected.size,
119                normalize_type_string(&selected.type_string.to_string()),
120            ),
121            name,
122            Some(selected.modified),
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, no_gc: &NoGC) -> Result<SerializableFile, ()> {
143        let (_, blob_impl) = self.upcast::<Blob>().serialize(no_gc)?;
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, no_gc: &NoGC) -> Result<(FileId, SerializableFile), ()> {
159        Ok((FileId::new(), self.serialized_data(no_gc)?))
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            cx,
171            owner,
172            None,
173            serialized.blob_impl,
174            serialized.name.into(),
175            Some(modified.into()),
176            USVString::from(serialized.webkit_relative_path),
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> =
202            match process_blob_parts(cx.no_gc(), fileBits, filePropertyBag.parent.endings) {
203                Ok(bytes) => bytes,
204                Err(_) => return Err(Error::InvalidCharacter(None)),
205            };
206
207        let blobPropertyBag = &filePropertyBag.parent;
208        let modified = filePropertyBag
209            .lastModified
210            .map(|modified| OffsetDateTime::UNIX_EPOCH + Duration::milliseconds(modified))
211            .map(Into::into);
212
213        let type_string = normalize_type_string(&blobPropertyBag.type_.str());
214        Ok(File::new_with_proto(
215            cx,
216            global,
217            proto,
218            BlobImpl::new_from_bytes(bytes, type_string),
219            filename,
220            modified,
221            USVString::default(),
222        ))
223    }
224
225    /// <https://w3c.github.io/FileAPI/#dfn-name>
226    fn Name(&self) -> DOMString {
227        self.name.clone()
228    }
229
230    /// <https://wicg.github.io/entries-api/#dom-file-webkitrelativepath>
231    fn WebkitRelativePath(&self) -> USVString {
232        self.webkit_relative_path.clone()
233    }
234
235    /// <https://w3c.github.io/FileAPI/#dfn-lastModified>
236    fn LastModified(&self) -> i64 {
237        // This is first converted to a `time::OffsetDateTime` because it might be from before the
238        // Unix epoch in which case we will need to return a negative duration to script.
239        (OffsetDateTime::from(self.modified) - OffsetDateTime::UNIX_EPOCH).whole_milliseconds()
240            as i64
241    }
242}