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:
Martin Robinson 2025-01-10 13:19:40 +01:00 committed by GitHub
parent 73c0701c83
commit fbd77b4524
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 420 additions and 460 deletions

View file

@ -14,7 +14,7 @@ use base::id::HistoryStateId;
use cookie::Cookie;
use crossbeam_channel::{unbounded, Receiver, Sender};
use headers::{ContentType, HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
use http::{Error as HttpError, HeaderMap, StatusCode};
use http::{header, Error as HttpError, HeaderMap, HeaderValue, StatusCode};
use hyper_serde::Serde;
use hyper_util::client::legacy::Error as HyperError;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
@ -46,6 +46,10 @@ pub mod request;
pub mod response;
pub mod storage_thread;
/// <https://fetch.spec.whatwg.org/#document-accept-header-value>
pub const DOCUMENT_ACCEPT_HEADER_VALUE: HeaderValue =
HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
/// An implementation of the [Fetch specification](https://fetch.spec.whatwg.org/)
pub mod fetch {
pub mod headers;
@ -197,7 +201,7 @@ pub enum FetchResponseMsg {
}
impl FetchResponseMsg {
fn request_id(&self) -> RequestId {
pub fn request_id(&self) -> RequestId {
match self {
FetchResponseMsg::ProcessRequestBody(id) |
FetchResponseMsg::ProcessRequestEOF(id) |
@ -252,6 +256,15 @@ pub enum FetchMetadata {
},
}
impl FetchMetadata {
pub fn metadata(&self) -> &Metadata {
match self {
Self::Unfiltered(metadata) => metadata,
Self::Filtered { unsafe_, .. } => unsafe_,
}
}
}
pub trait FetchResponseListener {
fn process_request_body(&mut self, request_id: RequestId);
fn process_request_eof(&mut self, request_id: RequestId);
@ -511,6 +524,7 @@ pub enum CoreResourceMsg {
enum ToFetchThreadMessage {
StartFetch(
/* request_builder */ RequestBuilder,
/* response_init */ Option<ResponseInit>,
/* cancel_chan */ Option<IpcReceiver<()>>,
/* callback */ BoxedFetchCallback,
),
@ -570,14 +584,29 @@ impl FetchThread {
fn run(&mut self) {
loop {
match self.receiver.recv().unwrap() {
ToFetchThreadMessage::StartFetch(request_builder, canceller, callback) => {
ToFetchThreadMessage::StartFetch(
request_builder,
response_init,
canceller,
callback,
) => {
self.active_fetches.insert(request_builder.id, callback);
self.core_resource_thread
.send(CoreResourceMsg::Fetch(
// Only redirects have a `response_init` field.
let message = match response_init {
Some(response_init) => CoreResourceMsg::FetchRedirect(
request_builder,
response_init,
self.to_fetch_sender.clone(),
canceller,
),
None => CoreResourceMsg::Fetch(
request_builder,
FetchChannels::ResponseMsg(self.to_fetch_sender.clone(), canceller),
))
.unwrap();
),
};
self.core_resource_thread.send(message).unwrap();
},
ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
let request_id = fetch_response_msg.request_id();
@ -599,18 +628,23 @@ impl FetchThread {
}
}
/// Instruct the resource thread to make a new request.
static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
/// Instruct the resource thread to make a new fetch request.
pub fn fetch_async(
core_resource_thread: &CoreResourceThread,
request: RequestBuilder,
response_init: Option<ResponseInit>,
canceller: Option<IpcReceiver<()>>,
callback: BoxedFetchCallback,
) {
static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
let _ = FETCH_THREAD
.get_or_init(|| FetchThread::spawn(core_resource_thread))
.send(ToFetchThreadMessage::StartFetch(
request, canceller, callback,
request,
response_init,
canceller,
callback,
));
}
@ -945,5 +979,17 @@ pub fn http_percent_encode(bytes: &[u8]) -> String {
percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
}
pub fn set_default_accept_language(headers: &mut HeaderMap) {
if headers.contains_key(header::ACCEPT_LANGUAGE) {
return;
}
// TODO(eijebong): Change this once typed headers are done
headers.insert(
header::ACCEPT_LANGUAGE,
HeaderValue::from_static("en-US,en;q=0.5"),
);
}
pub static PRIVILEGED_SECRET: LazyLock<u32> =
LazyLock::new(|| servo_rand::ServoRng::default().next_u32());

View file

@ -47,7 +47,7 @@ use media::WindowGLContext;
use net_traits::image_cache::ImageCache;
use net_traits::request::{Referrer, RequestBody};
use net_traits::storage_thread::StorageType;
use net_traits::{FetchResponseMsg, ReferrerPolicy, ResourceThreads};
use net_traits::{ReferrerPolicy, ResourceThreads};
use pixels::{Image, PixelFormat};
use profile_traits::{mem, time as profile_time};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
@ -286,16 +286,12 @@ pub enum UpdatePipelineIdReason {
/// Messages sent from the constellation or layout to the script thread.
// FIXME: https://github.com/servo/servo/issues/34591
#[expect(clippy::large_enum_variant)]
#[derive(Deserialize, Serialize)]
pub enum ConstellationControlMsg {
/// Takes the associated window proxy out of "delaying-load-events-mode",
/// used if a scheduled navigated was refused by the embedder.
/// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
StopDelayingLoadEventsMode(PipelineId),
/// Sends the final response to script thread for fetching after all redirections
/// have been resolved
NavigationResponse(PipelineId, FetchResponseMsg),
/// Gives a channel and ID to a layout, as well as the ID of that layout's parent
AttachLayout(NewLayoutInfo),
/// Window resized. Sends a DOM event eventually, but first we combine events.
@ -413,7 +409,6 @@ impl fmt::Debug for ConstellationControlMsg {
use self::ConstellationControlMsg::*;
let variant = match *self {
StopDelayingLoadEventsMode(..) => "StopDelayingLoadsEventMode",
NavigationResponse(..) => "NavigationResponse",
AttachLayout(..) => "AttachLayout",
Resize(..) => "Resize",
ThemeChange(..) => "ThemeChange",

View file

@ -17,7 +17,6 @@ use embedder_traits::{EmbedderMsg, MediaSessionEvent};
use euclid::default::Size2D as UntypedSize2D;
use euclid::Size2D;
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use net_traits::request::RequestBuilder;
use net_traits::storage_thread::StorageType;
use net_traits::CoreResourceMsg;
use serde::{Deserialize, Serialize};
@ -128,9 +127,6 @@ pub enum ScriptMsg {
ScheduleBroadcast(BroadcastChannelRouterId, BroadcastMsg),
/// Forward a message to the embedder.
ForwardToEmbedder(EmbedderMsg),
/// Requests are sent to constellation and fetches are checked manually
/// for cross-origin loads
InitiateNavigateRequest(RequestBuilder, /* cancellation_chan */ IpcReceiver<()>),
/// Broadcast a storage event to every same-origin pipeline.
/// The strings are key, old value and new value.
BroadcastStorageEvent(
@ -271,7 +267,6 @@ impl fmt::Debug for ScriptMsg {
NewBroadcastChannelNameInRouter(..) => "NewBroadcastChannelNameInRouter",
ScheduleBroadcast(..) => "ScheduleBroadcast",
ForwardToEmbedder(..) => "ForwardToEmbedder",
InitiateNavigateRequest(..) => "InitiateNavigateRequest",
BroadcastStorageEvent(..) => "BroadcastStorageEvent",
ChangeRunningAnimationsState(..) => "ChangeRunningAnimationsState",
CreateCanvasPaintThread(..) => "CreateCanvasPaintThread",