mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
http://www.robohornet.org gives a score of 101.36 on master, and 102.68 with this PR. The latter is slightly better, but probably within noise level. So it looks like this PR does not affect DOM performance. This is expected since `Box::new` is defined as: ```rust impl<T> Box<T> { #[inline(always)] pub fn new(x: T) -> Box<T> { box x } } ``` With inlining, it should compile to the same as box syntax.
429 lines
14 KiB
Rust
429 lines
14 KiB
Rust
/* 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 base64;
|
|
use dom::bindings::cell::DomRefCell;
|
|
use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
|
|
use dom::bindings::codegen::Bindings::FileReaderBinding::{self, FileReaderConstants, FileReaderMethods};
|
|
use dom::bindings::codegen::UnionTypes::StringOrObject;
|
|
use dom::bindings::error::{Error, ErrorResult, Fallible};
|
|
use dom::bindings::inheritance::Castable;
|
|
use dom::bindings::refcounted::Trusted;
|
|
use dom::bindings::reflector::{DomObject, reflect_dom_object};
|
|
use dom::bindings::root::{DomRoot, MutNullableDom};
|
|
use dom::bindings::str::DOMString;
|
|
use dom::bindings::trace::RootedTraceableBox;
|
|
use dom::blob::Blob;
|
|
use dom::domexception::{DOMErrorName, DOMException};
|
|
use dom::event::{Event, EventBubbles, EventCancelable};
|
|
use dom::eventtarget::EventTarget;
|
|
use dom::globalscope::GlobalScope;
|
|
use dom::progressevent::ProgressEvent;
|
|
use dom_struct::dom_struct;
|
|
use encoding::all::UTF_8;
|
|
use encoding::label::encoding_from_whatwg_label;
|
|
use encoding::types::{DecoderTrap, EncodingRef};
|
|
use hyper::mime::{Attr, Mime};
|
|
use js::jsapi::Heap;
|
|
use js::jsapi::JSAutoCompartment;
|
|
use js::jsapi::JSContext;
|
|
use js::jsval::{self, JSVal};
|
|
use js::typedarray::{ArrayBuffer, CreateWith};
|
|
use servo_atoms::Atom;
|
|
use std::cell::Cell;
|
|
use std::ptr;
|
|
use std::sync::Arc;
|
|
use std::thread;
|
|
use task::TaskCanceller;
|
|
use task_source::TaskSource;
|
|
use task_source::file_reading::{FileReadingTask, FileReadingTaskSource};
|
|
|
|
#[derive(Clone, Copy, HeapSizeOf, JSTraceable, PartialEq)]
|
|
pub enum FileReaderFunction {
|
|
ReadAsText,
|
|
ReadAsDataUrl,
|
|
ReadAsArrayBuffer,
|
|
}
|
|
|
|
pub type TrustedFileReader = Trusted<FileReader>;
|
|
|
|
#[derive(Clone, HeapSizeOf)]
|
|
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: blobtype,
|
|
label: label,
|
|
function: function,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, HeapSizeOf, JSTraceable, PartialEq)]
|
|
pub struct GenerationId(u32);
|
|
|
|
#[repr(u16)]
|
|
#[derive(Clone, Copy, Debug, HeapSizeOf, JSTraceable, PartialEq)]
|
|
pub enum FileReaderReadyState {
|
|
Empty = FileReaderConstants::EMPTY,
|
|
Loading = FileReaderConstants::LOADING,
|
|
Done = FileReaderConstants::DONE,
|
|
}
|
|
|
|
#[derive(HeapSizeOf, JSTraceable)]
|
|
pub enum FileReaderResult {
|
|
ArrayBuffer(Heap<JSVal>),
|
|
String(DOMString),
|
|
}
|
|
|
|
#[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)),
|
|
}
|
|
}
|
|
|
|
pub fn new(global: &GlobalScope) -> DomRoot<FileReader> {
|
|
reflect_dom_object(Box::new(FileReader::new_inherited()),
|
|
global, FileReaderBinding::Wrap)
|
|
}
|
|
|
|
pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<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 exception = DOMException::new(&fr.global(), error);
|
|
fr.error.set(Some(&exception));
|
|
|
|
fr.dispatch_progress_event(atom!("error"), 0, None);
|
|
return_on_abort!();
|
|
// Step 3
|
|
fr.dispatch_progress_event(atom!("loadend"), 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(atom!("progress"), 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(atom!("loadstart"), 0, None);
|
|
}
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-readAsText
|
|
#[allow(unsafe_code)]
|
|
pub fn process_read_eof(filereader: TrustedFileReader, gen_id: GenerationId,
|
|
data: ReadMetaData, blob_contents: Arc<Vec<u8>>) {
|
|
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
|
|
|
|
match data.function {
|
|
FileReaderFunction::ReadAsDataUrl =>
|
|
FileReader::perform_readasdataurl(&fr.result, data, &blob_contents),
|
|
FileReaderFunction::ReadAsText =>
|
|
FileReader::perform_readastext(&fr.result, data, &blob_contents),
|
|
FileReaderFunction::ReadAsArrayBuffer => {
|
|
let _ac = JSAutoCompartment::new(fr.global().get_cx(), *fr.reflector().get_jsobject());
|
|
FileReader::perform_readasarraybuffer(&fr.result, fr.global().get_cx(), data, &blob_contents)
|
|
},
|
|
};
|
|
|
|
// Step 8.3
|
|
fr.dispatch_progress_event(atom!("load"), 0, None);
|
|
return_on_abort!();
|
|
// Step 8.4
|
|
if fr.ready_state.get() != FileReaderReadyState::Loading {
|
|
fr.dispatch_progress_event(atom!("loadend"), 0, None);
|
|
}
|
|
return_on_abort!();
|
|
// Step 9
|
|
fr.terminate_ongoing_reading();
|
|
}
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-readAsText
|
|
fn perform_readastext(result: &DomRefCell<Option<FileReaderResult>>, data: ReadMetaData, blob_bytes: &[u8]) {
|
|
let blob_label = &data.label;
|
|
let blob_type = &data.blobtype;
|
|
|
|
//https://w3c.github.io/FileAPI/#encoding-determination
|
|
// Steps 1 & 2 & 3
|
|
let mut encoding = blob_label.as_ref()
|
|
.map(|string| &**string)
|
|
.and_then(encoding_from_whatwg_label);
|
|
|
|
// Step 4 & 5
|
|
encoding = encoding.or_else(|| {
|
|
let resultmime = blob_type.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 = encoding.unwrap_or(UTF_8 as EncodingRef);
|
|
|
|
let convert = blob_bytes;
|
|
// Step 7
|
|
let output = enc.decode(convert, DecoderTrap::Replace).unwrap();
|
|
*result.borrow_mut() = Some(FileReaderResult::String(DOMString::from(output)));
|
|
}
|
|
|
|
//https://w3c.github.io/FileAPI/#dfn-readAsDataURL
|
|
fn perform_readasdataurl(result: &DomRefCell<Option<FileReaderResult>>, data: ReadMetaData, bytes: &[u8]) {
|
|
let base64 = base64::encode(bytes);
|
|
|
|
let output = if data.blobtype.is_empty() {
|
|
format!("data:base64,{}", base64)
|
|
} else {
|
|
format!("data:{};base64,{}", data.blobtype, base64)
|
|
};
|
|
|
|
*result.borrow_mut() = Some(FileReaderResult::String(DOMString::from(output)));
|
|
}
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-readAsArrayBuffer
|
|
#[allow(unsafe_code)]
|
|
fn perform_readasarraybuffer(result: &DomRefCell<Option<FileReaderResult>>,
|
|
cx: *mut JSContext, _: ReadMetaData, bytes: &[u8]) {
|
|
unsafe {
|
|
rooted!(in(cx) let mut array_buffer = ptr::null_mut());
|
|
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 for FileReader {
|
|
// https://w3c.github.io/FileAPI/#dfn-onloadstart
|
|
event_handler!(loadstart, GetOnloadstart, SetOnloadstart);
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-onprogress
|
|
event_handler!(progress, GetOnprogress, SetOnprogress);
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-onload
|
|
event_handler!(load, GetOnload, SetOnload);
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-onabort
|
|
event_handler!(abort, GetOnabort, SetOnabort);
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-onerror
|
|
event_handler!(error, GetOnerror, SetOnerror);
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-onloadend
|
|
event_handler!(loadend, GetOnloadend, SetOnloadend);
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-readAsArrayBuffer
|
|
fn ReadAsArrayBuffer(&self, blob: &Blob) -> ErrorResult {
|
|
self.read(FileReaderFunction::ReadAsArrayBuffer, blob, None)
|
|
}
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-readAsDataURL
|
|
fn ReadAsDataURL(&self, blob: &Blob) -> ErrorResult {
|
|
self.read(FileReaderFunction::ReadAsDataUrl, blob, None)
|
|
}
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-readAsText
|
|
fn ReadAsText(&self, blob: &Blob, label: Option<DOMString>) -> ErrorResult {
|
|
self.read(FileReaderFunction::ReadAsText, blob, label)
|
|
}
|
|
|
|
// 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 exception = DOMException::new(&self.global(), DOMErrorName::AbortError);
|
|
self.error.set(Some(&exception));
|
|
|
|
self.terminate_ongoing_reading();
|
|
// Steps 5 & 6
|
|
self.dispatch_progress_event(atom!("abort"), 0, None);
|
|
self.dispatch_progress_event(atom!("loadend"), 0, None);
|
|
}
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-error
|
|
fn GetError(&self) -> Option<DomRoot<DOMException>> {
|
|
self.error.get()
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
// https://w3c.github.io/FileAPI/#dfn-result
|
|
unsafe fn GetResult(&self, _: *mut 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());
|
|
result.set((*arr_buffer.ptr.get()).to_object());
|
|
StringOrObject::Object(result)
|
|
}
|
|
})
|
|
}
|
|
|
|
// https://w3c.github.io/FileAPI/#dfn-readyState
|
|
fn ReadyState(&self) -> u16 {
|
|
self.ready_state.get() as u16
|
|
}
|
|
}
|
|
|
|
|
|
impl FileReader {
|
|
fn dispatch_progress_event(&self, type_: Atom, loaded: u64, total: Option<u64>) {
|
|
let progressevent = ProgressEvent::new(&self.global(),
|
|
type_, EventBubbles::DoesNotBubble, EventCancelable::NotCancelable,
|
|
total.is_some(), loaded, total.unwrap_or(0));
|
|
progressevent.upcast::<Event>().fire(self.upcast());
|
|
}
|
|
|
|
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 {
|
|
// Step 1
|
|
if self.ready_state.get() == FileReaderReadyState::Loading {
|
|
return Err(Error::InvalidState);
|
|
}
|
|
|
|
// Step 2
|
|
self.change_ready_state(FileReaderReadyState::Loading);
|
|
|
|
// Step 3
|
|
let blob_contents = Arc::new(blob.get_bytes().unwrap_or(vec![]));
|
|
|
|
let type_ = blob.Type();
|
|
|
|
let load_data = ReadMetaData::new(String::from(type_), label.map(String::from), function);
|
|
|
|
let fr = Trusted::new(self);
|
|
let gen_id = self.generation_id.get();
|
|
|
|
let global = self.global();
|
|
let canceller = global.task_canceller();
|
|
let task_source = global.file_reading_task_source();
|
|
|
|
thread::Builder::new().name("file reader async operation".to_owned()).spawn(move || {
|
|
perform_annotated_read_operation(
|
|
gen_id,
|
|
load_data,
|
|
blob_contents,
|
|
fr,
|
|
task_source,
|
|
canceller,
|
|
)
|
|
}).expect("Thread spawning failed");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn change_ready_state(&self, state: FileReaderReadyState) {
|
|
self.ready_state.set(state);
|
|
}
|
|
}
|
|
|
|
// https://w3c.github.io/FileAPI/#thread-read-operation
|
|
fn perform_annotated_read_operation(
|
|
gen_id: GenerationId,
|
|
data: ReadMetaData,
|
|
blob_contents: Arc<Vec<u8>>,
|
|
filereader: TrustedFileReader,
|
|
task_source: FileReadingTaskSource,
|
|
canceller: TaskCanceller,
|
|
) {
|
|
// Step 4
|
|
let task = FileReadingTask::ProcessRead(filereader.clone(), gen_id);
|
|
task_source.queue_with_canceller(task, &canceller).unwrap();
|
|
|
|
let task = FileReadingTask::ProcessReadData(filereader.clone(), gen_id);
|
|
task_source.queue_with_canceller(task, &canceller).unwrap();
|
|
|
|
let task = FileReadingTask::ProcessReadEOF(filereader, gen_id, data, blob_contents);
|
|
task_source.queue_with_canceller(task, &canceller).unwrap();
|
|
}
|