1use atomic_refcell::AtomicRefCell;
6use devtools_traits::DevtoolScriptControlMsg::WantsLiveNotifications;
7use devtools_traits::{DevtoolScriptControlMsg, WorkerId};
8use malloc_size_of_derive::MallocSizeOf;
9use rustc_hash::FxHashSet;
10use serde::Serialize;
11use serde_json::{Map, Value};
12use servo_base::generic_channel::GenericSender;
13use servo_base::id::TEST_PIPELINE_ID;
14use servo_url::ServoUrl;
15
16use crate::StreamId;
17use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry};
18use crate::protocol::{ClientRequest, JsonPacketStream};
19use crate::resource::ResourceAvailable;
20
21#[derive(Clone, Copy, MallocSizeOf)]
22#[expect(dead_code)]
23pub enum WorkerType {
24 Dedicated = 0,
25 Shared = 1,
26 Service = 2,
27}
28
29#[derive(MallocSizeOf)]
30pub(crate) struct WorkerTargetActor {
31 pub name: String,
32 pub console_name: String,
33 pub thread_name: String,
34 pub worker_id: WorkerId,
35 pub url: ServoUrl,
36 pub type_: WorkerType,
37 pub script_sender: GenericSender<DevtoolScriptControlMsg>,
38 pub streams: AtomicRefCell<FxHashSet<StreamId>>,
39}
40
41impl ResourceAvailable for WorkerTargetActor {
42 fn actor_name(&self) -> String {
43 self.name.clone()
44 }
45}
46
47impl WorkerTargetActor {
48 pub fn register(
49 registry: &ActorRegistry,
50 console_name: String,
51 thread_name: String,
52 worker_id: WorkerId,
53 url: ServoUrl,
54 worker_type: WorkerType,
55 script_sender: GenericSender<DevtoolScriptControlMsg>,
56 ) -> String {
57 let name = registry.new_name::<Self>();
58 let actor = Self {
59 name: name.clone(),
60 console_name,
61 thread_name,
62 worker_id,
63 url,
64 type_: worker_type,
65 script_sender,
66 streams: Default::default(),
67 };
68 registry.register::<Self>(actor);
69 name
70 }
71}
72
73impl Actor for WorkerTargetActor {
74 fn name(&self) -> String {
75 self.name.clone()
76 }
77 fn handle_message(
78 &self,
79 mut request: ClientRequest,
80 _registry: &ActorRegistry,
81 msg_type: &str,
82 _msg: &Map<String, Value>,
83 stream_id: StreamId,
84 ) -> Result<(), ActorError> {
85 match msg_type {
86 "attach" => {
87 let msg = AttachedReply {
88 from: self.name(),
89 type_: "attached".to_owned(),
90 url: self.url.as_str().to_owned(),
91 };
92 request.write_json_packet(&msg)?;
94 self.streams.borrow_mut().insert(stream_id);
95 self.script_sender
97 .send(WantsLiveNotifications(TEST_PIPELINE_ID, true))
98 .unwrap();
99 },
100
101 "connect" => {
102 let msg = ConnectReply {
103 from: self.name(),
104 type_: "connected".to_owned(),
105 thread_actor: self.thread_name.clone(),
106 console_actor: self.console_name.clone(),
107 };
108 request.write_json_packet(&msg)?;
110 },
111
112 "detach" => {
113 let msg = DetachedReply {
114 from: self.name(),
115 type_: "detached".to_string(),
116 };
117 self.cleanup(stream_id);
118 request.write_json_packet(&msg)?;
120 },
121
122 "getPushSubscription" => {
123 let msg = GetPushSubscriptionReply {
124 from: self.name(),
125 subscription: None,
126 };
127 request.reply_final(&msg)?
128 },
129
130 _ => return Err(ActorError::UnrecognizedPacketType),
131 };
132 Ok(())
133 }
134
135 fn cleanup(&self, stream_id: StreamId) {
136 self.streams.borrow_mut().remove(&stream_id);
137 if self.streams.borrow().is_empty() {
138 self.script_sender
139 .send(WantsLiveNotifications(TEST_PIPELINE_ID, false))
140 .unwrap();
141 }
142 }
143}
144
145#[derive(Serialize)]
146struct GetPushSubscriptionReply {
147 from: String,
148 subscription: Option<()>,
149}
150
151#[derive(Serialize)]
152struct DetachedReply {
153 from: String,
154 #[serde(rename = "type")]
155 type_: String,
156}
157
158#[derive(Serialize)]
159struct AttachedReply {
160 from: String,
161 #[serde(rename = "type")]
162 type_: String,
163 url: String,
164}
165
166#[derive(Serialize)]
167#[serde(rename_all = "camelCase")]
168struct ConnectReply {
169 from: String,
170 #[serde(rename = "type")]
171 type_: String,
172 thread_actor: String,
173 console_actor: String,
174}
175
176#[derive(Serialize)]
177#[serde(rename_all = "camelCase")]
178struct WorkerTraits {
179 is_parent_intercept_enabled: bool,
180 supports_top_level_target_flag: bool,
181}
182
183#[derive(Serialize)]
184#[serde(rename_all = "camelCase")]
185pub(crate) struct WorkerTargetActorMsg {
186 actor: String,
187 console_actor: String,
188 thread_actor: String,
189 id: String,
190 url: String,
191 traits: WorkerTraits,
192 #[serde(rename = "type")]
193 type_: u32,
194 #[serde(rename = "targetType")]
195 target_type: String,
196}
197
198impl ActorEncode<WorkerTargetActorMsg> for WorkerTargetActor {
199 fn encode(&self, _: &ActorRegistry) -> WorkerTargetActorMsg {
200 WorkerTargetActorMsg {
201 actor: self.name(),
202 console_actor: self.console_name.clone(),
203 thread_actor: self.thread_name.clone(),
204 id: self.worker_id.0.to_string(),
205 url: self.url.to_string(),
206 traits: WorkerTraits {
207 is_parent_intercept_enabled: false,
208 supports_top_level_target_flag: false,
209 },
210 type_: self.type_ as u32,
211 target_type: match self.type_ {
212 WorkerType::Service => "service_worker",
213 _ => "worker",
214 }
215 .to_string(),
216 }
217 }
218}