From fee2ea34afe8bad593d43fdbdb2a29be1352826c Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Mon, 21 Apr 2025 05:41:55 +0200 Subject: [PATCH] constellation: Re-split structured data types into separate files (#36615) In #36364 I moved both serializable and transferable implementations from the `script_traits` crate into a single file called `message_ports.rs`. Gregory raised the point that this was a bit of a inaccurate grouping. This change attempts to fix it according to the division in the specification. See [the relevant thread on zulip][thread]. [thread]: https://servo.zulipchat.com/#narrow/channel/263398-general/topic/Organizing.20*_traits.20crates/near/510864104. Testing: Covered by existing test as this is just code movement. Signed-off-by: Martin Robinson --- .../constellation/from_script_message.rs | 6 +- components/shared/constellation/lib.rs | 35 +- .../shared/constellation/message_port.rs | 566 ------------------ .../constellation/structured_data/mod.rs | 91 +++ .../structured_data/serializable.rs | 314 ++++++++++ .../structured_data/transferable.rs | 172 ++++++ 6 files changed, 609 insertions(+), 575 deletions(-) delete mode 100644 components/shared/constellation/message_port.rs create mode 100644 components/shared/constellation/structured_data/mod.rs create mode 100644 components/shared/constellation/structured_data/serializable.rs create mode 100644 components/shared/constellation/structured_data/transferable.rs diff --git a/components/shared/constellation/from_script_message.rs b/components/shared/constellation/from_script_message.rs index bccb3059e24..8346551fd15 100644 --- a/components/shared/constellation/from_script_message.rs +++ b/components/shared/constellation/from_script_message.rs @@ -33,10 +33,8 @@ use strum_macros::IntoStaticStr; use webgpu_traits::{WebGPU, WebGPUAdapterResponse}; use webrender_api::ImageKey; -use crate::message_port::{ - BroadcastMsg, MessagePortMsg, PortMessageTask, StructuredSerializedData, -}; -use crate::{LogEntry, TraversalDirection, WindowSizeType}; +use crate::structured_data::{BroadcastMsg, StructuredSerializedData}; +use crate::{LogEntry, MessagePortMsg, PortMessageTask, TraversalDirection, WindowSizeType}; /// A Script to Constellation channel. #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/components/shared/constellation/lib.rs b/components/shared/constellation/lib.rs index 627741a40fb..548e17b532c 100644 --- a/components/shared/constellation/lib.rs +++ b/components/shared/constellation/lib.rs @@ -9,15 +9,15 @@ //! on other parts of Servo. mod from_script_message; -mod message_port; +mod structured_data; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::fmt; use std::time::Duration; use base::Epoch; use base::cross_process_instant::CrossProcessInstant; -use base::id::{PipelineId, WebViewId}; +use base::id::{MessagePortId, PipelineId, WebViewId}; use bitflags::bitflags; use embedder_traits::{ CompositorHitTestResult, Cursor, InputEvent, MediaSessionActionType, Theme, ViewportDetails, @@ -27,9 +27,9 @@ use euclid::Vector2D; pub use from_script_message::*; use ipc_channel::ipc::IpcSender; use malloc_size_of_derive::MallocSizeOf; -pub use message_port::*; use serde::{Deserialize, Serialize}; -use servo_url::ServoUrl; +use servo_url::{ImmutableOrigin, ServoUrl}; +pub use structured_data::*; use strum_macros::IntoStaticStr; use webrender_api::ExternalScrollId; use webrender_api::units::LayoutPixel; @@ -158,3 +158,28 @@ pub enum TraversalDirection { /// Travel backward the given number of documents. Back(usize), } + +/// A task on the +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct PortMessageTask { + /// The origin of this task. + pub origin: ImmutableOrigin, + /// A data-holder for serialized data and transferred objects. + pub data: StructuredSerializedData, +} + +/// Messages for communication between the constellation and a global managing ports. +#[derive(Debug, Deserialize, Serialize)] +#[allow(clippy::large_enum_variant)] +pub enum MessagePortMsg { + /// Complete the transfer for a batch of ports. + CompleteTransfer(HashMap>), + /// Complete the transfer of a single port, + /// whose transfer was pending because it had been requested + /// while a previous failed transfer was being rolled-back. + CompletePendingTransfer(MessagePortId, VecDeque), + /// Remove a port, the entangled one doesn't exists anymore. + RemoveMessagePort(MessagePortId), + /// Handle a new port-message-task. + NewTask(MessagePortId, PortMessageTask), +} diff --git a/components/shared/constellation/message_port.rs b/components/shared/constellation/message_port.rs deleted file mode 100644 index bb2929353af..00000000000 --- a/components/shared/constellation/message_port.rs +++ /dev/null @@ -1,566 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! This module contains data structures used for message ports and serializing -//! DOM objects to send across them as per -//! . -//! The implementations are here instead of in `script``, because these -//! types can be sent through the Constellation to other ScriptThreads, -//! and Constellation cannot depend directly on `script`. - -use std::cell::RefCell; -use std::collections::{HashMap, VecDeque}; -use std::path::PathBuf; - -use base::id::{BlobId, DomExceptionId, DomPointId, MessagePortId}; -use log::warn; -use malloc_size_of_derive::MallocSizeOf; -use net_traits::filemanager_thread::RelativePos; -use serde::{Deserialize, Serialize}; -use servo_url::ImmutableOrigin; -use strum::{EnumIter, IntoEnumIterator}; -use uuid::Uuid; - -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] -enum MessagePortState { - /// - Detached, - /// - /// The message-queue of this port is enabled, - /// the boolean represents awaiting completion of a transfer. - Enabled(bool), - /// - /// The message-queue of this port is disabled, - /// the boolean represents awaiting completion of a transfer. - Disabled(bool), -} - -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] -/// The data and logic backing the DOM managed MessagePort. -pub struct MessagePortImpl { - /// The current state of the port. - state: MessagePortState, - - /// - entangled_port: Option, - - /// - message_buffer: Option>, - - /// The UUID of this port. - message_port_id: MessagePortId, -} - -impl MessagePortImpl { - /// Create a new messageport impl. - pub fn new(port_id: MessagePortId) -> MessagePortImpl { - MessagePortImpl { - state: MessagePortState::Disabled(false), - entangled_port: None, - message_buffer: None, - message_port_id: port_id, - } - } - - /// Get the Id. - pub fn message_port_id(&self) -> &MessagePortId { - &self.message_port_id - } - - /// Maybe get the Id of the entangled port. - pub fn entangled_port_id(&self) -> Option { - self.entangled_port - } - - /// Entanged this port with another. - pub fn entangle(&mut self, other_id: MessagePortId) { - self.entangled_port = Some(other_id); - } - - /// Is this port enabled? - pub fn enabled(&self) -> bool { - matches!(self.state, MessagePortState::Enabled(_)) - } - - /// Mark this port as having been shipped. - /// - pub fn set_has_been_shipped(&mut self) { - match self.state { - MessagePortState::Detached => { - panic!("Messageport set_has_been_shipped called in detached state") - }, - MessagePortState::Enabled(_) => self.state = MessagePortState::Enabled(true), - MessagePortState::Disabled(_) => self.state = MessagePortState::Disabled(true), - } - } - - /// Handle the completion of the transfer, - /// this is data received from the constellation. - pub fn complete_transfer(&mut self, mut tasks: VecDeque) { - match self.state { - MessagePortState::Detached => return, - MessagePortState::Enabled(_) => self.state = MessagePortState::Enabled(false), - MessagePortState::Disabled(_) => self.state = MessagePortState::Disabled(false), - } - - // Note: these are the tasks that were buffered while the transfer was ongoing, - // hence they need to execute first. - // The global will call `start` if we are enabled, - // which will add tasks on the event-loop to dispatch incoming messages. - match self.message_buffer { - Some(ref mut incoming_buffer) => { - while let Some(task) = tasks.pop_back() { - incoming_buffer.push_front(task); - } - }, - None => self.message_buffer = Some(tasks), - } - } - - /// A message was received from our entangled port, - /// returns an optional task to be dispatched. - pub fn handle_incoming(&mut self, task: PortMessageTask) -> Option { - let should_dispatch = match self.state { - MessagePortState::Detached => return None, - MessagePortState::Enabled(in_transfer) => !in_transfer, - MessagePortState::Disabled(_) => false, - }; - - if should_dispatch { - Some(task) - } else { - match self.message_buffer { - Some(ref mut buffer) => { - buffer.push_back(task); - }, - None => { - let mut queue = VecDeque::new(); - queue.push_back(task); - self.message_buffer = Some(queue); - }, - } - None - } - } - - /// - /// returns an optional queue of tasks that were buffered while the port was disabled. - pub fn start(&mut self) -> Option> { - match self.state { - MessagePortState::Detached => return None, - MessagePortState::Enabled(_) => {}, - MessagePortState::Disabled(in_transfer) => { - self.state = MessagePortState::Enabled(in_transfer); - }, - } - if let MessagePortState::Enabled(true) = self.state { - return None; - } - self.message_buffer.take() - } - - /// - pub fn close(&mut self) { - // Step 1 - self.state = MessagePortState::Detached; - } -} - -/// A data-holder for serialized data and transferred objects. -/// -#[derive(Debug, Default, Deserialize, MallocSizeOf, Serialize)] -pub struct StructuredSerializedData { - /// Data serialized by SpiderMonkey. - pub serialized: Vec, - /// Serialized in a structured callback, - pub blobs: Option>, - /// Serialized point objects. - pub points: Option>, - /// Serialized exception objects. - pub exceptions: Option>, - /// Transferred objects. - pub ports: Option>, -} - -pub(crate) trait BroadcastClone -where - Self: Sized, -{ - /// The ID type that uniquely identify each value. - type Id: Eq + std::hash::Hash + Copy; - /// Clone this value so that it can be reused with a broadcast channel. - /// Only return None if cloning is impossible. - fn clone_for_broadcast(&self) -> Option; - /// The field from which to clone values. - fn source(data: &StructuredSerializedData) -> &Option>; - /// The field into which to place cloned values. - fn destination(data: &mut StructuredSerializedData) -> &mut Option>; -} - -/// All the DOM interfaces that can be serialized. -#[derive(Clone, Copy, Debug, EnumIter)] -pub enum Serializable { - /// The `Blob` interface. - Blob, - /// The `DOMPoint` interface. - DomPoint, - /// The `DOMPointReadOnly` interface. - DomPointReadOnly, - /// The `DOMException` interface. - DomException, -} - -impl Serializable { - fn clone_values(&self) -> fn(&StructuredSerializedData, &mut StructuredSerializedData) { - match self { - Serializable::Blob => StructuredSerializedData::clone_all_of_type::, - Serializable::DomPointReadOnly => { - StructuredSerializedData::clone_all_of_type:: - }, - Serializable::DomPoint => StructuredSerializedData::clone_all_of_type::, - Serializable::DomException => { - StructuredSerializedData::clone_all_of_type:: - }, - } - } -} - -/// All the DOM interfaces that can be transferred. -#[derive(Clone, Copy, Debug, EnumIter)] -pub enum Transferrable { - /// The `MessagePort` interface. - MessagePort, - /// The `ReadableStream` interface. - ReadableStream, - /// The `WritableStream` interface. - WritableStream, -} - -impl StructuredSerializedData { - fn is_empty(&self, val: Transferrable) -> bool { - fn is_field_empty(field: &Option>) -> bool { - field.as_ref().is_some_and(|h| h.is_empty()) - } - match val { - Transferrable::MessagePort => is_field_empty(&self.ports), - Transferrable::ReadableStream => is_field_empty(&self.ports), - Transferrable::WritableStream => is_field_empty(&self.ports), - } - } - - /// Clone all values of the same type stored in this StructuredSerializedData - /// into another instance. - fn clone_all_of_type(&self, cloned: &mut StructuredSerializedData) { - let existing = T::source(self); - let Some(existing) = existing else { return }; - let mut clones = HashMap::with_capacity(existing.len()); - - for (original_id, obj) in existing.iter() { - if let Some(clone) = obj.clone_for_broadcast() { - clones.insert(*original_id, clone); - } - } - - *T::destination(cloned) = Some(clones); - } - - /// Clone the serialized data for use with broadcast-channels. - pub fn clone_for_broadcast(&self) -> StructuredSerializedData { - for transferrable in Transferrable::iter() { - if !self.is_empty(transferrable) { - // Not panicking only because this is called from the constellation. - warn!( - "Attempt to broadcast structured serialized data including {:?} (should never happen).", - transferrable, - ); - } - } - - let serialized = self.serialized.clone(); - - let mut cloned = StructuredSerializedData { - serialized, - ..Default::default() - }; - - for serializable in Serializable::iter() { - let clone_impl = serializable.clone_values(); - clone_impl(self, &mut cloned); - } - - cloned - } -} - -/// A task on the -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] -pub struct PortMessageTask { - /// The origin of this task. - pub origin: ImmutableOrigin, - /// A data-holder for serialized data and transferred objects. - pub data: StructuredSerializedData, -} - -/// Messages for communication between the constellation and a global managing ports. -#[derive(Debug, Deserialize, Serialize)] -#[allow(clippy::large_enum_variant)] -pub enum MessagePortMsg { - /// Complete the transfer for a batch of ports. - CompleteTransfer(HashMap>), - /// Complete the transfer of a single port, - /// whose transfer was pending because it had been requested - /// while a previous failed transfer was being rolled-back. - CompletePendingTransfer(MessagePortId, VecDeque), - /// Remove a port, the entangled one doesn't exists anymore. - RemoveMessagePort(MessagePortId), - /// Handle a new port-message-task. - NewTask(MessagePortId, PortMessageTask), -} - -/// Message for communication between the constellation and a global managing broadcast channels. -#[derive(Debug, Deserialize, Serialize)] -pub struct BroadcastMsg { - /// The origin of this message. - pub origin: ImmutableOrigin, - /// The name of the channel. - pub channel_name: String, - /// A data-holder for serialized data. - pub data: StructuredSerializedData, -} - -impl Clone for BroadcastMsg { - fn clone(&self) -> BroadcastMsg { - BroadcastMsg { - data: self.data.clone_for_broadcast(), - origin: self.origin.clone(), - channel_name: self.channel_name.clone(), - } - } -} - -/// File-based blob -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] -pub struct FileBlob { - #[ignore_malloc_size_of = "Uuid are hard(not really)"] - id: Uuid, - #[ignore_malloc_size_of = "PathBuf are hard"] - name: Option, - cache: RefCell>>, - size: u64, -} - -impl FileBlob { - /// Create a new file blob. - pub fn new(id: Uuid, name: Option, cache: Option>, size: u64) -> FileBlob { - FileBlob { - id, - name, - cache: RefCell::new(cache), - size, - } - } - - /// Get the size of the file. - pub fn get_size(&self) -> u64 { - self.size - } - - /// Get the cached file data, if any. - pub fn get_cache(&self) -> Option> { - self.cache.borrow().clone() - } - - /// Cache data. - pub fn cache_bytes(&self, bytes: Vec) { - *self.cache.borrow_mut() = Some(bytes); - } - - /// Get the file id. - pub fn get_id(&self) -> Uuid { - self.id - } -} - -impl BroadcastClone for BlobImpl { - type Id = BlobId; - - fn source( - data: &StructuredSerializedData, - ) -> &Option> { - &data.blobs - } - - fn destination( - data: &mut StructuredSerializedData, - ) -> &mut Option> { - &mut data.blobs - } - - fn clone_for_broadcast(&self) -> Option { - let type_string = self.type_string(); - - if let BlobData::Memory(bytes) = self.blob_data() { - let blob_clone = BlobImpl::new_from_bytes(bytes.clone(), type_string); - - // Note: we insert the blob at the original id, - // otherwise this will not match the storage key as serialized by SM in `serialized`. - // The clone has it's own new Id however. - return Some(blob_clone); - } else { - // Not panicking only because this is called from the constellation. - log::warn!("Serialized blob not in memory format(should never happen)."); - } - None - } -} - -/// The data backing a DOM Blob. -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] -pub struct BlobImpl { - /// UUID of the blob. - blob_id: BlobId, - /// Content-type string - type_string: String, - /// Blob data-type. - blob_data: BlobData, - /// Sliced blobs referring to this one. - slices: Vec, -} - -/// Different backends of Blob -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] -pub enum BlobData { - /// File-based blob, whose content lives in the net process - File(FileBlob), - /// Memory-based blob, whose content lives in the script process - Memory(Vec), - /// Sliced blob, including parent blob-id and - /// relative positions of current slicing range, - /// IMPORTANT: The depth of tree is only two, i.e. the parent Blob must be - /// either File-based or Memory-based - Sliced(BlobId, RelativePos), -} - -impl BlobImpl { - /// Construct memory-backed BlobImpl - pub fn new_from_bytes(bytes: Vec, type_string: String) -> BlobImpl { - let blob_id = BlobId::new(); - let blob_data = BlobData::Memory(bytes); - BlobImpl { - blob_id, - type_string, - blob_data, - slices: vec![], - } - } - - /// Construct file-backed BlobImpl from File ID - pub fn new_from_file(file_id: Uuid, name: PathBuf, size: u64, type_string: String) -> BlobImpl { - let blob_id = BlobId::new(); - let blob_data = BlobData::File(FileBlob { - id: file_id, - name: Some(name), - cache: RefCell::new(None), - size, - }); - BlobImpl { - blob_id, - type_string, - blob_data, - slices: vec![], - } - } - - /// Construct a BlobImpl from a slice of a parent. - pub fn new_sliced(rel_pos: RelativePos, parent: BlobId, type_string: String) -> BlobImpl { - let blob_id = BlobId::new(); - let blob_data = BlobData::Sliced(parent, rel_pos); - BlobImpl { - blob_id, - type_string, - blob_data, - slices: vec![], - } - } - - /// Get a clone of the blob-id - pub fn blob_id(&self) -> BlobId { - self.blob_id - } - - /// Get a clone of the type-string - pub fn type_string(&self) -> String { - self.type_string.clone() - } - - /// Get a mutable ref to the data - pub fn blob_data(&self) -> &BlobData { - &self.blob_data - } - - /// Get a mutable ref to the data - pub fn blob_data_mut(&mut self) -> &mut BlobData { - &mut self.blob_data - } -} - -#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] -/// A serializable version of the DOMPoint/DOMPointReadOnly interface. -pub struct DomPoint { - /// The x coordinate. - pub x: f64, - /// The y coordinate. - pub y: f64, - /// The z coordinate. - pub z: f64, - /// The w coordinate. - pub w: f64, -} - -impl BroadcastClone for DomPoint { - type Id = DomPointId; - - fn source( - data: &StructuredSerializedData, - ) -> &Option> { - &data.points - } - - fn destination( - data: &mut StructuredSerializedData, - ) -> &mut Option> { - &mut data.points - } - - fn clone_for_broadcast(&self) -> Option { - Some(self.clone()) - } -} - -#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] -/// A serializable version of the DOMException interface. -pub struct DomException { - pub message: String, - pub name: String, -} - -impl BroadcastClone for DomException { - type Id = DomExceptionId; - - fn source( - data: &StructuredSerializedData, - ) -> &Option> { - &data.exceptions - } - - fn destination( - data: &mut StructuredSerializedData, - ) -> &mut Option> { - &mut data.exceptions - } - - fn clone_for_broadcast(&self) -> Option { - Some(self.clone()) - } -} diff --git a/components/shared/constellation/structured_data/mod.rs b/components/shared/constellation/structured_data/mod.rs new file mode 100644 index 00000000000..41fc05493a2 --- /dev/null +++ b/components/shared/constellation/structured_data/mod.rs @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! This module contains implementations of structured data as described in +//! + +mod serializable; +mod transferable; + +use std::collections::HashMap; + +use base::id::{BlobId, DomExceptionId, DomPointId, MessagePortId}; +use log::warn; +use malloc_size_of_derive::MallocSizeOf; +use serde::{Deserialize, Serialize}; +pub use serializable::*; +use strum::IntoEnumIterator; +pub use transferable::*; + +/// A data-holder for serialized data and transferred objects. +/// +#[derive(Debug, Default, Deserialize, MallocSizeOf, Serialize)] +pub struct StructuredSerializedData { + /// Data serialized by SpiderMonkey. + pub serialized: Vec, + /// Serialized in a structured callback, + pub blobs: Option>, + /// Serialized point objects. + pub points: Option>, + /// Serialized exception objects. + pub exceptions: Option>, + /// Transferred objects. + pub ports: Option>, +} + +impl StructuredSerializedData { + fn is_empty(&self, val: Transferrable) -> bool { + fn is_field_empty(field: &Option>) -> bool { + field.as_ref().is_some_and(|h| h.is_empty()) + } + match val { + Transferrable::MessagePort => is_field_empty(&self.ports), + Transferrable::ReadableStream => is_field_empty(&self.ports), + Transferrable::WritableStream => is_field_empty(&self.ports), + } + } + + /// Clone all values of the same type stored in this StructuredSerializedData + /// into another instance. + fn clone_all_of_type(&self, cloned: &mut StructuredSerializedData) { + let existing = T::source(self); + let Some(existing) = existing else { return }; + let mut clones = HashMap::with_capacity(existing.len()); + + for (original_id, obj) in existing.iter() { + if let Some(clone) = obj.clone_for_broadcast() { + clones.insert(*original_id, clone); + } + } + + *T::destination(cloned) = Some(clones); + } + + /// Clone the serialized data for use with broadcast-channels. + pub fn clone_for_broadcast(&self) -> StructuredSerializedData { + for transferrable in Transferrable::iter() { + if !self.is_empty(transferrable) { + // Not panicking only because this is called from the constellation. + warn!( + "Attempt to broadcast structured serialized data including {:?} (should never happen).", + transferrable, + ); + } + } + + let serialized = self.serialized.clone(); + + let mut cloned = StructuredSerializedData { + serialized, + ..Default::default() + }; + + for serializable in Serializable::iter() { + let clone_impl = serializable.clone_values(); + clone_impl(self, &mut cloned); + } + + cloned + } +} diff --git a/components/shared/constellation/structured_data/serializable.rs b/components/shared/constellation/structured_data/serializable.rs new file mode 100644 index 00000000000..abc05ad5758 --- /dev/null +++ b/components/shared/constellation/structured_data/serializable.rs @@ -0,0 +1,314 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! This module contains implementations in script that are serializable, +//! as per . +//! The implementations are here instead of in script as they need to +//! be passed through the Constellation. + +use std::cell::RefCell; +use std::collections::HashMap; +use std::path::PathBuf; + +use base::id::{BlobId, DomExceptionId, DomPointId}; +use malloc_size_of_derive::MallocSizeOf; +use net_traits::filemanager_thread::RelativePos; +use serde::{Deserialize, Serialize}; +use servo_url::ImmutableOrigin; +use strum::EnumIter; +use uuid::Uuid; + +use super::StructuredSerializedData; + +pub(crate) trait BroadcastClone +where + Self: Sized, +{ + /// The ID type that uniquely identify each value. + type Id: Eq + std::hash::Hash + Copy; + /// Clone this value so that it can be reused with a broadcast channel. + /// Only return None if cloning is impossible. + fn clone_for_broadcast(&self) -> Option; + /// The field from which to clone values. + fn source(data: &StructuredSerializedData) -> &Option>; + /// The field into which to place cloned values. + fn destination(data: &mut StructuredSerializedData) -> &mut Option>; +} + +/// All the DOM interfaces that can be serialized. +#[derive(Clone, Copy, Debug, EnumIter)] +pub enum Serializable { + /// The `Blob` interface. + Blob, + /// The `DOMPoint` interface. + DomPoint, + /// The `DOMPointReadOnly` interface. + DomPointReadOnly, + /// The `DOMException` interface. + DomException, +} + +impl Serializable { + pub(super) fn clone_values( + &self, + ) -> fn(&StructuredSerializedData, &mut StructuredSerializedData) { + match self { + Serializable::Blob => StructuredSerializedData::clone_all_of_type::, + Serializable::DomPointReadOnly => { + StructuredSerializedData::clone_all_of_type:: + }, + Serializable::DomPoint => StructuredSerializedData::clone_all_of_type::, + Serializable::DomException => { + StructuredSerializedData::clone_all_of_type:: + }, + } + } +} + +/// Message for communication between the constellation and a global managing broadcast channels. +#[derive(Debug, Deserialize, Serialize)] +pub struct BroadcastMsg { + /// The origin of this message. + pub origin: ImmutableOrigin, + /// The name of the channel. + pub channel_name: String, + /// A data-holder for serialized data. + pub data: StructuredSerializedData, +} + +impl Clone for BroadcastMsg { + fn clone(&self) -> BroadcastMsg { + BroadcastMsg { + data: self.data.clone_for_broadcast(), + origin: self.origin.clone(), + channel_name: self.channel_name.clone(), + } + } +} + +/// File-based blob +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct FileBlob { + #[ignore_malloc_size_of = "Uuid are hard(not really)"] + id: Uuid, + #[ignore_malloc_size_of = "PathBuf are hard"] + name: Option, + cache: RefCell>>, + size: u64, +} + +impl FileBlob { + /// Create a new file blob. + pub fn new(id: Uuid, name: Option, cache: Option>, size: u64) -> FileBlob { + FileBlob { + id, + name, + cache: RefCell::new(cache), + size, + } + } + + /// Get the size of the file. + pub fn get_size(&self) -> u64 { + self.size + } + + /// Get the cached file data, if any. + pub fn get_cache(&self) -> Option> { + self.cache.borrow().clone() + } + + /// Cache data. + pub fn cache_bytes(&self, bytes: Vec) { + *self.cache.borrow_mut() = Some(bytes); + } + + /// Get the file id. + pub fn get_id(&self) -> Uuid { + self.id + } +} + +impl BroadcastClone for BlobImpl { + type Id = BlobId; + + fn source( + data: &StructuredSerializedData, + ) -> &Option> { + &data.blobs + } + + fn destination( + data: &mut StructuredSerializedData, + ) -> &mut Option> { + &mut data.blobs + } + + fn clone_for_broadcast(&self) -> Option { + let type_string = self.type_string(); + + if let BlobData::Memory(bytes) = self.blob_data() { + let blob_clone = BlobImpl::new_from_bytes(bytes.clone(), type_string); + + // Note: we insert the blob at the original id, + // otherwise this will not match the storage key as serialized by SM in `serialized`. + // The clone has it's own new Id however. + return Some(blob_clone); + } else { + // Not panicking only because this is called from the constellation. + log::warn!("Serialized blob not in memory format(should never happen)."); + } + None + } +} + +/// The data backing a DOM Blob. +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct BlobImpl { + /// UUID of the blob. + blob_id: BlobId, + /// Content-type string + type_string: String, + /// Blob data-type. + blob_data: BlobData, + /// Sliced blobs referring to this one. + slices: Vec, +} + +/// Different backends of Blob +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum BlobData { + /// File-based blob, whose content lives in the net process + File(FileBlob), + /// Memory-based blob, whose content lives in the script process + Memory(Vec), + /// Sliced blob, including parent blob-id and + /// relative positions of current slicing range, + /// IMPORTANT: The depth of tree is only two, i.e. the parent Blob must be + /// either File-based or Memory-based + Sliced(BlobId, RelativePos), +} + +impl BlobImpl { + /// Construct memory-backed BlobImpl + pub fn new_from_bytes(bytes: Vec, type_string: String) -> BlobImpl { + let blob_id = BlobId::new(); + let blob_data = BlobData::Memory(bytes); + BlobImpl { + blob_id, + type_string, + blob_data, + slices: vec![], + } + } + + /// Construct file-backed BlobImpl from File ID + pub fn new_from_file(file_id: Uuid, name: PathBuf, size: u64, type_string: String) -> BlobImpl { + let blob_id = BlobId::new(); + let blob_data = BlobData::File(FileBlob { + id: file_id, + name: Some(name), + cache: RefCell::new(None), + size, + }); + BlobImpl { + blob_id, + type_string, + blob_data, + slices: vec![], + } + } + + /// Construct a BlobImpl from a slice of a parent. + pub fn new_sliced(rel_pos: RelativePos, parent: BlobId, type_string: String) -> BlobImpl { + let blob_id = BlobId::new(); + let blob_data = BlobData::Sliced(parent, rel_pos); + BlobImpl { + blob_id, + type_string, + blob_data, + slices: vec![], + } + } + + /// Get a clone of the blob-id + pub fn blob_id(&self) -> BlobId { + self.blob_id + } + + /// Get a clone of the type-string + pub fn type_string(&self) -> String { + self.type_string.clone() + } + + /// Get a mutable ref to the data + pub fn blob_data(&self) -> &BlobData { + &self.blob_data + } + + /// Get a mutable ref to the data + pub fn blob_data_mut(&mut self) -> &mut BlobData { + &mut self.blob_data + } +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +/// A serializable version of the DOMPoint/DOMPointReadOnly interface. +pub struct DomPoint { + /// The x coordinate. + pub x: f64, + /// The y coordinate. + pub y: f64, + /// The z coordinate. + pub z: f64, + /// The w coordinate. + pub w: f64, +} + +impl BroadcastClone for DomPoint { + type Id = DomPointId; + + fn source( + data: &StructuredSerializedData, + ) -> &Option> { + &data.points + } + + fn destination( + data: &mut StructuredSerializedData, + ) -> &mut Option> { + &mut data.points + } + + fn clone_for_broadcast(&self) -> Option { + Some(self.clone()) + } +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +/// A serializable version of the DOMException interface. +pub struct DomException { + pub message: String, + pub name: String, +} + +impl BroadcastClone for DomException { + type Id = DomExceptionId; + + fn source( + data: &StructuredSerializedData, + ) -> &Option> { + &data.exceptions + } + + fn destination( + data: &mut StructuredSerializedData, + ) -> &mut Option> { + &mut data.exceptions + } + + fn clone_for_broadcast(&self) -> Option { + Some(self.clone()) + } +} diff --git a/components/shared/constellation/structured_data/transferable.rs b/components/shared/constellation/structured_data/transferable.rs new file mode 100644 index 00000000000..cd6388f5f34 --- /dev/null +++ b/components/shared/constellation/structured_data/transferable.rs @@ -0,0 +1,172 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! This module contains implementations in script that are transferable as per +//! . The implementations are here +//! instead of in script as they need to be passed through the Constellation. + +use std::collections::VecDeque; + +use base::id::MessagePortId; +use malloc_size_of_derive::MallocSizeOf; +use serde::{Deserialize, Serialize}; +use strum::EnumIter; + +use crate::PortMessageTask; + +/// All the DOM interfaces that can be transferred. +#[derive(Clone, Copy, Debug, EnumIter)] +pub enum Transferrable { + /// The `MessagePort` interface. + MessagePort, + /// The `ReadableStream` interface. + ReadableStream, + /// The `WritableStream` interface. + WritableStream, +} + +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +enum MessagePortState { + /// + Detached, + /// + /// The message-queue of this port is enabled, + /// the boolean represents awaiting completion of a transfer. + Enabled(bool), + /// + /// The message-queue of this port is disabled, + /// the boolean represents awaiting completion of a transfer. + Disabled(bool), +} + +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +/// The data and logic backing the DOM managed MessagePort. +pub struct MessagePortImpl { + /// The current state of the port. + state: MessagePortState, + + /// + entangled_port: Option, + + /// + message_buffer: Option>, + + /// The UUID of this port. + message_port_id: MessagePortId, +} + +impl MessagePortImpl { + /// Create a new messageport impl. + pub fn new(port_id: MessagePortId) -> MessagePortImpl { + MessagePortImpl { + state: MessagePortState::Disabled(false), + entangled_port: None, + message_buffer: None, + message_port_id: port_id, + } + } + + /// Get the Id. + pub fn message_port_id(&self) -> &MessagePortId { + &self.message_port_id + } + + /// Maybe get the Id of the entangled port. + pub fn entangled_port_id(&self) -> Option { + self.entangled_port + } + + /// Entanged this port with another. + pub fn entangle(&mut self, other_id: MessagePortId) { + self.entangled_port = Some(other_id); + } + + /// Is this port enabled? + pub fn enabled(&self) -> bool { + matches!(self.state, MessagePortState::Enabled(_)) + } + + /// Mark this port as having been shipped. + /// + pub fn set_has_been_shipped(&mut self) { + match self.state { + MessagePortState::Detached => { + panic!("Messageport set_has_been_shipped called in detached state") + }, + MessagePortState::Enabled(_) => self.state = MessagePortState::Enabled(true), + MessagePortState::Disabled(_) => self.state = MessagePortState::Disabled(true), + } + } + + /// Handle the completion of the transfer, + /// this is data received from the constellation. + pub fn complete_transfer(&mut self, mut tasks: VecDeque) { + match self.state { + MessagePortState::Detached => return, + MessagePortState::Enabled(_) => self.state = MessagePortState::Enabled(false), + MessagePortState::Disabled(_) => self.state = MessagePortState::Disabled(false), + } + + // Note: these are the tasks that were buffered while the transfer was ongoing, + // hence they need to execute first. + // The global will call `start` if we are enabled, + // which will add tasks on the event-loop to dispatch incoming messages. + match self.message_buffer { + Some(ref mut incoming_buffer) => { + while let Some(task) = tasks.pop_back() { + incoming_buffer.push_front(task); + } + }, + None => self.message_buffer = Some(tasks), + } + } + + /// A message was received from our entangled port, + /// returns an optional task to be dispatched. + pub fn handle_incoming(&mut self, task: PortMessageTask) -> Option { + let should_dispatch = match self.state { + MessagePortState::Detached => return None, + MessagePortState::Enabled(in_transfer) => !in_transfer, + MessagePortState::Disabled(_) => false, + }; + + if should_dispatch { + Some(task) + } else { + match self.message_buffer { + Some(ref mut buffer) => { + buffer.push_back(task); + }, + None => { + let mut queue = VecDeque::new(); + queue.push_back(task); + self.message_buffer = Some(queue); + }, + } + None + } + } + + /// + /// returns an optional queue of tasks that were buffered while the port was disabled. + pub fn start(&mut self) -> Option> { + match self.state { + MessagePortState::Detached => return None, + MessagePortState::Enabled(_) => {}, + MessagePortState::Disabled(in_transfer) => { + self.state = MessagePortState::Enabled(in_transfer); + }, + } + if let MessagePortState::Enabled(true) = self.state { + return None; + } + self.message_buffer.take() + } + + /// + pub fn close(&mut self) { + // Step 1 + self.state = MessagePortState::Detached; + } +}