1use serde::{Deserialize, Serialize};
21use serde_json::{Value, json};
22
23use crate::identifiers::RequestId;
24
25#[derive(Debug, Clone, Deserialize)]
42pub struct Event {
43 pub id: RequestId,
45
46 #[serde(rename = "type")]
48 pub event_type: String,
49
50 pub method: String,
52
53 pub params: Value,
55}
56
57impl Event {
58 #[inline]
67 #[must_use]
68 pub fn module(&self) -> &str {
69 self.method.split('.').next().unwrap_or_default()
70 }
71
72 #[inline]
81 #[must_use]
82 pub fn event_name(&self) -> &str {
83 self.method.split('.').nth(1).unwrap_or_default()
84 }
85
86 #[must_use]
88 pub fn parse(&self) -> ParsedEvent {
89 self.parse_internal()
90 }
91}
92
93#[derive(Debug, Clone, Serialize)]
111pub struct EventReply {
112 pub id: RequestId,
114
115 #[serde(rename = "replyTo")]
117 pub reply_to: String,
118
119 pub result: Value,
121}
122
123impl EventReply {
124 #[inline]
126 #[must_use]
127 pub fn new(id: RequestId, reply_to: impl Into<String>, result: Value) -> Self {
128 Self {
129 id,
130 reply_to: reply_to.into(),
131 result,
132 }
133 }
134
135 #[inline]
137 #[must_use]
138 pub fn allow(id: RequestId, reply_to: impl Into<String>) -> Self {
139 Self::new(id, reply_to, json!({ "action": "allow" }))
140 }
141
142 #[inline]
144 #[must_use]
145 pub fn block(id: RequestId, reply_to: impl Into<String>) -> Self {
146 Self::new(id, reply_to, json!({ "action": "block" }))
147 }
148
149 #[inline]
151 #[must_use]
152 pub fn redirect(id: RequestId, reply_to: impl Into<String>, url: impl Into<String>) -> Self {
153 Self::new(
154 id,
155 reply_to,
156 json!({ "action": "redirect", "url": url.into() }),
157 )
158 }
159}
160
161#[derive(Debug, Clone)]
167pub enum ParsedEvent {
168 BrowsingContextNavigationStarted {
170 tab_id: u32,
172 frame_id: u64,
174 url: String,
176 },
177
178 BrowsingContextDomContentLoaded {
180 tab_id: u32,
182 frame_id: u64,
184 url: String,
186 },
187
188 BrowsingContextLoad {
190 tab_id: u32,
192 frame_id: u64,
194 url: String,
196 },
197
198 BrowsingContextNavigationFailed {
200 tab_id: u32,
202 frame_id: u64,
204 url: String,
206 error: String,
208 },
209
210 ElementAdded {
212 selector: String,
214 element_id: String,
216 subscription_id: String,
218 tab_id: u32,
220 frame_id: u64,
222 },
223
224 ElementRemoved {
226 element_id: String,
228 tab_id: u32,
230 frame_id: u64,
232 },
233
234 ElementAttributeChanged {
236 element_id: String,
238 attribute_name: String,
240 old_value: Option<String>,
242 new_value: Option<String>,
244 tab_id: u32,
246 frame_id: u64,
248 },
249
250 NetworkBeforeRequestSent {
252 request_id: String,
254 url: String,
256 method: String,
258 resource_type: String,
260 },
261
262 NetworkResponseStarted {
264 request_id: String,
266 url: String,
268 status: u16,
270 status_text: String,
272 },
273
274 NetworkResponseCompleted {
276 request_id: String,
278 url: String,
280 status: u16,
282 },
283
284 Unknown {
286 method: String,
288 params: Value,
290 },
291}
292
293impl Event {
298 fn parse_internal(&self) -> ParsedEvent {
300 match self.method.as_str() {
301 "browsingContext.navigationStarted" => ParsedEvent::BrowsingContextNavigationStarted {
302 tab_id: self.get_u32("tabId"),
303 frame_id: self.get_u64("frameId"),
304 url: self.get_string("url"),
305 },
306
307 "browsingContext.domContentLoaded" => ParsedEvent::BrowsingContextDomContentLoaded {
308 tab_id: self.get_u32("tabId"),
309 frame_id: self.get_u64("frameId"),
310 url: self.get_string("url"),
311 },
312
313 "browsingContext.load" => ParsedEvent::BrowsingContextLoad {
314 tab_id: self.get_u32("tabId"),
315 frame_id: self.get_u64("frameId"),
316 url: self.get_string("url"),
317 },
318
319 "browsingContext.navigationFailed" => ParsedEvent::BrowsingContextNavigationFailed {
320 tab_id: self.get_u32("tabId"),
321 frame_id: self.get_u64("frameId"),
322 url: self.get_string("url"),
323 error: self.get_string("error"),
324 },
325
326 "element.added" => ParsedEvent::ElementAdded {
327 selector: self.get_string("selector"),
328 element_id: self.get_string("elementId"),
329 subscription_id: self.get_string("subscriptionId"),
330 tab_id: self.get_u32("tabId"),
331 frame_id: self.get_u64("frameId"),
332 },
333
334 "element.removed" => ParsedEvent::ElementRemoved {
335 element_id: self.get_string("elementId"),
336 tab_id: self.get_u32("tabId"),
337 frame_id: self.get_u64("frameId"),
338 },
339
340 "element.attributeChanged" => ParsedEvent::ElementAttributeChanged {
341 element_id: self.get_string("elementId"),
342 attribute_name: self.get_string("attributeName"),
343 old_value: self.get_optional_string("oldValue"),
344 new_value: self.get_optional_string("newValue"),
345 tab_id: self.get_u32("tabId"),
346 frame_id: self.get_u64("frameId"),
347 },
348
349 "network.beforeRequestSent" => ParsedEvent::NetworkBeforeRequestSent {
350 request_id: self.get_string("requestId"),
351 url: self.get_string("url"),
352 method: self.get_string_or("method", "GET"),
353 resource_type: self.get_string_or("resourceType", "other"),
354 },
355
356 "network.responseStarted" => ParsedEvent::NetworkResponseStarted {
357 request_id: self.get_string("requestId"),
358 url: self.get_string("url"),
359 status: self.get_u16("status"),
360 status_text: self.get_string("statusText"),
361 },
362
363 "network.responseCompleted" => ParsedEvent::NetworkResponseCompleted {
364 request_id: self.get_string("requestId"),
365 url: self.get_string("url"),
366 status: self.get_u16("status"),
367 },
368
369 _ => ParsedEvent::Unknown {
370 method: self.method.clone(),
371 params: self.params.clone(),
372 },
373 }
374 }
375
376 #[inline]
378 fn get_string(&self, key: &str) -> String {
379 self.params
380 .get(key)
381 .and_then(|v| v.as_str())
382 .unwrap_or_default()
383 .to_string()
384 }
385
386 #[inline]
388 fn get_string_or(&self, key: &str, default: &str) -> String {
389 self.params
390 .get(key)
391 .and_then(|v| v.as_str())
392 .unwrap_or(default)
393 .to_string()
394 }
395
396 #[inline]
398 fn get_optional_string(&self, key: &str) -> Option<String> {
399 self.params
400 .get(key)
401 .and_then(|v| v.as_str())
402 .map(|s| s.to_string())
403 }
404
405 #[inline]
407 fn get_u32(&self, key: &str) -> u32 {
408 self.params
409 .get(key)
410 .and_then(|v| v.as_u64())
411 .unwrap_or_default() as u32
412 }
413
414 #[inline]
416 fn get_u64(&self, key: &str) -> u64 {
417 self.params
418 .get(key)
419 .and_then(|v| v.as_u64())
420 .unwrap_or_default()
421 }
422
423 #[inline]
425 fn get_u16(&self, key: &str) -> u16 {
426 self.params
427 .get(key)
428 .and_then(|v| v.as_u64())
429 .unwrap_or_default() as u16
430 }
431}
432
433#[cfg(test)]
438mod tests {
439 use super::*;
440
441 #[test]
442 fn test_event_parsing() {
443 let json_str = r#"{
444 "id": "550e8400-e29b-41d4-a716-446655440000",
445 "type": "event",
446 "method": "browsingContext.load",
447 "params": {
448 "tabId": 1,
449 "frameId": 0,
450 "url": "https://example.com"
451 }
452 }"#;
453
454 let event: Event = serde_json::from_str(json_str).expect("parse event");
455 assert_eq!(event.module(), "browsingContext");
456 assert_eq!(event.event_name(), "load");
457
458 let parsed = event.parse();
459 match parsed {
460 ParsedEvent::BrowsingContextLoad {
461 tab_id,
462 frame_id,
463 url,
464 } => {
465 assert_eq!(tab_id, 1);
466 assert_eq!(frame_id, 0);
467 assert_eq!(url, "https://example.com");
468 }
469 _ => panic!("unexpected parsed event type"),
470 }
471 }
472
473 #[test]
474 fn test_event_reply_allow() {
475 let id = RequestId::generate();
476 let reply = EventReply::allow(id, "network.beforeRequestSent");
477 let json = serde_json::to_string(&reply).expect("serialize");
478
479 assert!(json.contains("replyTo"));
480 assert!(json.contains("allow"));
481 }
482
483 #[test]
484 fn test_event_reply_block() {
485 let id = RequestId::generate();
486 let reply = EventReply::block(id, "network.beforeRequestSent");
487 let json = serde_json::to_string(&reply).expect("serialize");
488
489 assert!(json.contains("block"));
490 }
491
492 #[test]
493 fn test_event_reply_redirect() {
494 let id = RequestId::generate();
495 let reply = EventReply::redirect(id, "network.beforeRequestSent", "https://other.com");
496 let json = serde_json::to_string(&reply).expect("serialize");
497
498 assert!(json.contains("redirect"));
499 assert!(json.contains("https://other.com"));
500 }
501
502 #[test]
503 fn test_element_added_parsing() {
504 let json_str = r##"{
505 "id": "550e8400-e29b-41d4-a716-446655440000",
506 "type": "event",
507 "method": "element.added",
508 "params": {
509 "selector": "#login-form",
510 "elementId": "elem-123",
511 "subscriptionId": "sub-456",
512 "tabId": 1,
513 "frameId": 0
514 }
515 }"##;
516
517 let event: Event = serde_json::from_str(json_str).expect("parse event");
518 let parsed = event.parse();
519
520 match parsed {
521 ParsedEvent::ElementAdded {
522 selector,
523 element_id,
524 ..
525 } => {
526 assert_eq!(selector, "#login-form");
527 assert_eq!(element_id, "elem-123");
528 }
529 _ => panic!("unexpected parsed event type"),
530 }
531 }
532
533 #[test]
534 fn test_unknown_event() {
535 let json_str = r#"{
536 "id": "550e8400-e29b-41d4-a716-446655440000",
537 "type": "event",
538 "method": "custom.unknownEvent",
539 "params": { "foo": "bar" }
540 }"#;
541
542 let event: Event = serde_json::from_str(json_str).expect("parse event");
543 let parsed = event.parse();
544
545 match parsed {
546 ParsedEvent::Unknown { method, .. } => {
547 assert_eq!(method, "custom.unknownEvent");
548 }
549 _ => panic!("expected Unknown variant"),
550 }
551 }
552}