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::{InRealm, enter_realm};
40use crate::script_runtime::{CanGc, JSContext};
41use crate::task::TaskOnce;
42
43#[allow(dead_code)]
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) {
53 self.handle_task(CanGc::note());
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}
81
82pub(crate) type TrustedFileReader = Trusted<FileReader>;
83
84#[derive(Clone, MallocSizeOf)]
85pub(crate) struct ReadMetaData {
86 pub(crate) blobtype: String,
87 pub(crate) label: Option<String>,
88 pub(crate) function: FileReaderFunction,
89}
90
91impl ReadMetaData {
92 pub(crate) fn new(
93 blobtype: String,
94 label: Option<String>,
95 function: FileReaderFunction,
96 ) -> ReadMetaData {
97 ReadMetaData {
98 blobtype,
99 label,
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)]
117pub(crate) enum FileReaderResult {
118 ArrayBuffer(#[ignore_malloc_size_of = "mozjs"] RootedTraceableBox<Heap<JSVal>>),
119 String(DOMString),
120}
121
122pub(crate) struct FileReaderSharedFunctionality;
123
124impl FileReaderSharedFunctionality {
125 pub(crate) fn dataurl_format(blob_contents: &[u8], blob_type: String) -> DOMString {
126 let base64 = base64::engine::general_purpose::STANDARD.encode(blob_contents);
127
128 let dataurl = if blob_type.is_empty() {
129 format!("data:base64,{}", base64)
130 } else {
131 format!("data:{};base64,{}", blob_type, base64)
132 };
133
134 DOMString::from(dataurl)
135 }
136
137 pub(crate) fn text_decode(
138 blob_contents: &[u8],
139 blob_type: &str,
140 blob_label: &Option<String>,
141 ) -> DOMString {
142 let mut encoding = blob_label
145 .as_ref()
146 .map(|string| string.as_bytes())
147 .and_then(Encoding::for_label);
148
149 encoding = encoding.or_else(|| {
151 let resultmime = blob_type.parse::<Mime>().ok();
152 resultmime.and_then(|mime| {
153 mime.params()
154 .find(|(k, _)| &mime::CHARSET == k)
155 .and_then(|(_, v)| Encoding::for_label(v.as_ref().as_bytes()))
156 })
157 });
158
159 let enc = encoding.unwrap_or(UTF_8);
161
162 let convert = blob_contents;
163 let (output, _, _) = enc.decode(convert);
165 DOMString::from(output)
166 }
167}
168
169#[dom_struct]
170pub(crate) struct FileReader {
171 eventtarget: EventTarget,
172 ready_state: Cell<FileReaderReadyState>,
173 error: MutNullableDom<DOMException>,
174 result: DomRefCell<Option<FileReaderResult>>,
175 generation_id: Cell<GenerationId>,
176}
177
178impl FileReader {
179 pub(crate) fn new_inherited() -> FileReader {
180 FileReader {
181 eventtarget: EventTarget::new_inherited(),
182 ready_state: Cell::new(FileReaderReadyState::Empty),
183 error: MutNullableDom::new(None),
184 result: DomRefCell::new(None),
185 generation_id: Cell::new(GenerationId(0)),
186 }
187 }
188
189 fn new(
190 global: &GlobalScope,
191 proto: Option<HandleObject>,
192 can_gc: CanGc,
193 ) -> DomRoot<FileReader> {
194 reflect_dom_object_with_proto(Box::new(FileReader::new_inherited()), global, proto, can_gc)
195 }
196
197 pub(crate) fn process_read_error(
199 filereader: TrustedFileReader,
200 gen_id: GenerationId,
201 error: DOMErrorName,
202 can_gc: CanGc,
203 ) {
204 let fr = filereader.root();
205
206 macro_rules! return_on_abort(
207 () => (
208 if gen_id != fr.generation_id.get() {
209 return
210 }
211 );
212 );
213
214 return_on_abort!();
215 fr.change_ready_state(FileReaderReadyState::Done);
217 *fr.result.borrow_mut() = None;
218
219 let exception = DOMException::new(&fr.global(), error, can_gc);
220 fr.error.set(Some(&exception));
221
222 fr.dispatch_progress_event(atom!("error"), 0, None, can_gc);
223 return_on_abort!();
224 fr.dispatch_progress_event(atom!("loadend"), 0, None, can_gc);
226 return_on_abort!();
227 fr.terminate_ongoing_reading();
229 }
230
231 pub(crate) fn process_read_data(
233 filereader: TrustedFileReader,
234 gen_id: GenerationId,
235 can_gc: CanGc,
236 ) {
237 let fr = filereader.root();
238
239 macro_rules! return_on_abort(
240 () => (
241 if gen_id != fr.generation_id.get() {
242 return
243 }
244 );
245 );
246 return_on_abort!();
247 fr.dispatch_progress_event(atom!("progress"), 0, None, can_gc);
249 }
250
251 pub(crate) fn process_read(filereader: TrustedFileReader, gen_id: GenerationId, can_gc: CanGc) {
253 let fr = filereader.root();
254
255 macro_rules! return_on_abort(
256 () => (
257 if gen_id != fr.generation_id.get() {
258 return
259 }
260 );
261 );
262 return_on_abort!();
263 fr.dispatch_progress_event(atom!("loadstart"), 0, None, can_gc);
265 }
266
267 pub(crate) fn process_read_eof(
269 filereader: TrustedFileReader,
270 gen_id: GenerationId,
271 data: ReadMetaData,
272 blob_contents: Vec<u8>,
273 can_gc: CanGc,
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
285 return_on_abort!();
286 fr.change_ready_state(FileReaderReadyState::Done);
288 match data.function {
291 FileReaderFunction::DataUrl => {
292 FileReader::perform_readasdataurl(&fr.result, data, &blob_contents)
293 },
294 FileReaderFunction::Text => {
295 FileReader::perform_readastext(&fr.result, data, &blob_contents)
296 },
297 FileReaderFunction::ArrayBuffer => {
298 let _ac = enter_realm(&*fr);
299 FileReader::perform_readasarraybuffer(
300 &fr.result,
301 GlobalScope::get_cx(),
302 data,
303 &blob_contents,
304 )
305 },
306 };
307
308 fr.dispatch_progress_event(atom!("load"), 0, None, can_gc);
310 return_on_abort!();
311 if fr.ready_state.get() != FileReaderReadyState::Loading {
313 fr.dispatch_progress_event(atom!("loadend"), 0, None, can_gc);
314 }
315 return_on_abort!();
316 }
317
318 fn perform_readastext(
320 result: &DomRefCell<Option<FileReaderResult>>,
321 data: ReadMetaData,
322 blob_bytes: &[u8],
323 ) {
324 let blob_label = &data.label;
325 let blob_type = &data.blobtype;
326
327 let output = FileReaderSharedFunctionality::text_decode(blob_bytes, blob_type, blob_label);
328 *result.borrow_mut() = Some(FileReaderResult::String(output));
329 }
330
331 fn perform_readasdataurl(
333 result: &DomRefCell<Option<FileReaderResult>>,
334 data: ReadMetaData,
335 bytes: &[u8],
336 ) {
337 let output = FileReaderSharedFunctionality::dataurl_format(bytes, data.blobtype);
338
339 *result.borrow_mut() = Some(FileReaderResult::String(output));
340 }
341
342 #[allow(unsafe_code)]
344 fn perform_readasarraybuffer(
345 result: &DomRefCell<Option<FileReaderResult>>,
346 cx: JSContext,
347 _: ReadMetaData,
348 bytes: &[u8],
349 ) {
350 unsafe {
351 rooted!(in(*cx) let mut array_buffer = ptr::null_mut::<JSObject>());
352 assert!(
353 ArrayBuffer::create(*cx, CreateWith::Slice(bytes), array_buffer.handle_mut())
354 .is_ok()
355 );
356
357 *result.borrow_mut() =
358 Some(FileReaderResult::ArrayBuffer(RootedTraceableBox::default()));
359
360 if let Some(FileReaderResult::ArrayBuffer(ref mut heap)) = *result.borrow_mut() {
361 heap.set(jsval::ObjectValue(array_buffer.get()));
362 };
363 }
364 }
365}
366
367impl FileReaderMethods<crate::DomTypeHolder> for FileReader {
368 fn Constructor(
370 global: &GlobalScope,
371 proto: Option<HandleObject>,
372 can_gc: CanGc,
373 ) -> Fallible<DomRoot<FileReader>> {
374 Ok(FileReader::new(global, proto, can_gc))
375 }
376
377 event_handler!(loadstart, GetOnloadstart, SetOnloadstart);
379
380 event_handler!(progress, GetOnprogress, SetOnprogress);
382
383 event_handler!(load, GetOnload, SetOnload);
385
386 event_handler!(abort, GetOnabort, SetOnabort);
388
389 event_handler!(error, GetOnerror, SetOnerror);
391
392 event_handler!(loadend, GetOnloadend, SetOnloadend);
394
395 fn ReadAsArrayBuffer(&self, blob: &Blob, realm: InRealm, can_gc: CanGc) -> ErrorResult {
397 self.read(FileReaderFunction::ArrayBuffer, blob, None, realm, can_gc)
398 }
399
400 fn ReadAsDataURL(&self, blob: &Blob, realm: InRealm, can_gc: CanGc) -> ErrorResult {
402 self.read(FileReaderFunction::DataUrl, blob, None, realm, can_gc)
403 }
404
405 fn ReadAsText(
407 &self,
408 blob: &Blob,
409 label: Option<DOMString>,
410 realm: InRealm,
411 can_gc: CanGc,
412 ) -> ErrorResult {
413 self.read(FileReaderFunction::Text, blob, label, realm, can_gc)
414 }
415
416 fn Abort(&self, can_gc: CanGc) {
418 if self.ready_state.get() == FileReaderReadyState::Loading {
420 self.change_ready_state(FileReaderReadyState::Done);
421 }
422 *self.result.borrow_mut() = None;
424
425 let exception = DOMException::new(&self.global(), DOMErrorName::AbortError, can_gc);
426 self.error.set(Some(&exception));
427
428 self.terminate_ongoing_reading();
429 self.dispatch_progress_event(atom!("abort"), 0, None, can_gc);
431 self.dispatch_progress_event(atom!("loadend"), 0, None, can_gc);
432 }
433
434 fn GetError(&self) -> Option<DomRoot<DOMException>> {
436 self.error.get()
437 }
438
439 #[allow(unsafe_code)]
440 fn GetResult(&self, _: JSContext) -> Option<StringOrObject> {
442 self.result.borrow().as_ref().map(|r| match *r {
443 FileReaderResult::String(ref string) => StringOrObject::String(string.clone()),
444 FileReaderResult::ArrayBuffer(ref arr_buffer) => {
445 let result = RootedTraceableBox::new(Heap::default());
446 unsafe {
447 result.set((*arr_buffer.ptr.get()).to_object());
448 }
449 StringOrObject::Object(result)
450 },
451 })
452 }
453
454 fn ReadyState(&self) -> u16 {
456 self.ready_state.get() as u16
457 }
458}
459
460impl FileReader {
461 fn dispatch_progress_event(&self, type_: Atom, loaded: u64, total: Option<u64>, can_gc: CanGc) {
462 let progressevent = ProgressEvent::new(
463 &self.global(),
464 type_,
465 EventBubbles::DoesNotBubble,
466 EventCancelable::NotCancelable,
467 total.is_some(),
468 Finite::wrap(loaded as f64),
469 Finite::wrap(total.unwrap_or(0) as f64),
470 can_gc,
471 );
472 progressevent.upcast::<Event>().fire(self.upcast(), can_gc);
473 }
474
475 fn terminate_ongoing_reading(&self) {
476 let GenerationId(prev_id) = self.generation_id.get();
477 self.generation_id.set(GenerationId(prev_id + 1));
478 }
479
480 fn read(
482 &self,
483 function: FileReaderFunction,
484 blob: &Blob,
485 label: Option<DOMString>,
486 realm: InRealm,
487 can_gc: CanGc,
488 ) -> ErrorResult {
489 let cx = GlobalScope::get_cx();
490
491 if self.ready_state.get() == FileReaderReadyState::Loading {
493 return Err(Error::InvalidState);
494 }
495
496 self.change_ready_state(FileReaderReadyState::Loading);
498
499 *self.result.borrow_mut() = None;
501
502 let stream = blob.get_stream(can_gc);
507
508 let reader = stream.and_then(|s| s.acquire_default_reader(can_gc))?;
510
511 let type_ = blob.Type();
512
513 let load_data = ReadMetaData::new(String::from(type_), label.map(String::from), function);
514
515 let GenerationId(prev_id) = self.generation_id.get();
516 self.generation_id.set(GenerationId(prev_id + 1));
517 let gen_id = self.generation_id.get();
518
519 let filereader_success = DomRoot::from_ref(self);
520 let filereader_error = DomRoot::from_ref(self);
521
522 reader.read_all_bytes(
532 cx,
533 &self.global(),
534 Rc::new(move |blob_contents| {
535 let global = filereader_success.global();
536 let task_manager = global.task_manager();
537 let task_source = task_manager.file_reading_task_source();
538
539 task_source.queue(FileReadingTask::ProcessRead(
545 Trusted::new(&filereader_success.clone()),
546 gen_id,
547 ));
548 if !blob_contents.is_empty() {
554 task_source.queue(FileReadingTask::ProcessReadData(
555 Trusted::new(&filereader_success.clone()),
556 gen_id,
557 ));
558 }
559 task_source.queue(FileReadingTask::ProcessReadEOF(
565 Trusted::new(&filereader_success.clone()),
566 gen_id,
567 load_data.clone(),
568 blob_contents.to_vec(),
569 ));
570 }),
571 Rc::new(move |_cx, _error| {
572 let global = filereader_error.global();
573 let task_manager = global.task_manager();
574 let task_source = task_manager.file_reading_task_source();
575
576 task_source.queue(FileReadingTask::ProcessReadError(
581 Trusted::new(&filereader_error),
582 gen_id,
583 DOMErrorName::OperationError,
584 ));
585 }),
586 realm,
587 can_gc,
588 );
589 Ok(())
590 }
591
592 fn change_ready_state(&self, state: FileReaderReadyState) {
593 self.ready_state.set(state);
594 }
595}