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::bindings::refcounted::Trusted;
19use crate::dom::bindings::root::DomRoot;
20use crate::dom::globalscope::GlobalScope;
21use crate::dom::performance::performanceentry::PerformanceEntry;
22use crate::dom::performance::performanceresourcetiming::{
23 InitiatorType, PerformanceResourceTiming,
24};
25use crate::tasks::task_source::SendableTaskSource;
26
27pub(crate) trait ResourceTimingListener {
28 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl);
29 fn resource_timing_global(&self) -> DomRoot<GlobalScope>;
30}
31
32pub(crate) fn submit_timing<T: ResourceTimingListener>(
33 cx: &mut JSContext,
34 listener: &T,
35 result: &Result<(), NetworkError>,
36 resource_timing: &ResourceFetchTiming,
37) {
38 if let Err(error) = &result &&
44 error.is_permanent_failure()
45 {
46 return;
47 }
48
49 if resource_timing.preloaded {
52 return;
53 }
54 if resource_timing.timing_type != ResourceTimingType::Resource &&
59 resource_timing.timing_type != ResourceTimingType::Error
60 {
61 warn!(
62 "Submitting non-resource ({:?}) timing as resource",
63 resource_timing.timing_type
64 );
65 return;
66 }
67
68 let (initiator_type, url) = listener.resource_timing_information();
69 if initiator_type == InitiatorType::Other {
70 warn!("Ignoring InitiatorType::Other resource {:?}", url);
71 return;
72 }
73
74 submit_timing_data(
75 cx,
76 &listener.resource_timing_global(),
77 url,
78 initiator_type,
79 resource_timing,
80 );
81}
82
83pub(crate) fn submit_timing_data(
84 cx: &mut JSContext,
85 global: &GlobalScope,
86 url: ServoUrl,
87 initiator_type: InitiatorType,
88 resource_timing: &ResourceFetchTiming,
89) {
90 let performance_entry =
91 PerformanceResourceTiming::new(cx, global, url, initiator_type, resource_timing);
92 global
93 .performance(cx)
94 .queue_entry(performance_entry.upcast::<PerformanceEntry>());
95}
96
97pub(crate) trait FetchResponseListener: Send + 'static {
98 fn should_invoke(&self) -> bool {
102 true
103 }
104
105 fn process_request_body(&mut self, request_id: RequestId);
106 fn process_response(
107 &mut self,
108 cx: &mut JSContext,
109 request_id: RequestId,
110 metadata: Result<FetchMetadata, NetworkError>,
111 );
112 fn process_response_chunk(&mut self, cx: &mut JSContext, request_id: RequestId, chunk: Bytes);
113 fn process_response_eof(
114 self,
115 cx: &mut JSContext,
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 request_id: RequestId,
124 violations: Vec<Violation>,
125 );
126
127 fn process_content_length(&mut self, _request_id: RequestId, _size: usize) {}
128}
129
130pub(crate) struct NetworkListener<Listener: FetchResponseListener> {
133 pub(crate) context: Arc<Mutex<Option<Listener>>>,
134 pub(crate) task_source: SendableTaskSource,
135 pub(crate) global_scope: Trusted<GlobalScope>,
137}
138
139impl<Listener: FetchResponseListener> NetworkListener<Listener> {
140 pub(crate) fn new(
141 context: Listener,
142 task_source: SendableTaskSource,
143 global_scope: &GlobalScope,
144 ) -> Self {
145 Self {
146 context: Arc::new(Mutex::new(Some(context))),
147 task_source,
148 global_scope: Trusted::new(global_scope),
149 }
150 }
151
152 pub(crate) fn notify(&mut self, message: FetchResponseMsg) {
153 let context = self.context.clone();
154 let global_scope = self.global_scope.clone();
155 self.task_source
156 .queue(task!(network_listener_response: move |cx| {
157 if let FetchResponseMsg::ProcessResponseEOF(request_id, ..) = &message {
158 global_scope
159 .root()
160 .fetch_group_mut()
161 .mark_fetch_request_as_done(request_id);
162 }
163
164 let mut context = context.lock().unwrap();
165 let Some(fetch_listener) = &mut *context else {
166 return;
167 };
168
169 if !fetch_listener.should_invoke() {
170 return;
171 }
172
173 match message {
174 FetchResponseMsg::ProcessRequestBody(request_id) => {
175 fetch_listener.process_request_body(request_id)
176 },
177 FetchResponseMsg::ProcessResponse(request_id, meta) => {
178 fetch_listener.process_response(cx, request_id, meta)
179 },
180 FetchResponseMsg::ProcessResponseChunk(request_id, data) => {
181 fetch_listener.process_response_chunk(cx, request_id, data)
182 },
183 FetchResponseMsg::ProcessResponseEOF(request_id, result, timing) => {
184 if let Some(fetch_listener) = context.take() {
185 fetch_listener.process_response_eof(cx, request_id, result, timing);
186 };
187 },
188 FetchResponseMsg::ProcessCspViolations(request_id, violations) => {
189 fetch_listener.process_csp_violations(cx, request_id, violations)
190 },
191 FetchResponseMsg::ProcessContentLength(request_id, size) => { fetch_listener.process_content_length(request_id, size) },
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}