/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use base::id::{BrowsingContextId, PipelineId}; use embedder_traits::{InputEvent, MouseLeftViewportEvent, Theme}; use euclid::Point2D; use log::warn; use rustc_hash::FxHashMap; use script_traits::{ConstellationInputEvent, ScriptThreadMessage}; use style_traits::CSSPixel; use crate::browsingcontext::BrowsingContext; use crate::pipeline::Pipeline; use crate::session_history::JointSessionHistory; /// The `Constellation`'s view of a `WebView` in the embedding layer. This tracks all of the /// `Constellation` state for this `WebView`. pub(crate) struct ConstellationWebView { /// The currently focused browsing context in this webview for key events. /// The focused pipeline is the current entry of the focused browsing /// context. pub focused_browsing_context_id: BrowsingContextId, /// The [`BrowsingContextId`] of the currently hovered browsing context, to use for /// knowing which frame is currently receiving cursor events. pub hovered_browsing_context_id: Option, /// The last mouse move point in the coordinate space of the Pipeline that it /// happened int. pub last_mouse_move_point: Point2D, /// The joint session history for this webview. pub session_history: JointSessionHistory, /// The [`Theme`] that this [`ConstellationWebView`] uses. This is communicated to all /// `ScriptThread`s so that they know how to render the contents of a particular `WebView. theme: Theme, } impl ConstellationWebView { pub(crate) fn new(focused_browsing_context_id: BrowsingContextId) -> Self { Self { focused_browsing_context_id, hovered_browsing_context_id: None, last_mouse_move_point: Default::default(), session_history: JointSessionHistory::new(), theme: Theme::Light, } } /// Set the [`Theme`] on this [`ConstellationWebView`] returning true if the theme changed. pub(crate) fn set_theme(&mut self, new_theme: Theme) -> bool { let old_theme = std::mem::replace(&mut self.theme, new_theme); old_theme != self.theme } /// Get the [`Theme`] of this [`ConstellationWebView`]. pub(crate) fn theme(&self) -> Theme { self.theme } fn target_pipeline_id_for_input_event( &self, event: &ConstellationInputEvent, browsing_contexts: &FxHashMap, ) -> Option { if let Some(hit_test_result) = &event.hit_test_result { return Some(hit_test_result.pipeline_id); } // If there's no hit test, send the event to either the hovered or focused browsing context, // depending on the event type. let browsing_context_id = if matches!(event.event, InputEvent::MouseLeftViewport(_)) { self.hovered_browsing_context_id .unwrap_or(self.focused_browsing_context_id) } else { self.focused_browsing_context_id }; Some(browsing_contexts.get(&browsing_context_id)?.pipeline_id) } pub(crate) fn forward_input_event( &mut self, event: ConstellationInputEvent, pipelines: &FxHashMap, browsing_contexts: &FxHashMap, ) { let Some(pipeline_id) = self.target_pipeline_id_for_input_event(&event, browsing_contexts) else { warn!("Unknown pipeline for input event. Ignoring."); return; }; let Some(pipeline) = pipelines.get(&pipeline_id) else { warn!("Unknown pipeline id {pipeline_id:?} for input event. Ignoring."); return; }; let mut update_hovered_browsing_context = |newly_hovered_browsing_context_id, focus_moving_to_another_iframe: bool| { let old_hovered_context_id = std::mem::replace( &mut self.hovered_browsing_context_id, newly_hovered_browsing_context_id, ); if old_hovered_context_id == newly_hovered_browsing_context_id { return; } let Some(old_hovered_context_id) = old_hovered_context_id else { return; }; let Some(pipeline) = browsing_contexts .get(&old_hovered_context_id) .and_then(|browsing_context| pipelines.get(&browsing_context.pipeline_id)) else { return; }; let mut synthetic_mouse_leave_event = event.clone(); synthetic_mouse_leave_event.event = InputEvent::MouseLeftViewport(MouseLeftViewportEvent { focus_moving_to_another_iframe, }); let _ = pipeline .event_loop .send(ScriptThreadMessage::SendInputEvent( pipeline.id, synthetic_mouse_leave_event, )); }; if let InputEvent::MouseLeftViewport(_) = &event.event { update_hovered_browsing_context(None, false); return; } if let InputEvent::MouseMove(_) = &event.event { update_hovered_browsing_context(Some(pipeline.browsing_context_id), true); self.last_mouse_move_point = event .hit_test_result .as_ref() .expect("MouseMove events should always have hit tests.") .point_in_viewport; } let _ = pipeline .event_loop .send(ScriptThreadMessage::SendInputEvent(pipeline.id, event)); } }