use super::*;
use bao_ipc::{Receiver, Sender};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use dpi::PhysicalSize;
use servo::{
AllowOrDenyRequest, ConsoleLogLevel, CreateNewWebViewRequest, DeviceIntPoint, DeviceIntRect,
DeviceIntSize, EmbedderControl, EmbedderControlId, InputEventId, InputEventResult, LoadStatus,
NavigationRequest, PermissionRequest, ScreenGeometry, ServoDelegate, ServoError, TraversalId,
WebView, WebViewDelegate,
};
use bao_cdp::servo_bridge::main_frame_id_for_target;
use bao_cdp::{BaoEvent, ConsoleMessage};
use bao_cdp_client::bridge::{ConsoleLevel, ServoEvent};
#[derive(Debug, Clone)]
pub struct SharedWorkerGlobalScopeState {
pub scope: WorkerGlobalScopeState,
pub shared_worker_id: SharedWorkerId,
pub has_onconnect: bool,
pub connect_count: usize,
}
impl SharedWorkerGlobalScopeState {
pub fn new(shared_worker_id: SharedWorkerId, config: &SharedWorkerScopeConfig) -> Self {
let worker_url = shared_worker_id.script_url.clone();
SharedWorkerGlobalScopeState {
scope: WorkerGlobalScopeState::new_shared(worker_url, config),
shared_worker_id,
has_onconnect: false,
connect_count: 0,
}
}
pub fn location(&self) -> Option<&WorkerLocation> {
self.scope.location.as_ref()
}
pub fn navigator(&self) -> &WorkerNavigator {
&self.scope.navigator
}
pub fn set_onconnect(&mut self) {
self.has_onconnect = true;
}
pub fn page_connected(&mut self) {
self.connect_count += 1;
}
}
#[derive(Debug)]
pub struct SharedWorkerPortChannel {
pub shared_worker_id: SharedWorkerId,
pub page_to_worker_tx: Sender<StructuredClonePayload>,
pub worker_to_page_rx: Receiver<WorkerStructuredMessage>,
}
impl SharedWorkerPortChannel {
pub fn new(shared_worker_id: SharedWorkerId) -> (Self, SharedWorkerPortEndpoints) {
let (page_to_worker_tx, page_to_worker_rx) = bao_ipc::unbounded::<StructuredClonePayload>();
let (worker_to_page_tx, worker_to_page_rx) =
bao_ipc::unbounded::<WorkerStructuredMessage>();
let port = SharedWorkerPortChannel {
shared_worker_id: shared_worker_id.clone(),
page_to_worker_tx,
worker_to_page_rx,
};
let endpoints = SharedWorkerPortEndpoints {
shared_worker_id,
page_to_worker_rx: Some(page_to_worker_rx),
worker_to_page_tx: Some(worker_to_page_tx),
};
(port, endpoints)
}
pub fn post_message_to_worker(
&self,
payload: StructuredClonePayload,
) -> Result<(), bao_ipc::SendError<StructuredClonePayload>> {
self.page_to_worker_tx.send(payload)
}
pub fn try_recv_from_worker(&self) -> Result<Option<WorkerStructuredMessage>, ()> {
try_recv_worker_msg(&self.worker_to_page_rx)
}
pub fn drain_worker_messages(&self) -> WorkerDrainResult {
drain_worker_rx(&self.worker_to_page_rx)
}
}
#[derive(Debug)]
pub struct SharedWorkerPortEndpoints {
pub shared_worker_id: SharedWorkerId,
pub page_to_worker_rx: Option<Receiver<StructuredClonePayload>>,
pub worker_to_page_tx: Option<Sender<WorkerStructuredMessage>>,
}
pub struct SharedWorkerChannelBridge {
pub shared_worker_id: SharedWorkerId,
pub port_channels: Vec<SharedWorkerPortChannel>,
}
impl SharedWorkerChannelBridge {
pub fn new(shared_worker_id: SharedWorkerId) -> Self {
SharedWorkerChannelBridge {
shared_worker_id,
port_channels: Vec::new(),
}
}
pub fn add_port(&mut self) -> SharedWorkerPortEndpoints {
let (port, endpoints) = SharedWorkerPortChannel::new(self.shared_worker_id.clone());
self.port_channels.push(port);
endpoints
}
pub fn drain_all_worker_messages(&self) -> (Vec<WorkerStructuredMessage>, Vec<SharedWorkerId>) {
let mut all_messages = Vec::new();
let mut disconnected = Vec::new();
for port in &self.port_channels {
let result = port.drain_worker_messages();
all_messages.extend(result.messages);
if result.disconnected {
disconnected.push(port.shared_worker_id.clone());
}
}
(all_messages, disconnected)
}
pub fn remove_disconnected_ports(&mut self) {
self.port_channels.retain(|port| {
match port.try_recv_from_worker() {
Ok(_) => true, Err(()) => false, }
});
}
pub fn port_count(&self) -> usize {
self.port_channels.len()
}
pub fn post_to_worker_from_port(
&self,
port_index: usize,
payload: StructuredClonePayload,
) -> Result<(), String> {
match self.port_channels.get(port_index) {
Some(port) => port
.post_message_to_worker(payload)
.map_err(|e| format!("SharedWorker port channel closed: {}", e)),
None => Err(format!(
"Invalid port index {} for SharedWorker",
port_index
)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SharedWorkerId {
pub script_url: String,
pub name: String,
}
#[derive(Debug, Clone)]
pub struct SharedWorkerHandle {
pub script_url: String,
pub name: String,
pub closing: Arc<AtomicBool>,
pub terminated: Arc<AtomicBool>,
pub connected_pages: Arc<std::sync::atomic::AtomicUsize>,
}
impl SharedWorkerHandle {
pub fn new(script_url: String, name: String) -> Self {
SharedWorkerHandle {
script_url,
name,
closing: Arc::new(AtomicBool::new(false)),
terminated: Arc::new(AtomicBool::new(false)),
connected_pages: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
}
}
pub fn id(&self) -> SharedWorkerId {
SharedWorkerId {
script_url: self.script_url.clone(),
name: self.name.clone(),
}
}
pub fn is_closing(&self) -> bool {
self.closing.load(Ordering::Acquire)
}
pub fn is_terminated(&self) -> bool {
self.terminated.load(Ordering::Acquire)
}
pub fn connected_page_count(&self) -> usize {
self.connected_pages.load(Ordering::Acquire)
}
pub fn close(&self) {
self.closing.store(true, Ordering::Release);
}
pub fn mark_terminated(&self) {
self.terminated.store(true, Ordering::Release);
}
pub fn page_connected(&self) {
self.connected_pages.fetch_add(1, Ordering::AcqRel);
}
pub fn page_disconnected(&self) -> usize {
self.connected_pages.fetch_sub(1, Ordering::AcqRel)
}
}
#[derive(Debug, Clone)]
pub struct SharedWorkerConnectEvent {
pub shared_worker_id: SharedWorkerId,
pub page_url: String,
}
#[derive(Debug, Clone)]
pub struct SharedWorkerScopeConfig {
pub stealth_profile: Option<bao_stealth::StealthProfile>,
pub user_agent: String,
pub platform: String,
pub hardware_concurrency: usize,
pub language: String,
pub languages: Vec<String>,
}
impl Default for SharedWorkerScopeConfig {
fn default() -> Self {
SharedWorkerScopeConfig {
stealth_profile: None,
user_agent: String::new(),
platform: String::new(),
hardware_concurrency: std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1),
language: "en-US".to_string(),
languages: vec!["en-US".to_string(), "en".to_string()],
}
}
}
#[derive(Debug)]
pub struct SharedWorkerPortRef {
handle: SharedWorkerHandle,
}
impl SharedWorkerPortRef {
pub fn new(handle: SharedWorkerHandle) -> Self {
handle.page_connected();
SharedWorkerPortRef { handle }
}
pub fn handle(&self) -> &SharedWorkerHandle {
&self.handle
}
}
impl Drop for SharedWorkerPortRef {
fn drop(&mut self) {
self.handle.page_disconnected();
}
}
impl Clone for SharedWorkerPortRef {
fn clone(&self) -> Self {
self.handle.page_connected();
SharedWorkerPortRef {
handle: self.handle.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shared_worker_id_equality() {
let id1 = SharedWorkerId {
script_url: "sw.js".to_string(),
name: "myworker".to_string(),
};
let id2 = SharedWorkerId {
script_url: "sw.js".to_string(),
name: "myworker".to_string(),
};
let id3 = SharedWorkerId {
script_url: "sw.js".to_string(),
name: "other".to_string(),
};
let id4 = SharedWorkerId {
script_url: "other.js".to_string(),
name: "myworker".to_string(),
};
assert_eq!(id1, id2);
assert_ne!(id1, id3); assert_ne!(id1, id4); }
#[test]
fn test_shared_worker_id_default_name() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: String::new(),
};
assert!(id.name.is_empty());
}
#[test]
fn test_shared_worker_handle_new_is_running() {
let handle = SharedWorkerHandle::new("sw.js".to_string(), "myname".to_string());
assert_eq!(handle.script_url, "sw.js");
assert_eq!(handle.name, "myname");
assert!(!handle.is_closing());
assert!(!handle.is_terminated());
assert_eq!(handle.connected_page_count(), 0);
}
#[test]
fn test_shared_worker_handle_id() {
let handle = SharedWorkerHandle::new("sw.js".to_string(), "myname".to_string());
let id = handle.id();
assert_eq!(id.script_url, "sw.js");
assert_eq!(id.name, "myname");
}
#[test]
fn test_shared_worker_handle_close() {
let handle = SharedWorkerHandle::new("sw.js".to_string(), String::new());
assert!(!handle.is_closing());
handle.close();
assert!(handle.is_closing());
handle.close();
assert!(handle.is_closing());
}
#[test]
fn test_shared_worker_handle_mark_terminated() {
let handle = SharedWorkerHandle::new("sw.js".to_string(), String::new());
assert!(!handle.is_terminated());
handle.mark_terminated();
assert!(handle.is_terminated());
}
#[test]
fn test_shared_worker_handle_connected_pages() {
let handle = SharedWorkerHandle::new("sw.js".to_string(), String::new());
assert_eq!(handle.connected_page_count(), 0);
handle.page_connected();
assert_eq!(handle.connected_page_count(), 1);
handle.page_connected();
assert_eq!(handle.connected_page_count(), 2);
handle.page_disconnected();
assert_eq!(handle.connected_page_count(), 1);
handle.page_disconnected();
assert_eq!(handle.connected_page_count(), 0);
}
#[test]
fn test_shared_worker_handle_clone_shares_state() {
let handle = SharedWorkerHandle::new("sw.js".to_string(), "name".to_string());
let clone = handle.clone();
handle.close();
assert!(
clone.is_closing(),
"clone should see closing flag from original"
);
clone.mark_terminated();
assert!(
handle.is_terminated(),
"original should see terminated flag from clone"
);
}
#[test]
fn test_shared_worker_port_ref_increments_connected() {
let handle = SharedWorkerHandle::new("sw.js".to_string(), String::new());
let port = SharedWorkerPortRef::new(handle.clone());
assert_eq!(handle.connected_page_count(), 1);
assert_eq!(port.handle().script_url, "sw.js");
}
#[test]
fn test_shared_worker_port_ref_drop_decrements_connected() {
let handle = SharedWorkerHandle::new("sw.js".to_string(), String::new());
{
let _port = SharedWorkerPortRef::new(handle.clone());
assert_eq!(handle.connected_page_count(), 1);
}
assert_eq!(
handle.connected_page_count(),
0,
"dropping port should decrement connected count"
);
}
#[test]
fn test_shared_worker_port_ref_clone_increments_connected() {
let handle = SharedWorkerHandle::new("sw.js".to_string(), String::new());
let port = SharedWorkerPortRef::new(handle.clone());
assert_eq!(handle.connected_page_count(), 1);
let _port2 = port.clone();
assert_eq!(handle.connected_page_count(), 2);
}
#[test]
fn test_shared_worker_port_ref_multiple_pages() {
let handle = SharedWorkerHandle::new("sw.js".to_string(), "shared".to_string());
let _port1 = SharedWorkerPortRef::new(handle.clone());
let _port2 = SharedWorkerPortRef::new(handle.clone());
assert_eq!(handle.connected_page_count(), 2);
}
#[test]
fn test_webview_state_track_shared_worker_port() {
let mut state = BaoWebViewState::default();
let handle = SharedWorkerHandle::new("sw.js".to_string(), "myname".to_string());
state.track_shared_worker_port(SharedWorkerPortRef::new(handle));
assert_eq!(state.shared_worker_port_count(), 1);
}
#[test]
fn test_webview_state_disconnect_shared_worker_ports() {
let mut state = BaoWebViewState::default();
let handle = SharedWorkerHandle::new("sw.js".to_string(), String::new());
state.track_shared_worker_port(SharedWorkerPortRef::new(handle.clone()));
state.track_shared_worker_port(SharedWorkerPortRef::new(handle.clone()));
assert_eq!(state.shared_worker_port_count(), 2);
assert_eq!(handle.connected_page_count(), 2);
state.disconnect_shared_worker_ports();
assert_eq!(state.shared_worker_port_count(), 0);
assert_eq!(
handle.connected_page_count(),
0,
"disconnect should drop ports and decrement counter"
);
}
#[test]
fn test_webview_state_disconnect_shared_worker_ports_empty() {
let mut state = BaoWebViewState::default();
state.disconnect_shared_worker_ports();
assert_eq!(state.shared_worker_port_count(), 0);
}
#[test]
fn test_delegate_register_shared_worker_new() {
let delegate = BaoServoDelegate::new();
let handle = SharedWorkerHandle::new("sw.js".to_string(), "myname".to_string());
let returned = delegate.register_shared_worker(handle);
assert_eq!(returned.script_url, "sw.js");
assert_eq!(returned.name, "myname");
assert_eq!(delegate.shared_worker_count(), 1);
}
#[test]
fn test_delegate_register_shared_worker_dedup() {
let delegate = BaoServoDelegate::new();
let handle1 = SharedWorkerHandle::new("sw.js".to_string(), "myname".to_string());
let handle2 = SharedWorkerHandle::new("sw.js".to_string(), "myname".to_string());
delegate.register_shared_worker(handle1);
let returned = delegate.register_shared_worker(handle2);
assert_eq!(delegate.shared_worker_count(), 1);
assert_eq!(returned.script_url, "sw.js");
}
#[test]
fn test_delegate_find_shared_worker() {
let delegate = BaoServoDelegate::new();
let handle = SharedWorkerHandle::new("sw.js".to_string(), "myname".to_string());
delegate.register_shared_worker(handle);
let found = delegate.find_shared_worker("sw.js", "myname");
assert!(found.is_some());
assert_eq!(found.unwrap().script_url, "sw.js");
assert!(delegate.find_shared_worker("other.js", "myname").is_none());
assert!(delegate.find_shared_worker("sw.js", "other").is_none());
}
#[test]
fn test_delegate_reap_terminated_shared_workers() {
let delegate = BaoServoDelegate::new();
let handle = SharedWorkerHandle::new("sw.js".to_string(), String::new());
delegate.register_shared_worker(handle.clone());
assert_eq!(delegate.shared_worker_count(), 1);
handle.close();
handle.mark_terminated();
delegate.reap_terminated_shared_workers();
assert_eq!(
delegate.shared_worker_count(),
0,
"terminated shared worker with zero pages should be reaped"
);
}
#[test]
fn test_delegate_reap_keeps_terminated_with_connected_pages() {
let delegate = BaoServoDelegate::new();
let handle = SharedWorkerHandle::new("sw.js".to_string(), String::new());
delegate.register_shared_worker(handle.clone());
let _port = SharedWorkerPortRef::new(handle.clone());
handle.close();
handle.mark_terminated();
delegate.reap_terminated_shared_workers();
assert_eq!(
delegate.shared_worker_count(),
1,
"terminated but still has connected pages — keep in registry"
);
}
#[test]
fn test_shared_worker_connect_event_creation() {
let event = SharedWorkerConnectEvent {
shared_worker_id: SharedWorkerId {
script_url: "sw.js".to_string(),
name: "myname".to_string(),
},
page_url: "https://example.com/page1".to_string(),
};
assert_eq!(event.shared_worker_id.script_url, "sw.js");
assert_eq!(event.shared_worker_id.name, "myname");
assert_eq!(event.page_url, "https://example.com/page1");
}
#[test]
fn test_forward_shared_worker_connect_event() {
let (tx, rx) = bao_ipc::unbounded::<ServoEvent>();
let state = BaoWebViewState {
event_tx: Some(tx),
cdp_target_id: Some("7".to_string()),
..Default::default()
};
let event = SharedWorkerConnectEvent {
shared_worker_id: SharedWorkerId {
script_url: "sw.js".to_string(),
name: "myname".to_string(),
},
page_url: "https://example.com".to_string(),
};
state.forward_shared_worker_connect_event(event);
let recv = rx.try_recv().unwrap();
match recv {
ServoEvent::Console { level, text, .. } => {
assert_eq!(level, ConsoleLevel::Debug);
assert!(text.contains("sw.js"));
assert!(text.contains("myname"));
assert!(text.contains("https://example.com"));
}
_ => panic!("expected Console event for shared worker connect"),
}
}
#[test]
fn test_forward_shared_worker_connect_event_no_tx() {
let state = BaoWebViewState::default();
let event = SharedWorkerConnectEvent {
shared_worker_id: SharedWorkerId {
script_url: "sw.js".to_string(),
name: String::new(),
},
page_url: "https://example.com".to_string(),
};
state.forward_shared_worker_connect_event(event);
}
#[test]
fn test_shared_worker_scope_config_default() {
let config = SharedWorkerScopeConfig::default();
assert!(config.stealth_profile.is_none());
assert!(config.user_agent.is_empty());
assert!(config.platform.is_empty());
assert!(config.hardware_concurrency > 0);
assert_eq!(config.language, "en-US");
assert!(!config.languages.is_empty());
}
#[test]
fn test_page_navigation_disconnects_shared_workers() {
let mut state = BaoWebViewState::default();
let handle = SharedWorkerHandle::new("sw.js".to_string(), String::new());
state.track_shared_worker_port(SharedWorkerPortRef::new(handle.clone()));
assert_eq!(state.shared_worker_port_count(), 1);
assert_eq!(handle.connected_page_count(), 1);
state.disconnect_shared_worker_ports();
assert_eq!(state.shared_worker_port_count(), 0);
assert_eq!(handle.connected_page_count(), 0);
}
#[test]
fn test_shared_worker_cross_page_sharing() {
let handle = SharedWorkerHandle::new("sw.js".to_string(), "shared".to_string());
let mut state1 = BaoWebViewState::default();
state1.track_shared_worker_port(SharedWorkerPortRef::new(handle.clone()));
assert_eq!(handle.connected_page_count(), 1);
let mut state2 = BaoWebViewState::default();
state2.track_shared_worker_port(SharedWorkerPortRef::new(handle.clone()));
assert_eq!(handle.connected_page_count(), 2);
state1.disconnect_shared_worker_ports();
assert_eq!(handle.connected_page_count(), 1);
assert_eq!(state2.shared_worker_port_count(), 1);
assert!(!handle.is_closing());
assert!(!handle.is_terminated());
}
#[test]
fn test_shared_worker_global_scope_state_new() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: "myworker".to_string(),
};
let config = SharedWorkerScopeConfig {
stealth_profile: None,
user_agent: "Bao/1.0".to_string(),
platform: "Linux".to_string(),
hardware_concurrency: 8,
language: "en-US".to_string(),
languages: vec!["en-US".to_string()],
};
let scope = SharedWorkerGlobalScopeState::new(id.clone(), &config);
assert_eq!(scope.shared_worker_id, id);
assert!(!scope.has_onconnect);
assert_eq!(scope.connect_count, 0);
assert_eq!(scope.scope.navigator.user_agent, "Bao/1.0");
}
#[test]
fn test_shared_worker_global_scope_state_location() {
let id = SharedWorkerId {
script_url: "https://example.com/sw.js".to_string(),
name: String::new(),
};
let config = SharedWorkerScopeConfig::default();
let scope = SharedWorkerGlobalScopeState::new(id, &config);
let loc = scope.location().unwrap();
assert_eq!(loc.hostname, "example.com");
assert_eq!(loc.pathname, "/sw.js");
}
#[test]
fn test_shared_worker_global_scope_state_navigator() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: "test".to_string(),
};
let config = SharedWorkerScopeConfig {
stealth_profile: None,
user_agent: "Bao/2.0".to_string(),
platform: "MacOS".to_string(),
hardware_concurrency: 4,
language: "ja".to_string(),
languages: vec!["ja".to_string()],
};
let scope = SharedWorkerGlobalScopeState::new(id, &config);
let nav = scope.navigator();
assert_eq!(nav.user_agent, "Bao/2.0");
assert_eq!(nav.hardware_concurrency, 4);
}
#[test]
fn test_shared_worker_global_scope_state_onconnect() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: String::new(),
};
let config = SharedWorkerScopeConfig::default();
let mut scope = SharedWorkerGlobalScopeState::new(id, &config);
assert!(!scope.has_onconnect);
scope.set_onconnect();
assert!(scope.has_onconnect);
}
#[test]
fn test_shared_worker_global_scope_state_connect_count() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: String::new(),
};
let config = SharedWorkerScopeConfig::default();
let mut scope = SharedWorkerGlobalScopeState::new(id, &config);
assert_eq!(scope.connect_count, 0);
scope.page_connected();
assert_eq!(scope.connect_count, 1);
scope.page_connected();
assert_eq!(scope.connect_count, 2);
}
#[test]
fn test_shared_worker_port_channel_creation() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: "test".to_string(),
};
let (port, endpoints) = SharedWorkerPortChannel::new(id.clone());
assert_eq!(port.shared_worker_id, id);
assert_eq!(endpoints.shared_worker_id, id);
assert!(endpoints.page_to_worker_rx.is_some());
assert!(endpoints.worker_to_page_tx.is_some());
}
#[test]
fn test_shared_worker_port_channel_page_to_worker() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: String::new(),
};
let (port, endpoints) = SharedWorkerPortChannel::new(id);
let payload = StructuredClonePayload {
data: vec![1, 2, 3],
transferable_count: 0,
};
port.post_message_to_worker(payload).unwrap();
let rx = endpoints.page_to_worker_rx.unwrap();
let received = rx.try_recv().unwrap();
assert_eq!(received.data, vec![1, 2, 3]);
}
#[test]
fn test_shared_worker_port_channel_worker_to_page() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: String::new(),
};
let (port, endpoints) = SharedWorkerPortChannel::new(id);
let msg = WorkerStructuredMessage::with_payload(
WorkerId("sw.js".to_string()),
WorkerMessageDirection::WorkerToPage,
vec![4, 5, 6],
0,
);
let tx = endpoints.worker_to_page_tx.unwrap();
tx.send(msg).unwrap();
let result = port.try_recv_from_worker().unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().payload.unwrap().data, vec![4, 5, 6]);
}
#[test]
fn test_shared_worker_port_channel_drain() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: String::new(),
};
let (port, endpoints) = SharedWorkerPortChannel::new(id);
let tx = endpoints.worker_to_page_tx.unwrap();
for i in 0..3 {
let msg = WorkerStructuredMessage::with_payload(
WorkerId("sw.js".to_string()),
WorkerMessageDirection::WorkerToPage,
vec![i],
0,
);
tx.send(msg).unwrap();
}
let result = port.drain_worker_messages();
assert_eq!(result.messages.len(), 3);
assert!(!result.disconnected);
let empty = port.drain_worker_messages();
assert!(empty.messages.is_empty());
assert!(!empty.disconnected);
}
#[test]
fn test_shared_worker_channel_bridge_new() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: "test".to_string(),
};
let bridge = SharedWorkerChannelBridge::new(id.clone());
assert_eq!(bridge.shared_worker_id, id);
assert_eq!(bridge.port_count(), 0);
}
#[test]
fn test_shared_worker_channel_bridge_add_port() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: "test".to_string(),
};
let mut bridge = SharedWorkerChannelBridge::new(id.clone());
let endpoints = bridge.add_port();
assert_eq!(bridge.port_count(), 1);
assert_eq!(endpoints.shared_worker_id, id);
assert!(endpoints.page_to_worker_rx.is_some());
assert!(endpoints.worker_to_page_tx.is_some());
}
#[test]
fn test_shared_worker_channel_bridge_multiple_ports() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: "test".to_string(),
};
let mut bridge = SharedWorkerChannelBridge::new(id);
bridge.add_port(); bridge.add_port(); bridge.add_port(); assert_eq!(bridge.port_count(), 3);
}
#[test]
fn test_shared_worker_channel_bridge_drain_all() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: "test".to_string(),
};
let mut bridge = SharedWorkerChannelBridge::new(id);
let endpoints1 = bridge.add_port();
let endpoints2 = bridge.add_port();
let tx1 = endpoints1.worker_to_page_tx.unwrap();
let tx2 = endpoints2.worker_to_page_tx.unwrap();
tx1.send(WorkerStructuredMessage::metadata_only(
WorkerId("sw.js".to_string()),
WorkerMessageDirection::WorkerToPage,
))
.unwrap();
tx2.send(WorkerStructuredMessage::metadata_only(
WorkerId("sw.js".to_string()),
WorkerMessageDirection::WorkerToPage,
))
.unwrap();
let (messages, disconnected) = bridge.drain_all_worker_messages();
assert_eq!(messages.len(), 2);
assert!(disconnected.is_empty());
}
#[test]
fn test_shared_worker_channel_bridge_post_to_worker() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: "test".to_string(),
};
let mut bridge = SharedWorkerChannelBridge::new(id);
let endpoints = bridge.add_port();
let payload = StructuredClonePayload {
data: vec![42],
transferable_count: 0,
};
bridge.post_to_worker_from_port(0, payload).unwrap();
let rx = endpoints.page_to_worker_rx.unwrap();
let received = rx.try_recv().unwrap();
assert_eq!(received.data, vec![42]);
}
#[test]
fn test_shared_worker_channel_bridge_post_invalid_port() {
let id = SharedWorkerId {
script_url: "sw.js".to_string(),
name: "test".to_string(),
};
let mut bridge = SharedWorkerChannelBridge::new(id);
bridge.add_port();
let payload = StructuredClonePayload {
data: vec![],
transferable_count: 0,
};
let result = bridge.post_to_worker_from_port(99, payload);
assert!(result.is_err());
}
}