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;
7
8use base::id::{BrowsingContextId, HistoryStateId, PipelineId, WebViewId};
9use compositing_traits::{CompositionPipeline, CompositorMsg, CompositorProxy};
10use constellation_traits::{LoadData, ServiceWorkerManagerFactory};
11use embedder_traits::{AnimationState, FocusSequenceNumber};
12use ipc_channel::Error;
13use layout_api::ScriptThreadFactory;
14use log::{debug, error, warn};
15use script_traits::{
16    DiscardBrowsingContext, DocumentActivity, NewPipelineInfo, ScriptThreadMessage,
17};
18use servo_url::ServoUrl;
19
20use crate::Constellation;
21use crate::event_loop::EventLoop;
22
23/// A `Pipeline` is the constellation's view of a `Window`. Each pipeline has an event loop
24/// (executed by a script thread). A script thread may be responsible for many pipelines.
25pub struct Pipeline {
26    /// The ID of the pipeline.
27    pub id: PipelineId,
28
29    /// The ID of the browsing context that contains this Pipeline.
30    pub browsing_context_id: BrowsingContextId,
31
32    /// The [`WebViewId`] of the `WebView` that contains this Pipeline.
33    pub webview_id: WebViewId,
34
35    pub opener: Option<BrowsingContextId>,
36
37    /// The event loop handling this pipeline.
38    pub event_loop: Rc<EventLoop>,
39
40    /// A channel to the compositor.
41    pub compositor_proxy: CompositorProxy,
42
43    /// The most recently loaded URL in this pipeline.
44    /// Note that this URL can change, for example if the page navigates
45    /// to a hash URL.
46    pub url: ServoUrl,
47
48    /// Whether this pipeline is currently running animations. Pipelines that are running
49    /// animations cause composites to be continually scheduled.
50    pub animation_state: AnimationState,
51
52    /// The child browsing contexts of this pipeline (these are iframes in the document).
53    pub children: Vec<BrowsingContextId>,
54
55    /// The Load Data used to create this pipeline.
56    pub load_data: LoadData,
57
58    /// The active history state for this pipeline.
59    pub history_state_id: Option<HistoryStateId>,
60
61    /// The history states owned by this pipeline.
62    pub history_states: HashSet<HistoryStateId>,
63
64    /// Has this pipeline received a notification that it is completely loaded?
65    pub completely_loaded: bool,
66
67    /// The title of this pipeline's document.
68    pub title: String,
69
70    pub focus_sequence: FocusSequenceNumber,
71}
72
73impl Pipeline {
74    /// Possibly starts a script thread, in a new process if requested.
75    pub(crate) fn spawn<STF: ScriptThreadFactory, SWF: ServiceWorkerManagerFactory>(
76        new_pipeline_info: NewPipelineInfo,
77        event_loop: Rc<EventLoop>,
78        constellation: &Constellation<STF, SWF>,
79        throttled: bool,
80    ) -> Result<Self, Error> {
81        if let Err(error) = event_loop.send(ScriptThreadMessage::SpawnPipeline(
82            new_pipeline_info.clone(),
83        )) {
84            error!("Could not spawn Pipeline in EventLoop: {error}");
85            return Err(error);
86        }
87
88        Ok(Self::new_already_spawned(
89            new_pipeline_info.new_pipeline_id,
90            new_pipeline_info.browsing_context_id,
91            new_pipeline_info.webview_id,
92            new_pipeline_info.opener,
93            event_loop,
94            constellation.compositor_proxy.clone(),
95            throttled,
96            new_pipeline_info.load_data,
97        ))
98    }
99
100    /// Creates a new `Pipeline`, after it has been spawned in its [`EventLoop`].
101    #[allow(clippy::too_many_arguments)]
102    pub fn new_already_spawned(
103        id: PipelineId,
104        browsing_context_id: BrowsingContextId,
105        webview_id: WebViewId,
106        opener: Option<BrowsingContextId>,
107        event_loop: Rc<EventLoop>,
108        compositor_proxy: CompositorProxy,
109        throttled: bool,
110        load_data: LoadData,
111    ) -> Self {
112        let pipeline = Self {
113            id,
114            browsing_context_id,
115            webview_id,
116            opener,
117            event_loop,
118            compositor_proxy,
119            url: load_data.url.clone(),
120            children: vec![],
121            animation_state: AnimationState::NoAnimationsPresent,
122            load_data,
123            history_state_id: None,
124            history_states: HashSet::new(),
125            completely_loaded: false,
126            title: String::new(),
127            focus_sequence: FocusSequenceNumber::default(),
128        };
129        pipeline.set_throttled(throttled);
130        pipeline
131    }
132
133    /// Let the `ScriptThread` for this [`Pipeline`] know that it has exited. If the `ScriptThread` hasn't
134    /// panicked and is still alive, it will send a `PipelineExited` message back to the `Constellation`
135    /// when it finishes cleaning up.
136    pub fn send_exit_message_to_script(&self, discard_bc: DiscardBrowsingContext) {
137        debug!("{:?} Sending exit message to script", self.id);
138
139        // Script thread handles shutting down layout, and layout handles shutting down the painter.
140        // For now, if the script thread has failed, we give up on clean shutdown.
141        if let Err(error) = self.event_loop.send(ScriptThreadMessage::ExitPipeline(
142            self.webview_id,
143            self.id,
144            discard_bc,
145        )) {
146            warn!("Sending script exit message failed ({error}).");
147        }
148    }
149
150    /// Notify this pipeline of its activity.
151    pub fn set_activity(&self, activity: DocumentActivity) {
152        let msg = ScriptThreadMessage::SetDocumentActivity(self.id, activity);
153        if let Err(e) = self.event_loop.send(msg) {
154            warn!("Sending activity message failed ({}).", e);
155        }
156    }
157
158    /// The compositor's view of a pipeline.
159    pub fn to_sendable(&self) -> CompositionPipeline {
160        CompositionPipeline {
161            id: self.id,
162            webview_id: self.webview_id,
163        }
164    }
165
166    /// Add a new child browsing context.
167    pub fn add_child(&mut self, browsing_context_id: BrowsingContextId) {
168        self.children.push(browsing_context_id);
169    }
170
171    /// Remove a child browsing context.
172    pub fn remove_child(&mut self, browsing_context_id: BrowsingContextId) {
173        match self
174            .children
175            .iter()
176            .position(|id| *id == browsing_context_id)
177        {
178            None => {
179                warn!(
180                    "Pipeline remove child already removed ({:?}).",
181                    browsing_context_id
182                )
183            },
184            Some(index) => {
185                self.children.remove(index);
186            },
187        }
188    }
189
190    /// Set whether to make pipeline use less resources, by stopping animations and
191    /// running timers at a heavily limited rate.
192    pub fn set_throttled(&self, throttled: bool) {
193        let script_msg = ScriptThreadMessage::SetThrottled(self.webview_id, self.id, throttled);
194        let compositor_msg = CompositorMsg::SetThrottled(self.webview_id, self.id, throttled);
195        let err = self.event_loop.send(script_msg);
196        if let Err(e) = err {
197            warn!("Sending SetThrottled to script failed ({}).", e);
198        }
199        self.compositor_proxy.send(compositor_msg);
200    }
201}