| /* 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 fnv::FnvHashMap; |
| use log::warn; |
| 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<BrowsingContextId>, |
| |
| /// The last mouse move point in the coordinate space of the Pipeline that it |
| /// happened int. |
| pub last_mouse_move_point: Point2D<f32, CSSPixel>, |
| |
| /// 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: &FnvHashMap<BrowsingContextId, BrowsingContext>, |
| ) -> Option<PipelineId> { |
| 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: &FnvHashMap<PipelineId, Pipeline>, |
| browsing_contexts: &FnvHashMap<BrowsingContextId, BrowsingContext>, |
| ) { |
| 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)); |
| } |
| } |