1use std::cell::{Cell, RefCell};
6use std::rc::Rc;
7
8use euclid::Scale;
9use servo::{
10 AuthenticationRequest, Cursor, DeviceIndependentIntRect, DeviceIndependentPixel,
11 DeviceIntPoint, DeviceIntSize, DevicePixel, EmbedderControl, EmbedderControlId, GenericSender,
12 InputEventId, InputEventResult, MediaSessionEvent, PermissionRequest, RenderingContext,
13 ScreenGeometry, WebView, WebViewBuilder, WebViewId,
14};
15use url::Url;
16
17use crate::running_app_state::{RunningAppState, UserInterfaceCommand, WebViewCollection};
18
19#[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
21pub(crate) const LINE_HEIGHT: f32 = 76.0;
22#[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
23pub(crate) const LINE_WIDTH: f32 = 76.0;
24
25#[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
28pub(crate) const MIN_WINDOW_INNER_SIZE: DeviceIntSize = DeviceIntSize::new(100, 100);
29
30#[derive(Copy, Clone, Eq, Hash, PartialEq)]
31pub(crate) struct ServoShellWindowId(u64);
32
33impl From<u64> for ServoShellWindowId {
34 fn from(value: u64) -> Self {
35 Self(value)
36 }
37}
38
39pub(crate) struct ServoShellWindow {
40 pub(crate) webview_collection: RefCell<WebViewCollection>,
42 platform_window: Rc<dyn PlatformWindow>,
44 close_scheduled: Cell<bool>,
46 needs_update: Cell<bool>,
48 needs_repaint: Cell<bool>,
51 pending_favicon_loads: RefCell<Vec<WebViewId>>,
54}
55
56impl ServoShellWindow {
57 pub(crate) fn new(platform_window: Rc<dyn PlatformWindow>) -> Self {
58 Self {
59 webview_collection: Default::default(),
60 platform_window,
61 close_scheduled: Default::default(),
62 needs_update: Default::default(),
63 needs_repaint: Default::default(),
64 pending_favicon_loads: Default::default(),
65 }
66 }
67
68 pub(crate) fn id(&self) -> ServoShellWindowId {
69 self.platform_window().id()
70 }
71
72 pub(crate) fn create_and_activate_toplevel_webview(
73 &self,
74 state: Rc<RunningAppState>,
75 url: Url,
76 ) -> WebView {
77 let webview = self.create_toplevel_webview(state, url);
78 self.activate_webview(webview.id());
79 webview
80 }
81
82 pub(crate) fn create_toplevel_webview(&self, state: Rc<RunningAppState>, url: Url) -> WebView {
83 let webview = WebViewBuilder::new(state.servo(), self.platform_window.rendering_context())
84 .url(url)
85 .hidpi_scale_factor(self.platform_window.hidpi_scale_factor())
86 .delegate(state.clone())
87 .build();
88
89 webview.notify_theme_change(self.platform_window.theme());
90 self.add_webview(webview.clone());
91 webview
92 }
93
94 pub(crate) fn repaint_webviews(&self) {
96 let Some(webview) = self.active_webview() else {
97 return;
98 };
99
100 self.platform_window()
101 .rendering_context()
102 .make_current()
103 .expect("Could not make PlatformWindow RenderingContext current");
104 webview.paint();
105 self.platform_window().rendering_context().present();
106 }
107
108 pub(crate) fn should_close(&self) -> bool {
110 self.webview_collection.borrow().is_empty() || self.close_scheduled.get()
111 }
112
113 pub(crate) fn contains_webview(&self, id: WebViewId) -> bool {
114 self.webview_collection.borrow().contains(id)
115 }
116
117 pub(crate) fn webview_by_id(&self, id: WebViewId) -> Option<WebView> {
118 self.webview_collection.borrow().get(id).cloned()
119 }
120
121 pub(crate) fn set_needs_update(&self) {
122 self.needs_update.set(true);
123 }
124
125 pub(crate) fn set_needs_repaint(&self) {
126 self.needs_repaint.set(true)
127 }
128
129 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
130 pub(crate) fn schedule_close(&self) {
131 self.close_scheduled.set(true)
132 }
133
134 pub(crate) fn platform_window(&self) -> Rc<dyn PlatformWindow> {
135 self.platform_window.clone()
136 }
137
138 pub(crate) fn focused(&self) -> bool {
139 self.platform_window.focused()
140 }
141
142 pub(crate) fn add_webview(&self, webview: WebView) {
143 self.webview_collection.borrow_mut().add(webview);
144 self.set_needs_update();
145 self.set_needs_repaint();
146 }
147
148 pub(crate) fn webview_ids(&self) -> Vec<WebViewId> {
149 self.webview_collection.borrow().creation_order.clone()
150 }
151
152 pub(crate) fn webviews(&self) -> Vec<(WebViewId, WebView)> {
154 self.webview_collection
155 .borrow()
156 .all_in_creation_order()
157 .map(|(id, webview)| (id, webview.clone()))
158 .collect()
159 }
160
161 pub(crate) fn activate_webview(&self, webview_id: WebViewId) {
162 self.webview_collection
163 .borrow_mut()
164 .activate_webview(webview_id);
165 self.set_needs_update();
166 }
167
168 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
169 pub(crate) fn activate_webview_by_index(&self, index_to_activate: usize) {
170 self.webview_collection
171 .borrow_mut()
172 .activate_webview_by_index(index_to_activate);
173 self.set_needs_update();
174 }
175
176 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
177 pub(crate) fn get_active_webview_index(&self) -> Option<usize> {
178 let active_id = self.webview_collection.borrow().active_id()?;
179 self.webviews()
180 .iter()
181 .position(|webview| webview.0 == active_id)
182 }
183
184 pub(crate) fn update_and_request_repaint_if_necessary(&self, state: &RunningAppState) {
185 let updated_user_interface = self.needs_update.take() &&
186 self.platform_window
187 .update_user_interface_state(state, self);
188
189 let needs_repaint = self.needs_repaint.take();
192 if updated_user_interface || needs_repaint {
193 self.platform_window.request_repaint(self);
194 }
195 }
196
197 pub(crate) fn close_webview(&self, webview_id: WebViewId) {
202 let mut webview_collection = self.webview_collection.borrow_mut();
203 if webview_collection.remove(webview_id).is_none() {
204 return;
205 }
206 self.platform_window
207 .dismiss_embedder_controls_for_webview(webview_id);
208
209 self.set_needs_update();
210 self.set_needs_repaint();
211 }
212
213 pub(crate) fn notify_favicon_changed(&self, webview: WebView) {
214 self.pending_favicon_loads.borrow_mut().push(webview.id());
215 self.set_needs_repaint();
216 }
217
218 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
219 pub(crate) fn hidpi_scale_factor_changed(&self) {
220 let new_scale_factor = self.platform_window.hidpi_scale_factor();
221 for webview in self.webview_collection.borrow().values() {
222 webview.set_hidpi_scale_factor(new_scale_factor);
223 }
224 }
225
226 pub(crate) fn active_webview(&self) -> Option<WebView> {
227 self.webview_collection.borrow().active().cloned()
228 }
229
230 #[cfg_attr(
231 not(any(target_os = "android", target_env = "ohos")),
232 expect(dead_code)
233 )]
234 pub(crate) fn active_or_newest_webview(&self) -> Option<WebView> {
235 let webview_collection = self.webview_collection.borrow();
236 webview_collection
237 .active()
238 .or(webview_collection.newest())
239 .cloned()
240 }
241
242 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
244 pub(crate) fn take_pending_favicon_loads(&self) -> Vec<WebViewId> {
245 std::mem::take(&mut *self.pending_favicon_loads.borrow_mut())
246 }
247
248 pub(crate) fn show_embedder_control(
249 &self,
250 webview: WebView,
251 embedder_control: EmbedderControl,
252 ) {
253 self.platform_window
254 .show_embedder_control(webview.id(), embedder_control);
255 self.set_needs_update();
256 self.set_needs_repaint();
257 }
258
259 pub(crate) fn hide_embedder_control(
260 &self,
261 webview: WebView,
262 embedder_control: EmbedderControlId,
263 ) {
264 self.platform_window
265 .hide_embedder_control(webview.id(), embedder_control);
266 self.set_needs_update();
267 self.set_needs_repaint();
268 }
269}
270
271pub(crate) trait PlatformWindow {
275 fn id(&self) -> ServoShellWindowId;
276 fn screen_geometry(&self) -> ScreenGeometry;
277 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
278 fn device_hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel>;
279 fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel>;
280 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
281 fn get_fullscreen(&self) -> bool;
282 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
286 fn rebuild_user_interface(&self, _: &RunningAppState, _: &ServoShellWindow) {}
287 fn update_user_interface_state(&self, _: &RunningAppState, _: &ServoShellWindow) -> bool {
291 false
292 }
293 #[cfg(not(any(target_os = "android", target_env = "ohos")))]
299 fn handle_winit_window_event(
300 &self,
301 _: Rc<RunningAppState>,
302 _: &ServoShellWindow,
303 _: winit::event::WindowEvent,
304 ) {
305 }
306 #[cfg(not(any(target_os = "android", target_env = "ohos")))]
312 fn handle_winit_app_event(&self, _: crate::desktop::event_loop::AppEvent) {}
313 fn take_user_interface_commands(&self) -> Vec<UserInterfaceCommand> {
314 Default::default()
315 }
316 fn request_repaint(&self, _: &ServoShellWindow);
320 fn request_resize(&self, webview: &WebView, outer_size: DeviceIntSize)
323 -> Option<DeviceIntSize>;
324 fn set_position(&self, _point: DeviceIntPoint) {}
325 fn set_fullscreen(&self, _state: bool) {}
326 fn set_cursor(&self, _cursor: Cursor) {}
327 #[cfg(all(
328 feature = "webxr",
329 not(any(target_os = "android", target_env = "ohos"))
330 ))]
331 fn new_glwindow(
332 &self,
333 event_loop: &winit::event_loop::ActiveEventLoop,
334 ) -> Rc<dyn servo::webxr::GlWindow>;
335 fn rendering_context(&self) -> Rc<dyn RenderingContext>;
337 fn theme(&self) -> servo::Theme {
338 servo::Theme::Light
339 }
340 fn window_rect(&self) -> DeviceIndependentIntRect;
341 fn maximize(&self, _: &WebView) {}
342 fn focused(&self) -> bool;
343
344 fn show_embedder_control(&self, _: WebViewId, _: EmbedderControl) {}
345 fn hide_embedder_control(&self, _: WebViewId, _: EmbedderControlId) {}
346 fn dismiss_embedder_controls_for_webview(&self, _: WebViewId) {}
347 fn show_bluetooth_device_dialog(
348 &self,
349 _: WebViewId,
350 _devices: Vec<String>,
351 _: GenericSender<Option<String>>,
352 ) {
353 }
354 fn show_permission_dialog(&self, _: WebViewId, _: PermissionRequest) {}
355 fn show_http_authentication_dialog(&self, _: WebViewId, _: AuthenticationRequest) {}
356
357 fn notify_input_event_handled(
358 &self,
359 _webview: &WebView,
360 _id: InputEventId,
361 _result: InputEventResult,
362 ) {
363 }
364
365 fn notify_media_session_event(&self, _: MediaSessionEvent) {}
366 fn notify_crashed(&self, _: WebView, _reason: String, _backtrace: Option<String>) {}
367}