Implement read methods on FileReaderSync

This commit is contained in:
Jonas Reinwald 2017-12-07 16:57:06 +01:00 committed by Josh Matthews
parent 3e8caa4679
commit 0fdafb08c8
9 changed files with 277 additions and 104 deletions

View file

@ -69,6 +69,8 @@ pub enum Error {
TypeMismatch, TypeMismatch,
/// InvalidModificationError DOMException /// InvalidModificationError DOMException
InvalidModification, InvalidModification,
/// NotReadableError DOMException
NotReadable,
/// TypeError JavaScript Error /// TypeError JavaScript Error
Type(String), Type(String),
@ -110,6 +112,7 @@ pub unsafe fn throw_dom_exception(cx: *mut JSContext, global: &GlobalScope, resu
Error::QuotaExceeded => DOMErrorName::QuotaExceededError, Error::QuotaExceeded => DOMErrorName::QuotaExceededError,
Error::TypeMismatch => DOMErrorName::TypeMismatchError, Error::TypeMismatch => DOMErrorName::TypeMismatchError,
Error::InvalidModification => DOMErrorName::InvalidModificationError, Error::InvalidModification => DOMErrorName::InvalidModificationError,
Error::NotReadable => DOMErrorName::NotReadableError,
Error::Type(message) => { Error::Type(message) => {
assert!(!JS_IsExceptionPending(cx)); assert!(!JS_IsExceptionPending(cx));
throw_type_error(cx, &message); throw_type_error(cx, &message);

View file

@ -35,6 +35,7 @@ pub enum DOMErrorName {
TimeoutError = DOMExceptionConstants::TIMEOUT_ERR, TimeoutError = DOMExceptionConstants::TIMEOUT_ERR,
InvalidNodeTypeError = DOMExceptionConstants::INVALID_NODE_TYPE_ERR, InvalidNodeTypeError = DOMExceptionConstants::INVALID_NODE_TYPE_ERR,
DataCloneError = DOMExceptionConstants::DATA_CLONE_ERR, DataCloneError = DOMExceptionConstants::DATA_CLONE_ERR,
NotReadableError = DOMExceptionConstants::NOT_READABLE_ERR,
} }
#[dom_struct] #[dom_struct]
@ -94,6 +95,7 @@ impl DOMExceptionMethods for DOMException {
DOMErrorName::InvalidNodeTypeError => DOMErrorName::InvalidNodeTypeError =>
"The supplied node is incorrect or has an incorrect ancestor for this operation.", "The supplied node is incorrect or has an incorrect ancestor for this operation.",
DOMErrorName::DataCloneError => "The object can not be cloned.", DOMErrorName::DataCloneError => "The object can not be cloned.",
DOMErrorName::NotReadableError => "The I/O read operation failed."
}; };
DOMString::from(message) DOMString::from(message)

View file

@ -51,12 +51,15 @@ pub type TrustedFileReader = Trusted<FileReader>;
pub struct ReadMetaData { pub struct ReadMetaData {
pub blobtype: String, pub blobtype: String,
pub label: Option<String>, pub label: Option<String>,
pub function: FileReaderFunction pub function: FileReaderFunction,
} }
impl ReadMetaData { impl ReadMetaData {
pub fn new(blobtype: String, pub fn new(
label: Option<String>, function: FileReaderFunction) -> ReadMetaData { blobtype: String,
label: Option<String>,
function: FileReaderFunction,
) -> ReadMetaData {
ReadMetaData { ReadMetaData {
blobtype: blobtype, blobtype: blobtype,
label: label, label: label,
@ -82,6 +85,54 @@ pub enum FileReaderResult {
String(DOMString), String(DOMString),
} }
pub struct FileReaderSharedFunctionality;
impl FileReaderSharedFunctionality {
pub fn dataurl_format(blob_contents: &[u8], blob_type: String) -> DOMString {
let base64 = base64::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 {
//https://w3c.github.io/FileAPI/#encoding-determination
// Steps 1 & 2 & 3
let mut encoding = blob_label
.as_ref()
.map(|string| string.as_bytes())
.and_then(Encoding::for_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::for_label(v.as_str().as_bytes()))
})
});
// Step 6
let enc = encoding.unwrap_or(UTF_8);
let convert = blob_contents;
// Step 7
let (output, _, _) = enc.decode(convert);
DOMString::from(output)
}
}
#[dom_struct] #[dom_struct]
pub struct FileReader { pub struct FileReader {
eventtarget: EventTarget, eventtarget: EventTarget,
@ -103,8 +154,11 @@ impl FileReader {
} }
pub fn new(global: &GlobalScope) -> DomRoot<FileReader> { pub fn new(global: &GlobalScope) -> DomRoot<FileReader> {
reflect_dom_object(Box::new(FileReader::new_inherited()), reflect_dom_object(
global, FileReaderBinding::Wrap) Box::new(FileReader::new_inherited()),
global,
FileReaderBinding::Wrap,
)
} }
pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<FileReader>> { pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<FileReader>> {
@ -112,7 +166,11 @@ impl FileReader {
} }
//https://w3c.github.io/FileAPI/#dfn-error-steps //https://w3c.github.io/FileAPI/#dfn-error-steps
pub fn process_read_error(filereader: TrustedFileReader, gen_id: GenerationId, error: DOMErrorName) { pub fn process_read_error(
filereader: TrustedFileReader,
gen_id: GenerationId,
error: DOMErrorName,
) {
let fr = filereader.root(); let fr = filereader.root();
macro_rules! return_on_abort( macro_rules! return_on_abort(
@ -174,8 +232,12 @@ impl FileReader {
// https://w3c.github.io/FileAPI/#dfn-readAsText // https://w3c.github.io/FileAPI/#dfn-readAsText
#[allow(unsafe_code)] #[allow(unsafe_code)]
pub fn process_read_eof(filereader: TrustedFileReader, gen_id: GenerationId, pub fn process_read_eof(
data: ReadMetaData, blob_contents: Arc<Vec<u8>>) { filereader: TrustedFileReader,
gen_id: GenerationId,
data: ReadMetaData,
blob_contents: Arc<Vec<u8>>,
) {
let fr = filereader.root(); let fr = filereader.root();
macro_rules! return_on_abort( macro_rules! return_on_abort(
@ -192,13 +254,21 @@ impl FileReader {
// Step 8.2 // Step 8.2
match data.function { match data.function {
FileReaderFunction::ReadAsDataUrl => FileReaderFunction::ReadAsDataUrl => {
FileReader::perform_readasdataurl(&fr.result, data, &blob_contents), FileReader::perform_readasdataurl(&fr.result, data, &blob_contents)
FileReaderFunction::ReadAsText => },
FileReader::perform_readastext(&fr.result, data, &blob_contents), FileReaderFunction::ReadAsText => {
FileReader::perform_readastext(&fr.result, data, &blob_contents)
},
FileReaderFunction::ReadAsArrayBuffer => { FileReaderFunction::ReadAsArrayBuffer => {
let _ac = JSAutoCompartment::new(fr.global().get_cx(), *fr.reflector().get_jsobject()); let _ac =
FileReader::perform_readasarraybuffer(&fr.result, fr.global().get_cx(), data, &blob_contents) JSAutoCompartment::new(fr.global().get_cx(), *fr.reflector().get_jsobject());
FileReader::perform_readasarraybuffer(
&fr.result,
fr.global().get_cx(),
data,
&blob_contents,
)
}, },
}; };
@ -215,55 +285,43 @@ impl FileReader {
} }
// https://w3c.github.io/FileAPI/#dfn-readAsText // https://w3c.github.io/FileAPI/#dfn-readAsText
fn perform_readastext(result: &DomRefCell<Option<FileReaderResult>>, data: ReadMetaData, blob_bytes: &[u8]) { fn perform_readastext(
result: &DomRefCell<Option<FileReaderResult>>,
data: ReadMetaData,
blob_bytes: &[u8],
) {
let blob_label = &data.label; let blob_label = &data.label;
let blob_type = &data.blobtype; let blob_type = &data.blobtype;
//https://w3c.github.io/FileAPI/#encoding-determination let output = FileReaderSharedFunctionality::text_decode(blob_bytes, blob_type, blob_label);
// Steps 1 & 2 & 3 *result.borrow_mut() = Some(FileReaderResult::String(output));
let mut encoding = blob_label.as_ref()
.map(|string| string.as_bytes())
.and_then(Encoding::for_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::for_label(v.as_str().as_bytes()))
})
});
// Step 6
let enc = encoding.unwrap_or(UTF_8);
let convert = blob_bytes;
// Step 7
let (output, _, _) = enc.decode(convert);
*result.borrow_mut() = Some(FileReaderResult::String(DOMString::from(output)));
} }
//https://w3c.github.io/FileAPI/#dfn-readAsDataURL //https://w3c.github.io/FileAPI/#dfn-readAsDataURL
fn perform_readasdataurl(result: &DomRefCell<Option<FileReaderResult>>, data: ReadMetaData, bytes: &[u8]) { fn perform_readasdataurl(
let base64 = base64::encode(bytes); result: &DomRefCell<Option<FileReaderResult>>,
data: ReadMetaData,
bytes: &[u8],
) {
let output = FileReaderSharedFunctionality::dataurl_format(bytes, data.blobtype);
let output = if data.blobtype.is_empty() { *result.borrow_mut() = Some(FileReaderResult::String(output));
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 // https://w3c.github.io/FileAPI/#dfn-readAsArrayBuffer
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn perform_readasarraybuffer(result: &DomRefCell<Option<FileReaderResult>>, fn perform_readasarraybuffer(
cx: *mut JSContext, _: ReadMetaData, bytes: &[u8]) { result: &DomRefCell<Option<FileReaderResult>>,
cx: *mut JSContext,
_: ReadMetaData,
bytes: &[u8],
) {
unsafe { unsafe {
rooted!(in(cx) let mut array_buffer = ptr::null_mut::<JSObject>()); rooted!(in(cx) let mut array_buffer = ptr::null_mut::<JSObject>());
assert!(ArrayBuffer::create(cx, CreateWith::Slice(bytes), array_buffer.handle_mut()).is_ok()); assert!(
ArrayBuffer::create(cx, CreateWith::Slice(bytes), array_buffer.handle_mut())
.is_ok()
);
*result.borrow_mut() = Some(FileReaderResult::ArrayBuffer(Heap::default())); *result.borrow_mut() = Some(FileReaderResult::ArrayBuffer(Heap::default()));
@ -335,13 +393,12 @@ impl FileReaderMethods for FileReader {
// https://w3c.github.io/FileAPI/#dfn-result // https://w3c.github.io/FileAPI/#dfn-result
unsafe fn GetResult(&self, _: *mut JSContext) -> Option<StringOrObject> { unsafe fn GetResult(&self, _: *mut JSContext) -> Option<StringOrObject> {
self.result.borrow().as_ref().map(|r| match *r { self.result.borrow().as_ref().map(|r| match *r {
FileReaderResult::String(ref string) => FileReaderResult::String(ref string) => StringOrObject::String(string.clone()),
StringOrObject::String(string.clone()),
FileReaderResult::ArrayBuffer(ref arr_buffer) => { FileReaderResult::ArrayBuffer(ref arr_buffer) => {
let result = RootedTraceableBox::new(Heap::default()); let result = RootedTraceableBox::new(Heap::default());
result.set((*arr_buffer.ptr.get()).to_object()); result.set((*arr_buffer.ptr.get()).to_object());
StringOrObject::Object(result) StringOrObject::Object(result)
} },
}) })
} }
@ -351,12 +408,17 @@ impl FileReaderMethods for FileReader {
} }
} }
impl FileReader { impl FileReader {
fn dispatch_progress_event(&self, type_: Atom, loaded: u64, total: Option<u64>) { fn dispatch_progress_event(&self, type_: Atom, loaded: u64, total: Option<u64>) {
let progressevent = ProgressEvent::new(&self.global(), let progressevent = ProgressEvent::new(
type_, EventBubbles::DoesNotBubble, EventCancelable::NotCancelable, &self.global(),
total.is_some(), loaded, total.unwrap_or(0)); type_,
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
total.is_some(),
loaded,
total.unwrap_or(0),
);
progressevent.upcast::<Event>().fire(self.upcast()); progressevent.upcast::<Event>().fire(self.upcast());
} }
@ -365,7 +427,12 @@ impl FileReader {
self.generation_id.set(GenerationId(prev_id + 1)); self.generation_id.set(GenerationId(prev_id + 1));
} }
fn read(&self, function: FileReaderFunction, blob: &Blob, label: Option<DOMString>) -> ErrorResult { fn read(
&self,
function: FileReaderFunction,
blob: &Blob,
label: Option<DOMString>,
) -> ErrorResult {
// Step 1 // Step 1
if self.ready_state.get() == FileReaderReadyState::Loading { if self.ready_state.get() == FileReaderReadyState::Loading {
return Err(Error::InvalidState); return Err(Error::InvalidState);
@ -388,16 +455,19 @@ impl FileReader {
let canceller = global.task_canceller(); let canceller = global.task_canceller();
let task_source = global.file_reading_task_source(); let task_source = global.file_reading_task_source();
thread::Builder::new().name("file reader async operation".to_owned()).spawn(move || { thread::Builder::new()
perform_annotated_read_operation( .name("file reader async operation".to_owned())
gen_id, .spawn(move || {
load_data, perform_annotated_read_operation(
blob_contents, gen_id,
fr, load_data,
task_source, blob_contents,
canceller, fr,
) task_source,
}).expect("Thread spawning failed"); canceller,
)
})
.expect("Thread spawning failed");
Ok(()) Ok(())
} }

View file

@ -2,17 +2,25 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use dom::bindings::codegen::Bindings::FileReaderSyncBinding; use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
use dom::bindings::error::Fallible; use dom::bindings::codegen::Bindings::FileReaderSyncBinding::{FileReaderSyncBinding, FileReaderSyncMethods};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::reflector::reflect_dom_object; use dom::bindings::reflector::reflect_dom_object;
use dom::bindings::root::DomRoot; use dom::bindings::root::DomRoot;
use dom::bindings::str::DOMString;
use dom::blob::Blob;
use dom::eventtarget::EventTarget; use dom::eventtarget::EventTarget;
use dom::filereader::FileReaderSharedFunctionality;
use dom::globalscope::GlobalScope; use dom::globalscope::GlobalScope;
use dom_struct::dom_struct; use dom_struct::dom_struct;
use js::jsapi::{JSContext, JSObject};
use js::typedarray::{ArrayBuffer, CreateWith};
use std::ptr;
use std::ptr::NonNull;
#[dom_struct] #[dom_struct]
pub struct FileReaderSync { pub struct FileReaderSync {
eventtarget: EventTarget eventtarget: EventTarget,
} }
impl FileReaderSync { impl FileReaderSync {
@ -23,11 +31,79 @@ impl FileReaderSync {
} }
pub fn new(global: &GlobalScope) -> DomRoot<FileReaderSync> { pub fn new(global: &GlobalScope) -> DomRoot<FileReaderSync> {
reflect_dom_object(Box::new(FileReaderSync::new_inherited()), reflect_dom_object(
global, FileReaderSyncBinding::Wrap) Box::new(FileReaderSync::new_inherited()),
global,
FileReaderSyncBinding::Wrap,
)
} }
pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<FileReaderSync>> { pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<FileReaderSync>> {
Ok(FileReaderSync::new(global)) Ok(FileReaderSync::new(global))
} }
fn get_blob_bytes(blob: &Blob) -> Result<Vec<u8>, Error> {
blob.get_bytes().map_err(|_| Error::NotReadable)
}
}
impl FileReaderSyncMethods for FileReaderSync {
// https://w3c.github.io/FileAPI/#readAsBinaryStringSyncSection
fn ReadAsBinaryString(&self, blob: &Blob) -> Fallible<DOMString> {
// step 1
let blob_contents = FileReaderSync::get_blob_bytes(blob)?;
// step 2
Ok(DOMString::from(String::from_utf8_lossy(&blob_contents)))
}
// https://w3c.github.io/FileAPI/#readAsTextSync
fn ReadAsText(&self, blob: &Blob, label: Option<DOMString>) -> Fallible<DOMString> {
// step 1
let blob_contents = FileReaderSync::get_blob_bytes(blob)?;
// step 2
let blob_label = label.map(String::from);
let blob_type = String::from(blob.Type());
let output =
FileReaderSharedFunctionality::text_decode(&blob_contents, &blob_type, &blob_label);
Ok(output)
}
// https://w3c.github.io/FileAPI/#readAsDataURLSync-section
fn ReadAsDataURL(&self, blob: &Blob) -> Fallible<DOMString> {
// step 1
let blob_contents = FileReaderSync::get_blob_bytes(blob)?;
// step 2
let output =
FileReaderSharedFunctionality::dataurl_format(&blob_contents, blob.Type().to_string());
Ok(output)
}
#[allow(unsafe_code)]
// https://w3c.github.io/FileAPI/#readAsArrayBufferSyncSection
unsafe fn ReadAsArrayBuffer(
&self,
cx: *mut JSContext,
blob: &Blob,
) -> Fallible<NonNull<JSObject>> {
// step 1
let blob_contents = FileReaderSync::get_blob_bytes(blob)?;
// step 2
rooted!(in(cx) let mut array_buffer = ptr::null_mut::<JSObject>());
assert!(
ArrayBuffer::create(
cx,
CreateWith::Slice(&blob_contents),
array_buffer.handle_mut()
).is_ok()
);
Ok(NonNull::new_unchecked(array_buffer.get()))
}
} }

View file

@ -34,6 +34,7 @@ interface DOMException {
const unsigned short TIMEOUT_ERR = 23; const unsigned short TIMEOUT_ERR = 23;
const unsigned short INVALID_NODE_TYPE_ERR = 24; const unsigned short INVALID_NODE_TYPE_ERR = 24;
const unsigned short DATA_CLONE_ERR = 25; const unsigned short DATA_CLONE_ERR = 25;
const unsigned short NOT_READABLE_ERR = 26;
// Error code as u16 // Error code as u16
readonly attribute unsigned short code; readonly attribute unsigned short code;

View file

@ -8,8 +8,12 @@
interface FileReaderSync { interface FileReaderSync {
// Synchronously return strings // Synchronously return strings
// ArrayBuffer readAsArrayBuffer(Blob blob); [Throws]
// DOMString readAsBinaryString(Blob blob); ArrayBuffer readAsArrayBuffer(Blob blob);
// DOMString readAsText(Blob blob, optional DOMString label); [Throws]
// DOMString readAsDataURL(Blob blob); DOMString readAsBinaryString(Blob blob);
[Throws]
DOMString readAsText(Blob blob, optional DOMString label);
[Throws]
DOMString readAsDataURL(Blob blob);
}; };

View file

@ -1,11 +0,0 @@
[FileReaderSync.worker.html]
type: testharness
[readAsText]
expected: FAIL
[readAsDataURL]
expected: FAIL
[readAsArrayBuffer]
expected: FAIL

View file

@ -409016,7 +409016,7 @@
"testharness" "testharness"
], ],
"FileAPI/FileReaderSync.worker.js": [ "FileAPI/FileReaderSync.worker.js": [
"19741fbd0498bf9135408ceb6128221cbeb4e2f3", "9d9a2b77b7fbd8ad4edb75228bd7bca1f915ad61",
"testharness" "testharness"
], ],
"FileAPI/OWNERS": [ "FileAPI/OWNERS": [

View file

@ -1,28 +1,56 @@
importScripts("/resources/testharness.js"); importScripts("/resources/testharness.js");
var blob, readerSync; var blob, empty_blob, readerSync;
setup(function() { setup(() => {
readerSync = new FileReaderSync(); readerSync = new FileReaderSync();
blob = new Blob(["test"]); blob = new Blob(["test"]);
empty_blob = new Blob();
}); });
test(function() { test(() => {
assert_true(readerSync instanceof FileReaderSync); assert_true(readerSync instanceof FileReaderSync);
}, "Interface"); }, "Interface");
test(function() { test(() => {
var text = readerSync.readAsText(blob); var text = readerSync.readAsText(blob);
assert_equals(text, "test"); assert_equals(text, "test");
}, "readAsText"); }, "readAsText");
test(function() { test(() => {
var data = readerSync.readAsDataURL(blob); var text = readerSync.readAsText(empty_blob);
assert_equals(data.indexOf("data:"), 0); assert_equals(text, "");
}, "readAsText with empty blob");
test(() => {
var data = readerSync.readAsDataURL(blob);
assert_equals(data.indexOf("data:"), 0);
}, "readAsDataURL"); }, "readAsDataURL");
test(function() { test(() => {
var data = readerSync.readAsArrayBuffer(blob); var data = readerSync.readAsDataURL(empty_blob);
assert_true(data instanceof ArrayBuffer); assert_equals(data.indexOf("data:"), 0);
}, "readAsDataURL with empty blob");
test(() => {
var data = readerSync.readAsBinaryString(blob);
assert_equals(data, "test");
}, "readAsBinaryString");
test(() => {
var data = readerSync.readAsBinaryString(empty_blob);
assert_equals(data, "");
}, "readAsBinaryString with empty blob");
test(() => {
var data = readerSync.readAsArrayBuffer(blob);
assert_true(data instanceof ArrayBuffer);
assert_equals(data.byteLength, "test".length);
}, "readAsArrayBuffer"); }, "readAsArrayBuffer");
test(() => {
var data = readerSync.readAsArrayBuffer(empty_blob);
assert_true(data instanceof ArrayBuffer);
assert_equals(data.byteLength, 0);
}, "readAsArrayBuffer with empty blob");
done(); done();