aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/constellation/constellation.rs213
-rw-r--r--components/msg/constellation_msg.rs43
-rw-r--r--components/script/dom/bindings/trace.rs6
-rw-r--r--components/script/dom/broadcastchannel.rs106
-rw-r--r--components/script/dom/globalscope.rs281
-rw-r--r--components/script/dom/mod.rs1
-rw-r--r--components/script/dom/webidls/BroadcastChannel.webidl18
-rw-r--r--components/script/dom/window.rs4
-rw-r--r--components/script_traits/lib.rs65
-rw-r--r--components/script_traits/script_msg.rs24
10 files changed, 747 insertions, 14 deletions
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs
index 629921035f0..47e59c37342 100644
--- a/components/constellation/constellation.rs
+++ b/components/constellation/constellation.rs
@@ -126,12 +126,12 @@ use log::{Level, LevelFilter, Log, Metadata, Record};
use media::{GLPlayerThreads, WindowGLContext};
use msg::constellation_msg::{BackgroundHangMonitorRegister, HangMonitorAlert, SamplerControlMsg};
use msg::constellation_msg::{
- BrowsingContextGroupId, BrowsingContextId, HistoryStateId, PipelineId,
- TopLevelBrowsingContextId,
+ BroadcastChannelRouterId, MessagePortId, MessagePortRouterId, PipelineNamespace,
+ PipelineNamespaceId, PipelineNamespaceRequest, TraversalDirection,
};
use msg::constellation_msg::{
- MessagePortId, MessagePortRouterId, PipelineNamespace, PipelineNamespaceId,
- PipelineNamespaceRequest, TraversalDirection,
+ BrowsingContextGroupId, BrowsingContextId, HistoryStateId, PipelineId,
+ TopLevelBrowsingContextId,
};
use net_traits::pub_domains::reg_host;
use net_traits::request::RequestBuilder;
@@ -142,7 +142,8 @@ use profile_traits::time;
use script_traits::CompositorEvent::{MouseButtonEvent, MouseMoveEvent};
use script_traits::{webdriver_msg, LogEntry, ScriptToConstellationChan, ServiceWorkerMsg};
use script_traits::{
- AnimationState, AnimationTickType, AuxiliaryBrowsingContextLoadInfo, CompositorEvent,
+ AnimationState, AnimationTickType, AuxiliaryBrowsingContextLoadInfo, BroadcastMsg,
+ CompositorEvent,
};
use script_traits::{ConstellationControlMsg, DiscardBrowsingContext};
use script_traits::{DocumentActivity, DocumentState, LayoutControlMsg, LoadData, LoadOrigin};
@@ -399,6 +400,12 @@ pub struct Constellation<Message, LTF, STF> {
/// A map of router-id to ipc-sender, to route messages to ports.
message_port_routers: HashMap<MessagePortRouterId, IpcSender<MessagePortMsg>>,
+ /// A map of broadcast routers to their IPC sender.
+ broadcast_routers: HashMap<BroadcastChannelRouterId, IpcSender<BroadcastMsg>>,
+
+ /// A map of origin to a map of channel-name to a list of relevant routers.
+ broadcast_channels: HashMap<ImmutableOrigin, HashMap<String, Vec<BroadcastChannelRouterId>>>,
+
/// The set of all the pipelines in the browser. (See the `pipeline` module
/// for more details.)
pipelines: HashMap<PipelineId, Pipeline>,
@@ -961,6 +968,8 @@ where
browsing_context_group_next_id: Default::default(),
message_ports: HashMap::new(),
message_port_routers: HashMap::new(),
+ broadcast_routers: HashMap::new(),
+ broadcast_channels: HashMap::new(),
pipelines: HashMap::new(),
browsing_contexts: HashMap::new(),
pending_changes: vec![],
@@ -1760,6 +1769,36 @@ where
FromScriptMsg::EntanglePorts(port1, port2) => {
self.handle_entangle_messageports(port1, port2);
},
+ FromScriptMsg::NewBroadcastChannelRouter(router_id, ipc_sender, origin) => {
+ self.handle_new_broadcast_channel_router(
+ source_pipeline_id,
+ router_id,
+ ipc_sender,
+ origin,
+ );
+ },
+ FromScriptMsg::NewBroadcastChannelNameInRouter(router_id, channel_name, origin) => {
+ self.handle_new_broadcast_channel_name_in_router(
+ source_pipeline_id,
+ router_id,
+ channel_name,
+ origin,
+ );
+ },
+ FromScriptMsg::RemoveBroadcastChannelNameInRouter(router_id, channel_name, origin) => {
+ self.handle_remove_broadcast_channel_name_in_router(
+ source_pipeline_id,
+ router_id,
+ channel_name,
+ origin,
+ );
+ },
+ FromScriptMsg::RemoveBroadcastChannelRouter(router_id, origin) => {
+ self.handle_remove_broadcast_channel_router(source_pipeline_id, router_id, origin);
+ },
+ FromScriptMsg::ScheduleBroadcast(router_id, message) => {
+ self.handle_schedule_broadcast(source_pipeline_id, router_id, message);
+ },
FromScriptMsg::ForwardToEmbedder(embedder_msg) => {
self.embedder_proxy
.send((Some(source_top_ctx_id), embedder_msg));
@@ -1976,6 +2015,170 @@ where
}
}
+ /// Check the origin of a message against that of the pipeline it came from.
+ /// Note: this is still limited as a security check,
+ /// see https://github.com/servo/servo/issues/11722
+ fn check_origin_against_pipeline(
+ &self,
+ pipeline_id: &PipelineId,
+ origin: &ImmutableOrigin,
+ ) -> Result<(), ()> {
+ let pipeline_origin = match self.pipelines.get(&pipeline_id) {
+ Some(pipeline) => pipeline.load_data.url.origin(),
+ None => {
+ warn!("Received message from closed or unknown pipeline.");
+ return Err(());
+ },
+ };
+ if &pipeline_origin == origin {
+ return Ok(());
+ }
+ Err(())
+ }
+
+ /// Broadcast a message via routers in various event-loops.
+ fn handle_schedule_broadcast(
+ &self,
+ pipeline_id: PipelineId,
+ router_id: BroadcastChannelRouterId,
+ message: BroadcastMsg,
+ ) {
+ if self
+ .check_origin_against_pipeline(&pipeline_id, &message.origin)
+ .is_err()
+ {
+ return warn!(
+ "Attempt to schedule broadcast from an origin not matching the origin of the msg."
+ );
+ }
+ if let Some(channels) = self.broadcast_channels.get(&message.origin) {
+ let routers = match channels.get(&message.channel_name) {
+ Some(routers) => routers,
+ None => return warn!("Broadcast to channel name without active routers."),
+ };
+ for router in routers {
+ // Exclude the sender of the broadcast.
+ // Broadcasting locally is done at the point of sending.
+ if router == &router_id {
+ continue;
+ }
+
+ if let Some(sender) = self.broadcast_routers.get(&router) {
+ if sender.send(message.clone()).is_err() {
+ warn!("Failed to broadcast message to router: {:?}", router);
+ }
+ } else {
+ warn!("No sender for broadcast router: {:?}", router);
+ }
+ }
+ } else {
+ warn!(
+ "Attempt to schedule a broadcast for an origin without routers {:?}",
+ message.origin
+ );
+ }
+ }
+
+ /// Remove a channel-name for a given broadcast router.
+ fn handle_remove_broadcast_channel_name_in_router(
+ &mut self,
+ pipeline_id: PipelineId,
+ router_id: BroadcastChannelRouterId,
+ channel_name: String,
+ origin: ImmutableOrigin,
+ ) {
+ if self
+ .check_origin_against_pipeline(&pipeline_id, &origin)
+ .is_err()
+ {
+ return warn!("Attempt to remove channel name from an unexpected origin.");
+ }
+ if let Some(channels) = self.broadcast_channels.get_mut(&origin) {
+ let is_empty = if let Some(routers) = channels.get_mut(&channel_name) {
+ routers.retain(|router| router != &router_id);
+ routers.is_empty()
+ } else {
+ return warn!(
+ "Multiple attemps to remove name for broadcast-channel {:?} at {:?}",
+ channel_name, origin
+ );
+ };
+ if is_empty {
+ channels.remove(&channel_name);
+ }
+ } else {
+ warn!(
+ "Attempt to remove a channel-name for an origin without channels {:?}",
+ origin
+ );
+ }
+ }
+
+ /// Note a new channel-name relevant to a given broadcast router.
+ fn handle_new_broadcast_channel_name_in_router(
+ &mut self,
+ pipeline_id: PipelineId,
+ router_id: BroadcastChannelRouterId,
+ channel_name: String,
+ origin: ImmutableOrigin,
+ ) {
+ if self
+ .check_origin_against_pipeline(&pipeline_id, &origin)
+ .is_err()
+ {
+ return warn!("Attempt to add channel name from an unexpected origin.");
+ }
+ let channels = self
+ .broadcast_channels
+ .entry(origin)
+ .or_insert_with(HashMap::new);
+
+ let routers = channels.entry(channel_name).or_insert_with(Vec::new);
+
+ routers.push(router_id);
+ }
+
+ /// Remove a broadcast router.
+ fn handle_remove_broadcast_channel_router(
+ &mut self,
+ pipeline_id: PipelineId,
+ router_id: BroadcastChannelRouterId,
+ origin: ImmutableOrigin,
+ ) {
+ if self
+ .check_origin_against_pipeline(&pipeline_id, &origin)
+ .is_err()
+ {
+ return warn!("Attempt to remove broadcast router from an unexpected origin.");
+ }
+ if self.broadcast_routers.remove(&router_id).is_none() {
+ warn!("Attempt to remove unknown broadcast-channel router.");
+ }
+ }
+
+ /// Add a new broadcast router.
+ fn handle_new_broadcast_channel_router(
+ &mut self,
+ pipeline_id: PipelineId,
+ router_id: BroadcastChannelRouterId,
+ ipc_sender: IpcSender<BroadcastMsg>,
+ origin: ImmutableOrigin,
+ ) {
+ if self
+ .check_origin_against_pipeline(&pipeline_id, &origin)
+ .is_err()
+ {
+ return warn!("Attempt to add broadcast router from an unexpected origin.");
+ }
+ if self
+ .broadcast_routers
+ .insert(router_id, ipc_sender)
+ .is_some()
+ {
+ warn!("Multple attempt to add broadcast-channel router.");
+ }
+ }
+
fn handle_request_wgpu_adapter(
&mut self,
source_pipeline_id: PipelineId,
diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs
index f4e7a517673..a878bc43d3d 100644
--- a/components/msg/constellation_msg.rs
+++ b/components/msg/constellation_msg.rs
@@ -171,6 +171,13 @@ impl PipelineNamespace {
}
}
+ fn next_broadcast_channel_router_id(&mut self) -> BroadcastChannelRouterId {
+ BroadcastChannelRouterId {
+ namespace_id: self.id,
+ index: BroadcastChannelRouterIndex(self.next_index()),
+ }
+ }
+
fn next_blob_id(&mut self) -> BlobId {
BlobId {
namespace_id: self.id,
@@ -381,6 +388,42 @@ impl fmt::Display for MessagePortRouterId {
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
+pub struct BroadcastChannelRouterIndex(pub NonZeroU32);
+malloc_size_of_is_0!(BroadcastChannelRouterIndex);
+
+#[derive(
+ Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize,
+)]
+pub struct BroadcastChannelRouterId {
+ pub namespace_id: PipelineNamespaceId,
+ pub index: BroadcastChannelRouterIndex,
+}
+
+impl BroadcastChannelRouterId {
+ pub fn new() -> BroadcastChannelRouterId {
+ PIPELINE_NAMESPACE.with(|tls| {
+ let mut namespace = tls.get().expect("No namespace set for this thread!");
+ let next_broadcast_channel_router_id = namespace.next_broadcast_channel_router_id();
+ tls.set(Some(namespace));
+ next_broadcast_channel_router_id
+ })
+ }
+}
+
+impl fmt::Display for BroadcastChannelRouterId {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ let PipelineNamespaceId(namespace_id) = self.namespace_id;
+ let BroadcastChannelRouterIndex(index) = self.index;
+ write!(
+ fmt,
+ "(BroadcastChannelRouterId{},{})",
+ namespace_id,
+ index.get()
+ )
+ }
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct BlobIndex(pub NonZeroU32);
malloc_size_of_is_0!(BlobIndex);
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs
index 3fb93b98dfa..5e99e78d03d 100644
--- a/components/script/dom/bindings/trace.rs
+++ b/components/script/dom/bindings/trace.rs
@@ -83,8 +83,8 @@ use media::WindowGLContext;
use metrics::{InteractiveMetrics, InteractiveWindow};
use mime::Mime;
use msg::constellation_msg::{
- BlobId, BrowsingContextId, HistoryStateId, MessagePortId, MessagePortRouterId, PipelineId,
- TopLevelBrowsingContextId,
+ BlobId, BroadcastChannelRouterId, BrowsingContextId, HistoryStateId, MessagePortId,
+ MessagePortRouterId, PipelineId, TopLevelBrowsingContextId,
};
use net_traits::filemanager_thread::RelativePos;
use net_traits::image::base::{Image, ImageMetadata};
@@ -175,6 +175,8 @@ unsafe_no_jsmanaged_fields!(MessagePortId);
unsafe_no_jsmanaged_fields!(RefCell<Option<MessagePortId>>);
unsafe_no_jsmanaged_fields!(MessagePortRouterId);
+unsafe_no_jsmanaged_fields!(BroadcastChannelRouterId);
+
unsafe_no_jsmanaged_fields!(BlobId);
unsafe_no_jsmanaged_fields!(BlobImpl);
diff --git a/components/script/dom/broadcastchannel.rs b/components/script/dom/broadcastchannel.rs
new file mode 100644
index 00000000000..4a1e35d2ad8
--- /dev/null
+++ b/components/script/dom/broadcastchannel.rs
@@ -0,0 +1,106 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use crate::dom::bindings::codegen::Bindings::BroadcastChannelBinding::{
+ BroadcastChannelMethods, Wrap,
+};
+use crate::dom::bindings::error::{Error, ErrorResult};
+use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::bindings::structuredclone;
+use crate::dom::eventtarget::EventTarget;
+use crate::dom::globalscope::GlobalScope;
+use crate::script_runtime::JSContext as SafeJSContext;
+use dom_struct::dom_struct;
+use js::rust::HandleValue;
+use script_traits::BroadcastMsg;
+use std::cell::Cell;
+use uuid::Uuid;
+
+#[dom_struct]
+pub struct BroadcastChannel {
+ eventtarget: EventTarget,
+ name: DOMString,
+ closed: Cell<bool>,
+ id: Uuid,
+}
+
+impl BroadcastChannel {
+ /// <https://html.spec.whatwg.org/multipage/#broadcastchannel>
+ #[allow(non_snake_case)]
+ pub fn Constructor(global: &GlobalScope, name: DOMString) -> DomRoot<BroadcastChannel> {
+ BroadcastChannel::new(global, name)
+ }
+
+ pub fn new(global: &GlobalScope, name: DOMString) -> DomRoot<BroadcastChannel> {
+ let channel = reflect_dom_object(
+ Box::new(BroadcastChannel::new_inherited(name)),
+ global,
+ Wrap,
+ );
+ global.track_broadcast_channel(&*channel);
+ channel
+ }
+
+ pub fn new_inherited(name: DOMString) -> BroadcastChannel {
+ BroadcastChannel {
+ eventtarget: EventTarget::new_inherited(),
+ name,
+ closed: Default::default(),
+ id: Uuid::new_v4(),
+ }
+ }
+
+ /// The unique Id of this channel.
+ /// Used for filtering out the sender from the local broadcast.
+ pub fn id(&self) -> &Uuid {
+ &self.id
+ }
+
+ /// Is this channel closed?
+ pub fn closed(&self) -> bool {
+ self.closed.get()
+ }
+}
+
+impl BroadcastChannelMethods for BroadcastChannel {
+ /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage>
+ fn PostMessage(&self, cx: SafeJSContext, message: HandleValue) -> ErrorResult {
+ // Step 3, if closed.
+ if self.closed.get() {
+ return Err(Error::InvalidState);
+ }
+
+ // Step 6, StructuredSerialize(message).
+ let data = structuredclone::write(cx, message, None)?;
+
+ let global = self.global();
+
+ let msg = BroadcastMsg {
+ origin: global.origin().immutable().clone(),
+ channel_name: self.Name().to_string(),
+ data,
+ };
+
+ global.schedule_broadcast(msg, &self.id);
+ Ok(())
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-name>
+ fn Name(&self) -> DOMString {
+ self.name.clone()
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-close>
+ fn Close(&self) {
+ self.closed.set(true);
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#handler-broadcastchannel-onmessageerror>
+ event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
+
+ /// <https://html.spec.whatwg.org/multipage/#handler-broadcastchannel-onmessage>
+ event_handler!(message, GetOnmessage, SetOnmessage);
+}
diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs
index de3e83101e1..2ee7190cfed 100644
--- a/components/script/dom/globalscope.rs
+++ b/components/script/dom/globalscope.rs
@@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::BroadcastChannelBinding::BroadcastChannelMethods;
use crate::dom::bindings::codegen::Bindings::EventSourceBinding::EventSourceBinding::EventSourceMethods;
use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
@@ -19,6 +20,7 @@ use crate::dom::bindings::structuredclone;
use crate::dom::bindings::utils::to_frozen_array;
use crate::dom::bindings::weakref::{DOMTracker, WeakRef};
use crate::dom::blob::Blob;
+use crate::dom::broadcastchannel::BroadcastChannel;
use crate::dom::crypto::Crypto;
use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
use crate::dom::errorevent::ErrorEvent;
@@ -71,7 +73,9 @@ use js::rust::wrappers::EvaluateUtf8;
use js::rust::{get_object_class, CompileOptionsWrapper, ParentRuntime, Runtime};
use js::rust::{HandleValue, MutableHandleValue};
use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
-use msg::constellation_msg::{BlobId, MessagePortId, MessagePortRouterId, PipelineId};
+use msg::constellation_msg::{
+ BlobId, BroadcastChannelRouterId, MessagePortId, MessagePortRouterId, PipelineId,
+};
use net_traits::blob_url_store::{get_blob_origin, BlobBuf};
use net_traits::filemanager_thread::{
FileManagerResult, FileManagerThreadMsg, ReadFileProgress, RelativePos,
@@ -82,7 +86,8 @@ use profile_traits::{ipc as profile_ipc, mem as profile_mem, time as profile_tim
use script_traits::serializable::{BlobData, BlobImpl, FileBlob};
use script_traits::transferable::MessagePortImpl;
use script_traits::{
- MessagePortMsg, MsDuration, PortMessageTask, ScriptMsg, ScriptToConstellationChan, TimerEvent,
+ BroadcastMsg, MessagePortMsg, MsDuration, PortMessageTask, ScriptMsg,
+ ScriptToConstellationChan, TimerEvent,
};
use script_traits::{TimerEventId, TimerSchedulerMsg, TimerSource};
use servo_url::{MutableOrigin, ServoUrl};
@@ -124,6 +129,9 @@ pub struct GlobalScope {
/// The message-port router id for this global, if it is managing ports.
message_port_state: DomRefCell<MessagePortState>,
+ /// The broadcast channels state this global, if it is managing any.
+ broadcast_channel_state: DomRefCell<BroadcastChannelState>,
+
/// The blobs managed by this global, if any.
blob_state: DomRefCell<BlobState>,
@@ -237,6 +245,13 @@ struct MessageListener {
context: Trusted<GlobalScope>,
}
+/// A wrapper for broadcasts coming in over IPC, and the event-loop.
+struct BroadcastListener {
+ canceller: TaskCanceller,
+ task_source: DOMManipulationTaskSource,
+ context: Trusted<GlobalScope>,
+}
+
/// A wrapper between timer events coming in over IPC, and the event-loop.
struct TimerListener {
canceller: TaskCanceller,
@@ -310,6 +325,23 @@ pub struct ManagedMessagePort {
closed: bool,
}
+/// State representing whether this global is currently managing broadcast channels.
+#[derive(JSTraceable, MallocSizeOf)]
+#[unrooted_must_root_lint::must_root]
+pub enum BroadcastChannelState {
+ /// The broadcast-channel router id for this global, and a queue of managed channels.
+ /// Step 9, "sort destinations"
+ /// of https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage
+ /// requires keeping track of creation order, hence the queue.
+ Managed(
+ BroadcastChannelRouterId,
+ /// The map of channel-name to queue of channels, in order of creation.
+ HashMap<DOMString, VecDeque<Dom<BroadcastChannel>>>,
+ ),
+ /// This global is not managing any broadcast channels at this time.
+ UnManaged,
+}
+
/// State representing whether this global is currently managing messageports.
#[derive(JSTraceable, MallocSizeOf)]
#[unrooted_must_root_lint::must_root]
@@ -323,6 +355,29 @@ pub enum MessagePortState {
UnManaged,
}
+impl BroadcastListener {
+ /// Handle a broadcast coming in over IPC,
+ /// by queueing the appropriate task on the relevant event-loop.
+ fn handle(&self, event: BroadcastMsg) {
+ let context = self.context.clone();
+
+ // Note: strictly speaking we should just queue the message event tasks,
+ // not queue a task that then queues more tasks.
+ // This however seems to be hard to avoid in the light of the IPC.
+ // One can imagine queueing tasks directly,
+ // for channels that would be in the same script-thread.
+ let _ = self.task_source.queue_with_canceller(
+ task!(broadcast_message_event: move || {
+ let global = context.root();
+ // Step 10 of https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage,
+ // For each BroadcastChannel object destination in destinations, queue a task.
+ global.broadcast_message_event(event, None);
+ }),
+ &self.canceller,
+ );
+ }
+}
+
impl TimerListener {
/// Handle a timer-event coming-in over IPC,
/// by queuing the appropriate task on the relevant event-loop.
@@ -501,6 +556,7 @@ impl GlobalScope {
) -> Self {
Self {
message_port_state: DomRefCell::new(MessagePortState::UnManaged),
+ broadcast_channel_state: DomRefCell::new(BroadcastChannelState::UnManaged),
blob_state: DomRefCell::new(BlobState::UnManaged),
eventtarget: EventTarget::new_inherited(),
crypto: Default::default(),
@@ -613,11 +669,18 @@ impl GlobalScope {
pub fn perform_a_dom_garbage_collection_checkpoint(&self) {
self.perform_a_message_port_garbage_collection_checkpoint();
self.perform_a_blob_garbage_collection_checkpoint();
+ self.perform_a_broadcast_channel_garbage_collection_checkpoint();
+ }
+
+ /// Remove the routers for ports and broadcast-channels.
+ pub fn remove_web_messaging_infra(&self) {
+ self.remove_message_ports_router();
+ self.remove_broadcast_channel_router();
}
/// Update our state to un-managed,
/// and tell the constellation to drop the sender to our message-port router.
- pub fn remove_message_ports_router(&self) {
+ fn remove_message_ports_router(&self) {
if let MessagePortState::Managed(router_id, _message_ports) =
&*self.message_port_state.borrow()
{
@@ -628,6 +691,22 @@ impl GlobalScope {
*self.message_port_state.borrow_mut() = MessagePortState::UnManaged;
}
+ /// Update our state to un-managed,
+ /// and tell the constellation to drop the sender to our broadcast router.
+ fn remove_broadcast_channel_router(&self) {
+ if let BroadcastChannelState::Managed(router_id, _channels) =
+ &*self.broadcast_channel_state.borrow()
+ {
+ let _ =
+ self.script_to_constellation_chan()
+ .send(ScriptMsg::RemoveBroadcastChannelRouter(
+ router_id.clone(),
+ self.origin().immutable().clone(),
+ ));
+ }
+ *self.broadcast_channel_state.borrow_mut() = BroadcastChannelState::UnManaged;
+ }
+
/// <https://html.spec.whatwg.org/multipage/#entangle>
pub fn entangle_ports(&self, port1: MessagePortId, port2: MessagePortId) {
if let MessagePortState::Managed(_id, message_ports) =
@@ -789,6 +868,115 @@ impl GlobalScope {
.send(ScriptMsg::RerouteMessagePort(port_id, task));
}
+ /// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage>
+ /// Step 7 and following steps.
+ pub fn schedule_broadcast(&self, msg: BroadcastMsg, channel_id: &Uuid) {
+ // First, broadcast locally.
+ self.broadcast_message_event(msg.clone(), Some(channel_id));
+
+ if let BroadcastChannelState::Managed(router_id, _) =
+ &*self.broadcast_channel_state.borrow()
+ {
+ // Second, broadcast to other globals via the constellation.
+ //
+ // Note: for globals in the same script-thread,
+ // we could skip the hop to the constellation.
+ let _ = self
+ .script_to_constellation_chan()
+ .send(ScriptMsg::ScheduleBroadcast(router_id.clone(), msg));
+ } else {
+ panic!("Attemps to broadcast a message via global not managing any channels.");
+ }
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage>
+ /// Step 7 and following steps.
+ pub fn broadcast_message_event(&self, event: BroadcastMsg, channel_id: Option<&Uuid>) {
+ if let BroadcastChannelState::Managed(_, channels) = &*self.broadcast_channel_state.borrow()
+ {
+ let BroadcastMsg {
+ data,
+ origin,
+ channel_name,
+ } = event;
+
+ // Step 7, a few preliminary steps.
+
+ // - Check the worker is not closing.
+ if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
+ if worker.is_closing() {
+ return;
+ }
+ }
+
+ // - Check the associated document is fully-active.
+ if let Some(window) = self.downcast::<Window>() {
+ if !window.Document().is_fully_active() {
+ return;
+ }
+ }
+
+ // - Check for a case-sensitive match for the name of the channel.
+ let channel_name = DOMString::from_string(channel_name);
+
+ if let Some(channels) = channels.get(&channel_name) {
+ channels
+ .iter()
+ .filter(|ref channel| {
+ // Step 8.
+ // Filter out the sender.
+ if let Some(id) = channel_id {
+ channel.id() != id
+ } else {
+ true
+ }
+ })
+ .map(|channel| DomRoot::from_ref(&**channel))
+ // Step 9, sort by creation order,
+ // done by using a queue to store channels in creation order.
+ .for_each(|channel| {
+ let data = data.clone_for_broadcast();
+ let origin = origin.clone();
+
+ // Step 10: Queue a task on the DOM manipulation task-source,
+ // to fire the message event
+ let channel = Trusted::new(&*channel);
+ let global = Trusted::new(&*self);
+ let _ = self.dom_manipulation_task_source().queue(
+ task!(process_pending_port_messages: move || {
+ let destination = channel.root();
+ let global = global.root();
+
+ // 10.1 Check for closed flag.
+ if destination.closed() {
+ return;
+ }
+
+ rooted!(in(*global.get_cx()) let mut message = UndefinedValue());
+
+ // Step 10.3 StructuredDeserialize(serialized, targetRealm).
+ if let Ok(ports) = structuredclone::read(&global, data, message.handle_mut()) {
+ // Step 10.4, Fire an event named message at destination.
+ MessageEvent::dispatch_jsval(
+ &*destination.upcast(),
+ &global,
+ message.handle(),
+ Some(&origin.ascii_serialization()),
+ None,
+ ports,
+ );
+ } else {
+ // Step 10.3, fire an event named messageerror at destination.
+ MessageEvent::dispatch_error(&*destination.upcast(), &global);
+ }
+ }),
+ &self,
+ );
+ });
+ }
+ }
+ }
+
/// Route the task to be handled by the relevant port.
pub fn route_task_to_port(&self, port_id: MessagePortId, task: PortMessageTask) {
let should_dispatch = if let MessagePortState::Managed(_id, message_ports) =
@@ -905,6 +1093,93 @@ impl GlobalScope {
}
}
+ /// Remove broadcast-channels that are closed.
+ /// TODO: Also remove them if they do not have an event-listener.
+ /// see https://github.com/servo/servo/issues/25772
+ pub fn perform_a_broadcast_channel_garbage_collection_checkpoint(&self) {
+ let is_empty = if let BroadcastChannelState::Managed(router_id, ref mut channels) =
+ &mut *self.broadcast_channel_state.borrow_mut()
+ {
+ channels.retain(|name, ref mut channels| {
+ channels.retain(|ref chan| !chan.closed());
+ if channels.is_empty() {
+ let _ = self.script_to_constellation_chan().send(
+ ScriptMsg::RemoveBroadcastChannelNameInRouter(
+ router_id.clone(),
+ name.to_string(),
+ self.origin().immutable().clone(),
+ ),
+ );
+ false
+ } else {
+ true
+ }
+ });
+ channels.is_empty()
+ } else {
+ false
+ };
+ if is_empty {
+ self.remove_broadcast_channel_router();
+ }
+ }
+
+ /// Start tracking a broadcast-channel.
+ pub fn track_broadcast_channel(&self, dom_channel: &BroadcastChannel) {
+ let mut current_state = self.broadcast_channel_state.borrow_mut();
+
+ if let BroadcastChannelState::UnManaged = &*current_state {
+ // Setup a route for IPC, for broadcasts from the constellation to our channels.
+ let (broadcast_control_sender, broadcast_control_receiver) =
+ ipc::channel().expect("ipc channel failure");
+ let context = Trusted::new(self);
+ let (task_source, canceller) = (
+ self.dom_manipulation_task_source(),
+ self.task_canceller(TaskSourceName::DOMManipulation),
+ );
+ let listener = BroadcastListener {
+ canceller,
+ task_source,
+ context,
+ };
+ ROUTER.add_route(
+ broadcast_control_receiver.to_opaque(),
+ Box::new(move |message| {
+ let msg = message.to();
+ match msg {
+ Ok(msg) => listener.handle(msg),
+ Err(err) => warn!("Error receiving a BroadcastMsg: {:?}", err),
+ }
+ }),
+ );
+ let router_id = BroadcastChannelRouterId::new();
+ *current_state = BroadcastChannelState::Managed(router_id.clone(), HashMap::new());
+ let _ = self
+ .script_to_constellation_chan()
+ .send(ScriptMsg::NewBroadcastChannelRouter(
+ router_id,
+ broadcast_control_sender,
+ self.origin().immutable().clone(),
+ ));
+ }
+
+ if let BroadcastChannelState::Managed(router_id, channels) = &mut *current_state {
+ let entry = channels.entry(dom_channel.Name()).or_insert_with(|| {
+ let _ = self.script_to_constellation_chan().send(
+ ScriptMsg::NewBroadcastChannelNameInRouter(
+ router_id.clone(),
+ dom_channel.Name().to_string(),
+ self.origin().immutable().clone(),
+ ),
+ );
+ VecDeque::new()
+ });
+ entry.push_back(Dom::from_ref(dom_channel));
+ } else {
+ panic!("track_broadcast_channel should have first switched the state to managed.");
+ }
+ }
+
/// Start tracking a message-port
pub fn track_message_port(&self, dom_port: &MessagePort, port_impl: Option<MessagePortImpl>) {
let mut current_state = self.message_port_state.borrow_mut();
diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs
index e25a88b9da2..fba7200c892 100644
--- a/components/script/dom/mod.rs
+++ b/components/script/dom/mod.rs
@@ -239,6 +239,7 @@ pub mod bluetoothremotegattdescriptor;
pub mod bluetoothremotegattserver;
pub mod bluetoothremotegattservice;
pub mod bluetoothuuid;
+pub mod broadcastchannel;
pub mod canvasgradient;
pub mod canvaspattern;
pub mod canvasrenderingcontext2d;
diff --git a/components/script/dom/webidls/BroadcastChannel.webidl b/components/script/dom/webidls/BroadcastChannel.webidl
new file mode 100644
index 00000000000..6d72f3997cf
--- /dev/null
+++ b/components/script/dom/webidls/BroadcastChannel.webidl
@@ -0,0 +1,18 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+/*
+ * The origin of this IDL file is:
+ * https://html.spec.whatwg.org/multipage/#broadcastchannel
+ */
+
+[Exposed=(Window,Worker)]
+interface BroadcastChannel : EventTarget {
+ constructor(DOMString name);
+
+ readonly attribute DOMString name;
+ [Throws] void postMessage(any message);
+ void close();
+ attribute EventHandler onmessage;
+ attribute EventHandler onmessageerror;
+};
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index 77021c3df48..c2679c2899a 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -1407,8 +1407,8 @@ impl Window {
.upcast::<Node>()
.teardown(self.layout_chan());
- // Tell the constellation to drop the sender to our message-port router, if there is any.
- self.upcast::<GlobalScope>().remove_message_ports_router();
+ // Remove the infra for managing messageports and broadcast channels.
+ self.upcast::<GlobalScope>().remove_web_messaging_infra();
// Clean up any active promises
// https://github.com/servo/servo/issues/15318
diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs
index d928335f8e3..9b4588a3cb0 100644
--- a/components/script_traits/lib.rs
+++ b/components/script_traits/lib.rs
@@ -21,7 +21,7 @@ pub mod serializable;
pub mod transferable;
pub mod webdriver_msg;
-use crate::serializable::BlobImpl;
+use crate::serializable::{BlobData, BlobImpl};
use crate::transferable::MessagePortImpl;
use crate::webdriver_msg::{LoadStatus, WebDriverScriptCommand};
use bluetooth_traits::BluetoothRequest;
@@ -955,6 +955,48 @@ pub struct StructuredSerializedData {
pub ports: Option<HashMap<MessagePortId, MessagePortImpl>>,
}
+impl StructuredSerializedData {
+ /// Clone the serialized data for use with broadcast-channels.
+ pub fn clone_for_broadcast(&self) -> StructuredSerializedData {
+ let serialized = self.serialized.clone();
+
+ let blobs = if let Some(blobs) = self.blobs.as_ref() {
+ let mut blob_clones = HashMap::with_capacity(blobs.len());
+
+ for (original_id, blob) in blobs.iter() {
+ let type_string = blob.type_string();
+
+ if let BlobData::Memory(ref bytes) = blob.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.
+ blob_clones.insert(original_id.clone(), blob_clone);
+ } else {
+ // Not panicking only because this is called from the constellation.
+ warn!("Serialized blob not in memory format(should never happen).");
+ }
+ }
+ Some(blob_clones)
+ } else {
+ None
+ };
+
+ if self.ports.is_some() {
+ // Not panicking only because this is called from the constellation.
+ warn!("Attempt to broadcast structured serialized data including ports(should never happen).");
+ }
+
+ StructuredSerializedData {
+ serialized,
+ blobs,
+ // Ports cannot be broadcast.
+ ports: None,
+ }
+ }
+}
+
/// A task on the https://html.spec.whatwg.org/multipage/#port-message-queue
#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct PortMessageTask {
@@ -979,6 +1021,27 @@ pub enum MessagePortMsg {
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(),
+ }
+ }
+}
+
/// The type of MediaSession action.
/// https://w3c.github.io/mediasession/#enumdef-mediasessionaction
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
diff --git a/components/script_traits/script_msg.rs b/components/script_traits/script_msg.rs
index 8a04b59d57d..918b4f4ff78 100644
--- a/components/script_traits/script_msg.rs
+++ b/components/script_traits/script_msg.rs
@@ -4,6 +4,7 @@
use crate::AnimationState;
use crate::AuxiliaryBrowsingContextLoadInfo;
+use crate::BroadcastMsg;
use crate::DocumentState;
use crate::IFrameLoadInfoWithData;
use crate::LayoutControlMsg;
@@ -22,7 +23,8 @@ use euclid::Size2D;
use gfx_traits::Epoch;
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use msg::constellation_msg::{
- BrowsingContextId, MessagePortId, MessagePortRouterId, PipelineId, TopLevelBrowsingContextId,
+ BroadcastChannelRouterId, BrowsingContextId, MessagePortId, MessagePortRouterId, PipelineId,
+ TopLevelBrowsingContextId,
};
use msg::constellation_msg::{HistoryStateId, TraversalDirection};
use net_traits::request::RequestBuilder;
@@ -142,6 +144,21 @@ pub enum ScriptMsg {
RemoveMessagePort(MessagePortId),
/// Entangle two message-ports.
EntanglePorts(MessagePortId, MessagePortId),
+ /// A global has started managing broadcast-channels.
+ NewBroadcastChannelRouter(
+ BroadcastChannelRouterId,
+ IpcSender<BroadcastMsg>,
+ ImmutableOrigin,
+ ),
+ /// A global has stopped managing broadcast-channels.
+ RemoveBroadcastChannelRouter(BroadcastChannelRouterId, ImmutableOrigin),
+ /// A global started managing broadcast channels for a given channel-name.
+ NewBroadcastChannelNameInRouter(BroadcastChannelRouterId, String, ImmutableOrigin),
+ /// A global stopped managing broadcast channels for a given channel-name.
+ RemoveBroadcastChannelNameInRouter(BroadcastChannelRouterId, String, ImmutableOrigin),
+ /// Broadcast a message to all same-origin broadcast channels,
+ /// excluding the source of the broadcast.
+ ScheduleBroadcast(BroadcastChannelRouterId, BroadcastMsg),
/// Forward a message to the embedder.
ForwardToEmbedder(EmbedderMsg),
/// Requests are sent to constellation and fetches are checked manually
@@ -280,6 +297,11 @@ impl fmt::Debug for ScriptMsg {
RemoveMessagePort(..) => "RemoveMessagePort",
MessagePortShipped(..) => "MessagePortShipped",
EntanglePorts(..) => "EntanglePorts",
+ NewBroadcastChannelRouter(..) => "NewBroadcastChannelRouter",
+ RemoveBroadcastChannelRouter(..) => "RemoveBroadcastChannelRouter",
+ RemoveBroadcastChannelNameInRouter(..) => "RemoveBroadcastChannelNameInRouter",
+ NewBroadcastChannelNameInRouter(..) => "NewBroadcastChannelNameInRouter",
+ ScheduleBroadcast(..) => "ScheduleBroadcast",
ForwardToEmbedder(..) => "ForwardToEmbedder",
InitiateNavigateRequest(..) => "InitiateNavigateRequest",
BroadcastStorageEvent(..) => "BroadcastStorageEvent",