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(CanGc::from_cx(cx));
54 }
55}
56
57impl FileReadingTask {
58 pub(crate) fn handle_task(self, can_gc: CanGc) {
59 use self::FileReadingTask::*;
60
61 match self {
62 ProcessRead(reader, gen_id) => FileReader::process_read(reader, gen_id, can_gc),
63 ProcessReadData(reader, gen_id) => {
64 FileReader::process_read_data(reader, gen_id, can_gc)
65 },
66 ProcessReadError(reader, gen_id, error) => {
67 FileReader::process_read_error(reader, gen_id, error, can_gc)
68 },
69 ProcessReadEOF(reader, gen_id, metadata, blob_contents) => {
70 FileReader::process_read_eof(reader, gen_id, metadata, blob_contents, can_gc)
71 },
72 }
73 }
74}
75#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
76pub(crate) enum FileReaderFunction {
77 Text,
78 DataUrl,
79 ArrayBuffer,
80 BinaryString,
81}
82
83pub(crate) type TrustedFileReader = Trusted<FileReader>;
84
85#[derive(Clone, MallocSizeOf)]
86pub(crate) struct ReadMetaData {
87 pub(crate) blobtype: String,
88 pub(crate) encoding: Option<String>,
89 pub(crate) function: FileReaderFunction,
90}
91
92impl ReadMetaData {
93 pub(crate) fn new(
94 blobtype: String,
95 encoding: Option<String>,
96 function: FileReaderFunction,
97 ) -> ReadMetaData {
98 ReadMetaData {
99 blobtype,
100 encoding,
101 function,
102 }
103 }
104}
105
106#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
107pub(crate) struct GenerationId(u32);
108
109#[repr(u16)]
110#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
111pub(crate) enum FileReaderReadyState {
112 Empty = FileReaderConstants::EMPTY,
113 Loading = FileReaderConstants::LOADING,
114 Done = FileReaderConstants::DONE,
115}
116
117#[derive(JSTraceable, MallocSizeOf)]
118pub(crate) enum FileReaderResult {
119 ArrayBuffer(#[ignore_malloc_size_of = "mozjs"] RootedTraceableBox<Heap<JSVal>>),
120 String(DOMString),
121}
122
123pub(crate) struct FileReaderSharedFunctionality;
124
125impl FileReaderSharedFunctionality {
126 pub(crate) fn dataurl_format(blob_contents: &[u8], blob_type: String) -> DOMString {
127 let base64 = base64::engine::general_purpose::STANDARD.encode(blob_contents);
128
129 let dataurl = if blob_type.is_empty() {
130 format!("data:base64,{}", base64)
131 } else {
132 format!("data:{};base64,{}", blob_type, base64)
133 };
134
135 DOMString::from(dataurl)
136 }
137
138 pub(crate) fn text_decode(
140 blob_contents: &[u8],
141 blob_type: &str,
142 encoding: &Option<String>,
143 ) -> DOMString {
144 let mut encoding = encoding
148 .as_ref()
149 .map(|string| string.as_bytes())
150 .and_then(Encoding::for_label);
151
152 encoding = encoding.or_else(|| {
154 let resultmime = blob_type.parse::<Mime>().ok();
155 resultmime.and_then(|mime| {
156 mime.params()
157 .find(|(k, _)| &mime::CHARSET == k)
158 .and_then(|(_, v)| Encoding::for_label(v.as_ref().as_bytes()))
159 })
160 });
161
162 let enc = encoding.unwrap_or(UTF_8);
164
165 let convert = blob_contents;
166 let (output, _, _) = enc.decode(convert);
168 DOMString::from(output)
169 }
170}
171
172#[dom_struct]
173pub(crate) struct FileReader {
174 eventtarget: EventTarget,
175 ready_state: Cell<FileReaderReadyState>,
176 error: MutNullableDom<DOMException>,
177 result: DomRefCell<Option<FileReaderResult>>,
178 generation_id: Cell<GenerationId>,
179}
180
181impl FileReader {
182 pub(crate) fn new_inherited() -> FileReader {
183 FileReader {
184 eventtarget: EventTarget::new_inherited(),
185 ready_state: Cell::new(FileReaderReadyState::Empty),
186 error: MutNullableDom::new(None),
187 result: DomRefCell::new(None),
188 generation_id: Cell::new(GenerationId(0)),
189 }
190 }
191
192 fn new(
193 global: &GlobalScope,
194 proto: Option<HandleObject>,
195 can_gc: CanGc,
196 ) -> DomRoot<FileReader> {
197 reflect_dom_object_with_proto(Box::new(FileReader::new_inherited()), global, proto, can_gc)
198 }
199
200 pub(crate) fn process_read_error(
202 filereader: TrustedFileReader,
203 gen_id: GenerationId,
204 error: DOMErrorName,
205 can_gc: CanGc,
206 ) {
207 let fr = filereader.root();
208
209 macro_rules! return_on_abort(
210 () => (
211 if gen_id != fr.generation_id.get() {
212 return
213 }
214 );
215 );
216
217 return_on_abort!();
218 fr.change_ready_state(FileReaderReadyState::Done);
220 *fr.result.borrow_mut() = None;
221
222 let exception = DOMException::new(&fr.global(), error, can_gc);
223 fr.error.set(Some(&exception));
224
225 fr.dispatch_progress_event(atom!("error"), 0, None, can_gc);
226 return_on_abort!();
227 fr.dispatch_progress_event(atom!("loadend"), 0, None, can_gc);
229 return_on_abort!();
230 fr.terminate_ongoing_reading();
232 }
233
234 pub(crate) fn process_read_data(
236 filereader: TrustedFileReader,
237 gen_id: GenerationId,
238 can_gc: CanGc,
239 ) {
240 let fr = filereader.root();
241
242 macro_rules! return_on_abort(
243 () => (
244 if gen_id != fr.generation_id.get() {
245 return
246 }
247 );
248 );
249 return_on_abort!();
250 fr.dispatch_progress_event(atom!("progress"), 0, None, can_gc);
252 }
253
254 pub(crate) fn process_read(filereader: TrustedFileReader, gen_id: GenerationId, can_gc: CanGc) {
256 let fr = filereader.root();
257
258 macro_rules! return_on_abort(
259 () => (
260 if gen_id != fr.generation_id.get() {
261 return
262 }
263 );
264 );
265 return_on_abort!();
266 fr.dispatch_progress_event(atom!("loadstart"), 0, None, can_gc);
268 }
269
270 pub(crate) fn process_read_eof(
272 filereader: TrustedFileReader,
273 gen_id: GenerationId,
274 data: ReadMetaData,
275 blob_contents: Vec<u8>,
276 can_gc: CanGc,
277 ) {
278 let fr = filereader.root();
279
280 macro_rules! return_on_abort(
281 () => (
282 if gen_id != fr.generation_id.get() {
283 return
284 }
285 );
286 );
287
288 return_on_abort!();
289 fr.change_ready_state(FileReaderReadyState::Done);
291
292 match data.function {
297 FileReaderFunction::DataUrl => {
298 FileReader::perform_readasdataurl(&fr.result, data, &blob_contents)
299 },
300 FileReaderFunction::Text => {
301 FileReader::perform_readastext(&fr.result, data, &blob_contents)
302 },
303 FileReaderFunction::ArrayBuffer => {
304 let _ac = enter_realm(&*fr);
305 FileReader::perform_readasarraybuffer(
306 &fr.result,
307 GlobalScope::get_cx(),
308 &blob_contents,
309 )
310 },
311 FileReaderFunction::BinaryString => {
312 FileReader::perform_readasbinarystring(&fr.result, &blob_contents)
313 },
314 };
315
316 fr.dispatch_progress_event(atom!("load"), 0, None, can_gc);
318 return_on_abort!();
319 if fr.ready_state.get() != FileReaderReadyState::Loading {
321 fr.dispatch_progress_event(atom!("loadend"), 0, None, can_gc);
322 }
323 return_on_abort!();
324 }
325
326 fn perform_readastext(
328 result: &DomRefCell<Option<FileReaderResult>>,
329 data: ReadMetaData,
330 blob_bytes: &[u8],
331 ) {
332 let encoding = &data.encoding;
333 let blob_type = &data.blobtype;
334
335 let output = FileReaderSharedFunctionality::text_decode(blob_bytes, blob_type, encoding);
336 *result.borrow_mut() = Some(FileReaderResult::String(output));
337 }
338
339 fn perform_readasdataurl(
341 result: &DomRefCell<Option<FileReaderResult>>,
342 data: ReadMetaData,
343 bytes: &[u8],
344 ) {
345 let output = FileReaderSharedFunctionality::dataurl_format(bytes, data.blobtype);
350
351 *result.borrow_mut() = Some(FileReaderResult::String(output));
352 }
353
354 fn perform_readasbinarystring(result: &DomRefCell<Option<FileReaderResult>>, bytes: &[u8]) {
358 *result.borrow_mut() = Some(FileReaderResult::String(DOMString::from(
359 String::from_utf8_lossy(bytes),
360 )));
361 }
362
363 #[expect(unsafe_code)]
366 fn perform_readasarraybuffer(
367 result: &DomRefCell<Option<FileReaderResult>>,
368 cx: JSContext,
369 bytes: &[u8],
370 ) {
371 unsafe {
372 rooted!(in(*cx) let mut array_buffer = ptr::null_mut::<JSObject>());
373 assert!(
374 ArrayBuffer::create(*cx, CreateWith::Slice(bytes), array_buffer.handle_mut())
375 .is_ok()
376 );
377
378 *result.borrow_mut() =
379 Some(FileReaderResult::ArrayBuffer(RootedTraceableBox::default()));
380
381 if let Some(FileReaderResult::ArrayBuffer(ref mut heap)) = *result.borrow_mut() {
382 heap.set(jsval::ObjectValue(array_buffer.get()));
383 };
384 }
385 }
386}
387
388impl FileReaderMethods<crate::DomTypeHolder> for FileReader {
389 fn Constructor(
391 global: &GlobalScope,
392 proto: Option<HandleObject>,
393 can_gc: CanGc,
394 ) -> Fallible<DomRoot<FileReader>> {
395 Ok(FileReader::new(global, proto, can_gc))
396 }
397
398 event_handler!(loadstart, GetOnloadstart, SetOnloadstart);
400
401 event_handler!(progress, GetOnprogress, SetOnprogress);
403
404 event_handler!(load, GetOnload, SetOnload);
406
407 event_handler!(abort, GetOnabort, SetOnabort);
409
410 event_handler!(error, GetOnerror, SetOnerror);
412
413 event_handler!(loadend, GetOnloadend, SetOnloadend);
415
416 fn ReadAsArrayBuffer(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
418 self.read(cx, FileReaderFunction::ArrayBuffer, blob, None)
421 }
422
423 fn ReadAsBinaryString(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
425 self.read(cx, FileReaderFunction::BinaryString, blob, None)
428 }
429
430 fn ReadAsDataURL(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
432 self.read(cx, FileReaderFunction::DataUrl, blob, None)
435 }
436
437 fn ReadAsText(
439 &self,
440 cx: &mut js::context::JSContext,
441 blob: &Blob,
442 encoding: Option<DOMString>,
443 ) -> ErrorResult {
444 self.read(cx, FileReaderFunction::Text, blob, encoding)
447 }
448
449 fn Abort(&self, can_gc: CanGc) {
451 if self.ready_state.get() == FileReaderReadyState::Loading {
453 self.change_ready_state(FileReaderReadyState::Done);
454 }
455 *self.result.borrow_mut() = None;
457
458 let exception = DOMException::new(&self.global(), DOMErrorName::AbortError, can_gc);
459 self.error.set(Some(&exception));
460
461 self.terminate_ongoing_reading();
462 self.dispatch_progress_event(atom!("abort"), 0, None, can_gc);
464 self.dispatch_progress_event(atom!("loadend"), 0, None, can_gc);
465 }
466
467 fn GetError(&self) -> Option<DomRoot<DOMException>> {
469 self.error.get()
470 }
471
472 #[expect(unsafe_code)]
473 fn GetResult(&self, _: JSContext) -> Option<StringOrObject> {
475 self.result.borrow().as_ref().map(|r| match *r {
476 FileReaderResult::String(ref string) => StringOrObject::String(string.clone()),
477 FileReaderResult::ArrayBuffer(ref arr_buffer) => {
478 let result = RootedTraceableBox::new(Heap::default());
479 unsafe {
480 result.set((*arr_buffer.ptr.get()).to_object());
481 }
482 StringOrObject::Object(result)
483 },
484 })
485 }
486
487 fn ReadyState(&self) -> u16 {
489 self.ready_state.get() as u16
490 }
491}
492
493impl FileReader {
494 fn dispatch_progress_event(&self, type_: Atom, loaded: u64, total: Option<u64>, can_gc: CanGc) {
495 let progressevent = ProgressEvent::new(
496 &self.global(),
497 type_,
498 EventBubbles::DoesNotBubble,
499 EventCancelable::NotCancelable,
500 total.is_some(),
501 Finite::wrap(loaded as f64),
502 Finite::wrap(total.unwrap_or(0) as f64),
503 can_gc,
504 );
505 progressevent.upcast::<Event>().fire(self.upcast(), can_gc);
506 }
507
508 fn terminate_ongoing_reading(&self) {
509 let GenerationId(prev_id) = self.generation_id.get();
510 self.generation_id.set(GenerationId(prev_id + 1));
511 }
512
513 fn read(
515 &self,
516 cx: &mut js::context::JSContext,
517 function: FileReaderFunction,
518 blob: &Blob,
519 encoding: Option<DOMString>,
520 ) -> ErrorResult {
521 if self.ready_state.get() == FileReaderReadyState::Loading {
523 return Err(Error::InvalidState(None));
524 }
525
526 self.change_ready_state(FileReaderReadyState::Loading);
528
529 *self.result.borrow_mut() = None;
531
532 let stream = blob.get_stream(cx);
537
538 let reader = stream.and_then(|s| s.acquire_default_reader(CanGc::from_cx(cx)))?;
540
541 let type_ = blob.Type();
542
543 let load_data =
544 ReadMetaData::new(String::from(type_), encoding.map(String::from), function);
545
546 let GenerationId(prev_id) = self.generation_id.get();
547 self.generation_id.set(GenerationId(prev_id + 1));
548 let gen_id = self.generation_id.get();
549
550 let filereader_success = DomRoot::from_ref(self);
551 let filereader_error = DomRoot::from_ref(self);
552
553 reader.read_all_bytes(
563 cx,
564 Rc::new(move |_cx, blob_contents| {
565 let global = filereader_success.global();
566 let task_manager = global.task_manager();
567 let task_source = task_manager.file_reading_task_source();
568
569 task_source.queue(FileReadingTask::ProcessRead(
575 Trusted::new(&filereader_success.clone()),
576 gen_id,
577 ));
578 if !blob_contents.is_empty() {
584 task_source.queue(FileReadingTask::ProcessReadData(
585 Trusted::new(&filereader_success.clone()),
586 gen_id,
587 ));
588 }
589 task_source.queue(FileReadingTask::ProcessReadEOF(
595 Trusted::new(&filereader_success.clone()),
596 gen_id,
597 load_data.clone(),
598 blob_contents.to_vec(),
599 ));
600 }),
601 Rc::new(move |_cx, _error| {
602 let global = filereader_error.global();
603 let task_manager = global.task_manager();
604 let task_source = task_manager.file_reading_task_source();
605
606 task_source.queue(FileReadingTask::ProcessReadError(
611 Trusted::new(&filereader_error),
612 gen_id,
613 DOMErrorName::OperationError,
614 ));
615 }),
616 );
617 Ok(())
618 }
619
620 fn change_ready_state(&self, state: FileReaderReadyState) {
621 self.ready_state.set(state);
622 }
623}