1use 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
25pub struct Pipeline {
28 pub id: PipelineId,
30
31 pub browsing_context_id: BrowsingContextId,
33
34 pub webview_id: WebViewId,
36
37 pub opener: Option<BrowsingContextId>,
38
39 pub event_loop: Rc<EventLoop>,
41
42 pub paint_proxy: PaintProxy,
44
45 pub url: ServoUrl,
49
50 pub animation_state: AnimationState,
53
54 pub document_callbacks_active: bool,
56
57 pub worker_callbacks_active: FxHashSet<WorkerId>,
59
60 pub last_callbacks_active_sent_to_paint: bool,
62
63 pub children: Vec<BrowsingContextId>,
65
66 pub load_data: LoadData,
68
69 pub history_state_id: Option<HistoryStateId>,
71
72 pub history_states: HashSet<HistoryStateId>,
74
75 pub completely_loaded: bool,
77
78 pub title: String,
80
81 pub focus_sequence: FocusSequenceNumber,
83
84 has_active_document: bool,
88}
89
90impl Pipeline {
91 pub(crate) fn spawn<STF: ScriptThreadFactory, SWF: ServiceWorkerManagerFactory>(
93 new_pipeline_info: NewPipelineInfo,
94 event_loop: Rc<EventLoop>,
95 constellation: &Constellation<STF, SWF>,
96 webview_hidden: bool,
97 ) -> Result<Self, SendError> {
98 if let Err(error) = event_loop.send(ScriptThreadMessage::SpawnPipeline(
99 new_pipeline_info.clone(),
100 )) {
101 error!("Could not spawn Pipeline in EventLoop: {error}");
102 return Err(error);
103 }
104
105 Ok(Self::new_already_spawned(
106 new_pipeline_info.new_pipeline_id,
107 new_pipeline_info.browsing_context_id,
108 new_pipeline_info.webview_id,
109 new_pipeline_info.opener,
110 event_loop,
111 constellation.paint_proxy.clone(),
112 webview_hidden,
113 new_pipeline_info.load_data,
114 ))
115 }
116
117 #[expect(clippy::too_many_arguments)]
119 pub fn new_already_spawned(
120 id: PipelineId,
121 browsing_context_id: BrowsingContextId,
122 webview_id: WebViewId,
123 opener: Option<BrowsingContextId>,
124 event_loop: Rc<EventLoop>,
125 paint_proxy: PaintProxy,
126 webview_hidden: bool,
127 load_data: LoadData,
128 ) -> Self {
129 let pipeline = Self {
130 id,
131 browsing_context_id,
132 webview_id,
133 opener,
134 event_loop,
135 paint_proxy,
136 url: load_data.url.clone(),
137 children: vec![],
138 animation_state: AnimationState::NoAnimationsPresent,
139 document_callbacks_active: false,
140 worker_callbacks_active: FxHashSet::default(),
141 last_callbacks_active_sent_to_paint: false,
142 load_data,
143 history_state_id: None,
144 history_states: HashSet::new(),
145 completely_loaded: false,
146 title: String::new(),
147 focus_sequence: FocusSequenceNumber::default(),
148 has_active_document: true,
150 };
151
152 if webview_hidden {
153 pipeline.send_throttle_messages(true);
154 }
155
156 pipeline
157 }
158
159 pub fn send_exit_message_to_script(&self, discard_bc: DiscardBrowsingContext) -> bool {
165 debug!("{:?} Sending exit message to script", self.id);
166
167 let result = self
170 .event_loop
171 .send(ScriptThreadMessage::ExitPipeline(
172 self.webview_id,
173 self.id,
174 discard_bc,
175 ))
176 .inspect_err(|error| warn!("Sending script exit message failed ({error})."));
177 !matches!(result, Err(SendError::Disconnected))
178 }
179
180 pub fn set_activity(&self, activity: DocumentActivity) {
182 let msg = ScriptThreadMessage::SetDocumentActivity(self.id, activity);
183 if let Err(e) = self.event_loop.send(msg) {
184 warn!("Sending activity message failed ({}).", e);
185 }
186 }
187
188 pub fn to_sendable(&self) -> CompositionPipeline {
190 CompositionPipeline {
191 id: self.id,
192 webview_id: self.webview_id,
193 }
194 }
195
196 pub fn add_child(&mut self, browsing_context_id: BrowsingContextId) {
198 self.children.push(browsing_context_id);
199 }
200
201 pub fn remove_child(&mut self, browsing_context_id: BrowsingContextId) {
203 match self
204 .children
205 .iter()
206 .position(|id| *id == browsing_context_id)
207 {
208 None => {
209 warn!(
210 "Pipeline remove child already removed ({:?}).",
211 browsing_context_id
212 )
213 },
214 Some(index) => {
215 self.children.remove(index);
216 },
217 }
218 }
219
220 pub(crate) fn set_has_active_document(&mut self, has_active_document: bool) {
222 if self.has_active_document == has_active_document {
223 return;
224 }
225
226 self.has_active_document = has_active_document;
227
228 if !self.has_active_document {
230 self.send_throttle_messages(true);
231 }
232 }
233
234 pub(crate) fn send_throttle_messages(&self, throttled: bool) {
237 let throttled = !self.has_active_document || throttled;
239
240 if let Err(error) = self
241 .event_loop
242 .send(ScriptThreadMessage::SetThrottled(self.id, throttled))
243 {
244 warn!("Sending SetThrottled to script failed ({error}).");
245 }
246 self.paint_proxy.send(PaintMessage::SetThrottled(
247 self.webview_id,
248 self.id,
249 throttled,
250 ));
251 }
252}