use atomic_refcell::AtomicRefCell;
use devtools_traits::DevtoolScriptControlMsg::WantsLiveNotifications;
use devtools_traits::{DevtoolScriptControlMsg, WorkerId};
use malloc_size_of_derive::MallocSizeOf;
use rustc_hash::FxHashSet;
use serde::Serialize;
use serde_json::{Map, Value};
use servo_base::generic_channel::GenericSender;
use servo_base::id::TEST_PIPELINE_ID;
use servo_url::ServoUrl;
use crate::StreamId;
use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry};
use crate::protocol::{ClientRequest, JsonPacketStream};
use crate::resource::ResourceAvailable;
#[derive(Clone, Copy, MallocSizeOf)]
#[expect(dead_code)]
pub enum WorkerType {
Dedicated = 0,
Shared = 1,
Service = 2,
}
#[derive(MallocSizeOf)]
pub(crate) struct WorkerActor {
pub name: String,
pub console_name: String,
pub thread_name: String,
pub worker_id: WorkerId,
pub url: ServoUrl,
pub type_: WorkerType,
pub script_chan: GenericSender<DevtoolScriptControlMsg>,
pub streams: AtomicRefCell<FxHashSet<StreamId>>,
}
impl ResourceAvailable for WorkerActor {
fn actor_name(&self) -> String {
self.name.clone()
}
}
impl Actor for WorkerActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(
&self,
mut request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream_id: StreamId,
) -> Result<(), ActorError> {
match msg_type {
"attach" => {
let msg = AttachedReply {
from: self.name(),
type_: "attached".to_owned(),
url: self.url.as_str().to_owned(),
};
request.write_json_packet(&msg)?;
self.streams.borrow_mut().insert(stream_id);
self.script_chan
.send(WantsLiveNotifications(TEST_PIPELINE_ID, true))
.unwrap();
},
"connect" => {
let msg = ConnectReply {
from: self.name(),
type_: "connected".to_owned(),
thread_actor: self.thread_name.clone(),
console_actor: self.console_name.clone(),
};
request.write_json_packet(&msg)?;
},
"detach" => {
let msg = DetachedReply {
from: self.name(),
type_: "detached".to_string(),
};
self.cleanup(stream_id);
request.write_json_packet(&msg)?;
},
"getPushSubscription" => {
let msg = GetPushSubscriptionReply {
from: self.name(),
subscription: None,
};
request.reply_final(&msg)?
},
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
fn cleanup(&self, stream_id: StreamId) {
self.streams.borrow_mut().remove(&stream_id);
if self.streams.borrow().is_empty() {
self.script_chan
.send(WantsLiveNotifications(TEST_PIPELINE_ID, false))
.unwrap();
}
}
}
#[derive(Serialize)]
struct GetPushSubscriptionReply {
from: String,
subscription: Option<()>,
}
#[derive(Serialize)]
struct DetachedReply {
from: String,
#[serde(rename = "type")]
type_: String,
}
#[derive(Serialize)]
struct AttachedReply {
from: String,
#[serde(rename = "type")]
type_: String,
url: String,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ConnectReply {
from: String,
#[serde(rename = "type")]
type_: String,
thread_actor: String,
console_actor: String,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct WorkerTraits {
is_parent_intercept_enabled: bool,
supports_top_level_target_flag: bool,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct WorkerActorMsg {
actor: String,
console_actor: String,
thread_actor: String,
id: String,
url: String,
traits: WorkerTraits,
#[serde(rename = "type")]
type_: u32,
#[serde(rename = "targetType")]
target_type: String,
}
impl ActorEncode<WorkerActorMsg> for WorkerActor {
fn encode(&self, _: &ActorRegistry) -> WorkerActorMsg {
WorkerActorMsg {
actor: self.name(),
console_actor: self.console_name.clone(),
thread_actor: self.thread_name.clone(),
id: self.worker_id.0.to_string(),
url: self.url.to_string(),
traits: WorkerTraits {
is_parent_intercept_enabled: false,
supports_top_level_target_flag: false,
},
type_: self.type_ as u32,
target_type: match self.type_ {
WorkerType::Service => "service_worker",
_ => "worker",
}
.to_string(),
}
}
}