Skip to main content

servo_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 log::warn;
8use rustc_hash::FxHashMap;
9use servo_base::generic_channel::GenericCallback;
10use servo_base::id::BroadcastChannelRouterId;
11use servo_constellation_traits::BroadcastChannelMsg;
12use servo_url::ImmutableOrigin;
13
14#[derive(Default)]
15pub(crate) struct BroadcastChannels {
16    /// A map of broadcast routers to their Generic sender.
17    routers: FxHashMap<BroadcastChannelRouterId, GenericCallback<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_callback: GenericCallback<BroadcastChannelMsg>,
30    ) {
31        if self.routers.insert(router_id, broadcast_callback).is_some() {
32            warn!("Multiple attempts to add BroadcastChannel router.");
33        }
34    }
35
36    /// Remove a broadcast router.
37    #[servo_tracing::instrument(skip_all)]
38    pub fn remove_broadcast_channel_router(&mut self, router_id: BroadcastChannelRouterId) {
39        if self.routers.remove(&router_id).is_none() {
40            warn!("Attempt to remove unknown BroadcastChannel router.");
41        }
42        // Also remove the router_id from the broadcast_channels list.
43        for channels in self.channels.values_mut() {
44            for routers in channels.values_mut() {
45                routers.retain(|router| router != &router_id);
46            }
47        }
48    }
49
50    /// Note a new channel-name relevant to a given broadcast router.
51    #[servo_tracing::instrument(skip_all)]
52    pub fn new_broadcast_channel_name_in_router(
53        &mut self,
54        router_id: BroadcastChannelRouterId,
55        channel_name: String,
56        origin: ImmutableOrigin,
57    ) {
58        let channels = self.channels.entry(origin).or_default();
59        let routers = channels.entry(channel_name).or_default();
60        routers.push(router_id);
61    }
62
63    /// Remove a channel-name for a given broadcast router.
64    #[servo_tracing::instrument(skip_all)]
65    pub fn remove_broadcast_channel_name_in_router(
66        &mut self,
67        router_id: BroadcastChannelRouterId,
68        channel_name: String,
69        origin: ImmutableOrigin,
70    ) {
71        if let Some(channels) = self.channels.get_mut(&origin) {
72            let is_empty = if let Some(routers) = channels.get_mut(&channel_name) {
73                routers.retain(|router| router != &router_id);
74                routers.is_empty()
75            } else {
76                return warn!(
77                    "Multiple attempts to remove name for BroadcastChannel {:?} at {:?}",
78                    channel_name, origin
79                );
80            };
81            if is_empty {
82                channels.remove(&channel_name);
83            }
84        } else {
85            warn!(
86                "Attempt to remove a channel name for an origin without channels {:?}",
87                origin
88            );
89        }
90    }
91
92    /// Broadcast a message via routers in various event-loops.
93    #[servo_tracing::instrument(skip_all)]
94    pub fn schedule_broadcast(
95        &self,
96        router_id: BroadcastChannelRouterId,
97        message: BroadcastChannelMsg,
98    ) {
99        if let Some(channels) = self.channels.get(&message.origin) {
100            let routers = match channels.get(&message.channel_name) {
101                Some(routers) => routers,
102                None => return warn!("Broadcast to channel name without active routers."),
103            };
104            for router in routers {
105                // Exclude the sender of the broadcast.
106                // Broadcasting locally is done at the point of sending.
107                if router == &router_id {
108                    continue;
109                }
110
111                if let Some(broadcast_ipc_sender) = self.routers.get(router) {
112                    if broadcast_ipc_sender.send(message.clone()).is_err() {
113                        warn!("Failed to broadcast message to router: {:?}", router);
114                    }
115                } else {
116                    warn!("No sender for broadcast router: {:?}", router);
117                }
118            }
119        } else {
120            warn!(
121                "Attempt to schedule a broadcast for an origin without routers {:?}",
122                message.origin
123            );
124        }
125    }
126}