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;
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    #[allow(clippy::too_many_arguments)]
170    pub(crate) fn new(
171        id: PipelineId,
172        browsing_context_id: BrowsingContextId,
173        webview_id: WebViewId,
174        parent_info: Option<PipelineId>,
175        opener: Option<BrowsingContextId>,
176        viewport_details: ViewportDetails,
177        theme: Theme,
178        origin: MutableOrigin,
179        load_data: LoadData,
180    ) -> InProgressLoad {
181        let url = load_data.url.clone();
182        InProgressLoad {
183            pipeline_id: id,
184            browsing_context_id,
185            webview_id,
186            parent_info,
187            opener,
188            viewport_details,
189            activity: DocumentActivity::FullyActive,
190            throttled: false,
191            origin,
192            navigation_start: CrossProcessInstant::now(),
193            canceller: Default::default(),
194            load_data,
195            url_list: vec![url],
196            theme,
197        }
198    }
199
200    pub(crate) fn request_builder(&mut self) -> RequestBuilder {
201        let id = self.pipeline_id;
202        let webview_id = self.webview_id;
203        let mut request_builder = RequestBuilder::new(
204            Some(webview_id),
205            self.load_data.url.clone(),
206            self.load_data.referrer.clone(),
207        )
208        .method(self.load_data.method.clone())
209        .destination(self.load_data.destination)
210        .mode(RequestMode::Navigate)
211        .credentials_mode(CredentialsMode::Include)
212        .use_url_credentials(true)
213        .pipeline_id(Some(id))
214        .referrer_policy(self.load_data.referrer_policy)
215        .policy_container(self.load_data.policy_container.clone().unwrap_or_default())
216        .insecure_requests_policy(
217            self.load_data
218                .inherited_insecure_requests_policy
219                .unwrap_or(InsecureRequestsPolicy::DoNotUpgrade),
220        )
221        .has_trustworthy_ancestor_origin(self.load_data.has_trustworthy_ancestor_origin)
222        .headers(self.load_data.headers.clone())
223        .body(self.load_data.data.clone())
224        .redirect_mode(RedirectMode::Manual)
225        .origin(self.origin.immutable().clone())
226        .crash(self.load_data.crash.clone());
227        request_builder.url_list = self.url_list.clone();
228
229        if !request_builder.headers.contains_key(header::ACCEPT) {
230            request_builder
231                .headers
232                .insert(header::ACCEPT, DOCUMENT_ACCEPT_HEADER_VALUE);
233        }
234        set_default_accept_language(&mut request_builder.headers);
235
236        request_builder
237    }
238}