devtools/actors/
root.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Connection point for all new remote devtools interactions, providing lists of know actors
6//! that perform more specific actions (targets, addons, browser chrome, etc.)
7//!
8//! Liberally derived from the [Firefox JS implementation].
9//!
10//! [Firefox JS implementation]: https://searchfox.org/mozilla-central/source/devtools/server/actors/root.js
11
12use std::cell::RefCell;
13
14use serde::Serialize;
15use serde_json::{Map, Value, json};
16
17use crate::StreamId;
18use crate::actor::{Actor, ActorError, ActorRegistry};
19use crate::actors::device::DeviceActor;
20use crate::actors::performance::PerformanceActor;
21use crate::actors::process::{ProcessActor, ProcessActorMsg};
22use crate::actors::tab::{TabDescriptorActor, TabDescriptorActorMsg};
23use crate::actors::worker::{WorkerActor, WorkerMsg};
24use crate::protocol::{ActorDescription, ClientRequest};
25
26#[derive(Serialize)]
27#[serde(rename_all = "camelCase")]
28struct ActorTraits {
29    sources: bool,
30    highlightable: bool,
31    custom_highlighters: bool,
32    network_monitor: bool,
33}
34
35#[derive(Serialize)]
36struct ListAddonsReply {
37    from: String,
38    addons: Vec<AddonMsg>,
39}
40
41#[derive(Serialize)]
42enum AddonMsg {}
43
44#[derive(Serialize)]
45#[serde(rename_all = "camelCase")]
46struct GetRootReply {
47    from: String,
48    selected: u32,
49    performance_actor: String,
50    device_actor: String,
51    preference_actor: String,
52}
53
54#[derive(Serialize)]
55struct ListTabsReply {
56    from: String,
57    tabs: Vec<TabDescriptorActorMsg>,
58}
59
60#[derive(Serialize)]
61struct GetTabReply {
62    from: String,
63    tab: TabDescriptorActorMsg,
64}
65
66#[derive(Serialize)]
67#[serde(rename_all = "camelCase")]
68pub struct RootActorMsg {
69    from: String,
70    application_type: String,
71    traits: ActorTraits,
72}
73
74#[derive(Serialize)]
75pub struct ProtocolDescriptionReply {
76    from: String,
77    types: Types,
78}
79
80#[derive(Serialize)]
81struct ListWorkersReply {
82    from: String,
83    workers: Vec<WorkerMsg>,
84}
85
86#[derive(Serialize)]
87struct ListServiceWorkerRegistrationsReply {
88    from: String,
89    registrations: Vec<u32>, // TODO: follow actual JSON structure.
90}
91
92#[derive(Serialize)]
93pub struct Types {
94    performance: ActorDescription,
95    device: ActorDescription,
96}
97
98#[derive(Serialize)]
99struct ListProcessesResponse {
100    from: String,
101    processes: Vec<ProcessActorMsg>,
102}
103
104#[derive(Default, Serialize)]
105#[serde(rename_all = "camelCase")]
106pub struct DescriptorTraits {
107    pub(crate) watcher: bool,
108    pub(crate) supports_reload_descriptor: bool,
109}
110
111#[derive(Serialize)]
112#[serde(rename_all = "camelCase")]
113struct GetProcessResponse {
114    from: String,
115    process_descriptor: ProcessActorMsg,
116}
117
118pub struct RootActor {
119    pub tabs: Vec<String>,
120    pub workers: Vec<String>,
121    pub performance: String,
122    pub device: String,
123    pub preference: String,
124    pub process: String,
125    pub active_tab: RefCell<Option<String>>,
126}
127
128impl Actor for RootActor {
129    fn name(&self) -> String {
130        "root".to_owned()
131    }
132
133    fn handle_message(
134        &self,
135        request: ClientRequest,
136        registry: &ActorRegistry,
137        msg_type: &str,
138        msg: &Map<String, Value>,
139        _id: StreamId,
140    ) -> Result<(), ActorError> {
141        match msg_type {
142            "connect" => {
143                let message = json!({
144                    "from": "root",
145                });
146                request.reply_final(&message)?
147            },
148            "listAddons" => {
149                let actor = ListAddonsReply {
150                    from: "root".to_owned(),
151                    addons: vec![],
152                };
153                request.reply_final(&actor)?
154            },
155
156            "listProcesses" => {
157                let process = registry.find::<ProcessActor>(&self.process).encodable();
158                let reply = ListProcessesResponse {
159                    from: self.name(),
160                    processes: vec![process],
161                };
162                request.reply_final(&reply)?
163            },
164
165            // TODO: Unexpected message getTarget for process (when inspecting)
166            "getProcess" => {
167                let process = registry.find::<ProcessActor>(&self.process).encodable();
168                let reply = GetProcessResponse {
169                    from: self.name(),
170                    process_descriptor: process,
171                };
172                request.reply_final(&reply)?
173            },
174
175            "getRoot" => {
176                let actor = GetRootReply {
177                    from: "root".to_owned(),
178                    selected: 0,
179                    performance_actor: self.performance.clone(),
180                    device_actor: self.device.clone(),
181                    preference_actor: self.preference.clone(),
182                };
183                request.reply_final(&actor)?
184            },
185
186            "listTabs" => {
187                let actor = ListTabsReply {
188                    from: "root".to_owned(),
189                    tabs: self
190                        .tabs
191                        .iter()
192                        .filter_map(|target| {
193                            let tab_actor = registry.find::<TabDescriptorActor>(target);
194                            // Filter out iframes and workers
195                            if tab_actor.is_top_level_global() {
196                                Some(tab_actor.encodable(registry, false))
197                            } else {
198                                None
199                            }
200                        })
201                        .collect(),
202                };
203                request.reply_final(&actor)?
204            },
205
206            "listServiceWorkerRegistrations" => {
207                let reply = ListServiceWorkerRegistrationsReply {
208                    from: self.name(),
209                    registrations: vec![],
210                };
211                request.reply_final(&reply)?
212            },
213
214            "listWorkers" => {
215                let reply = ListWorkersReply {
216                    from: self.name(),
217                    workers: self
218                        .workers
219                        .iter()
220                        .map(|name| registry.find::<WorkerActor>(name).encodable())
221                        .collect(),
222                };
223                request.reply_final(&reply)?
224            },
225
226            "getTab" => {
227                let browser_id = msg
228                    .get("browserId")
229                    .ok_or(ActorError::MissingParameter)?
230                    .as_u64()
231                    .ok_or(ActorError::BadParameterType)?;
232                let Some(tab) = self.get_tab_msg_by_browser_id(registry, browser_id as u32) else {
233                    return Err(ActorError::Internal);
234                };
235
236                let reply = GetTabReply {
237                    from: self.name(),
238                    tab,
239                };
240                request.reply_final(&reply)?
241            },
242
243            "protocolDescription" => {
244                let msg = ProtocolDescriptionReply {
245                    from: self.name(),
246                    types: Types {
247                        performance: PerformanceActor::description(),
248                        device: DeviceActor::description(),
249                    },
250                };
251                request.reply_final(&msg)?
252            },
253
254            _ => return Err(ActorError::UnrecognizedPacketType),
255        };
256        Ok(())
257    }
258}
259
260impl RootActor {
261    pub fn encodable(&self) -> RootActorMsg {
262        RootActorMsg {
263            from: "root".to_owned(),
264            application_type: "browser".to_owned(),
265            traits: ActorTraits {
266                sources: false,
267                highlightable: true,
268                custom_highlighters: true,
269                network_monitor: true,
270            },
271        }
272    }
273
274    fn get_tab_msg_by_browser_id(
275        &self,
276        registry: &ActorRegistry,
277        browser_id: u32,
278    ) -> Option<TabDescriptorActorMsg> {
279        let tab_msg = self
280            .tabs
281            .iter()
282            .map(|target| {
283                registry
284                    .find::<TabDescriptorActor>(target)
285                    .encodable(registry, true)
286            })
287            .find(|tab| tab.browser_id() == browser_id);
288
289        if let Some(ref msg) = tab_msg {
290            *self.active_tab.borrow_mut() = Some(msg.actor());
291        }
292        tab_msg
293    }
294
295    #[allow(dead_code)]
296    pub fn active_tab(&self) -> Option<String> {
297        self.active_tab.borrow().clone()
298    }
299}