/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/bindings/structuredclone.rs
885 строк
33 KB
Gae24
script: Drop unsafe `ToJSValConvertible::to_jsval` and wrapper trait `SafeToJSValConvertible` (#46890)
31 июл 2026, 10:20
Не верифицирован
31 июл 2026, 10:20
559e0a5
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! This module implements structured cloning, as defined by [HTML](https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data). use std::ffi::CStr; use std::os::raw; use std::ptr::{self, NonNull}; use js::context::{JSContext, NoGC}; use js::conversions::ToJSValConvertible; use js::gc::RootedVec; use js::glue::{ CopyJSStructuredCloneData, GetLengthOfJSStructuredCloneData, WriteBytesToJSStructuredCloneData, }; use js::jsapi::{ CloneDataPolicy, HandleObject as RawHandleObject, Heap, JS_ReadUint32Pair, JS_STRUCTURED_CLONE_VERSION, JS_WriteUint32Pair, JSContext as RawJSContext, JSObject, JSStructuredCloneCallbacks, JSStructuredCloneReader, JSStructuredCloneWriter, MutableHandleObject as RawMutableHandleObject, StructuredCloneScope, TransferableOwnership, }; use js::jsval::UndefinedValue; use js::realm::CurrentRealm; use js::rust::wrappers2::{JS_IsExceptionPending, JS_ReadStructuredClone, JS_WriteStructuredClone}; use js::rust::{ CustomAutoRooterGuard, HandleValue, JSAutoStructuredCloneBufferWrapper, MutableHandleValue, }; use rustc_hash::FxHashMap; use script_bindings::conversions::IDLInterface; use servo_base::id::{ BlobId, CryptoKeyId, DomExceptionId, DomMatrixId, DomPointId, DomQuadId, DomRectId, FileId, FileListId, ImageBitmapId, ImageDataId, Index, MessagePortId, NamespaceIndex, OffscreenCanvasId, PipelineNamespaceId, QuotaExceededErrorId, }; use servo_constellation_traits::{ BlobImpl, DomException, DomMatrix, DomPoint, DomQuad, DomRect, MessagePortImpl, Serializable as SerializableInterface, SerializableCryptoKey, SerializableFile, SerializableFileList, SerializableImageBitmap, SerializableImageData, SerializableQuotaExceededError, StructuredSerializedData, TransferableOffscreenCanvas, Transferrable as TransferrableInterface, TransformStreamData, }; use strum::IntoEnumIterator; use crate::dom::bindings::conversions::root_from_object; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::serializable::{Serializable, StorageKey}; use crate::dom::bindings::transferable::Transferable; use crate::dom::blob::Blob; use crate::dom::cryptokey::CryptoKey; use crate::dom::dompoint::DOMPoint; use crate::dom::dompointreadonly::DOMPointReadOnly; use crate::dom::file::File; use crate::dom::filelist::FileList; use crate::dom::globalscope::GlobalScope; use crate::dom::imagebitmap::ImageBitmap; use crate::dom::imagedata::ImageData; use crate::dom::messageport::MessagePort; use crate::dom::offscreencanvas::OffscreenCanvas; use crate::dom::stream::readablestream::ReadableStream; use crate::dom::stream::writablestream::WritableStream; use crate::dom::types::{ DOMException, DOMMatrix, DOMMatrixReadOnly, DOMQuad, DOMRect, DOMRectReadOnly, QuotaExceededError, TransformStream, }; use crate::realms::enter_auto_realm; // 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)] pub(super) enum StructuredCloneTags { /// To support additional types, add new tags with values incremented from the last one before Max. Min = 0xFFFF8000, DomBlob = 0xFFFF8001, DomFile = 0xFFFF8002, DomFileList = 0xFFFF8003, MessagePort = 0xFFFF8004, Principals = 0xFFFF8005, DomPointReadOnly = 0xFFFF8006, DomPoint = 0xFFFF8007, ReadableStream = 0xFFFF8008, DomException = 0xFFFF8009, WritableStream = 0xFFFF800A, TransformStream = 0xFFFF800B, ImageBitmap = 0xFFFF800C, OffscreenCanvas = 0xFFFF800D, QuotaExceededError = 0xFFFF800E, DomRect = 0xFFFF800F, DomRectReadOnly = 0xFFFF8010, DomQuad = 0xFFFF8011, DomMatrix = 0xFFFF8012, DomMatrixReadOnly = 0xFFFF8013, ImageData = 0xFFFF8014, CryptoKey = 0xFFFF8015, Max = 0xFFFFFFFF, } impl From<SerializableInterface> for StructuredCloneTags { fn from(v: SerializableInterface) -> Self { match v { SerializableInterface::File => StructuredCloneTags::DomFile, SerializableInterface::FileList => StructuredCloneTags::DomFileList, SerializableInterface::Blob => StructuredCloneTags::DomBlob, SerializableInterface::DomPoint => StructuredCloneTags::DomPoint, SerializableInterface::DomPointReadOnly => StructuredCloneTags::DomPointReadOnly, SerializableInterface::DomRect => StructuredCloneTags::DomRect, SerializableInterface::DomRectReadOnly => StructuredCloneTags::DomRectReadOnly, SerializableInterface::DomQuad => StructuredCloneTags::DomQuad, SerializableInterface::DomMatrix => StructuredCloneTags::DomMatrix, SerializableInterface::DomMatrixReadOnly => StructuredCloneTags::DomMatrixReadOnly, SerializableInterface::DomException => StructuredCloneTags::DomException, SerializableInterface::ImageBitmap => StructuredCloneTags::ImageBitmap, SerializableInterface::QuotaExceededError => StructuredCloneTags::QuotaExceededError, SerializableInterface::ImageData => StructuredCloneTags::ImageData, SerializableInterface::CryptoKey => StructuredCloneTags::CryptoKey, } } } impl From<TransferrableInterface> for StructuredCloneTags { fn from(v: TransferrableInterface) -> Self { match v { TransferrableInterface::ImageBitmap => StructuredCloneTags::ImageBitmap, TransferrableInterface::MessagePort => StructuredCloneTags::MessagePort, TransferrableInterface::OffscreenCanvas => StructuredCloneTags::OffscreenCanvas, TransferrableInterface::ReadableStream => StructuredCloneTags::ReadableStream, TransferrableInterface::WritableStream => StructuredCloneTags::WritableStream, TransferrableInterface::TransformStream => StructuredCloneTags::TransformStream, } } } fn reader_for_type( val: SerializableInterface, ) -> unsafe fn( cx: &mut JSContext, &GlobalScope, *mut JSStructuredCloneReader, &mut StructuredDataReader<'_>, ) -> *mut JSObject { match val { SerializableInterface::File => read_object::<File>, SerializableInterface::FileList => read_object::<FileList>, SerializableInterface::Blob => read_object::<Blob>, SerializableInterface::DomPoint => read_object::<DOMPoint>, SerializableInterface::DomPointReadOnly => read_object::<DOMPointReadOnly>, SerializableInterface::DomRect => read_object::<DOMRect>, SerializableInterface::DomRectReadOnly => read_object::<DOMRectReadOnly>, SerializableInterface::DomQuad => read_object::<DOMQuad>, SerializableInterface::DomMatrix => read_object::<DOMMatrix>, SerializableInterface::DomMatrixReadOnly => read_object::<DOMMatrixReadOnly>, SerializableInterface::DomException => read_object::<DOMException>, SerializableInterface::ImageBitmap => read_object::<ImageBitmap>, SerializableInterface::QuotaExceededError => read_object::<QuotaExceededError>, SerializableInterface::ImageData => read_object::<ImageData>, SerializableInterface::CryptoKey => read_object::<CryptoKey>, } } unsafe fn read_object<T: Serializable>( cx: &mut JSContext, owner: &GlobalScope, r: *mut JSStructuredCloneReader, sc_reader: &mut StructuredDataReader<'_>, ) -> *mut JSObject { let mut name_space: u32 = 0; let mut index: u32 = 0; unsafe { assert!(JS_ReadUint32Pair( r, &mut name_space as *mut u32, &mut index as *mut u32 )); } let storage_key = StorageKey { index, name_space }; // 1. Re-build the key for the storage location // of the serialized object. let id: NamespaceIndex<T::Index> = storage_key.into(); // 2. Get the transferred object from its storage, using the key. let objects = T::serialized_storage(StructuredData::Reader(sc_reader)); let objects_map = objects .as_mut() .expect("The SC holder does not have any relevant objects"); let serialized = objects_map .remove(&id) .expect("No object to be deserialized found."); if objects_map.is_empty() { *objects = None; } if let Ok(obj) = T::deserialize(cx, owner, serialized) { let reflector = obj.reflector().get_jsobject().get(); sc_reader.roots.push(Heap::boxed(reflector)); return reflector; } warn!("Reading structured data failed in {:?}.", owner.get_url()); ptr::null_mut() } unsafe fn write_object<T: Serializable>( no_gc: &NoGC, interface: SerializableInterface, owner: &GlobalScope, object: &T, w: *mut JSStructuredCloneWriter, sc_writer: &mut StructuredDataWriter, ) -> bool { if let Ok((new_id, serialized)) = object.serialize(no_gc) { let objects = T::serialized_storage(StructuredData::Writer(sc_writer)) .get_or_insert(FxHashMap::default()); objects.insert(new_id, serialized); let storage_key = StorageKey::new(new_id); unsafe { assert!(JS_WriteUint32Pair( w, StructuredCloneTags::from(interface) as u32, 0 )); assert!(JS_WriteUint32Pair( w, storage_key.name_space, storage_key.index )); } return true; } warn!("Writing structured data failed in {:?}.", owner.get_url()); false } unsafe extern "C" fn read_callback( cx: *mut RawJSContext, r: *mut JSStructuredCloneReader, _policy: *const CloneDataPolicy, 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" ); // SAFETY: it is safe to construct a JSContext from engine hook. let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) }; let cx = &mut cx; let sc_reader = unsafe { &mut *(closure as *mut StructuredDataReader<'_>) }; let mut realm = CurrentRealm::assert(cx); let global = GlobalScope::from_current_realm(&mut realm); for serializable in SerializableInterface::iter() { if tag == StructuredCloneTags::from(serializable) as u32 { let reader = reader_for_type(serializable); return unsafe { reader(cx, &global, r, sc_reader) }; } } ptr::null_mut() } enum OperationError { InterfaceDoesNotMatch, Exception(Error), } unsafe fn try_serialize<T: Serializable + IDLInterface>( cx: &mut JSContext, val: SerializableInterface, object: RawHandleObject, global: &GlobalScope, w: *mut JSStructuredCloneWriter, writer: &mut StructuredDataWriter, ) -> Result<bool, OperationError> { let object = unsafe { root_from_object::<T>(cx, *object) }; if let Ok(obj) = object { return unsafe { Ok(write_object(cx.no_gc(), val, global, &*obj, w, writer)) }; } Err(OperationError::InterfaceDoesNotMatch) } type SerializeOperation = unsafe fn( &mut JSContext, SerializableInterface, RawHandleObject, &GlobalScope, *mut JSStructuredCloneWriter, &mut StructuredDataWriter, ) -> Result<bool, OperationError>; fn serialize_for_type(val: SerializableInterface) -> SerializeOperation { match val { SerializableInterface::File => try_serialize::<File>, SerializableInterface::FileList => try_serialize::<FileList>, SerializableInterface::Blob => try_serialize::<Blob>, SerializableInterface::DomPoint => try_serialize::<DOMPoint>, SerializableInterface::DomPointReadOnly => try_serialize::<DOMPointReadOnly>, SerializableInterface::DomRect => try_serialize::<DOMRect>, SerializableInterface::DomRectReadOnly => try_serialize::<DOMRectReadOnly>, SerializableInterface::DomQuad => try_serialize::<DOMQuad>, SerializableInterface::DomMatrix => try_serialize::<DOMMatrix>, SerializableInterface::DomMatrixReadOnly => try_serialize::<DOMMatrixReadOnly>, SerializableInterface::DomException => try_serialize::<DOMException>, SerializableInterface::ImageBitmap => try_serialize::<ImageBitmap>, SerializableInterface::QuotaExceededError => try_serialize::<QuotaExceededError>, SerializableInterface::ImageData => try_serialize::<ImageData>, SerializableInterface::CryptoKey => try_serialize::<CryptoKey>, } } unsafe extern "C" fn write_callback( cx: *mut RawJSContext, w: *mut JSStructuredCloneWriter, obj: RawHandleObject, _same_process_scope_required: *mut bool, closure: *mut raw::c_void, ) -> bool { // SAFETY: it is safe to construct a JSContext from engine hook. let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) }; let cx = &mut cx; let sc_writer = unsafe { &mut *(closure as *mut StructuredDataWriter) }; let mut realm = CurrentRealm::assert(cx); let global = GlobalScope::from_current_realm(&mut realm); for serializable in SerializableInterface::iter() { let serializer = serialize_for_type(serializable); if let Ok(result) = unsafe { serializer(cx, serializable, obj, &global, w, sc_writer) } { return result; } } false } fn receiver_for_type( val: TransferrableInterface, ) -> fn( &mut JSContext, &GlobalScope, &mut StructuredDataReader<'_>, u64, RawMutableHandleObject, ) -> Result<(), ()> { match val { TransferrableInterface::ImageBitmap => receive_object::<ImageBitmap>, TransferrableInterface::MessagePort => receive_object::<MessagePort>, TransferrableInterface::OffscreenCanvas => receive_object::<OffscreenCanvas>, TransferrableInterface::ReadableStream => receive_object::<ReadableStream>, TransferrableInterface::WritableStream => receive_object::<WritableStream>, TransferrableInterface::TransformStream => receive_object::<TransformStream>, } } fn receive_object<T: Transferable>( cx: &mut JSContext, owner: &GlobalScope, sc_reader: &mut StructuredDataReader<'_>, extra_data: u64, return_object: RawMutableHandleObject, ) -> Result<(), ()> { // 1. Re-build the key for the storage location // of the transferred object. let big: [u8; 8] = extra_data.to_ne_bytes(); let (name_space, index) = big.split_at(4); let namespace_id = PipelineNamespaceId(u32::from_ne_bytes( name_space .try_into() .expect("name_space to be a slice of four."), )); let id: NamespaceIndex<T::Index> = NamespaceIndex { namespace_id, index: Index::new(u32::from_ne_bytes( index.try_into().expect("index to be a slice of four."), )) .expect("Index to be non-zero"), }; // 2. Get the transferred object from its storage, using the key. let storage = T::serialized_storage(StructuredData::Reader(sc_reader)); let serialized = if let Some(objects) = storage.as_mut() { let object = objects.remove(&id).expect("Transferred port to be stored"); if objects.is_empty() { *storage = None; } object } else { panic!( "An interface was transfer-received, yet the SC holder does not have any serialized objects" ); }; let Ok(received) = T::transfer_receive(cx, owner, id, serialized) else { return Err(()); }; return_object.set(received.reflector().rootable().get()); sc_reader.roots.push(Heap::boxed(return_object.get())); Ok(()) } unsafe extern "C" fn read_transfer_callback( cx: *mut RawJSContext, _r: *mut JSStructuredCloneReader, _policy: *const CloneDataPolicy, tag: u32, _content: *mut raw::c_void, extra_data: u64, closure: *mut raw::c_void, return_object: RawMutableHandleObject, ) -> bool { let sc_reader = unsafe { &mut *(closure as *mut StructuredDataReader<'_>) }; let mut cx = unsafe { // This is safe because we are in SM hook JSContext::from_ptr( NonNull::new(cx).expect("JSContext pointer should not be null in SM hook"), ) }; let mut realm = CurrentRealm::assert(&mut cx); let owner = GlobalScope::from_current_realm(&mut realm); for transferrable in TransferrableInterface::iter() { if tag == StructuredCloneTags::from(transferrable) as u32 { let transfer_receiver = receiver_for_type(transferrable); if transfer_receiver(&mut realm, &owner, sc_reader, extra_data, return_object).is_ok() { return true; } } } false } unsafe fn try_transfer<T: Transferable + IDLInterface>( interface: TransferrableInterface, obj: RawHandleObject, cx: &mut JSContext, sc_writer: &mut StructuredDataWriter, tag: *mut u32, ownership: *mut TransferableOwnership, extra_data: *mut u64, ) -> Result<(), OperationError> { let object = unsafe { root_from_object::<T>(cx, *obj) }; let Ok(object) = object else { return Err(OperationError::InterfaceDoesNotMatch); }; unsafe { *tag = StructuredCloneTags::from(interface) as u32 }; unsafe { *ownership = TransferableOwnership::SCTAG_TMO_CUSTOM }; let (id, object) = object.transfer(cx).map_err(OperationError::Exception)?; // 2. Store the transferred object at a given key. let objects = T::serialized_storage(StructuredData::Writer(sc_writer)) .get_or_insert(FxHashMap::default()); objects.insert(id, object); let index = id.index.0.get(); let mut big: [u8; 8] = [0; 8]; let name_space = id.namespace_id.0.to_ne_bytes(); let index = index.to_ne_bytes(); let (left, right) = big.split_at_mut(4); left.copy_from_slice(&name_space); right.copy_from_slice(&index); // 3. Return a u64 representation of the key where the object is stored. unsafe { *extra_data = u64::from_ne_bytes(big) }; Ok(()) } type TransferOperation = unsafe fn( TransferrableInterface, RawHandleObject, &mut JSContext, &mut StructuredDataWriter, *mut u32, *mut TransferableOwnership, *mut u64, ) -> Result<(), OperationError>; fn transfer_for_type(val: TransferrableInterface) -> TransferOperation { match val { TransferrableInterface::ImageBitmap => try_transfer::<ImageBitmap>, TransferrableInterface::MessagePort => try_transfer::<MessagePort>, TransferrableInterface::OffscreenCanvas => try_transfer::<OffscreenCanvas>, TransferrableInterface::ReadableStream => try_transfer::<ReadableStream>, TransferrableInterface::WritableStream => try_transfer::<WritableStream>, TransferrableInterface::TransformStream => try_transfer::<TransformStream>, } } /// <https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer> unsafe extern "C" fn write_transfer_callback( cx: *mut RawJSContext, obj: RawHandleObject, closure: *mut raw::c_void, tag: *mut u32, ownership: *mut TransferableOwnership, _content: *mut *mut raw::c_void, extra_data: *mut u64, ) -> bool { let sc_writer = unsafe { &mut *(closure as *mut StructuredDataWriter) }; let mut cx = unsafe { // This is safe because we are in SM hook JSContext::from_ptr( NonNull::new(cx).expect("JSContext pointer should not be null in SM hook"), ) }; for transferable in TransferrableInterface::iter() { let try_transfer = transfer_for_type(transferable); let transfer_result = unsafe { try_transfer( transferable, obj, &mut cx, sc_writer, tag, ownership, extra_data, ) }; match transfer_result { Err(error) => match error { OperationError::InterfaceDoesNotMatch => {}, OperationError::Exception(error) => { sc_writer.error = Some(error); return false; }, }, Ok(..) => return true, } } 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 fn can_transfer_for_type( cx: &mut JSContext, transferable: TransferrableInterface, obj: RawHandleObject, ) -> Result<bool, ()> { unsafe fn can_transfer<T: Transferable + IDLInterface>( cx: &mut JSContext, obj: RawHandleObject, ) -> Result<bool, ()> { unsafe { root_from_object::<T>(cx, *obj).map(|o| Transferable::can_transfer(&*o)) } } unsafe { match transferable { TransferrableInterface::ImageBitmap => can_transfer::<ImageBitmap>(cx, obj), TransferrableInterface::MessagePort => can_transfer::<MessagePort>(cx, obj), TransferrableInterface::OffscreenCanvas => can_transfer::<OffscreenCanvas>(cx, obj), TransferrableInterface::ReadableStream => can_transfer::<ReadableStream>(cx, obj), TransferrableInterface::WritableStream => can_transfer::<WritableStream>(cx, obj), TransferrableInterface::TransformStream => can_transfer::<TransformStream>(cx, obj), } } } unsafe extern "C" fn can_transfer_callback( cx: *mut RawJSContext, obj: RawHandleObject, _same_process_scope_required: *mut bool, _closure: *mut raw::c_void, ) -> bool { // SAFETY: it is safe to construct a JSContext from engine hook. let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) }; let cx = &mut cx; for transferable in TransferrableInterface::iter() { let can_transfer = unsafe { can_transfer_for_type(cx, transferable, obj) }; if let Ok(can_transfer) = can_transfer { return can_transfer; } } false } unsafe extern "C" fn report_error_callback( _cx: *mut RawJSContext, _errorid: u32, closure: *mut raw::c_void, error_message: *const ::std::os::raw::c_char, ) { let msg_result = unsafe { CStr::from_ptr(error_message).to_str().map(str::to_string) }; if let Ok(msg) = msg_result { let error = unsafe { &mut *(closure as *mut Option<Error>) }; if error.is_none() { *error = Some(Error::DataClone(Some(msg))); } } } unsafe extern "C" fn sab_cloned_callback( _cx: *mut RawJSContext, _receiving: bool, _closure: *mut ::std::os::raw::c_void, ) -> bool { false } 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), canTransfer: Some(can_transfer_callback), sabCloned: Some(sab_cloned_callback), }; pub(crate) enum StructuredData<'a, 'b> { Reader(&'a mut StructuredDataReader<'b>), Writer(&'a mut StructuredDataWriter), } /// Reader and writer structs for results from, and inputs to, structured-data read/write operations. /// <https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data> #[repr(C)] pub(crate) struct StructuredDataReader<'a> { /// A error record. error: Option<Error>, /// Rooted copies of every deserialized object to ensure they are not garbage collected. roots: RootedVec<'a, Box<Heap<*mut JSObject>>>, /// A map of port implementations, /// used as part of the "transfer-receiving" steps of ports, /// to produce the DOM ports stored in `message_ports` above. pub(crate) port_impls: Option<FxHashMap<MessagePortId, MessagePortImpl>>, /// A map of transform stream implementations, pub(crate) transform_streams_port_impls: Option<FxHashMap<MessagePortId, TransformStreamData>>, /// A map of blob implementations, /// used as part of the "deserialize" steps of blobs, /// to produce the DOM blobs stored in `blobs` above. pub(crate) blob_impls: Option<FxHashMap<BlobId, BlobImpl>>, /// A map of serialized files. pub(crate) files: Option<FxHashMap<FileId, SerializableFile>>, /// A map of serialized file lists. pub(crate) file_lists: Option<FxHashMap<FileListId, SerializableFileList>>, /// A map of serialized points. pub(crate) points: Option<FxHashMap<DomPointId, DomPoint>>, /// A map of serialized rects. pub(crate) rects: Option<FxHashMap<DomRectId, DomRect>>, /// A map of serialized quads. pub(crate) quads: Option<FxHashMap<DomQuadId, DomQuad>>, /// A map of serialized matrices. pub(crate) matrices: Option<FxHashMap<DomMatrixId, DomMatrix>>, /// A map of serialized exceptions. pub(crate) exceptions: Option<FxHashMap<DomExceptionId, DomException>>, /// A map of serialized quota exceeded errors. pub(crate) quota_exceeded_errors: Option<FxHashMap<QuotaExceededErrorId, SerializableQuotaExceededError>>, // A map of serialized image bitmaps. pub(crate) image_bitmaps: Option<FxHashMap<ImageBitmapId, SerializableImageBitmap>>, /// A map of transferred image bitmaps. pub(crate) transferred_image_bitmaps: Option<FxHashMap<ImageBitmapId, SerializableImageBitmap>>, /// A map of transferred offscreen canvases. pub(crate) offscreen_canvases: Option<FxHashMap<OffscreenCanvasId, TransferableOffscreenCanvas>>, // A map of serialized image data. pub(crate) image_data: Option<FxHashMap<ImageDataId, SerializableImageData>>, // A map of serialized crypto keys. pub(crate) crypto_keys: Option<FxHashMap<CryptoKeyId, SerializableCryptoKey>>, } /// A data holder for transferred and serialized objects. #[derive(Default)] #[repr(C)] pub(crate) struct StructuredDataWriter { /// Error record. pub(crate) error: Option<Error>, /// Transferred ports. pub(crate) ports: Option<FxHashMap<MessagePortId, MessagePortImpl>>, /// Transferred transform streams. pub(crate) transform_streams_port: Option<FxHashMap<MessagePortId, TransformStreamData>>, /// Serialized points. pub(crate) points: Option<FxHashMap<DomPointId, DomPoint>>, /// Serialized rects. pub(crate) rects: Option<FxHashMap<DomRectId, DomRect>>, /// Serialized quads. pub(crate) quads: Option<FxHashMap<DomQuadId, DomQuad>>, /// Serialized matrices. pub(crate) matrices: Option<FxHashMap<DomMatrixId, DomMatrix>>, /// Serialized exceptions. pub(crate) exceptions: Option<FxHashMap<DomExceptionId, DomException>>, /// Serialized quota exceeded errors. pub(crate) quota_exceeded_errors: Option<FxHashMap<QuotaExceededErrorId, SerializableQuotaExceededError>>, /// Serialized blobs. pub(crate) blobs: Option<FxHashMap<BlobId, BlobImpl>>, /// Serialized files. pub(crate) files: Option<FxHashMap<FileId, SerializableFile>>, /// Serialized file lists. pub(crate) file_lists: Option<FxHashMap<FileListId, SerializableFileList>>, /// Serialized image bitmaps. pub(crate) image_bitmaps: Option<FxHashMap<ImageBitmapId, SerializableImageBitmap>>, /// Transferred image bitmaps. pub(crate) transferred_image_bitmaps: Option<FxHashMap<ImageBitmapId, SerializableImageBitmap>>, /// Transferred offscreen canvases. pub(crate) offscreen_canvases: Option<FxHashMap<OffscreenCanvasId, TransferableOffscreenCanvas>>, // A map of serialized image data. pub(crate) image_data: Option<FxHashMap<ImageDataId, SerializableImageData>>, // A map of serialized crypto keys. pub(crate) crypto_keys: Option<FxHashMap<CryptoKeyId, SerializableCryptoKey>>, } /// Writes a structured clone. Returns a `DataClone` error if that fails. pub(crate) fn write( cx: &mut JSContext, message: HandleValue, transfer: Option<CustomAutoRooterGuard<Vec<*mut JSObject>>>, ) -> Fallible<StructuredSerializedData> { unsafe { rooted!(&in(cx) let mut val = UndefinedValue()); if let Some(transfer) = transfer { transfer.safe_to_jsval(cx, val.handle_mut()); } let mut sc_writer = StructuredDataWriter::default(); let sc_writer_ptr = &mut sc_writer as *mut _; let scbuf = JSAutoStructuredCloneBufferWrapper::new( StructuredCloneScope::DifferentProcess, &STRUCTURED_CLONE_CALLBACKS, ); let scdata = &mut ((*scbuf.as_raw_ptr()).data_); let policy = CloneDataPolicy { allowIntraClusterClonableSharedObjects_: false, allowSharedMemoryObjects_: false, }; let result = JS_WriteStructuredClone( cx, message, scdata, StructuredCloneScope::DifferentProcess, &policy, &STRUCTURED_CLONE_CALLBACKS, sc_writer_ptr as *mut raw::c_void, val.handle(), ); if !result { let error = if JS_IsExceptionPending(cx) { Error::JSFailed } else { sc_writer.error.unwrap_or(Error::DataClone(None)) }; return Err(error); } let nbytes = GetLengthOfJSStructuredCloneData(scdata); let mut data = Vec::with_capacity(nbytes); CopyJSStructuredCloneData(scdata, data.as_mut_ptr()); data.set_len(nbytes); let data = StructuredSerializedData { serialized: data, ports: sc_writer.ports.take(), transform_streams: sc_writer.transform_streams_port.take(), points: sc_writer.points.take(), rects: sc_writer.rects.take(), quads: sc_writer.quads.take(), matrices: sc_writer.matrices.take(), exceptions: sc_writer.exceptions.take(), quota_exceeded_errors: sc_writer.quota_exceeded_errors.take(), blobs: sc_writer.blobs.take(), files: sc_writer.files.take(), file_lists: sc_writer.file_lists.take(), image_bitmaps: sc_writer.image_bitmaps.take(), transferred_image_bitmaps: sc_writer.transferred_image_bitmaps.take(), offscreen_canvases: sc_writer.offscreen_canvases.take(), image_data: sc_writer.image_data.take(), crypto_keys: sc_writer.crypto_keys.take(), }; Ok(data) } } /// Read structured serialized data, possibly containing transferred objects. /// Returns a vec of rooted transfer-received ports, or an error. pub(crate) fn read( cx: &mut JSContext, global: &GlobalScope, mut data: StructuredSerializedData, rval: MutableHandleValue, ) -> Fallible<Vec<DomRoot<MessagePort>>> { let mut realm = enter_auto_realm(cx, global); let cx = &mut realm.current_realm(); rooted_vec!(let mut roots); let mut sc_reader = StructuredDataReader { error: None, roots, port_impls: data.ports.take(), transform_streams_port_impls: data.transform_streams.take(), blob_impls: data.blobs.take(), files: data.files.take(), file_lists: data.file_lists.take(), points: data.points.take(), rects: data.rects.take(), quads: data.quads.take(), matrices: data.matrices.take(), exceptions: data.exceptions.take(), quota_exceeded_errors: data.quota_exceeded_errors.take(), image_bitmaps: data.image_bitmaps.take(), transferred_image_bitmaps: data.transferred_image_bitmaps.take(), offscreen_canvases: data.offscreen_canvases.take(), image_data: data.image_data.take(), crypto_keys: data.crypto_keys.take(), }; let sc_reader_ptr = &mut sc_reader as *mut _; unsafe { let scbuf = JSAutoStructuredCloneBufferWrapper::new( StructuredCloneScope::DifferentProcess, &STRUCTURED_CLONE_CALLBACKS, ); let scdata = &mut ((*scbuf.as_raw_ptr()).data_); WriteBytesToJSStructuredCloneData( data.serialized.as_mut_ptr() as *const u8, data.serialized.len(), scdata, ); let result = JS_ReadStructuredClone( cx, scdata, JS_STRUCTURED_CLONE_VERSION, StructuredCloneScope::DifferentProcess, rval, &CloneDataPolicy { allowIntraClusterClonableSharedObjects_: false, allowSharedMemoryObjects_: false, }, &STRUCTURED_CLONE_CALLBACKS, sc_reader_ptr as *mut raw::c_void, ); if !result { let error = if JS_IsExceptionPending(cx) { Error::JSFailed } else { sc_reader.error.unwrap_or(Error::DataClone(None)) }; return Err(error); } let mut message_ports = vec![]; for reflector in sc_reader.roots.iter() { let Ok(message_port) = root_from_object::<MessagePort>(cx, reflector.get()) else { continue; }; message_ports.push(message_port); } // Any transfer-received port-impls should have been taken out. assert!(sc_reader.port_impls.is_none()); Ok(message_ports) } }