mirror of
https://github.com/lexmount/moli.git
synced 2026-10-04 08:00:45 +00:00
1283 lines
46 KiB
Rust
1283 lines
46 KiB
Rust
use std::{
|
|
cell::RefCell,
|
|
collections::{HashMap, HashSet},
|
|
fmt,
|
|
rc::Rc,
|
|
sync::Arc,
|
|
};
|
|
|
|
use crate::{
|
|
context_bootstrap::{
|
|
CryptoKeyAlgorithmClonePayload, CryptoKeyClonePayload, FileSystemFileSnapshotClonePayload,
|
|
FileSystemHandleClonePayload, ImageDataClonePayload, ReadableStreamClonePayload,
|
|
TransformStreamClonePayload, WritableStreamClonePayload,
|
|
attach_file_system_file_snapshot_clone_payload, build_file_object,
|
|
build_file_system_handle_from_clone_payload, build_image_data_object_from_clone_payload,
|
|
build_readable_stream_clone_shell, build_transform_stream_clone_shell,
|
|
build_writable_stream_clone_shell, crypto_key_clone_payload_from_object,
|
|
crypto_key_object_from_clone_payload, detach_message_port_owner_for_transfer,
|
|
detach_transferred_message_port, dom_exception_clone_fields,
|
|
ensure_message_port_wrapper_for_id, file_system_file_snapshot_clone_payload_from_object,
|
|
file_system_handle_clone_payload_from_object, image_data_clone_payload_from_object,
|
|
initialize_readable_stream_clone_shell, initialize_transform_stream_clone_shell,
|
|
initialize_writable_stream_clone_shell, is_crypto_key_object, is_image_data_object,
|
|
is_readable_stream_object, is_transform_stream_object, is_writable_stream_object,
|
|
message_port_id_from_object, new_dom_exception_value, new_quota_exceeded_error_value,
|
|
prepare_readable_stream_transfer, prepare_transform_stream_transfer,
|
|
prepare_writable_stream_transfer, quota_exceeded_error_clone_fields,
|
|
require_internal_stream_value, selected_file_from_object,
|
|
},
|
|
dom::native::SelectedFile,
|
|
types::MessagePortId,
|
|
};
|
|
pub(crate) use moli_structured_clone::{
|
|
StructuredCloneBytes as StructuredCloneWireBytes, TransferredArrayBuffer,
|
|
};
|
|
use v8::{ValueDeserializerHelper, ValueSerializerHelper};
|
|
|
|
const HOST_OBJECT_TAG_MESSAGE_PORT: u32 = 1;
|
|
const HOST_OBJECT_TAG_IMAGE_DATA: u32 = 2;
|
|
pub(crate) const HOST_OBJECT_TAG_CRYPTO_KEY: u32 = 3;
|
|
const HOST_OBJECT_TAG_READABLE_STREAM: u32 = 4;
|
|
pub(crate) const HOST_OBJECT_TAG_BLOB: u32 = 5;
|
|
const HOST_OBJECT_TAG_DOM_EXCEPTION: u32 = 6;
|
|
pub(crate) const HOST_OBJECT_TAG_FILE_SYSTEM_HANDLE: u32 = 7;
|
|
const HOST_OBJECT_TAG_QUOTA_EXCEEDED_ERROR: u32 = 8;
|
|
const HOST_OBJECT_TAG_WRITABLE_STREAM: u32 = 9;
|
|
const HOST_OBJECT_TAG_TRANSFORM_STREAM: u32 = 10;
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub(crate) struct V8StructuredClonePayload {
|
|
pub(crate) base: StructuredCloneWireBytes<MessagePortId>,
|
|
wasm_modules: Vec<ClonedWasmModule>,
|
|
readable_streams: Vec<ClonedReadableStream>,
|
|
writable_streams: Vec<ClonedWritableStream>,
|
|
transform_streams: Vec<ClonedTransformStream>,
|
|
blobs: Vec<ClonedBlob>,
|
|
file_system_handles: Vec<ClonedFileSystemHandle>,
|
|
pub(crate) metadata: StructuredCloneMetadata,
|
|
}
|
|
|
|
impl V8StructuredClonePayload {
|
|
pub(crate) fn transferred_message_ports(&self) -> &[MessagePortId] {
|
|
&self.base.transferred_message_ports
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct ClonedWasmModule {
|
|
clone_id: u32,
|
|
compiled_module: Arc<v8::CompiledWasmModule>,
|
|
instantiation_exceeds_v8_limit: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct ClonedReadableStream {
|
|
clone_id: u32,
|
|
payload: ReadableStreamClonePayload,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct ClonedWritableStream {
|
|
clone_id: u32,
|
|
payload: WritableStreamClonePayload,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct ClonedTransformStream {
|
|
clone_id: u32,
|
|
payload: TransformStreamClonePayload,
|
|
}
|
|
|
|
enum DeferredStreamMaterialization {
|
|
Readable {
|
|
shell: v8::Global<v8::Object>,
|
|
payload: ReadableStreamClonePayload,
|
|
},
|
|
Writable {
|
|
shell: v8::Global<v8::Object>,
|
|
payload: WritableStreamClonePayload,
|
|
},
|
|
Transform {
|
|
shell: v8::Global<v8::Object>,
|
|
payload: TransformStreamClonePayload,
|
|
},
|
|
}
|
|
|
|
impl DeferredStreamMaterialization {
|
|
fn initialize<'s>(&self, scope: &mut v8::PinScope<'s, '_>) -> Option<()> {
|
|
match self {
|
|
Self::Readable { shell, payload } => {
|
|
let shell = v8::Local::new(scope, shell);
|
|
initialize_readable_stream_clone_shell(scope, shell, payload)
|
|
}
|
|
Self::Writable { shell, payload } => {
|
|
let shell = v8::Local::new(scope, shell);
|
|
initialize_writable_stream_clone_shell(scope, shell, payload)
|
|
}
|
|
Self::Transform { shell, payload } => {
|
|
let shell = v8::Local::new(scope, shell);
|
|
initialize_transform_stream_clone_shell(scope, shell, payload)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn record_port_ids(&self, port_ids: &mut HashSet<MessagePortId>) {
|
|
match self {
|
|
Self::Readable { payload, .. } => {
|
|
port_ids.insert(payload.port_id());
|
|
}
|
|
Self::Writable { payload, .. } => {
|
|
port_ids.insert(payload.port_id());
|
|
}
|
|
Self::Transform { payload, .. } => {
|
|
port_ids.extend(payload.port_ids());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn discard_all_transferred_stream_channels(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
payload: &V8StructuredClonePayload,
|
|
) {
|
|
for stream in &payload.readable_streams {
|
|
stream.payload.discard_port(scope);
|
|
}
|
|
for stream in &payload.writable_streams {
|
|
stream.payload.discard_port(scope);
|
|
}
|
|
for stream in &payload.transform_streams {
|
|
stream.payload.discard_ports(scope);
|
|
}
|
|
}
|
|
|
|
fn discard_unclaimed_transferred_stream_channels(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
payload: &V8StructuredClonePayload,
|
|
materializations: &[DeferredStreamMaterialization],
|
|
) {
|
|
let mut claimed_port_ids = HashSet::new();
|
|
for materialization in materializations {
|
|
materialization.record_port_ids(&mut claimed_port_ids);
|
|
}
|
|
for stream in &payload.readable_streams {
|
|
if !claimed_port_ids.contains(&stream.payload.port_id()) {
|
|
stream.payload.discard_port(scope);
|
|
}
|
|
}
|
|
for stream in &payload.writable_streams {
|
|
if !claimed_port_ids.contains(&stream.payload.port_id()) {
|
|
stream.payload.discard_port(scope);
|
|
}
|
|
}
|
|
for stream in &payload.transform_streams {
|
|
let port_ids = stream.payload.port_ids();
|
|
if port_ids
|
|
.iter()
|
|
.all(|port_id| !claimed_port_ids.contains(port_id))
|
|
{
|
|
stream.payload.discard_ports(scope);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct ClonedBlob {
|
|
clone_id: u32,
|
|
payload: BlobClonePayload,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct ClonedFileSystemHandle {
|
|
clone_id: u32,
|
|
payload: FileSystemHandleClonePayload,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) enum BlobClonePayload {
|
|
Blob {
|
|
bytes: Vec<u8>,
|
|
mime_type: String,
|
|
},
|
|
File {
|
|
file: SelectedFile,
|
|
opfs_snapshot: Option<FileSystemFileSnapshotClonePayload>,
|
|
},
|
|
}
|
|
|
|
impl fmt::Debug for ClonedWasmModule {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("ClonedWasmModule")
|
|
.field("clone_id", &self.clone_id)
|
|
.field(
|
|
"instantiation_exceeds_v8_limit",
|
|
&self.instantiation_exceeds_v8_limit,
|
|
)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
struct ClonedWasmModuleStore {
|
|
next_id: u32,
|
|
modules: Vec<ClonedWasmModule>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub(crate) enum StructuredClonePolicy {
|
|
Runtime,
|
|
RuntimeMessage,
|
|
Storage,
|
|
}
|
|
|
|
impl StructuredClonePolicy {
|
|
fn allows_wasm_module(self) -> bool {
|
|
matches!(self, Self::Runtime | Self::RuntimeMessage)
|
|
}
|
|
|
|
fn metadata_for_wasm_modules(self, contains_wasm_module: bool) -> StructuredCloneMetadata {
|
|
StructuredCloneMetadata {
|
|
contains_wasm_module,
|
|
origin_check_required: contains_wasm_module && self == Self::RuntimeMessage,
|
|
locked_to_sender_agent_cluster: contains_wasm_module && self == Self::RuntimeMessage,
|
|
sender_agent_cluster: None,
|
|
sender_origin: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub(crate) enum RuntimeMessageAgentCluster {
|
|
WindowOrDedicatedWorker,
|
|
SharedWorker,
|
|
ServiceWorker,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub(crate) struct StructuredCloneMetadata {
|
|
pub(crate) contains_wasm_module: bool,
|
|
pub(crate) origin_check_required: bool,
|
|
pub(crate) locked_to_sender_agent_cluster: bool,
|
|
pub(crate) sender_agent_cluster: Option<RuntimeMessageAgentCluster>,
|
|
pub(crate) sender_origin: Option<String>,
|
|
}
|
|
|
|
struct WireSerializer {
|
|
allowed_message_port_ids: HashSet<MessagePortId>,
|
|
allowed_readable_streams: Vec<v8::Global<v8::Object>>,
|
|
allowed_writable_streams: Vec<v8::Global<v8::Object>>,
|
|
allowed_transform_streams: Vec<v8::Global<v8::Object>>,
|
|
policy: StructuredClonePolicy,
|
|
wasm_modules: Rc<RefCell<ClonedWasmModuleStore>>,
|
|
blobs: Rc<RefCell<ClonedBlobStore>>,
|
|
file_system_handles: Rc<RefCell<ClonedFileSystemHandleStore>>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
struct ClonedBlobStore {
|
|
next_id: u32,
|
|
blobs: Vec<ClonedBlob>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
struct ClonedFileSystemHandleStore {
|
|
next_id: u32,
|
|
handles: Vec<ClonedFileSystemHandle>,
|
|
}
|
|
|
|
impl v8::ValueSerializerImpl for WireSerializer {
|
|
fn throw_data_clone_error<'s>(
|
|
&self,
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
message: v8::Local<'s, v8::String>,
|
|
) {
|
|
let message = message.to_rust_string_lossy(scope);
|
|
let exception = new_dom_exception_value(scope, &message, "DataCloneError");
|
|
scope.throw_exception(exception);
|
|
}
|
|
|
|
fn has_custom_host_object(&self, _isolate: &v8::Isolate) -> bool {
|
|
true
|
|
}
|
|
|
|
fn is_host_object<'s>(
|
|
&self,
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
) -> Option<bool> {
|
|
Some(
|
|
message_port_id_from_object(scope, object).is_some()
|
|
|| is_image_data_object(scope, object)
|
|
|| is_crypto_key_object(scope, object)
|
|
|| is_readable_stream_object(scope, object)
|
|
|| is_writable_stream_object(scope, object)
|
|
|| is_transform_stream_object(scope, object)
|
|
|| crate::blob::is_blob_object(scope, object)
|
|
|| file_system_handle_clone_payload_from_object(scope, object).is_some()
|
|
|| dom_exception_clone_fields(scope, object).is_some(),
|
|
)
|
|
}
|
|
|
|
fn write_host_object<'s>(
|
|
&self,
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
serializer: &dyn v8::ValueSerializerHelper,
|
|
) -> Option<bool> {
|
|
if let Some(port_id) = message_port_id_from_object(scope, object) {
|
|
if !self.allowed_message_port_ids.contains(&port_id) {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"MessagePort must be listed in the postMessage transfer list.",
|
|
);
|
|
return None;
|
|
}
|
|
serializer.write_uint32(HOST_OBJECT_TAG_MESSAGE_PORT);
|
|
serializer.write_uint64(port_id);
|
|
return Some(true);
|
|
}
|
|
if let Some(payload) = image_data_clone_payload_from_object(scope, object) {
|
|
write_image_data_payload(serializer, payload);
|
|
return Some(true);
|
|
}
|
|
if write_crypto_key_payload(scope, object, serializer).is_some() {
|
|
return Some(true);
|
|
}
|
|
if is_readable_stream_object(scope, object) {
|
|
let Some(index) = self
|
|
.allowed_readable_streams
|
|
.iter()
|
|
.position(|allowed| v8::Local::new(scope, allowed).strict_equals(object.into()))
|
|
else {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"ReadableStream must be listed in the postMessage transfer list.",
|
|
);
|
|
return None;
|
|
};
|
|
let Ok(clone_id) = u32::try_from(index) else {
|
|
throw_data_clone_exception(scope, "Too many ReadableStreams in structured clone.");
|
|
return None;
|
|
};
|
|
serializer.write_uint32(HOST_OBJECT_TAG_READABLE_STREAM);
|
|
serializer.write_uint32(clone_id);
|
|
return Some(true);
|
|
}
|
|
if is_writable_stream_object(scope, object) {
|
|
let Some(index) = self
|
|
.allowed_writable_streams
|
|
.iter()
|
|
.position(|allowed| v8::Local::new(scope, allowed).strict_equals(object.into()))
|
|
else {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"WritableStream must be listed in the postMessage transfer list.",
|
|
);
|
|
return None;
|
|
};
|
|
let Ok(clone_id) = u32::try_from(index) else {
|
|
throw_data_clone_exception(scope, "Too many WritableStreams in structured clone.");
|
|
return None;
|
|
};
|
|
serializer.write_uint32(HOST_OBJECT_TAG_WRITABLE_STREAM);
|
|
serializer.write_uint32(clone_id);
|
|
return Some(true);
|
|
}
|
|
if is_transform_stream_object(scope, object) {
|
|
let Some(index) = self
|
|
.allowed_transform_streams
|
|
.iter()
|
|
.position(|allowed| v8::Local::new(scope, allowed).strict_equals(object.into()))
|
|
else {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"TransformStream must be listed in the postMessage transfer list.",
|
|
);
|
|
return None;
|
|
};
|
|
let Ok(clone_id) = u32::try_from(index) else {
|
|
throw_data_clone_exception(scope, "Too many TransformStreams in structured clone.");
|
|
return None;
|
|
};
|
|
serializer.write_uint32(HOST_OBJECT_TAG_TRANSFORM_STREAM);
|
|
serializer.write_uint32(clone_id);
|
|
return Some(true);
|
|
}
|
|
if let Some(payload) = blob_clone_payload_from_object(scope, object) {
|
|
let mut store = self.blobs.borrow_mut();
|
|
let clone_id = store.next_id;
|
|
let Some(next_id) = store.next_id.checked_add(1) else {
|
|
drop(store);
|
|
throw_data_clone_exception(scope, "Too many Blobs in structured clone.");
|
|
return None;
|
|
};
|
|
store.next_id = next_id;
|
|
store.blobs.push(ClonedBlob { clone_id, payload });
|
|
serializer.write_uint32(HOST_OBJECT_TAG_BLOB);
|
|
serializer.write_uint32(clone_id);
|
|
return Some(true);
|
|
}
|
|
if let Some(payload) = file_system_handle_clone_payload_from_object(scope, object) {
|
|
let mut store = self.file_system_handles.borrow_mut();
|
|
let clone_id = store.next_id;
|
|
let Some(next_id) = store.next_id.checked_add(1) else {
|
|
drop(store);
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"Too many FileSystemHandles in structured clone.",
|
|
);
|
|
return None;
|
|
};
|
|
store.next_id = next_id;
|
|
store
|
|
.handles
|
|
.push(ClonedFileSystemHandle { clone_id, payload });
|
|
serializer.write_uint32(HOST_OBJECT_TAG_FILE_SYSTEM_HANDLE);
|
|
serializer.write_uint32(clone_id);
|
|
return Some(true);
|
|
}
|
|
if let Some((message, quota, requested)) = quota_exceeded_error_clone_fields(scope, object)
|
|
{
|
|
serializer.write_uint32(HOST_OBJECT_TAG_QUOTA_EXCEEDED_ERROR);
|
|
write_string(serializer, &message);
|
|
write_optional_double(serializer, quota);
|
|
write_optional_double(serializer, requested);
|
|
return Some(true);
|
|
}
|
|
if let Some((message, name)) = dom_exception_clone_fields(scope, object) {
|
|
serializer.write_uint32(HOST_OBJECT_TAG_DOM_EXCEPTION);
|
|
write_string(serializer, &message);
|
|
write_string(serializer, &name);
|
|
return Some(true);
|
|
}
|
|
throw_data_clone_exception(scope, "Unsupported host object during structured clone.");
|
|
None
|
|
}
|
|
|
|
fn get_wasm_module_transfer_id(
|
|
&self,
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
module: v8::Local<v8::WasmModuleObject>,
|
|
) -> Option<u32> {
|
|
if !self.policy.allows_wasm_module() {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"A WebAssembly.Module can not be serialized for storage.",
|
|
);
|
|
return None;
|
|
}
|
|
|
|
let mut store = self.wasm_modules.borrow_mut();
|
|
let clone_id = store.next_id;
|
|
let Some(next_id) = store.next_id.checked_add(1) else {
|
|
drop(store);
|
|
throw_data_clone_exception(scope, "Too many WebAssembly modules in structured clone.");
|
|
return None;
|
|
};
|
|
store.next_id = next_id;
|
|
store.modules.push(ClonedWasmModule {
|
|
clone_id,
|
|
compiled_module: Arc::new(module.get_compiled_module()),
|
|
instantiation_exceeds_v8_limit:
|
|
crate::context_bootstrap::module_instantiation_exceeds_v8_limit(
|
|
scope,
|
|
module.into(),
|
|
),
|
|
});
|
|
Some(clone_id)
|
|
}
|
|
}
|
|
|
|
struct WireDeserializer {
|
|
wasm_modules: HashMap<u32, ClonedWasmModule>,
|
|
readable_streams: HashMap<u32, ReadableStreamClonePayload>,
|
|
writable_streams: HashMap<u32, WritableStreamClonePayload>,
|
|
transform_streams: HashMap<u32, TransformStreamClonePayload>,
|
|
deferred_streams: Rc<RefCell<Vec<DeferredStreamMaterialization>>>,
|
|
blobs: HashMap<u32, BlobClonePayload>,
|
|
file_system_handles: HashMap<u32, FileSystemHandleClonePayload>,
|
|
}
|
|
|
|
impl v8::ValueDeserializerImpl for WireDeserializer {
|
|
fn read_host_object<'s>(
|
|
&self,
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
deserializer: &dyn v8::ValueDeserializerHelper,
|
|
) -> Option<v8::Local<'s, v8::Object>> {
|
|
let mut tag = 0;
|
|
if !deserializer.read_uint32(&mut tag) {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"Failed to deserialize structured clone host object.",
|
|
);
|
|
return None;
|
|
}
|
|
match tag {
|
|
HOST_OBJECT_TAG_MESSAGE_PORT => {
|
|
let mut port_id = 0;
|
|
if !deserializer.read_uint64(&mut port_id) {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"Failed to deserialize transferred MessagePort.",
|
|
);
|
|
return None;
|
|
}
|
|
ensure_message_port_wrapper_for_id(scope, port_id)
|
|
}
|
|
HOST_OBJECT_TAG_IMAGE_DATA => read_image_data_payload(scope, deserializer),
|
|
HOST_OBJECT_TAG_CRYPTO_KEY => {
|
|
read_crypto_key_payload(scope, deserializer).or_else(|| {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"Failed to deserialize structured clone CryptoKey.",
|
|
);
|
|
None
|
|
})
|
|
}
|
|
HOST_OBJECT_TAG_READABLE_STREAM => {
|
|
let clone_id = read_u32(deserializer)?;
|
|
let Some(payload) = self.readable_streams.get(&clone_id).cloned() else {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"Missing ReadableStream payload during structured clone.",
|
|
);
|
|
return None;
|
|
};
|
|
// V8 invokes host-object decoding inside a
|
|
// DisallowJavascriptExecutionScope. Return the final stream
|
|
// identity now, but defer controller/Promise/MessagePort
|
|
// initialization until ReadValue has left that scope.
|
|
let shell = build_readable_stream_clone_shell(scope);
|
|
self.deferred_streams
|
|
.borrow_mut()
|
|
.push(DeferredStreamMaterialization::Readable {
|
|
shell: v8::Global::new(scope, shell),
|
|
payload,
|
|
});
|
|
Some(shell)
|
|
}
|
|
HOST_OBJECT_TAG_WRITABLE_STREAM => {
|
|
let clone_id = read_u32(deserializer)?;
|
|
let Some(payload) = self.writable_streams.get(&clone_id).cloned() else {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"Missing WritableStream payload during structured clone.",
|
|
);
|
|
return None;
|
|
};
|
|
let shell = build_writable_stream_clone_shell(scope);
|
|
self.deferred_streams
|
|
.borrow_mut()
|
|
.push(DeferredStreamMaterialization::Writable {
|
|
shell: v8::Global::new(scope, shell),
|
|
payload,
|
|
});
|
|
Some(shell)
|
|
}
|
|
HOST_OBJECT_TAG_TRANSFORM_STREAM => {
|
|
let clone_id = read_u32(deserializer)?;
|
|
let Some(payload) = self.transform_streams.get(&clone_id).cloned() else {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"Missing TransformStream payload during structured clone.",
|
|
);
|
|
return None;
|
|
};
|
|
let shell = build_transform_stream_clone_shell(scope);
|
|
self.deferred_streams
|
|
.borrow_mut()
|
|
.push(DeferredStreamMaterialization::Transform {
|
|
shell: v8::Global::new(scope, shell),
|
|
payload,
|
|
});
|
|
Some(shell)
|
|
}
|
|
HOST_OBJECT_TAG_BLOB => {
|
|
let clone_id = read_u32(deserializer)?;
|
|
let Some(payload) = self.blobs.get(&clone_id) else {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"Missing Blob payload during structured clone.",
|
|
);
|
|
return None;
|
|
};
|
|
build_blob_object_from_clone_payload(scope, payload)
|
|
}
|
|
HOST_OBJECT_TAG_FILE_SYSTEM_HANDLE => {
|
|
let clone_id = read_u32(deserializer)?;
|
|
let Some(payload) = self.file_system_handles.get(&clone_id) else {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"Missing FileSystemHandle payload during structured clone.",
|
|
);
|
|
return None;
|
|
};
|
|
build_file_system_handle_from_clone_payload(scope, payload).or_else(|| {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"FileSystemHandle is not authorized for this storage context.",
|
|
);
|
|
None
|
|
})
|
|
}
|
|
HOST_OBJECT_TAG_DOM_EXCEPTION => {
|
|
let message = read_string(deserializer)?;
|
|
let name = read_string(deserializer)?;
|
|
v8::Local::<v8::Object>::try_from(new_dom_exception_value(scope, &message, &name))
|
|
.ok()
|
|
}
|
|
HOST_OBJECT_TAG_QUOTA_EXCEEDED_ERROR => {
|
|
let message = read_string(deserializer)?;
|
|
let quota = read_optional_double(deserializer)?;
|
|
let requested = read_optional_double(deserializer)?;
|
|
v8::Local::<v8::Object>::try_from(new_quota_exceeded_error_value(
|
|
scope, &message, quota, requested,
|
|
))
|
|
.ok()
|
|
}
|
|
_ => {
|
|
throw_data_clone_exception(scope, "Unsupported structured clone host object.");
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_wasm_module_from_id<'s>(
|
|
&self,
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
clone_id: u32,
|
|
) -> Option<v8::Local<'s, v8::WasmModuleObject>> {
|
|
let Some(cloned_module) = self.wasm_modules.get(&clone_id) else {
|
|
throw_data_clone_exception(
|
|
scope,
|
|
"Missing WebAssembly.Module payload during structured clone.",
|
|
);
|
|
return None;
|
|
};
|
|
let module = v8::WasmModuleObject::from_compiled_module(
|
|
scope,
|
|
cloned_module.compiled_module.as_ref(),
|
|
)?;
|
|
if cloned_module.instantiation_exceeds_v8_limit {
|
|
crate::context_bootstrap::mark_module_instantiation_exceeds_v8_limit(
|
|
scope,
|
|
module.into(),
|
|
);
|
|
}
|
|
rehome_deserialized_wasm_module_for_active_child_window(scope, module);
|
|
Some(module)
|
|
}
|
|
}
|
|
|
|
fn rehome_deserialized_wasm_module_for_active_child_window<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
module: v8::Local<'s, v8::WasmModuleObject>,
|
|
) {
|
|
let Some(_handle) = crate::native_bridge::active_child_window_handle(scope) else {
|
|
return;
|
|
};
|
|
let context = scope.get_current_context();
|
|
let Some(prototype) = crate::context_bootstrap::webassembly_default_prototype_for_context(
|
|
scope, context, "Module",
|
|
) else {
|
|
return;
|
|
};
|
|
|
|
// Keep this scoped to WebAssembly.Module structured clone. Message
|
|
// delivery enters the target realm before deserialization, so use that
|
|
// context's stable intrinsic prototype rather than a mutable global.
|
|
let object = v8::Local::<v8::Object>::from(module);
|
|
let _ = object.set_prototype(scope, prototype.into());
|
|
}
|
|
|
|
pub(crate) fn blob_clone_payload_from_object<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
) -> Option<BlobClonePayload> {
|
|
if let Some(file) = selected_file_from_object(scope, object) {
|
|
return Some(BlobClonePayload::File {
|
|
file,
|
|
opfs_snapshot: file_system_file_snapshot_clone_payload_from_object(scope, object),
|
|
});
|
|
}
|
|
let bytes = crate::blob::blob_bytes_from_object(scope, object)?;
|
|
let mime_type = crate::blob::blob_mime_type_from_object(scope, object).unwrap_or_default();
|
|
Some(BlobClonePayload::Blob { bytes, mime_type })
|
|
}
|
|
|
|
pub(crate) fn build_blob_object_from_clone_payload<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
payload: &BlobClonePayload,
|
|
) -> Option<v8::Local<'s, v8::Object>> {
|
|
match payload {
|
|
BlobClonePayload::Blob { bytes, mime_type } => {
|
|
crate::blob::build_blob_object(scope, bytes.clone(), mime_type.clone())
|
|
}
|
|
BlobClonePayload::File {
|
|
file,
|
|
opfs_snapshot,
|
|
} => {
|
|
let object = build_file_object(scope, file)?;
|
|
if let Some(snapshot) = opfs_snapshot {
|
|
attach_file_system_file_snapshot_clone_payload(scope, object, snapshot)?;
|
|
}
|
|
Some(object)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn write_image_data_payload(
|
|
serializer: &dyn v8::ValueSerializerHelper,
|
|
payload: ImageDataClonePayload,
|
|
) {
|
|
serializer.write_uint32(HOST_OBJECT_TAG_IMAGE_DATA);
|
|
serializer.write_uint32(payload.width);
|
|
serializer.write_uint32(payload.height);
|
|
write_raw_vec(serializer, payload.color_space.as_bytes());
|
|
write_raw_vec(serializer, &payload.bytes);
|
|
}
|
|
|
|
fn read_image_data_payload<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
deserializer: &dyn v8::ValueDeserializerHelper,
|
|
) -> Option<v8::Local<'s, v8::Object>> {
|
|
let mut width = 0;
|
|
let mut height = 0;
|
|
if !deserializer.read_uint32(&mut width) || !deserializer.read_uint32(&mut height) {
|
|
throw_data_clone_exception(scope, "Failed to deserialize ImageData dimensions.");
|
|
return None;
|
|
}
|
|
let color_space = read_raw_vec(deserializer)
|
|
.and_then(|bytes| String::from_utf8(bytes).ok())
|
|
.unwrap_or_else(|| "srgb".to_owned());
|
|
let Some(bytes) = read_raw_vec(deserializer) else {
|
|
throw_data_clone_exception(scope, "Failed to deserialize ImageData pixel data.");
|
|
return None;
|
|
};
|
|
build_image_data_object_from_clone_payload(
|
|
scope,
|
|
ImageDataClonePayload {
|
|
width,
|
|
height,
|
|
color_space,
|
|
bytes,
|
|
},
|
|
)
|
|
}
|
|
|
|
pub(crate) fn write_crypto_key_payload<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
serializer: &dyn v8::ValueSerializerHelper,
|
|
) -> Option<()> {
|
|
let payload = crypto_key_clone_payload_from_object(scope, object)?;
|
|
serializer.write_uint32(HOST_OBJECT_TAG_CRYPTO_KEY);
|
|
write_string(serializer, &payload.key_type);
|
|
write_string(serializer, &payload.algorithm.name);
|
|
write_optional_string(serializer, payload.algorithm.hash_name.as_deref());
|
|
write_optional_usize(serializer, payload.algorithm.length_bits)?;
|
|
write_optional_string(serializer, payload.algorithm.named_curve.as_deref());
|
|
write_optional_usize(serializer, payload.algorithm.modulus_length_bits)?;
|
|
write_optional_raw_vec(serializer, payload.algorithm.public_exponent.as_deref())?;
|
|
serializer.write_uint32(u32::from(payload.extractable));
|
|
serializer.write_uint32(payload.usages.len().try_into().ok()?);
|
|
for usage in &payload.usages {
|
|
write_string(serializer, usage);
|
|
}
|
|
write_raw_vec(serializer, &payload.key_bytes);
|
|
Some(())
|
|
}
|
|
|
|
pub(crate) fn read_crypto_key_payload<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
deserializer: &dyn v8::ValueDeserializerHelper,
|
|
) -> Option<v8::Local<'s, v8::Object>> {
|
|
let key_type = read_string(deserializer)?;
|
|
let algorithm_name = read_string(deserializer)?;
|
|
let hash_name = read_optional_string(deserializer)?;
|
|
let length_bits = read_optional_usize(deserializer)?;
|
|
let named_curve = read_optional_string(deserializer)?;
|
|
let modulus_length_bits = read_optional_usize(deserializer)?;
|
|
let public_exponent = read_optional_raw_vec(deserializer)?;
|
|
let extractable = read_u32(deserializer)? != 0;
|
|
let usage_count = read_u32(deserializer)?;
|
|
if usage_count > 8 {
|
|
return None;
|
|
}
|
|
let mut usages = Vec::with_capacity(usage_count as usize);
|
|
for _ in 0..usage_count {
|
|
usages.push(read_string(deserializer)?);
|
|
}
|
|
let key_bytes = read_raw_vec(deserializer)?;
|
|
crypto_key_object_from_clone_payload(
|
|
scope,
|
|
CryptoKeyClonePayload {
|
|
key_type,
|
|
algorithm: CryptoKeyAlgorithmClonePayload {
|
|
name: algorithm_name,
|
|
hash_name,
|
|
length_bits,
|
|
named_curve,
|
|
modulus_length_bits,
|
|
public_exponent,
|
|
},
|
|
extractable,
|
|
usages,
|
|
key_bytes,
|
|
},
|
|
)
|
|
}
|
|
|
|
fn write_string(serializer: &dyn v8::ValueSerializerHelper, value: &str) {
|
|
write_raw_vec(serializer, value.as_bytes());
|
|
}
|
|
|
|
fn write_optional_string(serializer: &dyn v8::ValueSerializerHelper, value: Option<&str>) {
|
|
match value {
|
|
Some(value) => {
|
|
serializer.write_uint32(1);
|
|
write_string(serializer, value);
|
|
}
|
|
None => serializer.write_uint32(0),
|
|
}
|
|
}
|
|
|
|
fn write_optional_double(serializer: &dyn v8::ValueSerializerHelper, value: Option<f64>) {
|
|
match value {
|
|
Some(value) => {
|
|
serializer.write_uint32(1);
|
|
serializer.write_double(value);
|
|
}
|
|
None => serializer.write_uint32(0),
|
|
}
|
|
}
|
|
|
|
fn write_optional_usize(
|
|
serializer: &dyn v8::ValueSerializerHelper,
|
|
value: Option<usize>,
|
|
) -> Option<()> {
|
|
match value {
|
|
Some(value) => {
|
|
serializer.write_uint32(1);
|
|
serializer.write_uint32(value.try_into().ok()?);
|
|
}
|
|
None => serializer.write_uint32(0),
|
|
}
|
|
Some(())
|
|
}
|
|
|
|
fn write_optional_raw_vec(
|
|
serializer: &dyn v8::ValueSerializerHelper,
|
|
value: Option<&[u8]>,
|
|
) -> Option<()> {
|
|
match value {
|
|
Some(value) => {
|
|
serializer.write_uint32(1);
|
|
write_raw_vec(serializer, value);
|
|
}
|
|
None => serializer.write_uint32(0),
|
|
}
|
|
Some(())
|
|
}
|
|
|
|
fn read_u32(deserializer: &dyn v8::ValueDeserializerHelper) -> Option<u32> {
|
|
let mut value = 0;
|
|
deserializer.read_uint32(&mut value).then_some(value)
|
|
}
|
|
|
|
fn read_string(deserializer: &dyn v8::ValueDeserializerHelper) -> Option<String> {
|
|
String::from_utf8(read_raw_vec(deserializer)?).ok()
|
|
}
|
|
|
|
fn read_optional_string(deserializer: &dyn v8::ValueDeserializerHelper) -> Option<Option<String>> {
|
|
match read_u32(deserializer)? {
|
|
0 => Some(None),
|
|
1 => Some(Some(read_string(deserializer)?)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn read_optional_double(deserializer: &dyn v8::ValueDeserializerHelper) -> Option<Option<f64>> {
|
|
match read_u32(deserializer)? {
|
|
0 => Some(None),
|
|
1 => {
|
|
let mut value = 0.0;
|
|
deserializer.read_double(&mut value).then_some(Some(value))
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn read_optional_usize(deserializer: &dyn v8::ValueDeserializerHelper) -> Option<Option<usize>> {
|
|
match read_u32(deserializer)? {
|
|
0 => Some(None),
|
|
1 => Some(Some(read_u32(deserializer)? as usize)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn read_optional_raw_vec(
|
|
deserializer: &dyn v8::ValueDeserializerHelper,
|
|
) -> Option<Option<Vec<u8>>> {
|
|
match read_u32(deserializer)? {
|
|
0 => Some(None),
|
|
1 => Some(Some(read_raw_vec(deserializer)?)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn write_raw_vec(serializer: &dyn v8::ValueSerializerHelper, bytes: &[u8]) {
|
|
serializer.write_uint32(bytes.len() as u32);
|
|
serializer.write_raw_bytes(bytes);
|
|
}
|
|
|
|
fn read_raw_vec(deserializer: &dyn v8::ValueDeserializerHelper) -> Option<Vec<u8>> {
|
|
let mut len = 0;
|
|
if !deserializer.read_uint32(&mut len) {
|
|
return None;
|
|
}
|
|
deserializer.read_raw_bytes(len as usize).map(Vec::from)
|
|
}
|
|
|
|
fn throw_data_clone_exception<'s>(scope: &mut v8::PinScope<'s, '_>, message: &str) {
|
|
let exception = new_dom_exception_value(scope, message, "DataCloneError");
|
|
scope.throw_exception(exception);
|
|
}
|
|
|
|
fn copy_array_buffer_bytes(buffer: v8::Local<'_, v8::ArrayBuffer>) -> Vec<u8> {
|
|
let backing_store = buffer.get_backing_store();
|
|
let length = backing_store.byte_length();
|
|
if length == 0 {
|
|
return Vec::new();
|
|
}
|
|
let Some(data) = backing_store.data() else {
|
|
return Vec::new();
|
|
};
|
|
// SAFETY: `BackingStore::data()` is valid for the lifetime of the backing
|
|
// store, which we keep alive for the duration of this copy.
|
|
unsafe { std::slice::from_raw_parts(data.as_ptr().cast::<u8>(), length) }.to_vec()
|
|
}
|
|
|
|
pub(crate) fn serialize_for_wire_for_runtime<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
) -> Option<V8StructuredClonePayload> {
|
|
serialize_for_wire_with_policy(
|
|
scope,
|
|
value,
|
|
&[],
|
|
&[],
|
|
&[],
|
|
&[],
|
|
&[],
|
|
StructuredClonePolicy::Runtime,
|
|
)
|
|
}
|
|
|
|
pub(crate) fn serialize_for_wire_for_runtime_with_transfers<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
array_buffer_transfers: &[v8::Local<'s, v8::ArrayBuffer>],
|
|
message_port_transfers: &[v8::Local<'s, v8::Object>],
|
|
readable_stream_transfers: &[v8::Local<'s, v8::Object>],
|
|
writable_stream_transfers: &[v8::Local<'s, v8::Object>],
|
|
transform_stream_transfers: &[v8::Local<'s, v8::Object>],
|
|
) -> Option<V8StructuredClonePayload> {
|
|
serialize_for_wire_with_policy(
|
|
scope,
|
|
value,
|
|
array_buffer_transfers,
|
|
message_port_transfers,
|
|
readable_stream_transfers,
|
|
writable_stream_transfers,
|
|
transform_stream_transfers,
|
|
StructuredClonePolicy::Runtime,
|
|
)
|
|
}
|
|
|
|
pub(crate) fn serialize_for_wire_for_storage<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
) -> Option<V8StructuredClonePayload> {
|
|
serialize_for_wire_with_policy(
|
|
scope,
|
|
value,
|
|
&[],
|
|
&[],
|
|
&[],
|
|
&[],
|
|
&[],
|
|
StructuredClonePolicy::Storage,
|
|
)
|
|
}
|
|
|
|
pub(crate) fn serialize_for_wire_for_runtime_message<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
array_buffer_transfers: &[v8::Local<'s, v8::ArrayBuffer>],
|
|
message_port_transfers: &[v8::Local<'s, v8::Object>],
|
|
readable_stream_transfers: &[v8::Local<'s, v8::Object>],
|
|
writable_stream_transfers: &[v8::Local<'s, v8::Object>],
|
|
transform_stream_transfers: &[v8::Local<'s, v8::Object>],
|
|
) -> Option<V8StructuredClonePayload> {
|
|
serialize_for_wire_with_policy(
|
|
scope,
|
|
value,
|
|
array_buffer_transfers,
|
|
message_port_transfers,
|
|
readable_stream_transfers,
|
|
writable_stream_transfers,
|
|
transform_stream_transfers,
|
|
StructuredClonePolicy::RuntimeMessage,
|
|
)
|
|
}
|
|
|
|
fn serialize_for_wire_with_policy<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
array_buffer_transfers: &[v8::Local<'s, v8::ArrayBuffer>],
|
|
message_port_transfers: &[v8::Local<'s, v8::Object>],
|
|
readable_stream_transfers: &[v8::Local<'s, v8::Object>],
|
|
writable_stream_transfers: &[v8::Local<'s, v8::Object>],
|
|
transform_stream_transfers: &[v8::Local<'s, v8::Object>],
|
|
policy: StructuredClonePolicy,
|
|
) -> Option<V8StructuredClonePayload> {
|
|
let context = scope.get_current_context();
|
|
let transferred_message_ports: Vec<MessagePortId> = message_port_transfers
|
|
.iter()
|
|
.filter_map(|port| message_port_id_from_object(scope, *port))
|
|
.collect();
|
|
let wasm_modules = Rc::new(RefCell::new(ClonedWasmModuleStore::default()));
|
|
let blobs = Rc::new(RefCell::new(ClonedBlobStore::default()));
|
|
let file_system_handles = Rc::new(RefCell::new(ClonedFileSystemHandleStore::default()));
|
|
let serializer = v8::ValueSerializer::new(
|
|
scope,
|
|
Box::new(WireSerializer {
|
|
allowed_message_port_ids: transferred_message_ports.iter().copied().collect(),
|
|
allowed_readable_streams: readable_stream_transfers
|
|
.iter()
|
|
.map(|stream| v8::Global::new(scope, *stream))
|
|
.collect(),
|
|
allowed_writable_streams: writable_stream_transfers
|
|
.iter()
|
|
.map(|stream| v8::Global::new(scope, *stream))
|
|
.collect(),
|
|
allowed_transform_streams: transform_stream_transfers
|
|
.iter()
|
|
.map(|stream| v8::Global::new(scope, *stream))
|
|
.collect(),
|
|
policy,
|
|
wasm_modules: Rc::clone(&wasm_modules),
|
|
blobs: Rc::clone(&blobs),
|
|
file_system_handles: Rc::clone(&file_system_handles),
|
|
}),
|
|
);
|
|
serializer.write_header();
|
|
let mut transferred_array_buffers = Vec::with_capacity(array_buffer_transfers.len());
|
|
for (index, buffer) in array_buffer_transfers.iter().enumerate() {
|
|
if buffer.get_backing_store().is_resizable_by_user_javascript() {
|
|
// V8's inline wire representation preserves a resizable backing
|
|
// store's maximum length and length-tracking views. The transfer
|
|
// list still detaches the sender-side buffer after serialization.
|
|
continue;
|
|
}
|
|
let transfer_id = index as u32 + 1;
|
|
serializer.transfer_array_buffer(transfer_id, *buffer);
|
|
transferred_array_buffers.push(TransferredArrayBuffer {
|
|
transfer_id,
|
|
bytes: copy_array_buffer_bytes(*buffer),
|
|
});
|
|
}
|
|
let Some(true) = serializer.write_value(context, value) else {
|
|
return None;
|
|
};
|
|
for buffer in array_buffer_transfers {
|
|
if buffer.detach(None) != Some(true) {
|
|
throw_data_clone_exception(scope, "Failed to transfer ArrayBuffer.");
|
|
return None;
|
|
}
|
|
}
|
|
for port in message_port_transfers {
|
|
let Some(port_id) = message_port_id_from_object(scope, *port) else {
|
|
continue;
|
|
};
|
|
detach_message_port_owner_for_transfer(scope, port_id);
|
|
detach_transferred_message_port(scope, *port);
|
|
}
|
|
let mut readable_streams = Vec::with_capacity(readable_stream_transfers.len());
|
|
for (clone_id, stream) in readable_stream_transfers.iter().enumerate() {
|
|
let prepared = prepare_readable_stream_transfer(scope, *stream)?;
|
|
let clone_id =
|
|
u32::try_from(clone_id).expect("a JavaScript transfer sequence length fits in u32");
|
|
readable_streams.push(ClonedReadableStream {
|
|
clone_id,
|
|
payload: prepared.commit(scope),
|
|
});
|
|
}
|
|
let mut writable_streams = Vec::with_capacity(writable_stream_transfers.len());
|
|
for (clone_id, stream) in writable_stream_transfers.iter().enumerate() {
|
|
let prepared = prepare_writable_stream_transfer(scope, *stream)?;
|
|
let clone_id =
|
|
u32::try_from(clone_id).expect("a JavaScript transfer sequence length fits in u32");
|
|
writable_streams.push(ClonedWritableStream {
|
|
clone_id,
|
|
payload: prepared.commit(scope),
|
|
});
|
|
}
|
|
let mut transform_streams = Vec::with_capacity(transform_stream_transfers.len());
|
|
for (clone_id, stream) in transform_stream_transfers.iter().enumerate() {
|
|
let prepared = prepare_transform_stream_transfer(scope, *stream)?;
|
|
let clone_id =
|
|
u32::try_from(clone_id).expect("a JavaScript transfer sequence length fits in u32");
|
|
transform_streams.push(ClonedTransformStream {
|
|
clone_id,
|
|
payload: prepared.commit(scope),
|
|
});
|
|
}
|
|
let wasm_modules = wasm_modules.borrow().modules.clone();
|
|
let blobs = blobs.borrow().blobs.clone();
|
|
let file_system_handles = file_system_handles.borrow().handles.clone();
|
|
let metadata = policy.metadata_for_wasm_modules(!wasm_modules.is_empty());
|
|
Some(V8StructuredClonePayload {
|
|
base: StructuredCloneWireBytes {
|
|
wire_bytes: serializer.release(),
|
|
transferred_array_buffers,
|
|
transferred_message_ports,
|
|
},
|
|
wasm_modules,
|
|
readable_streams,
|
|
writable_streams,
|
|
transform_streams,
|
|
blobs,
|
|
file_system_handles,
|
|
metadata,
|
|
})
|
|
}
|
|
|
|
pub(crate) fn deserialize_from_wire<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
payload: &V8StructuredClonePayload,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
deserialize_from_wire_impl(scope, payload)
|
|
}
|
|
|
|
pub(crate) fn deserialize_message_event_from_wire<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
payload: &V8StructuredClonePayload,
|
|
) -> Option<(v8::Local<'s, v8::Value>, v8::Local<'s, v8::Array>)> {
|
|
if validate_message_event_metadata(scope, payload).is_none() {
|
|
discard_all_transferred_stream_channels(scope, payload);
|
|
return None;
|
|
}
|
|
let value = deserialize_from_wire_impl(scope, payload)?;
|
|
let ports = v8::Array::new(scope, payload.transferred_message_ports().len() as i32);
|
|
for (index, port_id) in payload.transferred_message_ports().iter().enumerate() {
|
|
if let Some(port) = ensure_message_port_wrapper_for_id(scope, *port_id) {
|
|
let _ = ports.set_index(scope, index as u32, v8::Local::<v8::Value>::from(port));
|
|
}
|
|
}
|
|
let _ = ports.set_integrity_level(scope, v8::IntegrityLevel::Frozen);
|
|
Some((value, ports))
|
|
}
|
|
|
|
fn validate_message_event_metadata(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
payload: &V8StructuredClonePayload,
|
|
) -> Option<()> {
|
|
let metadata = &payload.metadata;
|
|
if metadata.contains_wasm_module
|
|
&& (!metadata.origin_check_required
|
|
|| !metadata.locked_to_sender_agent_cluster
|
|
|| metadata.sender_agent_cluster.is_none())
|
|
{
|
|
throw_data_clone_exception(scope, "Invalid WebAssembly.Module message clone metadata.");
|
|
return None;
|
|
}
|
|
Some(())
|
|
}
|
|
|
|
fn deserialize_from_wire_impl<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
payload: &V8StructuredClonePayload,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
let context = scope.get_current_context();
|
|
let wasm_modules = payload
|
|
.wasm_modules
|
|
.iter()
|
|
.map(|module| (module.clone_id, module.clone()))
|
|
.collect();
|
|
let readable_streams = payload
|
|
.readable_streams
|
|
.iter()
|
|
.map(|stream| (stream.clone_id, stream.payload.clone()))
|
|
.collect();
|
|
let writable_streams = payload
|
|
.writable_streams
|
|
.iter()
|
|
.map(|stream| (stream.clone_id, stream.payload.clone()))
|
|
.collect();
|
|
let transform_streams = payload
|
|
.transform_streams
|
|
.iter()
|
|
.map(|stream| (stream.clone_id, stream.payload.clone()))
|
|
.collect();
|
|
let blobs = payload
|
|
.blobs
|
|
.iter()
|
|
.map(|blob| (blob.clone_id, blob.payload.clone()))
|
|
.collect();
|
|
let file_system_handles = payload
|
|
.file_system_handles
|
|
.iter()
|
|
.map(|handle| (handle.clone_id, handle.payload.clone()))
|
|
.collect();
|
|
let deferred_streams = Rc::new(RefCell::new(Vec::new()));
|
|
let deserializer = v8::ValueDeserializer::new(
|
|
scope,
|
|
Box::new(WireDeserializer {
|
|
wasm_modules,
|
|
readable_streams,
|
|
writable_streams,
|
|
transform_streams,
|
|
deferred_streams: Rc::clone(&deferred_streams),
|
|
blobs,
|
|
file_system_handles,
|
|
}),
|
|
&payload.base.wire_bytes,
|
|
);
|
|
let Some(true) = deserializer.read_header(context) else {
|
|
discard_all_transferred_stream_channels(scope, payload);
|
|
return None;
|
|
};
|
|
for transfer in &payload.base.transferred_array_buffers {
|
|
let backing_store = v8::ArrayBuffer::new_backing_store_from_vec(transfer.bytes.clone());
|
|
let backing_store = backing_store.make_shared();
|
|
let buffer = v8::ArrayBuffer::with_backing_store(scope, &backing_store);
|
|
deserializer.transfer_array_buffer(transfer.transfer_id, buffer);
|
|
}
|
|
let Some(value) = deserializer.read_value(context) else {
|
|
drop(deserializer);
|
|
discard_all_transferred_stream_channels(scope, payload);
|
|
return None;
|
|
};
|
|
drop(deserializer);
|
|
|
|
let deferred = std::mem::take(&mut *deferred_streams.borrow_mut());
|
|
discard_unclaimed_transferred_stream_channels(scope, payload, &deferred);
|
|
for materialization in &deferred {
|
|
if materialization.initialize(scope).is_none() {
|
|
discard_all_transferred_stream_channels(scope, payload);
|
|
if crate::worker::worker_termination_requested(scope) {
|
|
return None;
|
|
}
|
|
require_internal_stream_value::<()>(
|
|
None,
|
|
"clone-shell materialization",
|
|
"deferred transferred Streams batch",
|
|
);
|
|
}
|
|
}
|
|
Some(value)
|
|
}
|