1use 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 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 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 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 fn serialize(&self) -> Result<(FileId, SerializableFile), ()> {
159 Ok((FileId::new(), self.serialized_data()?))
160 }
161
162 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 #[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 fn Name(&self) -> DOMString {
226 self.name.clone()
227 }
228
229 fn WebkitRelativePath(&self) -> USVString {
231 self.webkit_relative_path.clone()
232 }
233
234 fn LastModified(&self) -> i64 {
236 (OffsetDateTime::from(self.modified) - OffsetDateTime::UNIX_EPOCH).whole_milliseconds()
239 as i64
240 }
241}