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