diff options
-rw-r--r-- | components/constellation/constellation.rs | 70 | ||||
-rw-r--r-- | components/constellation/lib.rs | 3 | ||||
-rw-r--r-- | components/constellation/pipeline.rs | 150 | ||||
-rw-r--r-- | components/constellation/sandboxing.rs | 202 | ||||
-rw-r--r-- | components/constellation/serviceworker.rs | 56 | ||||
-rw-r--r-- | components/script/init.rs | 7 | ||||
-rw-r--r-- | components/script/lib.rs | 4 | ||||
-rw-r--r-- | components/script/serviceworker_manager.rs | 64 | ||||
-rw-r--r-- | components/script_traits/lib.rs | 7 | ||||
-rw-r--r-- | components/script_traits/script_msg.rs | 15 | ||||
-rw-r--r-- | components/servo/lib.rs | 72 |
11 files changed, 405 insertions, 245 deletions
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 25ba57b04df..265db68e7ae 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -96,6 +96,7 @@ use crate::browsingcontext::{ use crate::event_loop::EventLoop; use crate::network_listener::NetworkListener; use crate::pipeline::{InitialPipelineState, Pipeline}; +use crate::serviceworker::ServiceWorkerUnprivilegedContent; use crate::session_history::{ JointSessionHistory, NeedsToReload, SessionHistoryChange, SessionHistoryDiff, }; @@ -151,10 +152,15 @@ use script_traits::{HistoryEntryReplacement, IFrameSizeMsg, WindowSizeData, Wind use script_traits::{ IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerSchedulerMsg, }; -use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory}; +use script_traits::{ + LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory, + ServiceWorkerManagerFactory, +}; use script_traits::{MediaSessionActionType, MouseEventType}; use script_traits::{MessagePortMsg, PortMessageTask, StructuredSerializedData}; -use script_traits::{SWManagerMsg, ScopeThings, UpdatePipelineIdReason, WebDriverCommandMsg}; +use script_traits::{ + SWManagerMsg, SWManagerSenders, ScopeThings, UpdatePipelineIdReason, WebDriverCommandMsg, +}; use serde::{Deserialize, Serialize}; use servo_config::{opts, pref}; use servo_rand::{random, Rng, ServoRng, SliceRandom}; @@ -259,7 +265,7 @@ struct BrowsingContextGroup { /// `LayoutThread` in the `layout` crate, and `ScriptThread` in /// the `script` crate). Script and layout communicate using a `Message` /// type. -pub struct Constellation<Message, LTF, STF> { +pub struct Constellation<Message, LTF, STF, SWF> { /// An ipc-sender/threaded-receiver pair /// to facilitate installing pipeline namespaces in threads /// via a per-process installer. @@ -348,9 +354,8 @@ pub struct Constellation<Message, LTF, STF> { /// bluetooth thread. bluetooth_thread: IpcSender<BluetoothRequest>, - /// An IPC channel for the constellation to send messages to the - /// Service Worker Manager thread. - swmanager_chan: Option<IpcSender<ServiceWorkerMsg>>, + /// A map of origin to sender to a Service worker manager. + sw_managers: HashMap<ImmutableOrigin, IpcSender<ServiceWorkerMsg>>, /// An IPC channel for Service Worker Manager threads to send /// messages to the constellation. This is the SW Manager thread's @@ -453,7 +458,7 @@ pub struct Constellation<Message, LTF, STF> { random_pipeline_closure: Option<(ServoRng, f32)>, /// Phantom data that keeps the Rust type system happy. - phantom: PhantomData<(Message, LTF, STF)>, + phantom: PhantomData<(Message, LTF, STF, SWF)>, /// Entry point to create and get channels to a WebGLThread. webgl_threads: Option<WebGLThreads>, @@ -813,10 +818,11 @@ fn handle_webrender_message( } } -impl<Message, LTF, STF> Constellation<Message, LTF, STF> +impl<Message, LTF, STF, SWF> Constellation<Message, LTF, STF, SWF> where LTF: LayoutThreadFactory<Message = Message>, STF: ScriptThreadFactory<Message = Message>, + SWF: ServiceWorkerManagerFactory, { /// Create a new constellation thread. pub fn start( @@ -829,12 +835,11 @@ where enable_canvas_antialiasing: bool, canvas_chan: Sender<ConstellationCanvasMsg>, ipc_canvas_chan: IpcSender<CanvasMsg>, - ) -> (Sender<FromCompositorMsg>, IpcSender<SWManagerMsg>) { + ) -> Sender<FromCompositorMsg> { let (compositor_sender, compositor_receiver) = unbounded(); // service worker manager to communicate with constellation let (swmanager_sender, swmanager_receiver) = ipc::channel().expect("ipc channel failure"); - let sw_mgr_clone = swmanager_sender.clone(); thread::Builder::new() .name("Constellation".to_owned()) @@ -937,7 +942,7 @@ where }), ); - let mut constellation: Constellation<Message, LTF, STF> = Constellation { + let mut constellation: Constellation<Message, LTF, STF, SWF> = Constellation { namespace_receiver, namespace_sender, script_sender: ipc_script_sender, @@ -961,9 +966,9 @@ where public_resource_threads: state.public_resource_threads, private_resource_threads: state.private_resource_threads, font_cache_thread: state.font_cache_thread, - swmanager_chan: None, + sw_managers: Default::default(), swmanager_receiver: swmanager_receiver, - swmanager_sender: sw_mgr_clone, + swmanager_sender, browsing_context_group_set: Default::default(), browsing_context_group_next_id: Default::default(), message_ports: HashMap::new(), @@ -1022,7 +1027,7 @@ where }) .expect("Thread spawning failed"); - (compositor_sender, swmanager_sender) + compositor_sender } /// The main event loop for the constellation. @@ -1530,9 +1535,9 @@ where fn handle_request_from_swmanager(&mut self, message: SWManagerMsg) { match message { - SWManagerMsg::OwnSender(sw_sender) => { - // store service worker manager for communicating with it. - self.swmanager_chan = Some(sw_sender); + SWManagerMsg::PostMessageToClient => { + // TODO: implement posting a message to a SW client. + // https://github.com/servo/servo/issues/24660 }, } } @@ -1965,7 +1970,7 @@ where self.handle_register_serviceworker(scope_things, scope); }, FromScriptMsg::ForwardDOMMessage(msg_vec, scope_url) => { - if let Some(ref mgr) = self.swmanager_chan { + if let Some(mgr) = self.sw_managers.get(&scope_url.origin()) { let _ = mgr.send(ServiceWorkerMsg::ForwardDOMMessage(msg_vec, scope_url)); } else { warn!("Unable to forward DOMMessage for postMessage call"); @@ -2621,11 +2626,32 @@ where } } - fn handle_register_serviceworker(&self, scope_things: ScopeThings, scope: ServoUrl) { - if let Some(ref mgr) = self.swmanager_chan { + fn handle_register_serviceworker(&mut self, scope_things: ScopeThings, scope: ServoUrl) { + let origin = scope.origin(); + + if let Some(mgr) = self.sw_managers.get(&origin) { let _ = mgr.send(ServiceWorkerMsg::RegisterServiceWorker(scope_things, scope)); } else { - warn!("sending scope info to service worker manager failed"); + let (own_sender, receiver) = ipc::channel().expect("Failed to create IPC channel!"); + + let sw_senders = SWManagerSenders { + swmanager_sender: self.swmanager_sender.clone(), + resource_sender: self.public_resource_threads.sender(), + own_sender: own_sender.clone(), + receiver, + }; + let content = ServiceWorkerUnprivilegedContent::new(sw_senders, origin.clone()); + + if opts::multiprocess() { + if content.spawn_multiprocess().is_err() { + return warn!("Failed to spawn process for SW manager."); + } + } else { + content.start::<SWF>(); + } + + let _ = own_sender.send(ServiceWorkerMsg::RegisterServiceWorker(scope_things, scope)); + self.sw_managers.insert(origin, own_sender); } } @@ -2763,7 +2789,7 @@ where } debug!("Exiting service worker manager thread."); - if let Some(mgr) = self.swmanager_chan.as_ref() { + for (_, mgr) in self.sw_managers.drain() { if let Err(e) = mgr.send(ServiceWorkerMsg::Exit) { warn!("Exit service worker manager failed ({})", e); } diff --git a/components/constellation/lib.rs b/components/constellation/lib.rs index b102a0bc2b5..0e28b880cce 100644 --- a/components/constellation/lib.rs +++ b/components/constellation/lib.rs @@ -24,6 +24,7 @@ mod pipeline; not(target_arch = "aarch64") ))] mod sandboxing; +mod serviceworker; mod session_history; mod timer_scheduler; @@ -38,4 +39,4 @@ pub use crate::pipeline::UnprivilegedPipelineContent; not(target_arch = "arm"), not(target_arch = "aarch64") ))] -pub use crate::sandboxing::content_process_sandbox_profile; +pub use crate::sandboxing::{content_process_sandbox_profile, UnprivilegedContent}; diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index 665ea177e79..16cb67ab9a2 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::event_loop::EventLoop; +use crate::sandboxing::{spawn_multiprocess, UnprivilegedContent}; use background_hang_monitor::HangMonitorRegister; use bluetooth_traits::BluetoothRequest; use canvas_traits::webgl::WebGLPipeline; @@ -27,7 +28,7 @@ use msg::constellation_msg::{ }; use net::image_cache::ImageCacheImpl; use net_traits::image_cache::ImageCache; -use net_traits::{IpcSend, ResourceThreads}; +use net_traits::ResourceThreads; use profile_traits::mem as profile_mem; use profile_traits::time; use script_traits::{ @@ -35,16 +36,12 @@ use script_traits::{ }; use script_traits::{DocumentActivity, InitialScriptState}; use script_traits::{LayoutControlMsg, LayoutMsg, LoadData}; -use script_traits::{NewLayoutInfo, SWManagerMsg, SWManagerSenders}; +use script_traits::{NewLayoutInfo, SWManagerMsg}; use script_traits::{ScriptThreadFactory, TimerSchedulerMsg, WindowSizeData}; use servo_config::opts::{self, Opts}; use servo_config::{prefs, prefs::PrefValue}; use servo_url::ServoUrl; use std::collections::{HashMap, HashSet}; -#[cfg(not(windows))] -use std::env; -use std::ffi::OsStr; -use std::process; use std::rc::Rc; use std::sync::atomic::AtomicBool; use std::sync::Arc; @@ -632,109 +629,8 @@ impl UnprivilegedPipelineContent { } } - #[cfg(any( - target_os = "android", - target_arch = "arm", - all(target_arch = "aarch64", not(target_os = "windows")) - ))] pub fn spawn_multiprocess(self) -> Result<(), Error> { - use ipc_channel::ipc::IpcOneShotServer; - // Note that this function can panic, due to process creation, - // avoiding this panic would require a mechanism for dealing - // with low-resource scenarios. - let (server, token) = IpcOneShotServer::<IpcSender<UnprivilegedPipelineContent>>::new() - .expect("Failed to create IPC one-shot server."); - - let path_to_self = env::current_exe().expect("Failed to get current executor."); - let mut child_process = process::Command::new(path_to_self); - self.setup_common(&mut child_process, token); - let _ = child_process - .spawn() - .expect("Failed to start unsandboxed child process!"); - - let (_receiver, sender) = server.accept().expect("Server failed to accept."); - sender.send(self)?; - - Ok(()) - } - - #[cfg(all( - not(target_os = "windows"), - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "arm"), - not(target_arch = "aarch64") - ))] - pub fn spawn_multiprocess(self) -> Result<(), Error> { - use crate::sandboxing::content_process_sandbox_profile; - use gaol::sandbox::{self, Sandbox, SandboxMethods}; - use ipc_channel::ipc::IpcOneShotServer; - - impl CommandMethods for sandbox::Command { - fn arg<T>(&mut self, arg: T) - where - T: AsRef<OsStr>, - { - self.arg(arg); - } - - fn env<T, U>(&mut self, key: T, val: U) - where - T: AsRef<OsStr>, - U: AsRef<OsStr>, - { - self.env(key, val); - } - } - - // Note that this function can panic, due to process creation, - // avoiding this panic would require a mechanism for dealing - // with low-resource scenarios. - let (server, token) = IpcOneShotServer::<IpcSender<UnprivilegedPipelineContent>>::new() - .expect("Failed to create IPC one-shot server."); - - // If there is a sandbox, use the `gaol` API to create the child process. - if self.opts.sandbox { - let mut command = sandbox::Command::me().expect("Failed to get current sandbox."); - self.setup_common(&mut command, token); - - let profile = content_process_sandbox_profile(); - let _ = Sandbox::new(profile) - .start(&mut command) - .expect("Failed to start sandboxed child process!"); - } else { - let path_to_self = env::current_exe().expect("Failed to get current executor."); - let mut child_process = process::Command::new(path_to_self); - self.setup_common(&mut child_process, token); - let _ = child_process - .spawn() - .expect("Failed to start unsandboxed child process!"); - } - - let (_receiver, sender) = server.accept().expect("Server failed to accept."); - sender.send(self)?; - - Ok(()) - } - - #[cfg(any(target_os = "windows", target_os = "ios"))] - pub fn spawn_multiprocess(self) -> Result<(), Error> { - error!("Multiprocess is not supported on Windows or iOS."); - process::exit(1); - } - - #[cfg(not(windows))] - fn setup_common<C: CommandMethods>(&self, command: &mut C, token: String) { - C::arg(command, "--content-process"); - C::arg(command, token); - - if let Ok(value) = env::var("RUST_BACKTRACE") { - C::env(command, "RUST_BACKTRACE", value); - } - - if let Ok(value) = env::var("RUST_LOG") { - C::env(command, "RUST_LOG", value); - } + spawn_multiprocess(UnprivilegedContent::Pipeline(self)) } pub fn register_with_background_hang_monitor( @@ -763,42 +659,4 @@ impl UnprivilegedPipelineContent { pub fn prefs(&self) -> HashMap<String, PrefValue> { self.prefs.clone() } - - pub fn swmanager_senders(&self) -> SWManagerSenders { - SWManagerSenders { - swmanager_sender: self.swmanager_thread.clone(), - resource_sender: self.resource_threads.sender(), - } - } -} - -/// A trait to unify commands launched as multiprocess with or without a sandbox. -trait CommandMethods { - /// A command line argument. - fn arg<T>(&mut self, arg: T) - where - T: AsRef<OsStr>; - - /// An environment variable. - fn env<T, U>(&mut self, key: T, val: U) - where - T: AsRef<OsStr>, - U: AsRef<OsStr>; -} - -impl CommandMethods for process::Command { - fn arg<T>(&mut self, arg: T) - where - T: AsRef<OsStr>, - { - self.arg(arg); - } - - fn env<T, U>(&mut self, key: T, val: U) - where - T: AsRef<OsStr>, - U: AsRef<OsStr>, - { - self.env(key, val); - } } diff --git a/components/constellation/sandboxing.rs b/components/constellation/sandboxing.rs index 4b6df8326fc..9905253965b 100644 --- a/components/constellation/sandboxing.rs +++ b/components/constellation/sandboxing.rs @@ -2,14 +2,53 @@ * 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/. */ -use embedder_traits::resources; +use crate::pipeline::UnprivilegedPipelineContent; +use crate::serviceworker::ServiceWorkerUnprivilegedContent; +#[cfg(all( + not(target_os = "windows"), + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "arm"), + not(target_arch = "aarch64") +))] use gaol::profile::{Operation, PathPattern, Profile}; -use std::path::PathBuf; +use ipc_channel::Error; +use servo_config::opts::Opts; +use servo_config::prefs::PrefValue; +use std::collections::HashMap; +#[cfg(not(windows))] +use std::env; +use std::ffi::OsStr; +use std::process; + +#[derive(Deserialize, Serialize)] +pub enum UnprivilegedContent { + Pipeline(UnprivilegedPipelineContent), + ServiceWorker(ServiceWorkerUnprivilegedContent), +} + +impl UnprivilegedContent { + pub fn opts(&self) -> Opts { + match self { + UnprivilegedContent::Pipeline(content) => content.opts(), + UnprivilegedContent::ServiceWorker(content) => content.opts(), + } + } + + pub fn prefs(&self) -> HashMap<String, PrefValue> { + match self { + UnprivilegedContent::Pipeline(content) => content.prefs(), + UnprivilegedContent::ServiceWorker(content) => content.prefs(), + } + } +} /// Our content process sandbox profile on Mac. As restrictive as possible. #[cfg(target_os = "macos")] pub fn content_process_sandbox_profile() -> Profile { + use embedder_traits::resources; use gaol::platform; + use std::path::PathBuf; let mut operations = vec![ Operation::FileReadAll(PathPattern::Literal(PathBuf::from("/dev/urandom"))), @@ -46,8 +85,18 @@ pub fn content_process_sandbox_profile() -> Profile { } /// Our content process sandbox profile on Linux. As restrictive as possible. -#[cfg(not(target_os = "macos"))] +#[cfg(all( + not(target_os = "macos"), + not(target_os = "windows"), + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "arm"), + not(target_arch = "aarch64") +))] pub fn content_process_sandbox_profile() -> Profile { + use embedder_traits::resources; + use std::path::PathBuf; + let mut operations = vec![Operation::FileReadAll(PathPattern::Literal(PathBuf::from( "/dev/urandom", )))]; @@ -65,3 +114,150 @@ pub fn content_process_sandbox_profile() -> Profile { Profile::new(operations).expect("Failed to create sandbox profile!") } + +#[cfg(any( + target_os = "windows", + target_os = "ios", + target_os = "android", + target_arch = "arm", + target_arch = "aarch64", +))] +pub fn content_process_sandbox_profile() { + error!("Sandboxed multiprocess is not supported on this platform."); + process::exit(1); +} + +#[cfg(any( + target_os = "android", + target_arch = "arm", + all(target_arch = "aarch64", not(target_os = "windows")) +))] +pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<(), Error> { + use ipc_channel::ipc::{IpcOneShotServer, IpcSender}; + // Note that this function can panic, due to process creation, + // avoiding this panic would require a mechanism for dealing + // with low-resource scenarios. + let (server, token) = IpcOneShotServer::<IpcSender<UnprivilegedContent>>::new() + .expect("Failed to create IPC one-shot server."); + + let path_to_self = env::current_exe().expect("Failed to get current executor."); + let mut child_process = process::Command::new(path_to_self); + setup_common(&mut child_process, token); + let _ = child_process + .spawn() + .expect("Failed to start unsandboxed child process!"); + + let (_receiver, sender) = server.accept().expect("Server failed to accept."); + sender.send(content)?; + + Ok(()) +} + +#[cfg(all( + not(target_os = "windows"), + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "arm"), + not(target_arch = "aarch64") +))] +pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<(), Error> { + use gaol::sandbox::{self, Sandbox, SandboxMethods}; + use ipc_channel::ipc::{IpcOneShotServer, IpcSender}; + + impl CommandMethods for sandbox::Command { + fn arg<T>(&mut self, arg: T) + where + T: AsRef<OsStr>, + { + self.arg(arg); + } + + fn env<T, U>(&mut self, key: T, val: U) + where + T: AsRef<OsStr>, + U: AsRef<OsStr>, + { + self.env(key, val); + } + } + + // Note that this function can panic, due to process creation, + // avoiding this panic would require a mechanism for dealing + // with low-resource scenarios. + let (server, token) = IpcOneShotServer::<IpcSender<UnprivilegedContent>>::new() + .expect("Failed to create IPC one-shot server."); + + // If there is a sandbox, use the `gaol` API to create the child process. + if content.opts().sandbox { + let mut command = sandbox::Command::me().expect("Failed to get current sandbox."); + setup_common(&mut command, token); + + let profile = content_process_sandbox_profile(); + let _ = Sandbox::new(profile) + .start(&mut command) + .expect("Failed to start sandboxed child process!"); + } else { + let path_to_self = env::current_exe().expect("Failed to get current executor."); + let mut child_process = process::Command::new(path_to_self); + setup_common(&mut child_process, token); + let _ = child_process + .spawn() + .expect("Failed to start unsandboxed child process!"); + } + + let (_receiver, sender) = server.accept().expect("Server failed to accept."); + sender.send(content)?; + + Ok(()) +} + +#[cfg(any(target_os = "windows", target_os = "ios"))] +pub fn spawn_multiprocess(_content: UnprivilegedContent) -> Result<(), Error> { + error!("Multiprocess is not supported on Windows or iOS."); + process::exit(1); +} + +#[cfg(not(windows))] +fn setup_common<C: CommandMethods>(command: &mut C, token: String) { + C::arg(command, "--content-process"); + C::arg(command, token); + + if let Ok(value) = env::var("RUST_BACKTRACE") { + C::env(command, "RUST_BACKTRACE", value); + } + + if let Ok(value) = env::var("RUST_LOG") { + C::env(command, "RUST_LOG", value); + } +} + +/// A trait to unify commands launched as multiprocess with or without a sandbox. +trait CommandMethods { + /// A command line argument. + fn arg<T>(&mut self, arg: T) + where + T: AsRef<OsStr>; + + /// An environment variable. + fn env<T, U>(&mut self, key: T, val: U) + where + T: AsRef<OsStr>, + U: AsRef<OsStr>; +} + +impl CommandMethods for process::Command { + fn arg<T>(&mut self, arg: T) + where + T: AsRef<OsStr>, + { + self.arg(arg); + } + + fn env<T, U>(&mut self, key: T, val: U) + where + T: AsRef<OsStr>, + U: AsRef<OsStr>, + { + self.env(key, val); + } +} diff --git a/components/constellation/serviceworker.rs b/components/constellation/serviceworker.rs new file mode 100644 index 00000000000..95bb8571b3a --- /dev/null +++ b/components/constellation/serviceworker.rs @@ -0,0 +1,56 @@ +/* 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/. */ + +use crate::sandboxing::{spawn_multiprocess, UnprivilegedContent}; +use ipc_channel::Error; +use script_traits::{SWManagerSenders, ServiceWorkerManagerFactory}; +use servo_config::opts::{self, Opts}; +use servo_config::prefs::{self, PrefValue}; +use servo_url::ImmutableOrigin; +use std::collections::HashMap; + +/// Conceptually, this is glue to start an agent-cluster for a service worker agent. +/// <https://html.spec.whatwg.org/multipage/#obtain-a-service-worker-agent> +#[derive(Deserialize, Serialize)] +pub struct ServiceWorkerUnprivilegedContent { + opts: Opts, + prefs: HashMap<String, PrefValue>, + senders: SWManagerSenders, + origin: ImmutableOrigin, +} + +impl ServiceWorkerUnprivilegedContent { + pub fn new( + senders: SWManagerSenders, + origin: ImmutableOrigin, + ) -> ServiceWorkerUnprivilegedContent { + ServiceWorkerUnprivilegedContent { + opts: (*opts::get()).clone(), + prefs: prefs::pref_map().iter().collect(), + senders, + origin, + } + } + + /// Start the agent-cluster. + pub fn start<SWF>(self) + where + SWF: ServiceWorkerManagerFactory, + { + SWF::create(self.senders, self.origin); + } + + /// Start the agent-cluster in it's own process. + pub fn spawn_multiprocess(self) -> Result<(), Error> { + spawn_multiprocess(UnprivilegedContent::ServiceWorker(self)) + } + + pub fn opts(&self) -> Opts { + self.opts.clone() + } + + pub fn prefs(&self) -> HashMap<String, PrefValue> { + self.prefs.clone() + } +} diff --git a/components/script/init.rs b/components/script/init.rs index 30e01dda74e..823f045fad4 100644 --- a/components/script/init.rs +++ b/components/script/init.rs @@ -5,8 +5,6 @@ use crate::dom::bindings::codegen::RegisterBindings; use crate::dom::bindings::proxyhandler; use crate::script_runtime::JSEngineSetup; -use crate::serviceworker_manager::ServiceWorkerManager; -use script_traits::SWManagerSenders; #[cfg(target_os = "linux")] #[allow(unsafe_code)] @@ -51,11 +49,6 @@ fn perform_platform_specific_initialization() { #[cfg(not(target_os = "linux"))] fn perform_platform_specific_initialization() {} -pub fn init_service_workers(sw_senders: SWManagerSenders) { - // Spawn the service worker manager passing the constellation sender - ServiceWorkerManager::spawn_manager(sw_senders); -} - #[allow(unsafe_code)] pub fn init() -> JSEngineSetup { unsafe { diff --git a/components/script/lib.rs b/components/script/lib.rs index 13791748931..07090f4716b 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -91,7 +91,7 @@ pub mod script_runtime; #[allow(unsafe_code)] pub mod script_thread; #[warn(deprecated)] -mod serviceworker_manager; +pub mod serviceworker_manager; #[warn(deprecated)] mod serviceworkerjob; #[warn(deprecated)] @@ -115,7 +115,7 @@ mod unpremultiplytable; #[warn(deprecated)] mod webdriver_handlers; -pub use init::{init, init_service_workers}; +pub use init::init; pub use script_runtime::JSEngineSetup; /// A module with everything layout can use from script. diff --git a/components/script/serviceworker_manager.rs b/components/script/serviceworker_manager.rs index 109628ae94e..66d4d995d32 100644 --- a/components/script/serviceworker_manager.rs +++ b/components/script/serviceworker_manager.rs @@ -15,8 +15,12 @@ use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg}; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use net_traits::{CoreResourceMsg, CustomResponseMediator}; -use script_traits::{DOMMessage, SWManagerMsg, SWManagerSenders, ScopeThings, ServiceWorkerMsg}; +use script_traits::{ + DOMMessage, SWManagerMsg, SWManagerSenders, ScopeThings, ServiceWorkerManagerFactory, + ServiceWorkerMsg, +}; use servo_config::pref; +use servo_url::ImmutableOrigin; use servo_url::ServoUrl; use std::collections::HashMap; use std::thread; @@ -31,6 +35,9 @@ pub struct ServiceWorkerManager { registered_workers: HashMap<ServoUrl, ScopeThings>, // map of active service worker descriptors active_workers: HashMap<ServoUrl, Sender<ServiceWorkerScriptMsg>>, + // Will be useful to implement posting a message to a client. + // See https://github.com/servo/servo/issues/24660 + _constellation_sender: IpcSender<SWManagerMsg>, // own sender to send messages here own_sender: IpcSender<ServiceWorkerMsg>, // receiver to receive messages from constellation @@ -44,6 +51,7 @@ impl ServiceWorkerManager { own_sender: IpcSender<ServiceWorkerMsg>, from_constellation_receiver: Receiver<ServiceWorkerMsg>, resource_port: Receiver<CustomResponseMediator>, + constellation_sender: IpcSender<SWManagerMsg>, ) -> ServiceWorkerManager { ServiceWorkerManager { registered_workers: HashMap::new(), @@ -51,30 +59,10 @@ impl ServiceWorkerManager { own_sender: own_sender, own_port: from_constellation_receiver, resource_receiver: resource_port, + _constellation_sender: constellation_sender, } } - pub fn spawn_manager(sw_senders: SWManagerSenders) { - let (own_sender, from_constellation_receiver) = ipc::channel().unwrap(); - let (resource_chan, resource_port) = ipc::channel().unwrap(); - let from_constellation = - ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(from_constellation_receiver); - let resource_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(resource_port); - let _ = sw_senders - .resource_sender - .send(CoreResourceMsg::NetworkMediator(resource_chan)); - let _ = sw_senders - .swmanager_sender - .send(SWManagerMsg::OwnSender(own_sender.clone())); - thread::Builder::new() - .name("ServiceWorkerManager".to_owned()) - .spawn(move || { - ServiceWorkerManager::new(own_sender, from_constellation, resource_port) - .handle_message(); - }) - .expect("Thread spawning failed"); - } - pub fn get_matching_scope(&self, load_url: &ServoUrl) -> Option<ServoUrl> { for scope in self.registered_workers.keys() { if longest_prefix_match(&scope, load_url) { @@ -203,6 +191,38 @@ impl ServiceWorkerManager { } } +impl ServiceWorkerManagerFactory for ServiceWorkerManager { + fn create(sw_senders: SWManagerSenders, _origin: ImmutableOrigin) { + let (resource_chan, resource_port) = ipc::channel().unwrap(); + + let SWManagerSenders { + resource_sender, + own_sender, + receiver, + swmanager_sender: constellation_sender, + } = sw_senders; + + let from_constellation = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(receiver); + let resource_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(resource_port); + let _ = resource_sender.send(CoreResourceMsg::NetworkMediator(resource_chan)); + if thread::Builder::new() + .name("ServiceWorkerManager".to_owned()) + .spawn(move || { + ServiceWorkerManager::new( + own_sender, + from_constellation, + resource_port, + constellation_sender, + ) + .handle_message(); + }) + .is_err() + { + warn!("ServiceWorkerManager thread spawning failed"); + } + } +} + pub fn serviceworker_enabled() -> bool { pref!(dom.serviceworker.enabled) } diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 9b4588a3cb0..b41cc44d678 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -706,6 +706,13 @@ pub trait ScriptThreadFactory { ) -> (Sender<Self::Message>, Receiver<Self::Message>); } +/// This trait allows creating a `ServiceWorkerManager` without depending on the `script` +/// crate. +pub trait ServiceWorkerManagerFactory { + /// Create a `ServiceWorkerManager`. + fn create(sw_senders: SWManagerSenders, origin: ImmutableOrigin); +} + /// Whether the sandbox attribute is present for an iframe element #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum IFrameSandboxState { diff --git a/components/script_traits/script_msg.rs b/components/script_traits/script_msg.rs index 918b4f4ff78..245a02ec6d2 100644 --- a/components/script_traits/script_msg.rs +++ b/components/script_traits/script_msg.rs @@ -370,11 +370,16 @@ pub struct DOMMessage { } /// Channels to allow service worker manager to communicate with constellation and resource thread +#[derive(Deserialize, Serialize)] pub struct SWManagerSenders { - /// sender for communicating with constellation + /// Sender of messages to the constellation. pub swmanager_sender: IpcSender<SWManagerMsg>, - /// sender for communicating with resource thread + /// Sender for communicating with resource thread. pub resource_sender: IpcSender<CoreResourceMsg>, + /// Sender of messages to the manager. + pub own_sender: IpcSender<ServiceWorkerMsg>, + /// Receiver of messages from the constellation. + pub receiver: IpcReceiver<ServiceWorkerMsg>, } /// Messages sent to Service Worker Manager thread @@ -393,6 +398,8 @@ pub enum ServiceWorkerMsg { /// Messages outgoing from the Service Worker Manager thread to constellation #[derive(Debug, Deserialize, Serialize)] pub enum SWManagerMsg { - /// Provide the constellation with a means of communicating with the Service Worker Manager - OwnSender(IpcSender<ServiceWorkerMsg>), + /// Placeholder to keep the enum, + /// as it will be needed when implementing + /// https://github.com/servo/servo/issues/24660 + PostMessageToClient, } diff --git a/components/servo/lib.rs b/components/servo/lib.rs index 5c54f654881..dff8c842e4f 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -80,7 +80,7 @@ use compositing::{CompositingReason, ConstellationMsg, IOCompositor, ShutdownSta not(target_arch = "aarch64") ))] use constellation::content_process_sandbox_profile; -use constellation::{Constellation, InitialConstellationState, UnprivilegedPipelineContent}; +use constellation::{Constellation, InitialConstellationState, UnprivilegedContent}; use constellation::{FromCompositorLogger, FromScriptLogger}; use crossbeam_channel::{unbounded, Sender}; use embedder_traits::{EmbedderMsg, EmbedderProxy, EmbedderReceiver, EventLoopWaker}; @@ -105,8 +105,9 @@ use profile::mem as profile_mem; use profile::time as profile_time; use profile_traits::mem; use profile_traits::time; +use script::serviceworker_manager::ServiceWorkerManager; use script::JSEngineSetup; -use script_traits::{SWManagerSenders, ScriptToConstellationChan, WindowSizeData}; +use script_traits::{ScriptToConstellationChan, WindowSizeData}; use servo_config::opts; use servo_config::{pref, prefs}; use servo_media::player::context::GlContext; @@ -519,7 +520,7 @@ where // Create the constellation, which maintains the engine // pipelines, including the script and layout threads, as well // as the navigation context. - let (constellation_chan, sw_senders) = create_constellation( + let constellation_chan = create_constellation( opts.user_agent.clone(), opts.config_dir.clone(), embedder_proxy, @@ -541,9 +542,6 @@ where pending_wr_frame.clone(), ); - // Send the constellation's swmanager sender to service worker manager thread - script::init_service_workers(sw_senders); - if cfg!(feature = "webdriver") { if let Some(port) = opts.webdriver_port { webdriver(port, constellation_chan.clone()); @@ -879,7 +877,7 @@ fn create_constellation( event_loop_waker: Option<Box<dyn EventLoopWaker>>, initial_window_size: WindowSizeData, pending_wr_frame: Arc<AtomicBool>, -) -> (Sender<ConstellationMsg>, SWManagerSenders) { +) -> Sender<ConstellationMsg> { // Global configuration options, parsed from the command line. let opts = opts::get(); @@ -900,8 +898,6 @@ fn create_constellation( webrender_api_sender.create_api(), ); - let resource_sender = public_resource_threads.sender(); - let initial_state = InitialConstellationState { compositor_proxy, embedder_proxy, @@ -926,10 +922,11 @@ fn create_constellation( let (canvas_chan, ipc_canvas_chan) = canvas::canvas_paint_thread::CanvasPaintThread::start(); - let (constellation_chan, from_swmanager_sender) = Constellation::< + let constellation_chan = Constellation::< script_layout_interface::message::Msg, layout_thread::LayoutThread, script::script_thread::ScriptThread, + script::serviceworker_manager::ServiceWorkerManager, >::start( initial_state, initial_window_size, @@ -949,13 +946,7 @@ fn create_constellation( .unwrap(); } - // channels to communicate with Service Worker Manager - let sw_senders = SWManagerSenders { - swmanager_sender: from_swmanager_sender, - resource_sender: resource_sender, - }; - - (constellation_chan, sw_senders) + constellation_chan } // A logger that logs to two downstream loggers. @@ -997,45 +988,50 @@ pub fn set_logger(script_to_constellation_chan: ScriptToConstellationChan) { /// Content process entry point. pub fn run_content_process(token: String) { let (unprivileged_content_sender, unprivileged_content_receiver) = - ipc::channel::<UnprivilegedPipelineContent>().unwrap(); - let connection_bootstrap: IpcSender<IpcSender<UnprivilegedPipelineContent>> = + ipc::channel::<UnprivilegedContent>().unwrap(); + let connection_bootstrap: IpcSender<IpcSender<UnprivilegedContent>> = IpcSender::connect(token).unwrap(); connection_bootstrap .send(unprivileged_content_sender) .unwrap(); - let mut unprivileged_content = unprivileged_content_receiver.recv().unwrap(); + let unprivileged_content = unprivileged_content_receiver.recv().unwrap(); opts::set_options(unprivileged_content.opts()); prefs::pref_map() .set_all(unprivileged_content.prefs()) .expect("Failed to set preferences"); - set_logger(unprivileged_content.script_to_constellation_chan().clone()); // Enter the sandbox if necessary. if opts::get().sandbox { create_sandbox(); } - let background_hang_monitor_register = if opts::get().background_hang_monitor { - unprivileged_content.register_with_background_hang_monitor() - } else { - None - }; - - // send the required channels to the service worker manager - let sw_senders = unprivileged_content.swmanager_senders(); let _js_engine_setup = script::init(); - script::init_service_workers(sw_senders); - media_platform::init(); + match unprivileged_content { + UnprivilegedContent::Pipeline(mut content) => { + media_platform::init(); + + set_logger(content.script_to_constellation_chan().clone()); - unprivileged_content.start_all::<script_layout_interface::message::Msg, - layout_thread::LayoutThread, - script::script_thread::ScriptThread>( - true, - background_hang_monitor_register, - None, - ); + let background_hang_monitor_register = if opts::get().background_hang_monitor { + content.register_with_background_hang_monitor() + } else { + None + }; + + content.start_all::<script_layout_interface::message::Msg, + layout_thread::LayoutThread, + script::script_thread::ScriptThread>( + true, + background_hang_monitor_register, + None, + ); + }, + UnprivilegedContent::ServiceWorker(content) => { + content.start::<ServiceWorkerManager>(); + }, + } } #[cfg(all( |