constellation/
pipeline.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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
53/// A `Pipeline` is the constellation's view of a `Window`. Each pipeline has an event loop
54/// (executed by a script thread). A script thread may be responsible for many pipelines.
55pub struct Pipeline {
56    /// The ID of the pipeline.
57    pub id: PipelineId,
58
59    /// The ID of the browsing context that contains this Pipeline.
60    pub browsing_context_id: BrowsingContextId,
61
62    /// The [`WebViewId`] of the `WebView` that contains this Pipeline.
63    pub webview_id: WebViewId,
64
65    pub opener: Option<BrowsingContextId>,
66
67    /// The event loop handling this pipeline.
68    pub event_loop: Rc<EventLoop>,
69
70    /// A channel to the compositor.
71    pub compositor_proxy: CompositorProxy,
72
73    /// The most recently loaded URL in this pipeline.
74    /// Note that this URL can change, for example if the page navigates
75    /// to a hash URL.
76    pub url: ServoUrl,
77
78    /// Whether this pipeline is currently running animations. Pipelines that are running
79    /// animations cause composites to be continually scheduled.
80    pub animation_state: AnimationState,
81
82    /// The child browsing contexts of this pipeline (these are iframes in the document).
83    pub children: Vec<BrowsingContextId>,
84
85    /// The Load Data used to create this pipeline.
86    pub load_data: LoadData,
87
88    /// The active history state for this pipeline.
89    pub history_state_id: Option<HistoryStateId>,
90
91    /// The history states owned by this pipeline.
92    pub history_states: HashSet<HistoryStateId>,
93
94    /// Has this pipeline received a notification that it is completely loaded?
95    pub completely_loaded: bool,
96
97    /// The title of this pipeline's document.
98    pub title: String,
99
100    pub focus_sequence: FocusSequenceNumber,
101}
102
103/// Initial setup data needed to construct a pipeline.
104///
105/// *DO NOT* add any Senders to this unless you absolutely know what you're doing, or pcwalton will
106/// have to rewrite your code. Use IPC senders instead.
107pub struct InitialPipelineState {
108    /// The ID of the pipeline to create.
109    pub id: PipelineId,
110
111    /// The ID of the browsing context that contains this Pipeline.
112    pub browsing_context_id: BrowsingContextId,
113
114    /// The ID of the top-level browsing context that contains this Pipeline.
115    pub webview_id: WebViewId,
116
117    /// The ID of the parent pipeline and frame type, if any.
118    /// If `None`, this is the root.
119    pub parent_pipeline_id: Option<PipelineId>,
120
121    pub opener: Option<BrowsingContextId>,
122
123    /// A channel to the associated constellation.
124    pub script_to_constellation_chan: ScriptToConstellationChan,
125
126    /// A channel to the embedder.
127    pub script_to_embedder_chan: ScriptToEmbedderChan,
128
129    /// A sender to request pipeline namespace ids.
130    pub namespace_request_sender: GenericSender<PipelineNamespaceRequest>,
131
132    /// A handle to register components for hang monitoring.
133    /// None when in multiprocess mode.
134    pub background_monitor_register: Option<Box<dyn BackgroundHangMonitorRegister>>,
135
136    /// A channel for the background hang monitor to send messages to the constellation.
137    pub background_hang_monitor_to_constellation_chan: GenericSender<HangMonitorAlert>,
138
139    /// A fatory for creating layouts to be used by the ScriptThread.
140    pub layout_factory: Arc<dyn LayoutFactory>,
141
142    /// A channel to the compositor.
143    pub compositor_proxy: CompositorProxy,
144
145    /// A channel to the developer tools, if applicable.
146    pub devtools_sender: Option<Sender<DevtoolsControlMsg>>,
147
148    /// A channel to the bluetooth thread.
149    #[cfg(feature = "bluetooth")]
150    pub bluetooth_thread: IpcSender<BluetoothRequest>,
151
152    /// A proxy to the system font service, responsible for managing the list of system fonts.
153    pub system_font_service: Arc<SystemFontServiceProxy>,
154
155    /// Channels to the resource-related threads.
156    pub resource_threads: ResourceThreads,
157
158    /// Channels to the storage-related threads.
159    pub storage_threads: StorageThreads,
160
161    /// A channel to the time profiler thread.
162    pub time_profiler_chan: time::ProfilerChan,
163
164    /// A channel to the memory profiler thread.
165    pub mem_profiler_chan: profile_mem::ProfilerChan,
166
167    /// The initial [`ViewportDetails`] to use when starting this new [`Pipeline`].
168    pub viewport_details: ViewportDetails,
169
170    /// The initial [`Theme`] to use when starting this new [`Pipeline`].
171    pub theme: Theme,
172
173    /// The ID of the pipeline namespace for this script thread.
174    pub pipeline_namespace_id: PipelineNamespaceId,
175
176    /// The event loop to run in, if applicable.
177    pub event_loop: Option<Rc<EventLoop>>,
178
179    /// Information about the page to load.
180    pub load_data: LoadData,
181
182    /// Whether the browsing context in which pipeline is embedded is throttled,
183    /// using less resources by stopping animations and running timers at a
184    /// heavily limited rate. This field is only used to notify script and
185    /// compositor threads after spawning a pipeline.
186    pub prev_throttled: bool,
187
188    /// A channel to the WebGL thread.
189    pub webgl_chan: Option<WebGLPipeline>,
190
191    /// The XR device registry
192    pub webxr_registry: Option<webxr_api::Registry>,
193
194    /// Application window's GL Context for Media player
195    pub player_context: WindowGLContext,
196
197    /// The image bytes associated with the BrokenImageIcon embedder resource.
198    pub broken_image_icon_data: Vec<u8>,
199
200    /// User content manager
201    pub user_content_manager: UserContentManager,
202
203    /// A list of URLs that can access privileged internal APIs.
204    pub privileged_urls: Vec<ServoUrl>,
205
206    /// The [`ImageCacheFactory`] used for the single-process mode.
207    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    /// Possibly starts a script thread, in a new process if requested.
219    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        // Note: we allow channel creation to panic, since recovering from this
234        // probably requires a general low-memory strategy.
235        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                // Route messages coming from content to devtools as appropriate.
249                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                // Spawn the child process.
299                //
300                // Yes, that's all there is to it!
301                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                    // Should not be None in single-process mode.
329                    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    /// Creates a new `Pipeline`, after the script has been spawned.
365    #[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    /// Let the `ScriptThread` for this [`Pipeline`] know that it has exited. If the `ScriptThread` hasn't
400    /// panicked and is still alive, it will send a `PipelineExited` message back to the `Constellation`
401    /// when it finishes cleaning up.
402    pub fn send_exit_message_to_script(&self, discard_bc: DiscardBrowsingContext) {
403        debug!("{:?} Sending exit message to script", self.id);
404
405        // Script thread handles shutting down layout, and layout handles shutting down the painter.
406        // For now, if the script thread has failed, we give up on clean shutdown.
407        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    /// Notify this pipeline of its activity.
417    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    /// The compositor's view of a pipeline.
425    pub fn to_sendable(&self) -> CompositionPipeline {
426        CompositionPipeline {
427            id: self.id,
428            webview_id: self.webview_id,
429        }
430    }
431
432    /// Add a new child browsing context.
433    pub fn add_child(&mut self, browsing_context_id: BrowsingContextId) {
434        self.children.push(browsing_context_id);
435    }
436
437    /// Remove a child browsing context.
438    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    /// Set whether to make pipeline use less resources, by stopping animations and
457    /// running timers at a heavily limited rate.
458    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}