1use std::ffi::c_void;
8use std::ptr;
9
10use bytes::{Bytes, BytesMut};
11use ion::class::Reflector;
12use ion::clone::{StructuredCloneBuffer, read_uint64, write_uint64};
13use ion::flags::PropertyFlags;
14use ion::function::Opt;
15use ion::{ClassDefinition as _, Context, FromValue, Local, Object, ResultExc, Value, js_fn};
16use mozjs::jsapi::{
17 CloneDataPolicy, Handle, JS_ReadBytes, JS_ReadString, JS_WriteBytes, JS_WriteString, JS_WriteUint32Pair, JSContext,
18 JSObject, JSStructuredCloneCallbacks, JSStructuredCloneReader, JSStructuredCloneWriter, StructuredCloneScope,
19};
20
21use crate::globals::file::Blob;
22
23#[derive(Clone, Copy, Debug)]
24#[repr(u32)]
25enum StructuredCloneTags {
26 Min = 0xFFFF8000,
27 BlobSameProcess = 0xFFFF8001,
28 BlobDifferentProcess = 0xFFFF8002,
29 Max = 0xFFFFFFFF,
30}
31
32#[derive(Debug, Default)]
33pub struct StructuredCloneDataHolder {
34 blob_data: Vec<Bytes>,
35}
36
37unsafe extern "C" fn read_callback(
38 cx: *mut JSContext, r: *mut JSStructuredCloneReader, _: *const CloneDataPolicy, tag: u32, _data: u32,
39 private: *mut c_void,
40) -> *mut JSObject {
41 assert!(
42 tag > StructuredCloneTags::Min as u32,
43 "Expected Tag Below StructuredCloneTags::Min"
44 );
45 assert!(
46 tag < StructuredCloneTags::Max as u32,
47 "Expected Tag Below StructuredCloneTags::Max"
48 );
49
50 let cx = unsafe { &Context::new_unchecked(cx) };
51 let data = unsafe { &mut *private.cast::<StructuredCloneDataHolder>() };
52
53 if tag == StructuredCloneTags::BlobSameProcess as u32 {
54 let index;
55 let mut kind = ion::String::new(cx);
56
57 unsafe {
58 index = read_uint64(r).unwrap() as usize;
59 JS_ReadString(r, kind.handle_mut().into());
60 }
61
62 Blob::new_object(
63 cx,
64 Box::new(Blob {
65 reflector: Reflector::default(),
66 bytes: data.blob_data[index].clone(),
67 kind: Some(kind.to_owned(cx).unwrap()),
68 }),
69 )
70 } else if tag == StructuredCloneTags::BlobDifferentProcess as u32 {
71 let mut bytes;
72 let mut kind = ion::String::new(cx);
73
74 unsafe {
75 let len = read_uint64(r).unwrap() as usize;
76 bytes = BytesMut::with_capacity(len);
77 JS_ReadBytes(r, bytes.as_mut_ptr().cast(), len);
78 bytes.set_len(len);
79 JS_ReadString(r, kind.handle_mut().into());
80 }
81
82 Blob::new_object(
83 cx,
84 Box::new(Blob {
85 reflector: Reflector::default(),
86 bytes: bytes.freeze(),
87 kind: Some(kind.to_owned(cx).unwrap()),
88 }),
89 )
90 } else {
91 ptr::null_mut()
92 }
93}
94
95unsafe extern "C" fn write_callback(
96 cx: *mut JSContext, w: *mut JSStructuredCloneWriter, obj: Handle<*mut JSObject>, same_process_scope: *mut bool,
97 private: *mut c_void,
98) -> bool {
99 let cx = unsafe { &Context::new_unchecked(cx) };
100 let object = Object::from(unsafe { Local::from_raw_handle(obj) });
101 let data = unsafe { &mut *private.cast::<StructuredCloneDataHolder>() };
102
103 if let Ok(blob) = Blob::get_private(cx, &object) {
104 let kind = ion::String::copy_from_str(cx, blob.kind.as_deref().unwrap_or("")).unwrap();
105
106 unsafe {
107 if *same_process_scope {
108 JS_WriteUint32Pair(w, StructuredCloneTags::BlobSameProcess as u32, 0);
109 write_uint64(w, data.blob_data.len() as u64);
110 data.blob_data.push(blob.bytes.clone());
111 } else {
112 JS_WriteUint32Pair(w, StructuredCloneTags::BlobDifferentProcess as u32, 0);
113 write_uint64(w, blob.bytes.len() as u64);
114 JS_WriteBytes(w, blob.bytes.as_ptr().cast(), blob.bytes.len());
115 }
116 JS_WriteString(w, kind.handle().into());
117 }
118 }
119
120 true
121}
122
123pub static STRUCTURED_CLONE_CALLBACKS: JSStructuredCloneCallbacks = JSStructuredCloneCallbacks {
124 read: Some(read_callback),
125 write: Some(write_callback),
126 reportError: None,
127 readTransfer: None,
128 writeTransfer: None,
129 freeTransfer: None,
130 canTransfer: None,
131 sabCloned: None,
132};
133
134#[derive(FromValue)]
135struct StructuredCloneOptions<'cx> {
136 transfer: Vec<Object<'cx>>,
137}
138
139#[js_fn]
140fn structured_clone<'cx>(
141 cx: &'cx Context, data: Value<'cx>, Opt(options): Opt<StructuredCloneOptions<'cx>>,
142) -> ResultExc<Value<'cx>> {
143 let transfer = options.map(|o| o.transfer);
144 let policy = CloneDataPolicy {
145 allowIntraClusterClonableSharedObjects_: false,
146 allowSharedMemoryObjects_: true,
147 };
148
149 let mut buffer = StructuredCloneBuffer::new(
150 StructuredCloneScope::SameProcess,
151 &STRUCTURED_CLONE_CALLBACKS,
152 Some(Box::new(StructuredCloneDataHolder::default())),
153 );
154 buffer.write(cx, &data, transfer, &policy)?;
155 buffer.read(cx, &policy)
156}
157
158pub fn define(cx: &Context, global: &Object) -> bool {
159 !global
160 .define_method(
161 cx,
162 "structuredClone",
163 structured_clone,
164 1,
165 PropertyFlags::CONSTANT_ENUMERATED,
166 )
167 .handle()
168 .is_null()
169}