1use std::cell::{Cell, RefCell};
6use std::collections::{HashMap, VecDeque};
7use std::rc::Rc;
8
9use embedder_traits::user_contents::UserContentManagerId;
10use embedder_traits::{InputEvent, MouseLeftViewportEvent, Theme, ViewportDetails};
11use euclid::{Point2D, Size2D};
12use log::{debug, warn};
13use paint_api::{PaintMessage, PaintProxy};
14use rustc_hash::{FxHashMap, FxHashSet};
15use script_traits::{ConstellationInputEvent, ScriptThreadMessage, WebViewState};
16use servo_base::Epoch;
17use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
18use servo_constellation_traits::{ScreenshotReadinessResponse, SessionHistoryTraversalRequest};
19use style_traits::CSSPixel;
20
21use crate::browsingcontext::{BrowsingContext, FullyActiveBrowsingContextsIterator};
22use crate::constellation::BrowsingContextGroup;
23use crate::pipeline::Pipeline;
24use crate::screenshot_readiness_request::{ScreenshotReadinessRequest, ScreenshotRequestState};
25use crate::session_history::{JointSessionHistory, SessionHistoryChange};
26
27pub(crate) struct ConstellationWebView {
30 webview_id: WebViewId,
32
33 pub active_top_level_pipeline_id: Option<PipelineId>,
35
36 pub active_top_level_pipeline_epoch: Epoch,
38
39 pub pending_changes: Vec<SessionHistoryChange>,
45
46 browsing_context_group: Rc<RefCell<BrowsingContextGroup>>,
50
51 pub focused_browsing_context_id: BrowsingContextId,
55
56 pub hovered_browsing_context_id: Option<BrowsingContextId>,
59
60 pub last_mouse_move_point: Point2D<f32, CSSPixel>,
63
64 pub session_history: JointSessionHistory,
66
67 pub session_history_traversal_request_queue: VecDeque<SessionHistoryTraversalRequest>,
72
73 pub ongoing_history_traversal_request: Option<OngoingHistoryTraversalRequest>,
76
77 pending_viewport_details: HashMap<BrowsingContextId, ViewportDetails>,
80
81 pub user_content_manager_id: Option<UserContentManagerId>,
85
86 theme: Theme,
89
90 hidden: bool,
93
94 has_system_focus: bool,
98
99 pub accessibility_active: bool,
106
107 screenshot_readiness_requests: Vec<ScreenshotReadinessRequest>,
111}
112
113impl ConstellationWebView {
114 pub(crate) fn new(
115 webview_id: WebViewId,
116 browsing_context_group: Rc<RefCell<BrowsingContextGroup>>,
117 focused_browsing_context_id: BrowsingContextId,
118 user_content_manager_id: Option<UserContentManagerId>,
119 ) -> Self {
120 Self {
121 webview_id,
122 user_content_manager_id,
123 active_top_level_pipeline_id: None,
124 active_top_level_pipeline_epoch: Epoch::default(),
125 pending_changes: Default::default(),
126 browsing_context_group,
127 focused_browsing_context_id,
128 hovered_browsing_context_id: None,
129 last_mouse_move_point: Default::default(),
130 session_history: JointSessionHistory::new(),
131 session_history_traversal_request_queue: Default::default(),
132 ongoing_history_traversal_request: None,
133 pending_viewport_details: Default::default(),
134 theme: Theme::Light,
135 hidden: false,
136 has_system_focus: true,
137 accessibility_active: false,
138 screenshot_readiness_requests: Default::default(),
139 }
140 }
141
142 pub(crate) fn browsing_context_group(&self) -> Rc<RefCell<BrowsingContextGroup>> {
143 self.browsing_context_group.clone()
144 }
145
146 pub(crate) fn set_theme(&mut self, new_theme: Theme) -> bool {
148 let old_theme = std::mem::replace(&mut self.theme, new_theme);
149 old_theme != self.theme
150 }
151
152 pub(crate) fn theme(&self) -> Theme {
154 self.theme
155 }
156
157 pub(crate) fn hidden(&self) -> bool {
159 self.hidden
160 }
161
162 pub(crate) fn state(&self) -> WebViewState {
165 WebViewState {
166 id: self.webview_id,
167 theme: Cell::new(self.theme),
168 has_system_focus: Cell::new(self.has_system_focus),
169 }
170 }
171
172 pub(crate) fn set_hidden(&mut self, hidden: bool) -> bool {
175 let old_hidden = std::mem::replace(&mut self.hidden, hidden);
176 old_hidden != self.hidden
177 }
178
179 pub(crate) fn set_has_system_focus(&mut self, has_system_focus: bool) -> bool {
182 let old_has_system_focus = std::mem::replace(&mut self.has_system_focus, has_system_focus);
183 old_has_system_focus != self.has_system_focus
184 }
185
186 fn target_pipeline_id_for_input_event(
187 &self,
188 event: &ConstellationInputEvent,
189 browsing_contexts: &FxHashMap<BrowsingContextId, BrowsingContext>,
190 ) -> Option<PipelineId> {
191 if let Some(hit_test_result) = &event.hit_test_result {
192 return Some(hit_test_result.pipeline_id);
193 }
194
195 let browsing_context_id = if matches!(event.event.event, InputEvent::MouseLeftViewport(_)) {
198 self.hovered_browsing_context_id
199 .unwrap_or(self.focused_browsing_context_id)
200 } else {
201 self.focused_browsing_context_id
202 };
203
204 Some(browsing_contexts.get(&browsing_context_id)?.pipeline_id)
205 }
206
207 pub(crate) fn forward_input_event(
210 &mut self,
211 event: ConstellationInputEvent,
212 pipelines: &FxHashMap<PipelineId, Pipeline>,
213 browsing_contexts: &FxHashMap<BrowsingContextId, BrowsingContext>,
214 ) -> bool {
215 let Some(pipeline_id) = self.target_pipeline_id_for_input_event(&event, browsing_contexts)
216 else {
217 warn!("Unknown pipeline for input event. Ignoring.");
218 return false;
219 };
220 let Some(pipeline) = pipelines.get(&pipeline_id) else {
221 warn!("Unknown pipeline id {pipeline_id:?} for input event. Ignoring.");
222 return false;
223 };
224
225 let mut update_hovered_browsing_context =
226 |newly_hovered_browsing_context_id, focus_moving_to_another_iframe: bool| {
227 let old_hovered_context_id = std::mem::replace(
228 &mut self.hovered_browsing_context_id,
229 newly_hovered_browsing_context_id,
230 );
231 if old_hovered_context_id == newly_hovered_browsing_context_id {
232 return;
233 }
234 let Some(old_hovered_context_id) = old_hovered_context_id else {
235 return;
236 };
237 let Some(pipeline) = browsing_contexts
238 .get(&old_hovered_context_id)
239 .and_then(|browsing_context| pipelines.get(&browsing_context.pipeline_id))
240 else {
241 return;
242 };
243
244 let mut synthetic_mouse_leave_event = event.clone();
245 synthetic_mouse_leave_event.event.event =
246 InputEvent::MouseLeftViewport(MouseLeftViewportEvent {
247 focus_moving_to_another_iframe,
248 });
249
250 let _ = pipeline
251 .event_loop
252 .send(ScriptThreadMessage::SendInputEvent(
253 self.webview_id,
254 pipeline.id,
255 synthetic_mouse_leave_event,
256 ));
257 };
258
259 if let InputEvent::MouseLeftViewport(_) = &event.event.event {
260 update_hovered_browsing_context(None, false);
261 return true;
262 }
263
264 if let InputEvent::MouseMove(_) = &event.event.event {
265 update_hovered_browsing_context(Some(pipeline.browsing_context_id), true);
266 self.last_mouse_move_point = event
267 .hit_test_result
268 .as_ref()
269 .expect("MouseMove events should always have hit tests.")
270 .point_in_viewport;
271 }
272
273 let _ = pipeline
274 .event_loop
275 .send(ScriptThreadMessage::SendInputEvent(
276 self.webview_id,
277 pipeline.id,
278 event,
279 ));
280 true
281 }
282
283 pub(crate) fn close_browsing_context(&mut self, browsing_context_id: BrowsingContextId) {
284 self.take_pending_viewport_details(&browsing_context_id);
285 self.session_history
286 .remove_entries_for_browsing_context(browsing_context_id);
287 }
288
289 pub(crate) fn maybe_finish_ongoing_session_history_traversal_request(
293 &mut self,
294 ) -> Option<SessionHistoryTraversalRequest> {
295 let ongoing_history_traversal_request = self.ongoing_history_traversal_request.as_mut()?;
296
297 let pipelines_with_pending_changes = self
298 .pending_changes
299 .iter()
300 .map(|change| change.new_pipeline_id)
301 .collect::<FxHashSet<_>>();
302 ongoing_history_traversal_request
303 .pipelines_awaiting_activation
304 .retain(|pipeline_id| pipelines_with_pending_changes.contains(pipeline_id));
305
306 if !ongoing_history_traversal_request
307 .pipelines_awaiting_activation
308 .is_empty()
309 {
310 return None;
311 }
312 Some(
313 self.ongoing_history_traversal_request
314 .take()
315 .expect("Guaranteed above")
316 .traversal_request,
317 )
318 }
319
320 pub(crate) fn has_pending_change(&self) -> bool {
321 !self.pending_changes.is_empty()
322 }
323
324 pub(crate) fn pipeline_is_pending(&self, pipeline_id: PipelineId) -> bool {
325 self.pending_changes
326 .iter()
327 .any(|pending_change| pending_change.new_pipeline_id == pipeline_id)
328 }
329
330 pub(crate) fn add_pending_change(&mut self, change: SessionHistoryChange) {
331 debug!(
332 "adding pending session history change with {}",
333 if change.replace.is_some() {
334 "replacement"
335 } else {
336 "no replacement"
337 },
338 );
339 self.pending_changes.push(change);
340 }
341
342 pub(crate) fn remove_pending_change_for_pipeline(
343 &mut self,
344 pipeline_id: PipelineId,
345 ) -> Option<SessionHistoryChange> {
346 let pending_index = self
347 .pending_changes
348 .iter()
349 .rposition(|change| change.new_pipeline_id == pipeline_id)?;
350 Some(self.pending_changes.swap_remove(pending_index))
351 }
352
353 pub(crate) fn remove_pending_changes_for_browsing_context_id(
354 &mut self,
355 browsing_context_id: BrowsingContextId,
356 ) -> Vec<SessionHistoryChange> {
357 self.pending_changes
358 .extract_if(.., |change| {
359 change.browsing_context_id == browsing_context_id
360 })
361 .collect()
362 }
363
364 #[servo_tracing::instrument(skip_all)]
365 pub(crate) fn handle_screenshot_readiness_request(
366 &mut self,
367 browsing_contexts: &FxHashMap<BrowsingContextId, BrowsingContext>,
368 pipelines: &FxHashMap<PipelineId, Pipeline>,
369 ) {
370 self.screenshot_readiness_requests
371 .push(ScreenshotReadinessRequest {
372 pipeline_states: Default::default(),
373 state: Default::default(),
374 });
375 self.send_screenshot_readiness_requests_to_pipelines(browsing_contexts, pipelines);
376 }
377
378 pub(crate) fn send_screenshot_readiness_requests_to_pipelines(
379 &mut self,
380 browsing_contexts: &FxHashMap<BrowsingContextId, BrowsingContext>,
381 pipelines: &FxHashMap<PipelineId, Pipeline>,
382 ) {
383 if self.has_pending_change() {
385 return;
386 }
387
388 for screenshot_request in self.screenshot_readiness_requests.iter_mut() {
389 if screenshot_request.state != ScreenshotRequestState::Pending {
391 continue;
392 }
393
394 screenshot_request.pipeline_states = FullyActiveBrowsingContextsIterator::new(
395 self.webview_id.into(),
396 browsing_contexts,
397 pipelines,
398 )
399 .filter_map(|browsing_context| {
400 let pipeline_id = browsing_context.pipeline_id;
401 let Some(pipeline) = pipelines.get(&pipeline_id) else {
402 return None;
404 };
405 if browsing_context.viewport_details.size == Size2D::zero() {
409 return None;
410 }
411 let _ = pipeline
412 .event_loop
413 .send(ScriptThreadMessage::RequestScreenshotReadiness(
414 pipeline.webview_id,
415 pipeline_id,
416 ));
417 Some((pipeline_id, None))
418 })
419 .collect();
420 screenshot_request.state = ScreenshotRequestState::WaitingOnScript;
421 }
422 }
423
424 #[servo_tracing::instrument(skip_all)]
425 pub(crate) fn handle_screenshot_readiness_response(
426 &mut self,
427 updated_pipeline_id: PipelineId,
428 response: ScreenshotReadinessResponse,
429 paint_proxy: &PaintProxy,
430 ) {
431 if self.screenshot_readiness_requests.is_empty() {
432 return;
433 }
434
435 self.screenshot_readiness_requests
436 .retain_mut(|screenshot_request| {
437 if screenshot_request.state != ScreenshotRequestState::WaitingOnScript {
438 return true;
439 }
440
441 let mut has_pending_pipeline = false;
442 screenshot_request
443 .pipeline_states
444 .retain(|pipeline_id, state| {
445 if *pipeline_id != updated_pipeline_id {
446 has_pending_pipeline |= state.is_none();
447 return true;
448 }
449 match response {
450 ScreenshotReadinessResponse::Ready(epoch) => {
451 *state = Some(epoch);
452 true
453 },
454 ScreenshotReadinessResponse::NoLongerActive => false,
455 }
456 });
457
458 if has_pending_pipeline {
459 return true;
460 }
461
462 let pipelines_and_epochs = screenshot_request
463 .pipeline_states
464 .iter()
465 .map(|(pipeline_id, epoch)| {
466 (
467 *pipeline_id,
468 epoch.expect("Should have an epoch when pipeline is ready."),
469 )
470 })
471 .collect();
472 paint_proxy.send(PaintMessage::ScreenshotReadinessReponse(
473 self.webview_id,
474 pipelines_and_epochs,
475 ));
476
477 false
478 });
479 }
480
481 pub(crate) fn add_viewport_details(
482 &mut self,
483 browsing_context_id: BrowsingContextId,
484 viewport_details: ViewportDetails,
485 ) {
486 self.pending_viewport_details
487 .insert(browsing_context_id, viewport_details);
488 }
489
490 pub(crate) fn take_pending_viewport_details(
491 &mut self,
492 browsing_context_id: &BrowsingContextId,
493 ) -> Option<ViewportDetails> {
494 self.pending_viewport_details.remove(browsing_context_id)
495 }
496}
497
498pub(crate) struct OngoingHistoryTraversalRequest {
501 pub traversal_request: SessionHistoryTraversalRequest,
503 pub pipelines_awaiting_activation: FxHashSet<PipelineId>,
508}