1use std::cell::Cell;
10use std::rc::Rc;
11
12use content_security_policy::sandboxing_directive::SandboxingFlagSet;
13use crossbeam_channel::Sender;
14use embedder_traits::user_contents::UserContentManagerId;
15use embedder_traits::{ViewportDetails, WebDriverLoadStatus};
16use http::header;
17use js::context::JSContext;
18use net_traits::blob_url_store::UrlWithBlobClaim;
19use net_traits::fetch::fetch_controller::FetchController;
20use net_traits::request::{
21 CredentialsMode, InsecureRequestsPolicy, Origin, PreloadedResources, RedirectMode,
22 RequestBuilder, RequestClient, RequestMode,
23};
24use net_traits::response::ResponseInit;
25use net_traits::{
26 BoxedFetchCallback, CoreResourceThread, DOCUMENT_ACCEPT_HEADER_VALUE, FetchResponseMsg,
27 Metadata, ReferrerPolicy, fetch_async, set_default_accept_language,
28};
29use script_bindings::inheritance::Castable;
30use script_traits::{DocumentActivity, NewPipelineInfo, WebViewState};
31use servo_base::cross_process_instant::CrossProcessInstant;
32use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
33use servo_constellation_traits::{
34 LoadData, LoadOrigin, NavigationHistoryBehavior, ScriptToConstellationMessage,
35 TargetSnapshotParams,
36};
37use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
38use url::Position;
39
40use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
41use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
42use crate::dom::bindings::refcounted::Trusted;
43use crate::dom::document::AbortReason;
44use crate::dom::element::Element;
45use crate::dom::html::htmliframeelement::HTMLIFrameElement;
46use crate::dom::node::node::NodeTraits;
47use crate::dom::window::Window;
48use crate::dom::windowproxy::WindowProxy;
49use crate::event_loop::script_thread::ScriptThread;
50use crate::messaging::MainThreadScriptMsg;
51
52#[derive(Clone)]
53pub struct NavigationListener {
54 request_builder: RequestBuilder,
55 main_thread_sender: Sender<MainThreadScriptMsg>,
56 send_results_to_main_thread: Cell<bool>,
59}
60
61impl NavigationListener {
62 pub(crate) fn into_callback(self) -> BoxedFetchCallback {
63 Box::new(move |response_msg| self.notify_fetch(response_msg))
64 }
65
66 pub fn new(
67 request_builder: RequestBuilder,
68 main_thread_sender: Sender<MainThreadScriptMsg>,
69 ) -> NavigationListener {
70 NavigationListener {
71 request_builder,
72 main_thread_sender,
73 send_results_to_main_thread: Cell::new(true),
74 }
75 }
76
77 pub fn initiate_fetch(
78 self,
79 core_resource_thread: &CoreResourceThread,
80 response_init: Option<ResponseInit>,
81 ) {
82 fetch_async(
83 core_resource_thread,
84 self.request_builder.clone(),
85 response_init,
86 self.into_callback(),
87 );
88 }
89
90 fn notify_fetch(&self, message: FetchResponseMsg) {
91 if !self.send_results_to_main_thread.get() {
94 return;
95 }
96
97 if Self::http_redirect_metadata(&message).is_some() {
99 self.send_results_to_main_thread.set(false);
100 }
101
102 let pipeline_id = self
103 .request_builder
104 .pipeline_id
105 .expect("Navigation should always have an associated Pipeline");
106 let result = self
107 .main_thread_sender
108 .send(MainThreadScriptMsg::NavigationResponse {
109 pipeline_id,
110 message: Box::new(message),
111 });
112
113 if let Err(error) = result {
114 warn!(
115 "Failed to send network message to pipeline {:?}: {error:?}",
116 pipeline_id
117 );
118 }
119 }
120
121 pub(crate) fn http_redirect_metadata(message: &FetchResponseMsg) -> Option<&Metadata> {
122 let FetchResponseMsg::ProcessResponse(_, Ok(metadata)) = message else {
123 return None;
124 };
125
126 let metadata = metadata.metadata();
128 if !matches!(
129 metadata.location_url,
130 Some(Ok(ref location_url)) if matches!(location_url.scheme(), "http" | "https")
131 ) {
132 return None;
133 }
134
135 Some(metadata)
136 }
137}
138
139#[derive(JSTraceable)]
144pub(crate) struct InProgressLoad {
145 #[no_trace]
147 pub(crate) pipeline_id: PipelineId,
148 #[no_trace]
150 pub(crate) browsing_context_id: BrowsingContextId,
151 #[no_trace]
153 pub(crate) webview_state: Rc<WebViewState>,
154 #[no_trace]
156 pub(crate) parent_info: Option<PipelineId>,
157 #[no_trace]
159 pub(crate) opener: Option<BrowsingContextId>,
160 #[no_trace]
162 pub(crate) viewport_details: ViewportDetails,
163 #[no_trace]
165 pub(crate) activity: DocumentActivity,
166 pub(crate) throttled: bool,
168 #[no_trace]
170 pub(crate) navigation_start: CrossProcessInstant,
171 #[no_trace]
173 pub(crate) controller: FetchController,
174 #[no_trace]
176 pub(crate) load_data: LoadData,
177 #[no_trace]
180 pub(crate) url_list: Vec<UrlWithBlobClaim>,
181 #[no_trace]
182 pub(crate) user_content_manager_id: Option<UserContentManagerId>,
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(
194 new_pipeline_info: NewPipelineInfo,
195 webview_state: Rc<WebViewState>,
196 ) -> InProgressLoad {
197 let url = new_pipeline_info.load_data.url.clone();
198
199 InProgressLoad {
200 pipeline_id: new_pipeline_info.new_pipeline_id,
201 browsing_context_id: new_pipeline_info.browsing_context_id,
202 webview_state,
203 parent_info: new_pipeline_info.parent_info,
204 opener: new_pipeline_info.opener,
205 viewport_details: new_pipeline_info.viewport_details,
206 activity: DocumentActivity::FullyActive,
207 throttled: false,
208 navigation_start: CrossProcessInstant::now(),
209 controller: Default::default(),
210 load_data: new_pipeline_info.load_data,
211 url_list: vec![UrlWithBlobClaim::from_url_without_having_claimed_blob(url)],
212 user_content_manager_id: new_pipeline_info.user_content_manager_id,
213 target_snapshot_params: new_pipeline_info.target_snapshot_params,
214 frame_name: new_pipeline_info.frame_name,
215 }
216 }
217
218 pub(crate) fn webview_id(&self) -> WebViewId {
219 self.webview_state.id
220 }
221
222 pub(crate) fn request_builder(&mut self) -> RequestBuilder {
223 let client_origin = match self.load_data.load_origin {
224 LoadOrigin::Script(ref initiator_origin) => initiator_origin.immutable().clone(),
225 _ => ImmutableOrigin::new_opaque(),
226 };
227
228 let id = self.pipeline_id;
229 let webview_id = self.webview_state.id;
230
231 let insecure_requests_policy = self
232 .load_data
233 .inherited_insecure_requests_policy
234 .unwrap_or(InsecureRequestsPolicy::DoNotUpgrade);
235
236 let request_client = RequestClient {
237 preloaded_resources: PreloadedResources::default(),
238 policy_container: self.load_data.policy_container.clone().unwrap_or_default(),
239 origin: Origin::Origin(client_origin),
240 is_nested_browsing_context: self.parent_info.is_some(),
241 insecure_requests_policy,
242 has_trustworthy_ancestor_origin: self.load_data.has_trustworthy_ancestor_origin,
243 network_partition_key: None,
244 };
245
246 let mut request_builder = RequestBuilder::new(
247 Some(webview_id),
248 UrlWithBlobClaim::from_url_without_having_claimed_blob(self.load_data.url.clone()),
249 self.load_data.referrer.clone(),
250 )
251 .method(self.load_data.method.clone())
252 .destination(self.load_data.destination)
253 .mode(RequestMode::Navigate)
254 .credentials_mode(CredentialsMode::Include)
255 .use_url_credentials(true)
256 .pipeline_id(Some(id))
257 .referrer_policy(self.load_data.referrer_policy)
258 .policy_container(self.load_data.policy_container.clone().unwrap_or_default())
259 .headers(self.load_data.headers.clone())
260 .body(self.load_data.data.clone())
261 .redirect_mode(RedirectMode::Manual)
262 .crash(self.load_data.crash.clone())
263 .client(request_client)
264 .url_list(self.url_list.clone());
265
266 request_builder.reload_navigation = self.load_data.reload_navigation;
267 request_builder.history_navigation = self.load_data.history_navigation;
268
269 if !request_builder.headers.contains_key(header::ACCEPT) {
270 request_builder
271 .headers
272 .insert(header::ACCEPT, DOCUMENT_ACCEPT_HEADER_VALUE);
273 }
274 set_default_accept_language(&mut request_builder.headers);
275
276 request_builder
277 }
278}
279
280pub(crate) fn determine_the_origin(
282 url: Option<&ServoUrl>,
283 sandbox_flags: SandboxingFlagSet,
284 source_origin: Option<MutableOrigin>,
285) -> MutableOrigin {
286 let is_sandboxed =
288 sandbox_flags.contains(SandboxingFlagSet::SANDBOXED_ORIGIN_BROWSING_CONTEXT_FLAG);
289 if is_sandboxed {
290 return MutableOrigin::new(ImmutableOrigin::new_opaque());
291 }
292
293 let Some(url) = url else {
295 return MutableOrigin::new(ImmutableOrigin::new_opaque());
296 };
297
298 if url.as_str() == "about:srcdoc" {
300 let source_origin =
302 source_origin.expect("Can't have a null source origin for about:srcdoc");
303 return source_origin;
305 }
306
307 if url.as_str() == "about:blank" &&
309 let Some(source_origin) = source_origin
310 {
311 return source_origin;
312 }
313
314 MutableOrigin::new(url.origin())
316}
317
318fn navigate_to_fragment(
320 cx: &mut JSContext,
321 window: &Window,
322 url: &ServoUrl,
323 history_handling: NavigationHistoryBehavior,
324) {
325 let doc = window.Document();
326 window.send_to_constellation(ScriptToConstellationMessage::NavigatedToFragment(
349 url.clone(),
350 history_handling,
351 ));
352 let old_url = doc.url();
354 doc.set_url(url.clone());
355 doc.update_document_for_history_step_application(&old_url, url);
358 let Some(fragment) = url.fragment() else {
360 unreachable!("Must always have a fragment");
361 };
362 doc.scroll_to_the_fragment(cx, fragment);
363 }
368
369pub(crate) fn navigate(
371 cx: &mut JSContext,
372 window: &Window,
373 history_handling: NavigationHistoryBehavior,
374 force_reload: bool,
375 mut load_data: LoadData,
376) {
377 let document = window.Document();
378
379 if force_reload {
381 load_data.reload_navigation = true;
383 }
384
385 let initiator_origin_snapshot = &load_data.load_origin;
387
388 let pipeline_id = window.pipeline_id();
393 let window_proxy = window.window_proxy();
394 if let Some(active) = window_proxy.currently_active() &&
395 pipeline_id == active &&
396 document.is_prompting_or_unloading()
397 {
398 return;
399 }
400
401 let history_handling = if history_handling == NavigationHistoryBehavior::Auto {
403 if let LoadOrigin::Script(initiator_origin) = initiator_origin_snapshot {
410 if load_data.url == document.url() && initiator_origin.same_origin(&*document.origin())
411 {
412 NavigationHistoryBehavior::Replace
413 } else {
414 NavigationHistoryBehavior::Push
416 }
417 } else {
418 NavigationHistoryBehavior::Push
420 }
421 } else {
422 history_handling
423 };
424
425 let history_handling =
430 if load_data.url.scheme() == "javascript" || document.is_initial_about_blank() {
431 NavigationHistoryBehavior::Replace
432 } else {
433 history_handling
434 };
435
436 if !force_reload
440 && load_data.url.as_url()[..Position::AfterQuery] ==
442 document.url().as_url()[..Position::AfterQuery]
443 && load_data.url.fragment().is_some()
445 {
446 let webdriver_sender = window.webdriver_load_status_sender();
449 if let Some(ref sender) = webdriver_sender {
450 let _ = sender.send(WebDriverLoadStatus::NavigationStart);
451 }
452 navigate_to_fragment(cx, window, &load_data.url, history_handling);
453 if let Some(sender) = webdriver_sender {
455 let _ = sender.send(WebDriverLoadStatus::NavigationStop);
456 }
457 return;
458 }
459
460 let window_proxy = window.window_proxy();
462 if window_proxy.parent().is_some() {
463 window_proxy.start_delaying_load_events_mode();
464 }
465
466 let target_snapshot_params = snapshot_target_snapshot_params(&window_proxy);
469
470 if let Some(sender) = window.webdriver_load_status_sender() {
475 let _ = sender.send(WebDriverLoadStatus::NavigationStart);
476 }
477
478 if load_data.url.scheme() == "javascript" {
485 let Some(initiator_pipeline_id) = load_data.creator_pipeline_id else {
491 unreachable!("javascript: URL navigations must have a creator pipeline");
492 };
493 let Some(initiator_window) = ScriptThread::find_window(initiator_pipeline_id) else {
494 warn!("Can't find global for navigation initiator");
495 return;
496 };
497
498 let target_window = Trusted::new(window);
499 let mut load_data = load_data;
500 let initiator_window = Trusted::new(&*initiator_window);
501 let task = task!(navigate_javascript: move |cx| {
502 let target_window = target_window.root();
504 let initiator_window = initiator_window.root();
505 if ScriptThread::navigate_to_javascript_url(cx, initiator_window.upcast(), target_window.upcast(), &mut load_data, None, None) {
506 target_window
507 .as_global_scope()
508 .script_to_constellation_chan()
509 .send(ScriptToConstellationMessage::LoadUrl(load_data, history_handling, target_snapshot_params))
510 .unwrap();
511 } else {
512 let window_proxy = target_window.window_proxy();
516 if window_proxy.parent().is_some() {
517 window_proxy.stop_delaying_load_events_mode();
518 }
519 }
520 });
521 window
522 .as_global_scope()
523 .task_manager()
524 .navigation_and_traversal_task_source()
525 .queue(task);
526 return;
528 }
529
530 let unload_prompt_canceled = document.check_if_unloading_is_cancelled(cx, false);
541 if !unload_prompt_canceled {
546 return;
552 }
553
554 let trusted_document = Trusted::new(&*document);
558 window
559 .task_manager()
560 .navigation_and_traversal_task_source()
561 .queue(task!(abort_a_document_and_its_descendants: move |cx| {
562 trusted_document.root().abort_a_document_and_its_descendants(cx, AbortReason::Navigate);
563 }));
564
565 window.send_to_constellation(ScriptToConstellationMessage::LoadUrl(
570 load_data,
571 history_handling,
572 target_snapshot_params,
573 ));
574}
575
576pub(crate) fn determine_creation_sandboxing_flags(
578 browsing_context: Option<&WindowProxy>,
579 element: Option<&Element>,
580) -> SandboxingFlagSet {
581 match element {
585 None => browsing_context
588 .and_then(|browsing_context| browsing_context.document())
589 .map(|document| document.active_sandboxing_flag_set())
590 .unwrap_or(SandboxingFlagSet::empty()),
591 Some(element) => {
592 element
597 .downcast::<HTMLIFrameElement>()
598 .map(|iframe| iframe.sandboxing_flag_set())
599 .unwrap_or(SandboxingFlagSet::empty())
600 .union(element.owner_document().active_sandboxing_flag_set())
601 },
602 }
603}
604
605pub(crate) fn determine_iframe_element_referrer_policy(
607 element: Option<&Element>,
608) -> ReferrerPolicy {
609 element
612 .and_then(|element| element.downcast::<HTMLIFrameElement>())
613 .map(|iframe| {
614 let token = iframe.ReferrerPolicy();
615 ReferrerPolicy::from(&*token.str())
616 })
617 .unwrap_or(ReferrerPolicy::EmptyString)
619}
620
621pub(crate) fn snapshot_target_snapshot_params(navigable: &WindowProxy) -> TargetSnapshotParams {
623 let container = navigable.frame_element();
625 let sandboxing_flags = determine_creation_sandboxing_flags(Some(navigable), container);
628 let iframe_element_referrer_policy = determine_iframe_element_referrer_policy(container);
631 TargetSnapshotParams {
632 sandboxing_flags,
633 iframe_element_referrer_policy,
634 }
635}