runtime/globals/
clone.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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}