1use atomic_refcell::AtomicRefCell;
10use devtools_traits::DevtoolScriptControlMsg::{self, GetCssDatabase, SimulateColorScheme};
11use devtools_traits::{DevtoolsPageInfo, NavigationState};
12use embedder_traits::Theme;
13use malloc_size_of_derive::MallocSizeOf;
14use rustc_hash::FxHashMap;
15use serde::Serialize;
16use serde_json::{Map, Value};
17use servo_base::generic_channel::{self, GenericSender, SendError};
18use servo_base::id::PipelineId;
19
20use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry};
21use crate::actors::inspector::InspectorActor;
22use crate::actors::inspector::accessibility::AccessibilityActor;
23use crate::actors::inspector::css_properties::CssPropertiesActor;
24use crate::actors::reflow::ReflowActor;
25use crate::actors::stylesheets::StyleSheetsActor;
26use crate::actors::tab::TabDescriptorActor;
27use crate::actors::thread::ThreadActor;
28use crate::actors::watcher::{SessionContext, SessionContextType, WatcherActor};
29use crate::id::{DevtoolsBrowserId, DevtoolsBrowsingContextId, DevtoolsOuterWindowId, IdMap};
30use crate::protocol::{ClientRequest, DevtoolsConnection, JsonPacketStream};
31use crate::resource::ResourceAvailable;
32use crate::{EmptyReplyMsg, StreamId};
33
34#[derive(Serialize)]
35struct ListWorkersReply {
36 from: String,
37 workers: Vec<()>,
38}
39
40#[derive(Serialize)]
41struct FrameUpdateReply {
42 from: String,
43 #[serde(rename = "type")]
44 type_: String,
45 frames: Vec<FrameUpdateMsg>,
46}
47
48#[derive(Serialize)]
49#[serde(rename_all = "camelCase")]
50struct FrameUpdateMsg {
51 id: u32,
52 is_top_level: bool,
53 url: String,
54 title: String,
55}
56
57#[derive(Serialize)]
58struct TabNavigated {
59 from: String,
60 #[serde(rename = "type")]
61 type_: String,
62 url: String,
63 title: Option<String>,
64 #[serde(rename = "nativeConsoleAPI")]
65 native_console_api: bool,
66 state: String,
67 is_frame_switching: bool,
68}
69
70#[derive(Serialize)]
71#[serde(rename_all = "camelCase")]
72struct BrowsingContextTraits {
73 frames: bool,
74 is_browsing_context: bool,
75 log_in_page: bool,
76 navigation: bool,
77 supports_top_level_target_flag: bool,
78 watchpoints: bool,
79}
80
81#[derive(Serialize)]
82#[serde(rename_all = "lowercase")]
83enum TargetType {
84 Frame,
85 }
87
88#[derive(Serialize)]
89#[serde(rename_all = "camelCase")]
90pub(crate) struct BrowsingContextActorMsg {
91 actor: String,
92 title: String,
93 url: String,
94 #[serde(rename = "browserId")]
96 browser_id: u32,
97 #[serde(rename = "outerWindowID")]
98 outer_window_id: u32,
99 #[serde(rename = "browsingContextID")]
100 browsing_context_id: u32,
101 is_top_level_target: bool,
102 traits: BrowsingContextTraits,
103 accessibility_actor: String,
105 console_actor: String,
106 css_properties_actor: String,
107 inspector_actor: String,
108 reflow_actor: String,
109 style_sheets_actor: String,
110 thread_actor: String,
111 target_type: TargetType,
112 }
127
128#[derive(MallocSizeOf)]
132pub(crate) struct BrowsingContextActor {
133 name: String,
134 pub title: AtomicRefCell<String>,
135 pub url: AtomicRefCell<String>,
136 pub browser_id: DevtoolsBrowserId,
138 active_pipeline_id: AtomicRefCell<PipelineId>,
140 active_outer_window_id: AtomicRefCell<DevtoolsOuterWindowId>,
141 pub browsing_context_id: DevtoolsBrowsingContextId,
142 accessibility_name: String,
143 pub console_name: String,
144 css_properties_name: String,
145 pub(crate) inspector_name: String,
146 reflow_name: String,
147 style_sheets_name: String,
148 pub thread_name: String,
149 _tab: String,
150 script_chans: AtomicRefCell<FxHashMap<PipelineId, GenericSender<DevtoolScriptControlMsg>>>,
157 pub watcher_name: String,
158}
159
160impl ResourceAvailable for BrowsingContextActor {
161 fn actor_name(&self) -> String {
162 self.name.clone()
163 }
164}
165
166impl Actor for BrowsingContextActor {
167 fn name(&self) -> String {
168 self.name.clone()
169 }
170
171 fn handle_message(
172 &self,
173 request: ClientRequest,
174 _registry: &ActorRegistry,
175 msg_type: &str,
176 _msg: &Map<String, Value>,
177 _id: StreamId,
178 ) -> Result<(), ActorError> {
179 match msg_type {
180 "listFrames" => {
181 let msg = EmptyReplyMsg { from: self.name() };
183 request.reply_final(&msg)?
184 },
185 "listWorkers" => {
186 request.reply_final(&ListWorkersReply {
187 from: self.name(),
188 workers: vec![],
190 })?
191 },
192 _ => return Err(ActorError::UnrecognizedPacketType),
193 };
194 Ok(())
195 }
196}
197
198impl BrowsingContextActor {
199 #[expect(clippy::too_many_arguments)]
200 pub(crate) fn register(
201 registry: &ActorRegistry,
202 console_name: String,
203 browser_id: DevtoolsBrowserId,
204 browsing_context_id: DevtoolsBrowsingContextId,
205 page_info: DevtoolsPageInfo,
206 pipeline_id: PipelineId,
207 outer_window_id: DevtoolsOuterWindowId,
208 script_sender: GenericSender<DevtoolScriptControlMsg>,
209 ) -> String {
210 let name = registry.new_name::<BrowsingContextActor>();
211 let DevtoolsPageInfo {
212 title,
213 url,
214 is_top_level_global,
215 ..
216 } = page_info;
217
218 let accessibility_name = AccessibilityActor::register(registry);
219
220 let properties = (|| {
221 let (properties_sender, properties_receiver) = generic_channel::channel()?;
222 script_sender.send(GetCssDatabase(properties_sender)).ok()?;
223 properties_receiver.recv().ok()
224 })()
225 .unwrap_or_default();
226 let css_properties_name = CssPropertiesActor::register(registry, properties);
227
228 let inspector_name = InspectorActor::register(registry, name.clone());
229
230 let reflow_name = ReflowActor::register(registry);
231
232 let style_sheets_name = StyleSheetsActor::register(registry);
233
234 let tab_descriptor_actor =
235 TabDescriptorActor::new(registry, name.clone(), is_top_level_global);
236
237 let thread_name =
238 ThreadActor::register(registry, script_sender.clone(), Some(name.clone()));
239
240 let watcher_actor = WatcherActor::new(
241 registry,
242 name.clone(),
243 SessionContext::new(SessionContextType::BrowserElement),
244 );
245
246 let mut script_chans = FxHashMap::default();
247 script_chans.insert(pipeline_id, script_sender);
248
249 let actor = BrowsingContextActor {
250 name: name.clone(),
251 script_chans: AtomicRefCell::new(script_chans),
252 title: AtomicRefCell::new(title),
253 url: AtomicRefCell::new(url.into_string()),
254 active_pipeline_id: AtomicRefCell::new(pipeline_id),
255 active_outer_window_id: AtomicRefCell::new(outer_window_id),
256 browser_id,
257 browsing_context_id,
258 accessibility_name,
259 console_name,
260 css_properties_name,
261 inspector_name,
262 reflow_name,
263 style_sheets_name,
264 _tab: tab_descriptor_actor.name(),
265 thread_name,
266 watcher_name: watcher_actor.name(),
267 };
268
269 registry.register(tab_descriptor_actor);
270 registry.register(watcher_actor);
271 registry.register::<Self>(actor);
272
273 name
274 }
275
276 pub(crate) fn handle_new_global(
277 &self,
278 pipeline: PipelineId,
279 script_sender: GenericSender<DevtoolScriptControlMsg>,
280 ) {
281 self.script_chans
282 .borrow_mut()
283 .insert(pipeline, script_sender);
284 }
285
286 pub(crate) fn handle_navigate<'a>(
287 &self,
288 state: NavigationState,
289 id_map: &mut IdMap,
290 connections: impl Iterator<Item = &'a mut DevtoolsConnection>,
291 ) {
292 let (pipeline_id, title, url, state) = match state {
293 NavigationState::Start(url) => (None, None, url, "start"),
294 NavigationState::Stop(pipeline, info) => {
295 (Some(pipeline), Some(info.title), info.url, "stop")
296 },
297 };
298 if let Some(pipeline_id) = pipeline_id {
299 let outer_window_id = id_map.outer_window_id(pipeline_id);
300 *self.active_outer_window_id.borrow_mut() = outer_window_id;
301 *self.active_pipeline_id.borrow_mut() = pipeline_id;
302 }
303 url.as_str().clone_into(&mut self.url.borrow_mut());
304 if let Some(ref t) = title {
305 self.title.borrow_mut().clone_from(t);
306 }
307
308 let msg = TabNavigated {
309 from: self.name(),
310 type_: "tabNavigated".to_owned(),
311 url: url.as_str().to_owned(),
312 title,
313 native_console_api: true,
314 state: state.to_owned(),
315 is_frame_switching: false,
316 };
317
318 for stream in connections {
319 let _ = stream.write_json_packet(&msg);
320 }
321 }
322
323 pub(crate) fn title_changed(&self, pipeline_id: PipelineId, title: String) {
324 if pipeline_id != self.pipeline_id() {
325 return;
326 }
327 *self.title.borrow_mut() = title;
328 }
329
330 pub(crate) fn frame_update(&self, request: &mut ClientRequest) {
331 let _ = request.write_json_packet(&FrameUpdateReply {
332 from: self.name(),
333 type_: "frameUpdate".into(),
334 frames: vec![FrameUpdateMsg {
335 id: self.browsing_context_id.value(),
336 is_top_level: true,
337 title: self.title.borrow().clone(),
338 url: self.url.borrow().clone(),
339 }],
340 });
341 }
342
343 pub fn simulate_color_scheme(&self, theme: Theme) -> Result<(), ()> {
344 self.script_chan()
345 .send(SimulateColorScheme(self.pipeline_id(), theme))
346 .map_err(|_| ())
347 }
348
349 pub(crate) fn pipeline_id(&self) -> PipelineId {
350 *self.active_pipeline_id.borrow()
351 }
352
353 pub(crate) fn outer_window_id(&self) -> DevtoolsOuterWindowId {
354 *self.active_outer_window_id.borrow()
355 }
356
357 pub(crate) fn script_chan(&self) -> GenericSender<DevtoolScriptControlMsg> {
359 self.script_chans
360 .borrow()
361 .get(&self.pipeline_id())
362 .unwrap()
363 .clone()
364 }
365
366 pub(crate) fn instruct_script_to_send_live_updates(&self, should_send_updates: bool) {
367 let result = self
368 .script_chan()
369 .send(DevtoolScriptControlMsg::WantsLiveNotifications(
370 self.pipeline_id(),
371 should_send_updates,
372 ));
373
374 debug_assert!(matches!(result, Ok(_) | Err(SendError::Disconnected)));
377 }
378}
379
380impl ActorEncode<BrowsingContextActorMsg> for BrowsingContextActor {
381 fn encode(&self, _: &ActorRegistry) -> BrowsingContextActorMsg {
382 BrowsingContextActorMsg {
383 actor: self.name(),
384 traits: BrowsingContextTraits {
385 is_browsing_context: true,
386 frames: true,
387 log_in_page: false,
388 navigation: true,
389 supports_top_level_target_flag: true,
390 watchpoints: true,
391 },
392 title: self.title.borrow().clone(),
393 url: self.url.borrow().clone(),
394 browser_id: self.browser_id.value(),
395 browsing_context_id: self.browsing_context_id.value(),
396 outer_window_id: self.outer_window_id().value(),
397 is_top_level_target: true,
398 accessibility_actor: self.accessibility_name.clone(),
399 console_actor: self.console_name.clone(),
400 css_properties_actor: self.css_properties_name.clone(),
401 inspector_actor: self.inspector_name.clone(),
402 reflow_actor: self.reflow_name.clone(),
403 style_sheets_actor: self.style_sheets_name.clone(),
404 thread_actor: self.thread_name.clone(),
405 target_type: TargetType::Frame,
406 }
407 }
408}