Skip to main content

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