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::num::Finite;
18use stylo_atoms::Atom;
19
20use crate::dom::bindings::cell::DomRefCell;
21use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
22use crate::dom::bindings::codegen::Bindings::FileReaderBinding::{
23 FileReaderConstants, FileReaderMethods,
24};
25use crate::dom::bindings::codegen::UnionTypes::StringOrObject;
26use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
27use crate::dom::bindings::inheritance::Castable;
28use crate::dom::bindings::refcounted::Trusted;
29use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto};
30use crate::dom::bindings::root::{DomRoot, MutNullableDom};
31use crate::dom::bindings::str::DOMString;
32use crate::dom::bindings::trace::RootedTraceableBox;
33use crate::dom::blob::Blob;
34use crate::dom::domexception::{DOMErrorName, DOMException};
35use crate::dom::event::{Event, EventBubbles, EventCancelable};
36use crate::dom::eventtarget::EventTarget;
37use crate::dom::globalscope::GlobalScope;
38use crate::dom::progressevent::ProgressEvent;
39use crate::realms::enter_realm;
40use crate::script_runtime::{CanGc, JSContext};
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) {
52 self.handle_task(CanGc::note());
53 }
54}
55
56impl FileReadingTask {
57 pub(crate) fn handle_task(self, can_gc: CanGc) {
58 use self::FileReadingTask::*;
59
60 match self {
61 ProcessRead(reader, gen_id) => FileReader::process_read(reader, gen_id, can_gc),
62 ProcessReadData(reader, gen_id) => {
63 FileReader::process_read_data(reader, gen_id, can_gc)
64 },
65 ProcessReadError(reader, gen_id, error) => {
66 FileReader::process_read_error(reader, gen_id, error, can_gc)
67 },
68 ProcessReadEOF(reader, gen_id, metadata, blob_contents) => {
69 FileReader::process_read_eof(reader, gen_id, metadata, blob_contents, can_gc)
70 },
71 }
72 }
73}
74#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
75pub(crate) enum FileReaderFunction {
76 Text,
77 DataUrl,
78 ArrayBuffer,
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) label: Option<String>,
87 pub(crate) function: FileReaderFunction,
88}
89
90impl ReadMetaData {
91 pub(crate) fn new(
92 blobtype: String,
93 label: Option<String>,
94 function: FileReaderFunction,
95 ) -> ReadMetaData {
96 ReadMetaData {
97 blobtype,
98 label,
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_format(blob_contents: &[u8], blob_type: String) -> DOMString {
125 let base64 = base64::engine::general_purpose::STANDARD.encode(blob_contents);
126
127 let dataurl = if blob_type.is_empty() {
128 format!("data:base64,{}", base64)
129 } else {
130 format!("data:{};base64,{}", blob_type, base64)
131 };
132
133 DOMString::from(dataurl)
134 }
135
136 pub(crate) fn text_decode(
137 blob_contents: &[u8],
138 blob_type: &str,
139 blob_label: &Option<String>,
140 ) -> DOMString {
141 let mut encoding = blob_label
144 .as_ref()
145 .map(|string| string.as_bytes())
146 .and_then(Encoding::for_label);
147
148 encoding = encoding.or_else(|| {
150 let resultmime = blob_type.parse::<Mime>().ok();
151 resultmime.and_then(|mime| {
152 mime.params()
153 .find(|(k, _)| &mime::CHARSET == k)
154 .and_then(|(_, v)| Encoding::for_label(v.as_ref().as_bytes()))
155 })
156 });
157
158 let enc = encoding.unwrap_or(UTF_8);
160
161 let convert = blob_contents;
162 let (output, _, _) = enc.decode(convert);
164 DOMString::from(output)
165 }
166}
167
168#[dom_struct]
169pub(crate) struct FileReader {
170 eventtarget: EventTarget,
171 ready_state: Cell<FileReaderReadyState>,
172 error: MutNullableDom<DOMException>,
173 result: DomRefCell<Option<FileReaderResult>>,
174 generation_id: Cell<GenerationId>,
175}
176
177impl FileReader {
178 pub(crate) fn new_inherited() -> FileReader {
179 FileReader {
180 eventtarget: EventTarget::new_inherited(),
181 ready_state: Cell::new(FileReaderReadyState::Empty),
182 error: MutNullableDom::new(None),
183 result: DomRefCell::new(None),
184 generation_id: Cell::new(GenerationId(0)),
185 }
186 }
187
188 fn new(
189 global: &GlobalScope,
190 proto: Option<HandleObject>,
191 can_gc: CanGc,
192 ) -> DomRoot<FileReader> {
193 reflect_dom_object_with_proto(Box::new(FileReader::new_inherited()), global, proto, can_gc)
194 }
195
196 pub(crate) fn process_read_error(
198 filereader: TrustedFileReader,
199 gen_id: GenerationId,
200 error: DOMErrorName,
201 can_gc: CanGc,
202 ) {
203 let fr = filereader.root();
204
205 macro_rules! return_on_abort(
206 () => (
207 if gen_id != fr.generation_id.get() {
208 return
209 }
210 );
211 );
212
213 return_on_abort!();
214 fr.change_ready_state(FileReaderReadyState::Done);
216 *fr.result.borrow_mut() = None;
217
218 let exception = DOMException::new(&fr.global(), error, can_gc);
219 fr.error.set(Some(&exception));
220
221 fr.dispatch_progress_event(atom!("error"), 0, None, can_gc);
222 return_on_abort!();
223 fr.dispatch_progress_event(atom!("loadend"), 0, None, can_gc);
225 return_on_abort!();
226 fr.terminate_ongoing_reading();
228 }
229
230 pub(crate) fn process_read_data(
232 filereader: TrustedFileReader,
233 gen_id: GenerationId,
234 can_gc: CanGc,
235 ) {
236 let fr = filereader.root();
237
238 macro_rules! return_on_abort(
239 () => (
240 if gen_id != fr.generation_id.get() {
241 return
242 }
243 );
244 );
245 return_on_abort!();
246 fr.dispatch_progress_event(atom!("progress"), 0, None, can_gc);
248 }
249
250 pub(crate) fn process_read(filereader: TrustedFileReader, gen_id: GenerationId, can_gc: CanGc) {
252 let fr = filereader.root();
253
254 macro_rules! return_on_abort(
255 () => (
256 if gen_id != fr.generation_id.get() {
257 return
258 }
259 );
260 );
261 return_on_abort!();
262 fr.dispatch_progress_event(atom!("loadstart"), 0, None, can_gc);
264 }
265
266 pub(crate) fn process_read_eof(
268 filereader: TrustedFileReader,
269 gen_id: GenerationId,
270 data: ReadMetaData,
271 blob_contents: Vec<u8>,
272 can_gc: CanGc,
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
284 return_on_abort!();
285 fr.change_ready_state(FileReaderReadyState::Done);
287 match data.function {
290 FileReaderFunction::DataUrl => {
291 FileReader::perform_readasdataurl(&fr.result, data, &blob_contents)
292 },
293 FileReaderFunction::Text => {
294 FileReader::perform_readastext(&fr.result, data, &blob_contents)
295 },
296 FileReaderFunction::ArrayBuffer => {
297 let _ac = enter_realm(&*fr);
298 FileReader::perform_readasarraybuffer(
299 &fr.result,
300 GlobalScope::get_cx(),
301 data,
302 &blob_contents,
303 )
304 },
305 };
306
307 fr.dispatch_progress_event(atom!("load"), 0, None, can_gc);
309 return_on_abort!();
310 if fr.ready_state.get() != FileReaderReadyState::Loading {
312 fr.dispatch_progress_event(atom!("loadend"), 0, None, can_gc);
313 }
314 return_on_abort!();
315 }
316
317 fn perform_readastext(
319 result: &DomRefCell<Option<FileReaderResult>>,
320 data: ReadMetaData,
321 blob_bytes: &[u8],
322 ) {
323 let blob_label = &data.label;
324 let blob_type = &data.blobtype;
325
326 let output = FileReaderSharedFunctionality::text_decode(blob_bytes, blob_type, blob_label);
327 *result.borrow_mut() = Some(FileReaderResult::String(output));
328 }
329
330 fn perform_readasdataurl(
332 result: &DomRefCell<Option<FileReaderResult>>,
333 data: ReadMetaData,
334 bytes: &[u8],
335 ) {
336 let output = FileReaderSharedFunctionality::dataurl_format(bytes, data.blobtype);
337
338 *result.borrow_mut() = Some(FileReaderResult::String(output));
339 }
340
341 #[expect(unsafe_code)]
343 fn perform_readasarraybuffer(
344 result: &DomRefCell<Option<FileReaderResult>>,
345 cx: JSContext,
346 _: ReadMetaData,
347 bytes: &[u8],
348 ) {
349 unsafe {
350 rooted!(in(*cx) let mut array_buffer = ptr::null_mut::<JSObject>());
351 assert!(
352 ArrayBuffer::create(*cx, CreateWith::Slice(bytes), array_buffer.handle_mut())
353 .is_ok()
354 );
355
356 *result.borrow_mut() =
357 Some(FileReaderResult::ArrayBuffer(RootedTraceableBox::default()));
358
359 if let Some(FileReaderResult::ArrayBuffer(ref mut heap)) = *result.borrow_mut() {
360 heap.set(jsval::ObjectValue(array_buffer.get()));
361 };
362 }
363 }
364}
365
366impl FileReaderMethods<crate::DomTypeHolder> for FileReader {
367 fn Constructor(
369 global: &GlobalScope,
370 proto: Option<HandleObject>,
371 can_gc: CanGc,
372 ) -> Fallible<DomRoot<FileReader>> {
373 Ok(FileReader::new(global, proto, can_gc))
374 }
375
376 event_handler!(loadstart, GetOnloadstart, SetOnloadstart);
378
379 event_handler!(progress, GetOnprogress, SetOnprogress);
381
382 event_handler!(load, GetOnload, SetOnload);
384
385 event_handler!(abort, GetOnabort, SetOnabort);
387
388 event_handler!(error, GetOnerror, SetOnerror);
390
391 event_handler!(loadend, GetOnloadend, SetOnloadend);
393
394 fn ReadAsArrayBuffer(&self, blob: &Blob, can_gc: CanGc) -> ErrorResult {
396 self.read(FileReaderFunction::ArrayBuffer, blob, None, can_gc)
397 }
398
399 fn ReadAsDataURL(&self, blob: &Blob, can_gc: CanGc) -> ErrorResult {
401 self.read(FileReaderFunction::DataUrl, blob, None, can_gc)
402 }
403
404 fn ReadAsText(&self, blob: &Blob, label: Option<DOMString>, can_gc: CanGc) -> ErrorResult {
406 self.read(FileReaderFunction::Text, blob, label, can_gc)
407 }
408
409 fn Abort(&self, can_gc: CanGc) {
411 if self.ready_state.get() == FileReaderReadyState::Loading {
413 self.change_ready_state(FileReaderReadyState::Done);
414 }
415 *self.result.borrow_mut() = None;
417
418 let exception = DOMException::new(&self.global(), DOMErrorName::AbortError, can_gc);
419 self.error.set(Some(&exception));
420
421 self.terminate_ongoing_reading();
422 self.dispatch_progress_event(atom!("abort"), 0, None, can_gc);
424 self.dispatch_progress_event(atom!("loadend"), 0, None, can_gc);
425 }
426
427 fn GetError(&self) -> Option<DomRoot<DOMException>> {
429 self.error.get()
430 }
431
432 #[expect(unsafe_code)]
433 fn GetResult(&self, _: JSContext) -> Option<StringOrObject> {
435 self.result.borrow().as_ref().map(|r| match *r {
436 FileReaderResult::String(ref string) => StringOrObject::String(string.clone()),
437 FileReaderResult::ArrayBuffer(ref arr_buffer) => {
438 let result = RootedTraceableBox::new(Heap::default());
439 unsafe {
440 result.set((*arr_buffer.ptr.get()).to_object());
441 }
442 StringOrObject::Object(result)
443 },
444 })
445 }
446
447 fn ReadyState(&self) -> u16 {
449 self.ready_state.get() as u16
450 }
451}
452
453impl FileReader {
454 fn dispatch_progress_event(&self, type_: Atom, loaded: u64, total: Option<u64>, can_gc: CanGc) {
455 let progressevent = ProgressEvent::new(
456 &self.global(),
457 type_,
458 EventBubbles::DoesNotBubble,
459 EventCancelable::NotCancelable,
460 total.is_some(),
461 Finite::wrap(loaded as f64),
462 Finite::wrap(total.unwrap_or(0) as f64),
463 can_gc,
464 );
465 progressevent.upcast::<Event>().fire(self.upcast(), can_gc);
466 }
467
468 fn terminate_ongoing_reading(&self) {
469 let GenerationId(prev_id) = self.generation_id.get();
470 self.generation_id.set(GenerationId(prev_id + 1));
471 }
472
473 fn read(
475 &self,
476 function: FileReaderFunction,
477 blob: &Blob,
478 label: Option<DOMString>,
479 can_gc: CanGc,
480 ) -> ErrorResult {
481 let cx = GlobalScope::get_cx();
482
483 if self.ready_state.get() == FileReaderReadyState::Loading {
485 return Err(Error::InvalidState(None));
486 }
487
488 self.change_ready_state(FileReaderReadyState::Loading);
490
491 *self.result.borrow_mut() = None;
493
494 let stream = blob.get_stream(can_gc);
499
500 let reader = stream.and_then(|s| s.acquire_default_reader(can_gc))?;
502
503 let type_ = blob.Type();
504
505 let load_data = ReadMetaData::new(String::from(type_), label.map(String::from), function);
506
507 let GenerationId(prev_id) = self.generation_id.get();
508 self.generation_id.set(GenerationId(prev_id + 1));
509 let gen_id = self.generation_id.get();
510
511 let filereader_success = DomRoot::from_ref(self);
512 let filereader_error = DomRoot::from_ref(self);
513
514 reader.read_all_bytes(
524 cx,
525 Rc::new(move |blob_contents| {
526 let global = filereader_success.global();
527 let task_manager = global.task_manager();
528 let task_source = task_manager.file_reading_task_source();
529
530 task_source.queue(FileReadingTask::ProcessRead(
536 Trusted::new(&filereader_success.clone()),
537 gen_id,
538 ));
539 if !blob_contents.is_empty() {
545 task_source.queue(FileReadingTask::ProcessReadData(
546 Trusted::new(&filereader_success.clone()),
547 gen_id,
548 ));
549 }
550 task_source.queue(FileReadingTask::ProcessReadEOF(
556 Trusted::new(&filereader_success.clone()),
557 gen_id,
558 load_data.clone(),
559 blob_contents.to_vec(),
560 ));
561 }),
562 Rc::new(move |_cx, _error| {
563 let global = filereader_error.global();
564 let task_manager = global.task_manager();
565 let task_source = task_manager.file_reading_task_source();
566
567 task_source.queue(FileReadingTask::ProcessReadError(
572 Trusted::new(&filereader_error),
573 gen_id,
574 DOMErrorName::OperationError,
575 ));
576 }),
577 can_gc,
578 );
579 Ok(())
580 }
581
582 fn change_ready_state(&self, state: FileReaderReadyState) {
583 self.ready_state.set(state);
584 }
585}