constellation/
constellation_webview.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use base::id::{BrowsingContextId, PipelineId, WebViewId};
6use embedder_traits::{InputEvent, MouseLeftViewportEvent, Theme};
7use euclid::Point2D;
8use log::warn;
9use rustc_hash::FxHashMap;
10use script_traits::{ConstellationInputEvent, ScriptThreadMessage};
11use style_traits::CSSPixel;
12
13use crate::browsingcontext::BrowsingContext;
14use crate::pipeline::Pipeline;
15use crate::session_history::JointSessionHistory;
16
17/// The `Constellation`'s view of a `WebView` in the embedding layer. This tracks all of the
18/// `Constellation` state for this `WebView`.
19pub(crate) struct ConstellationWebView {
20    /// The [`WebViewId`] of this [`ConstellationWebView`].
21    webview_id: WebViewId,
22
23    /// The currently focused browsing context in this webview for key events.
24    /// The focused pipeline is the current entry of the focused browsing
25    /// context.
26    pub focused_browsing_context_id: BrowsingContextId,
27
28    /// The [`BrowsingContextId`] of the currently hovered browsing context, to use for
29    /// knowing which frame is currently receiving cursor events.
30    pub hovered_browsing_context_id: Option<BrowsingContextId>,
31
32    /// The last mouse move point in the coordinate space of the Pipeline that it
33    /// happened int.
34    pub last_mouse_move_point: Point2D<f32, CSSPixel>,
35
36    /// The joint session history for this webview.
37    pub session_history: JointSessionHistory,
38
39    /// The [`Theme`] that this [`ConstellationWebView`] uses. This is communicated to all
40    /// `ScriptThread`s so that they know how to render the contents of a particular `WebView.
41    theme: Theme,
42}
43
44impl ConstellationWebView {
45    pub(crate) fn new(
46        webview_id: WebViewId,
47        focused_browsing_context_id: BrowsingContextId,
48    ) -> Self {
49        Self {
50            webview_id,
51            focused_browsing_context_id,
52            hovered_browsing_context_id: None,
53            last_mouse_move_point: Default::default(),
54            session_history: JointSessionHistory::new(),
55            theme: Theme::Light,
56        }
57    }
58
59    /// Set the [`Theme`] on this [`ConstellationWebView`] returning true if the theme changed.
60    pub(crate) fn set_theme(&mut self, new_theme: Theme) -> bool {
61        let old_theme = std::mem::replace(&mut self.theme, new_theme);
62        old_theme != self.theme
63    }
64
65    /// Get the [`Theme`] of this [`ConstellationWebView`].
66    pub(crate) fn theme(&self) -> Theme {
67        self.theme
68    }
69
70    fn target_pipeline_id_for_input_event(
71        &self,
72        event: &ConstellationInputEvent,
73        browsing_contexts: &FxHashMap<BrowsingContextId, BrowsingContext>,
74    ) -> Option<PipelineId> {
75        if let Some(hit_test_result) = &event.hit_test_result {
76            return Some(hit_test_result.pipeline_id);
77        }
78
79        // If there's no hit test, send the event to either the hovered or focused browsing context,
80        // depending on the event type.
81        let browsing_context_id = if matches!(event.event.event, InputEvent::MouseLeftViewport(_)) {
82            self.hovered_browsing_context_id
83                .unwrap_or(self.focused_browsing_context_id)
84        } else {
85            self.focused_browsing_context_id
86        };
87
88        Some(browsing_contexts.get(&browsing_context_id)?.pipeline_id)
89    }
90
91    /// Forward the [`InputEvent`] to this [`ConstellationWebView`]. Returns false if
92    /// the event could not be forwarded or true otherwise.
93    pub(crate) fn forward_input_event(
94        &mut self,
95        event: ConstellationInputEvent,
96        pipelines: &FxHashMap<PipelineId, Pipeline>,
97        browsing_contexts: &FxHashMap<BrowsingContextId, BrowsingContext>,
98    ) -> bool {
99        let Some(pipeline_id) = self.target_pipeline_id_for_input_event(&event, browsing_contexts)
100        else {
101            warn!("Unknown pipeline for input event. Ignoring.");
102            return false;
103        };
104        let Some(pipeline) = pipelines.get(&pipeline_id) else {
105            warn!("Unknown pipeline id {pipeline_id:?} for input event. Ignoring.");
106            return false;
107        };
108
109        let mut update_hovered_browsing_context =
110            |newly_hovered_browsing_context_id, focus_moving_to_another_iframe: bool| {
111                let old_hovered_context_id = std::mem::replace(
112                    &mut self.hovered_browsing_context_id,
113                    newly_hovered_browsing_context_id,
114                );
115                if old_hovered_context_id == newly_hovered_browsing_context_id {
116                    return;
117                }
118                let Some(old_hovered_context_id) = old_hovered_context_id else {
119                    return;
120                };
121                let Some(pipeline) = browsing_contexts
122                    .get(&old_hovered_context_id)
123                    .and_then(|browsing_context| pipelines.get(&browsing_context.pipeline_id))
124                else {
125                    return;
126                };
127
128                let mut synthetic_mouse_leave_event = event.clone();
129                synthetic_mouse_leave_event.event.event =
130                    InputEvent::MouseLeftViewport(MouseLeftViewportEvent {
131                        focus_moving_to_another_iframe,
132                    });
133
134                let _ = pipeline
135                    .event_loop
136                    .send(ScriptThreadMessage::SendInputEvent(
137                        self.webview_id,
138                        pipeline.id,
139                        synthetic_mouse_leave_event,
140                    ));
141            };
142
143        if let InputEvent::MouseLeftViewport(_) = &event.event.event {
144            update_hovered_browsing_context(None, false);
145            return true;
146        }
147
148        if let InputEvent::MouseMove(_) = &event.event.event {
149            update_hovered_browsing_context(Some(pipeline.browsing_context_id), true);
150            self.last_mouse_move_point = event
151                .hit_test_result
152                .as_ref()
153                .expect("MouseMove events should always have hit tests.")
154                .point_in_viewport;
155        }
156
157        let _ = pipeline
158            .event_loop
159            .send(ScriptThreadMessage::SendInputEvent(
160                self.webview_id,
161                pipeline.id,
162                event,
163            ));
164        true
165    }
166}