use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use std::sync::Arc;
use background_hang_monitor::HangMonitorRegister;
use background_hang_monitor_api::{
BackgroundHangMonitorControlMsg, BackgroundHangMonitorRegister, HangMonitorAlert,
};
use base::id::{
BrowsingContextId, HistoryStateId, PipelineId, PipelineNamespace, PipelineNamespaceId,
PipelineNamespaceRequest, TopLevelBrowsingContextId,
};
use base::Epoch;
use bluetooth_traits::BluetoothRequest;
use canvas_traits::webgl::WebGLPipeline;
use compositing_traits::{CompositionPipeline, CompositorMsg, CompositorProxy};
use crossbeam_channel::{unbounded, Sender};
use devtools_traits::{DevtoolsControlMsg, ScriptToDevtoolsControlMsg};
use fonts::{SystemFontServiceProxy, SystemFontServiceProxySender};
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use ipc_channel::Error;
use log::{debug, error, warn};
use media::WindowGLContext;
use net::image_cache::ImageCacheImpl;
use net_traits::image_cache::ImageCache;
use net_traits::ResourceThreads;
use profile_traits::{mem as profile_mem, time};
use script_layout_interface::{LayoutFactory, ScriptThreadFactory};
use script_traits::{
AnimationState, ConstellationControlMsg, DiscardBrowsingContext, DocumentActivity,
InitialScriptState, LayoutMsg, LoadData, NewLayoutInfo, SWManagerMsg,
ScriptToConstellationChan, TimerSchedulerMsg, WindowSizeData,
};
use serde::{Deserialize, Serialize};
use servo_config::opts::{self, Opts};
use servo_config::prefs;
use servo_config::prefs::PrefValue;
use servo_url::ServoUrl;
use webrender_api::DocumentId;
use webrender_traits::CrossProcessCompositorApi;
use crate::event_loop::EventLoop;
use crate::sandboxing::{spawn_multiprocess, UnprivilegedContent};
pub struct Pipeline {
pub id: PipelineId,
pub browsing_context_id: BrowsingContextId,
pub top_level_browsing_context_id: TopLevelBrowsingContextId,
pub opener: Option<BrowsingContextId>,
pub event_loop: Rc<EventLoop>,
pub compositor_proxy: CompositorProxy,
pub url: ServoUrl,
pub animation_state: AnimationState,
pub children: Vec<BrowsingContextId>,
pub load_data: LoadData,
pub history_state_id: Option<HistoryStateId>,
pub history_states: HashSet<HistoryStateId>,
pub completely_loaded: bool,
pub title: String,
pub layout_epoch: Epoch,
}
pub struct InitialPipelineState {
pub id: PipelineId,
pub browsing_context_id: BrowsingContextId,
pub top_level_browsing_context_id: TopLevelBrowsingContextId,
pub parent_pipeline_id: Option<PipelineId>,
pub opener: Option<BrowsingContextId>,
pub script_to_constellation_chan: ScriptToConstellationChan,
pub namespace_request_sender: IpcSender<PipelineNamespaceRequest>,
pub background_monitor_register: Option<Box<dyn BackgroundHangMonitorRegister>>,
pub background_hang_monitor_to_constellation_chan: IpcSender<HangMonitorAlert>,
pub layout_to_constellation_chan: IpcSender<LayoutMsg>,
pub layout_factory: Arc<dyn LayoutFactory>,
pub scheduler_chan: IpcSender<TimerSchedulerMsg>,
pub compositor_proxy: CompositorProxy,
pub devtools_sender: Option<Sender<DevtoolsControlMsg>>,
pub bluetooth_thread: IpcSender<BluetoothRequest>,
pub swmanager_thread: IpcSender<SWManagerMsg>,
pub system_font_service: Arc<SystemFontServiceProxy>,
pub resource_threads: ResourceThreads,
pub time_profiler_chan: time::ProfilerChan,
pub mem_profiler_chan: profile_mem::ProfilerChan,
pub window_size: WindowSizeData,
pub pipeline_namespace_id: PipelineNamespaceId,
pub event_loop: Option<Rc<EventLoop>>,
pub load_data: LoadData,
pub prev_throttled: bool,
pub webrender_document: DocumentId,
pub webgl_chan: Option<WebGLPipeline>,
pub webxr_registry: Option<webxr_api::Registry>,
pub player_context: WindowGLContext,
pub user_agent: Cow<'static, str>,
}
pub struct NewPipeline {
pub pipeline: Pipeline,
pub bhm_control_chan: Option<IpcSender<BackgroundHangMonitorControlMsg>>,
}
impl Pipeline {
pub fn spawn<STF: ScriptThreadFactory>(
state: InitialPipelineState,
) -> Result<NewPipeline, Error> {
let (script_chan, bhm_control_chan) = match state.event_loop {
Some(script_chan) => {
let new_layout_info = NewLayoutInfo {
parent_info: state.parent_pipeline_id,
new_pipeline_id: state.id,
browsing_context_id: state.browsing_context_id,
top_level_browsing_context_id: state.top_level_browsing_context_id,
opener: state.opener,
load_data: state.load_data.clone(),
window_size: state.window_size,
};
if let Err(e) =
script_chan.send(ConstellationControlMsg::AttachLayout(new_layout_info))
{
warn!("Sending to script during pipeline creation failed ({})", e);
}
(script_chan, None)
},
None => {
let (script_chan, script_port) = ipc::channel().expect("Pipeline script chan");
let script_to_devtools_ipc_sender =
state.devtools_sender.as_ref().map(|devtools_sender| {
let (script_to_devtools_ipc_sender, script_to_devtools_ipc_receiver) =
ipc::channel().expect("Pipeline script to devtools chan");
let devtools_sender = (*devtools_sender).clone();
ROUTER.add_typed_route(
script_to_devtools_ipc_receiver,
Box::new(move |message| match message {
Err(e) => {
error!("Cast to ScriptToDevtoolsControlMsg failed ({}).", e)
},
Ok(message) => {
if let Err(e) = devtools_sender
.send(DevtoolsControlMsg::FromScript(message))
{
warn!("Sending to devtools failed ({:?})", e)
}
},
}),
);
script_to_devtools_ipc_sender
});
let mut unprivileged_pipeline_content = UnprivilegedPipelineContent {
id: state.id,
browsing_context_id: state.browsing_context_id,
top_level_browsing_context_id: state.top_level_browsing_context_id,
parent_pipeline_id: state.parent_pipeline_id,
opener: state.opener,
script_to_constellation_chan: state.script_to_constellation_chan.clone(),
namespace_request_sender: state.namespace_request_sender,
background_hang_monitor_to_constellation_chan: state
.background_hang_monitor_to_constellation_chan
.clone(),
bhm_control_port: None,
scheduler_chan: state.scheduler_chan,
devtools_ipc_sender: script_to_devtools_ipc_sender,
bluetooth_thread: state.bluetooth_thread,
swmanager_thread: state.swmanager_thread,
system_font_service: state.system_font_service.to_sender(),
resource_threads: state.resource_threads,
time_profiler_chan: state.time_profiler_chan,
mem_profiler_chan: state.mem_profiler_chan,
window_size: state.window_size,
layout_to_constellation_chan: state.layout_to_constellation_chan,
script_chan: script_chan.clone(),
load_data: state.load_data.clone(),
script_port,
opts: (*opts::get()).clone(),
prefs: prefs::pref_map().iter().collect(),
pipeline_namespace_id: state.pipeline_namespace_id,
webrender_document: state.webrender_document,
cross_process_compositor_api: state
.compositor_proxy
.cross_process_compositor_api
.clone(),
webgl_chan: state.webgl_chan,
webxr_registry: state.webxr_registry,
player_context: state.player_context,
user_agent: state.user_agent,
};
let bhm_control_chan = if opts::multiprocess() {
let (bhm_control_chan, bhm_control_port) =
ipc::channel().expect("Sampler chan");
unprivileged_pipeline_content.bhm_control_port = Some(bhm_control_port);
unprivileged_pipeline_content.spawn_multiprocess()?;
Some(bhm_control_chan)
} else {
let register = state
.background_monitor_register
.expect("Couldn't start content, no background monitor has been initiated");
unprivileged_pipeline_content.start_all::<STF>(
false,
state.layout_factory,
register,
);
None
};
(EventLoop::new(script_chan), bhm_control_chan)
},
};
let pipeline = Pipeline::new(
state.id,
state.browsing_context_id,
state.top_level_browsing_context_id,
state.opener,
script_chan,
state.compositor_proxy,
state.prev_throttled,
state.load_data,
);
Ok(NewPipeline {
pipeline,
bhm_control_chan,
})
}
#[allow(clippy::too_many_arguments)]
pub fn new(
id: PipelineId,
browsing_context_id: BrowsingContextId,
top_level_browsing_context_id: TopLevelBrowsingContextId,
opener: Option<BrowsingContextId>,
event_loop: Rc<EventLoop>,
compositor_proxy: CompositorProxy,
throttled: bool,
load_data: LoadData,
) -> Pipeline {
let pipeline = Pipeline {
id,
browsing_context_id,
top_level_browsing_context_id,
opener,
event_loop,
compositor_proxy,
url: load_data.url.clone(),
children: vec![],
animation_state: AnimationState::NoAnimationsPresent,
load_data,
history_state_id: None,
history_states: HashSet::new(),
completely_loaded: false,
title: String::new(),
layout_epoch: Epoch(0),
};
pipeline.set_throttled(throttled);
pipeline
}
pub fn exit(&self, discard_bc: DiscardBrowsingContext) {
debug!("pipeline {:?} exiting", self.id);
if let Ok((sender, receiver)) = ipc::channel() {
self.compositor_proxy
.send(CompositorMsg::PipelineExited(self.id, sender));
if let Err(e) = receiver.recv() {
warn!("Sending exit message failed ({:?}).", e);
}
}
let msg = ConstellationControlMsg::ExitPipeline(self.id, discard_bc);
if let Err(e) = self.event_loop.send(msg) {
warn!("Sending script exit message failed ({}).", e);
}
}
pub fn force_exit(&self, discard_bc: DiscardBrowsingContext) {
let msg = ConstellationControlMsg::ExitPipeline(self.id, discard_bc);
if let Err(e) = self.event_loop.send(msg) {
warn!("Sending script exit message failed ({}).", e);
}
}
pub fn set_activity(&self, activity: DocumentActivity) {
let msg = ConstellationControlMsg::SetDocumentActivity(self.id, activity);
if let Err(e) = self.event_loop.send(msg) {
warn!("Sending activity message failed ({}).", e);
}
}
pub fn to_sendable(&self) -> CompositionPipeline {
CompositionPipeline {
id: self.id,
top_level_browsing_context_id: self.top_level_browsing_context_id,
script_chan: self.event_loop.sender(),
}
}
pub fn add_child(&mut self, browsing_context_id: BrowsingContextId) {
self.children.push(browsing_context_id);
}
pub fn remove_child(&mut self, browsing_context_id: BrowsingContextId) {
match self
.children
.iter()
.position(|id| *id == browsing_context_id)
{
None => {
warn!(
"Pipeline remove child already removed ({:?}).",
browsing_context_id
)
},
Some(index) => {
self.children.remove(index);
},
}
}
pub fn set_throttled(&self, throttled: bool) {
let script_msg = ConstellationControlMsg::SetThrottled(self.id, throttled);
let compositor_msg = CompositorMsg::SetThrottled(self.id, throttled);
let err = self.event_loop.send(script_msg);
if let Err(e) = err {
warn!("Sending SetThrottled to script failed ({}).", e);
}
self.compositor_proxy.send(compositor_msg);
}
}
#[derive(Deserialize, Serialize)]
pub struct UnprivilegedPipelineContent {
id: PipelineId,
top_level_browsing_context_id: TopLevelBrowsingContextId,
browsing_context_id: BrowsingContextId,
parent_pipeline_id: Option<PipelineId>,
opener: Option<BrowsingContextId>,
namespace_request_sender: IpcSender<PipelineNamespaceRequest>,
script_to_constellation_chan: ScriptToConstellationChan,
background_hang_monitor_to_constellation_chan: IpcSender<HangMonitorAlert>,
bhm_control_port: Option<IpcReceiver<BackgroundHangMonitorControlMsg>>,
layout_to_constellation_chan: IpcSender<LayoutMsg>,
scheduler_chan: IpcSender<TimerSchedulerMsg>,
devtools_ipc_sender: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
bluetooth_thread: IpcSender<BluetoothRequest>,
swmanager_thread: IpcSender<SWManagerMsg>,
system_font_service: SystemFontServiceProxySender,
resource_threads: ResourceThreads,
time_profiler_chan: time::ProfilerChan,
mem_profiler_chan: profile_mem::ProfilerChan,
window_size: WindowSizeData,
script_chan: IpcSender<ConstellationControlMsg>,
load_data: LoadData,
script_port: IpcReceiver<ConstellationControlMsg>,
opts: Opts,
prefs: HashMap<String, PrefValue>,
pipeline_namespace_id: PipelineNamespaceId,
cross_process_compositor_api: CrossProcessCompositorApi,
webrender_document: DocumentId,
webgl_chan: Option<WebGLPipeline>,
webxr_registry: Option<webxr_api::Registry>,
player_context: WindowGLContext,
user_agent: Cow<'static, str>,
}
impl UnprivilegedPipelineContent {
pub fn start_all<STF: ScriptThreadFactory>(
self,
wait_for_completion: bool,
layout_factory: Arc<dyn LayoutFactory>,
background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
) {
PipelineNamespace::set_installer_sender(self.namespace_request_sender);
let image_cache = Arc::new(ImageCacheImpl::new(
self.cross_process_compositor_api.clone(),
));
let (content_process_shutdown_chan, content_process_shutdown_port) = unbounded();
STF::create(
InitialScriptState {
id: self.id,
browsing_context_id: self.browsing_context_id,
top_level_browsing_context_id: self.top_level_browsing_context_id,
parent_info: self.parent_pipeline_id,
opener: self.opener,
control_chan: self.script_chan.clone(),
control_port: self.script_port,
script_to_constellation_chan: self.script_to_constellation_chan.clone(),
background_hang_monitor_register: background_hang_monitor_register.clone(),
layout_to_constellation_chan: self.layout_to_constellation_chan.clone(),
scheduler_chan: self.scheduler_chan,
bluetooth_thread: self.bluetooth_thread,
resource_threads: self.resource_threads,
image_cache: image_cache.clone(),
time_profiler_chan: self.time_profiler_chan.clone(),
mem_profiler_chan: self.mem_profiler_chan.clone(),
devtools_chan: self.devtools_ipc_sender,
window_size: self.window_size,
pipeline_namespace_id: self.pipeline_namespace_id,
content_process_shutdown_chan,
webgl_chan: self.webgl_chan,
webxr_registry: self.webxr_registry,
webrender_document: self.webrender_document,
compositor_api: self.cross_process_compositor_api.clone(),
player_context: self.player_context.clone(),
inherited_secure_context: self.load_data.inherited_secure_context,
},
layout_factory,
Arc::new(self.system_font_service.to_proxy()),
self.load_data.clone(),
self.user_agent,
);
if wait_for_completion {
match content_process_shutdown_port.recv() {
Ok(()) => {},
Err(_) => error!("Script-thread shut-down unexpectedly"),
}
}
}
pub fn spawn_multiprocess(self) -> Result<(), Error> {
spawn_multiprocess(UnprivilegedContent::Pipeline(self))
}
pub fn register_with_background_hang_monitor(
&mut self,
) -> Box<dyn BackgroundHangMonitorRegister> {
HangMonitorRegister::init(
self.background_hang_monitor_to_constellation_chan.clone(),
self.bhm_control_port.take().expect("no sampling profiler?"),
opts::get().background_hang_monitor,
)
}
pub fn script_to_constellation_chan(&self) -> &ScriptToConstellationChan {
&self.script_to_constellation_chan
}
pub fn opts(&self) -> Opts {
self.opts.clone()
}
pub fn prefs(&self) -> HashMap<String, PrefValue> {
self.prefs.clone()
}
}