devtools/actors/
worker.rs1use 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 WorkerActor {
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_chan: GenericSender<DevtoolScriptControlMsg>,
38 pub streams: AtomicRefCell<FxHashSet<StreamId>>,
39}
40
41impl ResourceAvailable for WorkerActor {
42 fn actor_name(&self) -> String {
43 self.name.clone()
44 }
45}
46
47impl Actor for WorkerActor {
48 fn name(&self) -> String {
49 self.name.clone()
50 }
51 fn handle_message(
52 &self,
53 mut request: ClientRequest,
54 _registry: &ActorRegistry,
55 msg_type: &str,
56 _msg: &Map<String, Value>,
57 stream_id: StreamId,
58 ) -> Result<(), ActorError> {
59 match msg_type {
60 "attach" => {
61 let msg = AttachedReply {
62 from: self.name(),
63 type_: "attached".to_owned(),
64 url: self.url.as_str().to_owned(),
65 };
66 request.write_json_packet(&msg)?;
68 self.streams.borrow_mut().insert(stream_id);
69 self.script_chan
71 .send(WantsLiveNotifications(TEST_PIPELINE_ID, true))
72 .unwrap();
73 },
74
75 "connect" => {
76 let msg = ConnectReply {
77 from: self.name(),
78 type_: "connected".to_owned(),
79 thread_actor: self.thread_name.clone(),
80 console_actor: self.console_name.clone(),
81 };
82 request.write_json_packet(&msg)?;
84 },
85
86 "detach" => {
87 let msg = DetachedReply {
88 from: self.name(),
89 type_: "detached".to_string(),
90 };
91 self.cleanup(stream_id);
92 request.write_json_packet(&msg)?;
94 },
95
96 "getPushSubscription" => {
97 let msg = GetPushSubscriptionReply {
98 from: self.name(),
99 subscription: None,
100 };
101 request.reply_final(&msg)?
102 },
103
104 _ => return Err(ActorError::UnrecognizedPacketType),
105 };
106 Ok(())
107 }
108
109 fn cleanup(&self, stream_id: StreamId) {
110 self.streams.borrow_mut().remove(&stream_id);
111 if self.streams.borrow().is_empty() {
112 self.script_chan
113 .send(WantsLiveNotifications(TEST_PIPELINE_ID, false))
114 .unwrap();
115 }
116 }
117}
118
119#[derive(Serialize)]
120struct GetPushSubscriptionReply {
121 from: String,
122 subscription: Option<()>,
123}
124
125#[derive(Serialize)]
126struct DetachedReply {
127 from: String,
128 #[serde(rename = "type")]
129 type_: String,
130}
131
132#[derive(Serialize)]
133struct AttachedReply {
134 from: String,
135 #[serde(rename = "type")]
136 type_: String,
137 url: String,
138}
139
140#[derive(Serialize)]
141#[serde(rename_all = "camelCase")]
142struct ConnectReply {
143 from: String,
144 #[serde(rename = "type")]
145 type_: String,
146 thread_actor: String,
147 console_actor: String,
148}
149
150#[derive(Serialize)]
151#[serde(rename_all = "camelCase")]
152struct WorkerTraits {
153 is_parent_intercept_enabled: bool,
154 supports_top_level_target_flag: bool,
155}
156
157#[derive(Serialize)]
158#[serde(rename_all = "camelCase")]
159pub(crate) struct WorkerActorMsg {
160 actor: String,
161 console_actor: String,
162 thread_actor: String,
163 id: String,
164 url: String,
165 traits: WorkerTraits,
166 #[serde(rename = "type")]
167 type_: u32,
168 #[serde(rename = "targetType")]
169 target_type: String,
170}
171
172impl ActorEncode<WorkerActorMsg> for WorkerActor {
173 fn encode(&self, _: &ActorRegistry) -> WorkerActorMsg {
174 WorkerActorMsg {
175 actor: self.name(),
176 console_actor: self.console_name.clone(),
177 thread_actor: self.thread_name.clone(),
178 id: self.worker_id.0.to_string(),
179 url: self.url.to_string(),
180 traits: WorkerTraits {
181 is_parent_intercept_enabled: false,
182 supports_top_level_target_flag: false,
183 },
184 type_: self.type_ as u32,
185 target_type: match self.type_ {
186 WorkerType::Service => "service_worker",
187 _ => "worker",
188 }
189 .to_string(),
190 }
191 }
192}