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 all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -5,15 +5,164 @@
//! 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::{HandleValue, MutableHandleValue};
use js::jsapi::{JSContext, JS_ReadStructuredClone, JS_STRUCTURED_CLONE_VERSION};
use js::jsapi::{JS_ClearPendingException, JS_WriteStructuredClone};
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(())
}

unsafe extern "C" fn read_callback(cx: *mut JSContext,
r: *mut JSStructuredCloneReader,
tag: u32,
_data: u32,
_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()
}

unsafe extern "C" fn write_callback(_cx: *mut JSContext,
w: *mut JSStructuredCloneWriter,
obj: HandleObject,
_closure: *mut raw::c_void)
-> bool {
if let Ok(blob) = root_from_handleobject::<Blob>(obj) {
return write_blob(blob, w).is_ok()
}
return false
}

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 {
false
}

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 {
false
}

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

unsafe extern "C" fn report_error_callback(_cx: *mut JSContext, _errorid: u32) {
}

static STRUCTURED_CLONE_CALLBACKS: JSStructuredCloneCallbacks = JSStructuredCloneCallbacks {
read: Some(read_callback),
write: Some(write_callback),
reportError: Some(report_error_callback),
readTransfer: Some(read_transfer_callback),
writeTransfer: Some(write_transfer_callback),
freeTransfer: Some(free_transfer_callback),
};

/// A buffer for a structured clone.
pub enum StructuredCloneData {
/// A non-serializable (default) variant
@@ -32,7 +181,7 @@ impl StructuredCloneData {
message,
&mut data,
&mut nbytes,
ptr::null(),
&STRUCTURED_CLONE_CALLBACKS,
ptr::null_mut(),
HandleValue::undefined())
};
@@ -60,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);
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);
}));
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.