script/dom/bindings/
structuredclone.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! This module implements structured cloning, as defined by [HTML](https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data).
6
7use std::ffi::CStr;
8use std::os::raw;
9use std::ptr::{self, NonNull};
10
11use js::gc::RootedVec;
12use js::glue::{
13    CopyJSStructuredCloneData, GetLengthOfJSStructuredCloneData, WriteBytesToJSStructuredCloneData,
14};
15use js::jsapi::{
16    CloneDataPolicy, HandleObject as RawHandleObject, Heap, JS_IsExceptionPending,
17    JS_ReadUint32Pair, JS_STRUCTURED_CLONE_VERSION, JS_WriteUint32Pair, JSContext, JSObject,
18    JSStructuredCloneCallbacks, JSStructuredCloneReader, JSStructuredCloneWriter,
19    MutableHandleObject as RawMutableHandleObject, StructuredCloneScope, TransferableOwnership,
20};
21use js::jsval::UndefinedValue;
22use js::realm::CurrentRealm;
23use js::rust::wrappers::{JS_ReadStructuredClone, JS_WriteStructuredClone};
24use js::rust::{
25    CustomAutoRooterGuard, HandleValue, JSAutoStructuredCloneBufferWrapper, MutableHandleValue,
26};
27use rustc_hash::FxHashMap;
28use script_bindings::conversions::{IDLInterface, SafeToJSValConvertible};
29use servo_base::id::{
30    BlobId, DomExceptionId, DomMatrixId, DomPointId, DomQuadId, DomRectId, FileId, FileListId,
31    ImageBitmapId, ImageDataId, Index, MessagePortId, NamespaceIndex, OffscreenCanvasId,
32    PipelineNamespaceId, QuotaExceededErrorId,
33};
34use servo_constellation_traits::{
35    BlobImpl, DomException, DomMatrix, DomPoint, DomQuad, DomRect, MessagePortImpl,
36    Serializable as SerializableInterface, SerializableFile, SerializableFileList,
37    SerializableImageBitmap, SerializableImageData, SerializableQuotaExceededError,
38    StructuredSerializedData, TransferableOffscreenCanvas, Transferrable as TransferrableInterface,
39    TransformStreamData,
40};
41use strum::IntoEnumIterator;
42
43use crate::dom::bindings::conversions::root_from_object;
44use crate::dom::bindings::error::{Error, Fallible};
45use crate::dom::bindings::root::DomRoot;
46use crate::dom::bindings::serializable::{Serializable, StorageKey};
47use crate::dom::bindings::transferable::Transferable;
48use crate::dom::blob::Blob;
49use crate::dom::dompoint::DOMPoint;
50use crate::dom::dompointreadonly::DOMPointReadOnly;
51use crate::dom::file::File;
52use crate::dom::filelist::FileList;
53use crate::dom::globalscope::GlobalScope;
54use crate::dom::imagebitmap::ImageBitmap;
55use crate::dom::imagedata::ImageData;
56use crate::dom::messageport::MessagePort;
57use crate::dom::offscreencanvas::OffscreenCanvas;
58use crate::dom::stream::readablestream::ReadableStream;
59use crate::dom::stream::writablestream::WritableStream;
60use crate::dom::types::{
61    DOMException, DOMMatrix, DOMMatrixReadOnly, DOMQuad, DOMRect, DOMRectReadOnly,
62    QuotaExceededError, TransformStream,
63};
64use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
65use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
66
67// TODO: Should we add Min and Max const to https://github.com/servo/rust-mozjs/blob/master/src/consts.rs?
68// TODO: Determine for sure which value Min and Max should have.
69// NOTE: Current values found at https://dxr.mozilla.org/mozilla-central/
70// rev/ff04d410e74b69acfab17ef7e73e7397602d5a68/js/public/StructuredClone.h#323
71#[repr(u32)]
72pub(super) enum StructuredCloneTags {
73    /// To support additional types, add new tags with values incremented from the last one before Max.
74    Min = 0xFFFF8000,
75    DomBlob = 0xFFFF8001,
76    DomFile = 0xFFFF8002,
77    DomFileList = 0xFFFF8003,
78    MessagePort = 0xFFFF8004,
79    Principals = 0xFFFF8005,
80    DomPointReadOnly = 0xFFFF8006,
81    DomPoint = 0xFFFF8007,
82    ReadableStream = 0xFFFF8008,
83    DomException = 0xFFFF8009,
84    WritableStream = 0xFFFF800A,
85    TransformStream = 0xFFFF800B,
86    ImageBitmap = 0xFFFF800C,
87    OffscreenCanvas = 0xFFFF800D,
88    QuotaExceededError = 0xFFFF800E,
89    DomRect = 0xFFFF800F,
90    DomRectReadOnly = 0xFFFF8010,
91    DomQuad = 0xFFFF8011,
92    DomMatrix = 0xFFFF8012,
93    DomMatrixReadOnly = 0xFFFF8013,
94    ImageData = 0xFFFF8014,
95    Max = 0xFFFFFFFF,
96}
97
98impl From<SerializableInterface> for StructuredCloneTags {
99    fn from(v: SerializableInterface) -> Self {
100        match v {
101            SerializableInterface::File => StructuredCloneTags::DomFile,
102            SerializableInterface::FileList => StructuredCloneTags::DomFileList,
103            SerializableInterface::Blob => StructuredCloneTags::DomBlob,
104            SerializableInterface::DomPoint => StructuredCloneTags::DomPoint,
105            SerializableInterface::DomPointReadOnly => StructuredCloneTags::DomPointReadOnly,
106            SerializableInterface::DomRect => StructuredCloneTags::DomRect,
107            SerializableInterface::DomRectReadOnly => StructuredCloneTags::DomRectReadOnly,
108            SerializableInterface::DomQuad => StructuredCloneTags::DomQuad,
109            SerializableInterface::DomMatrix => StructuredCloneTags::DomMatrix,
110            SerializableInterface::DomMatrixReadOnly => StructuredCloneTags::DomMatrixReadOnly,
111            SerializableInterface::DomException => StructuredCloneTags::DomException,
112            SerializableInterface::ImageBitmap => StructuredCloneTags::ImageBitmap,
113            SerializableInterface::QuotaExceededError => StructuredCloneTags::QuotaExceededError,
114            SerializableInterface::ImageData => StructuredCloneTags::ImageData,
115        }
116    }
117}
118
119impl From<TransferrableInterface> for StructuredCloneTags {
120    fn from(v: TransferrableInterface) -> Self {
121        match v {
122            TransferrableInterface::ImageBitmap => StructuredCloneTags::ImageBitmap,
123            TransferrableInterface::MessagePort => StructuredCloneTags::MessagePort,
124            TransferrableInterface::OffscreenCanvas => StructuredCloneTags::OffscreenCanvas,
125            TransferrableInterface::ReadableStream => StructuredCloneTags::ReadableStream,
126            TransferrableInterface::WritableStream => StructuredCloneTags::WritableStream,
127            TransferrableInterface::TransformStream => StructuredCloneTags::TransformStream,
128        }
129    }
130}
131
132fn reader_for_type(
133    val: SerializableInterface,
134) -> unsafe fn(
135    &GlobalScope,
136    *mut JSStructuredCloneReader,
137    &mut StructuredDataReader<'_>,
138    CanGc,
139) -> *mut JSObject {
140    match val {
141        SerializableInterface::File => read_object::<File>,
142        SerializableInterface::FileList => read_object::<FileList>,
143        SerializableInterface::Blob => read_object::<Blob>,
144        SerializableInterface::DomPoint => read_object::<DOMPoint>,
145        SerializableInterface::DomPointReadOnly => read_object::<DOMPointReadOnly>,
146        SerializableInterface::DomRect => read_object::<DOMRect>,
147        SerializableInterface::DomRectReadOnly => read_object::<DOMRectReadOnly>,
148        SerializableInterface::DomQuad => read_object::<DOMQuad>,
149        SerializableInterface::DomMatrix => read_object::<DOMMatrix>,
150        SerializableInterface::DomMatrixReadOnly => read_object::<DOMMatrixReadOnly>,
151        SerializableInterface::DomException => read_object::<DOMException>,
152        SerializableInterface::ImageBitmap => read_object::<ImageBitmap>,
153        SerializableInterface::QuotaExceededError => read_object::<QuotaExceededError>,
154        SerializableInterface::ImageData => read_object::<ImageData>,
155    }
156}
157
158unsafe fn read_object<T: Serializable>(
159    owner: &GlobalScope,
160    r: *mut JSStructuredCloneReader,
161    sc_reader: &mut StructuredDataReader<'_>,
162    can_gc: CanGc,
163) -> *mut JSObject {
164    let mut name_space: u32 = 0;
165    let mut index: u32 = 0;
166    unsafe {
167        assert!(JS_ReadUint32Pair(
168            r,
169            &mut name_space as *mut u32,
170            &mut index as *mut u32
171        ));
172    }
173    let storage_key = StorageKey { index, name_space };
174
175    // 1. Re-build the key for the storage location
176    // of the serialized object.
177    let id: NamespaceIndex<T::Index> = storage_key.into();
178
179    // 2. Get the transferred object from its storage, using the key.
180    let objects = T::serialized_storage(StructuredData::Reader(sc_reader));
181    let objects_map = objects
182        .as_mut()
183        .expect("The SC holder does not have any relevant objects");
184    let serialized = objects_map
185        .remove(&id)
186        .expect("No object to be deserialized found.");
187    if objects_map.is_empty() {
188        *objects = None;
189    }
190
191    if let Ok(obj) = T::deserialize(owner, serialized, can_gc) {
192        let reflector = obj.reflector().get_jsobject().get();
193        sc_reader.roots.push(Heap::boxed(reflector));
194        return reflector;
195    }
196    warn!("Reading structured data failed in {:?}.", owner.get_url());
197    ptr::null_mut()
198}
199
200unsafe fn write_object<T: Serializable>(
201    interface: SerializableInterface,
202    owner: &GlobalScope,
203    object: &T,
204    w: *mut JSStructuredCloneWriter,
205    sc_writer: &mut StructuredDataWriter,
206) -> bool {
207    if let Ok((new_id, serialized)) = object.serialize() {
208        let objects = T::serialized_storage(StructuredData::Writer(sc_writer))
209            .get_or_insert(FxHashMap::default());
210        objects.insert(new_id, serialized);
211        let storage_key = StorageKey::new(new_id);
212
213        unsafe {
214            assert!(JS_WriteUint32Pair(
215                w,
216                StructuredCloneTags::from(interface) as u32,
217                0
218            ));
219            assert!(JS_WriteUint32Pair(
220                w,
221                storage_key.name_space,
222                storage_key.index
223            ));
224        }
225        return true;
226    }
227    warn!("Writing structured data failed in {:?}.", owner.get_url());
228    false
229}
230
231unsafe extern "C" fn read_callback(
232    cx: *mut JSContext,
233    r: *mut JSStructuredCloneReader,
234    _policy: *const CloneDataPolicy,
235    tag: u32,
236    _data: u32,
237    closure: *mut raw::c_void,
238) -> *mut JSObject {
239    assert!(
240        tag < StructuredCloneTags::Max as u32,
241        "tag should be lower than StructuredCloneTags::Max"
242    );
243    assert!(
244        tag > StructuredCloneTags::Min as u32,
245        "tag should be higher than StructuredCloneTags::Min"
246    );
247
248    unsafe {
249        let sc_reader = &mut *(closure as *mut StructuredDataReader<'_>);
250        let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx));
251        let global = GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof));
252        for serializable in SerializableInterface::iter() {
253            if tag == StructuredCloneTags::from(serializable) as u32 {
254                let reader = reader_for_type(serializable);
255                return reader(&global, r, sc_reader, CanGc::note());
256            }
257        }
258    }
259
260    ptr::null_mut()
261}
262
263enum OperationError {
264    InterfaceDoesNotMatch,
265    Exception(Error),
266}
267
268unsafe fn try_serialize<T: Serializable + IDLInterface>(
269    val: SerializableInterface,
270    cx: *mut JSContext,
271    object: RawHandleObject,
272    global: &GlobalScope,
273    w: *mut JSStructuredCloneWriter,
274    writer: &mut StructuredDataWriter,
275) -> Result<bool, OperationError> {
276    let object = unsafe { root_from_object::<T>(*object, cx) };
277    if let Ok(obj) = object {
278        return unsafe { Ok(write_object(val, global, &*obj, w, writer)) };
279    }
280    Err(OperationError::InterfaceDoesNotMatch)
281}
282
283type SerializeOperation = unsafe fn(
284    SerializableInterface,
285    *mut JSContext,
286    RawHandleObject,
287    &GlobalScope,
288    *mut JSStructuredCloneWriter,
289    &mut StructuredDataWriter,
290) -> Result<bool, OperationError>;
291
292fn serialize_for_type(val: SerializableInterface) -> SerializeOperation {
293    match val {
294        SerializableInterface::File => try_serialize::<File>,
295        SerializableInterface::FileList => try_serialize::<FileList>,
296        SerializableInterface::Blob => try_serialize::<Blob>,
297        SerializableInterface::DomPoint => try_serialize::<DOMPoint>,
298        SerializableInterface::DomPointReadOnly => try_serialize::<DOMPointReadOnly>,
299        SerializableInterface::DomRect => try_serialize::<DOMRect>,
300        SerializableInterface::DomRectReadOnly => try_serialize::<DOMRectReadOnly>,
301        SerializableInterface::DomQuad => try_serialize::<DOMQuad>,
302        SerializableInterface::DomMatrix => try_serialize::<DOMMatrix>,
303        SerializableInterface::DomMatrixReadOnly => try_serialize::<DOMMatrixReadOnly>,
304        SerializableInterface::DomException => try_serialize::<DOMException>,
305        SerializableInterface::ImageBitmap => try_serialize::<ImageBitmap>,
306        SerializableInterface::QuotaExceededError => try_serialize::<QuotaExceededError>,
307        SerializableInterface::ImageData => try_serialize::<ImageData>,
308    }
309}
310
311unsafe extern "C" fn write_callback(
312    cx: *mut JSContext,
313    w: *mut JSStructuredCloneWriter,
314    obj: RawHandleObject,
315    _same_process_scope_required: *mut bool,
316    closure: *mut raw::c_void,
317) -> bool {
318    unsafe {
319        let sc_writer = &mut *(closure as *mut StructuredDataWriter);
320        let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx));
321        let global = GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof));
322        for serializable in SerializableInterface::iter() {
323            let serializer = serialize_for_type(serializable);
324            if let Ok(result) = serializer(serializable, cx, obj, &global, w, sc_writer) {
325                return result;
326            }
327        }
328    }
329    false
330}
331
332fn receiver_for_type(
333    val: TransferrableInterface,
334) -> fn(
335    &mut js::context::JSContext,
336    &GlobalScope,
337    &mut StructuredDataReader<'_>,
338    u64,
339    RawMutableHandleObject,
340) -> Result<(), ()> {
341    match val {
342        TransferrableInterface::ImageBitmap => receive_object::<ImageBitmap>,
343        TransferrableInterface::MessagePort => receive_object::<MessagePort>,
344        TransferrableInterface::OffscreenCanvas => receive_object::<OffscreenCanvas>,
345        TransferrableInterface::ReadableStream => receive_object::<ReadableStream>,
346        TransferrableInterface::WritableStream => receive_object::<WritableStream>,
347        TransferrableInterface::TransformStream => receive_object::<TransformStream>,
348    }
349}
350
351fn receive_object<T: Transferable>(
352    cx: &mut js::context::JSContext,
353    owner: &GlobalScope,
354    sc_reader: &mut StructuredDataReader<'_>,
355    extra_data: u64,
356    return_object: RawMutableHandleObject,
357) -> Result<(), ()> {
358    // 1. Re-build the key for the storage location
359    // of the transferred object.
360    let big: [u8; 8] = extra_data.to_ne_bytes();
361    let (name_space, index) = big.split_at(4);
362
363    let namespace_id = PipelineNamespaceId(u32::from_ne_bytes(
364        name_space
365            .try_into()
366            .expect("name_space to be a slice of four."),
367    ));
368    let id: NamespaceIndex<T::Index> = NamespaceIndex {
369        namespace_id,
370        index: Index::new(u32::from_ne_bytes(
371            index.try_into().expect("index to be a slice of four."),
372        ))
373        .expect("Index to be non-zero"),
374    };
375
376    // 2. Get the transferred object from its storage, using the key.
377    let storage = T::serialized_storage(StructuredData::Reader(sc_reader));
378    let serialized = if let Some(objects) = storage.as_mut() {
379        let object = objects.remove(&id).expect("Transferred port to be stored");
380        if objects.is_empty() {
381            *storage = None;
382        }
383        object
384    } else {
385        panic!(
386            "An interface was transfer-received, yet the SC holder does not have any serialized objects"
387        );
388    };
389
390    let Ok(received) = T::transfer_receive(cx, owner, id, serialized) else {
391        return Err(());
392    };
393    return_object.set(received.reflector().rootable().get());
394    sc_reader.roots.push(Heap::boxed(return_object.get()));
395    Ok(())
396}
397
398unsafe extern "C" fn read_transfer_callback(
399    cx: *mut JSContext,
400    _r: *mut JSStructuredCloneReader,
401    _policy: *const CloneDataPolicy,
402    tag: u32,
403    _content: *mut raw::c_void,
404    extra_data: u64,
405    closure: *mut raw::c_void,
406    return_object: RawMutableHandleObject,
407) -> bool {
408    let sc_reader = unsafe { &mut *(closure as *mut StructuredDataReader<'_>) };
409    let mut cx = unsafe {
410        // This is safe because we are in SM hook
411        js::context::JSContext::from_ptr(
412            NonNull::new(cx).expect("JSContext pointer should not be null in SM hook"),
413        )
414    };
415    let mut cx = CurrentRealm::assert(&mut cx);
416    let owner = GlobalScope::from_current_realm(&cx);
417
418    for transferrable in TransferrableInterface::iter() {
419        if tag == StructuredCloneTags::from(transferrable) as u32 {
420            let transfer_receiver = receiver_for_type(transferrable);
421            if transfer_receiver(&mut cx, &owner, sc_reader, extra_data, return_object).is_ok() {
422                return true;
423            }
424        }
425    }
426    false
427}
428
429unsafe fn try_transfer<T: Transferable + IDLInterface>(
430    interface: TransferrableInterface,
431    obj: RawHandleObject,
432    cx: &mut js::context::JSContext,
433    sc_writer: &mut StructuredDataWriter,
434    tag: *mut u32,
435    ownership: *mut TransferableOwnership,
436    extra_data: *mut u64,
437) -> Result<(), OperationError> {
438    let object = unsafe { root_from_object::<T>(*obj, cx.raw_cx()) };
439    let Ok(object) = object else {
440        return Err(OperationError::InterfaceDoesNotMatch);
441    };
442
443    unsafe { *tag = StructuredCloneTags::from(interface) as u32 };
444    unsafe { *ownership = TransferableOwnership::SCTAG_TMO_CUSTOM };
445
446    let (id, object) = object.transfer(cx).map_err(OperationError::Exception)?;
447
448    // 2. Store the transferred object at a given key.
449    let objects = T::serialized_storage(StructuredData::Writer(sc_writer))
450        .get_or_insert(FxHashMap::default());
451    objects.insert(id, object);
452
453    let index = id.index.0.get();
454
455    let mut big: [u8; 8] = [0; 8];
456    let name_space = id.namespace_id.0.to_ne_bytes();
457    let index = index.to_ne_bytes();
458
459    let (left, right) = big.split_at_mut(4);
460    left.copy_from_slice(&name_space);
461    right.copy_from_slice(&index);
462
463    // 3. Return a u64 representation of the key where the object is stored.
464    unsafe { *extra_data = u64::from_ne_bytes(big) };
465    Ok(())
466}
467
468type TransferOperation = unsafe fn(
469    TransferrableInterface,
470    RawHandleObject,
471    &mut js::context::JSContext,
472    &mut StructuredDataWriter,
473    *mut u32,
474    *mut TransferableOwnership,
475    *mut u64,
476) -> Result<(), OperationError>;
477
478fn transfer_for_type(val: TransferrableInterface) -> TransferOperation {
479    match val {
480        TransferrableInterface::ImageBitmap => try_transfer::<ImageBitmap>,
481        TransferrableInterface::MessagePort => try_transfer::<MessagePort>,
482        TransferrableInterface::OffscreenCanvas => try_transfer::<OffscreenCanvas>,
483        TransferrableInterface::ReadableStream => try_transfer::<ReadableStream>,
484        TransferrableInterface::WritableStream => try_transfer::<WritableStream>,
485        TransferrableInterface::TransformStream => try_transfer::<TransformStream>,
486    }
487}
488
489/// <https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer>
490unsafe extern "C" fn write_transfer_callback(
491    cx: *mut JSContext,
492    obj: RawHandleObject,
493    closure: *mut raw::c_void,
494    tag: *mut u32,
495    ownership: *mut TransferableOwnership,
496    _content: *mut *mut raw::c_void,
497    extra_data: *mut u64,
498) -> bool {
499    let sc_writer = unsafe { &mut *(closure as *mut StructuredDataWriter) };
500    let mut cx = unsafe {
501        // This is safe because we are in SM hook
502        js::context::JSContext::from_ptr(
503            NonNull::new(cx).expect("JSContext pointer should not be null in SM hook"),
504        )
505    };
506    for transferable in TransferrableInterface::iter() {
507        let try_transfer = transfer_for_type(transferable);
508
509        let transfer_result = unsafe {
510            try_transfer(
511                transferable,
512                obj,
513                &mut cx,
514                sc_writer,
515                tag,
516                ownership,
517                extra_data,
518            )
519        };
520        match transfer_result {
521            Err(error) => match error {
522                OperationError::InterfaceDoesNotMatch => {},
523                OperationError::Exception(error) => {
524                    sc_writer.error = Some(error);
525                    return false;
526                },
527            },
528            Ok(..) => return true,
529        }
530    }
531
532    false
533}
534
535unsafe extern "C" fn free_transfer_callback(
536    _tag: u32,
537    _ownership: TransferableOwnership,
538    _content: *mut raw::c_void,
539    _extra_data: u64,
540    _closure: *mut raw::c_void,
541) {
542}
543
544unsafe fn can_transfer_for_type(
545    transferable: TransferrableInterface,
546    obj: RawHandleObject,
547    cx: *mut JSContext,
548) -> Result<bool, ()> {
549    unsafe fn can_transfer<T: Transferable + IDLInterface>(
550        obj: RawHandleObject,
551        cx: *mut JSContext,
552    ) -> Result<bool, ()> {
553        unsafe { root_from_object::<T>(*obj, cx).map(|o| Transferable::can_transfer(&*o)) }
554    }
555
556    unsafe {
557        match transferable {
558            TransferrableInterface::ImageBitmap => can_transfer::<ImageBitmap>(obj, cx),
559            TransferrableInterface::MessagePort => can_transfer::<MessagePort>(obj, cx),
560            TransferrableInterface::OffscreenCanvas => can_transfer::<OffscreenCanvas>(obj, cx),
561            TransferrableInterface::ReadableStream => can_transfer::<ReadableStream>(obj, cx),
562            TransferrableInterface::WritableStream => can_transfer::<WritableStream>(obj, cx),
563            TransferrableInterface::TransformStream => can_transfer::<TransformStream>(obj, cx),
564        }
565    }
566}
567
568unsafe extern "C" fn can_transfer_callback(
569    cx: *mut JSContext,
570    obj: RawHandleObject,
571    _same_process_scope_required: *mut bool,
572    _closure: *mut raw::c_void,
573) -> bool {
574    for transferable in TransferrableInterface::iter() {
575        let can_transfer = unsafe { can_transfer_for_type(transferable, obj, cx) };
576        if let Ok(can_transfer) = can_transfer {
577            return can_transfer;
578        }
579    }
580    false
581}
582
583unsafe extern "C" fn report_error_callback(
584    _cx: *mut JSContext,
585    _errorid: u32,
586    closure: *mut raw::c_void,
587    error_message: *const ::std::os::raw::c_char,
588) {
589    let msg_result = unsafe { CStr::from_ptr(error_message).to_str().map(str::to_string) };
590
591    if let Ok(msg) = msg_result {
592        let error = unsafe { &mut *(closure as *mut Option<Error>) };
593
594        if error.is_none() {
595            *error = Some(Error::DataClone(Some(msg)));
596        }
597    }
598}
599
600unsafe extern "C" fn sab_cloned_callback(
601    _cx: *mut JSContext,
602    _receiving: bool,
603    _closure: *mut ::std::os::raw::c_void,
604) -> bool {
605    false
606}
607
608static STRUCTURED_CLONE_CALLBACKS: JSStructuredCloneCallbacks = JSStructuredCloneCallbacks {
609    read: Some(read_callback),
610    write: Some(write_callback),
611    reportError: Some(report_error_callback),
612    readTransfer: Some(read_transfer_callback),
613    writeTransfer: Some(write_transfer_callback),
614    freeTransfer: Some(free_transfer_callback),
615    canTransfer: Some(can_transfer_callback),
616    sabCloned: Some(sab_cloned_callback),
617};
618
619pub(crate) enum StructuredData<'a, 'b> {
620    Reader(&'a mut StructuredDataReader<'b>),
621    Writer(&'a mut StructuredDataWriter),
622}
623
624/// Reader and writer structs for results from, and inputs to, structured-data read/write operations.
625/// <https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data>
626#[repr(C)]
627pub(crate) struct StructuredDataReader<'a> {
628    /// A error record.
629    error: Option<Error>,
630    /// Rooted copies of every deserialized object to ensure they are not garbage collected.
631    roots: RootedVec<'a, Box<Heap<*mut JSObject>>>,
632    /// A map of port implementations,
633    /// used as part of the "transfer-receiving" steps of ports,
634    /// to produce the DOM ports stored in `message_ports` above.
635    pub(crate) port_impls: Option<FxHashMap<MessagePortId, MessagePortImpl>>,
636    /// A map of transform stream implementations,
637    pub(crate) transform_streams_port_impls: Option<FxHashMap<MessagePortId, TransformStreamData>>,
638    /// A map of blob implementations,
639    /// used as part of the "deserialize" steps of blobs,
640    /// to produce the DOM blobs stored in `blobs` above.
641    pub(crate) blob_impls: Option<FxHashMap<BlobId, BlobImpl>>,
642    /// A map of serialized files.
643    pub(crate) files: Option<FxHashMap<FileId, SerializableFile>>,
644    /// A map of serialized file lists.
645    pub(crate) file_lists: Option<FxHashMap<FileListId, SerializableFileList>>,
646    /// A map of serialized points.
647    pub(crate) points: Option<FxHashMap<DomPointId, DomPoint>>,
648    /// A map of serialized rects.
649    pub(crate) rects: Option<FxHashMap<DomRectId, DomRect>>,
650    /// A map of serialized quads.
651    pub(crate) quads: Option<FxHashMap<DomQuadId, DomQuad>>,
652    /// A map of serialized matrices.
653    pub(crate) matrices: Option<FxHashMap<DomMatrixId, DomMatrix>>,
654    /// A map of serialized exceptions.
655    pub(crate) exceptions: Option<FxHashMap<DomExceptionId, DomException>>,
656    /// A map of serialized quota exceeded errors.
657    pub(crate) quota_exceeded_errors:
658        Option<FxHashMap<QuotaExceededErrorId, SerializableQuotaExceededError>>,
659    // A map of serialized image bitmaps.
660    pub(crate) image_bitmaps: Option<FxHashMap<ImageBitmapId, SerializableImageBitmap>>,
661    /// A map of transferred image bitmaps.
662    pub(crate) transferred_image_bitmaps: Option<FxHashMap<ImageBitmapId, SerializableImageBitmap>>,
663    /// A map of transferred offscreen canvases.
664    pub(crate) offscreen_canvases:
665        Option<FxHashMap<OffscreenCanvasId, TransferableOffscreenCanvas>>,
666    // A map of serialized image data.
667    pub(crate) image_data: Option<FxHashMap<ImageDataId, SerializableImageData>>,
668}
669
670/// A data holder for transferred and serialized objects.
671#[derive(Default)]
672#[repr(C)]
673pub(crate) struct StructuredDataWriter {
674    /// Error record.
675    pub(crate) error: Option<Error>,
676    /// Transferred ports.
677    pub(crate) ports: Option<FxHashMap<MessagePortId, MessagePortImpl>>,
678    /// Transferred transform streams.
679    pub(crate) transform_streams_port: Option<FxHashMap<MessagePortId, TransformStreamData>>,
680    /// Serialized points.
681    pub(crate) points: Option<FxHashMap<DomPointId, DomPoint>>,
682    /// Serialized rects.
683    pub(crate) rects: Option<FxHashMap<DomRectId, DomRect>>,
684    /// Serialized quads.
685    pub(crate) quads: Option<FxHashMap<DomQuadId, DomQuad>>,
686    /// Serialized matrices.
687    pub(crate) matrices: Option<FxHashMap<DomMatrixId, DomMatrix>>,
688    /// Serialized exceptions.
689    pub(crate) exceptions: Option<FxHashMap<DomExceptionId, DomException>>,
690    /// Serialized quota exceeded errors.
691    pub(crate) quota_exceeded_errors:
692        Option<FxHashMap<QuotaExceededErrorId, SerializableQuotaExceededError>>,
693    /// Serialized blobs.
694    pub(crate) blobs: Option<FxHashMap<BlobId, BlobImpl>>,
695    /// Serialized files.
696    pub(crate) files: Option<FxHashMap<FileId, SerializableFile>>,
697    /// Serialized file lists.
698    pub(crate) file_lists: Option<FxHashMap<FileListId, SerializableFileList>>,
699    /// Serialized image bitmaps.
700    pub(crate) image_bitmaps: Option<FxHashMap<ImageBitmapId, SerializableImageBitmap>>,
701    /// Transferred image bitmaps.
702    pub(crate) transferred_image_bitmaps: Option<FxHashMap<ImageBitmapId, SerializableImageBitmap>>,
703    /// Transferred offscreen canvases.
704    pub(crate) offscreen_canvases:
705        Option<FxHashMap<OffscreenCanvasId, TransferableOffscreenCanvas>>,
706    // A map of serialized image data.
707    pub(crate) image_data: Option<FxHashMap<ImageDataId, SerializableImageData>>,
708}
709
710/// Writes a structured clone. Returns a `DataClone` error if that fails.
711pub(crate) fn write(
712    cx: SafeJSContext,
713    message: HandleValue,
714    transfer: Option<CustomAutoRooterGuard<Vec<*mut JSObject>>>,
715) -> Fallible<StructuredSerializedData> {
716    unsafe {
717        rooted!(in(*cx) let mut val = UndefinedValue());
718        if let Some(transfer) = transfer {
719            transfer.safe_to_jsval(cx, val.handle_mut(), CanGc::note());
720        }
721        let mut sc_writer = StructuredDataWriter::default();
722        let sc_writer_ptr = &mut sc_writer as *mut _;
723
724        let scbuf = JSAutoStructuredCloneBufferWrapper::new(
725            StructuredCloneScope::DifferentProcess,
726            &STRUCTURED_CLONE_CALLBACKS,
727        );
728        let scdata = &mut ((*scbuf.as_raw_ptr()).data_);
729        let policy = CloneDataPolicy {
730            allowIntraClusterClonableSharedObjects_: false,
731            allowSharedMemoryObjects_: false,
732        };
733        let result = JS_WriteStructuredClone(
734            *cx,
735            message,
736            scdata,
737            StructuredCloneScope::DifferentProcess,
738            &policy,
739            &STRUCTURED_CLONE_CALLBACKS,
740            sc_writer_ptr as *mut raw::c_void,
741            val.handle(),
742        );
743        if !result {
744            let error = if JS_IsExceptionPending(*cx) {
745                Error::JSFailed
746            } else {
747                sc_writer.error.unwrap_or(Error::DataClone(None))
748            };
749
750            return Err(error);
751        }
752
753        let nbytes = GetLengthOfJSStructuredCloneData(scdata);
754        let mut data = Vec::with_capacity(nbytes);
755        CopyJSStructuredCloneData(scdata, data.as_mut_ptr());
756        data.set_len(nbytes);
757
758        let data = StructuredSerializedData {
759            serialized: data,
760            ports: sc_writer.ports.take(),
761            transform_streams: sc_writer.transform_streams_port.take(),
762            points: sc_writer.points.take(),
763            rects: sc_writer.rects.take(),
764            quads: sc_writer.quads.take(),
765            matrices: sc_writer.matrices.take(),
766            exceptions: sc_writer.exceptions.take(),
767            quota_exceeded_errors: sc_writer.quota_exceeded_errors.take(),
768            blobs: sc_writer.blobs.take(),
769            files: sc_writer.files.take(),
770            file_lists: sc_writer.file_lists.take(),
771            image_bitmaps: sc_writer.image_bitmaps.take(),
772            transferred_image_bitmaps: sc_writer.transferred_image_bitmaps.take(),
773            offscreen_canvases: sc_writer.offscreen_canvases.take(),
774            image_data: sc_writer.image_data.take(),
775        };
776
777        Ok(data)
778    }
779}
780
781/// Read structured serialized data, possibly containing transferred objects.
782/// Returns a vec of rooted transfer-received ports, or an error.
783pub(crate) fn read(
784    global: &GlobalScope,
785    mut data: StructuredSerializedData,
786    rval: MutableHandleValue,
787    _can_gc: CanGc,
788) -> Fallible<Vec<DomRoot<MessagePort>>> {
789    let cx = GlobalScope::get_cx();
790    let _ac = enter_realm(global);
791    rooted_vec!(let mut roots);
792    let mut sc_reader = StructuredDataReader {
793        error: None,
794        roots,
795        port_impls: data.ports.take(),
796        transform_streams_port_impls: data.transform_streams.take(),
797        blob_impls: data.blobs.take(),
798        files: data.files.take(),
799        file_lists: data.file_lists.take(),
800        points: data.points.take(),
801        rects: data.rects.take(),
802        quads: data.quads.take(),
803        matrices: data.matrices.take(),
804        exceptions: data.exceptions.take(),
805        quota_exceeded_errors: data.quota_exceeded_errors.take(),
806        image_bitmaps: data.image_bitmaps.take(),
807        transferred_image_bitmaps: data.transferred_image_bitmaps.take(),
808        offscreen_canvases: data.offscreen_canvases.take(),
809        image_data: data.image_data.take(),
810    };
811    let sc_reader_ptr = &mut sc_reader as *mut _;
812    unsafe {
813        let scbuf = JSAutoStructuredCloneBufferWrapper::new(
814            StructuredCloneScope::DifferentProcess,
815            &STRUCTURED_CLONE_CALLBACKS,
816        );
817        let scdata = &mut ((*scbuf.as_raw_ptr()).data_);
818
819        WriteBytesToJSStructuredCloneData(
820            data.serialized.as_mut_ptr() as *const u8,
821            data.serialized.len(),
822            scdata,
823        );
824
825        let result = JS_ReadStructuredClone(
826            *cx,
827            scdata,
828            JS_STRUCTURED_CLONE_VERSION,
829            StructuredCloneScope::DifferentProcess,
830            rval,
831            &CloneDataPolicy {
832                allowIntraClusterClonableSharedObjects_: false,
833                allowSharedMemoryObjects_: false,
834            },
835            &STRUCTURED_CLONE_CALLBACKS,
836            sc_reader_ptr as *mut raw::c_void,
837        );
838        if !result {
839            let error = if JS_IsExceptionPending(*cx) {
840                Error::JSFailed
841            } else {
842                sc_reader.error.unwrap_or(Error::DataClone(None))
843            };
844
845            return Err(error);
846        }
847
848        let mut message_ports = vec![];
849        for reflector in sc_reader.roots.iter() {
850            let Ok(message_port) = root_from_object::<MessagePort>(reflector.get(), *cx) else {
851                continue;
852            };
853            message_ports.push(message_port);
854        }
855        // Any transfer-received port-impls should have been taken out.
856        assert!(sc_reader.port_impls.is_none());
857        Ok(message_ports)
858    }
859}