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(cx, CreateWith::Slice(bytes), array_buffer.handle_mut())
388 .is_ok()
389 );
390
391 *result.borrow_mut() =
392 Some(FileReaderResult::ArrayBuffer(RootedTraceableBox::default()));
393
394 if let Some(FileReaderResult::ArrayBuffer(ref mut heap)) = *result.borrow_mut() {
395 heap.set(jsval::ObjectValue(array_buffer.get()));
396 };
397 }
398 }
399}
400
401impl FileReaderMethods<crate::DomTypeHolder> for FileReader {
402 fn Constructor(
404 cx: &mut js::context::JSContext,
405 global: &GlobalScope,
406 proto: Option<HandleObject>,
407 ) -> Fallible<DomRoot<FileReader>> {
408 Ok(FileReader::new(cx, global, proto))
409 }
410
411 event_handler!(loadstart, GetOnloadstart, SetOnloadstart);
413
414 event_handler!(progress, GetOnprogress, SetOnprogress);
416
417 event_handler!(load, GetOnload, SetOnload);
419
420 event_handler!(abort, GetOnabort, SetOnabort);
422
423 event_handler!(error, GetOnerror, SetOnerror);
425
426 event_handler!(loadend, GetOnloadend, SetOnloadend);
428
429 fn ReadAsArrayBuffer(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
431 self.read(cx, FileReaderFunction::ArrayBuffer, blob, None)
434 }
435
436 fn ReadAsBinaryString(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
438 self.read(cx, FileReaderFunction::BinaryString, blob, None)
441 }
442
443 fn ReadAsDataURL(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
445 self.read(cx, FileReaderFunction::DataUrl, blob, None)
448 }
449
450 fn ReadAsText(
452 &self,
453 cx: &mut js::context::JSContext,
454 blob: &Blob,
455 encoding: Option<DOMString>,
456 ) -> ErrorResult {
457 self.read(cx, FileReaderFunction::Text, blob, encoding)
460 }
461
462 fn Abort(&self, cx: &mut js::context::JSContext) {
464 if self.ready_state.get() == FileReaderReadyState::Loading {
466 self.change_ready_state(FileReaderReadyState::Done);
467 }
468 *self.result.borrow_mut() = None;
470
471 let exception = DOMException::new(cx, &self.global(), DOMErrorName::AbortError);
472 self.error.set(Some(&exception));
473
474 self.terminate_ongoing_reading();
475 self.dispatch_progress_event(cx, atom!("abort"), 0, None);
477 self.dispatch_progress_event(cx, atom!("loadend"), 0, None);
478 }
479
480 fn GetError(&self) -> Option<DomRoot<DOMException>> {
482 self.error.get()
483 }
484
485 #[expect(unsafe_code)]
486 fn GetResult(&self) -> Option<StringOrObject> {
488 self.result.borrow().as_ref().map(|r| match *r {
489 FileReaderResult::String(ref string) => StringOrObject::String(string.clone()),
490 FileReaderResult::ArrayBuffer(ref arr_buffer) => {
491 let result = RootedTraceableBox::new(Heap::default());
492 unsafe {
493 result.set((*arr_buffer.ptr.get()).to_object());
494 }
495 StringOrObject::Object(result)
496 },
497 })
498 }
499
500 fn ReadyState(&self) -> u16 {
502 self.ready_state.get() as u16
503 }
504}
505
506impl FileReader {
507 fn dispatch_progress_event(
508 &self,
509 cx: &mut js::context::JSContext,
510 type_: Atom,
511 loaded: u64,
512 total: Option<u64>,
513 ) {
514 let progressevent = ProgressEvent::new(
515 cx,
516 &self.global(),
517 type_,
518 EventBubbles::DoesNotBubble,
519 EventCancelable::NotCancelable,
520 total.is_some(),
521 Finite::wrap(loaded as f64),
522 Finite::wrap(total.unwrap_or(0) as f64),
523 );
524 progressevent.upcast::<Event>().fire(cx, self.upcast());
525 }
526
527 fn terminate_ongoing_reading(&self) {
528 let GenerationId(prev_id) = self.generation_id.get();
529 self.generation_id.set(GenerationId(prev_id + 1));
530 }
531
532 fn read(
534 &self,
535 cx: &mut js::context::JSContext,
536 function: FileReaderFunction,
537 blob: &Blob,
538 encoding: Option<DOMString>,
539 ) -> ErrorResult {
540 if self.ready_state.get() == FileReaderReadyState::Loading {
542 return Err(Error::InvalidState(None));
543 }
544
545 self.change_ready_state(FileReaderReadyState::Loading);
547
548 *self.result.borrow_mut() = None;
550
551 let stream = blob.get_stream(cx);
556
557 let reader = stream.and_then(|s| s.acquire_default_reader(cx))?;
559
560 let load_data = ReadMetaData::new(
561 String::from(blob.Type()),
562 encoding.map(String::from),
563 function,
564 );
565
566 let GenerationId(prev_id) = self.generation_id.get();
567 self.generation_id.set(GenerationId(prev_id + 1));
568 let gen_id = self.generation_id.get();
569
570 let filereader_success = DomRoot::from_ref(self);
571 let filereader_error = DomRoot::from_ref(self);
572
573 reader.read_all_bytes(
583 cx,
584 Rc::new(move |_cx, blob_contents| {
585 let global = filereader_success.global();
586 let task_manager = global.task_manager();
587 let task_source = task_manager.file_reading_task_source();
588
589 task_source.queue(FileReadingTask::ProcessRead(
595 Trusted::new(&filereader_success.clone()),
596 gen_id,
597 ));
598 if !blob_contents.is_empty() {
604 task_source.queue(FileReadingTask::ProcessReadData(
605 Trusted::new(&filereader_success.clone()),
606 gen_id,
607 ));
608 }
609 task_source.queue(FileReadingTask::ProcessReadEOF(
615 Trusted::new(&filereader_success.clone()),
616 gen_id,
617 load_data.clone(),
618 blob_contents.to_vec(),
619 ));
620 }),
621 Rc::new(move |_cx, _error| {
622 let global = filereader_error.global();
623 let task_manager = global.task_manager();
624 let task_source = task_manager.file_reading_task_source();
625
626 task_source.queue(FileReadingTask::ProcessReadError(
631 Trusted::new(&filereader_error),
632 gen_id,
633 DOMErrorName::OperationError,
634 ));
635 }),
636 );
637 Ok(())
638 }
639
640 fn change_ready_state(&self, state: FileReaderReadyState) {
641 self.ready_state.set(state);
642 }
643}