/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::codegen::Bindings::FileReaderBinding; use dom::bindings::codegen::Bindings::FileReaderBinding::{FileReaderConstants, FileReaderMethods}; use dom::bindings::codegen::InheritTypes::{EventCast, EventTargetCast}; use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; use dom::bindings::error::{ErrorResult, Fallible}; use dom::bindings::error::Error::InvalidState; use dom::bindings::global::{GlobalRef, GlobalField}; use dom::bindings::js::{Root, JS, MutNullableHeap}; use dom::bindings::refcounted::Trusted; use dom::bindings::utils::{reflect_dom_object, Reflectable}; use dom::event::{EventHelpers, EventCancelable, EventBubbles}; use dom::eventtarget::{EventTarget, EventTargetHelpers, EventTargetTypeId}; use dom::blob::Blob; use dom::blob::BlobHelpers; use dom::domexception::{DOMException, DOMErrorName}; use dom::progressevent::ProgressEvent; use encoding::all::UTF_8; use encoding::types::{EncodingRef, DecoderTrap}; use encoding::label::encoding_from_whatwg_label; use hyper::mime::{Mime, Attr}; use script_task::{ScriptChan, ScriptMsg, Runnable, ScriptPort}; use std::cell::{Cell, RefCell}; use std::sync::mpsc::Receiver; use util::str::DOMString; use util::task::spawn_named; use rustc_serialize::base64::{Config, ToBase64, CharacterSet, Newline}; #[derive(PartialEq, Clone, Copy, JSTraceable)] pub enum FileReaderFunction { ReadAsText, ReadAsDataUrl, } pub type TrustedFileReader = Trusted; pub struct ReadData { pub bytes: Receiver>>, pub blobtype: DOMString, pub label: Option, pub function: FileReaderFunction } impl ReadData { pub fn new(bytes: Receiver>>, blobtype: DOMString, label: Option, function: FileReaderFunction) -> ReadData { ReadData { bytes: bytes, blobtype: blobtype, label: label, function: function, } } } #[derive(Clone)] pub struct BlobBody { pub bytes: Vec, pub blobtype: DOMString, pub label: Option, pub function: FileReaderFunction } impl BlobBody { pub fn new(bytes: Vec, blobtype: DOMString, label: Option, function: FileReaderFunction) -> BlobBody { BlobBody { bytes: bytes, blobtype: blobtype, label: label, function: function, } } } #[derive(PartialEq, Clone, Copy, JSTraceable)] pub struct GenerationId(u32); #[repr(u16)] #[derive(Copy, Clone, Debug, PartialEq, JSTraceable)] pub enum FileReaderReadyState { Empty = FileReaderConstants::EMPTY, Loading = FileReaderConstants::LOADING, Done = FileReaderConstants::DONE, } #[dom_struct] pub struct FileReader { eventtarget: EventTarget, global: GlobalField, ready_state: Cell, error: MutNullableHeap>, result: RefCell>, generation_id: Cell, } impl FileReader { pub fn new_inherited(global: GlobalRef) -> FileReader { FileReader { eventtarget: EventTarget::new_inherited(EventTargetTypeId::FileReader),//? global: GlobalField::from_rooted(&global), ready_state: Cell::new(FileReaderReadyState::Empty), error: MutNullableHeap::new(None), result: RefCell::new(None), generation_id: Cell::new(GenerationId(0)), } } pub fn new(global: GlobalRef) -> Root { reflect_dom_object(box FileReader::new_inherited(global), global, FileReaderBinding::Wrap) } pub fn Constructor(global: GlobalRef) -> Fallible> { Ok(FileReader::new(global)) } //https://w3c.github.io/FileAPI/#dfn-error-steps pub fn process_read_error(filereader: TrustedFileReader, gen_id: GenerationId, error: DOMErrorName) { let fr = filereader.root(); macro_rules! return_on_abort( () => ( if gen_id != fr.generation_id.get() { return } ); ); return_on_abort!(); // Step 1 fr.change_ready_state(FileReaderReadyState::Done); *fr.result.borrow_mut() = None; let global = fr.global.root(); let exception = DOMException::new(global.r(), error); fr.error.set(Some(JS::from_rooted(&exception))); fr.dispatch_progress_event("error".to_owned(), 0, None); return_on_abort!(); // Step 3 fr.dispatch_progress_event("loadend".to_owned(), 0, None); return_on_abort!(); // Step 4 fr.terminate_ongoing_reading(); } // https://w3c.github.io/FileAPI/#dfn-readAsText pub fn process_read_data(filereader: TrustedFileReader, gen_id: GenerationId) { let fr = filereader.root(); macro_rules! return_on_abort( () => ( if gen_id != fr.generation_id.get() { return } ); ); return_on_abort!(); //FIXME Step 7 send current progress fr.dispatch_progress_event("progress".to_owned(), 0, None); } // https://w3c.github.io/FileAPI/#dfn-readAsText pub fn process_read(filereader: TrustedFileReader, gen_id: GenerationId) { let fr = filereader.root(); macro_rules! return_on_abort( () => ( if gen_id != fr.generation_id.get() { return } ); ); return_on_abort!(); // Step 6 fr.dispatch_progress_event("loadstart".to_owned(), 0, None); } // https://w3c.github.io/FileAPI/#dfn-readAsText pub fn process_read_eof(filereader: TrustedFileReader, gen_id: GenerationId, data: Option) { let fr = filereader.root(); macro_rules! return_on_abort( () => ( if gen_id != fr.generation_id.get() { return } ); ); return_on_abort!(); // Step 8.1 fr.change_ready_state(FileReaderReadyState::Done); // Step 8.2 let output = match data { Some(blob_body) => { match blob_body.function { FileReaderFunction::ReadAsDataUrl => FileReader::perform_readasdataurl(blob_body), FileReaderFunction::ReadAsText => FileReader::perform_readastext(blob_body), } }, None => { Ok(None) } }; //FIXME handle error if error is possible *fr.result.borrow_mut() = output.unwrap(); // Step 8.3 fr.dispatch_progress_event("load".to_owned(), 0, None); return_on_abort!(); // Step 8.4 if fr.ready_state.get() != FileReaderReadyState::Loading { fr.dispatch_progress_event("loadend".to_owned(), 0, None); } return_on_abort!(); // Step 9 fr.terminate_ongoing_reading(); } // https://w3c.github.io/FileAPI/#dfn-readAsText fn perform_readastext(blob_body: BlobBody) -> Result, DOMErrorName> { //https://w3c.github.io/FileAPI/#encoding-determination // Steps 1 & 2 & 3 let mut encoding = match blob_body.label { Some(e) => encoding_from_whatwg_label(&e), None => None }; // Step 4 & 5 encoding = match encoding { Some(e) => Some(e), None => { let resultmime = blob_body.blobtype.parse::().ok(); resultmime.and_then(|Mime(_, _, ref parameters)| { parameters.iter() .find(|&&(ref k, _)| &Attr::Charset == k) .and_then(|&(_, ref v)| encoding_from_whatwg_label(&v.to_string())) }) } }; // Step 6 let enc = match encoding { Some(code) => code, None => UTF_8 as EncodingRef }; let convert = &blob_body.bytes[..]; // Step 7 let output = enc.decode(convert, DecoderTrap::Replace).unwrap(); Ok(Some(output)) } //https://w3c.github.io/FileAPI/#dfn-readAsDataURL fn perform_readasdataurl(blob_body: BlobBody) -> Result, DOMErrorName> { let config = Config { char_set: CharacterSet::UrlSafe, newline: Newline::LF, pad: true, line_length: None }; let base64 = blob_body.bytes.to_base64(config); let output = if blob_body.blobtype.is_empty() { format!("data:base64,{}", base64) } else { format!("data:{};base64,{}", blob_body.blobtype, base64) }; Ok(Some(output)) } } impl<'a> FileReaderMethods for &'a FileReader { event_handler!(loadstart, GetOnloadstart, SetOnloadstart); event_handler!(progress, GetOnprogress, SetOnprogress); event_handler!(load, GetOnload, SetOnload); event_handler!(abort, GetOnabort, SetOnabort); event_handler!(error, GetOnerror, SetOnerror); event_handler!(loadend, GetOnloadend, SetOnloadend); //TODO https://w3c.github.io/FileAPI/#dfn-readAsArrayBuffer //https://w3c.github.io/FileAPI/#dfn-readAsDataURL fn ReadAsDataURL(self, blob: &Blob) -> ErrorResult { let global = self.global.root(); // Step 1 if self.ready_state.get() == FileReaderReadyState::Loading { return Err(InvalidState); } //TODO STEP 2 if isClosed implemented in Blob // Step 3 self.change_ready_state(FileReaderReadyState::Loading); let bytes = blob.read_out_buffer(); let type_ = blob.read_out_type(); let load_data = ReadData::new(bytes, type_, None, FileReaderFunction::ReadAsDataUrl); self.read(load_data, global.r()) } // https://w3c.github.io/FileAPI/#dfn-readAsText fn ReadAsText(self, blob: &Blob, label:Option) -> ErrorResult { let global = self.global.root(); // Step 1 if self.ready_state.get() == FileReaderReadyState::Loading { return Err(InvalidState); } //TODO STEP 2 if isClosed implemented in Blob // Step 3 self.change_ready_state(FileReaderReadyState::Loading); let bytes = blob.read_out_buffer(); let type_ = blob.read_out_type(); let load_data = ReadData::new(bytes, type_, label, FileReaderFunction::ReadAsText); self.read(load_data, global.r()) } // https://w3c.github.io/FileAPI/#dfn-abort fn Abort(self) { // Step 2 if self.ready_state.get() == FileReaderReadyState::Loading { self.change_ready_state(FileReaderReadyState::Done); } // Steps 1 & 3 *self.result.borrow_mut() = None; let global = self.global.root(); let exception = DOMException::new(global.r(), DOMErrorName::AbortError); self.error.set(Some(JS::from_rooted(&exception))); self.terminate_ongoing_reading(); // Steps 5 & 6 self.dispatch_progress_event("abort".to_owned(), 0, None); self.dispatch_progress_event("loadend".to_owned(), 0, None); } fn GetError(self) -> Option> { self.error.get().map(|error| error.root()) } fn GetResult(self) -> Option { self.result.borrow().clone() } fn ReadyState(self) -> u16 { self.ready_state.get() as u16 } } trait PrivateFileReaderHelpers { fn dispatch_progress_event(self, type_: DOMString, loaded: u64, total: Option); fn terminate_ongoing_reading(self); fn read(self, read_data: ReadData, global: GlobalRef) -> ErrorResult; fn change_ready_state(self, state: FileReaderReadyState); } impl<'a> PrivateFileReaderHelpers for &'a FileReader { fn dispatch_progress_event(self, type_: DOMString, loaded: u64, total: Option) { let global = self.global.root(); let progressevent = ProgressEvent::new(global.r(), type_, EventBubbles::DoesNotBubble, EventCancelable::NotCancelable, total.is_some(), loaded, total.unwrap_or(0)); let target = EventTargetCast::from_ref(self); let event = EventCast::from_ref(progressevent.r()); event.fire(target); } fn terminate_ongoing_reading(self) { let GenerationId(prev_id) = self.generation_id.get(); self.generation_id.set(GenerationId(prev_id + 1)); } fn read(self, read_data: ReadData, global: GlobalRef) -> ErrorResult { let fr = Trusted::new(global.get_cx(), self, global.script_chan()); let gen_id = self.generation_id.get(); let script_chan = global.script_chan(); spawn_named("file reader async operation".to_owned(), move || { perform_annotated_read_operation(gen_id, read_data, fr, script_chan) }); Ok(()) } fn change_ready_state(self, state: FileReaderReadyState) { self.ready_state.set(state); } } #[derive(Clone)] pub enum Process { ProcessRead(TrustedFileReader, GenerationId), ProcessReadData(TrustedFileReader, GenerationId, DOMString), ProcessReadError(TrustedFileReader, GenerationId, DOMErrorName), ProcessReadEOF(TrustedFileReader, GenerationId, Option) } impl Process { fn call(self, chan: &Box) { let task = box FileReaderEvent::new(self); chan.send(ScriptMsg::RunnableMsg(task)).unwrap(); } pub fn handle(process: Process) { match process { Process::ProcessRead(filereader, gen_id) => { FileReader::process_read(filereader, gen_id); }, Process::ProcessReadData(filereader, gen_id, _) => { FileReader::process_read_data(filereader, gen_id); }, Process::ProcessReadError(filereader, gen_id, error) => { FileReader::process_read_error(filereader, gen_id, error); }, Process::ProcessReadEOF(filereader, gen_id, blob_body) => { FileReader::process_read_eof(filereader, gen_id, blob_body); } } } } pub struct FileReaderEvent { process: Process, } impl FileReaderEvent { pub fn new(process: Process) -> FileReaderEvent { FileReaderEvent { process: process, } } } impl Runnable for FileReaderEvent { fn handler(self: Box) { let this = *self; Process::handle(this.process); } } //https://w3c.github.io/FileAPI/#task-read-operation fn perform_annotated_read_operation(gen_id: GenerationId, read_data: ReadData, filereader: TrustedFileReader, script_chan: Box) { let chan = &script_chan; // Step 4 Process::ProcessRead(filereader.clone(), gen_id).call(chan); Process::ProcessReadData(filereader.clone(), gen_id, DOMString::new()).call(chan); let output = match read_data.bytes.recv() { Ok(bytes) => bytes, Err(_) => { Process::ProcessReadError(filereader, gen_id, DOMErrorName::NotFoundError).call(chan); return; } }; let blobtype = read_data.blobtype.clone(); let label = read_data.label.clone(); let blob_body = output.map(|bytes| { BlobBody::new(bytes, blobtype, label, read_data.function) }); Process::ProcessReadEOF(filereader, gen_id, blob_body).call(chan); }