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