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