1use 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 }
76
77#[derive(Serialize)]
78#[serde(rename_all = "camelCase")]
79pub(crate) struct BrowsingContextActorMsg {
80 actor: String,
81 title: String,
82 url: String,
83 #[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 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 }
116
117#[derive(MallocSizeOf)]
121pub(crate) struct BrowsingContextActor {
122 name: String,
123 pub browser_id: DevtoolsBrowserId,
125 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 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 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 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 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 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}