constellation/
broadcastchannel.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::collections::HashMap;
6
7use base::id::BroadcastChannelRouterId;
8use constellation_traits::BroadcastChannelMsg;
9use ipc_channel::ipc::IpcSender;
10use log::warn;
11use rustc_hash::FxHashMap;
12use servo_url::ImmutableOrigin;
13
14#[derive(Default)]
15pub(crate) struct BroadcastChannels {
16    /// A map of broadcast routers to their IPC sender.
17    routers: FxHashMap<BroadcastChannelRouterId, IpcSender<BroadcastChannelMsg>>,
18
19    /// A map of origin to a map of channel name to a list of relevant routers.
20    channels: HashMap<ImmutableOrigin, HashMap<String, Vec<BroadcastChannelRouterId>>>,
21}
22
23impl BroadcastChannels {
24    /// Add a new broadcast router.
25    #[servo_tracing::instrument(skip_all)]
26    pub fn new_broadcast_channel_router(
27        &mut self,
28        router_id: BroadcastChannelRouterId,
29        broadcast_ipc_sender: IpcSender<BroadcastChannelMsg>,
30    ) {
31        if self
32            .routers
33            .insert(router_id, broadcast_ipc_sender)
34            .is_some()
35        {
36            warn!("Multiple attempts to add BroadcastChannel router.");
37        }
38    }
39
40    /// Remove a broadcast router.
41    #[servo_tracing::instrument(skip_all)]
42    pub fn remove_broadcast_channel_router(&mut self, router_id: BroadcastChannelRouterId) {
43        if self.routers.remove(&router_id).is_none() {
44            warn!("Attempt to remove unknown BroadcastChannel router.");
45        }
46        // Also remove the router_id from the broadcast_channels list.
47        for channels in self.channels.values_mut() {
48            for routers in channels.values_mut() {
49                routers.retain(|router| router != &router_id);
50            }
51        }
52    }
53
54    /// Note a new channel-name relevant to a given broadcast router.
55    #[servo_tracing::instrument(skip_all)]
56    pub fn new_broadcast_channel_name_in_router(
57        &mut self,
58        router_id: BroadcastChannelRouterId,
59        channel_name: String,
60        origin: ImmutableOrigin,
61    ) {
62        let channels = self.channels.entry(origin).or_default();
63        let routers = channels.entry(channel_name).or_default();
64        routers.push(router_id);
65    }
66
67    /// Remove a channel-name for a given broadcast router.
68    #[servo_tracing::instrument(skip_all)]
69    pub fn remove_broadcast_channel_name_in_router(
70        &mut self,
71        router_id: BroadcastChannelRouterId,
72        channel_name: String,
73        origin: ImmutableOrigin,
74    ) {
75        if let Some(channels) = self.channels.get_mut(&origin) {
76            let is_empty = if let Some(routers) = channels.get_mut(&channel_name) {
77                routers.retain(|router| router != &router_id);
78                routers.is_empty()
79            } else {
80                return warn!(
81                    "Multiple attempts to remove name for BroadcastChannel {:?} at {:?}",
82                    channel_name, origin
83                );
84            };
85            if is_empty {
86                channels.remove(&channel_name);
87            }
88        } else {
89            warn!(
90                "Attempt to remove a channel name for an origin without channels {:?}",
91                origin
92            );
93        }
94    }
95
96    /// Broadcast a message via routers in various event-loops.
97    #[servo_tracing::instrument(skip_all)]
98    pub fn schedule_broadcast(
99        &self,
100        router_id: BroadcastChannelRouterId,
101        message: BroadcastChannelMsg,
102    ) {
103        if let Some(channels) = self.channels.get(&message.origin) {
104            let routers = match channels.get(&message.channel_name) {
105                Some(routers) => routers,
106                None => return warn!("Broadcast to channel name without active routers."),
107            };
108            for router in routers {
109                // Exclude the sender of the broadcast.
110                // Broadcasting locally is done at the point of sending.
111                if router == &router_id {
112                    continue;
113                }
114
115                if let Some(broadcast_ipc_sender) = self.routers.get(router) {
116                    if broadcast_ipc_sender.send(message.clone()).is_err() {
117                        warn!("Failed to broadcast message to router: {:?}", router);
118                    }
119                } else {
120                    warn!("No sender for broadcast router: {:?}", router);
121                }
122            }
123        } else {
124            warn!(
125                "Attempt to schedule a broadcast for an origin without routers {:?}",
126                message.origin
127            );
128        }
129    }
130}