Skip to main content

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