1use std::cell::Cell;
6use std::ptr;
7use std::rc::Rc;
8
9use base64::Engine;
10use dom_struct::dom_struct;
11use encoding_rs::{Encoding, UTF_8};
12use js::jsapi::{Heap, JSObject};
13use js::jsval::{self, JSVal};
14use js::rust::HandleObject;
15use js::typedarray::ArrayBufferU8;
16use mime::{self, Mime};
17use script_bindings::cell::DomRefCell;
18use script_bindings::num::Finite;
19use script_bindings::reflector::reflect_dom_object_with_proto;
20use stylo_atoms::Atom;
21
22use crate::dom::bindings::buffer_source::create_buffer_source;
23use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
24use crate::dom::bindings::codegen::Bindings::FileReaderBinding::{
25 FileReaderConstants, FileReaderMethods,
26};
27use crate::dom::bindings::codegen::UnionTypes::StringOrObject;
28use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
29use crate::dom::bindings::inheritance::Castable;
30use crate::dom::bindings::refcounted::Trusted;
31use crate::dom::bindings::reflector::DomGlobal;
32use crate::dom::bindings::root::{DomRoot, MutNullableDom};
33use crate::dom::bindings::str::DOMString;
34use crate::dom::bindings::trace::RootedTraceableBox;
35use crate::dom::blob::Blob;
36use crate::dom::domexception::{DOMErrorName, DOMException};
37use crate::dom::event::{Event, EventBubbles, EventCancelable};
38use crate::dom::eventtarget::EventTarget;
39use crate::dom::globalscope::GlobalScope;
40use crate::dom::progressevent::ProgressEvent;
41use crate::realms::enter_auto_realm;
42use crate::tasks::task::TaskOnce;
43
44pub(crate) enum FileReadingTask {
45 ProcessRead(TrustedFileReader, GenerationId),
46 ProcessReadData(TrustedFileReader, GenerationId),
47 ProcessReadError(TrustedFileReader, GenerationId, DOMErrorName),
48 ProcessReadEOF(TrustedFileReader, GenerationId, ReadMetaData, Vec<u8>),
49}
50
51impl TaskOnce for FileReadingTask {
52 fn run_once(self, cx: &mut js::context::JSContext) {
53 self.handle_task(cx);
54 }
55}
56
57impl FileReadingTask {
58 pub(crate) fn handle_task(self, cx: &mut js::context::JSContext) {
59 use self::FileReadingTask::*;
60
61 match self {
62 ProcessRead(reader, gen_id) => FileReader::process_read(cx, reader, gen_id),
63 ProcessReadData(reader, gen_id) => FileReader::process_read_data(cx, reader, gen_id),
64 ProcessReadError(reader, gen_id, error) => {
65 FileReader::process_read_error(cx, reader, gen_id, error)
66 },
67 ProcessReadEOF(reader, gen_id, metadata, blob_contents) => {
68 FileReader::process_read_eof(cx, reader, gen_id, metadata, blob_contents)
69 },
70 }
71 }
72}
73#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
74pub(crate) enum FileReaderFunction {
75 Text,
76 DataUrl,
77 ArrayBuffer,
78 BinaryString,
79}
80
81pub(crate) type TrustedFileReader = Trusted<FileReader>;
82
83#[derive(Clone, MallocSizeOf)]
84pub(crate) struct ReadMetaData {
85 pub(crate) blobtype: String,
86 pub(crate) encoding: Option<String>,
87 pub(crate) function: FileReaderFunction,
88}
89
90impl ReadMetaData {
91 pub(crate) fn new(
92 blobtype: String,
93 encoding: Option<String>,
94 function: FileReaderFunction,
95 ) -> ReadMetaData {
96 ReadMetaData {
97 blobtype,
98 encoding,
99 function,
100 }
101 }
102}
103
104#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
105pub(crate) struct GenerationId(u32);
106
107#[repr(u16)]
108#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
109pub(crate) enum FileReaderReadyState {
110 Empty = FileReaderConstants::EMPTY,
111 Loading = FileReaderConstants::LOADING,
112 Done = FileReaderConstants::DONE,
113}
114
115#[derive(JSTraceable, MallocSizeOf)]
116pub(crate) enum FileReaderResult {
117 ArrayBuffer(#[ignore_malloc_size_of = "mozjs"] RootedTraceableBox<Heap<JSVal>>),
118 String(DOMString),
119}
120
121pub(crate) struct FileReaderSharedFunctionality;
122
123impl FileReaderSharedFunctionality {
124 pub(crate) fn dataurl_for_bytes(bytes: &[u8], blob_type: &str) -> DOMString {
126 let mime_type = if blob_type.is_empty() {
130 "application/octet-stream"
131 } else {
132 blob_type
133 };
134
135 Self::dataurl_format(bytes, mime_type)
136 }
137
138 fn dataurl_format(bytes: &[u8], mime_type: &str) -> DOMString {
141 let base64 = base64::engine::general_purpose::STANDARD.encode(bytes);
142 let dataurl = format!("data:{};base64,{}", mime_type, base64);
143
144 DOMString::from(dataurl)
145 }
146
147 pub(crate) fn binary_string_for_bytes(bytes: &[u8]) -> DOMString {
149 DOMString::from(bytes.iter().map(|&byte| byte as char).collect::<String>())
150 }
151
152 pub(crate) fn text_for_bytes(
154 bytes: &[u8],
155 blob_type: &str,
156 encoding: &Option<String>,
157 ) -> DOMString {
158 let mut encoding = encoding
162 .as_ref()
163 .map(|string| string.as_bytes())
164 .and_then(Encoding::for_label);
165
166 encoding = encoding.or_else(|| {
168 let resultmime = blob_type.parse::<Mime>().ok();
169 resultmime.and_then(|mime| {
170 mime.params()
171 .find(|(k, _)| &mime::CHARSET == k)
172 .and_then(|(_, v)| Encoding::for_label(v.as_ref().as_bytes()))
173 })
174 });
175
176 let enc = encoding.unwrap_or(UTF_8);
178
179 let convert = bytes;
180 let (output, _, _) = enc.decode(convert);
183 DOMString::from(output)
184 }
185}
186
187#[dom_struct]
188pub(crate) struct FileReader {
189 eventtarget: EventTarget,
190 ready_state: Cell<FileReaderReadyState>,
191 error: MutNullableDom<DOMException>,
192 result: DomRefCell<Option<FileReaderResult>>,
193 generation_id: Cell<GenerationId>,
194}
195
196impl FileReader {
197 pub(crate) fn new_inherited() -> FileReader {
198 FileReader {
199 eventtarget: EventTarget::new_inherited(),
200 ready_state: Cell::new(FileReaderReadyState::Empty),
201 error: MutNullableDom::new(None),
202 result: DomRefCell::new(None),
203 generation_id: Cell::new(GenerationId(0)),
204 }
205 }
206
207 fn new(
208 cx: &mut js::context::JSContext,
209 global: &GlobalScope,
210 proto: Option<HandleObject>,
211 ) -> DomRoot<FileReader> {
212 reflect_dom_object_with_proto(cx, Box::new(FileReader::new_inherited()), global, proto)
213 }
214
215 pub(crate) fn process_read_error(
217 cx: &mut js::context::JSContext,
218 filereader: TrustedFileReader,
219 gen_id: GenerationId,
220 error: DOMErrorName,
221 ) {
222 let fr = filereader.root();
223
224 macro_rules! return_on_abort(
225 () => (
226 if gen_id != fr.generation_id.get() {
227 return
228 }
229 );
230 );
231
232 return_on_abort!();
233 fr.change_ready_state(FileReaderReadyState::Done);
235 *fr.result.borrow_mut() = None;
236
237 let exception = DOMException::new(cx, &fr.global(), error);
238 fr.error.set(Some(&exception));
239
240 fr.dispatch_progress_event(cx, atom!("error"), 0, None);
241 return_on_abort!();
242 fr.dispatch_progress_event(cx, atom!("loadend"), 0, None);
244 return_on_abort!();
245 fr.terminate_ongoing_reading();
247 }
248
249 pub(crate) fn process_read_data(
251 cx: &mut js::context::JSContext,
252 filereader: TrustedFileReader,
253 gen_id: GenerationId,
254 ) {
255 let fr = filereader.root();
256
257 macro_rules! return_on_abort(
258 () => (
259 if gen_id != fr.generation_id.get() {
260 return
261 }
262 );
263 );
264 return_on_abort!();
265 fr.dispatch_progress_event(cx, atom!("progress"), 0, None);
267 }
268
269 pub(crate) fn process_read(
271 cx: &mut js::context::JSContext,
272 filereader: TrustedFileReader,
273 gen_id: GenerationId,
274 ) {
275 let fr = filereader.root();
276
277 macro_rules! return_on_abort(
278 () => (
279 if gen_id != fr.generation_id.get() {
280 return
281 }
282 );
283 );
284 return_on_abort!();
285 fr.dispatch_progress_event(cx, atom!("loadstart"), 0, None);
287 }
288
289 pub(crate) fn process_read_eof(
291 cx: &mut js::context::JSContext,
292 filereader: TrustedFileReader,
293 gen_id: GenerationId,
294 data: ReadMetaData,
295 blob_contents: Vec<u8>,
296 ) {
297 let fr = filereader.root();
298
299 macro_rules! return_on_abort(
300 () => (
301 if gen_id != fr.generation_id.get() {
302 return
303 }
304 );
305 );
306
307 return_on_abort!();
308 fr.change_ready_state(FileReaderReadyState::Done);
310
311 match data.function {
316 FileReaderFunction::DataUrl => {
317 FileReader::perform_readasdataurl(&fr.result, data, &blob_contents)
318 },
319 FileReaderFunction::Text => {
320 FileReader::perform_readastext(&fr.result, data, &blob_contents)
321 },
322 FileReaderFunction::ArrayBuffer => {
323 let mut realm = enter_auto_realm(cx, &*fr);
324 let cx = &mut realm.current_realm();
325 FileReader::perform_readasarraybuffer(cx, &fr.result, &blob_contents)
326 },
327 FileReaderFunction::BinaryString => {
328 FileReader::perform_readasbinarystring(&fr.result, &blob_contents)
329 },
330 };
331
332 fr.dispatch_progress_event(cx, atom!("load"), 0, None);
334 return_on_abort!();
335 if fr.ready_state.get() != FileReaderReadyState::Loading {
337 fr.dispatch_progress_event(cx, atom!("loadend"), 0, None);
338 }
339 return_on_abort!();
340 }
341
342 fn perform_readastext(
344 result: &DomRefCell<Option<FileReaderResult>>,
345 data: ReadMetaData,
346 blob_bytes: &[u8],
347 ) {
348 *result.borrow_mut() = Some(FileReaderResult::String(
349 FileReaderSharedFunctionality::text_for_bytes(
350 blob_bytes,
351 &data.blobtype,
352 &data.encoding,
353 ),
354 ));
355 }
356
357 fn perform_readasdataurl(
359 result: &DomRefCell<Option<FileReaderResult>>,
360 data: ReadMetaData,
361 bytes: &[u8],
362 ) {
363 *result.borrow_mut() = Some(FileReaderResult::String(
364 FileReaderSharedFunctionality::dataurl_for_bytes(bytes, &data.blobtype),
365 ));
366 }
367
368 fn perform_readasbinarystring(result: &DomRefCell<Option<FileReaderResult>>, bytes: &[u8]) {
372 *result.borrow_mut() = Some(FileReaderResult::String(
373 FileReaderSharedFunctionality::binary_string_for_bytes(bytes),
374 ));
375 }
376
377 fn perform_readasarraybuffer(
380 cx: &mut js::context::JSContext,
381 result: &DomRefCell<Option<FileReaderResult>>,
382 bytes: &[u8],
383 ) {
384 rooted!(&in(cx) let mut array_buffer = ptr::null_mut::<JSObject>());
385 assert!(
386 create_buffer_source::<ArrayBufferU8>(cx, bytes, array_buffer.handle_mut()).is_ok()
387 );
388
389 *result.borrow_mut() = Some(FileReaderResult::ArrayBuffer(RootedTraceableBox::default()));
390
391 if let Some(FileReaderResult::ArrayBuffer(ref mut heap)) = *result.borrow_mut() {
392 heap.set(jsval::ObjectValue(array_buffer.get()));
393 };
394 }
395}
396
397impl FileReaderMethods<crate::DomTypeHolder> for FileReader {
398 fn Constructor(
400 cx: &mut js::context::JSContext,
401 global: &GlobalScope,
402 proto: Option<HandleObject>,
403 ) -> Fallible<DomRoot<FileReader>> {
404 Ok(FileReader::new(cx, global, proto))
405 }
406
407 event_handler!(loadstart, GetOnloadstart, SetOnloadstart);
409
410 event_handler!(progress, GetOnprogress, SetOnprogress);
412
413 event_handler!(load, GetOnload, SetOnload);
415
416 event_handler!(abort, GetOnabort, SetOnabort);
418
419 event_handler!(error, GetOnerror, SetOnerror);
421
422 event_handler!(loadend, GetOnloadend, SetOnloadend);
424
425 fn ReadAsArrayBuffer(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
427 self.read(cx, FileReaderFunction::ArrayBuffer, blob, None)
430 }
431
432 fn ReadAsBinaryString(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
434 self.read(cx, FileReaderFunction::BinaryString, blob, None)
437 }
438
439 fn ReadAsDataURL(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
441 self.read(cx, FileReaderFunction::DataUrl, blob, None)
444 }
445
446 fn ReadAsText(
448 &self,
449 cx: &mut js::context::JSContext,
450 blob: &Blob,
451 encoding: Option<DOMString>,
452 ) -> ErrorResult {
453 self.read(cx, FileReaderFunction::Text, blob, encoding)
456 }
457
458 fn Abort(&self, cx: &mut js::context::JSContext) {
460 if self.ready_state.get() == FileReaderReadyState::Loading {
462 self.change_ready_state(FileReaderReadyState::Done);
463 }
464 *self.result.borrow_mut() = None;
466
467 let exception = DOMException::new(cx, &self.global(), DOMErrorName::AbortError);
468 self.error.set(Some(&exception));
469
470 self.terminate_ongoing_reading();
471 self.dispatch_progress_event(cx, atom!("abort"), 0, None);
473 self.dispatch_progress_event(cx, atom!("loadend"), 0, None);
474 }
475
476 fn GetError(&self) -> Option<DomRoot<DOMException>> {
478 self.error.get()
479 }
480
481 #[expect(unsafe_code)]
482 fn GetResult(&self) -> Option<StringOrObject> {
484 self.result.borrow().as_ref().map(|r| match *r {
485 FileReaderResult::String(ref string) => StringOrObject::String(string.clone()),
486 FileReaderResult::ArrayBuffer(ref arr_buffer) => {
487 let result = RootedTraceableBox::new(Heap::default());
488 unsafe {
489 result.set((*arr_buffer.ptr.get()).to_object());
490 }
491 StringOrObject::Object(result)
492 },
493 })
494 }
495
496 fn ReadyState(&self) -> u16 {
498 self.ready_state.get() as u16
499 }
500}
501
502impl FileReader {
503 fn dispatch_progress_event(
504 &self,
505 cx: &mut js::context::JSContext,
506 type_: Atom,
507 loaded: u64,
508 total: Option<u64>,
509 ) {
510 let progressevent = ProgressEvent::new(
511 cx,
512 &self.global(),
513 type_,
514 EventBubbles::DoesNotBubble,
515 EventCancelable::NotCancelable,
516 total.is_some(),
517 Finite::wrap(loaded as f64),
518 Finite::wrap(total.unwrap_or(0) as f64),
519 );
520 progressevent.upcast::<Event>().fire(cx, self.upcast());
521 }
522
523 fn terminate_ongoing_reading(&self) {
524 let GenerationId(prev_id) = self.generation_id.get();
525 self.generation_id.set(GenerationId(prev_id + 1));
526 }
527
528 fn read(
530 &self,
531 cx: &mut js::context::JSContext,
532 function: FileReaderFunction,
533 blob: &Blob,
534 encoding: Option<DOMString>,
535 ) -> ErrorResult {
536 if self.ready_state.get() == FileReaderReadyState::Loading {
538 return Err(Error::InvalidState(None));
539 }
540
541 self.change_ready_state(FileReaderReadyState::Loading);
543
544 *self.result.borrow_mut() = None;
546
547 let stream = blob.get_stream(cx);
552
553 let reader = stream.and_then(|s| s.acquire_default_reader(cx))?;
555
556 let load_data = ReadMetaData::new(
557 String::from(blob.Type()),
558 encoding.map(String::from),
559 function,
560 );
561
562 let GenerationId(prev_id) = self.generation_id.get();
563 self.generation_id.set(GenerationId(prev_id + 1));
564 let gen_id = self.generation_id.get();
565
566 let filereader_success = DomRoot::from_ref(self);
567 let filereader_error = DomRoot::from_ref(self);
568
569 reader.read_all_bytes(
579 cx,
580 Rc::new(move |_cx, blob_contents| {
581 let global = filereader_success.global();
582 let task_manager = global.task_manager();
583 let task_source = task_manager.file_reading_task_source();
584
585 task_source.queue(FileReadingTask::ProcessRead(
591 Trusted::new(&filereader_success.clone()),
592 gen_id,
593 ));
594 if !blob_contents.is_empty() {
600 task_source.queue(FileReadingTask::ProcessReadData(
601 Trusted::new(&filereader_success.clone()),
602 gen_id,
603 ));
604 }
605 task_source.queue(FileReadingTask::ProcessReadEOF(
611 Trusted::new(&filereader_success.clone()),
612 gen_id,
613 load_data.clone(),
614 blob_contents.to_vec(),
615 ));
616 }),
617 Rc::new(move |_cx, _error| {
618 let global = filereader_error.global();
619 let task_manager = global.task_manager();
620 let task_source = task_manager.file_reading_task_source();
621
622 task_source.queue(FileReadingTask::ProcessReadError(
627 Trusted::new(&filereader_error),
628 gen_id,
629 DOMErrorName::OperationError,
630 ));
631 }),
632 );
633 Ok(())
634 }
635
636 fn change_ready_state(&self, state: FileReaderReadyState) {
637 self.ready_state.set(state);
638 }
639}