1use std::cell::Cell;
10
11use base::cross_process_instant::CrossProcessInstant;
12use base::id::{BrowsingContextId, PipelineId, WebViewId};
13use constellation_traits::LoadData;
14use crossbeam_channel::Sender;
15use embedder_traits::{Theme, ViewportDetails};
16use http::header;
17use net_traits::request::{
18 CredentialsMode, InsecureRequestsPolicy, RedirectMode, RequestBuilder, RequestMode,
19};
20use net_traits::response::ResponseInit;
21use net_traits::{
22 BoxedFetchCallback, CoreResourceThread, DOCUMENT_ACCEPT_HEADER_VALUE, FetchResponseMsg,
23 Metadata, fetch_async, set_default_accept_language,
24};
25use script_traits::DocumentActivity;
26use servo_url::{MutableOrigin, ServoUrl};
27
28use crate::fetch::FetchCanceller;
29use crate::messaging::MainThreadScriptMsg;
30
31#[derive(Clone)]
32pub struct NavigationListener {
33 request_builder: RequestBuilder,
34 main_thread_sender: Sender<MainThreadScriptMsg>,
35 send_results_to_main_thread: Cell<bool>,
38}
39
40impl NavigationListener {
41 pub(crate) fn into_callback(self) -> BoxedFetchCallback {
42 Box::new(move |response_msg| self.notify_fetch(response_msg))
43 }
44
45 pub fn new(
46 request_builder: RequestBuilder,
47 main_thread_sender: Sender<MainThreadScriptMsg>,
48 ) -> NavigationListener {
49 NavigationListener {
50 request_builder,
51 main_thread_sender,
52 send_results_to_main_thread: Cell::new(true),
53 }
54 }
55
56 pub fn initiate_fetch(
57 self,
58 core_resource_thread: &CoreResourceThread,
59 response_init: Option<ResponseInit>,
60 ) {
61 fetch_async(
62 core_resource_thread,
63 self.request_builder.clone(),
64 response_init,
65 self.into_callback(),
66 );
67 }
68
69 fn notify_fetch(&self, message: FetchResponseMsg) {
70 if !self.send_results_to_main_thread.get() {
73 return;
74 }
75
76 if Self::http_redirect_metadata(&message).is_some() {
78 self.send_results_to_main_thread.set(false);
79 }
80
81 let pipeline_id = self
82 .request_builder
83 .pipeline_id
84 .expect("Navigation should always have an associated Pipeline");
85 let result = self
86 .main_thread_sender
87 .send(MainThreadScriptMsg::NavigationResponse {
88 pipeline_id,
89 message: Box::new(message),
90 });
91
92 if let Err(error) = result {
93 warn!(
94 "Failed to send network message to pipeline {:?}: {error:?}",
95 pipeline_id
96 );
97 }
98 }
99
100 pub(crate) fn http_redirect_metadata(message: &FetchResponseMsg) -> Option<&Metadata> {
101 let FetchResponseMsg::ProcessResponse(_, Ok(metadata)) = message else {
102 return None;
103 };
104
105 let metadata = metadata.metadata();
107 if !matches!(
108 metadata.location_url,
109 Some(Ok(ref location_url)) if matches!(location_url.scheme(), "http" | "https")
110 ) {
111 return None;
112 }
113
114 Some(metadata)
115 }
116}
117
118#[derive(JSTraceable)]
123pub(crate) struct InProgressLoad {
124 #[no_trace]
126 pub(crate) pipeline_id: PipelineId,
127 #[no_trace]
129 pub(crate) browsing_context_id: BrowsingContextId,
130 #[no_trace]
132 pub(crate) webview_id: WebViewId,
133 #[no_trace]
135 pub(crate) parent_info: Option<PipelineId>,
136 #[no_trace]
138 pub(crate) opener: Option<BrowsingContextId>,
139 #[no_trace]
141 pub(crate) viewport_details: ViewportDetails,
142 #[no_trace]
144 pub(crate) activity: DocumentActivity,
145 pub(crate) throttled: bool,
147 #[no_trace]
149 pub(crate) origin: MutableOrigin,
150 #[no_trace]
152 pub(crate) navigation_start: CrossProcessInstant,
153 pub(crate) canceller: FetchCanceller,
155 #[no_trace]
157 pub(crate) load_data: LoadData,
158 #[no_trace]
161 pub(crate) url_list: Vec<ServoUrl>,
162 #[no_trace]
164 pub(crate) theme: Theme,
165}
166
167impl InProgressLoad {
168 #[allow(clippy::too_many_arguments)]
170 pub(crate) fn new(
171 id: PipelineId,
172 browsing_context_id: BrowsingContextId,
173 webview_id: WebViewId,
174 parent_info: Option<PipelineId>,
175 opener: Option<BrowsingContextId>,
176 viewport_details: ViewportDetails,
177 theme: Theme,
178 origin: MutableOrigin,
179 load_data: LoadData,
180 ) -> InProgressLoad {
181 let url = load_data.url.clone();
182 InProgressLoad {
183 pipeline_id: id,
184 browsing_context_id,
185 webview_id,
186 parent_info,
187 opener,
188 viewport_details,
189 activity: DocumentActivity::FullyActive,
190 throttled: false,
191 origin,
192 navigation_start: CrossProcessInstant::now(),
193 canceller: Default::default(),
194 load_data,
195 url_list: vec![url],
196 theme,
197 }
198 }
199
200 pub(crate) fn request_builder(&mut self) -> RequestBuilder {
201 let id = self.pipeline_id;
202 let webview_id = self.webview_id;
203 let mut request_builder = RequestBuilder::new(
204 Some(webview_id),
205 self.load_data.url.clone(),
206 self.load_data.referrer.clone(),
207 )
208 .method(self.load_data.method.clone())
209 .destination(self.load_data.destination)
210 .mode(RequestMode::Navigate)
211 .credentials_mode(CredentialsMode::Include)
212 .use_url_credentials(true)
213 .pipeline_id(Some(id))
214 .referrer_policy(self.load_data.referrer_policy)
215 .policy_container(self.load_data.policy_container.clone().unwrap_or_default())
216 .insecure_requests_policy(
217 self.load_data
218 .inherited_insecure_requests_policy
219 .unwrap_or(InsecureRequestsPolicy::DoNotUpgrade),
220 )
221 .has_trustworthy_ancestor_origin(self.load_data.has_trustworthy_ancestor_origin)
222 .headers(self.load_data.headers.clone())
223 .body(self.load_data.data.clone())
224 .redirect_mode(RedirectMode::Manual)
225 .origin(self.origin.immutable().clone())
226 .crash(self.load_data.crash.clone());
227 request_builder.url_list = self.url_list.clone();
228
229 if !request_builder.headers.contains_key(header::ACCEPT) {
230 request_builder
231 .headers
232 .insert(header::ACCEPT, DOCUMENT_ACCEPT_HEADER_VALUE);
233 }
234 set_default_accept_language(&mut request_builder.headers);
235
236 request_builder
237 }
238}