1use std::collections::HashMap;
13
14use atomic_refcell::AtomicRefCell;
15use malloc_size_of_derive::MallocSizeOf;
16use serde::Serialize;
17use serde_json::{Map, Value};
18
19use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry};
20use crate::actors::browsing_context::BrowsingContextActor;
21use crate::actors::device::DeviceActor;
22use crate::actors::performance::PerformanceActor;
23use crate::actors::preference::PreferenceActor;
24use crate::actors::process::{ProcessActor, ProcessActorMsg};
25use crate::actors::tab::{TabDescriptorActor, TabDescriptorActorMsg};
26use crate::actors::worker::{WorkerTargetActor, WorkerTargetActorMsg};
27use crate::protocol::{ActorDescription, ClientRequest};
28use crate::{EmptyReplyMsg, StreamId};
29
30#[derive(Serialize)]
31#[serde(rename_all = "camelCase")]
32struct ServiceWorkerInfo {
33 actor: String,
34 url: String,
35 state: u32,
36 state_text: String,
37 id: String,
38 fetch: bool,
39 traits: HashMap<&'static str, bool>,
40}
41
42#[derive(Serialize)]
43#[serde(rename_all = "camelCase")]
44struct ServiceWorkerRegistrationMsg {
45 actor: String,
46 scope: String,
47 url: String,
48 registration_state: String,
49 last_update_time: u64,
50 traits: HashMap<&'static str, bool>,
51 evaluating_worker: Option<ServiceWorkerInfo>,
54 installing_worker: Option<ServiceWorkerInfo>,
55 waiting_worker: Option<ServiceWorkerInfo>,
56 active_worker: Option<ServiceWorkerInfo>,
57}
58
59#[derive(Serialize)]
60#[serde(rename_all = "camelCase")]
61struct RootTraits {
62 sources: bool,
63 highlightable: bool,
64 custom_highlighters: bool,
65 network_monitor: bool,
66 resources: HashMap<&'static str, bool>,
67}
68
69#[derive(Serialize)]
70struct ListAddonsReply {
71 from: String,
72 addons: Vec<AddonMsg>,
73}
74
75#[derive(Serialize)]
76enum AddonMsg {}
77
78#[derive(Clone, Default, Serialize, MallocSizeOf)]
79#[serde(rename_all = "camelCase")]
80struct GlobalActors {
81 device_actor: String,
82 perf_actor: String,
83 preference_actor: String,
84 }
90
91#[derive(Serialize)]
92struct GetRootReply {
93 from: String,
94 #[serde(flatten)]
95 global_actors: GlobalActors,
96}
97
98#[derive(Serialize)]
99struct ListTabsReply {
100 from: String,
101 tabs: Vec<TabDescriptorActorMsg>,
102}
103
104#[derive(Serialize)]
105struct GetTabReply {
106 from: String,
107 tab: TabDescriptorActorMsg,
108}
109
110#[derive(Serialize)]
111#[serde(rename_all = "camelCase")]
112pub(crate) struct RootActorMsg {
113 from: String,
114 application_type: String,
115 traits: RootTraits,
116}
117
118#[derive(Serialize)]
119pub(crate) struct ProtocolDescriptionReply {
120 from: String,
121 types: Types,
122}
123
124#[derive(Serialize)]
125struct ListWorkersReply {
126 from: String,
127 workers: Vec<WorkerTargetActorMsg>,
128}
129
130#[derive(Serialize)]
131struct ListServiceWorkerRegistrationsReply {
132 from: String,
133 registrations: Vec<ServiceWorkerRegistrationMsg>,
134}
135
136#[derive(Serialize)]
137pub(crate) struct Types {
138 performance: ActorDescription,
139 device: ActorDescription,
140}
141
142#[derive(Serialize)]
143struct ListProcessesResponse {
144 from: String,
145 processes: Vec<ProcessActorMsg>,
146}
147
148#[derive(Default, Serialize)]
149#[serde(rename_all = "camelCase")]
150pub(crate) struct DescriptorTraits {
151 pub(crate) watcher: bool,
152 pub(crate) supports_reload_descriptor: bool,
153 pub(crate) supports_navigation: bool,
154}
155
156#[derive(Serialize)]
157#[serde(rename_all = "camelCase")]
158struct GetProcessResponse {
159 from: String,
160 process_descriptor: ProcessActorMsg,
161}
162
163#[derive(MallocSizeOf)]
164pub(crate) struct RootActor {
165 name: String,
166 active_tab: AtomicRefCell<Option<String>>,
167 global_actors: GlobalActors,
168 process_name: String,
169 pub tabs: AtomicRefCell<Vec<String>>,
170 pub workers: AtomicRefCell<Vec<String>>,
171 pub service_workers: AtomicRefCell<Vec<String>>,
172}
173
174impl Actor for RootActor {
175 fn name(&self) -> &str {
176 &self.name
177 }
178
179 fn handle_message(
180 &self,
181 request: ClientRequest,
182 registry: &ActorRegistry,
183 msg_type: &str,
184 msg: &Map<String, Value>,
185 _id: StreamId,
186 ) -> Result<(), ActorError> {
187 match msg_type {
188 "connect" => {
189 let message = EmptyReplyMsg {
190 from: self.name().into(),
191 };
192 request.reply_final(&message)?
193 },
194
195 "getProcess" => {
197 let process_descriptor = registry.encode::<ProcessActor, _>(&self.process_name);
198 let reply = GetProcessResponse {
199 from: self.name().into(),
200 process_descriptor,
201 };
202 request.reply_final(&reply)?
203 },
204
205 "getRoot" => {
206 let reply = GetRootReply {
207 from: self.name().into(),
208 global_actors: self.global_actors.clone(),
209 };
210 request.reply_final(&reply)?
211 },
212
213 "getTab" => {
214 let browser_id = msg
215 .get("browserId")
216 .ok_or(ActorError::MissingParameter)?
217 .as_u64()
218 .ok_or(ActorError::BadParameterType)?;
219 let Some(tab) = self.get_tab_msg_by_browser_id(registry, browser_id as u32) else {
220 return Err(ActorError::Internal);
221 };
222
223 let reply = GetTabReply {
224 from: self.name().into(),
225 tab,
226 };
227 request.reply_final(&reply)?
228 },
229
230 "listAddons" => {
231 let reply = ListAddonsReply {
232 from: "root".to_owned(),
233 addons: vec![],
234 };
235 request.reply_final(&reply)?
236 },
237
238 "listProcesses" => {
239 let process_descriptor = registry.encode::<ProcessActor, _>(&self.process_name);
240 let reply = ListProcessesResponse {
241 from: self.name().into(),
242 processes: vec![process_descriptor],
243 };
244 request.reply_final(&reply)?
245 },
246
247 "listServiceWorkerRegistrations" => {
248 let registrations = self
249 .service_workers
250 .borrow()
251 .iter()
252 .map(|worker_name| {
253 let worker_actor = registry.find::<WorkerTargetActor>(worker_name);
254 let url = worker_actor.url.to_string();
255 let scope = url.clone();
257 ServiceWorkerRegistrationMsg {
258 actor: worker_actor.name().into(),
259 scope,
260 url: url.clone(),
261 registration_state: String::new(),
262 last_update_time: 0,
263 traits: HashMap::new(),
264 evaluating_worker: None,
265 installing_worker: None,
266 waiting_worker: None,
267 active_worker: Some(ServiceWorkerInfo {
268 actor: worker_actor.name().into(),
269 url,
270 state: 4, state_text: "activated".to_string(),
272 id: worker_actor.worker_id.to_string(),
273 fetch: false,
274 traits: HashMap::new(),
275 }),
276 }
277 })
278 .collect();
279 let reply = ListServiceWorkerRegistrationsReply {
280 from: self.name().into(),
281 registrations,
282 };
283 request.reply_final(&reply)?
284 },
285
286 "listTabs" => {
287 let top_level_tabs: Vec<_> = self
288 .tabs
289 .borrow()
290 .iter()
291 .filter(|&tab_name| {
292 registry
293 .find::<TabDescriptorActor>(tab_name)
294 .is_top_level_global(registry)
295 })
296 .cloned()
297 .collect();
298
299 let active_tab_name = self.active_tab.borrow().clone();
301 let some_active_tab = active_tab_name
302 .as_ref()
303 .is_some_and(|name| top_level_tabs.contains(name));
304 if let Some(first_tab) = top_level_tabs.first() &&
305 !some_active_tab
306 {
307 *self.active_tab.borrow_mut() = Some(first_tab.clone());
308 }
309
310 let reply = ListTabsReply {
311 from: self.name().into(),
312 tabs: top_level_tabs
313 .iter()
314 .map(|tab_name| registry.encode::<TabDescriptorActor, _>(tab_name))
315 .collect(),
316 };
317 request.reply_final(&reply)?
318 },
319
320 "listWorkers" => {
321 let reply = ListWorkersReply {
322 from: self.name().into(),
323 workers: self
324 .workers
325 .borrow()
326 .iter()
327 .map(|worker_name| registry.encode::<WorkerTargetActor, _>(worker_name))
328 .collect(),
329 };
330 request.reply_final(&reply)?
331 },
332
333 "protocolDescription" => {
334 let msg = ProtocolDescriptionReply {
335 from: self.name().into(),
336 types: Types {
337 performance: PerformanceActor::description(),
338 device: DeviceActor::description(),
339 },
340 };
341 request.reply_final(&msg)?
342 },
343
344 "watchResources" => {
345 request.reply_final(&EmptyReplyMsg {
347 from: self.name().into(),
348 })?
349 },
350
351 "unwatchResources" => {
352 request.reply_final(&EmptyReplyMsg {
354 from: self.name().into(),
355 })?
356 },
357
358 _ => return Err(ActorError::UnrecognizedPacketType),
359 };
360 Ok(())
361 }
362}
363
364impl RootActor {
365 pub fn register(registry: &mut ActorRegistry) {
367 let device_actor = DeviceActor::register(registry);
369 let performance_actor = PerformanceActor::register(registry);
370 let preference_actor = PreferenceActor::register(registry);
371
372 let process_actor = ProcessActor::register(registry);
374
375 let root_actor = Self {
377 name: "root".into(),
379 global_actors: GlobalActors {
380 device_actor: device_actor.name().into(),
381 perf_actor: performance_actor.name().into(),
382 preference_actor: preference_actor.name().into(),
383 },
384 process_name: process_actor.name().into(),
385 active_tab: AtomicRefCell::new(None),
386 tabs: AtomicRefCell::new(vec![]),
387 workers: AtomicRefCell::new(vec![]),
388 service_workers: AtomicRefCell::new(vec![]),
389 };
390
391 registry.register(root_actor);
392 }
393
394 fn get_tab_msg_by_browser_id(
395 &self,
396 registry: &ActorRegistry,
397 browser_id: u32,
398 ) -> Option<TabDescriptorActorMsg> {
399 let tab_descriptor_actor = self
400 .tabs
401 .borrow()
402 .iter()
403 .map(|tab_descriptor_name| registry.find::<TabDescriptorActor>(tab_descriptor_name))
404 .find(|tab_descriptor_actor| {
405 let browsing_context = registry
406 .find::<BrowsingContextActor>(&tab_descriptor_actor.browsing_context_name);
407 browsing_context.browser_id.value() == browser_id
408 });
409
410 if let Some(tab_descriptor_actor) = tab_descriptor_actor {
411 *self.active_tab.borrow_mut() = Some(tab_descriptor_actor.name().to_owned());
412 Some(registry.encode::<TabDescriptorActor, _>(tab_descriptor_actor.name()))
413 } else {
414 None
415 }
416 }
417
418 pub fn active_tab(&self) -> Option<String> {
419 self.active_tab.borrow().clone()
420 }
421}
422
423impl ActorEncode<RootActorMsg> for RootActor {
424 fn encode(&self, _: &ActorRegistry) -> RootActorMsg {
425 RootActorMsg {
426 from: self.name().into(),
427 application_type: "browser".to_owned(),
428 traits: RootTraits {
429 sources: false,
430 highlightable: true,
431 custom_highlighters: true,
432 network_monitor: true,
433 resources: HashMap::from([("extensions-backgroundscript-status", true)]),
434 },
435 }
436 }
437}