/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! The listener that encapsulates all state for an in-progress document request. //! Any redirects that are encountered are followed. Whenever a non-redirect //! response is received, it is forwarded to the appropriate script thread. use std::cell::Cell; use base::cross_process_instant::CrossProcessInstant; use base::id::{BrowsingContextId, PipelineId, WebViewId}; use constellation_traits::LoadData; use content_security_policy::Destination; use crossbeam_channel::Sender; use embedder_traits::ViewportDetails; use http::header; use net_traits::request::{ CredentialsMode, InsecureRequestsPolicy, RedirectMode, RequestBuilder, RequestMode, }; use net_traits::response::ResponseInit; use net_traits::{ BoxedFetchCallback, CoreResourceThread, DOCUMENT_ACCEPT_HEADER_VALUE, FetchResponseMsg, Metadata, fetch_async, set_default_accept_language, }; use script_traits::DocumentActivity; use servo_url::{MutableOrigin, ServoUrl}; use crate::fetch::FetchCanceller; use crate::messaging::MainThreadScriptMsg; #[derive(Clone)] pub struct NavigationListener { request_builder: RequestBuilder, main_thread_sender: Sender, // Whether or not results are sent to the main thread. After a redirect results are no longer sent, // as the main thread has already started a new request. send_results_to_main_thread: Cell, } impl NavigationListener { pub(crate) fn into_callback(self) -> BoxedFetchCallback { Box::new(move |response_msg| self.notify_fetch(response_msg)) } pub fn new( request_builder: RequestBuilder, main_thread_sender: Sender, ) -> NavigationListener { NavigationListener { request_builder, main_thread_sender, send_results_to_main_thread: Cell::new(true), } } pub fn initiate_fetch( self, core_resource_thread: &CoreResourceThread, response_init: Option, ) { fetch_async( core_resource_thread, self.request_builder.clone(), response_init, self.into_callback(), ); } fn notify_fetch(&self, message: FetchResponseMsg) { // If we've already asked the main thread to redirect the response, then stop sending results // for this fetch. The main thread has already replaced it. if !self.send_results_to_main_thread.get() { return; } // If this is a redirect, don't send any more message after this one. if Self::http_redirect_metadata(&message).is_some() { self.send_results_to_main_thread.set(false); } let pipeline_id = self .request_builder .pipeline_id .expect("Navigation should always have an associated Pipeline"); let result = self .main_thread_sender .send(MainThreadScriptMsg::NavigationResponse { pipeline_id, message: Box::new(message), }); if let Err(error) = result { warn!( "Failed to send network message to pipeline {:?}: {error:?}", pipeline_id ); } } pub(crate) fn http_redirect_metadata(message: &FetchResponseMsg) -> Option<&Metadata> { let FetchResponseMsg::ProcessResponse(_, Ok(metadata)) = message else { return None; }; // Don't allow redirects for non HTTP(S) URLs. let metadata = metadata.metadata(); if !matches!( metadata.location_url, Some(Ok(ref location_url)) if matches!(location_url.scheme(), "http" | "https") ) { return None; } Some(metadata) } } /// A document load that is in the process of fetching the requested resource. Contains /// data that will need to be present when the document and frame tree entry are created, /// but is only easily available at initiation of the load and on a push basis (so some /// data will be updated according to future resize events, viewport changes, etc.) #[derive(JSTraceable)] pub(crate) struct InProgressLoad { /// The pipeline which requested this load. #[no_trace] pub(crate) pipeline_id: PipelineId, /// The browsing context being loaded into. #[no_trace] pub(crate) browsing_context_id: BrowsingContextId, /// The top level ancestor browsing context. #[no_trace] pub(crate) webview_id: WebViewId, /// The parent pipeline and frame type associated with this load, if any. #[no_trace] pub(crate) parent_info: Option, /// The opener, if this is an auxiliary. #[no_trace] pub(crate) opener: Option, /// The current window size associated with this pipeline. #[no_trace] pub(crate) viewport_details: ViewportDetails, /// The activity level of the document (inactive, active or fully active). #[no_trace] pub(crate) activity: DocumentActivity, /// Window is throttled, running timers at a heavily limited rate. pub(crate) throttled: bool, /// The origin for the document #[no_trace] pub(crate) origin: MutableOrigin, /// Timestamp reporting the time when the browser started this load. #[no_trace] pub(crate) navigation_start: CrossProcessInstant, /// For cancelling the fetch pub(crate) canceller: FetchCanceller, /// The [`LoadData`] associated with this load. #[no_trace] pub(crate) load_data: LoadData, /// A list of URL to keep track of all the redirects that have happened during /// this load. #[no_trace] pub(crate) url_list: Vec, } impl InProgressLoad { /// Create a new InProgressLoad object. #[allow(clippy::too_many_arguments)] pub(crate) fn new( id: PipelineId, browsing_context_id: BrowsingContextId, webview_id: WebViewId, parent_info: Option, opener: Option, viewport_details: ViewportDetails, origin: MutableOrigin, load_data: LoadData, ) -> InProgressLoad { let url = load_data.url.clone(); InProgressLoad { pipeline_id: id, browsing_context_id, webview_id, parent_info, opener, viewport_details, activity: DocumentActivity::FullyActive, throttled: false, origin, navigation_start: CrossProcessInstant::now(), canceller: Default::default(), load_data, url_list: vec![url], } } pub(crate) fn request_builder(&mut self) -> RequestBuilder { let id = self.pipeline_id; let webview_id = self.webview_id; let mut request_builder = RequestBuilder::new( Some(webview_id), self.load_data.url.clone(), self.load_data.referrer.clone(), ) .method(self.load_data.method.clone()) .destination(Destination::Document) .mode(RequestMode::Navigate) .credentials_mode(CredentialsMode::Include) .use_url_credentials(true) .pipeline_id(Some(id)) .referrer_policy(self.load_data.referrer_policy) .insecure_requests_policy( self.load_data .inherited_insecure_requests_policy .unwrap_or(InsecureRequestsPolicy::DoNotUpgrade), ) .has_trustworthy_ancestor_origin(self.load_data.has_trustworthy_ancestor_origin) .headers(self.load_data.headers.clone()) .body(self.load_data.data.clone()) .redirect_mode(RedirectMode::Manual) .origin(self.origin.immutable().clone()) .crash(self.load_data.crash.clone()); request_builder.url_list = self.url_list.clone(); if !request_builder.headers.contains_key(header::ACCEPT) { request_builder .headers .insert(header::ACCEPT, DOCUMENT_ACCEPT_HEADER_VALUE); } set_default_accept_language(&mut request_builder.headers); request_builder } }