1use 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::domstring::TracedDOMString;
13use script_bindings::reflector::reflect_weak_referenceable_dom_object_with_proto;
14use script_bindings::serializable::Serializable;
15use script_bindings::structuredclone::StructuredData;
16use servo_base::id::{FileId, FileIndex};
17use servo_constellation_traits::{BlobImpl, SerializableFile};
18use time::{Duration, OffsetDateTime};
19
20use crate::dom::bindings::codegen::Bindings::FileBinding;
21use crate::dom::bindings::codegen::Bindings::FileBinding::FileMethods;
22use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlobOrString;
23use crate::dom::bindings::error::{Error, Fallible};
24use crate::dom::bindings::inheritance::Castable;
25use crate::dom::bindings::root::DomRoot;
26use crate::dom::bindings::str::{DOMString, USVString};
27use crate::dom::blob::{Blob, normalize_type_string, process_blob_parts};
28use crate::dom::globalscope::GlobalScope;
29use crate::dom::window::Window;
30
31#[dom_struct]
32pub(crate) struct File {
33 blob: Blob,
34 name: TracedDOMString,
35 modified: SystemTime,
36 webkit_relative_path: USVString,
40}
41
42impl File {
43 fn new_inherited(
44 blob_impl: &BlobImpl,
45 name: DOMString,
46 modified: Option<SystemTime>,
47 webkit_relative_path: USVString,
48 ) -> File {
49 File {
50 blob: Blob::new_inherited(blob_impl),
51 name: name.traced(),
52 modified: modified.unwrap_or_else(SystemTime::now),
54 webkit_relative_path,
55 }
56 }
57
58 pub(crate) fn new(
59 cx: &mut JSContext,
60 global: &GlobalScope,
61 blob_impl: BlobImpl,
62 name: DOMString,
63 modified: Option<SystemTime>,
64 ) -> DomRoot<File> {
65 Self::new_with_proto(
66 cx,
67 global,
68 None,
69 blob_impl,
70 name,
71 modified,
72 USVString::default(),
73 )
74 }
75
76 fn new_with_proto(
77 cx: &mut JSContext,
78 global: &GlobalScope,
79 proto: Option<HandleObject>,
80 blob_impl: BlobImpl,
81 name: DOMString,
82 modified: Option<SystemTime>,
83 webkit_relative_path: USVString,
84 ) -> DomRoot<File> {
85 let file = reflect_weak_referenceable_dom_object_with_proto(
86 cx,
87 Rc::new(File::new_inherited(
88 &blob_impl,
89 name,
90 modified,
91 webkit_relative_path,
92 )),
93 global,
94 proto,
95 );
96 global.track_file(&file, blob_impl);
97 file
98 }
99
100 pub(crate) fn new_from_selected(
102 cx: &mut JSContext,
103 window: &Window,
104 selected: SelectedFile,
105 ) -> DomRoot<File> {
106 let name = DOMString::from(
107 selected
108 .filename
109 .to_str()
110 .expect("File name encoding error"),
111 );
112
113 File::new(
114 cx,
115 window.upcast(),
116 BlobImpl::new_from_file(
117 selected.id,
118 selected.filename,
119 selected.size,
120 normalize_type_string(&selected.type_string.to_string()),
121 ),
122 name,
123 Some(selected.modified),
124 )
125 }
126
127 pub(crate) fn file_bytes(&self) -> Result<Vec<u8>, ()> {
128 self.blob.get_bytes()
129 }
130
131 pub(crate) fn name(&self) -> &TracedDOMString {
132 &self.name
133 }
134
135 pub(crate) fn file_type(&self) -> String {
136 self.blob.type_string()
137 }
138
139 pub(crate) fn get_modified(&self) -> SystemTime {
140 self.modified
141 }
142
143 pub(crate) fn serialized_data(&self, no_gc: &NoGC) -> Result<SerializableFile, ()> {
144 let (_, blob_impl) = self.upcast::<Blob>().serialize(no_gc)?;
145 Ok(SerializableFile {
146 blob_impl,
147 name: self.name.to_string(),
148 modified: self.LastModified(),
149 webkit_relative_path: self.webkit_relative_path.to_string(),
150 })
151 }
152}
153
154impl Serializable<crate::DomTypeHolder> for File {
155 type Index = FileIndex;
156 type Data = SerializableFile;
157
158 fn serialize(&self, no_gc: &NoGC) -> Result<(FileId, SerializableFile), ()> {
160 Ok((FileId::new(), self.serialized_data(no_gc)?))
161 }
162
163 fn deserialize(
165 cx: &mut JSContext,
166 owner: &GlobalScope,
167 serialized: SerializableFile,
168 ) -> Result<DomRoot<Self>, ()> {
169 let modified = OffsetDateTime::UNIX_EPOCH + Duration::milliseconds(serialized.modified);
170 Ok(File::new_with_proto(
171 cx,
172 owner,
173 None,
174 serialized.blob_impl,
175 serialized.name.into(),
176 Some(modified.into()),
177 USVString::from(serialized.webkit_relative_path),
178 ))
179 }
180
181 fn serialized_storage<'a>(
182 reader: StructuredData<'a, '_>,
183 ) -> &'a mut Option<rustc_hash::FxHashMap<FileId, Self::Data>> {
184 match reader {
185 StructuredData::Reader(r) => &mut r.files,
186 StructuredData::Writer(w) => &mut w.files,
187 }
188 }
189}
190
191impl FileMethods<crate::DomTypeHolder> for File {
192 #[expect(non_snake_case)]
194 fn Constructor(
195 cx: &mut JSContext,
196 global: &GlobalScope,
197 proto: Option<HandleObject>,
198 fileBits: Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>,
199 filename: DOMString,
200 filePropertyBag: &FileBinding::FilePropertyBag,
201 ) -> Fallible<DomRoot<File>> {
202 let bytes: Vec<u8> =
203 match process_blob_parts(cx.no_gc(), fileBits, filePropertyBag.parent.endings) {
204 Ok(bytes) => bytes,
205 Err(_) => return Err(Error::InvalidCharacter(None)),
206 };
207
208 let blobPropertyBag = &filePropertyBag.parent;
209 let modified = filePropertyBag
210 .lastModified
211 .map(|modified| OffsetDateTime::UNIX_EPOCH + Duration::milliseconds(modified))
212 .map(Into::into);
213
214 let type_string = normalize_type_string(&blobPropertyBag.type_.str());
215 Ok(File::new_with_proto(
216 cx,
217 global,
218 proto,
219 BlobImpl::new_from_bytes(bytes, type_string),
220 filename,
221 modified,
222 USVString::default(),
223 ))
224 }
225
226 fn Name(&self) -> DOMString {
228 self.name.clone().root()
229 }
230
231 fn WebkitRelativePath(&self) -> USVString {
233 self.webkit_relative_path.clone()
234 }
235
236 fn LastModified(&self) -> i64 {
238 (OffsetDateTime::from(self.modified) - OffsetDateTime::UNIX_EPOCH).whole_milliseconds()
241 as i64
242 }
243}