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;
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}
36
37impl File {
38    fn new_inherited(blob_impl: &BlobImpl, name: DOMString, modified: Option<SystemTime>) -> File {
39        File {
40            blob: Blob::new_inherited(blob_impl),
41            name,
42            // https://w3c.github.io/FileAPI/#dfn-lastModified
43            modified: modified.unwrap_or_else(SystemTime::now),
44        }
45    }
46
47    pub(crate) fn new(
48        global: &GlobalScope,
49        blob_impl: BlobImpl,
50        name: DOMString,
51        modified: Option<SystemTime>,
52        can_gc: CanGc,
53    ) -> DomRoot<File> {
54        Self::new_with_proto(global, None, blob_impl, name, modified, can_gc)
55    }
56
57    fn new_with_proto(
58        global: &GlobalScope,
59        proto: Option<HandleObject>,
60        blob_impl: BlobImpl,
61        name: DOMString,
62        modified: Option<SystemTime>,
63        can_gc: CanGc,
64    ) -> DomRoot<File> {
65        let file = reflect_dom_object_with_proto(
66            Box::new(File::new_inherited(&blob_impl, name, modified)),
67            global,
68            proto,
69            can_gc,
70        );
71        global.track_file(&file, blob_impl);
72        file
73    }
74
75    // Construct from selected file message from file manager thread
76    pub(crate) fn new_from_selected(
77        window: &Window,
78        selected: SelectedFile,
79        can_gc: CanGc,
80    ) -> DomRoot<File> {
81        let name = DOMString::from(
82            selected
83                .filename
84                .to_str()
85                .expect("File name encoding error"),
86        );
87
88        File::new(
89            window.upcast(),
90            BlobImpl::new_from_file(
91                selected.id,
92                selected.filename,
93                selected.size,
94                normalize_type_string(&selected.type_string.to_string()),
95            ),
96            name,
97            Some(selected.modified),
98            can_gc,
99        )
100    }
101
102    pub(crate) fn file_bytes(&self) -> Result<Vec<u8>, ()> {
103        self.blob.get_bytes()
104    }
105
106    pub(crate) fn name(&self) -> &DOMString {
107        &self.name
108    }
109
110    pub(crate) fn file_type(&self) -> String {
111        self.blob.type_string()
112    }
113
114    pub(crate) fn get_modified(&self) -> SystemTime {
115        self.modified
116    }
117
118    pub(crate) fn serialized_data(&self) -> Result<SerializableFile, ()> {
119        let (_, blob_impl) = self.upcast::<Blob>().serialize()?;
120        Ok(SerializableFile {
121            blob_impl,
122            name: self.name.to_string(),
123            modified: self.LastModified(),
124        })
125    }
126}
127
128impl Serializable for File {
129    type Index = FileIndex;
130    type Data = SerializableFile;
131
132    /// <https://html.spec.whatwg.org/multipage/#serialization-steps>
133    fn serialize(&self) -> Result<(FileId, SerializableFile), ()> {
134        Ok((FileId::new(), self.serialized_data()?))
135    }
136
137    /// <https://html.spec.whatwg.org/multipage/#deserialization-steps>
138    fn deserialize(
139        cx: &mut JSContext,
140        owner: &GlobalScope,
141        serialized: SerializableFile,
142    ) -> Result<DomRoot<Self>, ()> {
143        let modified = OffsetDateTime::UNIX_EPOCH + Duration::milliseconds(serialized.modified);
144        Ok(File::new(
145            owner,
146            serialized.blob_impl,
147            serialized.name.into(),
148            Some(modified.into()),
149            CanGc::from_cx(cx),
150        ))
151    }
152
153    fn serialized_storage<'a>(
154        reader: StructuredData<'a, '_>,
155    ) -> &'a mut Option<rustc_hash::FxHashMap<FileId, Self::Data>> {
156        match reader {
157            StructuredData::Reader(r) => &mut r.files,
158            StructuredData::Writer(w) => &mut w.files,
159        }
160    }
161}
162
163impl FileMethods<crate::DomTypeHolder> for File {
164    // https://w3c.github.io/FileAPI/#file-constructor
165    #[expect(non_snake_case)]
166    fn Constructor(
167        global: &GlobalScope,
168        proto: Option<HandleObject>,
169        can_gc: CanGc,
170        fileBits: Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>,
171        filename: DOMString,
172        filePropertyBag: &FileBinding::FilePropertyBag,
173    ) -> Fallible<DomRoot<File>> {
174        let bytes: Vec<u8> = match process_blob_parts(fileBits, filePropertyBag.parent.endings) {
175            Ok(bytes) => bytes,
176            Err(_) => return Err(Error::InvalidCharacter(None)),
177        };
178
179        let blobPropertyBag = &filePropertyBag.parent;
180        let modified = filePropertyBag
181            .lastModified
182            .map(|modified| OffsetDateTime::UNIX_EPOCH + Duration::milliseconds(modified))
183            .map(Into::into);
184
185        let type_string = normalize_type_string(&blobPropertyBag.type_.str());
186        Ok(File::new_with_proto(
187            global,
188            proto,
189            BlobImpl::new_from_bytes(bytes, type_string),
190            filename,
191            modified,
192            can_gc,
193        ))
194    }
195
196    /// <https://w3c.github.io/FileAPI/#dfn-name>
197    fn Name(&self) -> DOMString {
198        self.name.clone()
199    }
200
201    /// <https://w3c.github.io/FileAPI/#dfn-lastModified>
202    fn LastModified(&self) -> i64 {
203        // This is first converted to a `time::OffsetDateTime` because it might be from before the
204        // Unix epoch in which case we will need to return a negative duration to script.
205        (OffsetDateTime::from(self.modified) - OffsetDateTime::UNIX_EPOCH).whole_milliseconds()
206            as i64
207    }
208}