mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
script: Move navigation fetching to the ScriptThread
(#34919)
This allows reusing the asynchrnous fetch mechanism that we use for page resources and is likely a step toward removing the `FetchThread`. Benefits: - Reduces IPC traffic during navigation. Now instead of bouncing between the constellation and the `ScriptThread` responses are sent directly to the `ScriptThread`. - Allows cancelling loads after redirects, which was not possible before. There is the question of what to do when a redirect is cross-origin (#23037). This currently isn't handled properly as the `Constellation` sends data to the same `Pipeline` that initiated the load. This change doesn't fix this issue, but does make it more possible for the `ScriptThread` to shut down the pipeline and ask the `Constellation` to replace it with a new one. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
73c0701c83
commit
fbd77b4524
16 changed files with 420 additions and 460 deletions
224
components/script/navigation.rs
Normal file
224
components/script/navigation.rs
Normal file
|
@ -0,0 +1,224 @@
|
|||
/* 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, TopLevelBrowsingContextId};
|
||||
use content_security_policy::Destination;
|
||||
use crossbeam_channel::Sender;
|
||||
use http::header;
|
||||
use ipc_channel::ipc;
|
||||
use net_traits::request::{CredentialsMode, RedirectMode, RequestBuilder, RequestMode};
|
||||
use net_traits::response::ResponseInit;
|
||||
use net_traits::{
|
||||
fetch_async, set_default_accept_language, BoxedFetchCallback, CoreResourceThread,
|
||||
FetchResponseMsg, Metadata, DOCUMENT_ACCEPT_HEADER_VALUE,
|
||||
};
|
||||
use script_traits::{DocumentActivity, LoadData, WindowSizeData};
|
||||
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>,
|
||||
cancellation_receiver: Option<ipc::IpcReceiver<()>>,
|
||||
) {
|
||||
fetch_async(
|
||||
core_resource_thread,
|
||||
self.request_builder.clone(),
|
||||
response_init,
|
||||
cancellation_receiver,
|
||||
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(ref 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) top_level_browsing_context_id: TopLevelBrowsingContextId,
|
||||
/// 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) window_size: WindowSizeData,
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
impl InProgressLoad {
|
||||
/// Create a new InProgressLoad object.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
id: PipelineId,
|
||||
browsing_context_id: BrowsingContextId,
|
||||
top_level_browsing_context_id: TopLevelBrowsingContextId,
|
||||
parent_info: Option<PipelineId>,
|
||||
opener: Option<BrowsingContextId>,
|
||||
window_size: WindowSizeData,
|
||||
origin: MutableOrigin,
|
||||
load_data: LoadData,
|
||||
) -> InProgressLoad {
|
||||
let url = load_data.url.clone();
|
||||
InProgressLoad {
|
||||
pipeline_id: id,
|
||||
browsing_context_id,
|
||||
top_level_browsing_context_id,
|
||||
parent_info,
|
||||
opener,
|
||||
window_size,
|
||||
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 top_level_browsing_context_id = self.top_level_browsing_context_id;
|
||||
let mut request_builder =
|
||||
RequestBuilder::new(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))
|
||||
.target_browsing_context_id(Some(top_level_browsing_context_id))
|
||||
.referrer_policy(self.load_data.referrer_policy)
|
||||
.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
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue