diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index b5efc709eb2..9fb06ce7a85 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -129,9 +129,9 @@ use keyboard_types::{CompositionEvent, KeyboardEvent}; use log::{debug, error, info, trace, warn}; use media::{GLPlayerThreads, WindowGLContext}; use net_traits::pub_domains::reg_host; -use net_traits::request::{Referrer, RequestBuilder}; +use net_traits::request::Referrer; use net_traits::storage_thread::{StorageThreadMsg, StorageType}; -use net_traits::{self, FetchResponseMsg, IpcSend, ReferrerPolicy, ResourceThreads}; +use net_traits::{self, IpcSend, ReferrerPolicy, ResourceThreads}; use profile_traits::{mem, time}; use script_layout_interface::{LayoutFactory, ScriptThreadFactory}; use script_traits::CompositorEvent::{MouseButtonEvent, MouseMoveEvent}; @@ -166,7 +166,6 @@ use crate::browsingcontext::{ NewBrowsingContextInfo, }; use crate::event_loop::EventLoop; -use crate::network_listener::NetworkListener; use crate::pipeline::{InitialPipelineState, Pipeline}; use crate::serviceworker::ServiceWorkerUnprivilegedContent; use crate::session_history::{ @@ -317,12 +316,6 @@ pub struct Constellation { /// This is the constellation's view of `layout_sender`. layout_receiver: Receiver>, - /// A channel for network listener to send messages to the constellation. - network_listener_sender: Sender<(PipelineId, FetchResponseMsg)>, - - /// A channel for the constellation to receive messages from network listener. - network_listener_receiver: Receiver<(PipelineId, FetchResponseMsg)>, - /// A channel for the constellation to receive messages from the compositor thread. compositor_receiver: Receiver, @@ -685,8 +678,6 @@ where layout_ipc_receiver, ); - let (network_listener_sender, network_listener_receiver) = unbounded(); - let swmanager_receiver = route_ipc_receiver_to_new_crossbeam_receiver_preserving_errors( swmanager_ipc_receiver, @@ -715,8 +706,6 @@ where compositor_receiver, layout_factory, layout_receiver, - network_listener_sender, - network_listener_receiver, embedder_proxy: state.embedder_proxy, compositor_proxy: state.compositor_proxy, webviews: WebViewManager::default(), @@ -1156,15 +1145,12 @@ where )] fn handle_request(&mut self) { #[derive(Debug)] - // FIXME: https://github.com/servo/servo/issues/34591 - #[expect(clippy::large_enum_variant)] enum Request { PipelineNamespace(PipelineNamespaceRequest), Script((PipelineId, FromScriptMsg)), BackgroundHangMonitor(HangMonitorAlert), Compositor(FromCompositorMsg), Layout(FromLayoutMsg), - NetworkListener((PipelineId, FetchResponseMsg)), FromSWManager(SWManagerMsg), } // Get one incoming request. @@ -1198,11 +1184,6 @@ where recv(self.layout_receiver) -> msg => { msg.expect("Unexpected layout channel panic in constellation").map(Request::Layout) } - recv(self.network_listener_receiver) -> msg => { - Ok(Request::NetworkListener( - msg.expect("Unexpected network listener channel panic in constellation") - )) - } recv(self.swmanager_receiver) -> msg => { msg.expect("Unexpected SW channel panic in constellation").map(Request::FromSWManager) } @@ -1228,9 +1209,6 @@ where Request::Layout(message) => { self.handle_request_from_layout(message); }, - Request::NetworkListener(message) => { - self.handle_request_from_network_listener(message); - }, Request::FromSWManager(message) => { self.handle_request_from_swmanager(message); }, @@ -1263,26 +1241,6 @@ where } } - #[cfg_attr( - feature = "tracing", - tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") - )] - fn handle_request_from_network_listener(&mut self, message: (PipelineId, FetchResponseMsg)) { - let (id, message_) = message; - let result = match self.pipelines.get(&id) { - Some(pipeline) => { - let msg = ConstellationControlMsg::NavigationResponse(id, message_); - pipeline.event_loop.send(msg) - }, - None => { - return warn!("{}: Got fetch data after closure!", id); - }, - }; - if let Err(e) = result { - self.handle_send_error(id, e); - } - } - fn handle_request_from_swmanager(&mut self, message: SWManagerMsg) { match message { SWManagerMsg::PostMessageToClient => { @@ -1615,10 +1573,6 @@ where FromScriptMsg::DiscardTopLevelBrowsingContext => { self.handle_close_top_level_browsing_context(source_top_ctx_id); }, - - FromScriptMsg::InitiateNavigateRequest(req_init, cancel_chan) => { - self.handle_navigate_request(source_pipeline_id, req_init, cancel_chan); - }, FromScriptMsg::ScriptLoadedURLInIFrame(load_info) => { self.handle_script_loaded_url_in_iframe_msg(load_info); }, @@ -3202,26 +3156,6 @@ where } } - #[cfg_attr( - feature = "tracing", - tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") - )] - fn handle_navigate_request( - &self, - id: PipelineId, - request_builder: RequestBuilder, - cancel_chan: IpcReceiver<()>, - ) { - let listener = NetworkListener::new( - request_builder, - id, - self.public_resource_threads.clone(), - self.network_listener_sender.clone(), - ); - - listener.initiate_fetch(Some(cancel_chan)); - } - // The script thread associated with pipeline_id has loaded a URL in an // iframe via script. This will result in a new pipeline being spawned and // a child being added to the parent browsing context. This message is never diff --git a/components/constellation/lib.rs b/components/constellation/lib.rs index 0f70773ccfc..8b4577458f8 100644 --- a/components/constellation/lib.rs +++ b/components/constellation/lib.rs @@ -11,7 +11,6 @@ mod browsingcontext; mod constellation; mod event_loop; mod logging; -mod network_listener; mod pipeline; mod sandboxing; mod serviceworker; diff --git a/components/constellation/network_listener.rs b/components/constellation/network_listener.rs deleted file mode 100644 index bad08d5e630..00000000000 --- a/components/constellation/network_listener.rs +++ /dev/null @@ -1,192 +0,0 @@ -/* 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 base::id::PipelineId; -use crossbeam_channel::Sender; -use http::{header, HeaderMap}; -use ipc_channel::ipc; -use ipc_channel::router::ROUTER; -use log::warn; -use net::http_loader::{set_default_accept_language, DOCUMENT_ACCEPT_HEADER_VALUE}; -use net_traits::request::{Referrer, RequestBuilder, RequestId}; -use net_traits::response::ResponseInit; -use net_traits::{ - CoreResourceMsg, FetchChannels, FetchMetadata, FetchResponseMsg, IpcSend, NetworkError, - ResourceThreads, -}; - -pub struct NetworkListener { - res_init: Option, - request_builder: RequestBuilder, - pipeline_id: PipelineId, - resource_threads: ResourceThreads, - sender: Sender<(PipelineId, FetchResponseMsg)>, - should_send: bool, -} - -impl NetworkListener { - pub fn new( - request_builder: RequestBuilder, - pipeline_id: PipelineId, - resource_threads: ResourceThreads, - sender: Sender<(PipelineId, FetchResponseMsg)>, - ) -> NetworkListener { - NetworkListener { - res_init: None, - request_builder, - pipeline_id, - resource_threads, - sender, - should_send: false, - } - } - - pub fn initiate_fetch(&self, cancel_chan: Option>) { - let (ipc_sender, ipc_receiver) = ipc::channel().expect("Failed to create IPC channel!"); - - let mut listener = NetworkListener { - res_init: self.res_init.clone(), - request_builder: self.request_builder.clone(), - resource_threads: self.resource_threads.clone(), - sender: self.sender.clone(), - pipeline_id: self.pipeline_id, - should_send: false, - }; - - let msg = match self.res_init { - Some(ref res_init_) => CoreResourceMsg::FetchRedirect( - self.request_builder.clone(), - res_init_.clone(), - ipc_sender, - None, - ), - None => { - if !listener - .request_builder - .headers - .contains_key(header::ACCEPT) - { - listener - .request_builder - .headers - .insert(header::ACCEPT, DOCUMENT_ACCEPT_HEADER_VALUE); - } - - set_default_accept_language(&mut listener.request_builder.headers); - - CoreResourceMsg::Fetch( - listener.request_builder.clone(), - FetchChannels::ResponseMsg(ipc_sender, cancel_chan), - ) - }, - }; - - ROUTER.add_typed_route( - ipc_receiver, - Box::new(move |message| { - match message { - Ok(FetchResponseMsg::ProcessResponse(request_id, res)) => { - listener.check_redirect(request_id, res) - }, - Ok(msg_) => listener.send(msg_), - Err(e) => warn!("Error while receiving network listener message: {}", e), - }; - }), - ); - - if let Err(e) = self.resource_threads.sender().send(msg) { - warn!("Resource thread unavailable ({})", e); - } - } - - fn check_redirect( - &mut self, - request_id: RequestId, - message: Result, - ) { - match message { - Ok(res_metadata) => { - let metadata = match res_metadata { - FetchMetadata::Filtered { ref unsafe_, .. } => unsafe_, - FetchMetadata::Unfiltered(ref m) => m, - }; - - match metadata.location_url { - // https://html.spec.whatwg.org/multipage/#process-a-navigate-fetch - // Step 7-4. - Some(Ok(ref location_url)) - if matches!(location_url.scheme(), "http" | "https") => - { - if self.request_builder.url_list.is_empty() { - self.request_builder - .url_list - .push(self.request_builder.url.clone()); - } - self.request_builder - .url_list - .push(metadata.final_url.clone()); - - self.request_builder.referrer = metadata - .referrer - .clone() - .map(Referrer::ReferrerUrl) - .unwrap_or(Referrer::NoReferrer); - self.request_builder.referrer_policy = metadata.referrer_policy; - - let headers = if let Some(ref headers) = metadata.headers { - headers.clone().into_inner() - } else { - HeaderMap::new() - }; - - self.res_init = Some(ResponseInit { - url: metadata.final_url.clone(), - location_url: metadata.location_url.clone(), - headers, - referrer: metadata.referrer.clone(), - status_code: metadata - .status - .try_code() - .map(|code| code.as_u16()) - .unwrap_or(200), - }); - - // XXXManishearth we don't have the cancel_chan anymore and - // can't use it here. - // - // Ideally the Fetch code would handle manual redirects on its own - self.initiate_fetch(None); - }, - _ => { - // Response should be processed by script thread. - self.should_send = true; - self.send(FetchResponseMsg::ProcessResponse( - request_id, - Ok(res_metadata), - )); - }, - }; - }, - Err(e) => { - self.should_send = true; - self.send(FetchResponseMsg::ProcessResponse(request_id, Err(e))) - }, - }; - } - - fn send(&mut self, msg: FetchResponseMsg) { - if self.should_send { - if let Err(e) = self.sender.send((self.pipeline_id, msg)) { - warn!( - "Failed to forward network message to pipeline {:?}: {:?}", - self.pipeline_id, e - ); - } - } - } -} diff --git a/components/constellation/tracing.rs b/components/constellation/tracing.rs index c81da81fa89..88df4b49d6b 100644 --- a/components/constellation/tracing.rs +++ b/components/constellation/tracing.rs @@ -150,7 +150,6 @@ mod from_script { }, Self::ScheduleBroadcast(..) => target!("ScheduleBroadcast"), Self::ForwardToEmbedder(msg) => msg.log_target(), - Self::InitiateNavigateRequest(..) => target!("InitiateNavigateRequest"), Self::BroadcastStorageEvent(..) => target!("BroadcastStorageEvent"), Self::ChangeRunningAnimationsState(..) => target!("ChangeRunningAnimationsState"), Self::CreateCanvasPaintThread(..) => target!("CreateCanvasPaintThread"), diff --git a/components/fonts/font_context.rs b/components/fonts/font_context.rs index bba34146945..3604dfbae35 100644 --- a/components/fonts/font_context.rs +++ b/components/fonts/font_context.rs @@ -661,6 +661,7 @@ impl RemoteWebFontDownloader { &core_resource_thread_clone, request, None, + None, Box::new(move |response_message| { match downloader.handle_web_font_fetch_message(response_message) { DownloaderResponseResult::InProcess => {}, diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 00f8bf1e496..17e08b6c4f6 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -28,8 +28,8 @@ use net_traits::request::{ }; use net_traits::response::{Response, ResponseBody, ResponseType}; use net_traits::{ - FetchTaskTarget, NetworkError, ReferrerPolicy, ResourceAttribute, ResourceFetchTiming, - ResourceTimeValue, ResourceTimingType, + set_default_accept_language, FetchTaskTarget, NetworkError, ReferrerPolicy, ResourceAttribute, + ResourceFetchTiming, ResourceTimeValue, ResourceTimingType, }; use rustls_pki_types::CertificateDer; use serde::{Deserialize, Serialize}; @@ -40,10 +40,7 @@ use tokio::sync::mpsc::{UnboundedReceiver as TokioReceiver, UnboundedSender as T use crate::fetch::cors_cache::CorsCache; use crate::fetch::headers::determine_nosniff; use crate::filemanager_thread::FileManager; -use crate::http_loader::{ - determine_requests_referrer, http_fetch, set_default_accept, set_default_accept_language, - HttpState, -}; +use crate::http_loader::{determine_requests_referrer, http_fetch, set_default_accept, HttpState}; use crate::protocols::ProtocolRegistry; use crate::subresource_integrity::is_response_integrity_valid; diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index d795ef12841..5e72fbe11e4 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -56,6 +56,7 @@ use net_traits::response::{HttpsState, Response, ResponseBody, ResponseType}; use net_traits::{ CookieSource, FetchMetadata, NetworkError, RedirectEndValue, RedirectStartValue, ReferrerPolicy, ResourceAttribute, ResourceFetchTiming, ResourceTimeValue, + DOCUMENT_ACCEPT_HEADER_VALUE, }; use servo_arc::Arc; use servo_url::{ImmutableOrigin, ServoUrl}; @@ -77,10 +78,6 @@ use crate::hsts::HstsList; use crate::http_cache::{CacheKey, HttpCache}; use crate::resource_thread::{AuthCache, AuthCacheEntry}; -/// -pub const DOCUMENT_ACCEPT_HEADER_VALUE: HeaderValue = - HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); - /// The various states an entry of the HttpCache can be in. #[derive(Clone, Debug, Eq, PartialEq)] pub enum HttpCacheEntryState { @@ -146,18 +143,6 @@ fn set_default_accept_encoding(headers: &mut HeaderMap) { ); } -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"), - ); -} - /// fn no_referrer_when_downgrade(referrer_url: ServoUrl, current_url: ServoUrl) -> Option { // Step 1 diff --git a/components/script/document_loader.rs b/components/script/document_loader.rs index 25624c62c1e..06e5cfff7ca 100644 --- a/components/script/document_loader.rs +++ b/components/script/document_loader.rs @@ -141,6 +141,7 @@ impl DocumentLoader { fetch_async( &self.resource_threads.core_thread, request, + None, /* response_init */ Some(canceller), callback, ); diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 97d31ade46d..d336843a1f9 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -3262,6 +3262,7 @@ impl GlobalScope { fetch_async( &self.core_resource_thread(), request_builder, + None, cancellation_receiver, network_listener.into_callback(), ); diff --git a/components/script/lib.rs b/components/script/lib.rs index b9c09d33258..7c70825cb39 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -48,6 +48,7 @@ mod mem; #[allow(unsafe_code)] pub(crate) mod messaging; mod microtask; +mod navigation; mod network_listener; #[allow(dead_code)] mod realms; diff --git a/components/script/messaging.rs b/components/script/messaging.rs index d0fa7e7e38e..65429622b69 100644 --- a/components/script/messaging.rs +++ b/components/script/messaging.rs @@ -13,6 +13,7 @@ use crossbeam_channel::{select, Receiver, SendError, Sender}; use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg}; use ipc_channel::ipc::IpcSender; use net_traits::image_cache::PendingImageResponse; +use net_traits::FetchResponseMsg; use profile_traits::mem::{self as profile_mem, OpaqueSender, ReportsChan}; use profile_traits::time::{self as profile_time}; use script_traits::{ConstellationControlMsg, LayoutMsg, Painter, ScriptMsg}; @@ -49,7 +50,6 @@ impl MixedMessage { match self { MixedMessage::FromConstellation(ref inner_msg) => match *inner_msg { ConstellationControlMsg::StopDelayingLoadEventsMode(id) => Some(id), - ConstellationControlMsg::NavigationResponse(id, _) => Some(id), ConstellationControlMsg::AttachLayout(ref new_layout_info) => new_layout_info .parent_info .or(Some(new_layout_info.new_pipeline_id)), @@ -95,6 +95,7 @@ impl MixedMessage { pipeline_id }, MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(_)) => None, + MainThreadScriptMsg::NavigationResponse { pipeline_id, .. } => Some(pipeline_id), MainThreadScriptMsg::WorkletLoaded(pipeline_id) => Some(pipeline_id), MainThreadScriptMsg::RegisterPaintWorklet { pipeline_id, .. } => Some(pipeline_id), MainThreadScriptMsg::Inactive => None, @@ -116,6 +117,10 @@ pub(crate) enum MainThreadScriptMsg { /// Notifies the script thread that a new worklet has been loaded, and thus the page should be /// reflowed. WorkletLoaded(PipelineId), + NavigationResponse { + pipeline_id: PipelineId, + message: Box, + }, /// Notifies the script thread that a new paint worklet has been registered. RegisterPaintWorklet { pipeline_id: PipelineId, diff --git a/components/script/navigation.rs b/components/script/navigation.rs new file mode 100644 index 00000000000..109b079239f --- /dev/null +++ b/components/script/navigation.rs @@ -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, + // 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, +} + +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, + ) -> 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, + cancellation_receiver: Option>, + ) { + 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, + /// The opener, if this is an auxiliary. + #[no_trace] + pub(crate) opener: Option, + /// 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, +} + +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, + opener: Option, + 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 + } +} diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 0ecef0de2d2..f7ff5c14dad 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -63,9 +63,8 @@ use media::WindowGLContext; use metrics::{PaintTimeMetrics, MAX_TASK_NS}; use mime::{self, Mime}; use net_traits::image_cache::{ImageCache, PendingImageResponse}; -use net_traits::request::{ - CredentialsMode, Destination, RedirectMode, RequestBuilder, RequestId, RequestMode, -}; +use net_traits::request::{Referrer, RequestId}; +use net_traits::response::ResponseInit; use net_traits::storage_thread::StorageType; use net_traits::{ FetchMetadata, FetchResponseListener, FetchResponseMsg, Metadata, NetworkError, @@ -140,12 +139,12 @@ use crate::dom::window::Window; use crate::dom::windowproxy::{CreatorBrowsingContextInfo, WindowProxy}; use crate::dom::worklet::WorkletThreadPool; use crate::dom::workletglobalscope::WorkletGlobalScopeInit; -use crate::fetch::FetchCanceller; use crate::messaging::{ CommonScriptMsg, MainThreadScriptMsg, MixedMessage, ScriptEventLoopSender, ScriptThreadReceivers, ScriptThreadSenders, }; use crate::microtask::{Microtask, MicrotaskQueue}; +use crate::navigation::{InProgressLoad, NavigationListener}; use crate::realms::enter_realm; use crate::script_module::ScriptFetchOptions; use crate::script_runtime::{ @@ -181,83 +180,6 @@ pub(crate) unsafe fn trace_thread(tr: *mut JSTracer) { }) } -/// 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)] -struct InProgressLoad { - /// The pipeline which requested this load. - #[no_trace] - pipeline_id: PipelineId, - /// The browsing context being loaded into. - #[no_trace] - browsing_context_id: BrowsingContextId, - /// The top level ancestor browsing context. - #[no_trace] - top_level_browsing_context_id: TopLevelBrowsingContextId, - /// The parent pipeline and frame type associated with this load, if any. - #[no_trace] - parent_info: Option, - /// The opener, if this is an auxiliary. - #[no_trace] - opener: Option, - /// The current window size associated with this pipeline. - #[no_trace] - window_size: WindowSizeData, - /// The activity level of the document (inactive, active or fully active). - #[no_trace] - activity: DocumentActivity, - /// Window is throttled, running timers at a heavily limited rate. - throttled: bool, - /// The requested URL of the load. - #[no_trace] - url: ServoUrl, - /// The origin for the document - #[no_trace] - origin: MutableOrigin, - /// Timestamp reporting the time when the browser started this load. - #[no_trace] - navigation_start: CrossProcessInstant, - /// For cancelling the fetch - canceller: FetchCanceller, - /// If inheriting the security context - inherited_secure_context: Option, -} - -impl InProgressLoad { - /// Create a new InProgressLoad object. - #[allow(clippy::too_many_arguments)] - fn new( - id: PipelineId, - browsing_context_id: BrowsingContextId, - top_level_browsing_context_id: TopLevelBrowsingContextId, - parent_info: Option, - opener: Option, - window_size: WindowSizeData, - url: ServoUrl, - origin: MutableOrigin, - inherited_secure_context: Option, - ) -> InProgressLoad { - let navigation_start = CrossProcessInstant::now(); - InProgressLoad { - pipeline_id: id, - browsing_context_id, - top_level_browsing_context_id, - parent_info, - opener, - window_size, - activity: DocumentActivity::FullyActive, - throttled: false, - url, - origin, - navigation_start, - canceller: Default::default(), - inherited_secure_context, - } - } -} - // We borrow the incomplete parser contexts mutably during parsing, // which is fine except that parsing can trigger evaluation, // which can trigger GC, and so we can end up tracing the script @@ -493,7 +415,6 @@ impl ScriptThreadFactory for ScriptThread { let top_level_browsing_context_id = state.top_level_browsing_context_id; let parent_info = state.parent_info; let opener = state.opener; - let secure = load_data.inherited_secure_context; let memory_profiler_sender = state.memory_profiler_sender.clone(); let window_size = state.window_size; @@ -507,18 +428,16 @@ impl ScriptThreadFactory for ScriptThread { let mut failsafe = ScriptMemoryFailsafe::new(&script_thread); let origin = MutableOrigin::new(load_data.url.origin()); - let new_load = InProgressLoad::new( + script_thread.pre_page_load(InProgressLoad::new( id, browsing_context_id, top_level_browsing_context_id, parent_info, opener, window_size, - load_data.url.clone(), origin, - secure, - ); - script_thread.pre_page_load(new_load, load_data); + load_data, + )); let reporter_name = format!("script-reporter-{:?}", id); memory_profiler_sender.run_with_memory_reporting( @@ -1822,20 +1741,6 @@ impl ScriptThread { ConstellationControlMsg::StopDelayingLoadEventsMode(pipeline_id) => { self.handle_stop_delaying_load_events_mode(pipeline_id) }, - ConstellationControlMsg::NavigationResponse(pipeline_id, fetch_data) => { - match fetch_data { - FetchResponseMsg::ProcessResponse(request_id, metadata) => { - self.handle_fetch_metadata(pipeline_id, request_id, metadata) - }, - FetchResponseMsg::ProcessResponseChunk(request_id, chunk) => { - self.handle_fetch_chunk(pipeline_id, request_id, chunk) - }, - FetchResponseMsg::ProcessResponseEOF(request_id, eof) => { - self.handle_fetch_eof(pipeline_id, request_id, eof) - }, - _ => unreachable!(), - }; - }, ConstellationControlMsg::NavigateIframe( parent_pipeline_id, browsing_context_id, @@ -2079,6 +1984,12 @@ impl ScriptThread { MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(chan)) => { self.collect_reports(chan) }, + MainThreadScriptMsg::NavigationResponse { + pipeline_id, + message, + } => { + self.handle_navigation_response(pipeline_id, *message); + }, MainThreadScriptMsg::WorkletLoaded(pipeline_id) => { self.handle_worklet_loaded(pipeline_id) }, @@ -2484,6 +2395,7 @@ impl ScriptThread { } = new_layout_info; // Kick off the fetch for the new resource. + let url = load_data.url.clone(); let new_load = InProgressLoad::new( new_pipeline_id, browsing_context_id, @@ -2491,16 +2403,15 @@ impl ScriptThread { parent_info, opener, window_size, - load_data.url.clone(), origin, - load_data.inherited_secure_context, + load_data, ); - if load_data.url.as_str() == "about:blank" { - self.start_page_load_about_blank(new_load, load_data); - } else if load_data.url.as_str() == "about:srcdoc" { - self.page_load_about_srcdoc(new_load, load_data); + if url.as_str() == "about:blank" { + self.start_page_load_about_blank(new_load); + } else if url.as_str() == "about:srcdoc" { + self.page_load_about_srcdoc(new_load); } else { - self.pre_page_load(new_load, load_data); + self.pre_page_load(new_load); } } @@ -3118,7 +3029,7 @@ impl ScriptThread { } debug!( "ScriptThread: loading {} on pipeline {:?}", - incomplete.url, incomplete.pipeline_id + incomplete.load_data.url, incomplete.pipeline_id ); let origin = if final_url.as_str() == "about:blank" || final_url.as_str() == "about:srcdoc" @@ -3201,7 +3112,7 @@ impl ScriptThread { self.player_context.clone(), #[cfg(feature = "webgpu")] self.gpu_id_hub.clone(), - incomplete.inherited_secure_context, + incomplete.load_data.inherited_secure_context, ); let _realm = enter_realm(&*window); @@ -3508,42 +3419,48 @@ impl ScriptThread { /// Instructs the constellation to fetch the document that will be loaded. Stores the InProgressLoad /// argument until a notification is received that the fetch is complete. - fn pre_page_load(&self, mut incomplete: InProgressLoad, load_data: LoadData) { - let id = incomplete.pipeline_id; - let top_level_browsing_context_id = incomplete.top_level_browsing_context_id; - let req_init = RequestBuilder::new(load_data.url.clone(), load_data.referrer) - .method(load_data.method) - .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(load_data.referrer_policy) - .headers(load_data.headers) - .body(load_data.data) - .redirect_mode(RedirectMode::Manual) - .origin(incomplete.origin.immutable().clone()) - .crash(load_data.crash); - - let context = ParserContext::new(id, load_data.url); + fn pre_page_load(&self, mut incomplete: InProgressLoad) { + let context = ParserContext::new(incomplete.pipeline_id, incomplete.load_data.url.clone()); self.incomplete_parser_contexts .0 .borrow_mut() - .push((id, context)); + .push((incomplete.pipeline_id, context)); - let cancel_chan = incomplete.canceller.initialize(); + let cancellation_receiver = incomplete.canceller.initialize(); + NavigationListener::new( + incomplete.request_builder(), + self.senders.self_sender.clone(), + ) + .initiate_fetch( + &self.resource_threads.core_thread, + None, + Some(cancellation_receiver), + ); - self.senders - .pipeline_to_constellation_sender - .send(( - id, - ScriptMsg::InitiateNavigateRequest(req_init, cancel_chan), - )) - .unwrap(); self.incomplete_loads.borrow_mut().push(incomplete); } + fn handle_navigation_response(&self, pipeline_id: PipelineId, message: FetchResponseMsg) { + if let Some(metadata) = NavigationListener::http_redirect_metadata(&message) { + self.handle_navigation_redirect(pipeline_id, metadata); + return; + }; + + match message { + FetchResponseMsg::ProcessResponse(request_id, metadata) => { + self.handle_fetch_metadata(pipeline_id, request_id, metadata) + }, + FetchResponseMsg::ProcessResponseChunk(request_id, chunk) => { + self.handle_fetch_chunk(pipeline_id, request_id, chunk) + }, + FetchResponseMsg::ProcessResponseEOF(request_id, eof) => { + self.handle_fetch_eof(pipeline_id, request_id, eof) + }, + FetchResponseMsg::ProcessRequestBody(..) => {}, + FetchResponseMsg::ProcessRequestEOF(..) => {}, + } + } + fn handle_fetch_metadata( &self, id: PipelineId, @@ -3596,24 +3513,74 @@ impl ScriptThread { } } + fn handle_navigation_redirect(&self, id: PipelineId, metadata: &Metadata) { + // TODO(mrobinson): This tries to accomplish some steps from + // , but it's + // very out of sync with the specification. + assert!(metadata.location_url.is_some()); + + let mut incomplete_loads = self.incomplete_loads.borrow_mut(); + let Some(incomplete_load) = incomplete_loads + .iter_mut() + .find(|incomplete_load| incomplete_load.pipeline_id == id) + else { + return; + }; + + // Update the `url_list` of the incomplete load to track all redirects. This will be reflected + // in the new `RequestBuilder` as well. + incomplete_load.url_list.push(metadata.final_url.clone()); + + let mut request_builder = incomplete_load.request_builder(); + request_builder.referrer = metadata + .referrer + .clone() + .map(Referrer::ReferrerUrl) + .unwrap_or(Referrer::NoReferrer); + request_builder.referrer_policy = metadata.referrer_policy; + + let headers = metadata + .headers + .as_ref() + .map(|headers| headers.clone().into_inner()) + .unwrap_or_default(); + + let response_init = Some(ResponseInit { + url: metadata.final_url.clone(), + location_url: metadata.location_url.clone(), + headers, + referrer: metadata.referrer.clone(), + status_code: metadata + .status + .try_code() + .map(|code| code.as_u16()) + .unwrap_or(200), + }); + + let cancellation_receiver = incomplete_load.canceller.initialize(); + NavigationListener::new(request_builder, self.senders.self_sender.clone()).initiate_fetch( + &self.resource_threads.core_thread, + response_init, + Some(cancellation_receiver), + ); + } + /// Synchronously fetch `about:blank`. Stores the `InProgressLoad` /// argument until a notification is received that the fetch is complete. - fn start_page_load_about_blank(&self, incomplete: InProgressLoad, load_data: LoadData) { + fn start_page_load_about_blank(&self, mut incomplete: InProgressLoad) { let id = incomplete.pipeline_id; - self.incomplete_loads.borrow_mut().push(incomplete); - let url = ServoUrl::parse("about:blank").unwrap(); let mut context = ParserContext::new(id, url.clone()); let mut meta = Metadata::default(url); meta.set_content_type(Some(&mime::TEXT_HTML)); - meta.set_referrer_policy(load_data.referrer_policy); + meta.set_referrer_policy(incomplete.load_data.referrer_policy); // If this page load is the result of a javascript scheme url, map // the evaluation result into a response. - let chunk = match load_data.js_eval_result { - Some(JsEvalResult::Ok(content)) => content, + let chunk = match incomplete.load_data.js_eval_result { + Some(JsEvalResult::Ok(ref mut content)) => std::mem::take(content), Some(JsEvalResult::NoContent) => { meta.status = http::StatusCode::NO_CONTENT.into(); vec![] @@ -3621,6 +3588,8 @@ impl ScriptThread { None => vec![], }; + self.incomplete_loads.borrow_mut().push(incomplete); + let dummy_request_id = RequestId::next(); context.process_response(dummy_request_id, Ok(FetchMetadata::Unfiltered(meta))); context.process_response_chunk(dummy_request_id, chunk); @@ -3631,20 +3600,20 @@ impl ScriptThread { } /// Synchronously parse a srcdoc document from a giving HTML string. - fn page_load_about_srcdoc(&self, incomplete: InProgressLoad, load_data: LoadData) { + fn page_load_about_srcdoc(&self, mut incomplete: InProgressLoad) { let id = incomplete.pipeline_id; + let url = ServoUrl::parse("about:srcdoc").unwrap(); + let mut meta = Metadata::default(url.clone()); + meta.set_content_type(Some(&mime::TEXT_HTML)); + meta.set_referrer_policy(incomplete.load_data.referrer_policy); + + let srcdoc = std::mem::take(&mut incomplete.load_data.srcdoc); + let chunk = srcdoc.into_bytes(); + self.incomplete_loads.borrow_mut().push(incomplete); - let url = ServoUrl::parse("about:srcdoc").unwrap(); - let mut context = ParserContext::new(id, url.clone()); - - let mut meta = Metadata::default(url); - meta.set_content_type(Some(&mime::TEXT_HTML)); - meta.set_referrer_policy(load_data.referrer_policy); - - let chunk = load_data.srcdoc.into_bytes(); - + let mut context = ParserContext::new(id, url); let dummy_request_id = RequestId::next(); context.process_response(dummy_request_id, Ok(FetchMetadata::Unfiltered(meta))); context.process_response_chunk(dummy_request_id, chunk); diff --git a/components/shared/net/lib.rs b/components/shared/net/lib.rs index 5b3c9d1f0e8..99be7b007f7 100644 --- a/components/shared/net/lib.rs +++ b/components/shared/net/lib.rs @@ -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; +/// +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, /* cancel_chan */ Option>, /* 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> = 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, canceller: Option>, callback: BoxedFetchCallback, ) { - static FETCH_THREAD: OnceLock> = 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 = LazyLock::new(|| servo_rand::ServoRng::default().next_u32()); diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index 13f66dd13c6..e6901be54b6 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -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. /// 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", diff --git a/components/shared/script/script_msg.rs b/components/shared/script/script_msg.rs index c391267643a..e5d3c60b4b5 100644 --- a/components/shared/script/script_msg.rs +++ b/components/shared/script/script_msg.rs @@ -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",