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::request::{
19 CredentialsMode, InsecureRequestsPolicy, Origin, PreloadedResources, RedirectMode,
20 RequestBuilder, RequestClient, RequestMode,
21};
22use net_traits::response::ResponseInit;
23use net_traits::{
24 BoxedFetchCallback, CoreResourceThread, DOCUMENT_ACCEPT_HEADER_VALUE, FetchResponseMsg,
25 Metadata, ReferrerPolicy, fetch_async, set_default_accept_language,
26};
27use script_bindings::inheritance::Castable;
28use script_traits::{DocumentActivity, NewPipelineInfo};
29use servo_base::cross_process_instant::CrossProcessInstant;
30use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
31use servo_constellation_traits::{
32 LoadData, LoadOrigin, NavigationHistoryBehavior, ScriptToConstellationMessage,
33 TargetSnapshotParams,
34};
35use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
36use url::Position;
37
38use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
39use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
40use crate::dom::bindings::refcounted::Trusted;
41use crate::dom::element::Element;
42use crate::dom::html::htmliframeelement::HTMLIFrameElement;
43use crate::dom::node::node::NodeTraits;
44use crate::dom::window::Window;
45use crate::dom::windowproxy::WindowProxy;
46use crate::event_loop::script_thread::ScriptThread;
47use crate::fetch::fetch::FetchCanceller;
48use crate::messaging::MainThreadScriptMsg;
49
50#[derive(Clone)]
51pub struct NavigationListener {
52 request_builder: RequestBuilder,
53 main_thread_sender: Sender<MainThreadScriptMsg>,
54 send_results_to_main_thread: Cell<bool>,
57}
58
59impl NavigationListener {
60 pub(crate) fn into_callback(self) -> BoxedFetchCallback {
61 Box::new(move |response_msg| self.notify_fetch(response_msg))
62 }
63
64 pub fn new(
65 request_builder: RequestBuilder,
66 main_thread_sender: Sender<MainThreadScriptMsg>,
67 ) -> NavigationListener {
68 NavigationListener {
69 request_builder,
70 main_thread_sender,
71 send_results_to_main_thread: Cell::new(true),
72 }
73 }
74
75 pub fn initiate_fetch(
76 self,
77 core_resource_thread: &CoreResourceThread,
78 response_init: Option<ResponseInit>,
79 ) {
80 fetch_async(
81 core_resource_thread,
82 self.request_builder.clone(),
83 response_init,
84 self.into_callback(),
85 );
86 }
87
88 fn notify_fetch(&self, message: FetchResponseMsg) {
89 if !self.send_results_to_main_thread.get() {
92 return;
93 }
94
95 if Self::http_redirect_metadata(&message).is_some() {
97 self.send_results_to_main_thread.set(false);
98 }
99
100 let pipeline_id = self
101 .request_builder
102 .pipeline_id
103 .expect("Navigation should always have an associated Pipeline");
104 let result = self
105 .main_thread_sender
106 .send(MainThreadScriptMsg::NavigationResponse {
107 pipeline_id,
108 message: Box::new(message),
109 });
110
111 if let Err(error) = result {
112 warn!(
113 "Failed to send network message to pipeline {:?}: {error:?}",
114 pipeline_id
115 );
116 }
117 }
118
119 pub(crate) fn http_redirect_metadata(message: &FetchResponseMsg) -> Option<&Metadata> {
120 let FetchResponseMsg::ProcessResponse(_, Ok(metadata)) = message else {
121 return None;
122 };
123
124 let metadata = metadata.metadata();
126 if !matches!(
127 metadata.location_url,
128 Some(Ok(ref location_url)) if matches!(location_url.scheme(), "http" | "https")
129 ) {
130 return None;
131 }
132
133 Some(metadata)
134 }
135}
136
137#[derive(JSTraceable)]
142pub(crate) struct InProgressLoad {
143 #[no_trace]
145 pub(crate) pipeline_id: PipelineId,
146 #[no_trace]
148 pub(crate) browsing_context_id: BrowsingContextId,
149 #[no_trace]
151 pub(crate) webview_id: WebViewId,
152 #[no_trace]
154 pub(crate) parent_info: Option<PipelineId>,
155 #[no_trace]
157 pub(crate) opener: Option<BrowsingContextId>,
158 #[no_trace]
160 pub(crate) viewport_details: ViewportDetails,
161 #[no_trace]
163 pub(crate) activity: DocumentActivity,
164 pub(crate) throttled: bool,
166 #[no_trace]
168 pub(crate) navigation_start: CrossProcessInstant,
169 pub(crate) canceller: FetchCanceller,
171 #[no_trace]
173 pub(crate) load_data: LoadData,
174 #[no_trace]
177 pub(crate) url_list: Vec<ServoUrl>,
178 #[no_trace]
179 pub(crate) user_content_manager_id: Option<UserContentManagerId>,
181 #[no_trace]
183 pub(crate) embedder_theme: Theme,
184 #[no_trace]
186 pub(crate) target_snapshot_params: TargetSnapshotParams,
187 pub(crate) frame_name: Option<String>,
189}
190
191impl InProgressLoad {
192 pub(crate) fn new(new_pipeline_info: NewPipelineInfo) -> InProgressLoad {
194 let url = new_pipeline_info.load_data.url.clone();
195
196 InProgressLoad {
197 pipeline_id: new_pipeline_info.new_pipeline_id,
198 browsing_context_id: new_pipeline_info.browsing_context_id,
199 webview_id: new_pipeline_info.webview_id,
200 parent_info: new_pipeline_info.parent_info,
201 opener: new_pipeline_info.opener,
202 viewport_details: new_pipeline_info.viewport_details,
203 activity: DocumentActivity::FullyActive,
204 throttled: false,
205 navigation_start: CrossProcessInstant::now(),
206 canceller: Default::default(),
207 load_data: new_pipeline_info.load_data,
208 url_list: vec![url],
209 user_content_manager_id: new_pipeline_info.user_content_manager_id,
210 embedder_theme: new_pipeline_info.embedder_theme,
211 target_snapshot_params: new_pipeline_info.target_snapshot_params,
212 frame_name: new_pipeline_info.frame_name,
213 }
214 }
215
216 pub(crate) fn request_builder(&mut self) -> RequestBuilder {
217 let client_origin = match self.load_data.load_origin {
218 LoadOrigin::Script(ref initiator_origin) => initiator_origin.immutable().clone(),
219 _ => ImmutableOrigin::new_opaque(),
220 };
221
222 let id = self.pipeline_id;
223 let webview_id = self.webview_id;
224
225 let insecure_requests_policy = self
226 .load_data
227 .inherited_insecure_requests_policy
228 .unwrap_or(InsecureRequestsPolicy::DoNotUpgrade);
229
230 let request_client = RequestClient {
231 preloaded_resources: PreloadedResources::default(),
232 policy_container: self.load_data.policy_container.clone().unwrap_or_default(),
233 origin: Origin::Origin(client_origin),
234 is_nested_browsing_context: self.parent_info.is_some(),
235 insecure_requests_policy,
236 has_trustworthy_ancestor_origin: self.load_data.has_trustworthy_ancestor_origin,
237 };
238
239 let mut request_builder = RequestBuilder::new(
240 Some(webview_id),
241 UrlWithBlobClaim::from_url_without_having_claimed_blob(self.load_data.url.clone()),
242 self.load_data.referrer.clone(),
243 )
244 .method(self.load_data.method.clone())
245 .destination(self.load_data.destination)
246 .mode(RequestMode::Navigate)
247 .credentials_mode(CredentialsMode::Include)
248 .use_url_credentials(true)
249 .pipeline_id(Some(id))
250 .referrer_policy(self.load_data.referrer_policy)
251 .policy_container(self.load_data.policy_container.clone().unwrap_or_default())
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 request_builder.reload_navigation = self.load_data.reload_navigation;
260 request_builder.history_navigation = self.load_data.history_navigation;
261
262 if !request_builder.headers.contains_key(header::ACCEPT) {
263 request_builder
264 .headers
265 .insert(header::ACCEPT, DOCUMENT_ACCEPT_HEADER_VALUE);
266 }
267 set_default_accept_language(&mut request_builder.headers);
268
269 request_builder
270 }
271}
272
273pub(crate) fn determine_the_origin(
275 url: Option<&ServoUrl>,
276 sandbox_flags: SandboxingFlagSet,
277 source_origin: Option<MutableOrigin>,
278) -> MutableOrigin {
279 let is_sandboxed =
281 sandbox_flags.contains(SandboxingFlagSet::SANDBOXED_ORIGIN_BROWSING_CONTEXT_FLAG);
282 if is_sandboxed {
283 return MutableOrigin::new(ImmutableOrigin::new_opaque());
284 }
285
286 let Some(url) = url else {
288 return MutableOrigin::new(ImmutableOrigin::new_opaque());
289 };
290
291 if url.as_str() == "about:srcdoc" {
293 let source_origin =
295 source_origin.expect("Can't have a null source origin for about:srcdoc");
296 return source_origin;
298 }
299
300 if url.as_str() == "about:blank" &&
302 let Some(source_origin) = source_origin
303 {
304 return source_origin;
305 }
306
307 MutableOrigin::new(url.origin())
309}
310
311fn navigate_to_fragment(
313 cx: &mut JSContext,
314 window: &Window,
315 url: &ServoUrl,
316 history_handling: NavigationHistoryBehavior,
317) {
318 let doc = window.Document();
319 window.send_to_constellation(ScriptToConstellationMessage::NavigatedToFragment(
342 url.clone(),
343 history_handling,
344 ));
345 let old_url = doc.url();
347 doc.set_url(url.clone());
348 doc.update_document_for_history_step_application(&old_url, url);
351 let Some(fragment) = url.fragment() else {
353 unreachable!("Must always have a fragment");
354 };
355 doc.scroll_to_the_fragment(cx, fragment);
356 }
361
362pub(crate) fn navigate(
364 cx: &mut JSContext,
365 window: &Window,
366 history_handling: NavigationHistoryBehavior,
367 force_reload: bool,
368 mut load_data: LoadData,
369) {
370 let document = window.Document();
371
372 if force_reload {
374 load_data.reload_navigation = true;
376 }
377
378 let initiator_origin_snapshot = &load_data.load_origin;
380
381 let pipeline_id = window.pipeline_id();
386 let window_proxy = window.window_proxy();
387 if let Some(active) = window_proxy.currently_active() &&
388 pipeline_id == active &&
389 document.is_prompting_or_unloading()
390 {
391 return;
392 }
393
394 let history_handling = if history_handling == NavigationHistoryBehavior::Auto {
396 if let LoadOrigin::Script(initiator_origin) = initiator_origin_snapshot {
403 if load_data.url == document.url() && initiator_origin.same_origin(&*document.origin())
404 {
405 NavigationHistoryBehavior::Replace
406 } else {
407 NavigationHistoryBehavior::Push
409 }
410 } else {
411 NavigationHistoryBehavior::Push
413 }
414 } else {
415 history_handling
416 };
417
418 let history_handling =
423 if load_data.url.scheme() == "javascript" || document.is_initial_about_blank() {
424 NavigationHistoryBehavior::Replace
425 } else {
426 history_handling
427 };
428
429 if !force_reload
433 && load_data.url.as_url()[..Position::AfterQuery] ==
435 document.url().as_url()[..Position::AfterQuery]
436 && load_data.url.fragment().is_some()
438 {
439 let webdriver_sender = window.webdriver_load_status_sender();
442 if let Some(ref sender) = webdriver_sender {
443 let _ = sender.send(WebDriverLoadStatus::NavigationStart);
444 }
445 navigate_to_fragment(cx, window, &load_data.url, history_handling);
446 if let Some(sender) = webdriver_sender {
448 let _ = sender.send(WebDriverLoadStatus::NavigationStop);
449 }
450 return;
451 }
452
453 let window_proxy = window.window_proxy();
455 if window_proxy.parent().is_some() {
456 window_proxy.start_delaying_load_events_mode();
457 }
458
459 let target_snapshot_params = snapshot_target_snapshot_params(&window_proxy);
462
463 if let Some(sender) = window.webdriver_load_status_sender() {
468 let _ = sender.send(WebDriverLoadStatus::NavigationStart);
469 }
470
471 if load_data.url.scheme() == "javascript" {
478 let Some(initiator_pipeline_id) = load_data.creator_pipeline_id else {
484 unreachable!("javascript: URL navigations must have a creator pipeline");
485 };
486 let Some(initiator_window) = ScriptThread::find_window(initiator_pipeline_id) else {
487 warn!("Can't find global for navigation initiator");
488 return;
489 };
490
491 let target_window = Trusted::new(window);
492 let mut load_data = load_data;
493 let initiator_window = Trusted::new(&*initiator_window);
494 let task = task!(navigate_javascript: move |cx| {
495 let target_window = target_window.root();
497 let initiator_window = initiator_window.root();
498 if ScriptThread::navigate_to_javascript_url(cx, initiator_window.upcast(), target_window.upcast(), &mut load_data, None, None) {
499 target_window
500 .as_global_scope()
501 .script_to_constellation_chan()
502 .send(ScriptToConstellationMessage::LoadUrl(load_data, history_handling, target_snapshot_params))
503 .unwrap();
504 } else {
505 let window_proxy = target_window.window_proxy();
509 if window_proxy.parent().is_some() {
510 window_proxy.stop_delaying_load_events_mode();
511 }
512 }
513 });
514 window
515 .as_global_scope()
516 .task_manager()
517 .navigation_and_traversal_task_source()
518 .queue(task);
519 return;
521 }
522
523 let unload_prompt_canceled = document.check_if_unloading_is_cancelled(cx, false);
534 if !unload_prompt_canceled {
539 return;
545 }
546
547 let trusted_document = Trusted::new(&*document);
551 window
552 .task_manager()
553 .navigation_and_traversal_task_source()
554 .queue(task!(abort_a_document_and_its_descendants: move |cx| {
555 trusted_document.root().abort_a_document_and_its_descendants(cx);
556 }));
557
558 window.send_to_constellation(ScriptToConstellationMessage::LoadUrl(
563 load_data,
564 history_handling,
565 target_snapshot_params,
566 ));
567}
568
569pub(crate) fn determine_creation_sandboxing_flags(
571 browsing_context: Option<&WindowProxy>,
572 element: Option<&Element>,
573) -> SandboxingFlagSet {
574 match element {
578 None => browsing_context
581 .and_then(|browsing_context| browsing_context.document())
582 .map(|document| document.active_sandboxing_flag_set())
583 .unwrap_or(SandboxingFlagSet::empty()),
584 Some(element) => {
585 element
590 .downcast::<HTMLIFrameElement>()
591 .map(|iframe| iframe.sandboxing_flag_set())
592 .unwrap_or(SandboxingFlagSet::empty())
593 .union(element.owner_document().active_sandboxing_flag_set())
594 },
595 }
596}
597
598pub(crate) fn determine_iframe_element_referrer_policy(
600 element: Option<&Element>,
601) -> ReferrerPolicy {
602 element
605 .and_then(|element| element.downcast::<HTMLIFrameElement>())
606 .map(|iframe| {
607 let token = iframe.ReferrerPolicy();
608 ReferrerPolicy::from(&*token.str())
609 })
610 .unwrap_or(ReferrerPolicy::EmptyString)
612}
613
614pub(crate) fn snapshot_target_snapshot_params(navigable: &WindowProxy) -> TargetSnapshotParams {
616 let container = navigable.frame_element();
618 let sandboxing_flags = determine_creation_sandboxing_flags(Some(navigable), container);
621 let iframe_element_referrer_policy = determine_iframe_element_referrer_policy(container);
624 TargetSnapshotParams {
625 sandboxing_flags,
626 iframe_element_referrer_policy,
627 }
628}