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 #[servo_tracing::instrument(skip_all)]
354 pub(crate) fn handle_screenshot_readiness_request(
355 &mut self,
356 browsing_contexts: &FxHashMap<BrowsingContextId, BrowsingContext>,
357 pipelines: &FxHashMap<PipelineId, Pipeline>,
358 ) {
359 self.screenshot_readiness_requests
360 .push(ScreenshotReadinessRequest {
361 pipeline_states: Default::default(),
362 state: Default::default(),
363 });
364 self.send_screenshot_readiness_requests_to_pipelines(browsing_contexts, pipelines);
365 }
366
367 pub(crate) fn send_screenshot_readiness_requests_to_pipelines(
368 &mut self,
369 browsing_contexts: &FxHashMap<BrowsingContextId, BrowsingContext>,
370 pipelines: &FxHashMap<PipelineId, Pipeline>,
371 ) {
372 if self.has_pending_change() {
374 return;
375 }
376
377 for screenshot_request in self.screenshot_readiness_requests.iter_mut() {
378 if screenshot_request.state != ScreenshotRequestState::Pending {
380 continue;
381 }
382
383 screenshot_request.pipeline_states = FullyActiveBrowsingContextsIterator::new(
384 self.webview_id.into(),
385 browsing_contexts,
386 pipelines,
387 )
388 .filter_map(|browsing_context| {
389 let pipeline_id = browsing_context.pipeline_id;
390 let Some(pipeline) = pipelines.get(&pipeline_id) else {
391 return None;
393 };
394 if browsing_context.viewport_details.size == Size2D::zero() {
398 return None;
399 }
400 let _ = pipeline
401 .event_loop
402 .send(ScriptThreadMessage::RequestScreenshotReadiness(
403 pipeline.webview_id,
404 pipeline_id,
405 ));
406 Some((pipeline_id, None))
407 })
408 .collect();
409 screenshot_request.state = ScreenshotRequestState::WaitingOnScript;
410 }
411 }
412
413 #[servo_tracing::instrument(skip_all)]
414 pub(crate) fn handle_screenshot_readiness_response(
415 &mut self,
416 updated_pipeline_id: PipelineId,
417 response: ScreenshotReadinessResponse,
418 paint_proxy: &PaintProxy,
419 ) {
420 if self.screenshot_readiness_requests.is_empty() {
421 return;
422 }
423
424 self.screenshot_readiness_requests
425 .retain_mut(|screenshot_request| {
426 if screenshot_request.state != ScreenshotRequestState::WaitingOnScript {
427 return true;
428 }
429
430 let mut has_pending_pipeline = false;
431 screenshot_request
432 .pipeline_states
433 .retain(|pipeline_id, state| {
434 if *pipeline_id != updated_pipeline_id {
435 has_pending_pipeline |= state.is_none();
436 return true;
437 }
438 match response {
439 ScreenshotReadinessResponse::Ready(epoch) => {
440 *state = Some(epoch);
441 true
442 },
443 ScreenshotReadinessResponse::NoLongerActive => false,
444 }
445 });
446
447 if has_pending_pipeline {
448 return true;
449 }
450
451 let pipelines_and_epochs = screenshot_request
452 .pipeline_states
453 .iter()
454 .map(|(pipeline_id, epoch)| {
455 (
456 *pipeline_id,
457 epoch.expect("Should have an epoch when pipeline is ready."),
458 )
459 })
460 .collect();
461 paint_proxy.send(PaintMessage::ScreenshotReadinessReponse(
462 self.webview_id,
463 pipelines_and_epochs,
464 ));
465
466 false
467 });
468 }
469
470 pub(crate) fn add_viewport_details(
471 &mut self,
472 browsing_context_id: BrowsingContextId,
473 viewport_details: ViewportDetails,
474 ) {
475 self.pending_viewport_details
476 .insert(browsing_context_id, viewport_details);
477 }
478
479 pub(crate) fn take_pending_viewport_details(
480 &mut self,
481 browsing_context_id: &BrowsingContextId,
482 ) -> Option<ViewportDetails> {
483 self.pending_viewport_details.remove(browsing_context_id)
484 }
485}
486
487pub(crate) struct OngoingHistoryTraversalRequest {
490 pub traversal_request: SessionHistoryTraversalRequest,
492 pub pipelines_awaiting_activation: FxHashSet<PipelineId>,
497}