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