diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs index 405d245c4bb..ed3cf6aa6e2 100644 --- a/components/script/dom/bindings/structuredclone.rs +++ b/components/script/dom/bindings/structuredclone.rs @@ -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, + 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::(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())); } } diff --git a/components/script/dom/blob.rs b/components/script/dom/blob.rs index 036dcfab483..d8ed5fa1b64 100644 --- a/components/script/dom/blob.rs +++ b/components/script/dom/blob.rs @@ -163,6 +163,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 { diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json index 760a3f00a0b..906bfbb9348 100644 --- a/tests/wpt/metadata/MANIFEST.json +++ b/tests/wpt/metadata/MANIFEST.json @@ -90946,6 +90946,12 @@ {} ] ], + "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", @@ -173347,6 +173353,10 @@ "da39a3ee5e6b4b0d3255bfef95601890afd80709", "support" ], + "html/infrastructure/safe-passing-of-structured-data/structured_clone_blob.html": [ + "2a3deba2534cad6f5e0aa85cfc3c90debcead20a", + "testharness" + ], "html/infrastructure/terminology/.gitkeep": [ "da39a3ee5e6b4b0d3255bfef95601890afd80709", "support" diff --git a/tests/wpt/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/structured_clone_blob.html b/tests/wpt/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/structured_clone_blob.html new file mode 100644 index 00000000000..6b1abf5ef8d --- /dev/null +++ b/tests/wpt/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/structured_clone_blob.html @@ -0,0 +1,35 @@ + + + + +Safe passing of structured data - Blob + + + + + + +