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