1use std::collections::HashMap;
14
15use devtools_traits::get_time_stamp;
16use log::warn;
17use malloc_size_of_derive::MallocSizeOf;
18use serde::Serialize;
19use serde_json::{Map, Value};
20use servo_base::id::BrowsingContextId;
21use servo_url::ServoUrl;
22
23use self::network_parent::NetworkParentActor;
24use super::breakpoint::BreakpointListActor;
25use super::thread::ThreadActor;
26use super::worker::WorkerActorMsg;
27use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry};
28use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg};
29use crate::actors::console::ConsoleActor;
30use crate::actors::root::RootActor;
31use crate::actors::watcher::target_configuration::{
32 TargetConfigurationActor, TargetConfigurationActorMsg,
33};
34use crate::actors::watcher::thread_configuration::ThreadConfigurationActor;
35use crate::protocol::{ClientRequest, DevtoolsConnection, JsonPacketStream};
36use crate::resource::{ResourceArrayType, ResourceAvailable};
37use crate::{ActorMsg, EmptyReplyMsg, IdMap, StreamId, WorkerActor};
38
39pub mod network_parent;
40pub mod target_configuration;
41pub mod thread_configuration;
42
43#[derive(Serialize, MallocSizeOf)]
46#[serde(rename_all = "camelCase")]
47pub(crate) struct SessionContext {
48 is_server_target_switching_enabled: bool,
49 supported_targets: HashMap<&'static str, bool>,
50 supported_resources: HashMap<&'static str, bool>,
51 context_type: SessionContextType,
52}
53
54impl SessionContext {
55 pub fn new(context_type: SessionContextType) -> Self {
56 Self {
57 is_server_target_switching_enabled: false,
58 supported_targets: HashMap::from([
60 ("frame", true),
61 ("process", false),
62 ("worker", true),
63 ("service_worker", true),
64 ("shared_worker", false),
65 ]),
66 supported_resources: HashMap::from([
70 ("console-message", true),
71 ("css-change", true),
72 ("css-message", false),
73 ("css-registered-properties", false),
74 ("document-event", true),
75 ("Cache", false),
76 ("cookies", false),
77 ("error-message", true),
78 ("extension-storage", false),
79 ("indexed-db", false),
80 ("local-storage", false),
81 ("session-storage", false),
82 ("platform-message", true),
83 ("network-event", true),
84 ("network-event-stacktrace", false),
85 ("reflow", true),
86 ("stylesheet", false),
87 ("source", true),
88 ("thread-state", false),
89 ("server-sent-event", false),
90 ("websocket", false),
91 ("jstracer-trace", false),
92 ("jstracer-state", false),
93 ("last-private-context-exit", false),
94 ]),
95 context_type,
96 }
97 }
98}
99
100#[derive(Serialize, MallocSizeOf)]
101pub enum SessionContextType {
102 BrowserElement,
103 _ContextProcess,
104 _WebExtension,
105 _Worker,
106 _All,
107}
108
109#[derive(Serialize)]
110#[serde(untagged)]
111enum TargetActorMsg {
112 BrowsingContext(BrowsingContextActorMsg),
113 Worker(WorkerActorMsg),
114}
115
116#[derive(Serialize)]
117struct WatchTargetsReply {
118 from: String,
119 #[serde(rename = "type")]
120 type_: String,
121 target: TargetActorMsg,
122}
123
124#[derive(Serialize)]
125struct GetParentBrowsingContextIDReply {
126 from: String,
127 #[serde(rename = "browsingContextID")]
128 browsing_context_id: u32,
129}
130
131#[derive(Serialize)]
132struct GetNetworkParentActorReply {
133 from: String,
134 network: ActorMsg,
135}
136
137#[derive(Serialize)]
138struct GetTargetConfigurationActorReply {
139 from: String,
140 configuration: TargetConfigurationActorMsg,
141}
142
143#[derive(Serialize)]
144struct GetThreadConfigurationActorReply {
145 from: String,
146 configuration: ActorMsg,
147}
148
149#[derive(Serialize)]
150#[serde(rename_all = "camelCase")]
151struct GetBreakpointListActorReply {
152 from: String,
153 breakpoint_list: ActorMsg,
154}
155
156#[derive(Serialize)]
157#[serde(rename_all = "camelCase")]
158struct DocumentEvent {
159 #[serde(rename = "hasNativeConsoleAPI")]
160 has_native_console_api: Option<bool>,
161 name: String,
162 #[serde(rename = "newURI")]
163 new_uri: Option<String>,
164 time: u64,
165 title: Option<String>,
166 url: Option<String>,
167}
168
169#[derive(Serialize)]
170struct WatcherTraits {
171 resources: HashMap<&'static str, bool>,
172 #[serde(flatten)]
173 targets: HashMap<&'static str, bool>,
174}
175
176#[derive(Serialize)]
177pub(crate) struct WatcherActorMsg {
178 actor: String,
179 traits: WatcherTraits,
180}
181
182#[derive(MallocSizeOf)]
183pub(crate) struct WatcherActor {
184 name: String,
185 pub browsing_context_name: String,
186 network_parent_name: String,
187 target_configuration: String,
188 thread_configuration_name: String,
189 breakpoint_list_name: String,
190 session_context: SessionContext,
191}
192
193#[derive(Clone, Serialize)]
194#[serde(rename_all = "camelCase")]
195pub(crate) struct WillNavigateMessage {
196 #[serde(rename = "browsingContextID")]
197 browsing_context_id: u32,
198 inner_window_id: u32,
199 name: String,
200 time: u64,
201 is_frame_switching: bool,
202 #[serde(rename = "newURI")]
203 new_uri: ServoUrl,
204}
205
206impl Actor for WatcherActor {
207 fn name(&self) -> String {
208 self.name.clone()
209 }
210
211 fn handle_message(
234 &self,
235 mut request: ClientRequest,
236 registry: &ActorRegistry,
237 msg_type: &str,
238 msg: &Map<String, Value>,
239 _id: StreamId,
240 ) -> Result<(), ActorError> {
241 let browsing_context_actor =
242 registry.find::<BrowsingContextActor>(&self.browsing_context_name);
243 let root_actor = registry.find::<RootActor>("root");
244 match msg_type {
245 "watchTargets" => {
246 let target_type = msg
248 .get("targetType")
249 .and_then(Value::as_str)
250 .unwrap_or("frame"); if target_type == "frame" {
253 let msg = WatchTargetsReply {
254 from: self.name(),
255 type_: "target-available-form".into(),
256 target: TargetActorMsg::BrowsingContext(
257 browsing_context_actor.encode(registry),
258 ),
259 };
260 let _ = request.write_json_packet(&msg);
261
262 browsing_context_actor.frame_update(&mut request);
263 } else if target_type == "worker" {
264 for worker_name in &*root_actor.workers.borrow() {
265 let worker_msg = WatchTargetsReply {
266 from: self.name(),
267 type_: "target-available-form".into(),
268 target: TargetActorMsg::Worker(
269 registry.encode::<WorkerActor, _>(worker_name),
270 ),
271 };
272 let _ = request.write_json_packet(&worker_msg);
273 }
274 } else if target_type == "service_worker" {
275 for worker_name in &*root_actor.service_workers.borrow() {
276 let worker_msg = WatchTargetsReply {
277 from: self.name(),
278 type_: "target-available-form".into(),
279 target: TargetActorMsg::Worker(
280 registry.encode::<WorkerActor, _>(worker_name),
281 ),
282 };
283 let _ = request.write_json_packet(&worker_msg);
284 }
285 } else {
286 warn!("Unexpected target_type: {}", target_type);
287 }
288
289 let msg = EmptyReplyMsg { from: self.name() };
294 request.reply_final(&msg)?
295 },
296 "unwatchTargets" => {
297 request.mark_handled();
299 },
300 "watchResources" => {
301 let Some(resource_types) = msg.get("resourceTypes") else {
302 return Err(ActorError::MissingParameter);
303 };
304 let Some(resource_types) = resource_types.as_array() else {
305 return Err(ActorError::BadParameterType);
306 };
307
308 for resource in resource_types {
309 let Some(resource) = resource.as_str() else {
310 continue;
311 };
312 match resource {
313 "document-event" => {
314 for &name in ["dom-loading", "dom-interactive", "dom-complete"].iter() {
317 let event = DocumentEvent {
318 has_native_console_api: None,
319 name: name.into(),
320 new_uri: None,
321 time: get_time_stamp(),
322 title: Some(browsing_context_actor.title.borrow().clone()),
323 url: Some(browsing_context_actor.url.borrow().clone()),
324 };
325 browsing_context_actor.resource_array(
326 event,
327 resource.into(),
328 ResourceArrayType::Available,
329 &mut request,
330 );
331 }
332 },
333 "source" => {
334 let thread_actor =
335 registry.find::<ThreadActor>(&browsing_context_actor.thread_name);
336 browsing_context_actor.resources_array(
337 thread_actor.source_manager.source_forms(registry),
338 resource.into(),
339 ResourceArrayType::Available,
340 &mut request,
341 );
342
343 for worker_name in &*root_actor.workers.borrow() {
344 let worker_actor = registry.find::<WorkerActor>(worker_name);
345 let thread_actor =
346 registry.find::<ThreadActor>(&worker_actor.thread_name);
347
348 worker_actor.resources_array(
349 thread_actor.source_manager.source_forms(registry),
350 resource.into(),
351 ResourceArrayType::Available,
352 &mut request,
353 );
354 }
355 },
356 "console-message" | "error-message" => {
357 let console_actor =
358 registry.find::<ConsoleActor>(&browsing_context_actor.console_name);
359 console_actor.received_first_message_from_client();
360 browsing_context_actor.resources_array(
361 console_actor.get_cached_messages(registry, resource),
362 resource.into(),
363 ResourceArrayType::Available,
364 &mut request,
365 );
366
367 for worker_name in &*root_actor.workers.borrow() {
368 let worker_actor = registry.find::<WorkerActor>(worker_name);
369 let console_actor =
370 registry.find::<ConsoleActor>(&worker_actor.console_name);
371
372 worker_actor.resources_array(
373 console_actor.get_cached_messages(registry, resource),
374 resource.into(),
375 ResourceArrayType::Available,
376 &mut request,
377 );
378 }
379 },
380 "network-event" => {},
381 _ => warn!("resource {} not handled yet", resource),
382 }
383 }
384 let msg = EmptyReplyMsg { from: self.name() };
385 request.reply_final(&msg)?
386 },
387 "unwatchResources" => {
388 request.mark_handled();
390 },
391 "getParentBrowsingContextID" => {
392 let msg = GetParentBrowsingContextIDReply {
393 from: self.name(),
394 browsing_context_id: browsing_context_actor.browsing_context_id.value(),
395 };
396 request.reply_final(&msg)?
397 },
398 "getNetworkParentActor" => {
399 let msg = GetNetworkParentActorReply {
400 from: self.name(),
401 network: registry.encode::<NetworkParentActor, _>(&self.network_parent_name),
402 };
403 request.reply_final(&msg)?
404 },
405 "getTargetConfigurationActor" => {
406 let msg = GetTargetConfigurationActorReply {
407 from: self.name(),
408 configuration: registry
409 .encode::<TargetConfigurationActor, _>(&self.target_configuration),
410 };
411 request.reply_final(&msg)?
412 },
413 "getThreadConfigurationActor" => {
414 let msg = GetThreadConfigurationActorReply {
415 from: self.name(),
416 configuration: registry
417 .encode::<ThreadConfigurationActor, _>(&self.thread_configuration_name),
418 };
419 request.reply_final(&msg)?
420 },
421 "getBreakpointListActor" => {
422 let msg = GetBreakpointListActorReply {
423 from: self.name(),
424 breakpoint_list: registry
425 .encode::<BreakpointListActor, _>(&self.breakpoint_list_name),
426 };
427 request.reply_final(&msg)?
428 },
429 _ => return Err(ActorError::UnrecognizedPacketType),
430 };
431 Ok(())
432 }
433}
434
435impl ResourceAvailable for WatcherActor {
436 fn actor_name(&self) -> String {
437 self.name.clone()
438 }
439}
440
441impl WatcherActor {
442 pub fn new(
443 registry: &ActorRegistry,
444 browsing_context_name: String,
445 session_context: SessionContext,
446 ) -> Self {
447 let network_parent_name = NetworkParentActor::register(registry);
448 let target_configuration =
449 TargetConfigurationActor::new(registry.new_name::<TargetConfigurationActor>());
450 let thread_configuration_actor =
451 ThreadConfigurationActor::new(registry.new_name::<ThreadConfigurationActor>());
452 let breakpoint_list_name =
453 BreakpointListActor::register(registry, browsing_context_name.clone());
454
455 let watcher_actor = Self {
456 name: registry.new_name::<WatcherActor>(),
457 browsing_context_name,
458 network_parent_name,
459 target_configuration: target_configuration.name(),
460 thread_configuration_name: thread_configuration_actor.name(),
461 breakpoint_list_name,
462 session_context,
463 };
464
465 registry.register(target_configuration);
466 registry.register(thread_configuration_actor);
467
468 watcher_actor
469 }
470
471 pub fn emit_will_navigate<'a>(
472 &self,
473 browsing_context_id: BrowsingContextId,
474 url: ServoUrl,
475 connections: impl Iterator<Item = &'a mut DevtoolsConnection>,
476 id_map: &mut IdMap,
477 ) {
478 let msg = WillNavigateMessage {
479 browsing_context_id: id_map.browsing_context_id(browsing_context_id).value(),
480 inner_window_id: 0, name: "will-navigate".to_string(),
482 time: get_time_stamp(),
483 is_frame_switching: false, new_uri: url,
485 };
486
487 for stream in connections {
488 self.resource_array(
489 msg.clone(),
490 "document-event".to_string(),
491 ResourceArrayType::Available,
492 stream,
493 );
494 }
495 }
496}
497
498impl ActorEncode<WatcherActorMsg> for WatcherActor {
499 fn encode(&self, _: &ActorRegistry) -> WatcherActorMsg {
500 WatcherActorMsg {
501 actor: self.name(),
502 traits: WatcherTraits {
503 resources: self.session_context.supported_resources.clone(),
504 targets: self.session_context.supported_targets.clone(),
505 },
506 }
507 }
508}