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;
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::tasks::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(cx, Box::new(FileReader::new_inherited()), global, proto)
212 }
213
214 pub(crate) fn process_read_error(
216 cx: &mut js::context::JSContext,
217 filereader: TrustedFileReader,
218 gen_id: GenerationId,
219 error: DOMErrorName,
220 ) {
221 let fr = filereader.root();
222
223 macro_rules! return_on_abort(
224 () => (
225 if gen_id != fr.generation_id.get() {
226 return
227 }
228 );
229 );
230
231 return_on_abort!();
232 fr.change_ready_state(FileReaderReadyState::Done);
234 *fr.result.borrow_mut() = None;
235
236 let exception = DOMException::new(cx, &fr.global(), error);
237 fr.error.set(Some(&exception));
238
239 fr.dispatch_progress_event(cx, atom!("error"), 0, None);
240 return_on_abort!();
241 fr.dispatch_progress_event(cx, atom!("loadend"), 0, None);
243 return_on_abort!();
244 fr.terminate_ongoing_reading();
246 }
247
248 pub(crate) fn process_read_data(
250 cx: &mut js::context::JSContext,
251 filereader: TrustedFileReader,
252 gen_id: GenerationId,
253 ) {
254 let fr = filereader.root();
255
256 macro_rules! return_on_abort(
257 () => (
258 if gen_id != fr.generation_id.get() {
259 return
260 }
261 );
262 );
263 return_on_abort!();
264 fr.dispatch_progress_event(cx, atom!("progress"), 0, None);
266 }
267
268 pub(crate) fn process_read(
270 cx: &mut js::context::JSContext,
271 filereader: TrustedFileReader,
272 gen_id: GenerationId,
273 ) {
274 let fr = filereader.root();
275
276 macro_rules! return_on_abort(
277 () => (
278 if gen_id != fr.generation_id.get() {
279 return
280 }
281 );
282 );
283 return_on_abort!();
284 fr.dispatch_progress_event(cx, atom!("loadstart"), 0, None);
286 }
287
288 pub(crate) fn process_read_eof(
290 cx: &mut js::context::JSContext,
291 filereader: TrustedFileReader,
292 gen_id: GenerationId,
293 data: ReadMetaData,
294 blob_contents: Vec<u8>,
295 ) {
296 let fr = filereader.root();
297
298 macro_rules! return_on_abort(
299 () => (
300 if gen_id != fr.generation_id.get() {
301 return
302 }
303 );
304 );
305
306 return_on_abort!();
307 fr.change_ready_state(FileReaderReadyState::Done);
309
310 match data.function {
315 FileReaderFunction::DataUrl => {
316 FileReader::perform_readasdataurl(&fr.result, data, &blob_contents)
317 },
318 FileReaderFunction::Text => {
319 FileReader::perform_readastext(&fr.result, data, &blob_contents)
320 },
321 FileReaderFunction::ArrayBuffer => {
322 let mut realm = enter_auto_realm(cx, &*fr);
323 let cx = &mut realm.current_realm();
324 FileReader::perform_readasarraybuffer(cx, &fr.result, &blob_contents)
325 },
326 FileReaderFunction::BinaryString => {
327 FileReader::perform_readasbinarystring(&fr.result, &blob_contents)
328 },
329 };
330
331 fr.dispatch_progress_event(cx, atom!("load"), 0, None);
333 return_on_abort!();
334 if fr.ready_state.get() != FileReaderReadyState::Loading {
336 fr.dispatch_progress_event(cx, atom!("loadend"), 0, None);
337 }
338 return_on_abort!();
339 }
340
341 fn perform_readastext(
343 result: &DomRefCell<Option<FileReaderResult>>,
344 data: ReadMetaData,
345 blob_bytes: &[u8],
346 ) {
347 *result.borrow_mut() = Some(FileReaderResult::String(
348 FileReaderSharedFunctionality::text_for_bytes(
349 blob_bytes,
350 &data.blobtype,
351 &data.encoding,
352 ),
353 ));
354 }
355
356 fn perform_readasdataurl(
358 result: &DomRefCell<Option<FileReaderResult>>,
359 data: ReadMetaData,
360 bytes: &[u8],
361 ) {
362 *result.borrow_mut() = Some(FileReaderResult::String(
363 FileReaderSharedFunctionality::dataurl_for_bytes(bytes, &data.blobtype),
364 ));
365 }
366
367 fn perform_readasbinarystring(result: &DomRefCell<Option<FileReaderResult>>, bytes: &[u8]) {
371 *result.borrow_mut() = Some(FileReaderResult::String(
372 FileReaderSharedFunctionality::binary_string_for_bytes(bytes),
373 ));
374 }
375
376 #[expect(unsafe_code)]
379 fn perform_readasarraybuffer(
380 cx: &mut js::context::JSContext,
381 result: &DomRefCell<Option<FileReaderResult>>,
382 bytes: &[u8],
383 ) {
384 unsafe {
385 rooted!(&in(cx) let mut array_buffer = ptr::null_mut::<JSObject>());
386 assert!(
387 ArrayBuffer::create(
388 cx.raw_cx(),
389 CreateWith::Slice(bytes),
390 array_buffer.handle_mut()
391 )
392 .is_ok()
393 );
394
395 *result.borrow_mut() =
396 Some(FileReaderResult::ArrayBuffer(RootedTraceableBox::default()));
397
398 if let Some(FileReaderResult::ArrayBuffer(ref mut heap)) = *result.borrow_mut() {
399 heap.set(jsval::ObjectValue(array_buffer.get()));
400 };
401 }
402 }
403}
404
405impl FileReaderMethods<crate::DomTypeHolder> for FileReader {
406 fn Constructor(
408 cx: &mut js::context::JSContext,
409 global: &GlobalScope,
410 proto: Option<HandleObject>,
411 ) -> Fallible<DomRoot<FileReader>> {
412 Ok(FileReader::new(cx, global, proto))
413 }
414
415 event_handler!(loadstart, GetOnloadstart, SetOnloadstart);
417
418 event_handler!(progress, GetOnprogress, SetOnprogress);
420
421 event_handler!(load, GetOnload, SetOnload);
423
424 event_handler!(abort, GetOnabort, SetOnabort);
426
427 event_handler!(error, GetOnerror, SetOnerror);
429
430 event_handler!(loadend, GetOnloadend, SetOnloadend);
432
433 fn ReadAsArrayBuffer(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
435 self.read(cx, FileReaderFunction::ArrayBuffer, blob, None)
438 }
439
440 fn ReadAsBinaryString(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
442 self.read(cx, FileReaderFunction::BinaryString, blob, None)
445 }
446
447 fn ReadAsDataURL(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
449 self.read(cx, FileReaderFunction::DataUrl, blob, None)
452 }
453
454 fn ReadAsText(
456 &self,
457 cx: &mut js::context::JSContext,
458 blob: &Blob,
459 encoding: Option<DOMString>,
460 ) -> ErrorResult {
461 self.read(cx, FileReaderFunction::Text, blob, encoding)
464 }
465
466 fn Abort(&self, cx: &mut js::context::JSContext) {
468 if self.ready_state.get() == FileReaderReadyState::Loading {
470 self.change_ready_state(FileReaderReadyState::Done);
471 }
472 *self.result.borrow_mut() = None;
474
475 let exception = DOMException::new(cx, &self.global(), DOMErrorName::AbortError);
476 self.error.set(Some(&exception));
477
478 self.terminate_ongoing_reading();
479 self.dispatch_progress_event(cx, atom!("abort"), 0, None);
481 self.dispatch_progress_event(cx, atom!("loadend"), 0, None);
482 }
483
484 fn GetError(&self) -> Option<DomRoot<DOMException>> {
486 self.error.get()
487 }
488
489 #[expect(unsafe_code)]
490 fn GetResult(&self) -> Option<StringOrObject> {
492 self.result.borrow().as_ref().map(|r| match *r {
493 FileReaderResult::String(ref string) => StringOrObject::String(string.clone()),
494 FileReaderResult::ArrayBuffer(ref arr_buffer) => {
495 let result = RootedTraceableBox::new(Heap::default());
496 unsafe {
497 result.set((*arr_buffer.ptr.get()).to_object());
498 }
499 StringOrObject::Object(result)
500 },
501 })
502 }
503
504 fn ReadyState(&self) -> u16 {
506 self.ready_state.get() as u16
507 }
508}
509
510impl FileReader {
511 fn dispatch_progress_event(
512 &self,
513 cx: &mut js::context::JSContext,
514 type_: Atom,
515 loaded: u64,
516 total: Option<u64>,
517 ) {
518 let progressevent = ProgressEvent::new(
519 cx,
520 &self.global(),
521 type_,
522 EventBubbles::DoesNotBubble,
523 EventCancelable::NotCancelable,
524 total.is_some(),
525 Finite::wrap(loaded as f64),
526 Finite::wrap(total.unwrap_or(0) as f64),
527 );
528 progressevent.upcast::<Event>().fire(cx, self.upcast());
529 }
530
531 fn terminate_ongoing_reading(&self) {
532 let GenerationId(prev_id) = self.generation_id.get();
533 self.generation_id.set(GenerationId(prev_id + 1));
534 }
535
536 fn read(
538 &self,
539 cx: &mut js::context::JSContext,
540 function: FileReaderFunction,
541 blob: &Blob,
542 encoding: Option<DOMString>,
543 ) -> ErrorResult {
544 if self.ready_state.get() == FileReaderReadyState::Loading {
546 return Err(Error::InvalidState(None));
547 }
548
549 self.change_ready_state(FileReaderReadyState::Loading);
551
552 *self.result.borrow_mut() = None;
554
555 let stream = blob.get_stream(cx);
560
561 let reader = stream.and_then(|s| s.acquire_default_reader(cx))?;
563
564 let load_data = ReadMetaData::new(
565 String::from(blob.Type()),
566 encoding.map(String::from),
567 function,
568 );
569
570 let GenerationId(prev_id) = self.generation_id.get();
571 self.generation_id.set(GenerationId(prev_id + 1));
572 let gen_id = self.generation_id.get();
573
574 let filereader_success = DomRoot::from_ref(self);
575 let filereader_error = DomRoot::from_ref(self);
576
577 reader.read_all_bytes(
587 cx,
588 Rc::new(move |_cx, blob_contents| {
589 let global = filereader_success.global();
590 let task_manager = global.task_manager();
591 let task_source = task_manager.file_reading_task_source();
592
593 task_source.queue(FileReadingTask::ProcessRead(
599 Trusted::new(&filereader_success.clone()),
600 gen_id,
601 ));
602 if !blob_contents.is_empty() {
608 task_source.queue(FileReadingTask::ProcessReadData(
609 Trusted::new(&filereader_success.clone()),
610 gen_id,
611 ));
612 }
613 task_source.queue(FileReadingTask::ProcessReadEOF(
619 Trusted::new(&filereader_success.clone()),
620 gen_id,
621 load_data.clone(),
622 blob_contents.to_vec(),
623 ));
624 }),
625 Rc::new(move |_cx, _error| {
626 let global = filereader_error.global();
627 let task_manager = global.task_manager();
628 let task_source = task_manager.file_reading_task_source();
629
630 task_source.queue(FileReadingTask::ProcessReadError(
635 Trusted::new(&filereader_error),
636 gen_id,
637 DOMErrorName::OperationError,
638 ));
639 }),
640 );
641 Ok(())
642 }
643
644 fn change_ready_state(&self, state: FileReaderReadyState) {
645 self.ready_state.set(state);
646 }
647}