Skip to content

Commit

Permalink
Changed blob to use DataSlice with Arc in order to limit wasteful cop…
Browse files Browse the repository at this point in the history
…ying of byte vector
  • Loading branch information
craftytrickster committed Dec 11, 2015
1 parent d01233a commit 4f33b07
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 72 deletions.
135 changes: 85 additions & 50 deletions components/script/dom/blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,73 @@ use dom::bindings::error::Fallible;
use dom::bindings::global::{GlobalField, GlobalRef};
use dom::bindings::js::Root;
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::trace::JSTraceable;
use num::ToPrimitive;
use std::ascii::AsciiExt;
use std::borrow::ToOwned;
use std::cell::Cell;
use std::cmp::{max, min};
use std::sync::mpsc::Sender;
use std::sync::Arc;
use util::str::DOMString;

#[derive(Clone, JSTraceable)]
pub struct DataSlice {
bytes: Arc<Vec<u8>>,
bytes_start: usize,
bytes_end: usize
}

impl DataSlice {
pub fn new(bytes: Arc<Vec<u8>>, start: Option<i64>, end: Option<i64>) -> DataSlice {
let size = bytes.len() as i64;
let relativeStart: i64 = match start {
None => 0,
Some(start) => {
if start < 0 {
max(size + start, 0)
} else {
min(start, size)
}
}
};
let relativeEnd: i64 = match end {
None => size,
Some(end) => {
if end < 0 {
max(size + end, 0)
} else {
min(end, size)
}
}
};

let span: i64 = max(relativeEnd - relativeStart, 0);
let start = relativeStart.to_usize().unwrap();
let end = (relativeStart + span).to_usize().unwrap();

DataSlice {
bytes: bytes,
bytes_start: start,
bytes_end: end
}
}

pub fn get_bytes(&self) -> &[u8] {
&self.bytes[self.bytes_start..self.bytes_end]
}

pub fn size(&self) -> u64 {
(self.bytes_end as u64) - (self.bytes_start as u64)
}
}


// http://dev.w3.org/2006/webapi/FileAPI/#blob
#[dom_struct]
pub struct Blob {
reflector_: Reflector,
bytes: Option<Vec<u8>>,
#[ignore_heap_size_of = "No clear owner"]
data: DataSlice,
typeString: String,
global: GlobalField,
isClosed_: Cell<bool>,
Expand All @@ -33,25 +87,43 @@ fn is_ascii_printable(string: &str) -> bool {
}

impl Blob {
pub fn new_inherited(global: GlobalRef, bytes: Option<Vec<u8>>, typeString: &str) -> Blob {
pub fn new_inherited(global: GlobalRef, bytes: Vec<u8>, typeString: &str) -> Blob {
Blob {
reflector_: Reflector::new(),
bytes: bytes,
data: DataSlice::new(Arc::new(bytes), None, None),
typeString: typeString.to_owned(),
global: GlobalField::from_rooted(&global),
isClosed_: Cell::new(false),
}
}

pub fn new(global: GlobalRef, bytes: Option<Vec<u8>>, typeString: &str) -> Root<Blob> {
pub fn new(global: GlobalRef, bytes: Vec<u8>, typeString: &str) -> Root<Blob> {
reflect_dom_object(box Blob::new_inherited(global, bytes, typeString),
global,
BlobBinding::Wrap)
}

fn new_sliced(global: GlobalRef,
bytes: Arc<Vec<u8>>,
bytes_start: Option<i64>,
bytes_end: Option<i64>,
typeString: &str) -> Root<Blob> {

let boxed_blob =
box Blob {
reflector_: Reflector::new(),
data: DataSlice::new(bytes, bytes_start, bytes_end),
typeString: typeString.to_owned(),
global: GlobalField::from_rooted(&global),
isClosed_: Cell::new(false),
};

reflect_dom_object(boxed_blob, global, BlobBinding::Wrap)
}

// http://dev.w3.org/2006/webapi/FileAPI/#constructorBlob
pub fn Constructor(global: GlobalRef) -> Fallible<Root<Blob>> {
Ok(Blob::new(global, None, ""))
Ok(Blob::new(global, Vec::new(), ""))
}

// http://dev.w3.org/2006/webapi/FileAPI/#constructorBlob
Expand All @@ -61,7 +133,7 @@ impl Blob {
-> Fallible<Root<Blob>> {
// TODO: accept other blobParts types - ArrayBuffer or ArrayBufferView or Blob
// FIXME(ajeffrey): convert directly from a DOMString to a Vec<u8>
let bytes: Option<Vec<u8>> = Some(String::from(blobParts).into_bytes());
let bytes: Vec<u8> = String::from(blobParts).into_bytes();
let typeString = if is_ascii_printable(&blobPropertyBag.type_) {
&*blobPropertyBag.type_
} else {
Expand All @@ -70,23 +142,15 @@ impl Blob {
Ok(Blob::new(global, bytes, &typeString.to_ascii_lowercase()))
}

pub fn read_out_buffer(&self, send: Sender<Vec<u8>>) {
send.send(self.bytes.clone().unwrap_or(vec![])).unwrap();
}

// simpler to use version of read_out_buffer
pub fn clone_bytes(&self) -> Vec<u8> {
self.bytes.clone().unwrap_or(vec![])
pub fn get_data(&self) -> &DataSlice {
&self.data
}
}

impl BlobMethods for Blob {
// https://dev.w3.org/2006/webapi/FileAPI/#dfn-size
fn Size(&self) -> u64 {
match self.bytes {
None => 0,
Some(ref bytes) => bytes.len() as u64,
}
self.data.size()
}

// https://dev.w3.org/2006/webapi/FileAPI/#dfn-type
Expand All @@ -100,27 +164,7 @@ impl BlobMethods for Blob {
end: Option<i64>,
contentType: Option<DOMString>)
-> Root<Blob> {
let size: i64 = self.Size().to_i64().unwrap();
let relativeStart: i64 = match start {
None => 0,
Some(start) => {
if start < 0 {
max(size.to_i64().unwrap() + start, 0)
} else {
min(start, size)
}
}
};
let relativeEnd: i64 = match end {
None => size,
Some(end) => {
if end < 0 {
max(size + end, 0)
} else {
min(end, size)
}
}
};

let relativeContentType = match contentType {
None => DOMString::new(),
Some(mut str) => {
Expand All @@ -132,18 +176,9 @@ impl BlobMethods for Blob {
}
}
};
let span: i64 = max(relativeEnd - relativeStart, 0);
let global = self.global.root();
match self.bytes {
None => Blob::new(global.r(), None, &relativeContentType),
Some(ref vec) => {
let start = relativeStart.to_usize().unwrap();
let end = (relativeStart + span).to_usize().unwrap();
let mut bytes: Vec<u8> = Vec::new();
bytes.extend_from_slice(&vec[start..end]);
Blob::new(global.r(), Some(bytes), &relativeContentType)
}
}
let bytes = self.data.bytes.clone();
Blob::new_sliced(global.r(), bytes, start, end, &relativeContentType)
}

// https://dev.w3.org/2006/webapi/FileAPI/#dfn-isClosed
Expand Down
2 changes: 1 addition & 1 deletion components/script/dom/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl File {
_file_bits: &Blob, name: DOMString) -> File {
File {
//TODO: get type from the underlying filesystem instead of "".to_string()
blob: Blob::new_inherited(global, None, ""),
blob: Blob::new_inherited(global, Vec::new(), ""),
name: name,
}
// XXXManishearth Once Blob is able to store data
Expand Down
28 changes: 15 additions & 13 deletions components/script/dom/filereader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, MutNullableHeap, Root};
use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::{Reflectable, reflect_dom_object};
use dom::blob::Blob;
use dom::blob::{Blob, DataSlice};
use dom::domexception::{DOMErrorName, DOMException};
use dom::event::{Event, EventBubbles, EventCancelable};
use dom::eventtarget::EventTarget;
Expand Down Expand Up @@ -162,7 +162,7 @@ impl FileReader {

// https://w3c.github.io/FileAPI/#dfn-readAsText
pub fn process_read_eof(filereader: TrustedFileReader, gen_id: GenerationId,
data: ReadMetaData, blob_contents: Vec<u8>) {
data: ReadMetaData, blob_contents: DataSlice) {
let fr = filereader.root();

macro_rules! return_on_abort(
Expand All @@ -177,11 +177,13 @@ impl FileReader {
// Step 8.1
fr.change_ready_state(FileReaderReadyState::Done);
// Step 8.2

let bytes = blob_contents.get_bytes();
let output = match data.function {
FileReaderFunction::ReadAsDataUrl =>
FileReader::perform_readasdataurl(data, blob_contents),
FileReader::perform_readasdataurl(data, bytes),
FileReaderFunction::ReadAsText =>
FileReader::perform_readastext(data, blob_contents),
FileReader::perform_readastext(data, bytes),
};

*fr.result.borrow_mut() = Some(output);
Expand All @@ -199,12 +201,11 @@ impl FileReader {
}

// https://w3c.github.io/FileAPI/#dfn-readAsText
fn perform_readastext(data: ReadMetaData, blob_contents: Vec<u8>)
fn perform_readastext(data: ReadMetaData, blob_bytes: &[u8])
-> DOMString {

let blob_label = &data.label;
let blob_type = &data.blobtype;
let blob_bytes = &blob_contents[..];

//https://w3c.github.io/FileAPI/#encoding-determination
// Steps 1 & 2 & 3
Expand Down Expand Up @@ -232,15 +233,15 @@ impl FileReader {
}

//https://w3c.github.io/FileAPI/#dfn-readAsDataURL
fn perform_readasdataurl(data: ReadMetaData, blob_contents: Vec<u8>)
fn perform_readasdataurl(data: ReadMetaData, bytes: &[u8])
-> DOMString {
let config = Config {
char_set: CharacterSet::UrlSafe,
newline: Newline::LF,
pad: true,
line_length: None
};
let base64 = blob_contents.to_base64(config);
let base64 = bytes.to_base64(config);

let output = if data.blobtype.is_empty() {
format!("data:base64,{}", base64)
Expand Down Expand Up @@ -354,8 +355,9 @@ impl FileReader {
self.change_ready_state(FileReaderReadyState::Loading);

// Step 4
let (send, bytes) = mpsc::channel();
blob.read_out_buffer(send);
let (bytes_sender, bytes_receiver) = mpsc::channel();
bytes_sender.send(blob.get_data().clone()).unwrap();

let type_ = blob.Type();

let load_data = ReadMetaData::new(String::from(type_), label.map(String::from), function);
Expand All @@ -366,7 +368,7 @@ impl FileReader {
let script_chan = global.file_reading_task_source();

spawn_named("file reader async operation".to_owned(), move || {
perform_annotated_read_operation(gen_id, load_data, bytes, fr, script_chan)
perform_annotated_read_operation(gen_id, load_data, bytes_receiver, fr, script_chan)
});
Ok(())
}
Expand All @@ -381,7 +383,7 @@ pub enum FileReaderEvent {
ProcessRead(TrustedFileReader, GenerationId),
ProcessReadData(TrustedFileReader, GenerationId),
ProcessReadError(TrustedFileReader, GenerationId, DOMErrorName),
ProcessReadEOF(TrustedFileReader, GenerationId, ReadMetaData, Vec<u8>)
ProcessReadEOF(TrustedFileReader, GenerationId, ReadMetaData, DataSlice)
}

impl Runnable for FileReaderEvent {
Expand All @@ -405,7 +407,7 @@ impl Runnable for FileReaderEvent {
}

// https://w3c.github.io/FileAPI/#task-read-operation
fn perform_annotated_read_operation(gen_id: GenerationId, data: ReadMetaData, blob_contents: Receiver<Vec<u8>>,
fn perform_annotated_read_operation(gen_id: GenerationId, data: ReadMetaData, blob_contents: Receiver<DataSlice>,
filereader: TrustedFileReader, script_chan: Box<ScriptChan + Send>) {
let chan = &script_chan;
// Step 4
Expand Down
8 changes: 4 additions & 4 deletions components/script/dom/testbinding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ impl TestBindingMethods for TestBinding {
fn EnumAttribute(&self) -> TestEnum { TestEnum::_empty }
fn SetEnumAttribute(&self, _: TestEnum) {}
fn InterfaceAttribute(&self) -> Root<Blob> {
Blob::new(global_root_from_reflector(self).r(), None, "")
Blob::new(global_root_from_reflector(self).r(), Vec::new(), "")
}
fn SetInterfaceAttribute(&self, _: &Blob) {}
fn UnionAttribute(&self) -> HTMLElementOrLong { HTMLElementOrLong::eLong(0) }
Expand Down Expand Up @@ -143,7 +143,7 @@ impl TestBindingMethods for TestBinding {
fn SetAttr_to_automatically_rename(&self, _: DOMString) {}
fn GetEnumAttributeNullable(&self) -> Option<TestEnum> { Some(TestEnum::_empty) }
fn GetInterfaceAttributeNullable(&self) -> Option<Root<Blob>> {
Some(Blob::new(global_root_from_reflector(self).r(), None, ""))
Some(Blob::new(global_root_from_reflector(self).r(), Vec::new(), ""))
}
fn SetInterfaceAttributeNullable(&self, _: Option<&Blob>) {}
fn GetInterfaceAttributeWeak(&self) -> Option<Root<URL>> {
Expand Down Expand Up @@ -182,7 +182,7 @@ impl TestBindingMethods for TestBinding {
fn ReceiveByteString(&self) -> ByteString { ByteString::new(vec!()) }
fn ReceiveEnum(&self) -> TestEnum { TestEnum::_empty }
fn ReceiveInterface(&self) -> Root<Blob> {
Blob::new(global_root_from_reflector(self).r(), None, "")
Blob::new(global_root_from_reflector(self).r(), Vec::new(), "")
}
fn ReceiveAny(&self, _: *mut JSContext) -> JSVal { NullValue() }
fn ReceiveObject(&self, _: *mut JSContext) -> *mut JSObject { panic!() }
Expand All @@ -207,7 +207,7 @@ impl TestBindingMethods for TestBinding {
fn ReceiveNullableByteString(&self) -> Option<ByteString> { Some(ByteString::new(vec!())) }
fn ReceiveNullableEnum(&self) -> Option<TestEnum> { Some(TestEnum::_empty) }
fn ReceiveNullableInterface(&self) -> Option<Root<Blob>> {
Some(Blob::new(global_root_from_reflector(self).r(), None, ""))
Some(Blob::new(global_root_from_reflector(self).r(), Vec::new(), ""))
}
fn ReceiveNullableObject(&self, _: *mut JSContext) -> *mut JSObject { ptr::null_mut() }
fn ReceiveNullableUnion(&self) -> Option<HTMLElementOrLong> {
Expand Down
9 changes: 5 additions & 4 deletions components/script/dom/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,19 +374,20 @@ impl WebSocketMethods for WebSocket {
}

// https://html.spec.whatwg.org/multipage/#dom-websocket-send
fn Send_(&self, data: &Blob) -> Fallible<()> {
fn Send_(&self, blob: &Blob) -> Fallible<()> {

/* As per https://html.spec.whatwg.org/multipage/#websocket
the buffered amount needs to be clamped to u32, even though Blob.Size() is u64
If the buffer limit is reached in the first place, there are likely other major problems
*/
let data_byte_len = data.Size();
let data_byte_len = blob.Size();
let send_data = try!(self.send_impl(data_byte_len));

if send_data {
let mut other_sender = self.sender.borrow_mut();
let my_sender = other_sender.as_mut().unwrap();
let _ = my_sender.send(WebSocketDomAction::SendMessage(MessageData::Binary(data.clone_bytes())));
let bytes = blob.get_data().get_bytes().to_vec();
let _ = my_sender.send(WebSocketDomAction::SendMessage(MessageData::Binary(bytes)));
}

Ok(())
Expand Down Expand Up @@ -556,7 +557,7 @@ impl Runnable for MessageReceivedTask {
MessageData::Binary(data) => {
match ws.binary_type.get() {
BinaryType::Blob => {
let blob = Blob::new(global.r(), Some(data), "");
let blob = Blob::new(global.r(), data, "");
blob.to_jsval(cx, message.handle_mut());
}
BinaryType::Arraybuffer => {
Expand Down

0 comments on commit 4f33b07

Please sign in to comment.