diff options
Diffstat (limited to 'components/script/dom/filereader.rs')
-rw-r--r-- | components/script/dom/filereader.rs | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/components/script/dom/filereader.rs b/components/script/dom/filereader.rs new file mode 100644 index 00000000000..7ac8f876157 --- /dev/null +++ b/components/script/dom/filereader.rs @@ -0,0 +1,491 @@ +/* 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<FileReader>; + +pub struct ReadData { + pub bytes: Receiver<Option<Vec<u8>>>, + pub blobtype: DOMString, + pub label: Option<DOMString>, + pub function: FileReaderFunction +} + +impl ReadData { + pub fn new(bytes: Receiver<Option<Vec<u8>>>, blobtype: DOMString, + label: Option<DOMString>, function: FileReaderFunction) -> ReadData { + ReadData { + bytes: bytes, + blobtype: blobtype, + label: label, + function: function, + } + } +} + +#[derive(Clone)] +pub struct BlobBody { + pub bytes: Vec<u8>, + pub blobtype: DOMString, + pub label: Option<DOMString>, + pub function: FileReaderFunction +} + +impl BlobBody { + pub fn new(bytes: Vec<u8>, blobtype: DOMString, + label: Option<DOMString>, 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<FileReaderReadyState>, + error: MutNullableHeap<JS<DOMException>>, + result: RefCell<Option<DOMString>>, + generation_id: Cell<GenerationId>, +} + +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<FileReader> { + reflect_dom_object(box FileReader::new_inherited(global), + global, FileReaderBinding::Wrap) + } + + pub fn Constructor(global: GlobalRef) -> Fallible<Root<FileReader>> { + 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<BlobBody>) { + 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<Option<DOMString>, 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::<Mime>().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<Option<DOMString>, 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<DOMString>) -> 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<Root<DOMException>> { + self.error.get().map(|error| error.root()) + } + + fn GetResult(self) -> Option<DOMString> { + 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<u64>); + 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<u64>) { + + 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<BlobBody>) +} + +impl Process { + fn call(self, chan: &Box<ScriptChan + Send>) { + 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<FileReaderEvent>) { + 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<ScriptChan + Send>) { + 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); +} |