Skip to main content

servo_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 embedder_traits::user_contents::UserContentManagerId;
6use embedder_traits::{InputEvent, MouseLeftViewportEvent, Theme};
7use euclid::Point2D;
8use log::warn;
9use rustc_hash::FxHashMap;
10use script_traits::{ConstellationInputEvent, ScriptThreadMessage};
11use servo_base::Epoch;
12use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
13use style_traits::CSSPixel;
14
15use crate::browsingcontext::BrowsingContext;
16use crate::pipeline::Pipeline;
17use crate::session_history::JointSessionHistory;
18
19/// The `Constellation`'s view of a `WebView` in the embedding layer. This tracks all of the
20/// `Constellation` state for this `WebView`.
21pub(crate) struct ConstellationWebView {
22    /// The [`WebViewId`] of this [`ConstellationWebView`].
23    webview_id: WebViewId,
24
25    /// The [`PipelineId`] of the currently active pipeline at the top level of this WebView.
26    pub active_top_level_pipeline_id: Option<PipelineId>,
27    /// A counter for changes to [`Self::active_top_level_pipeline_id`].
28    pub active_top_level_pipeline_epoch: Epoch,
29
30    /// The currently focused browsing context in this webview for key events.
31    /// The focused pipeline is the current entry of the focused browsing
32    /// context.
33    pub focused_browsing_context_id: BrowsingContextId,
34
35    /// The [`BrowsingContextId`] of the currently hovered browsing context, to use for
36    /// knowing which frame is currently receiving cursor events.
37    pub hovered_browsing_context_id: Option<BrowsingContextId>,
38
39    /// The last mouse move point in the coordinate space of the Pipeline that it
40    /// happened int.
41    pub last_mouse_move_point: Point2D<f32, CSSPixel>,
42
43    /// The joint session history for this webview.
44    pub session_history: JointSessionHistory,
45
46    /// The [`UserContentManagerId`] for all pipelines in this `WebView`. This is `Some`
47    /// if the embedder has set a `UserContentManager` using the WebViewBuilder API and
48    /// it is `None` otherwise.
49    pub user_content_manager_id: Option<UserContentManagerId>,
50
51    /// The [`Theme`] that this [`ConstellationWebView`] uses. This is communicated to all
52    /// `ScriptThread`s so that they know how to render the contents of a particular `WebView.
53    theme: Theme,
54
55    /// Whether accessibility is active for this webview.
56    ///
57    /// Set by [`crate::Constellation::set_accessibility_active()`], and forwarded to the
58    /// webview’s *active* pipelines (of those that represent documents) at any given moment
59    /// via [`ScriptThreadMessage::SetAccessibilityActive`] in `set_accessibility_active()`
60    /// and [`crate::Constellation::set_frame_tree_for_webview()`].
61    pub accessibility_active: bool,
62}
63
64impl ConstellationWebView {
65    pub(crate) fn new(
66        webview_id: WebViewId,
67        focused_browsing_context_id: BrowsingContextId,
68        user_content_manager_id: Option<UserContentManagerId>,
69    ) -> Self {
70        Self {
71            webview_id,
72            user_content_manager_id,
73            active_top_level_pipeline_id: None,
74            active_top_level_pipeline_epoch: Epoch::default(),
75            focused_browsing_context_id,
76            hovered_browsing_context_id: None,
77            last_mouse_move_point: Default::default(),
78            session_history: JointSessionHistory::new(),
79            theme: Theme::Light,
80            accessibility_active: false,
81        }
82    }
83
84    /// Set the [`Theme`] on this [`ConstellationWebView`] returning true if the theme changed.
85    pub(crate) fn set_theme(&mut self, new_theme: Theme) -> bool {
86        let old_theme = std::mem::replace(&mut self.theme, new_theme);
87        old_theme != self.theme
88    }
89
90    /// Get the [`Theme`] of this [`ConstellationWebView`].
91    pub(crate) fn theme(&self) -> Theme {
92        self.theme
93    }
94
95    fn target_pipeline_id_for_input_event(
96        &self,
97        event: &ConstellationInputEvent,
98        browsing_contexts: &FxHashMap<BrowsingContextId, BrowsingContext>,
99    ) -> Option<PipelineId> {
100        if let Some(hit_test_result) = &event.hit_test_result {
101            return Some(hit_test_result.pipeline_id);
102        }
103
104        // If there's no hit test, send the event to either the hovered or focused browsing context,
105        // depending on the event type.
106        let browsing_context_id = if matches!(event.event.event, InputEvent::MouseLeftViewport(_)) {
107            self.hovered_browsing_context_id
108                .unwrap_or(self.focused_browsing_context_id)
109        } else {
110            self.focused_browsing_context_id
111        };
112
113        Some(browsing_contexts.get(&browsing_context_id)?.pipeline_id)
114    }
115
116    /// Forward the [`InputEvent`] to this [`ConstellationWebView`]. Returns false if
117    /// the event could not be forwarded or true otherwise.
118    pub(crate) fn forward_input_event(
119        &mut self,
120        event: ConstellationInputEvent,
121        pipelines: &FxHashMap<PipelineId, Pipeline>,
122        browsing_contexts: &FxHashMap<BrowsingContextId, BrowsingContext>,
123    ) -> bool {
124        let Some(pipeline_id) = self.target_pipeline_id_for_input_event(&event, browsing_contexts)
125        else {
126            warn!("Unknown pipeline for input event. Ignoring.");
127            return false;
128        };
129        let Some(pipeline) = pipelines.get(&pipeline_id) else {
130            warn!("Unknown pipeline id {pipeline_id:?} for input event. Ignoring.");
131            return false;
132        };
133
134        let mut update_hovered_browsing_context =
135            |newly_hovered_browsing_context_id, focus_moving_to_another_iframe: bool| {
136                let old_hovered_context_id = std::mem::replace(
137                    &mut self.hovered_browsing_context_id,
138                    newly_hovered_browsing_context_id,
139                );
140                if old_hovered_context_id == newly_hovered_browsing_context_id {
141                    return;
142                }
143                let Some(old_hovered_context_id) = old_hovered_context_id else {
144                    return;
145                };
146                let Some(pipeline) = browsing_contexts
147                    .get(&old_hovered_context_id)
148                    .and_then(|browsing_context| pipelines.get(&browsing_context.pipeline_id))
149                else {
150                    return;
151                };
152
153                let mut synthetic_mouse_leave_event = event.clone();
154                synthetic_mouse_leave_event.event.event =
155                    InputEvent::MouseLeftViewport(MouseLeftViewportEvent {
156                        focus_moving_to_another_iframe,
157                    });
158
159                let _ = pipeline
160                    .event_loop
161                    .send(ScriptThreadMessage::SendInputEvent(
162                        self.webview_id,
163                        pipeline.id,
164                        synthetic_mouse_leave_event,
165                    ));
166            };
167
168        if let InputEvent::MouseLeftViewport(_) = &event.event.event {
169            update_hovered_browsing_context(None, false);
170            return true;
171        }
172
173        if let InputEvent::MouseMove(_) = &event.event.event {
174            update_hovered_browsing_context(Some(pipeline.browsing_context_id), true);
175            self.last_mouse_move_point = event
176                .hit_test_result
177                .as_ref()
178                .expect("MouseMove events should always have hit tests.")
179                .point_in_viewport;
180        }
181
182        let _ = pipeline
183            .event_loop
184            .send(ScriptThreadMessage::SendInputEvent(
185                self.webview_id,
186                pipeline.id,
187                event,
188            ));
189        true
190    }
191}