script/
network_listener.rs1use std::sync::{Arc, Mutex};
6
7use content_security_policy::Violation;
8use js::context::JSContext;
9use net_traits::request::RequestId;
10use net_traits::{
11 BoxedFetchCallback, FetchMetadata, FetchResponseMsg, NetworkError, ResourceFetchTiming,
12 ResourceTimingType,
13};
14use servo_url::ServoUrl;
15
16use crate::dom::bindings::inheritance::Castable;
17use crate::dom::bindings::root::DomRoot;
18use crate::dom::globalscope::GlobalScope;
19use crate::dom::performance::performanceentry::PerformanceEntry;
20use crate::dom::performance::performanceresourcetiming::{
21 InitiatorType, PerformanceResourceTiming,
22};
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 cx: &mut JSContext,
32 listener: &T,
33 result: &Result<(), NetworkError>,
34 resource_timing: &ResourceFetchTiming,
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 cx,
74 &listener.resource_timing_global(),
75 url,
76 initiator_type,
77 resource_timing,
78 );
79}
80
81pub(crate) fn submit_timing_data(
82 cx: &mut JSContext,
83 global: &GlobalScope,
84 url: ServoUrl,
85 initiator_type: InitiatorType,
86 resource_timing: &ResourceFetchTiming,
87) {
88 let performance_entry =
89 PerformanceResourceTiming::new(cx, global, url, initiator_type, None, resource_timing);
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_response(
105 &mut self,
106 cx: &mut JSContext,
107 request_id: RequestId,
108 metadata: Result<FetchMetadata, NetworkError>,
109 );
110 fn process_response_chunk(&mut self, cx: &mut JSContext, request_id: RequestId, chunk: Vec<u8>);
111 fn process_response_eof(
112 self,
113 cx: &mut JSContext,
114 request_id: RequestId,
115 response: Result<(), NetworkError>,
116 timing: ResourceFetchTiming,
117 );
118 fn process_csp_violations(&mut self, request_id: RequestId, violations: Vec<Violation>);
119}
120
121pub(crate) struct NetworkListener<Listener: FetchResponseListener> {
124 pub(crate) context: Arc<Mutex<Option<Listener>>>,
125 pub(crate) task_source: SendableTaskSource,
126}
127
128impl<Listener: FetchResponseListener> NetworkListener<Listener> {
129 pub(crate) fn new(context: Listener, task_source: SendableTaskSource) -> Self {
130 Self {
131 context: Arc::new(Mutex::new(Some(context))),
132 task_source,
133 }
134 }
135
136 pub(crate) fn notify(&mut self, message: FetchResponseMsg) {
137 let context = self.context.clone();
138 self.task_source
139 .queue(task!(network_listener_response: move |cx| {
140 let mut context = context.lock().unwrap();
141 let Some(fetch_listener) = &mut *context else {
142 return;
143 };
144
145 if !fetch_listener.should_invoke() {
146 return;
147 }
148
149 match message {
150 FetchResponseMsg::ProcessRequestBody(request_id) => {
151 fetch_listener.process_request_body(request_id)
152 },
153 FetchResponseMsg::ProcessResponse(request_id, meta) => {
154 fetch_listener.process_response(cx, request_id, meta)
155 },
156 FetchResponseMsg::ProcessResponseChunk(request_id, data) => {
157 fetch_listener.process_response_chunk(cx, request_id, data.0)
158 },
159 FetchResponseMsg::ProcessResponseEOF(request_id, result, timing) => {
160 if let Some(fetch_listener) = context.take() {
161 fetch_listener.process_response_eof(cx, request_id, result, timing);
162 };
163 },
164 FetchResponseMsg::ProcessCspViolations(request_id, violations) => {
165 fetch_listener.process_csp_violations(request_id, violations)
166 },
167 }
168 }));
169 }
170
171 pub(crate) fn into_callback(mut self) -> BoxedFetchCallback {
172 Box::new(move |response_msg| self.notify(response_msg))
173 }
174}