| /* 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 crossbeam_channel::Sender; |
| use embedder_traits::{Theme, 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<MainThreadScriptMsg>, |
| // 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<bool>, |
| } |
| |
| 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<MainThreadScriptMsg>, |
| ) -> 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<ResponseInit>, |
| ) { |
| 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<PipelineId>, |
| /// The opener, if this is an auxiliary. |
| #[no_trace] |
| pub(crate) opener: Option<BrowsingContextId>, |
| /// 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<ServoUrl>, |
| /// The [`Theme`] to use for this page, once it loads. |
| #[no_trace] |
| pub(crate) theme: Theme, |
| } |
| |
| 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<PipelineId>, |
| opener: Option<BrowsingContextId>, |
| viewport_details: ViewportDetails, |
| theme: Theme, |
| 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], |
| theme, |
| } |
| } |
| |
| 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(self.load_data.destination) |
| .mode(RequestMode::Navigate) |
| .credentials_mode(CredentialsMode::Include) |
| .use_url_credentials(true) |
| .pipeline_id(Some(id)) |
| .referrer_policy(self.load_data.referrer_policy) |
| .policy_container(self.load_data.policy_container.clone().unwrap_or_default()) |
| .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 |
| } |
| } |