1use std::cell::Cell;
10
11use content_security_policy::sandboxing_directive::SandboxingFlagSet;
12use crossbeam_channel::Sender;
13use embedder_traits::user_contents::UserContentManagerId;
14use embedder_traits::{Theme, ViewportDetails, WebDriverLoadStatus};
15use http::header;
16use js::context::JSContext;
17use net_traits::blob_url_store::UrlWithBlobClaim;
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, ReferrerPolicy, fetch_async, set_default_accept_language,
27};
28use script_bindings::inheritance::Castable;
29use script_traits::{DocumentActivity, NewPipelineInfo};
30use servo_base::cross_process_instant::CrossProcessInstant;
31use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
32use servo_constellation_traits::{
33 LoadData, LoadOrigin, NavigationHistoryBehavior, ScriptToConstellationMessage,
34 TargetSnapshotParams,
35};
36use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
37use url::Position;
38
39use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
40use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
41use crate::dom::bindings::refcounted::Trusted;
42use crate::dom::element::Element;
43use crate::dom::html::htmliframeelement::HTMLIFrameElement;
44use crate::dom::node::node::NodeTraits;
45use crate::dom::window::Window;
46use crate::dom::windowproxy::WindowProxy;
47use crate::fetch::FetchCanceller;
48use crate::messaging::MainThreadScriptMsg;
49use crate::script_thread::ScriptThread;
50
51#[derive(Clone)]
52pub struct NavigationListener {
53 request_builder: RequestBuilder,
54 main_thread_sender: Sender<MainThreadScriptMsg>,
55 send_results_to_main_thread: Cell<bool>,
58}
59
60impl NavigationListener {
61 pub(crate) fn into_callback(self) -> BoxedFetchCallback {
62 Box::new(move |response_msg| self.notify_fetch(response_msg))
63 }
64
65 pub fn new(
66 request_builder: RequestBuilder,
67 main_thread_sender: Sender<MainThreadScriptMsg>,
68 ) -> NavigationListener {
69 NavigationListener {
70 request_builder,
71 main_thread_sender,
72 send_results_to_main_thread: Cell::new(true),
73 }
74 }
75
76 pub fn initiate_fetch(
77 self,
78 core_resource_thread: &CoreResourceThread,
79 response_init: Option<ResponseInit>,
80 ) {
81 fetch_async(
82 core_resource_thread,
83 self.request_builder.clone(),
84 response_init,
85 self.into_callback(),
86 );
87 }
88
89 fn notify_fetch(&self, message: FetchResponseMsg) {
90 if !self.send_results_to_main_thread.get() {
93 return;
94 }
95
96 if Self::http_redirect_metadata(&message).is_some() {
98 self.send_results_to_main_thread.set(false);
99 }
100
101 let pipeline_id = self
102 .request_builder
103 .pipeline_id
104 .expect("Navigation should always have an associated Pipeline");
105 let result = self
106 .main_thread_sender
107 .send(MainThreadScriptMsg::NavigationResponse {
108 pipeline_id,
109 message: Box::new(message),
110 });
111
112 if let Err(error) = result {
113 warn!(
114 "Failed to send network message to pipeline {:?}: {error:?}",
115 pipeline_id
116 );
117 }
118 }
119
120 pub(crate) fn http_redirect_metadata(message: &FetchResponseMsg) -> Option<&Metadata> {
121 let FetchResponseMsg::ProcessResponse(_, Ok(metadata)) = message else {
122 return None;
123 };
124
125 let metadata = metadata.metadata();
127 if !matches!(
128 metadata.location_url,
129 Some(Ok(ref location_url)) if matches!(location_url.scheme(), "http" | "https")
130 ) {
131 return None;
132 }
133
134 Some(metadata)
135 }
136}
137
138#[derive(JSTraceable)]
143pub(crate) struct InProgressLoad {
144 #[no_trace]
146 pub(crate) pipeline_id: PipelineId,
147 #[no_trace]
149 pub(crate) browsing_context_id: BrowsingContextId,
150 #[no_trace]
152 pub(crate) webview_id: WebViewId,
153 #[no_trace]
155 pub(crate) parent_info: Option<PipelineId>,
156 #[no_trace]
158 pub(crate) opener: Option<BrowsingContextId>,
159 #[no_trace]
161 pub(crate) viewport_details: ViewportDetails,
162 #[no_trace]
164 pub(crate) activity: DocumentActivity,
165 pub(crate) throttled: bool,
167 #[no_trace]
169 pub(crate) navigation_start: CrossProcessInstant,
170 pub(crate) canceller: FetchCanceller,
172 #[no_trace]
174 pub(crate) load_data: LoadData,
175 #[no_trace]
178 pub(crate) url_list: Vec<ServoUrl>,
179 #[no_trace]
180 pub(crate) user_content_manager_id: Option<UserContentManagerId>,
182 #[no_trace]
184 pub(crate) theme: Theme,
185 #[no_trace]
187 pub(crate) target_snapshot_params: TargetSnapshotParams,
188}
189
190impl InProgressLoad {
191 pub(crate) fn new(new_pipeline_info: NewPipelineInfo) -> InProgressLoad {
193 let url = new_pipeline_info.load_data.url.clone();
194 InProgressLoad {
195 pipeline_id: new_pipeline_info.new_pipeline_id,
196 browsing_context_id: new_pipeline_info.browsing_context_id,
197 webview_id: new_pipeline_info.webview_id,
198 parent_info: new_pipeline_info.parent_info,
199 opener: new_pipeline_info.opener,
200 viewport_details: new_pipeline_info.viewport_details,
201 activity: DocumentActivity::FullyActive,
202 throttled: false,
203 navigation_start: CrossProcessInstant::now(),
204 canceller: Default::default(),
205 load_data: new_pipeline_info.load_data,
206 url_list: vec![url],
207 user_content_manager_id: new_pipeline_info.user_content_manager_id,
208 theme: new_pipeline_info.theme,
209 target_snapshot_params: new_pipeline_info.target_snapshot_params,
210 }
211 }
212
213 pub(crate) fn request_builder(&mut self) -> RequestBuilder {
214 let client_origin = match self.load_data.load_origin {
215 LoadOrigin::Script(ref initiator_origin) => initiator_origin.immutable().clone(),
216 _ => ImmutableOrigin::new_opaque(),
217 };
218
219 let id = self.pipeline_id;
220 let webview_id = self.webview_id;
221
222 let insecure_requests_policy = self
223 .load_data
224 .inherited_insecure_requests_policy
225 .unwrap_or(InsecureRequestsPolicy::DoNotUpgrade);
226
227 let request_client = RequestClient {
228 preloaded_resources: PreloadedResources::default(),
229 policy_container: RequestPolicyContainer::PolicyContainer(
230 self.load_data.policy_container.clone().unwrap_or_default(),
231 ),
232 origin: Origin::Origin(client_origin),
233 is_nested_browsing_context: self.parent_info.is_some(),
234 insecure_requests_policy,
235 };
236
237 let mut request_builder = RequestBuilder::new(
238 Some(webview_id),
239 UrlWithBlobClaim::from_url_without_having_claimed_blob(self.load_data.url.clone()),
240 self.load_data.referrer.clone(),
241 )
242 .method(self.load_data.method.clone())
243 .destination(self.load_data.destination)
244 .mode(RequestMode::Navigate)
245 .credentials_mode(CredentialsMode::Include)
246 .use_url_credentials(true)
247 .pipeline_id(Some(id))
248 .referrer_policy(self.load_data.referrer_policy)
249 .policy_container(self.load_data.policy_container.clone().unwrap_or_default())
250 .insecure_requests_policy(insecure_requests_policy)
251 .has_trustworthy_ancestor_origin(self.load_data.has_trustworthy_ancestor_origin)
252 .headers(self.load_data.headers.clone())
253 .body(self.load_data.data.clone())
254 .redirect_mode(RedirectMode::Manual)
255 .crash(self.load_data.crash.clone())
256 .client(request_client)
257 .url_list(self.url_list.clone());
258
259 if !request_builder.headers.contains_key(header::ACCEPT) {
260 request_builder
261 .headers
262 .insert(header::ACCEPT, DOCUMENT_ACCEPT_HEADER_VALUE);
263 }
264 set_default_accept_language(&mut request_builder.headers);
265
266 request_builder
267 }
268}
269
270pub(crate) fn determine_the_origin(
272 url: Option<&ServoUrl>,
273 sandbox_flags: SandboxingFlagSet,
274 source_origin: Option<MutableOrigin>,
275) -> MutableOrigin {
276 let is_sandboxed =
278 sandbox_flags.contains(SandboxingFlagSet::SANDBOXED_ORIGIN_BROWSING_CONTEXT_FLAG);
279 if is_sandboxed {
280 return MutableOrigin::new(ImmutableOrigin::new_opaque());
281 }
282
283 let Some(url) = url else {
285 return MutableOrigin::new(ImmutableOrigin::new_opaque());
286 };
287
288 if url.as_str() == "about:srcdoc" {
290 let source_origin =
292 source_origin.expect("Can't have a null source origin for about:srcdoc");
293 return source_origin;
295 }
296
297 if url.as_str() == "about:blank" &&
299 let Some(source_origin) = source_origin
300 {
301 return source_origin;
302 }
303
304 MutableOrigin::new(url.origin())
306}
307
308fn navigate_to_fragment(
310 cx: &mut JSContext,
311 window: &Window,
312 url: &ServoUrl,
313 history_handling: NavigationHistoryBehavior,
314) {
315 let doc = window.Document();
316 window.send_to_constellation(ScriptToConstellationMessage::NavigatedToFragment(
339 url.clone(),
340 history_handling,
341 ));
342 let old_url = doc.url();
344 doc.set_url(url.clone());
345 doc.update_document_for_history_step_application(&old_url, url);
348 let Some(fragment) = url.fragment() else {
350 unreachable!("Must always have a fragment");
351 };
352 doc.scroll_to_the_fragment(cx, fragment);
353 }
358
359pub(crate) fn navigate(
361 cx: &mut JSContext,
362 window: &Window,
363 history_handling: NavigationHistoryBehavior,
364 force_reload: bool,
365 load_data: LoadData,
366) {
367 let doc = window.Document();
368
369 let initiator_origin_snapshot = &load_data.load_origin;
371
372 let pipeline_id = window.pipeline_id();
377 let window_proxy = window.window_proxy();
378 if let Some(active) = window_proxy.currently_active() &&
379 pipeline_id == active &&
380 doc.is_prompting_or_unloading()
381 {
382 return;
383 }
384
385 let history_handling = if history_handling == NavigationHistoryBehavior::Auto {
387 if let LoadOrigin::Script(initiator_origin) = initiator_origin_snapshot {
394 if load_data.url == doc.url() && initiator_origin.same_origin(&*doc.origin()) {
395 NavigationHistoryBehavior::Replace
396 } else {
397 NavigationHistoryBehavior::Push
399 }
400 } else {
401 NavigationHistoryBehavior::Push
403 }
404 } else {
405 history_handling
406 };
407
408 let history_handling = if load_data.url.scheme() == "javascript" || doc.is_initial_about_blank()
413 {
414 NavigationHistoryBehavior::Replace
415 } else {
416 history_handling
417 };
418
419 if !force_reload
423 && load_data.url.as_url()[..Position::AfterQuery] ==
425 doc.url().as_url()[..Position::AfterQuery]
426 && load_data.url.fragment().is_some()
428 {
429 let webdriver_sender = window.webdriver_load_status_sender();
432 if let Some(ref sender) = webdriver_sender {
433 let _ = sender.send(WebDriverLoadStatus::NavigationStart);
434 }
435 navigate_to_fragment(cx, window, &load_data.url, history_handling);
436 if let Some(sender) = webdriver_sender {
438 let _ = sender.send(WebDriverLoadStatus::NavigationStop);
439 }
440 return;
441 }
442
443 let window_proxy = window.window_proxy();
445 if window_proxy.parent().is_some() {
446 window_proxy.start_delaying_load_events_mode();
447 }
448
449 let target_snapshot_params = snapshot_target_snapshot_params(&window_proxy);
452
453 if let Some(sender) = window.webdriver_load_status_sender() {
458 let _ = sender.send(WebDriverLoadStatus::NavigationStart);
459 }
460
461 if load_data.url.scheme() == "javascript" {
468 let Some(initiator_pipeline_id) = load_data.creator_pipeline_id else {
474 unreachable!("javascript: URL navigations must have a creator pipeline");
475 };
476 let Some(initiator_window) = ScriptThread::find_window(initiator_pipeline_id) else {
477 warn!("Can't find global for navigation initiator");
478 return;
479 };
480
481 let target_window = Trusted::new(window);
482 let mut load_data = load_data;
483 let initiator_window = Trusted::new(&*initiator_window);
484 let task = task!(navigate_javascript: move |cx| {
485 let target_window = target_window.root();
487 let initiator_window = initiator_window.root();
488 if ScriptThread::navigate_to_javascript_url(cx, initiator_window.upcast(), target_window.upcast(), &mut load_data, None, None) {
489 target_window
490 .as_global_scope()
491 .script_to_constellation_chan()
492 .send(ScriptToConstellationMessage::LoadUrl(load_data, history_handling, target_snapshot_params))
493 .unwrap();
494 }
495 });
496 window
497 .as_global_scope()
498 .task_manager()
499 .navigation_and_traversal_task_source()
500 .queue(task);
501 return;
503 }
504
505 let unload_prompt_canceled = doc.check_if_unloading_is_cancelled(cx, false);
512 if !unload_prompt_canceled {
517 return;
523 }
524
525 window.send_to_constellation(ScriptToConstellationMessage::LoadUrl(
530 load_data,
531 history_handling,
532 target_snapshot_params,
533 ));
534}
535
536pub(crate) fn determine_creation_sandboxing_flags(
538 browsing_context: Option<&WindowProxy>,
539 element: Option<&Element>,
540) -> SandboxingFlagSet {
541 match element {
545 None => browsing_context
548 .and_then(|browsing_context| browsing_context.document())
549 .map(|document| document.active_sandboxing_flag_set())
550 .unwrap_or(SandboxingFlagSet::empty()),
551 Some(element) => {
552 element
557 .downcast::<HTMLIFrameElement>()
558 .map(|iframe| iframe.sandboxing_flag_set())
559 .unwrap_or(SandboxingFlagSet::empty())
560 .union(element.owner_document().active_sandboxing_flag_set())
561 },
562 }
563}
564
565pub(crate) fn determine_iframe_element_referrer_policy(
567 element: Option<&Element>,
568) -> ReferrerPolicy {
569 element
572 .and_then(|element| element.downcast::<HTMLIFrameElement>())
573 .map(|iframe| {
574 let token = iframe.ReferrerPolicy();
575 ReferrerPolicy::from(&*token.str())
576 })
577 .unwrap_or(ReferrerPolicy::EmptyString)
579}
580
581pub(crate) fn snapshot_target_snapshot_params(navigable: &WindowProxy) -> TargetSnapshotParams {
583 let container = navigable.frame_element();
585 let sandboxing_flags = determine_creation_sandboxing_flags(Some(navigable), container);
588 let iframe_element_referrer_policy = determine_iframe_element_referrer_policy(container);
591 TargetSnapshotParams {
592 sandboxing_flags,
593 iframe_element_referrer_policy,
594 }
595}