Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement structured clone callbacks - support Blob cloning #15519

Merged
merged 2 commits into from Mar 22, 2017
Merged
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Prev

support structured cloning for Blob

  • Loading branch information
gterzian committed Mar 22, 2017
commit 5c4f0be04872f5f2e8736b15a2169f9479509a0e
@@ -5,72 +5,155 @@
//! This module implements structured cloning, as defined by [HTML]
//! (https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data).

use dom::bindings::conversions::root_from_handleobject;
use dom::bindings::error::{Error, Fallible};
use dom::bindings::js::Root;
use dom::bindings::reflector::DomObject;
use dom::blob::{Blob, BlobImpl};
use dom::globalscope::GlobalScope;
use js::jsapi::{Handle, HandleObject, HandleValue, MutableHandleValue};
use js::jsapi::{Heap, JSContext};
use js::jsapi::{Handle, HandleObject, HandleValue, MutableHandleValue, JSAutoCompartment, JSContext};
use js::jsapi::{JSStructuredCloneCallbacks, JSStructuredCloneReader, JSStructuredCloneWriter};
use js::jsapi::{JS_ClearPendingException, JSObject, JS_ReadStructuredClone};
use js::jsapi::{JS_ReadBytes, JS_WriteBytes};
use js::jsapi::{JS_ReadUint32Pair, JS_WriteUint32Pair};
use js::jsapi::{JS_STRUCTURED_CLONE_VERSION, JS_WriteStructuredClone};
use js::jsapi::{MutableHandleObject, TransferableOwnership};
use libc::size_t;
use std::os::raw;
use std::ptr;
use std::slice;

// TODO: Should we add Min and Max const to https://github.com/servo/rust-mozjs/blob/master/src/consts.rs?
// TODO: Determine for sure which value Min and Max should have.
// NOTE: Current values found at https://dxr.mozilla.org/mozilla-central/
// rev/ff04d410e74b69acfab17ef7e73e7397602d5a68/js/public/StructuredClone.h#323
#[repr(u32)]
enum StructuredCloneTags {
/// To support additional types, add new tags with values incremented from the last one before Max.
Min = 0xFFFF8000,
DomBlob = 0xFFFF8001,
Max = 0xFFFFFFFF,
}

#[cfg(target_pointer_width = "64")]
unsafe fn write_length(w: *mut JSStructuredCloneWriter,
length: usize) {
let high: u32 = (length >> 32) as u32;
let low: u32 = length as u32;
assert!(JS_WriteUint32Pair(w, high, low));
}

#[cfg(target_pointer_width = "32")]
unsafe fn write_length(w: *mut JSStructuredCloneWriter,
length: usize) {
assert!(JS_WriteUint32Pair(w, length as u32, 0));
}

#[cfg(target_pointer_width = "64")]
unsafe fn read_length(r: *mut JSStructuredCloneReader)
-> usize {
let mut high: u32 = 0;
let mut low: u32 = 0;
assert!(JS_ReadUint32Pair(r, &mut high as *mut u32, &mut low as *mut u32));
return (low << high) as usize;
}

#[cfg(target_pointer_width = "32")]
unsafe fn read_length(r: *mut JSStructuredCloneReader)
-> usize {
let mut length: u32 = 0;
let mut zero: u32 = 0;
assert!(JS_ReadUint32Pair(r, &mut length as *mut u32, &mut zero as *mut u32));
return length as usize;
}

unsafe fn read_blob(cx: *mut JSContext,
r: *mut JSStructuredCloneReader)
-> *mut JSObject {
let blob_length = read_length(r);
let type_str_length = read_length(r);
let mut blob_buffer = vec![0u8; blob_length];
assert!(JS_ReadBytes(r, blob_buffer.as_mut_ptr() as *mut raw::c_void, blob_length));
let mut type_str_buffer = vec![0u8; type_str_length];
assert!(JS_ReadBytes(r, type_str_buffer.as_mut_ptr() as *mut raw::c_void, type_str_length));
let type_str = String::from_utf8_unchecked(type_str_buffer);
let target_global = GlobalScope::from_context(cx);
let blob = Blob::new(&target_global, BlobImpl::new_from_bytes(blob_buffer), type_str);
return blob.reflector().get_jsobject().get()
}

unsafe fn write_blob(blob: Root<Blob>,
w: *mut JSStructuredCloneWriter)
-> Result<(), ()> {
let blob_vec = try!(blob.get_bytes());
let blob_length = blob_vec.len();
let type_string_bytes = blob.get_type_string().as_bytes().to_vec();
let type_string_length = type_string_bytes.len();
assert!(JS_WriteUint32Pair(w, StructuredCloneTags::DomBlob as u32, 0));
write_length(w, blob_length);
write_length(w, type_string_length);
assert!(JS_WriteBytes(w, blob_vec.as_ptr() as *const raw::c_void, blob_length));
assert!(JS_WriteBytes(w, type_string_bytes.as_ptr() as *const raw::c_void, type_string_length));
return Ok(())
}

#[allow(dead_code)]
unsafe extern "C" fn read_callback(_cx: *mut JSContext,
_r: *mut JSStructuredCloneReader,
_tag: u32,
unsafe extern "C" fn read_callback(cx: *mut JSContext,
r: *mut JSStructuredCloneReader,
tag: u32,
_data: u32,
_closure: *mut raw::c_void) -> *mut JSObject {
Heap::default().get()
_closure: *mut raw::c_void)
-> *mut JSObject {
assert!(tag < StructuredCloneTags::Max as u32, "tag should be lower than StructuredCloneTags::Max");
assert!(tag > StructuredCloneTags::Min as u32, "tag should be higher than StructuredCloneTags::Min");
if tag == StructuredCloneTags::DomBlob as u32 {
return read_blob(cx, r)
}
return ptr::null_mut()
}

#[allow(dead_code)]
unsafe extern "C" fn write_callback(_cx: *mut JSContext,
_w: *mut JSStructuredCloneWriter,
_obj: HandleObject,
_closure: *mut raw::c_void) -> bool {
false
w: *mut JSStructuredCloneWriter,
obj: HandleObject,
_closure: *mut raw::c_void)
-> bool {
if let Ok(blob) = root_from_handleobject::<Blob>(obj) {

This comment has been minimized.

Copy link
@Ms2ger

Ms2ger Mar 10, 2017

Contributor

I think this will be more maintainable going forward if we create a write_blob function and move the contents of this outer if block into it.

return write_blob(blob, w).is_ok()
}
return false
}

#[allow(dead_code)]
unsafe extern "C" fn read_transfer_callback(_cx: *mut JSContext,
_r: *mut JSStructuredCloneReader,
_tag: u32,
_content: *mut raw::c_void,
_extra_data: u64,
_closure: *mut raw::c_void,
_return_object: MutableHandleObject) -> bool {
_return_object: MutableHandleObject)
-> bool {
false
}

#[allow(dead_code)]
unsafe extern "C" fn write_transfer_callback(_cx: *mut JSContext,
_obj: Handle<*mut JSObject>,
_closure: *mut raw::c_void,
_tag: *mut u32,
_ownership: *mut TransferableOwnership,
_content: *mut *mut raw::c_void,
_extra_data: *mut u64) -> bool {
_extra_data: *mut u64)
-> bool {
false
}

#[allow(dead_code)]
unsafe extern "C" fn free_transfer_callback(_tag: u32,
_ownership: TransferableOwnership,
_content: *mut raw::c_void,
_extra_data: u64,
_closure: *mut raw::c_void) {
}

#[allow(dead_code)]
unsafe extern "C" fn report_error_callback(_cx: *mut JSContext, _errorid: u32) {
}

#[allow(dead_code)]
static STRUCTURED_CLONE_CALLBACKS: JSStructuredCloneCallbacks = JSStructuredCloneCallbacks {
read: Some(read_callback),
write: Some(write_callback),
@@ -98,7 +181,7 @@ impl StructuredCloneData {
message,
&mut data,
&mut nbytes,
ptr::null(),
&STRUCTURED_CLONE_CALLBACKS,
ptr::null_mut(),
HandleValue::undefined())
};
@@ -126,14 +209,20 @@ impl StructuredCloneData {
/// Reads a structured clone.
///
/// Panics if `JS_ReadStructuredClone` fails.
fn read_clone(global: &GlobalScope, data: *mut u64, nbytes: size_t, rval: MutableHandleValue) {
fn read_clone(global: &GlobalScope,
data: *mut u64,
nbytes: size_t,
rval: MutableHandleValue) {
let cx = global.get_cx();
let globalhandle = global.reflector().get_jsobject();
let _ac = JSAutoCompartment::new(cx, globalhandle.get());
unsafe {
assert!(JS_ReadStructuredClone(global.get_cx(),
assert!(JS_ReadStructuredClone(cx,
data,
nbytes,
JS_STRUCTURED_CLONE_VERSION,
rval,
ptr::null(),
&STRUCTURED_CLONE_CALLBACKS,
ptr::null_mut()));
}
}
@@ -166,6 +166,11 @@ impl Blob {
}
}

/// Get a copy of the type_string
pub fn get_type_string(&self) -> String {
self.type_string.clone()
}

/// Get a FileID representing the Blob content,
/// used by URL.createObjectURL
pub fn get_blob_url_id(&self) -> Uuid {
{}
]
],
"html/infrastructure/safe-passing-of-structured-data/structured_clone_blob.html": [
[
"/html/infrastructure/safe-passing-of-structured-data/structured_clone_blob.html",
{}
]
],
"html/infrastructure/terminology/plugins/text-plain.html": [
[
"/html/infrastructure/terminology/plugins/text-plain.html",
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
"support"
],
"html/infrastructure/safe-passing-of-structured-data/structured_clone_blob.html": [
"2a3deba2534cad6f5e0aa85cfc3c90debcead20a",
"testharness"
],
"html/infrastructure/terminology/.gitkeep": [
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
"support"
@@ -0,0 +1,35 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Safe passing of structured data - Blob</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
async_test(function(t) {
var blob = new Blob(['<a id="a"><b id="b">hey!</b></a>'], {type:"text/plain"});
window.addEventListener("message", this.step_func(function(msg) {
assert_true(msg.data instanceof Blob);
assert_equals(msg.data.size, blob.size);

This comment has been minimized.

Copy link
@jdm

jdm Mar 16, 2017

Member

Add a assert_true(msg.data instanceof Blob), too.

This comment has been minimized.

Copy link
@gterzian

gterzian Mar 19, 2017

Author Member

this was done as well, although two lines lower so Github doesn't show it as outdated...

This comment has been minimized.

Copy link
@jdm

jdm Mar 19, 2017

Member

It probably makes sense to check that it's a Blob first, otherwise this check is likely to fail in a slightly more confusing fashion.

assert_equals(msg.data.type, blob.type);
var cloned_content, original_content;
var reader = new FileReader();
reader.addEventListener("loadend", this.step_func(function() {
original_content = reader.result;
var reader2 = new FileReader();
reader2.addEventListener("loadend", this.step_func_done(function() {
cloned_content = reader2.result;
assert_equals(typeof cloned_content, typeof original_content);
assert_equals(cloned_content, original_content);

This comment has been minimized.

Copy link
@jdm

jdm Mar 16, 2017

Member

Let's assert the types match, too.

}));
reader2.readAsText(msg.data);
}));
reader.readAsText(blob);
}), false);
window.postMessage(blob, '*');
}, "Cloning a Blob into the same realm");
</script>
</body>
</html>
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.