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