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