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_realm;
41use crate::script_runtime::{CanGc, JSContext};
42use crate::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 global: &GlobalScope,
209 proto: Option<HandleObject>,
210 can_gc: CanGc,
211 ) -> DomRoot<FileReader> {
212 reflect_dom_object_with_proto(Box::new(FileReader::new_inherited()), global, proto, can_gc)
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(&fr.global(), error, CanGc::from_cx(cx));
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 _ac = enter_realm(&*fr);
324 FileReader::perform_readasarraybuffer(
325 &fr.result,
326 GlobalScope::get_cx(),
327 &blob_contents,
328 )
329 },
330 FileReaderFunction::BinaryString => {
331 FileReader::perform_readasbinarystring(&fr.result, &blob_contents)
332 },
333 };
334
335 fr.dispatch_progress_event(cx, atom!("load"), 0, None);
337 return_on_abort!();
338 if fr.ready_state.get() != FileReaderReadyState::Loading {
340 fr.dispatch_progress_event(cx, atom!("loadend"), 0, None);
341 }
342 return_on_abort!();
343 }
344
345 fn perform_readastext(
347 result: &DomRefCell<Option<FileReaderResult>>,
348 data: ReadMetaData,
349 blob_bytes: &[u8],
350 ) {
351 *result.borrow_mut() = Some(FileReaderResult::String(
352 FileReaderSharedFunctionality::text_for_bytes(
353 blob_bytes,
354 &data.blobtype,
355 &data.encoding,
356 ),
357 ));
358 }
359
360 fn perform_readasdataurl(
362 result: &DomRefCell<Option<FileReaderResult>>,
363 data: ReadMetaData,
364 bytes: &[u8],
365 ) {
366 *result.borrow_mut() = Some(FileReaderResult::String(
367 FileReaderSharedFunctionality::dataurl_for_bytes(bytes, &data.blobtype),
368 ));
369 }
370
371 fn perform_readasbinarystring(result: &DomRefCell<Option<FileReaderResult>>, bytes: &[u8]) {
375 *result.borrow_mut() = Some(FileReaderResult::String(
376 FileReaderSharedFunctionality::binary_string_for_bytes(bytes),
377 ));
378 }
379
380 #[expect(unsafe_code)]
383 fn perform_readasarraybuffer(
384 result: &DomRefCell<Option<FileReaderResult>>,
385 cx: JSContext,
386 bytes: &[u8],
387 ) {
388 unsafe {
389 rooted!(in(*cx) let mut array_buffer = ptr::null_mut::<JSObject>());
390 assert!(
391 ArrayBuffer::create(*cx, CreateWith::Slice(bytes), array_buffer.handle_mut())
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 global: &GlobalScope,
409 proto: Option<HandleObject>,
410 can_gc: CanGc,
411 ) -> Fallible<DomRoot<FileReader>> {
412 Ok(FileReader::new(global, proto, can_gc))
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 =
476 DOMException::new(&self.global(), DOMErrorName::AbortError, CanGc::from_cx(cx));
477 self.error.set(Some(&exception));
478
479 self.terminate_ongoing_reading();
480 self.dispatch_progress_event(cx, atom!("abort"), 0, None);
482 self.dispatch_progress_event(cx, atom!("loadend"), 0, None);
483 }
484
485 fn GetError(&self) -> Option<DomRoot<DOMException>> {
487 self.error.get()
488 }
489
490 #[expect(unsafe_code)]
491 fn GetResult(&self, _: JSContext) -> Option<StringOrObject> {
493 self.result.borrow().as_ref().map(|r| match *r {
494 FileReaderResult::String(ref string) => StringOrObject::String(string.clone()),
495 FileReaderResult::ArrayBuffer(ref arr_buffer) => {
496 let result = RootedTraceableBox::new(Heap::default());
497 unsafe {
498 result.set((*arr_buffer.ptr.get()).to_object());
499 }
500 StringOrObject::Object(result)
501 },
502 })
503 }
504
505 fn ReadyState(&self) -> u16 {
507 self.ready_state.get() as u16
508 }
509}
510
511impl FileReader {
512 fn dispatch_progress_event(
513 &self,
514 cx: &mut js::context::JSContext,
515 type_: Atom,
516 loaded: u64,
517 total: Option<u64>,
518 ) {
519 let progressevent = ProgressEvent::new(
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 CanGc::from_cx(cx),
528 );
529 progressevent.upcast::<Event>().fire(cx, self.upcast());
530 }
531
532 fn terminate_ongoing_reading(&self) {
533 let GenerationId(prev_id) = self.generation_id.get();
534 self.generation_id.set(GenerationId(prev_id + 1));
535 }
536
537 fn read(
539 &self,
540 cx: &mut js::context::JSContext,
541 function: FileReaderFunction,
542 blob: &Blob,
543 encoding: Option<DOMString>,
544 ) -> ErrorResult {
545 if self.ready_state.get() == FileReaderReadyState::Loading {
547 return Err(Error::InvalidState(None));
548 }
549
550 self.change_ready_state(FileReaderReadyState::Loading);
552
553 *self.result.borrow_mut() = None;
555
556 let stream = blob.get_stream(cx);
561
562 let reader = stream.and_then(|s| s.acquire_default_reader(CanGc::from_cx(cx)))?;
564
565 let load_data = ReadMetaData::new(
566 String::from(blob.Type()),
567 encoding.map(String::from),
568 function,
569 );
570
571 let GenerationId(prev_id) = self.generation_id.get();
572 self.generation_id.set(GenerationId(prev_id + 1));
573 let gen_id = self.generation_id.get();
574
575 let filereader_success = DomRoot::from_ref(self);
576 let filereader_error = DomRoot::from_ref(self);
577
578 reader.read_all_bytes(
588 cx,
589 Rc::new(move |_cx, blob_contents| {
590 let global = filereader_success.global();
591 let task_manager = global.task_manager();
592 let task_source = task_manager.file_reading_task_source();
593
594 task_source.queue(FileReadingTask::ProcessRead(
600 Trusted::new(&filereader_success.clone()),
601 gen_id,
602 ));
603 if !blob_contents.is_empty() {
609 task_source.queue(FileReadingTask::ProcessReadData(
610 Trusted::new(&filereader_success.clone()),
611 gen_id,
612 ));
613 }
614 task_source.queue(FileReadingTask::ProcessReadEOF(
620 Trusted::new(&filereader_success.clone()),
621 gen_id,
622 load_data.clone(),
623 blob_contents.to_vec(),
624 ));
625 }),
626 Rc::new(move |_cx, _error| {
627 let global = filereader_error.global();
628 let task_manager = global.task_manager();
629 let task_source = task_manager.file_reading_task_source();
630
631 task_source.queue(FileReadingTask::ProcessReadError(
636 Trusted::new(&filereader_error),
637 gen_id,
638 DOMErrorName::OperationError,
639 ));
640 }),
641 );
642 Ok(())
643 }
644
645 fn change_ready_state(&self, state: FileReaderReadyState) {
646 self.ready_state.set(state);
647 }
648}