script/dom/
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 constellation_traits::BlobImpl;
8use dom_struct::dom_struct;
9use embedder_traits::SelectedFile;
10use js::rust::HandleObject;
11use time::{Duration, OffsetDateTime};
12
13use crate::dom::bindings::codegen::Bindings::FileBinding;
14use crate::dom::bindings::codegen::Bindings::FileBinding::FileMethods;
15use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlobOrString;
16use crate::dom::bindings::error::{Error, Fallible};
17use crate::dom::bindings::inheritance::Castable;
18use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
19use crate::dom::bindings::root::DomRoot;
20use crate::dom::bindings::str::DOMString;
21use crate::dom::blob::{Blob, blob_parts_to_bytes, normalize_type_string};
22use crate::dom::globalscope::GlobalScope;
23use crate::dom::window::Window;
24use crate::script_runtime::CanGc;
25
26#[dom_struct]
27pub(crate) struct File {
28    blob: Blob,
29    name: DOMString,
30    modified: SystemTime,
31}
32
33impl File {
34    fn new_inherited(blob_impl: &BlobImpl, name: DOMString, modified: Option<SystemTime>) -> File {
35        File {
36            blob: Blob::new_inherited(blob_impl),
37            name,
38            // https://w3c.github.io/FileAPI/#dfn-lastModified
39            modified: modified.unwrap_or_else(SystemTime::now),
40        }
41    }
42
43    pub(crate) fn new(
44        global: &GlobalScope,
45        blob_impl: BlobImpl,
46        name: DOMString,
47        modified: Option<SystemTime>,
48        can_gc: CanGc,
49    ) -> DomRoot<File> {
50        Self::new_with_proto(global, None, blob_impl, name, modified, can_gc)
51    }
52
53    fn new_with_proto(
54        global: &GlobalScope,
55        proto: Option<HandleObject>,
56        blob_impl: BlobImpl,
57        name: DOMString,
58        modified: Option<SystemTime>,
59        can_gc: CanGc,
60    ) -> DomRoot<File> {
61        let file = reflect_dom_object_with_proto(
62            Box::new(File::new_inherited(&blob_impl, name, modified)),
63            global,
64            proto,
65            can_gc,
66        );
67        global.track_file(&file, blob_impl);
68        file
69    }
70
71    // Construct from selected file message from file manager thread
72    pub(crate) fn new_from_selected(
73        window: &Window,
74        selected: SelectedFile,
75        can_gc: CanGc,
76    ) -> DomRoot<File> {
77        let name = DOMString::from(
78            selected
79                .filename
80                .to_str()
81                .expect("File name encoding error"),
82        );
83
84        File::new(
85            window.upcast(),
86            BlobImpl::new_from_file(
87                selected.id,
88                selected.filename,
89                selected.size,
90                normalize_type_string(&selected.type_string.to_string()),
91            ),
92            name,
93            Some(selected.modified),
94            can_gc,
95        )
96    }
97
98    pub(crate) fn file_bytes(&self) -> Result<Vec<u8>, ()> {
99        self.blob.get_bytes()
100    }
101
102    pub(crate) fn name(&self) -> &DOMString {
103        &self.name
104    }
105
106    pub(crate) fn file_type(&self) -> String {
107        self.blob.type_string()
108    }
109
110    pub(crate) fn get_modified(&self) -> SystemTime {
111        self.modified
112    }
113}
114
115impl FileMethods<crate::DomTypeHolder> for File {
116    // https://w3c.github.io/FileAPI/#file-constructor
117    #[expect(non_snake_case)]
118    fn Constructor(
119        global: &GlobalScope,
120        proto: Option<HandleObject>,
121        can_gc: CanGc,
122        fileBits: Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>,
123        filename: DOMString,
124        filePropertyBag: &FileBinding::FilePropertyBag,
125    ) -> Fallible<DomRoot<File>> {
126        let bytes: Vec<u8> = match blob_parts_to_bytes(fileBits) {
127            Ok(bytes) => bytes,
128            Err(_) => return Err(Error::InvalidCharacter(None)),
129        };
130
131        let blobPropertyBag = &filePropertyBag.parent;
132        let modified = filePropertyBag
133            .lastModified
134            .map(|modified| OffsetDateTime::UNIX_EPOCH + Duration::milliseconds(modified))
135            .map(Into::into);
136
137        let type_string = normalize_type_string(&blobPropertyBag.type_.str());
138        Ok(File::new_with_proto(
139            global,
140            proto,
141            BlobImpl::new_from_bytes(bytes, type_string),
142            filename,
143            modified,
144            can_gc,
145        ))
146    }
147
148    /// <https://w3c.github.io/FileAPI/#dfn-name>
149    fn Name(&self) -> DOMString {
150        self.name.clone()
151    }
152
153    /// <https://w3c.github.io/FileAPI/#dfn-lastModified>
154    fn LastModified(&self) -> i64 {
155        // This is first converted to a `time::OffsetDateTime` because it might be from before the
156        // Unix epoch in which case we will need to return a negative duration to script.
157        (OffsetDateTime::from(self.modified) - OffsetDateTime::UNIX_EPOCH).whole_milliseconds()
158            as i64
159    }
160}