Skip to main content

devtools/actors/
browsing_context.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//! Liberally derived from the [Firefox JS implementation](https://searchfox.org/mozilla-central/source/devtools/server/actors/webbrowser.js).
6//! Connection point for remote devtools that wish to investigate a particular Browsing Context's contents.
7//! Supports dynamic attaching and detaching which control notifications of navigation, etc.
8
9use std::sync::Arc;
10
11use atomic_refcell::AtomicRefCell;
12use devtools_traits::DevtoolScriptControlMsg::{self, GetCssDatabase, SimulateColorScheme};
13use devtools_traits::DevtoolsPageInfo;
14use embedder_traits::Theme;
15use malloc_size_of_derive::MallocSizeOf;
16use rustc_hash::FxHashMap;
17use serde::Serialize;
18use serde_json::{Map, Value};
19use servo_base::generic_channel::{self, GenericSender, SendError};
20use servo_base::id::PipelineId;
21
22use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry, new_actor_name};
23use crate::actors::inspector::InspectorActor;
24use crate::actors::inspector::accessibility::AccessibilityActor;
25use crate::actors::inspector::css_properties::CssPropertiesActor;
26use crate::actors::reflow::ReflowActor;
27use crate::actors::stylesheets::StyleSheetsActor;
28use crate::actors::tab::TabDescriptorActor;
29use crate::actors::thread::ThreadActor;
30use crate::actors::watcher::{SessionContext, SessionContextType, WatcherActor};
31use crate::id::{DevtoolsBrowserId, DevtoolsBrowsingContextId, DevtoolsOuterWindowId};
32use crate::protocol::{ClientRequest, JsonPacketStream};
33use crate::resource::ResourceAvailable;
34use crate::{EmptyReplyMsg, StreamId};
35
36#[derive(Serialize)]
37struct ListWorkersReply {
38    from: String,
39    workers: Vec<()>,
40}
41
42#[derive(Serialize)]
43struct FrameUpdateReply {
44    from: String,
45    #[serde(rename = "type")]
46    type_: String,
47    frames: Vec<FrameUpdateMsg>,
48}
49
50#[derive(Serialize)]
51#[serde(rename_all = "camelCase")]
52struct FrameUpdateMsg {
53    id: u32,
54    is_top_level: bool,
55    url: String,
56    title: String,
57}
58
59#[derive(Serialize)]
60#[serde(rename_all = "camelCase")]
61struct BrowsingContextTraits {
62    frames: bool,
63    is_browsing_context: bool,
64    log_in_page: bool,
65    navigation: bool,
66    supports_top_level_target_flag: bool,
67    watchpoints: bool,
68}
69
70#[derive(Serialize)]
71#[serde(rename_all = "lowercase")]
72enum TargetType {
73    Frame,
74    // Other target types not implemented yet.
75}
76
77#[derive(Serialize)]
78#[serde(rename_all = "camelCase")]
79pub(crate) struct BrowsingContextActorMsg {
80    actor: String,
81    title: String,
82    url: String,
83    /// This correspond to webview_id
84    #[serde(rename = "browserId")]
85    browser_id: u32,
86    #[serde(rename = "outerWindowID")]
87    outer_window_id: u32,
88    #[serde(rename = "browsingContextID")]
89    browsing_context_id: u32,
90    is_top_level_target: bool,
91    traits: BrowsingContextTraits,
92    // Implemented actors
93    accessibility_actor: String,
94    console_actor: String,
95    css_properties_actor: String,
96    inspector_actor: String,
97    reflow_actor: String,
98    style_sheets_actor: String,
99    thread_actor: String,
100    target_type: TargetType,
101    // Part of the official protocol, but not yet implemented.
102    // animations_actor: String,
103    // changes_actor: String,
104    // framerate_actor: String,
105    // manifest_actor: String,
106    // memory_actor: String,
107    // network_content_actor: String,
108    // objects_manager: String,
109    // performance_actor: String,
110    // resonsive_actor: String,
111    // storage_actor: String,
112    // tracer_actor: String,
113    // web_extension_inspected_window_actor: String,
114    // web_socket_actor: String,
115}
116
117/// The browsing context actor encompasses all of the other supporting actors when debugging a web
118/// view. To this extent, it contains a watcher actor that helps when communicating with the host,
119/// as well as resource actors that each perform one debugging function.
120#[derive(MallocSizeOf)]
121pub(crate) struct BrowsingContextActor {
122    name: String,
123    /// This corresponds to webview_id
124    pub browser_id: DevtoolsBrowserId,
125    // TODO: Should these ids be atomic?
126    active_pipeline_id: AtomicRefCell<PipelineId>,
127    active_outer_window_id: AtomicRefCell<DevtoolsOuterWindowId>,
128    pub browsing_context_id: DevtoolsBrowsingContextId,
129    accessibility_name: String,
130    pub console_name: String,
131    css_properties_name: String,
132    pub inspector_name: String,
133    pub page_info: AtomicRefCell<DevtoolsPageInfo>,
134    reflow_name: String,
135    pub style_sheets_name: String,
136    pub thread_name: String,
137    // Different pipelines may run on different script threads.
138    // These should be kept around even when the active pipeline is updated,
139    // in case the browsing context revisits a pipeline via history navigation.
140    // TODO: Each entry is stored forever; ideally there should be a way to
141    //       detect when `ScriptThread`s are destroyed and remove the associated
142    //       entries.
143    script_chans: AtomicRefCell<FxHashMap<PipelineId, GenericSender<DevtoolScriptControlMsg>>>,
144    pub watcher_name: String,
145}
146
147impl ResourceAvailable for BrowsingContextActor {
148    fn actor_name(&self) -> String {
149        self.name.clone()
150    }
151}
152
153impl Actor for BrowsingContextActor {
154    fn name(&self) -> &str {
155        &self.name
156    }
157
158    fn handle_message(
159        &self,
160        request: ClientRequest,
161        _registry: &ActorRegistry,
162        msg_type: &str,
163        _msg: &Map<String, Value>,
164        _id: StreamId,
165    ) -> Result<(), ActorError> {
166        match msg_type {
167            "listFrames" => {
168                // TODO: Find out what needs to be listed here
169                let msg = EmptyReplyMsg {
170                    from: self.name().into(),
171                };
172                request.reply_final(&msg)?
173            },
174            "listWorkers" => {
175                request.reply_final(&ListWorkersReply {
176                    from: self.name().into(),
177                    // TODO: Find out what needs to be listed here
178                    workers: vec![],
179                })?
180            },
181            _ => return Err(ActorError::UnrecognizedPacketType),
182        };
183        Ok(())
184    }
185}
186
187impl BrowsingContextActor {
188    #[expect(clippy::too_many_arguments)]
189    pub(crate) fn register(
190        registry: &ActorRegistry,
191        console_name: String,
192        browser_id: DevtoolsBrowserId,
193        browsing_context_id: DevtoolsBrowsingContextId,
194        page_info: DevtoolsPageInfo,
195        pipeline_id: PipelineId,
196        outer_window_id: DevtoolsOuterWindowId,
197        script_sender: GenericSender<DevtoolScriptControlMsg>,
198    ) -> Arc<Self> {
199        let name = new_actor_name::<BrowsingContextActor>();
200
201        let accessibility_actor = AccessibilityActor::register(registry);
202
203        let properties = (|| {
204            let (properties_sender, properties_receiver) = generic_channel::channel()?;
205            script_sender.send(GetCssDatabase(properties_sender)).ok()?;
206            properties_receiver.recv().ok()
207        })()
208        .unwrap_or_default();
209
210        let css_properties_actor = CssPropertiesActor::register(registry, properties);
211
212        let inspector_actor = InspectorActor::register(registry, name.clone());
213
214        let reflow_actor = ReflowActor::register(registry);
215
216        let style_sheets_actor =
217            StyleSheetsActor::register(registry, script_sender.clone(), name.clone());
218
219        let _ = TabDescriptorActor::register(registry, name.clone());
220
221        let thread_actor =
222            ThreadActor::register(registry, script_sender.clone(), Some(name.clone()));
223
224        let watcher_actor = WatcherActor::register(
225            registry,
226            name.clone(),
227            SessionContext::new(SessionContextType::BrowserElement),
228        );
229
230        let mut script_chans = FxHashMap::default();
231        script_chans.insert(pipeline_id, script_sender);
232
233        let actor = BrowsingContextActor {
234            name,
235            script_chans: script_chans.into(),
236            active_pipeline_id: pipeline_id.into(),
237            active_outer_window_id: outer_window_id.into(),
238            browser_id,
239            browsing_context_id,
240            accessibility_name: accessibility_actor.name().into(),
241            console_name,
242            css_properties_name: css_properties_actor.name().into(),
243            inspector_name: inspector_actor.name().into(),
244            page_info: page_info.into(),
245            reflow_name: reflow_actor.name().into(),
246            style_sheets_name: style_sheets_actor.name().into(),
247            thread_name: thread_actor.name().into(),
248            watcher_name: watcher_actor.name().into(),
249        };
250
251        registry.register::<Self>(actor)
252    }
253
254    pub(crate) fn handle_new_global(
255        &self,
256        pipeline: PipelineId,
257        script_sender: GenericSender<DevtoolScriptControlMsg>,
258    ) {
259        self.script_chans
260            .borrow_mut()
261            .insert(pipeline, script_sender);
262    }
263
264    pub(crate) fn title_changed(&self, pipeline_id: PipelineId, title: String) {
265        if pipeline_id != self.pipeline_id() {
266            return;
267        }
268        self.page_info.borrow_mut().title = title;
269    }
270
271    pub(crate) fn frame_update(&self, request: &mut ClientRequest) {
272        let _ = request.write_json_packet(&FrameUpdateReply {
273            from: self.name().into(),
274            type_: "frameUpdate".into(),
275            frames: vec![FrameUpdateMsg {
276                id: self.browsing_context_id.value(),
277                is_top_level: true,
278                title: self.title(),
279                url: self.url(),
280            }],
281        });
282    }
283
284    pub fn simulate_color_scheme(&self, theme: Option<Theme>) -> Result<(), ()> {
285        self.script_chan()
286            .send(SimulateColorScheme(self.pipeline_id(), theme))
287            .map_err(|_| ())
288    }
289
290    pub(crate) fn pipeline_id(&self) -> PipelineId {
291        *self.active_pipeline_id.borrow()
292    }
293
294    pub(crate) fn title(&self) -> String {
295        self.page_info.borrow().title.clone()
296    }
297
298    pub(crate) fn url(&self) -> String {
299        self.page_info.borrow().url.clone().into_string()
300    }
301
302    pub(crate) fn update_pipeline(
303        &self,
304        pipeline_id: PipelineId,
305        outer_window_id: DevtoolsOuterWindowId,
306        page_info: DevtoolsPageInfo,
307    ) {
308        *self.active_pipeline_id.borrow_mut() = pipeline_id;
309        *self.active_outer_window_id.borrow_mut() = outer_window_id;
310        *self.page_info.borrow_mut() = page_info;
311    }
312
313    pub(crate) fn outer_window_id(&self) -> DevtoolsOuterWindowId {
314        *self.active_outer_window_id.borrow()
315    }
316
317    /// Returns the script sender for the active pipeline.
318    pub(crate) fn script_chan(&self) -> GenericSender<DevtoolScriptControlMsg> {
319        self.script_chans
320            .borrow()
321            .get(&self.pipeline_id())
322            .unwrap()
323            .clone()
324    }
325
326    pub(crate) fn instruct_script_to_send_live_updates(&self, should_send_updates: bool) {
327        let result = self
328            .script_chan()
329            .send(DevtoolScriptControlMsg::WantsLiveNotifications(
330                self.pipeline_id(),
331                should_send_updates,
332            ));
333
334        // Notifying the script thread may fail with a "Disconnected" error if servo
335        // as a whole is being shut down.
336        debug_assert!(matches!(result, Ok(_) | Err(SendError::Disconnected)));
337    }
338}
339
340impl ActorEncode<BrowsingContextActorMsg> for BrowsingContextActor {
341    fn encode(&self, _: &ActorRegistry) -> BrowsingContextActorMsg {
342        BrowsingContextActorMsg {
343            actor: self.name().into(),
344            traits: BrowsingContextTraits {
345                is_browsing_context: true,
346                frames: true,
347                log_in_page: false,
348                navigation: true,
349                supports_top_level_target_flag: true,
350                watchpoints: true,
351            },
352            title: self.title(),
353            url: self.url(),
354            browser_id: self.browser_id.value(),
355            browsing_context_id: self.browsing_context_id.value(),
356            outer_window_id: self.outer_window_id().value(),
357            is_top_level_target: true,
358            accessibility_actor: self.accessibility_name.clone(),
359            console_actor: self.console_name.clone(),
360            css_properties_actor: self.css_properties_name.clone(),
361            inspector_actor: self.inspector_name.clone(),
362            reflow_actor: self.reflow_name.clone(),
363            style_sheets_actor: self.style_sheets_name.clone(),
364            thread_actor: self.thread_name.clone(),
365            target_type: TargetType::Frame,
366        }
367    }
368}