script/
network_listener.rs1use std::sync::{Arc, Mutex};
6
7use content_security_policy::Violation;
8use net_traits::request::RequestId;
9use net_traits::{
10 BoxedFetchCallback, FetchMetadata, FetchResponseMsg, NetworkError, ResourceFetchTiming,
11 ResourceTimingType,
12};
13use servo_url::ServoUrl;
14
15use crate::dom::bindings::inheritance::Castable;
16use crate::dom::bindings::root::DomRoot;
17use crate::dom::globalscope::GlobalScope;
18use crate::dom::performance::performanceentry::PerformanceEntry;
19use crate::dom::performance::performanceresourcetiming::{
20 InitiatorType, PerformanceResourceTiming,
21};
22use crate::script_runtime::CanGc;
23use crate::task_source::SendableTaskSource;
24
25pub(crate) trait ResourceTimingListener {
26 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl);
27 fn resource_timing_global(&self) -> DomRoot<GlobalScope>;
28}
29
30pub(crate) fn submit_timing<T: ResourceTimingListener>(
31 listener: &T,
32 result: &Result<(), NetworkError>,
33 resource_timing: &ResourceFetchTiming,
34 can_gc: CanGc,
35) {
36 if let Err(error) = &result {
42 if error.is_permanent_failure() {
43 return;
44 }
45 }
46
47 if resource_timing.preloaded {
50 return;
51 }
52 if resource_timing.timing_type != ResourceTimingType::Resource &&
57 resource_timing.timing_type != ResourceTimingType::Error
58 {
59 warn!(
60 "Submitting non-resource ({:?}) timing as resource",
61 resource_timing.timing_type
62 );
63 return;
64 }
65
66 let (initiator_type, url) = listener.resource_timing_information();
67 if initiator_type == InitiatorType::Other {
68 warn!("Ignoring InitiatorType::Other resource {:?}", url);
69 return;
70 }
71
72 submit_timing_data(
73 &listener.resource_timing_global(),
74 url,
75 initiator_type,
76 resource_timing,
77 can_gc,
78 );
79}
80
81pub(crate) fn submit_timing_data(
82 global: &GlobalScope,
83 url: ServoUrl,
84 initiator_type: InitiatorType,
85 resource_timing: &ResourceFetchTiming,
86 can_gc: CanGc,
87) {
88 let performance_entry =
89 PerformanceResourceTiming::new(global, url, initiator_type, None, resource_timing, can_gc);
90 global
91 .performance()
92 .queue_entry(performance_entry.upcast::<PerformanceEntry>());
93}
94
95pub(crate) trait FetchResponseListener: Send + 'static {
96 fn should_invoke(&self) -> bool {
100 true
101 }
102
103 fn process_request_body(&mut self, request_id: RequestId);
104 fn process_request_eof(&mut self, request_id: RequestId);
105 fn process_response(
106 &mut self,
107 request_id: RequestId,
108 metadata: Result<FetchMetadata, NetworkError>,
109 );
110 fn process_response_chunk(&mut self, request_id: RequestId, chunk: Vec<u8>);
111 fn process_response_eof(
112 self,
113 request_id: RequestId,
114 response: Result<(), NetworkError>,
115 timing: ResourceFetchTiming,
116 );
117 fn process_csp_violations(&mut self, request_id: RequestId, violations: Vec<Violation>);
118}
119
120pub(crate) struct NetworkListener<Listener: FetchResponseListener> {
123 pub(crate) context: Arc<Mutex<Option<Listener>>>,
124 pub(crate) task_source: SendableTaskSource,
125}
126
127impl<Listener: FetchResponseListener> NetworkListener<Listener> {
128 pub(crate) fn new(context: Listener, task_source: SendableTaskSource) -> Self {
129 Self {
130 context: Arc::new(Mutex::new(Some(context))),
131 task_source,
132 }
133 }
134
135 pub(crate) fn notify(&mut self, message: FetchResponseMsg) {
136 let context = self.context.clone();
137 self.task_source
138 .queue(task!(network_listener_response: move || {
139 let mut context = context.lock().unwrap();
140 let Some(fetch_listener) = &mut *context else {
141 return;
142 };
143
144 if !fetch_listener.should_invoke() {
145 return;
146 }
147
148 match message {
149 FetchResponseMsg::ProcessRequestBody(request_id) => {
150 fetch_listener.process_request_body(request_id)
151 },
152 FetchResponseMsg::ProcessRequestEOF(request_id) => {
153 fetch_listener.process_request_eof(request_id)
154 },
155 FetchResponseMsg::ProcessResponse(request_id, meta) => {
156 fetch_listener.process_response(request_id, meta)
157 },
158 FetchResponseMsg::ProcessResponseChunk(request_id, data) => {
159 fetch_listener.process_response_chunk(request_id, data.0)
160 },
161 FetchResponseMsg::ProcessResponseEOF(request_id, result, timing) => {
162 if let Some(fetch_listener) = context.take() {
163 fetch_listener.process_response_eof(request_id, result, timing);
164 };
165 },
166 FetchResponseMsg::ProcessCspViolations(request_id, violations) => {
167 fetch_listener.process_csp_violations(request_id, violations)
168 },
169 }
170 }));
171 }
172
173 pub(crate) fn into_callback(mut self) -> BoxedFetchCallback {
174 Box::new(move |response_msg| self.notify(response_msg))
175 }
176}