devtools/actors/
tab.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//! Descriptor actor that represents a web view. It can link a tab to the corresponding watcher
6//! actor to enable inspection.
7//!
8//! Liberally derived from the [Firefox JS implementation].
9//!
10//! [Firefox JS implementation]: https://searchfox.org/mozilla-central/source/devtools/server/actors/descriptors/tab.js
11
12use devtools_traits::DevtoolScriptControlMsg;
13use malloc_size_of_derive::MallocSizeOf;
14use serde::Serialize;
15use serde_json::{Map, Value};
16use servo_url::ServoUrl;
17
18use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry};
19use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg};
20use crate::actors::root::{DescriptorTraits, RootActor};
21use crate::actors::watcher::{WatcherActor, WatcherActorMsg};
22use crate::protocol::ClientRequest;
23use crate::{EmptyReplyMsg, StreamId};
24
25#[derive(Serialize)]
26#[serde(rename_all = "camelCase")]
27pub(crate) struct TabDescriptorActorMsg {
28    actor: String,
29    /// This correspond to webview_id
30    #[serde(rename = "browserId")]
31    browser_id: u32,
32    #[serde(rename = "browsingContextID")]
33    browsing_context_id: u32,
34    is_zombie_tab: bool,
35    #[serde(rename = "outerWindowID")]
36    outer_window_id: u32,
37    pub selected: bool,
38    title: String,
39    traits: DescriptorTraits,
40    url: String,
41}
42
43impl TabDescriptorActorMsg {
44    pub fn browser_id(&self) -> u32 {
45        self.browser_id
46    }
47
48    pub fn actor(&self) -> String {
49        self.actor.clone()
50    }
51}
52
53#[derive(Serialize)]
54struct GetTargetReply {
55    from: String,
56    frame: BrowsingContextActorMsg,
57}
58
59#[derive(Serialize)]
60struct GetFaviconReply {
61    from: String,
62    favicon: String,
63}
64
65#[derive(Serialize)]
66struct GetWatcherReply {
67    from: String,
68    #[serde(flatten)]
69    watcher: WatcherActorMsg,
70}
71
72#[derive(MallocSizeOf)]
73pub(crate) struct TabDescriptorActor {
74    name: String,
75    browsing_context_name: String,
76    is_top_level_global: bool,
77}
78
79impl Actor for TabDescriptorActor {
80    fn name(&self) -> String {
81        self.name.clone()
82    }
83
84    /// The tab actor can handle the following messages:
85    ///
86    /// - `getTarget`: Returns the surrounding `BrowsingContextActor`.
87    ///
88    /// - `getFavicon`: Should return the tab favicon, but it is not yet supported.
89    ///
90    /// - `getWatcher`: Returns a `WatcherActor` linked to the tab's `BrowsingContext`. It is used
91    ///   to describe the debugging capabilities of this tab.
92    ///
93    /// - `reloadDescriptor`: Causes the page to reload.
94    fn handle_message(
95        &self,
96        request: ClientRequest,
97        registry: &ActorRegistry,
98        msg_type: &str,
99        msg: &Map<String, Value>,
100        _id: StreamId,
101    ) -> Result<(), ActorError> {
102        let browsing_context_actor =
103            registry.find::<BrowsingContextActor>(&self.browsing_context_name);
104        let pipeline = browsing_context_actor.pipeline_id();
105
106        match msg_type {
107            "getTarget" => request.reply_final(&GetTargetReply {
108                from: self.name(),
109                frame: registry.encode::<BrowsingContextActor, _>(&self.browsing_context_name),
110            })?,
111            "getFavicon" => {
112                // TODO: Return a favicon when available
113                request.reply_final(&GetFaviconReply {
114                    from: self.name(),
115                    favicon: String::new(),
116                })?
117            },
118            "getWatcher" => request.reply_final(&GetWatcherReply {
119                from: self.name(),
120                watcher: registry.encode::<WatcherActor, _>(&browsing_context_actor.watcher_name),
121            })?,
122            "goBack" => {
123                browsing_context_actor
124                    .script_chan()
125                    .send(DevtoolScriptControlMsg::GoBack(pipeline))
126                    .map_err(|_| ActorError::Internal)?;
127                request.reply_final(&EmptyReplyMsg { from: self.name() })?
128            },
129            "goForward" => {
130                browsing_context_actor
131                    .script_chan()
132                    .send(DevtoolScriptControlMsg::GoForward(pipeline))
133                    .map_err(|_| ActorError::Internal)?;
134                request.reply_final(&EmptyReplyMsg { from: self.name() })?
135            },
136            "navigateTo" => {
137                if msg.get("waitForLoad").unwrap_or(&Value::Bool(false)) != &Value::Bool(false) {
138                    log::warn!("waitForLoad option for devtools navigation is not supported.");
139                }
140                let url = msg
141                    .get("url")
142                    .and_then(|value| value.as_str())
143                    .map(ServoUrl::parse)
144                    .ok_or(ActorError::Internal)?
145                    .map_err(|_| ActorError::Internal)?;
146
147                browsing_context_actor
148                    .script_chan()
149                    .send(DevtoolScriptControlMsg::NavigateTo(pipeline, url))
150                    .map_err(|_| ActorError::Internal)?;
151
152                request.reply_final(&EmptyReplyMsg { from: self.name() })?
153            },
154            "reloadDescriptor" => {
155                // There is an extra bypassCache parameter that we don't currently use.
156                browsing_context_actor
157                    .script_chan()
158                    .send(DevtoolScriptControlMsg::Reload(pipeline))
159                    .map_err(|_| ActorError::Internal)?;
160
161                request.reply_final(&EmptyReplyMsg { from: self.name() })?
162            },
163            _ => return Err(ActorError::UnrecognizedPacketType),
164        };
165        Ok(())
166    }
167}
168
169impl TabDescriptorActor {
170    pub(crate) fn new(
171        registry: &ActorRegistry,
172        browsing_context_name: String,
173        is_top_level_global: bool,
174    ) -> TabDescriptorActor {
175        let name = registry.new_name::<Self>();
176        let root_actor = registry.find::<RootActor>("root");
177        root_actor.tabs.borrow_mut().push(name.clone());
178        TabDescriptorActor {
179            name,
180            browsing_context_name,
181            is_top_level_global,
182        }
183    }
184
185    pub(crate) fn is_top_level_global(&self) -> bool {
186        self.is_top_level_global
187    }
188
189    pub fn browsing_context(&self) -> String {
190        self.browsing_context_name.clone()
191    }
192}
193
194impl ActorEncode<TabDescriptorActorMsg> for TabDescriptorActor {
195    fn encode(&self, registry: &ActorRegistry) -> TabDescriptorActorMsg {
196        let browsing_context_actor =
197            registry.find::<BrowsingContextActor>(&self.browsing_context_name);
198        let title = browsing_context_actor.title.borrow().clone();
199        let url = browsing_context_actor.url.borrow().clone();
200
201        TabDescriptorActorMsg {
202            actor: self.name(),
203            browser_id: browsing_context_actor.browser_id.value(),
204            browsing_context_id: browsing_context_actor.browsing_context_id.value(),
205            is_zombie_tab: false,
206            outer_window_id: browsing_context_actor.outer_window_id().value(),
207            selected: false,
208            title,
209            traits: DescriptorTraits {
210                watcher: true,
211                supports_navigation: true,
212                supports_reload_descriptor: true,
213            },
214            url,
215        }
216    }
217}