1use std::sync::{Arc, Mutex};
6
7use bytes::Bytes;
8use content_security_policy::Violation;
9use js::context::JSContext;
10use net_traits::request::RequestId;
11use net_traits::{
12 BoxedFetchCallback, FetchMetadata, FetchResponseMsg, NetworkError, ResourceFetchTiming,
13 ResourceTimingType,
14};
15use servo_url::ServoUrl;
16
17use crate::dom::bindings::inheritance::Castable;
18use crate::dom::globalscope::GlobalScope;
19use crate::dom::performance::performanceentry::PerformanceEntry;
20use crate::dom::performance::performanceresourcetiming::{
21 InitiatorType, PerformanceResourceTiming,
22};
23use crate::tasks::task_source::SendableTaskSource;
24
25pub(crate) trait ResourceTimingListener {
26 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl);
27}
28
29pub(crate) fn submit_timing<T: ResourceTimingListener>(
30 cx: &mut JSContext,
31 global: &GlobalScope,
32 listener: &T,
33 result: &Result<(), NetworkError>,
34 resource_timing: &ResourceFetchTiming,
35) {
36 if let Err(error) = &result &&
42 error.is_permanent_failure()
43 {
44 return;
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(cx, global, url, initiator_type, resource_timing);
73}
74
75pub(crate) fn submit_timing_data(
76 cx: &mut JSContext,
77 global: &GlobalScope,
78 url: ServoUrl,
79 initiator_type: InitiatorType,
80 resource_timing: &ResourceFetchTiming,
81) {
82 let performance_entry =
83 PerformanceResourceTiming::new(cx, global, url, initiator_type, resource_timing);
84 global
85 .performance(cx)
86 .queue_entry(performance_entry.upcast::<PerformanceEntry>());
87}
88
89pub(crate) trait FetchResponseListener: Send + 'static {
90 fn should_invoke(&self) -> bool {
94 true
95 }
96
97 fn process_request_body(&mut self, global: &GlobalScope, request_id: RequestId);
98 fn process_response(
99 &mut self,
100 cx: &mut JSContext,
101 global: &GlobalScope,
102 request_id: RequestId,
103 metadata: Result<FetchMetadata, NetworkError>,
104 );
105 fn process_response_chunk(
106 &mut self,
107 cx: &mut JSContext,
108 global: &GlobalScope,
109 request_id: RequestId,
110 chunk: Bytes,
111 );
112 fn process_response_eof(
113 self,
114 cx: &mut JSContext,
115 global: &GlobalScope,
116 request_id: RequestId,
117 response: Result<(), NetworkError>,
118 timing: ResourceFetchTiming,
119 );
120 fn process_csp_violations(
121 &mut self,
122 cx: &mut js::context::JSContext,
123 global: &GlobalScope,
124 request_id: RequestId,
125 violations: Vec<Violation>,
126 );
127
128 fn process_content_length(&mut self, _request_id: RequestId, _size: usize) {}
129}
130
131pub(crate) struct NetworkListener<Listener: FetchResponseListener> {
134 pub(crate) context: Arc<Mutex<Option<Listener>>>,
135 pub(crate) task_source: SendableTaskSource,
136}
137
138impl<Listener: FetchResponseListener> NetworkListener<Listener> {
139 pub(crate) fn new(context: Listener, task_source: SendableTaskSource) -> Self {
140 Self {
141 context: Arc::new(Mutex::new(Some(context))),
142 task_source,
143 }
144 }
145
146 pub(crate) fn notify(&mut self, message: FetchResponseMsg) {
147 let context = self.context.clone();
148
149 self.task_source
150 .queue(task!(network_listener_response: move |cx| {
151 let mut context = context.lock().unwrap();
152 let Some(fetch_listener) = &mut *context else {
153 return;
154 };
155
156 let Some(global) = GlobalScope::current() else {
159 return;
160 };
161
162 let is_eof = matches!(message, FetchResponseMsg::ProcessResponseEOF(..));
163 let request_id = message.request_id();
164
165 if fetch_listener.should_invoke() {
166 match message {
167 FetchResponseMsg::ProcessRequestBody(request_id) => {
168 fetch_listener.process_request_body(&global, request_id)
169 },
170 FetchResponseMsg::ProcessResponse(request_id, meta) => {
171 fetch_listener.process_response(cx, &global, request_id, meta)
172 },
173 FetchResponseMsg::ProcessResponseChunk(request_id, data) => {
174 fetch_listener.process_response_chunk(cx, &global, request_id, data)
175 },
176 FetchResponseMsg::ProcessResponseEOF(request_id, result, timing) => {
177 if let Some(fetch_listener) = context.take() {
178 fetch_listener.process_response_eof(cx, &global, request_id, result, timing);
179 };
180 },
181 FetchResponseMsg::ProcessCspViolations(request_id, violations) => {
182 fetch_listener.process_csp_violations(cx, &global, request_id, violations)
183 },
184 FetchResponseMsg::ProcessContentLength(request_id, size) => { fetch_listener.process_content_length(request_id, size) },
185 }
186 }
187
188 if is_eof {
189 global
190 .fetch_group_mut()
191 .mark_fetch_request_as_done(&request_id);
192 }
193 }));
194 }
195
196 pub(crate) fn into_callback(mut self) -> BoxedFetchCallback {
197 Box::new(move |response_msg| self.notify(response_msg))
198 }
199}