Skip to main content

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