1use std::collections::HashSet;
6use std::rc::Rc;
7use std::sync::Arc;
8use std::thread::JoinHandle;
9
10use background_hang_monitor_api::{
11 BackgroundHangMonitorControlMsg, BackgroundHangMonitorRegister, HangMonitorAlert,
12};
13use base::generic_channel::{self, GenericSender};
14use base::id::{
15 BrowsingContextId, HistoryStateId, PipelineId, PipelineNamespaceId, PipelineNamespaceRequest,
16 WebViewId,
17};
18#[cfg(feature = "bluetooth")]
19use bluetooth_traits::BluetoothRequest;
20use canvas_traits::webgl::WebGLPipeline;
21use compositing_traits::{CompositionPipeline, CompositorMsg, CompositorProxy};
22use constellation_traits::{LoadData, ScriptToConstellationChan};
23use crossbeam_channel::Sender;
24use devtools_traits::DevtoolsControlMsg;
25use embedder_traits::user_content_manager::UserContentManager;
26use embedder_traits::{
27 AnimationState, FocusSequenceNumber, ScriptToEmbedderChan, Theme, ViewportDetails,
28};
29use fonts::SystemFontServiceProxy;
30use ipc_channel::Error;
31use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
32use ipc_channel::router::ROUTER;
33use layout_api::{LayoutFactory, ScriptThreadFactory};
34use log::{debug, error, warn};
35use media::WindowGLContext;
36use net_traits::ResourceThreads;
37use net_traits::image_cache::ImageCacheFactory;
38use profile_traits::{mem as profile_mem, time};
39use script_traits::{
40 DiscardBrowsingContext, DocumentActivity, InitialScriptState, NewPipelineInfo,
41 ScriptThreadMessage,
42};
43use servo_config::opts::{self};
44use servo_config::prefs;
45use servo_url::ServoUrl;
46use storage_traits::StorageThreads;
47
48use crate::UnprivilegedContent;
49use crate::event_loop::{EventLoop, NewScriptEventLoopProcessInfo};
50use crate::process_manager::Process;
51use crate::sandboxing::spawn_multiprocess;
52
53pub struct Pipeline {
56 pub id: PipelineId,
58
59 pub browsing_context_id: BrowsingContextId,
61
62 pub webview_id: WebViewId,
64
65 pub opener: Option<BrowsingContextId>,
66
67 pub event_loop: Rc<EventLoop>,
69
70 pub compositor_proxy: CompositorProxy,
72
73 pub url: ServoUrl,
77
78 pub animation_state: AnimationState,
81
82 pub children: Vec<BrowsingContextId>,
84
85 pub load_data: LoadData,
87
88 pub history_state_id: Option<HistoryStateId>,
90
91 pub history_states: HashSet<HistoryStateId>,
93
94 pub completely_loaded: bool,
96
97 pub title: String,
99
100 pub focus_sequence: FocusSequenceNumber,
101}
102
103pub struct InitialPipelineState {
108 pub id: PipelineId,
110
111 pub browsing_context_id: BrowsingContextId,
113
114 pub webview_id: WebViewId,
116
117 pub parent_pipeline_id: Option<PipelineId>,
120
121 pub opener: Option<BrowsingContextId>,
122
123 pub script_to_constellation_chan: ScriptToConstellationChan,
125
126 pub script_to_embedder_chan: ScriptToEmbedderChan,
128
129 pub namespace_request_sender: GenericSender<PipelineNamespaceRequest>,
131
132 pub background_monitor_register: Option<Box<dyn BackgroundHangMonitorRegister>>,
135
136 pub background_hang_monitor_to_constellation_chan: GenericSender<HangMonitorAlert>,
138
139 pub layout_factory: Arc<dyn LayoutFactory>,
141
142 pub compositor_proxy: CompositorProxy,
144
145 pub devtools_sender: Option<Sender<DevtoolsControlMsg>>,
147
148 #[cfg(feature = "bluetooth")]
150 pub bluetooth_thread: IpcSender<BluetoothRequest>,
151
152 pub system_font_service: Arc<SystemFontServiceProxy>,
154
155 pub resource_threads: ResourceThreads,
157
158 pub storage_threads: StorageThreads,
160
161 pub time_profiler_chan: time::ProfilerChan,
163
164 pub mem_profiler_chan: profile_mem::ProfilerChan,
166
167 pub viewport_details: ViewportDetails,
169
170 pub theme: Theme,
172
173 pub pipeline_namespace_id: PipelineNamespaceId,
175
176 pub event_loop: Option<Rc<EventLoop>>,
178
179 pub load_data: LoadData,
181
182 pub prev_throttled: bool,
187
188 pub webgl_chan: Option<WebGLPipeline>,
190
191 pub webxr_registry: Option<webxr_api::Registry>,
193
194 pub player_context: WindowGLContext,
196
197 pub broken_image_icon_data: Vec<u8>,
199
200 pub user_content_manager: UserContentManager,
202
203 pub privileged_urls: Vec<ServoUrl>,
205
206 pub image_cache_factory: Arc<dyn ImageCacheFactory>,
208}
209
210pub struct NewPipeline {
211 pub pipeline: Pipeline,
212 pub bhm_control_chan: Option<GenericSender<BackgroundHangMonitorControlMsg>>,
213 pub lifeline: Option<(IpcReceiver<()>, Process)>,
214 pub join_handle: Option<JoinHandle<()>>,
215}
216
217impl Pipeline {
218 pub fn spawn<STF: ScriptThreadFactory>(
220 state: InitialPipelineState,
221 ) -> Result<NewPipeline, Error> {
222 let new_pipeline_info = NewPipelineInfo {
223 parent_info: state.parent_pipeline_id,
224 new_pipeline_id: state.id,
225 browsing_context_id: state.browsing_context_id,
226 webview_id: state.webview_id,
227 opener: state.opener,
228 load_data: state.load_data.clone(),
229 viewport_details: state.viewport_details,
230 theme: state.theme,
231 };
232
233 let (script_chan, (bhm_control_chan, lifeline, join_handle)) = match state.event_loop {
236 Some(script_chan) => {
237 if let Err(e) =
238 script_chan.send(ScriptThreadMessage::SpawnPipeline(new_pipeline_info))
239 {
240 warn!("Sending to script during pipeline creation failed ({})", e);
241 }
242 (script_chan, (None, None, None))
243 },
244 None => {
245 let (script_chan, script_port) =
246 base::generic_channel::channel().expect("Pipeline script chan");
247
248 let script_to_devtools_ipc_sender =
250 state.devtools_sender.as_ref().map(|devtools_sender| {
251 let (script_to_devtools_ipc_sender, script_to_devtools_ipc_receiver) =
252 ipc::channel().expect("Pipeline script to devtools chan");
253 let devtools_sender = (*devtools_sender).clone();
254 ROUTER.add_typed_route(
255 script_to_devtools_ipc_receiver,
256 Box::new(move |message| match message {
257 Err(e) => {
258 error!("Cast to ScriptToDevtoolsControlMsg failed ({}).", e)
259 },
260 Ok(message) => {
261 if let Err(e) = devtools_sender
262 .send(DevtoolsControlMsg::FromScript(message))
263 {
264 warn!("Sending to devtools failed ({:?})", e)
265 }
266 },
267 }),
268 );
269 script_to_devtools_ipc_sender
270 });
271
272 let initial_script_state = InitialScriptState {
273 pipeline_to_constellation_sender: state.script_to_constellation_chan.clone(),
274 script_to_embedder_sender: state.script_to_embedder_chan,
275 namespace_request_sender: state.namespace_request_sender,
276 devtools_server_sender: script_to_devtools_ipc_sender,
277 #[cfg(feature = "bluetooth")]
278 bluetooth_sender: state.bluetooth_thread,
279 system_font_service: state.system_font_service.to_sender(),
280 resource_threads: state.resource_threads,
281 storage_threads: state.storage_threads,
282 time_profiler_sender: state.time_profiler_chan,
283 memory_profiler_sender: state.mem_profiler_chan,
284 constellation_to_script_sender: script_chan.clone(),
285 constellation_to_script_receiver: script_port,
286 pipeline_namespace_id: state.pipeline_namespace_id,
287 cross_process_compositor_api: state
288 .compositor_proxy
289 .cross_process_compositor_api
290 .clone(),
291 webgl_chan: state.webgl_chan,
292 webxr_registry: state.webxr_registry,
293 player_context: state.player_context,
294 user_content_manager: state.user_content_manager,
295 privileged_urls: state.privileged_urls,
296 };
297
298 let multiprocess_data = if opts::get().multiprocess {
302 let (constellation_to_bhm_sender, constellation_to_bhm_receiver) =
303 generic_channel::channel().expect("Sampler chan");
304 let (lifeline_sender, lifeline_receiver) =
305 ipc::channel().expect("Failed to create lifeline channel");
306
307 let process = spawn_multiprocess(UnprivilegedContent::ScriptEventLoop(
308 NewScriptEventLoopProcessInfo {
309 new_pipeline_info,
310 initial_script_state,
311 constellation_to_bhm_receiver,
312 bhm_to_constellation_sender: state
313 .background_hang_monitor_to_constellation_chan
314 .clone(),
315 lifeline_sender,
316 opts: (*opts::get()).clone(),
317 prefs: Box::new(prefs::get().clone()),
318 broken_image_icon_data: state.broken_image_icon_data,
319 },
320 ))?;
321
322 (
323 Some(constellation_to_bhm_sender),
324 Some((lifeline_receiver, process)),
325 None,
326 )
327 } else {
328 let background_hang_monitor_register = state
330 .background_monitor_register
331 .expect("Couldn't start content, no background monitor has been initiated");
332 let (_, join_handle) = STF::create(
333 initial_script_state,
334 new_pipeline_info,
335 state.layout_factory,
336 state.image_cache_factory,
337 background_hang_monitor_register,
338 );
339 (None, None, Some(join_handle))
340 };
341
342 (EventLoop::new(script_chan), multiprocess_data)
343 },
344 };
345
346 let pipeline = Pipeline::new(
347 state.id,
348 state.browsing_context_id,
349 state.webview_id,
350 state.opener,
351 script_chan,
352 state.compositor_proxy,
353 state.prev_throttled,
354 state.load_data,
355 );
356 Ok(NewPipeline {
357 pipeline,
358 bhm_control_chan,
359 lifeline,
360 join_handle,
361 })
362 }
363
364 #[allow(clippy::too_many_arguments)]
366 pub fn new(
367 id: PipelineId,
368 browsing_context_id: BrowsingContextId,
369 webview_id: WebViewId,
370 opener: Option<BrowsingContextId>,
371 event_loop: Rc<EventLoop>,
372 compositor_proxy: CompositorProxy,
373 throttled: bool,
374 load_data: LoadData,
375 ) -> Pipeline {
376 let pipeline = Pipeline {
377 id,
378 browsing_context_id,
379 webview_id,
380 opener,
381 event_loop,
382 compositor_proxy,
383 url: load_data.url.clone(),
384 children: vec![],
385 animation_state: AnimationState::NoAnimationsPresent,
386 load_data,
387 history_state_id: None,
388 history_states: HashSet::new(),
389 completely_loaded: false,
390 title: String::new(),
391 focus_sequence: FocusSequenceNumber::default(),
392 };
393
394 pipeline.set_throttled(throttled);
395
396 pipeline
397 }
398
399 pub fn send_exit_message_to_script(&self, discard_bc: DiscardBrowsingContext) {
403 debug!("{:?} Sending exit message to script", self.id);
404
405 if let Err(error) = self.event_loop.send(ScriptThreadMessage::ExitPipeline(
408 self.webview_id,
409 self.id,
410 discard_bc,
411 )) {
412 warn!("Sending script exit message failed ({error}).");
413 }
414 }
415
416 pub fn set_activity(&self, activity: DocumentActivity) {
418 let msg = ScriptThreadMessage::SetDocumentActivity(self.id, activity);
419 if let Err(e) = self.event_loop.send(msg) {
420 warn!("Sending activity message failed ({}).", e);
421 }
422 }
423
424 pub fn to_sendable(&self) -> CompositionPipeline {
426 CompositionPipeline {
427 id: self.id,
428 webview_id: self.webview_id,
429 }
430 }
431
432 pub fn add_child(&mut self, browsing_context_id: BrowsingContextId) {
434 self.children.push(browsing_context_id);
435 }
436
437 pub fn remove_child(&mut self, browsing_context_id: BrowsingContextId) {
439 match self
440 .children
441 .iter()
442 .position(|id| *id == browsing_context_id)
443 {
444 None => {
445 warn!(
446 "Pipeline remove child already removed ({:?}).",
447 browsing_context_id
448 )
449 },
450 Some(index) => {
451 self.children.remove(index);
452 },
453 }
454 }
455
456 pub fn set_throttled(&self, throttled: bool) {
459 let script_msg = ScriptThreadMessage::SetThrottled(self.webview_id, self.id, throttled);
460 let compositor_msg = CompositorMsg::SetThrottled(self.webview_id, self.id, throttled);
461 let err = self.event_loop.send(script_msg);
462 if let Err(e) = err {
463 warn!("Sending SetThrottled to script failed ({}).", e);
464 }
465 self.compositor_proxy.send(compositor_msg);
466 }
467}