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