script/
navigation.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! The listener that encapsulates all state for an in-progress document request.
6//! Any redirects that are encountered are followed. Whenever a non-redirect
7//! response is received, it is forwarded to the appropriate script thread.
8
9use std::cell::Cell;
10
11use base::cross_process_instant::CrossProcessInstant;
12use base::id::{BrowsingContextId, PipelineId, WebViewId};
13use constellation_traits::LoadData;
14use crossbeam_channel::Sender;
15use embedder_traits::{Theme, ViewportDetails};
16use http::header;
17use net_traits::request::{
18    CredentialsMode, InsecureRequestsPolicy, RedirectMode, RequestBuilder, RequestMode,
19};
20use net_traits::response::ResponseInit;
21use net_traits::{
22    BoxedFetchCallback, CoreResourceThread, DOCUMENT_ACCEPT_HEADER_VALUE, FetchResponseMsg,
23    Metadata, fetch_async, set_default_accept_language,
24};
25use script_traits::{DocumentActivity, NewPipelineInfo};
26use servo_url::{MutableOrigin, ServoUrl};
27
28use crate::fetch::FetchCanceller;
29use crate::messaging::MainThreadScriptMsg;
30
31#[derive(Clone)]
32pub struct NavigationListener {
33    request_builder: RequestBuilder,
34    main_thread_sender: Sender<MainThreadScriptMsg>,
35    // Whether or not results are sent to the main thread. After a redirect results are no longer sent,
36    // as the main thread has already started a new request.
37    send_results_to_main_thread: Cell<bool>,
38}
39
40impl NavigationListener {
41    pub(crate) fn into_callback(self) -> BoxedFetchCallback {
42        Box::new(move |response_msg| self.notify_fetch(response_msg))
43    }
44
45    pub fn new(
46        request_builder: RequestBuilder,
47        main_thread_sender: Sender<MainThreadScriptMsg>,
48    ) -> NavigationListener {
49        NavigationListener {
50            request_builder,
51            main_thread_sender,
52            send_results_to_main_thread: Cell::new(true),
53        }
54    }
55
56    pub fn initiate_fetch(
57        self,
58        core_resource_thread: &CoreResourceThread,
59        response_init: Option<ResponseInit>,
60    ) {
61        fetch_async(
62            core_resource_thread,
63            self.request_builder.clone(),
64            response_init,
65            self.into_callback(),
66        );
67    }
68
69    fn notify_fetch(&self, message: FetchResponseMsg) {
70        // If we've already asked the main thread to redirect the response, then stop sending results
71        // for this fetch. The main thread has already replaced it.
72        if !self.send_results_to_main_thread.get() {
73            return;
74        }
75
76        // If this is a redirect, don't send any more message after this one.
77        if Self::http_redirect_metadata(&message).is_some() {
78            self.send_results_to_main_thread.set(false);
79        }
80
81        let pipeline_id = self
82            .request_builder
83            .pipeline_id
84            .expect("Navigation should always have an associated Pipeline");
85        let result = self
86            .main_thread_sender
87            .send(MainThreadScriptMsg::NavigationResponse {
88                pipeline_id,
89                message: Box::new(message),
90            });
91
92        if let Err(error) = result {
93            warn!(
94                "Failed to send network message to pipeline {:?}: {error:?}",
95                pipeline_id
96            );
97        }
98    }
99
100    pub(crate) fn http_redirect_metadata(message: &FetchResponseMsg) -> Option<&Metadata> {
101        let FetchResponseMsg::ProcessResponse(_, Ok(metadata)) = message else {
102            return None;
103        };
104
105        // Don't allow redirects for non HTTP(S) URLs.
106        let metadata = metadata.metadata();
107        if !matches!(
108            metadata.location_url,
109            Some(Ok(ref location_url)) if matches!(location_url.scheme(), "http" | "https")
110        ) {
111            return None;
112        }
113
114        Some(metadata)
115    }
116}
117
118/// A document load that is in the process of fetching the requested resource. Contains
119/// data that will need to be present when the document and frame tree entry are created,
120/// but is only easily available at initiation of the load and on a push basis (so some
121/// data will be updated according to future resize events, viewport changes, etc.)
122#[derive(JSTraceable)]
123pub(crate) struct InProgressLoad {
124    /// The pipeline which requested this load.
125    #[no_trace]
126    pub(crate) pipeline_id: PipelineId,
127    /// The browsing context being loaded into.
128    #[no_trace]
129    pub(crate) browsing_context_id: BrowsingContextId,
130    /// The top level ancestor browsing context.
131    #[no_trace]
132    pub(crate) webview_id: WebViewId,
133    /// The parent pipeline and frame type associated with this load, if any.
134    #[no_trace]
135    pub(crate) parent_info: Option<PipelineId>,
136    /// The opener, if this is an auxiliary.
137    #[no_trace]
138    pub(crate) opener: Option<BrowsingContextId>,
139    /// The current window size associated with this pipeline.
140    #[no_trace]
141    pub(crate) viewport_details: ViewportDetails,
142    /// The activity level of the document (inactive, active or fully active).
143    #[no_trace]
144    pub(crate) activity: DocumentActivity,
145    /// Window is throttled, running timers at a heavily limited rate.
146    pub(crate) throttled: bool,
147    /// The origin for the document
148    #[no_trace]
149    pub(crate) origin: MutableOrigin,
150    /// Timestamp reporting the time when the browser started this load.
151    #[no_trace]
152    pub(crate) navigation_start: CrossProcessInstant,
153    /// For cancelling the fetch
154    pub(crate) canceller: FetchCanceller,
155    /// The [`LoadData`] associated with this load.
156    #[no_trace]
157    pub(crate) load_data: LoadData,
158    /// A list of URL to keep track of all the redirects that have happened during
159    /// this load.
160    #[no_trace]
161    pub(crate) url_list: Vec<ServoUrl>,
162    /// The [`Theme`] to use for this page, once it loads.
163    #[no_trace]
164    pub(crate) theme: Theme,
165}
166
167impl InProgressLoad {
168    /// Create a new InProgressLoad object.
169    pub(crate) fn new(new_pipeline_info: NewPipelineInfo, origin: MutableOrigin) -> InProgressLoad {
170        let url = new_pipeline_info.load_data.url.clone();
171        InProgressLoad {
172            pipeline_id: new_pipeline_info.new_pipeline_id,
173            browsing_context_id: new_pipeline_info.browsing_context_id,
174            webview_id: new_pipeline_info.webview_id,
175            parent_info: new_pipeline_info.parent_info,
176            opener: new_pipeline_info.opener,
177            viewport_details: new_pipeline_info.viewport_details,
178            activity: DocumentActivity::FullyActive,
179            throttled: false,
180            origin,
181            navigation_start: CrossProcessInstant::now(),
182            canceller: Default::default(),
183            load_data: new_pipeline_info.load_data,
184            url_list: vec![url],
185            theme: new_pipeline_info.theme,
186        }
187    }
188
189    pub(crate) fn request_builder(&mut self) -> RequestBuilder {
190        let id = self.pipeline_id;
191        let webview_id = self.webview_id;
192        let mut request_builder = RequestBuilder::new(
193            Some(webview_id),
194            self.load_data.url.clone(),
195            self.load_data.referrer.clone(),
196        )
197        .method(self.load_data.method.clone())
198        .destination(self.load_data.destination)
199        .mode(RequestMode::Navigate)
200        .credentials_mode(CredentialsMode::Include)
201        .use_url_credentials(true)
202        .pipeline_id(Some(id))
203        .referrer_policy(self.load_data.referrer_policy)
204        .policy_container(self.load_data.policy_container.clone().unwrap_or_default())
205        .insecure_requests_policy(
206            self.load_data
207                .inherited_insecure_requests_policy
208                .unwrap_or(InsecureRequestsPolicy::DoNotUpgrade),
209        )
210        .has_trustworthy_ancestor_origin(self.load_data.has_trustworthy_ancestor_origin)
211        .headers(self.load_data.headers.clone())
212        .body(self.load_data.data.clone())
213        .redirect_mode(RedirectMode::Manual)
214        .origin(self.origin.immutable().clone())
215        .crash(self.load_data.crash.clone());
216        request_builder.url_list = self.url_list.clone();
217
218        if !request_builder.headers.contains_key(header::ACCEPT) {
219            request_builder
220                .headers
221                .insert(header::ACCEPT, DOCUMENT_ACCEPT_HEADER_VALUE);
222        }
223        set_default_accept_language(&mut request_builder.headers);
224
225        request_builder
226    }
227}