use std::cell::Cell;
use std::ptr;
use base64::Engine;
use dom_struct::dom_struct;
use encoding_rs::{Encoding, UTF_8};
use js::jsapi::{Heap, JSObject};
use js::jsval::{self, JSVal};
use js::rust::HandleObject;
use js::typedarray::{ArrayBuffer, CreateWith};
use mime::{self, Mime};
use servo_atoms::Atom;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
use crate::dom::bindings::codegen::Bindings::FileReaderBinding::{
FileReaderConstants, FileReaderMethods,
};
use crate::dom::bindings::codegen::UnionTypes::StringOrObject;
use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::blob::Blob;
use crate::dom::domexception::{DOMErrorName, DOMException};
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::progressevent::ProgressEvent;
use crate::realms::enter_realm;
use crate::script_runtime::{CanGc, JSContext};
use crate::task_source::file_reading::FileReadingTask;
use crate::task_source::{TaskSource, TaskSourceName};
#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
pub enum FileReaderFunction {
Text,
DataUrl,
ArrayBuffer,
}
pub type TrustedFileReader = Trusted<FileReader>;
#[derive(Clone, MallocSizeOf)]
pub struct ReadMetaData {
pub blobtype: String,
pub label: Option<String>,
pub function: FileReaderFunction,
}
impl ReadMetaData {
pub fn new(
blobtype: String,
label: Option<String>,
function: FileReaderFunction,
) -> ReadMetaData {
ReadMetaData {
blobtype,
label,
function,
}
}
}
#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
pub struct GenerationId(u32);
#[repr(u16)]
#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
pub enum FileReaderReadyState {
Empty = FileReaderConstants::EMPTY,
Loading = FileReaderConstants::LOADING,
Done = FileReaderConstants::DONE,
}
#[derive(JSTraceable, MallocSizeOf)]
pub enum FileReaderResult {
ArrayBuffer(#[ignore_malloc_size_of = "mozjs"] Heap<JSVal>),
String(DOMString),
}
pub struct FileReaderSharedFunctionality;
impl FileReaderSharedFunctionality {
pub fn dataurl_format(blob_contents: &[u8], blob_type: String) -> DOMString {
let base64 = base64::engine::general_purpose::STANDARD.encode(blob_contents);
let dataurl = if blob_type.is_empty() {
format!("data:base64,{}", base64)
} else {
format!("data:{};base64,{}", blob_type, base64)
};
DOMString::from(dataurl)
}
pub fn text_decode(
blob_contents: &[u8],
blob_type: &str,
blob_label: &Option<String>,
) -> DOMString {
let mut encoding = blob_label
.as_ref()
.map(|string| string.as_bytes())
.and_then(Encoding::for_label);
encoding = encoding.or_else(|| {
let resultmime = blob_type.parse::<Mime>().ok();
resultmime.and_then(|mime| {
mime.params()
.find(|(ref k, _)| &mime::CHARSET == k)
.and_then(|(_, ref v)| Encoding::for_label(v.as_ref().as_bytes()))
})
});
let enc = encoding.unwrap_or(UTF_8);
let convert = blob_contents;
let (output, _, _) = enc.decode(convert);
DOMString::from(output)
}
}
#[dom_struct]
pub struct FileReader {
eventtarget: EventTarget,
ready_state: Cell<FileReaderReadyState>,
error: MutNullableDom<DOMException>,
result: DomRefCell<Option<FileReaderResult>>,
generation_id: Cell<GenerationId>,
}
impl FileReader {
pub fn new_inherited() -> FileReader {
FileReader {
eventtarget: EventTarget::new_inherited(),
ready_state: Cell::new(FileReaderReadyState::Empty),
error: MutNullableDom::new(None),
result: DomRefCell::new(None),
generation_id: Cell::new(GenerationId(0)),
}
}
fn new(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<FileReader> {
reflect_dom_object_with_proto(Box::new(FileReader::new_inherited()), global, proto, can_gc)
}
pub fn process_read_error(
filereader: TrustedFileReader,
gen_id: GenerationId,
error: DOMErrorName,
can_gc: CanGc,
) {
let fr = filereader.root();
macro_rules! return_on_abort(
() => (
if gen_id != fr.generation_id.get() {
return
}
);
);
return_on_abort!();
fr.change_ready_state(FileReaderReadyState::Done);
*fr.result.borrow_mut() = None;
let exception = DOMException::new(&fr.global(), error);
fr.error.set(Some(&exception));
fr.dispatch_progress_event(atom!("error"), 0, None, can_gc);
return_on_abort!();
fr.dispatch_progress_event(atom!("loadend"), 0, None, can_gc);
return_on_abort!();
fr.terminate_ongoing_reading();
}
pub fn process_read_data(filereader: TrustedFileReader, gen_id: GenerationId, can_gc: CanGc) {
let fr = filereader.root();
macro_rules! return_on_abort(
() => (
if gen_id != fr.generation_id.get() {
return
}
);
);
return_on_abort!();
fr.dispatch_progress_event(atom!("progress"), 0, None, can_gc);
}
pub fn process_read(filereader: TrustedFileReader, gen_id: GenerationId, can_gc: CanGc) {
let fr = filereader.root();
macro_rules! return_on_abort(
() => (
if gen_id != fr.generation_id.get() {
return
}
);
);
return_on_abort!();
fr.dispatch_progress_event(atom!("loadstart"), 0, None, can_gc);
}
pub fn process_read_eof(
filereader: TrustedFileReader,
gen_id: GenerationId,
data: ReadMetaData,
blob_contents: Vec<u8>,
can_gc: CanGc,
) {
let fr = filereader.root();
macro_rules! return_on_abort(
() => (
if gen_id != fr.generation_id.get() {
return
}
);
);
return_on_abort!();
fr.change_ready_state(FileReaderReadyState::Done);
match data.function {
FileReaderFunction::DataUrl => {
FileReader::perform_readasdataurl(&fr.result, data, &blob_contents)
},
FileReaderFunction::Text => {
FileReader::perform_readastext(&fr.result, data, &blob_contents)
},
FileReaderFunction::ArrayBuffer => {
let _ac = enter_realm(&*fr);
FileReader::perform_readasarraybuffer(
&fr.result,
GlobalScope::get_cx(),
data,
&blob_contents,
)
},
};
fr.dispatch_progress_event(atom!("load"), 0, None, can_gc);
return_on_abort!();
if fr.ready_state.get() != FileReaderReadyState::Loading {
fr.dispatch_progress_event(atom!("loadend"), 0, None, can_gc);
}
return_on_abort!();
}
fn perform_readastext(
result: &DomRefCell<Option<FileReaderResult>>,
data: ReadMetaData,
blob_bytes: &[u8],
) {
let blob_label = &data.label;
let blob_type = &data.blobtype;
let output = FileReaderSharedFunctionality::text_decode(blob_bytes, blob_type, blob_label);
*result.borrow_mut() = Some(FileReaderResult::String(output));
}
fn perform_readasdataurl(
result: &DomRefCell<Option<FileReaderResult>>,
data: ReadMetaData,
bytes: &[u8],
) {
let output = FileReaderSharedFunctionality::dataurl_format(bytes, data.blobtype);
*result.borrow_mut() = Some(FileReaderResult::String(output));
}
#[allow(unsafe_code)]
fn perform_readasarraybuffer(
result: &DomRefCell<Option<FileReaderResult>>,
cx: JSContext,
_: ReadMetaData,
bytes: &[u8],
) {
unsafe {
rooted!(in(*cx) let mut array_buffer = ptr::null_mut::<JSObject>());
assert!(
ArrayBuffer::create(*cx, CreateWith::Slice(bytes), array_buffer.handle_mut())
.is_ok()
);
*result.borrow_mut() = Some(FileReaderResult::ArrayBuffer(Heap::default()));
if let Some(FileReaderResult::ArrayBuffer(ref mut heap)) = *result.borrow_mut() {
heap.set(jsval::ObjectValue(array_buffer.get()));
};
}
}
}
impl FileReaderMethods<crate::DomTypeHolder> for FileReader {
fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> Fallible<DomRoot<FileReader>> {
Ok(FileReader::new(global, proto, can_gc))
}
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);
fn ReadAsArrayBuffer(&self, blob: &Blob) -> ErrorResult {
self.read(FileReaderFunction::ArrayBuffer, blob, None)
}
fn ReadAsDataURL(&self, blob: &Blob) -> ErrorResult {
self.read(FileReaderFunction::DataUrl, blob, None)
}
fn ReadAsText(&self, blob: &Blob, label: Option<DOMString>) -> ErrorResult {
self.read(FileReaderFunction::Text, blob, label)
}
fn Abort(&self, can_gc: CanGc) {
if self.ready_state.get() == FileReaderReadyState::Loading {
self.change_ready_state(FileReaderReadyState::Done);
}
*self.result.borrow_mut() = None;
let exception = DOMException::new(&self.global(), DOMErrorName::AbortError);
self.error.set(Some(&exception));
self.terminate_ongoing_reading();
self.dispatch_progress_event(atom!("abort"), 0, None, can_gc);
self.dispatch_progress_event(atom!("loadend"), 0, None, can_gc);
}
fn GetError(&self) -> Option<DomRoot<DOMException>> {
self.error.get()
}
#[allow(unsafe_code)]
fn GetResult(&self, _: JSContext) -> Option<StringOrObject> {
self.result.borrow().as_ref().map(|r| match *r {
FileReaderResult::String(ref string) => StringOrObject::String(string.clone()),
FileReaderResult::ArrayBuffer(ref arr_buffer) => {
let result = RootedTraceableBox::new(Heap::default());
unsafe {
result.set((*arr_buffer.ptr.get()).to_object());
}
StringOrObject::Object(result)
},
})
}
fn ReadyState(&self) -> u16 {
self.ready_state.get() as u16
}
}
impl FileReader {
fn dispatch_progress_event(&self, type_: Atom, loaded: u64, total: Option<u64>, can_gc: CanGc) {
let progressevent = ProgressEvent::new(
&self.global(),
type_,
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
total.is_some(),
loaded,
total.unwrap_or(0),
can_gc,
);
progressevent.upcast::<Event>().fire(self.upcast(), can_gc);
}
fn terminate_ongoing_reading(&self) {
let GenerationId(prev_id) = self.generation_id.get();
self.generation_id.set(GenerationId(prev_id + 1));
}
fn read(
&self,
function: FileReaderFunction,
blob: &Blob,
label: Option<DOMString>,
) -> ErrorResult {
if self.ready_state.get() == FileReaderReadyState::Loading {
return Err(Error::InvalidState);
}
self.change_ready_state(FileReaderReadyState::Loading);
*self.result.borrow_mut() = None;
let type_ = blob.Type();
let load_data = ReadMetaData::new(String::from(type_), label.map(String::from), function);
let GenerationId(prev_id) = self.generation_id.get();
self.generation_id.set(GenerationId(prev_id + 1));
let gen_id = self.generation_id.get();
let blob_contents = blob.get_bytes().unwrap_or_else(|_| vec![]);
let filereader = Trusted::new(self);
let global = self.global();
let canceller = global.task_canceller(TaskSourceName::FileReading);
let task_source = global.file_reading_task_source();
let task = FileReadingTask::ProcessRead(filereader.clone(), gen_id);
task_source.queue_with_canceller(task, &canceller).unwrap();
if !blob_contents.is_empty() {
let task = FileReadingTask::ProcessReadData(filereader.clone(), gen_id);
task_source.queue_with_canceller(task, &canceller).unwrap();
}
let task = FileReadingTask::ProcessReadEOF(filereader, gen_id, load_data, blob_contents);
task_source.queue_with_canceller(task, &canceller).unwrap();
Ok(())
}
fn change_ready_state(&self, state: FileReaderReadyState) {
self.ready_state.set(state);
}
}