1use super::blockers::{
2 ignore_script_embedded, ignore_script_xhr, ignore_script_xhr_media,
3 intercept_manager::NetworkInterceptManager,
4 scripts::{
5 URL_IGNORE_SCRIPT_BASE_PATHS, URL_IGNORE_SCRIPT_STYLES_PATHS, URL_IGNORE_TRIE,
6 URL_IGNORE_TRIE_PATHS,
7 },
8 xhr::IGNORE_XHR_ASSETS,
9};
10use crate::auth::Credentials;
11use crate::cmd::CommandChain;
12use crate::handler::http::HttpRequest;
13use aho_corasick::AhoCorasick;
14use case_insensitive_string::CaseInsensitiveString;
15use chromiumoxide_cdp::cdp::browser_protocol::network::{
16 EmulateNetworkConditionsParams, EventLoadingFailed, EventLoadingFinished,
17 EventRequestServedFromCache, EventRequestWillBeSent, EventResponseReceived, Headers,
18 InterceptionId, RequestId, ResourceType, Response, SetCacheDisabledParams,
19 SetExtraHttpHeadersParams,
20};
21use chromiumoxide_cdp::cdp::browser_protocol::{
22 fetch::{
23 self, AuthChallengeResponse, AuthChallengeResponseResponse, ContinueRequestParams,
24 ContinueWithAuthParams, DisableParams, EventAuthRequired, EventRequestPaused,
25 RequestPattern,
26 },
27 network::SetBypassServiceWorkerParams,
28};
29use chromiumoxide_cdp::cdp::browser_protocol::{
30 network::EnableParams, security::SetIgnoreCertificateErrorsParams,
31};
32use chromiumoxide_types::{Command, Method, MethodId};
33use hashbrown::{HashMap, HashSet};
34use lazy_static::lazy_static;
35use reqwest::header::PROXY_AUTHORIZATION;
36use std::collections::VecDeque;
37use std::time::Duration;
38
39lazy_static! {
40 static ref JS_FRAMEWORK_ALLOW: Vec<&'static str> = vec![
42 "jquery", "angular",
44 "react", "vue", "bootstrap",
47 "d3",
48 "lodash",
49 "ajax",
50 "application",
51 "app", "main",
53 "index",
54 "bundle",
55 "vendor",
56 "runtime",
57 "polyfill",
58 "scripts",
59 "es2015.",
60 "es2020.",
61 "webpack",
62 "/wp-content/js/", "https://m.stripe.network/",
65 "https://challenges.cloudflare.com/",
66 "https://www.google.com/recaptcha/api.js",
67 "https://google.com/recaptcha/api.js",
68 "https://js.stripe.com/",
69 "https://cdn.prod.website-files.com/", "https://cdnjs.cloudflare.com/", "https://code.jquery.com/jquery-"
72 ];
73
74 pub static ref ALLOWED_MATCHER: AhoCorasick = AhoCorasick::new(JS_FRAMEWORK_ALLOW.iter()).unwrap();
76
77 pub static ref JS_FRAMEWORK_PATH: phf::Set<&'static str> = {
79 phf::phf_set! {
80 "_next/static/", "_astro/", "_app/immutable"
82 }
83 };
84
85 pub static ref IGNORE_CONTENT_TYPES: phf::Set<&'static str> = phf::phf_set! {
87 "application/pdf",
88 "application/zip",
89 "application/x-rar-compressed",
90 "application/x-tar",
91 "image/png",
92 "image/jpeg",
93 "image/gif",
94 "image/bmp",
95 "image/svg+xml",
96 "video/mp4",
97 "video/x-msvideo",
98 "video/x-matroska",
99 "video/webm",
100 "audio/mpeg",
101 "audio/ogg",
102 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
103 "application/vnd.ms-excel",
104 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
105 "application/vnd.ms-powerpoint",
106 "application/vnd.openxmlformats-officedocument.presentationml.presentation",
107 "application/x-7z-compressed",
108 "application/x-rpm",
109 "application/x-shockwave-flash",
110 };
111
112 pub static ref IGNORE_VISUAL_RESOURCE_MAP: phf::Set<&'static str> = phf::phf_set! {
114 "Image",
115 "Media",
116 "Font"
117 };
118
119 pub static ref IGNORE_NETWORKING_RESOURCE_MAP: phf::Set<&'static str> = phf::phf_set! {
121 "Prefetch",
122 "Ping",
123 };
124
125 pub static ref CSS_EXTENSION: CaseInsensitiveString = CaseInsensitiveString::from("css");
127
128
129 pub static ref INIT_CHAIN:Vec<(std::borrow::Cow<'static, str>, serde_json::Value)> = {
131 let enable = EnableParams::default();
132
133 if let Ok(c) = serde_json::to_value(&enable) {
134 vec![(enable.identifier(), c)]
135 } else {
136 vec![]
137 }
138 };
139
140 pub static ref INIT_CHAIN_IGNORE_HTTP_ERRORS:Vec<(std::borrow::Cow<'static, str>, serde_json::Value)> = {
142 let enable = EnableParams::default();
143 let mut v = vec![];
144 if let Ok(c) = serde_json::to_value(&enable) {
145 v.push((enable.identifier(), c));
146 }
147 let ignore = SetIgnoreCertificateErrorsParams::new(true);
148 if let Ok(ignored) = serde_json::to_value(&ignore) {
149 v.push((ignore.identifier(), ignored));
150 }
151
152 v
153 };
154
155 pub static ref ENABLE_FETCH: chromiumoxide_cdp::cdp::browser_protocol::fetch::EnableParams = {
157 fetch::EnableParams::builder()
158 .handle_auth_requests(true)
159 .pattern(RequestPattern::builder().url_pattern("*").build())
160 .build()
161 };
162}
163
164#[derive(Debug)]
165pub struct NetworkManager {
166 queued_events: VecDeque<NetworkEvent>,
167 ignore_httpserrors: bool,
168 requests: HashMap<RequestId, HttpRequest>,
169 requests_will_be_sent: HashMap<RequestId, EventRequestWillBeSent>,
171 extra_headers: std::collections::HashMap<String, String>,
172 request_id_to_interception_id: HashMap<RequestId, InterceptionId>,
173 user_cache_disabled: bool,
174 attempted_authentications: HashSet<RequestId>,
175 credentials: Option<Credentials>,
176 user_request_interception_enabled: bool,
178 protocol_request_interception_enabled: bool,
179 offline: bool,
180 request_timeout: Duration,
181 pub ignore_visuals: bool,
184 pub block_stylesheets: bool,
186 pub block_javascript: bool,
188 pub block_analytics: bool,
190 pub only_html: bool,
192 pub intercept_manager: NetworkInterceptManager,
194 pub document_reload_tracker: u8,
196 pub document_target_domain: String,
198}
199
200impl NetworkManager {
201 pub fn new(ignore_httpserrors: bool, request_timeout: Duration) -> Self {
202 Self {
203 queued_events: Default::default(),
204 ignore_httpserrors,
205 requests: Default::default(),
206 requests_will_be_sent: Default::default(),
207 extra_headers: Default::default(),
208 request_id_to_interception_id: Default::default(),
209 user_cache_disabled: false,
210 attempted_authentications: Default::default(),
211 credentials: None,
212 user_request_interception_enabled: false,
213 protocol_request_interception_enabled: false,
214 offline: false,
215 request_timeout,
216 ignore_visuals: false,
217 block_javascript: false,
218 block_stylesheets: false,
219 block_analytics: true,
220 only_html: false,
221 intercept_manager: NetworkInterceptManager::UNKNOWN,
222 document_reload_tracker: 0,
223 document_target_domain: String::new(),
224 }
225 }
226
227 pub fn init_commands(&self) -> CommandChain {
228 let cmds = if self.ignore_httpserrors {
229 INIT_CHAIN_IGNORE_HTTP_ERRORS.clone()
230 } else {
231 INIT_CHAIN.clone()
232 };
233
234 CommandChain::new(cmds, self.request_timeout)
235 }
236
237 fn push_cdp_request<T: Command>(&mut self, cmd: T) {
238 let method = cmd.identifier();
239 if let Ok(params) = serde_json::to_value(cmd) {
240 self.queued_events
241 .push_back(NetworkEvent::SendCdpRequest((method, params)));
242 }
243 }
244
245 pub fn poll(&mut self) -> Option<NetworkEvent> {
247 self.queued_events.pop_front()
248 }
249
250 pub fn extra_headers(&self) -> &std::collections::HashMap<String, String> {
251 &self.extra_headers
252 }
253
254 pub fn set_extra_headers(&mut self, headers: std::collections::HashMap<String, String>) {
255 self.extra_headers = headers;
256 self.extra_headers.remove(PROXY_AUTHORIZATION.as_str());
257 if let Ok(headers) = serde_json::to_value(&self.extra_headers) {
258 self.push_cdp_request(SetExtraHttpHeadersParams::new(Headers::new(headers)));
259 }
260 }
261
262 pub fn set_service_worker_enabled(&mut self, bypass: bool) {
263 self.push_cdp_request(SetBypassServiceWorkerParams::new(bypass));
264 }
265
266 pub fn set_request_interception(&mut self, enabled: bool) {
267 self.user_request_interception_enabled = enabled;
268 self.update_protocol_request_interception();
269 }
270
271 pub fn set_cache_enabled(&mut self, enabled: bool) {
272 self.user_cache_disabled = !enabled;
273 self.update_protocol_cache_disabled();
274 }
275
276 pub fn update_protocol_cache_disabled(&mut self) {
277 self.push_cdp_request(SetCacheDisabledParams::new(
278 self.user_cache_disabled || self.protocol_request_interception_enabled,
279 ));
280 }
281
282 pub fn authenticate(&mut self, credentials: Credentials) {
283 self.credentials = Some(credentials);
284 self.update_protocol_request_interception()
285 }
286
287 fn update_protocol_request_interception(&mut self) {
288 let enabled = self.user_request_interception_enabled || self.credentials.is_some();
289
290 if enabled == self.protocol_request_interception_enabled {
291 return;
292 }
293
294 self.update_protocol_cache_disabled();
295
296 if enabled {
297 self.push_cdp_request(ENABLE_FETCH.clone())
298 } else {
299 self.push_cdp_request(DisableParams::default())
300 }
301 }
302
303 pub(crate) fn ignore_script(
305 &self,
306 url: &str,
307 block_analytics: bool,
308 intercept_manager: NetworkInterceptManager,
309 ) -> bool {
310 let mut ignore_script = block_analytics && URL_IGNORE_TRIE.contains_prefix(url);
311
312 if !ignore_script {
313 if let Some(index) = url.find("//") {
314 let pos = index + 2;
315
316 if pos < url.len() {
318 if let Some(slash_index) = url[pos..].find('/') {
320 let base_path_index = pos + slash_index + 1;
321
322 if url.len() > base_path_index {
323 let new_url: &str = &url[base_path_index..];
324
325 ignore_script = URL_IGNORE_TRIE_PATHS.contains_prefix(new_url);
326
327 if !ignore_script
329 && intercept_manager == NetworkInterceptManager::UNKNOWN
330 {
331 let hydration_file =
332 JS_FRAMEWORK_PATH.iter().any(|p| new_url.starts_with(p));
333
334 if hydration_file && new_url.ends_with(".js") {
336 ignore_script = true;
337 }
338 }
339
340 if !ignore_script
341 && URL_IGNORE_SCRIPT_BASE_PATHS.contains_prefix(new_url)
342 {
343 ignore_script = true;
344 }
345
346 if !ignore_script
347 && self.ignore_visuals
348 && URL_IGNORE_SCRIPT_STYLES_PATHS.contains_prefix(new_url)
349 {
350 ignore_script = true;
351 }
352 }
353 }
354 }
355 }
356 }
357
358 if !ignore_script {
360 ignore_script = url.ends_with("analytics.js")
361 || url.ends_with("ads.js")
362 || url.ends_with("tracking.js")
363 || url.ends_with("track.js");
364 }
365
366 ignore_script
367 }
368
369 fn skip_xhr(
371 &self,
372 skip_networking: bool,
373 event: &EventRequestPaused,
374 network_event: bool,
375 ) -> bool {
376 if !skip_networking && network_event {
378 let request_url = event.request.url.as_str();
379
380 let skip_analytics = self.block_analytics && ignore_script_xhr(request_url);
382
383 if skip_analytics {
384 true
385 } else if self.block_stylesheets || self.ignore_visuals {
386 let block_css = self.block_stylesheets;
387 let block_media = self.ignore_visuals;
388
389 let mut block_request = false;
390
391 if let Some(position) = request_url.rfind('.') {
392 let hlen = request_url.len();
393 let has_asset = hlen - position;
394
395 if has_asset >= 3 {
396 let next_position = position + 1;
397
398 if block_media
399 && IGNORE_XHR_ASSETS.contains::<CaseInsensitiveString>(
400 &request_url[next_position..].into(),
401 )
402 {
403 block_request = true;
404 } else if block_css {
405 block_request =
406 CaseInsensitiveString::from(request_url[next_position..].as_bytes())
407 .contains(&**CSS_EXTENSION)
408 }
409 }
410 }
411
412 if !block_request {
413 block_request = ignore_script_xhr_media(request_url);
414 }
415
416 block_request
417 } else {
418 skip_networking
419 }
420 } else {
421 skip_networking
422 }
423 }
424
425 #[cfg(not(feature = "adblock"))]
426 pub fn on_fetch_request_paused(&mut self, event: &EventRequestPaused) {
427 use super::blockers::block_websites::block_website;
428
429 if !self.user_request_interception_enabled && self.protocol_request_interception_enabled {
430 self.push_cdp_request(ContinueRequestParams::new(event.request_id.clone()))
431 } else {
432 if let Some(network_id) = event.network_id.as_ref() {
433 if let Some(request_will_be_sent) =
434 self.requests_will_be_sent.remove(network_id.as_ref())
435 {
436 self.on_request(&request_will_be_sent, Some(event.request_id.clone().into()));
437 } else {
438 let current_url = event.request.url.as_str();
439 let javascript_resource = event.resource_type == ResourceType::Script;
440
441 let skip_networking = event.resource_type == ResourceType::Other
442 || event.resource_type == ResourceType::Manifest
443 || event.resource_type == ResourceType::CspViolationReport
444 || event.resource_type == ResourceType::Ping
445 || event.resource_type == ResourceType::Prefetch
446 || self.document_reload_tracker >= 3;
447
448 let network_resource = event.resource_type == ResourceType::Xhr
449 || event.resource_type == ResourceType::Fetch
450 || event.resource_type == ResourceType::WebSocket;
451
452 let document_resource = event.resource_type == ResourceType::Document;
453
454 if document_resource {
455 if self.document_target_domain == current_url {
456 self.document_reload_tracker += 1;
458 }
459 self.document_target_domain = event.request.url.clone();
460 }
461
462 let skip_networking = if !skip_networking {
464 IGNORE_NETWORKING_RESOURCE_MAP.contains(event.resource_type.as_ref())
465 || self.ignore_visuals
466 && (IGNORE_VISUAL_RESOURCE_MAP
467 .contains(event.resource_type.as_ref()))
468 || self.block_stylesheets
469 && ResourceType::Stylesheet == event.resource_type
470 || self.block_javascript
471 && javascript_resource
472 && self.intercept_manager == NetworkInterceptManager::UNKNOWN
473 && !ALLOWED_MATCHER.is_match(current_url)
474 } else {
475 skip_networking
476 };
477
478 let skip_networking = if !skip_networking
479 && (self.only_html || self.ignore_visuals)
480 && (javascript_resource || document_resource)
481 {
482 ignore_script_embedded(current_url)
483 } else {
484 skip_networking
485 };
486
487 let skip_networking = if !skip_networking && javascript_resource {
489 self.ignore_script(
490 current_url,
491 self.block_analytics,
492 self.intercept_manager,
493 )
494 } else {
495 skip_networking
496 };
497
498 let skip_networking = self.skip_xhr(skip_networking, &event, network_resource);
500
501 let skip_networking = if !skip_networking
503 && (javascript_resource || network_resource || document_resource)
504 {
505 self.intercept_manager.intercept_detection(
506 &event.request.url,
507 self.ignore_visuals,
508 network_resource,
509 )
510 } else {
511 skip_networking
512 };
513
514 let skip_networking =
515 if !skip_networking && (javascript_resource || network_resource) {
516 block_website(&event.request.url)
517 } else {
518 skip_networking
519 };
520
521 if skip_networking {
522 tracing::debug!(
523 "Blocked: {:?} - {}",
524 event.resource_type,
525 event.request.url
526 );
527 let fullfill_params =
528 crate::handler::network::fetch::FulfillRequestParams::new(
529 event.request_id.clone(),
530 200,
531 );
532 self.push_cdp_request(fullfill_params);
533 } else {
534 tracing::debug!(
535 "Allowed: {:?} - {}",
536 event.resource_type,
537 event.request.url
538 );
539
540 self.push_cdp_request(ContinueRequestParams::new(event.request_id.clone()))
541 }
542 }
543 } else {
544 self.push_cdp_request(ContinueRequestParams::new(event.request_id.clone()))
545 }
546 }
547 }
548
549 #[cfg(feature = "adblock")]
550 pub fn on_fetch_request_paused(&mut self, event: &EventRequestPaused) {
551 if !self.user_request_interception_enabled && self.protocol_request_interception_enabled {
552 self.push_cdp_request(ContinueRequestParams::new(event.request_id.clone()))
553 } else {
554 if let Some(network_id) = event.network_id.as_ref() {
555 if let Some(request_will_be_sent) =
556 self.requests_will_be_sent.remove(network_id.as_ref())
557 {
558 self.on_request(&request_will_be_sent, Some(event.request_id.clone().into()));
559 } else {
560 let current_url = event.request.url.as_str();
561 let javascript_resource = event.resource_type == ResourceType::Script;
562 let skip_networking = event.resource_type == ResourceType::Other
563 || event.resource_type == ResourceType::Manifest
564 || event.resource_type == ResourceType::CspViolationReport
565 || event.resource_type == ResourceType::Ping
566 || event.resource_type == ResourceType::Prefetch
567 || self.document_reload_tracker >= 3;
568 let network_resource = event.resource_type == ResourceType::Xhr
569 || event.resource_type == ResourceType::Fetch
570 || event.resource_type == ResourceType::WebSocket;
571 let document_resource = event.resource_type == ResourceType::Document;
572
573 if document_resource {
574 if self.document_target_domain == current_url {
575 self.document_reload_tracker += 1;
577 }
578 self.document_target_domain = event.request.url.clone();
579 }
580
581 let skip_networking = if !skip_networking {
583 IGNORE_NETWORKING_RESOURCE_MAP.contains(event.resource_type.as_ref())
584 || self.ignore_visuals
585 && (IGNORE_VISUAL_RESOURCE_MAP
586 .contains(event.resource_type.as_ref()))
587 || self.block_stylesheets
588 && ResourceType::Stylesheet == event.resource_type
589 || self.block_javascript
590 && javascript_resource
591 && self.intercept_manager == NetworkInterceptManager::UNKNOWN
592 && !ALLOWED_MATCHER.is_match(current_url)
593 } else {
594 skip_networking
595 };
596
597 let skip_networking = if !skip_networking {
598 self.detect_ad(event)
599 } else {
600 skip_networking
601 };
602
603 let skip_networking = if !skip_networking
604 && (self.only_html || self.ignore_visuals)
605 && (javascript_resource || document_resource)
606 {
607 ignore_script_embedded(current_url)
608 } else {
609 skip_networking
610 };
611
612 let skip_networking = if !skip_networking && javascript_resource {
614 self.ignore_script(
615 current_url,
616 self.block_analytics,
617 self.intercept_manager,
618 )
619 } else {
620 skip_networking
621 };
622
623 let skip_networking = self.skip_xhr(skip_networking, &event, network_resource);
625
626 let skip_networking = if !skip_networking
628 && (javascript_resource || network_resource || document_resource)
629 {
630 self.intercept_manager.intercept_detection(
631 &event.request.url,
632 self.ignore_visuals,
633 network_resource,
634 )
635 } else {
636 skip_networking
637 };
638
639 let skip_networking =
640 if !skip_networking && (javascript_resource || network_resource) {
641 block_website(&event.request.url)
642 } else {
643 skip_networking
644 };
645
646 if skip_networking {
647 let fullfill_params =
648 crate::handler::network::fetch::FulfillRequestParams::new(
649 event.request_id.clone(),
650 200,
651 );
652 self.push_cdp_request(fullfill_params);
653 } else {
654 self.push_cdp_request(ContinueRequestParams::new(event.request_id.clone()))
655 }
656 }
657 }
658 }
659
660 }
664
665 #[cfg(feature = "adblock")]
667 pub fn detect_ad(&self, event: &EventRequestPaused) -> bool {
668 use adblock::{
669 lists::{FilterSet, ParseOptions, RuleTypes},
670 Engine,
671 };
672
673 lazy_static::lazy_static! {
674 static ref AD_ENGINE: Engine = {
675 let mut filter_set = FilterSet::new(false);
676 let mut rules = ParseOptions::default();
677 rules.rule_types = RuleTypes::All;
678
679 filter_set.add_filters(
680 &*crate::handler::blockers::adblock_patterns::ADBLOCK_PATTERNS,
681 rules,
682 );
683
684 Engine::from_filter_set(filter_set, true)
685 };
686 };
687
688 let blockable = ResourceType::Image == event.resource_type
689 || event.resource_type == ResourceType::Media
690 || event.resource_type == ResourceType::Stylesheet
691 || event.resource_type == ResourceType::Document
692 || event.resource_type == ResourceType::Fetch
693 || event.resource_type == ResourceType::Xhr;
694
695 let u = &event.request.url;
696
697 let block_request = blockable
698 && {
700 let request = adblock::request::Request::preparsed(
701 &u,
702 "example.com",
703 "example.com",
704 &event.resource_type.as_ref().to_lowercase(),
705 !event.request.is_same_site.unwrap_or_default());
706
707 AD_ENGINE.check_network_request(&request).matched
708 };
709
710 block_request
711 }
712
713 pub fn on_fetch_auth_required(&mut self, event: &EventAuthRequired) {
714 let response = if self
715 .attempted_authentications
716 .contains(event.request_id.as_ref())
717 {
718 AuthChallengeResponseResponse::CancelAuth
719 } else if self.credentials.is_some() {
720 self.attempted_authentications
721 .insert(event.request_id.clone().into());
722 AuthChallengeResponseResponse::ProvideCredentials
723 } else {
724 AuthChallengeResponseResponse::Default
725 };
726
727 let mut auth = AuthChallengeResponse::new(response);
728 if let Some(creds) = self.credentials.clone() {
729 auth.username = Some(creds.username);
730 auth.password = Some(creds.password);
731 }
732 self.push_cdp_request(ContinueWithAuthParams::new(event.request_id.clone(), auth));
733 }
734
735 pub fn set_offline_mode(&mut self, value: bool) {
736 if self.offline == value {
737 return;
738 }
739 self.offline = value;
740 if let Ok(network) = EmulateNetworkConditionsParams::builder()
741 .offline(self.offline)
742 .latency(0)
743 .download_throughput(-1.)
744 .upload_throughput(-1.)
745 .build()
746 {
747 self.push_cdp_request(network);
748 }
749 }
750
751 pub fn on_request_will_be_sent(&mut self, event: &EventRequestWillBeSent) {
753 if self.protocol_request_interception_enabled && !event.request.url.starts_with("data:") {
754 if let Some(interception_id) = self
755 .request_id_to_interception_id
756 .remove(event.request_id.as_ref())
757 {
758 self.on_request(event, Some(interception_id));
759 } else {
760 self.requests_will_be_sent
762 .insert(event.request_id.clone(), event.clone());
763 }
764 } else {
765 self.on_request(event, None);
766 }
767 }
768
769 pub fn on_request_served_from_cache(&mut self, event: &EventRequestServedFromCache) {
770 if let Some(request) = self.requests.get_mut(event.request_id.as_ref()) {
771 request.from_memory_cache = true;
772 }
773 }
774
775 pub fn on_response_received(&mut self, event: &EventResponseReceived) {
776 if let Some(mut request) = self.requests.remove(event.request_id.as_ref()) {
777 request.set_response(event.response.clone());
778 self.queued_events
779 .push_back(NetworkEvent::RequestFinished(request))
780 }
781 }
782
783 pub fn on_network_loading_finished(&mut self, event: &EventLoadingFinished) {
784 if let Some(request) = self.requests.remove(event.request_id.as_ref()) {
785 if let Some(interception_id) = request.interception_id.as_ref() {
786 self.attempted_authentications
787 .remove(interception_id.as_ref());
788 }
789 self.queued_events
790 .push_back(NetworkEvent::RequestFinished(request));
791 }
792 }
793
794 pub fn on_network_loading_failed(&mut self, event: &EventLoadingFailed) {
795 if let Some(mut request) = self.requests.remove(event.request_id.as_ref()) {
796 request.failure_text = Some(event.error_text.clone());
797 if let Some(interception_id) = request.interception_id.as_ref() {
798 self.attempted_authentications
799 .remove(interception_id.as_ref());
800 }
801 self.queued_events
802 .push_back(NetworkEvent::RequestFailed(request));
803 }
804 }
805
806 fn on_request(
807 &mut self,
808 event: &EventRequestWillBeSent,
809 interception_id: Option<InterceptionId>,
810 ) {
811 let mut redirect_chain = Vec::new();
812 if let Some(redirect_resp) = event.redirect_response.as_ref() {
813 if let Some(mut request) = self.requests.remove(event.request_id.as_ref()) {
814 self.handle_request_redirect(&mut request, redirect_resp.clone());
815 redirect_chain = std::mem::take(&mut request.redirect_chain);
816 redirect_chain.push(request);
817 }
818 }
819 let request = HttpRequest::new(
820 event.request_id.clone(),
821 event.frame_id.clone(),
822 interception_id,
823 self.user_request_interception_enabled,
824 redirect_chain,
825 );
826
827 self.requests.insert(event.request_id.clone(), request);
828 self.queued_events
829 .push_back(NetworkEvent::Request(event.request_id.clone()));
830 }
831
832 fn handle_request_redirect(&mut self, request: &mut HttpRequest, response: Response) {
833 request.set_response(response);
834 if let Some(interception_id) = request.interception_id.as_ref() {
835 self.attempted_authentications
836 .remove(interception_id.as_ref());
837 }
838 }
839}
840
841#[derive(Debug)]
842pub enum NetworkEvent {
843 SendCdpRequest((MethodId, serde_json::Value)),
844 Request(RequestId),
845 Response(RequestId),
846 RequestFailed(HttpRequest),
847 RequestFinished(HttpRequest),
848}