1use std::collections::HashSet;
6use std::rc::Rc;
7
8use embedder_traits::{AnimationState, FocusSequenceNumber};
9use layout_api::ScriptThreadFactory;
10use log::{debug, error, warn};
11use paint_api::{CompositionPipeline, PaintMessage, PaintProxy};
12use rustc_hash::FxHashSet;
13use script_traits::{
14 DiscardBrowsingContext, DocumentActivity, NewPipelineInfo, ScriptThreadMessage,
15};
16use servo_base::generic_channel::SendError;
17use servo_base::id::{BrowsingContextId, HistoryStateId, PipelineId, WebViewId, WorkerId};
18use servo_constellation_traits::{LoadData, ServiceWorkerManagerFactory};
19use servo_url::ServoUrl;
20
21use crate::Constellation;
22use crate::event_loop::EventLoop;
23
24pub struct Pipeline {
27 pub id: PipelineId,
29
30 pub browsing_context_id: BrowsingContextId,
32
33 pub webview_id: WebViewId,
35
36 pub opener: Option<BrowsingContextId>,
37
38 pub event_loop: Rc<EventLoop>,
40
41 pub paint_proxy: PaintProxy,
43
44 pub url: ServoUrl,
48
49 pub animation_state: AnimationState,
52
53 pub document_callbacks_active: bool,
55
56 pub worker_callbacks_active: FxHashSet<WorkerId>,
58
59 pub last_callbacks_active_sent_to_paint: bool,
61
62 pub children: Vec<BrowsingContextId>,
64
65 pub load_data: LoadData,
67
68 pub history_state_id: Option<HistoryStateId>,
70
71 pub history_states: HashSet<HistoryStateId>,
73
74 pub completely_loaded: bool,
76
77 pub title: String,
79
80 pub focus_sequence: FocusSequenceNumber,
82
83 has_active_document: bool,
87}
88
89impl Pipeline {
90 pub(crate) fn spawn<STF: ScriptThreadFactory, SWF: ServiceWorkerManagerFactory>(
92 new_pipeline_info: NewPipelineInfo,
93 event_loop: Rc<EventLoop>,
94 constellation: &Constellation<STF, SWF>,
95 webview_hidden: bool,
96 ) -> Result<Self, SendError> {
97 if let Err(error) = event_loop.send(ScriptThreadMessage::SpawnPipeline(
98 new_pipeline_info.clone(),
99 )) {
100 error!("Could not spawn Pipeline in EventLoop: {error}");
101 return Err(error);
102 }
103
104 Ok(Self::new_already_spawned(
105 new_pipeline_info.new_pipeline_id,
106 new_pipeline_info.browsing_context_id,
107 new_pipeline_info.webview_state.id,
108 new_pipeline_info.opener,
109 event_loop,
110 constellation.paint_proxy.clone(),
111 webview_hidden,
112 new_pipeline_info.load_data,
113 ))
114 }
115
116 #[expect(clippy::too_many_arguments)]
118 pub fn new_already_spawned(
119 id: PipelineId,
120 browsing_context_id: BrowsingContextId,
121 webview_id: WebViewId,
122 opener: Option<BrowsingContextId>,
123 event_loop: Rc<EventLoop>,
124 paint_proxy: PaintProxy,
125 webview_hidden: bool,
126 load_data: LoadData,
127 ) -> Self {
128 let pipeline = Self {
129 id,
130 browsing_context_id,
131 webview_id,
132 opener,
133 event_loop,
134 paint_proxy,
135 url: load_data.url.clone(),
136 children: vec![],
137 animation_state: AnimationState::NoAnimationsPresent,
138 document_callbacks_active: false,
139 worker_callbacks_active: FxHashSet::default(),
140 last_callbacks_active_sent_to_paint: false,
141 load_data,
142 history_state_id: None,
143 history_states: HashSet::new(),
144 completely_loaded: false,
145 title: String::new(),
146 focus_sequence: FocusSequenceNumber::default(),
147 has_active_document: true,
149 };
150
151 if webview_hidden {
152 pipeline.send_throttle_messages(true);
153 }
154
155 pipeline
156 }
157
158 pub fn send_exit_message_to_script(&self, discard_bc: DiscardBrowsingContext) -> bool {
164 debug!("{:?} Sending exit message to script", self.id);
165
166 let result = self
169 .event_loop
170 .send(ScriptThreadMessage::ExitPipeline(
171 self.webview_id,
172 self.id,
173 discard_bc,
174 ))
175 .inspect_err(|error| warn!("Sending script exit message failed ({error})."));
176 !matches!(result, Err(SendError::Disconnected))
177 }
178
179 pub fn set_activity(&self, activity: DocumentActivity) {
181 let msg = ScriptThreadMessage::SetDocumentActivity(self.id, activity);
182 if let Err(e) = self.event_loop.send(msg) {
183 warn!("Sending activity message failed ({}).", e);
184 }
185 }
186
187 pub fn to_sendable(&self) -> CompositionPipeline {
189 CompositionPipeline {
190 id: self.id,
191 webview_id: self.webview_id,
192 }
193 }
194
195 pub fn add_child(&mut self, browsing_context_id: BrowsingContextId) {
197 self.children.push(browsing_context_id);
198 }
199
200 pub fn remove_child(&mut self, browsing_context_id: BrowsingContextId) {
202 match self
203 .children
204 .iter()
205 .position(|id| *id == browsing_context_id)
206 {
207 None => {
208 warn!(
209 "Pipeline remove child already removed ({:?}).",
210 browsing_context_id
211 )
212 },
213 Some(index) => {
214 self.children.remove(index);
215 },
216 }
217 }
218
219 pub(crate) fn set_has_active_document(&mut self, has_active_document: bool) {
221 if self.has_active_document == has_active_document {
222 return;
223 }
224
225 self.has_active_document = has_active_document;
226
227 if !self.has_active_document {
229 self.send_throttle_messages(true);
230 }
231 }
232
233 pub(crate) fn send_throttle_messages(&self, throttled: bool) {
236 let throttled = !self.has_active_document || throttled;
238
239 if let Err(error) = self
240 .event_loop
241 .send(ScriptThreadMessage::SetThrottled(self.id, throttled))
242 {
243 warn!("Sending SetThrottled to script failed ({error}).");
244 }
245 self.paint_proxy.send(PaintMessage::SetThrottled(
246 self.webview_id,
247 self.id,
248 throttled,
249 ));
250 }
251}