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, NewPipelineInfo};
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 pub(crate) fn new(new_pipeline_info: NewPipelineInfo, origin: MutableOrigin) -> InProgressLoad {
170 let url = new_pipeline_info.load_data.url.clone();
171 InProgressLoad {
172 pipeline_id: new_pipeline_info.new_pipeline_id,
173 browsing_context_id: new_pipeline_info.browsing_context_id,
174 webview_id: new_pipeline_info.webview_id,
175 parent_info: new_pipeline_info.parent_info,
176 opener: new_pipeline_info.opener,
177 viewport_details: new_pipeline_info.viewport_details,
178 activity: DocumentActivity::FullyActive,
179 throttled: false,
180 origin,
181 navigation_start: CrossProcessInstant::now(),
182 canceller: Default::default(),
183 load_data: new_pipeline_info.load_data,
184 url_list: vec![url],
185 theme: new_pipeline_info.theme,
186 }
187 }
188
189 pub(crate) fn request_builder(&mut self) -> RequestBuilder {
190 let id = self.pipeline_id;
191 let webview_id = self.webview_id;
192 let mut request_builder = RequestBuilder::new(
193 Some(webview_id),
194 self.load_data.url.clone(),
195 self.load_data.referrer.clone(),
196 )
197 .method(self.load_data.method.clone())
198 .destination(self.load_data.destination)
199 .mode(RequestMode::Navigate)
200 .credentials_mode(CredentialsMode::Include)
201 .use_url_credentials(true)
202 .pipeline_id(Some(id))
203 .referrer_policy(self.load_data.referrer_policy)
204 .policy_container(self.load_data.policy_container.clone().unwrap_or_default())
205 .insecure_requests_policy(
206 self.load_data
207 .inherited_insecure_requests_policy
208 .unwrap_or(InsecureRequestsPolicy::DoNotUpgrade),
209 )
210 .has_trustworthy_ancestor_origin(self.load_data.has_trustworthy_ancestor_origin)
211 .headers(self.load_data.headers.clone())
212 .body(self.load_data.data.clone())
213 .redirect_mode(RedirectMode::Manual)
214 .origin(self.origin.immutable().clone())
215 .crash(self.load_data.crash.clone());
216 request_builder.url_list = self.url_list.clone();
217
218 if !request_builder.headers.contains_key(header::ACCEPT) {
219 request_builder
220 .headers
221 .insert(header::ACCEPT, DOCUMENT_ACCEPT_HEADER_VALUE);
222 }
223 set_default_accept_language(&mut request_builder.headers);
224
225 request_builder
226 }
227}