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