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::ArrayBufferU8;
16use mime::{self, Mime};
17use script_bindings::cell::DomRefCell;
18use script_bindings::domstring::TracedDOMString;
19use script_bindings::num::Finite;
20use script_bindings::reflector::reflect_dom_object_with_proto;
21use stylo_atoms::Atom;
22
23use crate::dom::bindings::buffer_source::create_buffer_source;
24use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
25use crate::dom::bindings::codegen::Bindings::FileReaderBinding::{
26 FileReaderConstants, FileReaderMethods,
27};
28use crate::dom::bindings::codegen::UnionTypes::StringOrObject;
29use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
30use crate::dom::bindings::inheritance::Castable;
31use crate::dom::bindings::refcounted::Trusted;
32use crate::dom::bindings::reflector::DomGlobal;
33use crate::dom::bindings::root::{DomRoot, MutNullableDom};
34use crate::dom::bindings::str::DOMString;
35use crate::dom::bindings::trace::RootedTraceableBox;
36use crate::dom::blob::Blob;
37use crate::dom::domexception::{DOMErrorName, DOMException};
38use crate::dom::event::{Event, EventBubbles, EventCancelable};
39use crate::dom::eventtarget::EventTarget;
40use crate::dom::globalscope::GlobalScope;
41use crate::dom::progressevent::ProgressEvent;
42use crate::realms::enter_auto_realm;
43use crate::tasks::task::TaskOnce;
44
45pub(crate) enum FileReadingTask {
46 ProcessRead(TrustedFileReader, GenerationId),
47 ProcessReadData(TrustedFileReader, GenerationId),
48 ProcessReadError(TrustedFileReader, GenerationId, DOMErrorName),
49 ProcessReadEOF(TrustedFileReader, GenerationId, ReadMetaData, Vec<u8>),
50}
51
52impl TaskOnce for FileReadingTask {
53 fn run_once(self, cx: &mut js::context::JSContext) {
54 self.handle_task(cx);
55 }
56}
57
58impl FileReadingTask {
59 pub(crate) fn handle_task(self, cx: &mut js::context::JSContext) {
60 use self::FileReadingTask::*;
61
62 match self {
63 ProcessRead(reader, gen_id) => FileReader::process_read(cx, reader, gen_id),
64 ProcessReadData(reader, gen_id) => FileReader::process_read_data(cx, reader, gen_id),
65 ProcessReadError(reader, gen_id, error) => {
66 FileReader::process_read_error(cx, reader, gen_id, error)
67 },
68 ProcessReadEOF(reader, gen_id, metadata, blob_contents) => {
69 FileReader::process_read_eof(cx, reader, gen_id, metadata, blob_contents)
70 },
71 }
72 }
73}
74#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
75pub(crate) enum FileReaderFunction {
76 Text,
77 DataUrl,
78 ArrayBuffer,
79 BinaryString,
80}
81
82pub(crate) type TrustedFileReader = Trusted<FileReader>;
83
84#[derive(Clone, MallocSizeOf)]
85pub(crate) struct ReadMetaData {
86 pub(crate) blobtype: String,
87 pub(crate) encoding: Option<String>,
88 pub(crate) function: FileReaderFunction,
89}
90
91impl ReadMetaData {
92 pub(crate) fn new(
93 blobtype: String,
94 encoding: Option<String>,
95 function: FileReaderFunction,
96 ) -> ReadMetaData {
97 ReadMetaData {
98 blobtype,
99 encoding,
100 function,
101 }
102 }
103}
104
105#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
106pub(crate) struct GenerationId(u32);
107
108#[repr(u16)]
109#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
110pub(crate) enum FileReaderReadyState {
111 Empty = FileReaderConstants::EMPTY,
112 Loading = FileReaderConstants::LOADING,
113 Done = FileReaderConstants::DONE,
114}
115
116#[derive(JSTraceable, MallocSizeOf)]
117#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
118pub(crate) enum FileReaderResult {
119 ArrayBuffer(#[ignore_malloc_size_of = "mozjs"] Heap<JSVal>),
120 String(TracedDOMString),
121}
122
123pub(crate) struct FileReaderSharedFunctionality;
124
125impl FileReaderSharedFunctionality {
126 pub(crate) fn dataurl_for_bytes(bytes: &[u8], blob_type: &str) -> DOMString {
128 let mime_type = if blob_type.is_empty() {
132 "application/octet-stream"
133 } else {
134 blob_type
135 };
136
137 Self::dataurl_format(bytes, mime_type)
138 }
139
140 fn dataurl_format(bytes: &[u8], mime_type: &str) -> DOMString {
143 let base64 = base64::engine::general_purpose::STANDARD.encode(bytes);
144 let dataurl = format!("data:{};base64,{}", mime_type, base64);
145
146 DOMString::from(dataurl)
147 }
148
149 pub(crate) fn binary_string_for_bytes(bytes: &[u8]) -> DOMString {
151 DOMString::from(bytes.iter().map(|&byte| byte as char).collect::<String>())
152 }
153
154 pub(crate) fn text_for_bytes(
156 bytes: &[u8],
157 blob_type: &str,
158 encoding: &Option<String>,
159 ) -> DOMString {
160 let mut encoding = encoding
164 .as_ref()
165 .map(|string| string.as_bytes())
166 .and_then(Encoding::for_label);
167
168 encoding = encoding.or_else(|| {
170 let resultmime = blob_type.parse::<Mime>().ok();
171 resultmime.and_then(|mime| {
172 mime.params()
173 .find(|(k, _)| &mime::CHARSET == k)
174 .and_then(|(_, v)| Encoding::for_label(v.as_ref().as_bytes()))
175 })
176 });
177
178 let enc = encoding.unwrap_or(UTF_8);
180
181 let convert = bytes;
182 let (output, _, _) = enc.decode(convert);
185 DOMString::from(output)
186 }
187}
188
189#[dom_struct]
190pub(crate) struct FileReader {
191 eventtarget: EventTarget,
192 ready_state: Cell<FileReaderReadyState>,
193 error: MutNullableDom<DOMException>,
194 result: DomRefCell<Option<FileReaderResult>>,
195 generation_id: Cell<GenerationId>,
196}
197
198impl FileReader {
199 pub(crate) fn new_inherited() -> FileReader {
200 FileReader {
201 eventtarget: EventTarget::new_inherited(),
202 ready_state: Cell::new(FileReaderReadyState::Empty),
203 error: MutNullableDom::new(None),
204 result: DomRefCell::new(None),
205 generation_id: Cell::new(GenerationId(0)),
206 }
207 }
208
209 fn new(
210 cx: &mut js::context::JSContext,
211 global: &GlobalScope,
212 proto: Option<HandleObject>,
213 ) -> DomRoot<FileReader> {
214 reflect_dom_object_with_proto(cx, Box::new(FileReader::new_inherited()), global, proto)
215 }
216
217 pub(crate) fn process_read_error(
219 cx: &mut js::context::JSContext,
220 filereader: TrustedFileReader,
221 gen_id: GenerationId,
222 error: DOMErrorName,
223 ) {
224 let fr = filereader.root();
225
226 macro_rules! return_on_abort(
227 () => (
228 if gen_id != fr.generation_id.get() {
229 return
230 }
231 );
232 );
233
234 return_on_abort!();
235 fr.change_ready_state(FileReaderReadyState::Done);
237 *fr.result.borrow_mut() = None;
238
239 let exception = DOMException::new(cx, &fr.global(), error);
240 fr.error.set(Some(&exception));
241
242 fr.dispatch_progress_event(cx, atom!("error"), 0, None);
243 return_on_abort!();
244 fr.dispatch_progress_event(cx, atom!("loadend"), 0, None);
246 return_on_abort!();
247 fr.terminate_ongoing_reading();
249 }
250
251 pub(crate) fn process_read_data(
253 cx: &mut js::context::JSContext,
254 filereader: TrustedFileReader,
255 gen_id: GenerationId,
256 ) {
257 let fr = filereader.root();
258
259 macro_rules! return_on_abort(
260 () => (
261 if gen_id != fr.generation_id.get() {
262 return
263 }
264 );
265 );
266 return_on_abort!();
267 fr.dispatch_progress_event(cx, atom!("progress"), 0, None);
269 }
270
271 pub(crate) fn process_read(
273 cx: &mut js::context::JSContext,
274 filereader: TrustedFileReader,
275 gen_id: GenerationId,
276 ) {
277 let fr = filereader.root();
278
279 macro_rules! return_on_abort(
280 () => (
281 if gen_id != fr.generation_id.get() {
282 return
283 }
284 );
285 );
286 return_on_abort!();
287 fr.dispatch_progress_event(cx, atom!("loadstart"), 0, None);
289 }
290
291 pub(crate) fn process_read_eof(
293 cx: &mut js::context::JSContext,
294 filereader: TrustedFileReader,
295 gen_id: GenerationId,
296 data: ReadMetaData,
297 blob_contents: Vec<u8>,
298 ) {
299 let fr = filereader.root();
300
301 macro_rules! return_on_abort(
302 () => (
303 if gen_id != fr.generation_id.get() {
304 return
305 }
306 );
307 );
308
309 return_on_abort!();
310 fr.change_ready_state(FileReaderReadyState::Done);
312
313 match data.function {
318 FileReaderFunction::DataUrl => {
319 FileReader::perform_readasdataurl(&fr.result, data, &blob_contents)
320 },
321 FileReaderFunction::Text => {
322 FileReader::perform_readastext(&fr.result, data, &blob_contents)
323 },
324 FileReaderFunction::ArrayBuffer => {
325 let mut realm = enter_auto_realm(cx, &*fr);
326 let cx = &mut realm.current_realm();
327 FileReader::perform_readasarraybuffer(cx, &fr.result, &blob_contents)
328 },
329 FileReaderFunction::BinaryString => {
330 FileReader::perform_readasbinarystring(&fr.result, &blob_contents)
331 },
332 };
333
334 fr.dispatch_progress_event(cx, atom!("load"), 0, None);
336 return_on_abort!();
337 if fr.ready_state.get() != FileReaderReadyState::Loading {
339 fr.dispatch_progress_event(cx, atom!("loadend"), 0, None);
340 }
341 return_on_abort!();
342 }
343
344 fn perform_readastext(
346 result: &DomRefCell<Option<FileReaderResult>>,
347 data: ReadMetaData,
348 blob_bytes: &[u8],
349 ) {
350 *result.borrow_mut() = Some(FileReaderResult::String(
351 FileReaderSharedFunctionality::text_for_bytes(
352 blob_bytes,
353 &data.blobtype,
354 &data.encoding,
355 )
356 .traced(),
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).traced(),
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).traced(),
377 ));
378 }
379
380 fn perform_readasarraybuffer(
383 cx: &mut js::context::JSContext,
384 result: &DomRefCell<Option<FileReaderResult>>,
385 bytes: &[u8],
386 ) {
387 rooted!(&in(cx) let mut array_buffer = ptr::null_mut::<JSObject>());
388 assert!(
389 create_buffer_source::<ArrayBufferU8>(cx, bytes, array_buffer.handle_mut()).is_ok()
390 );
391
392 *result.borrow_mut() = Some(FileReaderResult::ArrayBuffer(Heap::default()));
393
394 if let Some(FileReaderResult::ArrayBuffer(heap)) = &*result.borrow() {
395 heap.set(jsval::ObjectValue(array_buffer.get()));
396 };
397 }
398}
399
400impl FileReaderMethods<crate::DomTypeHolder> for FileReader {
401 fn Constructor(
403 cx: &mut js::context::JSContext,
404 global: &GlobalScope,
405 proto: Option<HandleObject>,
406 ) -> Fallible<DomRoot<FileReader>> {
407 Ok(FileReader::new(cx, global, proto))
408 }
409
410 event_handler!(loadstart, GetOnloadstart, SetOnloadstart);
412
413 event_handler!(progress, GetOnprogress, SetOnprogress);
415
416 event_handler!(load, GetOnload, SetOnload);
418
419 event_handler!(abort, GetOnabort, SetOnabort);
421
422 event_handler!(error, GetOnerror, SetOnerror);
424
425 event_handler!(loadend, GetOnloadend, SetOnloadend);
427
428 fn ReadAsArrayBuffer(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
430 self.read(cx, FileReaderFunction::ArrayBuffer, blob, None)
433 }
434
435 fn ReadAsBinaryString(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
437 self.read(cx, FileReaderFunction::BinaryString, blob, None)
440 }
441
442 fn ReadAsDataURL(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult {
444 self.read(cx, FileReaderFunction::DataUrl, blob, None)
447 }
448
449 fn ReadAsText(
451 &self,
452 cx: &mut js::context::JSContext,
453 blob: &Blob,
454 encoding: Option<DOMString>,
455 ) -> ErrorResult {
456 self.read(cx, FileReaderFunction::Text, blob, encoding)
459 }
460
461 fn Abort(&self, cx: &mut js::context::JSContext) {
463 if self.ready_state.get() == FileReaderReadyState::Loading {
465 self.change_ready_state(FileReaderReadyState::Done);
466 }
467 *self.result.borrow_mut() = None;
469
470 let exception = DOMException::new(cx, &self.global(), DOMErrorName::AbortError);
471 self.error.set(Some(&exception));
472
473 self.terminate_ongoing_reading();
474 self.dispatch_progress_event(cx, atom!("abort"), 0, None);
476 self.dispatch_progress_event(cx, atom!("loadend"), 0, None);
477 }
478
479 fn GetError(&self) -> Option<DomRoot<DOMException>> {
481 self.error.get()
482 }
483
484 fn GetResult(&self) -> Option<StringOrObject> {
486 self.result.borrow().as_ref().map(|r| match *r {
487 FileReaderResult::String(ref string) => StringOrObject::String(string.clone().root()),
488 FileReaderResult::ArrayBuffer(ref array_buffer) => {
489 let result = RootedTraceableBox::new(Heap::default());
490 result.set(array_buffer.get().to_object());
491 StringOrObject::Object(result)
492 },
493 })
494 }
495
496 fn ReadyState(&self) -> u16 {
498 self.ready_state.get() as u16
499 }
500}
501
502impl FileReader {
503 fn dispatch_progress_event(
504 &self,
505 cx: &mut js::context::JSContext,
506 type_: Atom,
507 loaded: u64,
508 total: Option<u64>,
509 ) {
510 let progressevent = ProgressEvent::new(
511 cx,
512 &self.global(),
513 type_,
514 EventBubbles::DoesNotBubble,
515 EventCancelable::NotCancelable,
516 total.is_some(),
517 Finite::wrap(loaded as f64),
518 Finite::wrap(total.unwrap_or(0) as f64),
519 );
520 progressevent.upcast::<Event>().fire(cx, self.upcast());
521 }
522
523 fn terminate_ongoing_reading(&self) {
524 let GenerationId(prev_id) = self.generation_id.get();
525 self.generation_id.set(GenerationId(prev_id + 1));
526 }
527
528 fn read(
530 &self,
531 cx: &mut js::context::JSContext,
532 function: FileReaderFunction,
533 blob: &Blob,
534 encoding: Option<DOMString>,
535 ) -> ErrorResult {
536 if self.ready_state.get() == FileReaderReadyState::Loading {
538 return Err(Error::InvalidState(None));
539 }
540
541 self.change_ready_state(FileReaderReadyState::Loading);
543
544 *self.result.borrow_mut() = None;
546
547 let stream = blob.get_stream(cx);
552
553 let reader = stream.and_then(|s| s.acquire_default_reader(cx))?;
555
556 let load_data = ReadMetaData::new(
557 String::from(blob.Type()),
558 encoding.map(String::from),
559 function,
560 );
561
562 let GenerationId(prev_id) = self.generation_id.get();
563 self.generation_id.set(GenerationId(prev_id + 1));
564 let gen_id = self.generation_id.get();
565
566 let filereader_success = DomRoot::from_ref(self);
567 let filereader_error = DomRoot::from_ref(self);
568
569 reader.read_all_bytes(
579 cx,
580 Rc::new(move |_cx, blob_contents| {
581 let global = filereader_success.global();
582 let task_manager = global.task_manager();
583 let task_source = task_manager.file_reading_task_source();
584
585 task_source.queue(FileReadingTask::ProcessRead(
591 Trusted::new(&filereader_success.clone()),
592 gen_id,
593 ));
594 if !blob_contents.is_empty() {
600 task_source.queue(FileReadingTask::ProcessReadData(
601 Trusted::new(&filereader_success.clone()),
602 gen_id,
603 ));
604 }
605 task_source.queue(FileReadingTask::ProcessReadEOF(
611 Trusted::new(&filereader_success.clone()),
612 gen_id,
613 load_data.clone(),
614 blob_contents,
615 ));
616 }),
617 Rc::new(move |_cx, _error| {
618 let global = filereader_error.global();
619 let task_manager = global.task_manager();
620 let task_source = task_manager.file_reading_task_source();
621
622 task_source.queue(FileReadingTask::ProcessReadError(
627 Trusted::new(&filereader_error),
628 gen_id,
629 DOMErrorName::OperationError,
630 ));
631 }),
632 );
633 Ok(())
634 }
635
636 fn change_ready_state(&self, state: FileReaderReadyState) {
637 self.ready_state.set(state);
638 }
639}