1use std::cell::{Cell, RefCell};
6use std::rc::Rc;
7use std::sync::atomic::AtomicU64;
8
9use euclid::Scale;
10use log::warn;
11use servo::{
12 AuthenticationRequest, BluetoothDeviceSelectionRequest, ConsoleLogLevel, Cursor,
13 DeviceIndependentIntRect, DeviceIndependentPixel, DeviceIntPoint, DeviceIntSize, DevicePixel,
14 EmbedderControl, EmbedderControlId, InputEventId, InputEventResult, MediaSessionEvent,
15 PermissionRequest, RenderingContext, ScreenGeometry, WebView, WebViewBuilder, WebViewId,
16};
17use url::Url;
18
19use crate::parser::location_bar_input_to_url;
20use crate::running_app_state::{RunningAppState, UserInterfaceCommand, WebViewCollection};
21
22#[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
24pub(crate) const LINE_HEIGHT: f32 = 76.0;
25#[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
26pub(crate) const LINE_WIDTH: f32 = 76.0;
27
28#[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
31pub(crate) const MIN_WINDOW_INNER_SIZE: DeviceIntSize = DeviceIntSize::new(100, 100);
32
33static SERVOSHELL_WINDOW_ID: AtomicU64 = AtomicU64::new(0);
34
35#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
36pub(crate) struct ServoShellWindowId(u64);
37
38impl From<u64> for ServoShellWindowId {
39 fn from(value: u64) -> Self {
40 Self(value)
41 }
42}
43
44impl ServoShellWindowId {
45 #[cfg_attr(not(any(target_os = "android", target_env = "ohos")), expect(unused))]
46 pub(crate) fn next() -> ServoShellWindowId {
47 ServoShellWindowId(SERVOSHELL_WINDOW_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
48 }
49}
50
51pub(crate) struct ServoShellWindow {
52 pub(crate) webview_collection: RefCell<WebViewCollection>,
54 platform_window: Rc<dyn PlatformWindow>,
56 close_scheduled: Cell<bool>,
58 needs_update: Cell<bool>,
60 needs_repaint: Cell<bool>,
63 pending_favicon_loads: RefCell<Vec<WebViewId>>,
66 pending_commands: RefCell<Vec<UserInterfaceCommand>>,
68}
69
70impl ServoShellWindow {
71 pub(crate) fn new(platform_window: Rc<dyn PlatformWindow>) -> Self {
72 Self {
73 webview_collection: Default::default(),
74 platform_window,
75 close_scheduled: Default::default(),
76 needs_update: Default::default(),
77 needs_repaint: Default::default(),
78 pending_favicon_loads: Default::default(),
79 pending_commands: Default::default(),
80 }
81 }
82
83 pub(crate) fn id(&self) -> ServoShellWindowId {
84 self.platform_window().id()
85 }
86
87 pub(crate) fn create_and_activate_toplevel_webview(
89 self: &Rc<Self>,
90 state: Rc<RunningAppState>,
91 url: Url,
92 ) -> WebView {
93 let webview = self.create_toplevel_webview(state, url);
94 self.activate_webview(webview.id());
95 webview
96 }
97
98 #[servo::servo_tracing::instrument(skip(self, state))]
100 pub(crate) fn create_toplevel_webview(
101 self: &Rc<Self>,
102 state: Rc<RunningAppState>,
103 url: Url,
104 ) -> WebView {
105 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(unused_mut))]
106 let mut webview_builder =
107 WebViewBuilder::new(state.servo(), self.platform_window.rendering_context())
108 .url(url)
109 .hidpi_scale_factor(self.platform_window.hidpi_scale_factor())
110 .user_content_manager(state.user_content_manager.clone())
111 .delegate(state.clone());
112
113 #[cfg(all(
114 feature = "gamepad",
115 not(any(target_os = "android", target_env = "ohos"))
116 ))]
117 if let Some(gamepad_delegate) = state.gamepad_delegate() {
118 webview_builder = webview_builder.gamepad_delegate(gamepad_delegate);
119 }
120
121 let webview = webview_builder.build();
122 webview.notify_theme_change(self.platform_window.theme());
123 self.add_webview(webview.clone());
124
125 if state.accessibility_active() {
127 webview.set_accessibility_active(true);
130 }
131 webview
132 }
133
134 pub(crate) fn repaint_webviews(&self) {
136 let Some(webview) = self.active_webview() else {
137 return;
138 };
139
140 self.platform_window()
141 .rendering_context()
142 .make_current()
143 .expect("Could not make PlatformWindow RenderingContext current");
144 webview.paint();
145 self.platform_window().rendering_context().present();
146 }
147
148 pub(crate) fn should_close(&self) -> bool {
150 self.webview_collection.borrow().is_empty() || self.close_scheduled.get()
151 }
152
153 pub(crate) fn webview_by_id(&self, id: WebViewId) -> Option<WebView> {
154 self.webview_collection.borrow().get(id).cloned()
155 }
156
157 pub(crate) fn set_needs_update(&self) {
158 self.needs_update.set(true);
159 }
160
161 pub(crate) fn set_needs_repaint(&self) {
162 self.needs_repaint.set(true)
163 }
164
165 #[cfg_attr(target_os = "android", expect(dead_code))]
166 pub(crate) fn schedule_close(&self) {
167 self.close_scheduled.set(true)
168 }
169
170 pub(crate) fn platform_window(&self) -> Rc<dyn PlatformWindow> {
171 self.platform_window.clone()
172 }
173
174 pub(crate) fn focus(&self) {
175 self.platform_window.focus()
176 }
177
178 pub(crate) fn add_webview(&self, webview: WebView) {
179 self.webview_collection.borrow_mut().add(webview);
180 self.set_needs_update();
181 self.set_needs_repaint();
182 }
183
184 pub(crate) fn webview_ids(&self) -> Vec<WebViewId> {
185 self.webview_collection.borrow().creation_order.clone()
186 }
187
188 pub(crate) fn webviews(&self) -> Vec<(WebViewId, WebView)> {
190 self.webview_collection
191 .borrow()
192 .all_in_creation_order()
193 .map(|(id, webview)| (id, webview.clone()))
194 .collect()
195 }
196
197 pub(crate) fn activate_webview(&self, webview_id: WebViewId) {
198 self.webview_collection
199 .borrow_mut()
200 .activate_webview(webview_id);
201 self.set_needs_update();
202 }
203
204 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
205 pub(crate) fn activate_webview_by_index(&self, index_to_activate: usize) {
206 self.webview_collection
207 .borrow_mut()
208 .activate_webview_by_index(index_to_activate);
209 self.set_needs_update();
210 }
211
212 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
213 pub(crate) fn get_active_webview_index(&self) -> Option<usize> {
214 let active_id = self.webview_collection.borrow().active_id()?;
215 self.webviews()
216 .iter()
217 .position(|webview| webview.0 == active_id)
218 }
219
220 pub(crate) fn update_and_request_repaint_if_necessary(&self, state: &RunningAppState) {
221 let updated_user_interface = self.needs_update.take() &&
222 self.platform_window
223 .update_user_interface_state(state, self);
224
225 let needs_repaint = self.needs_repaint.take();
228 if updated_user_interface || needs_repaint {
229 self.platform_window.request_repaint(self);
230 }
231 }
232
233 pub(crate) fn close_webview(&self, webview_id: WebViewId) {
238 let mut webview_collection = self.webview_collection.borrow_mut();
239 if webview_collection.remove(webview_id).is_none() {
240 return;
241 }
242 self.platform_window
243 .dismiss_embedder_controls_for_webview(webview_id);
244
245 self.set_needs_update();
246 self.set_needs_repaint();
247 }
248
249 pub(crate) fn notify_favicon_changed(&self, webview: WebView) {
250 self.pending_favicon_loads.borrow_mut().push(webview.id());
251 self.set_needs_repaint();
252 }
253
254 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
255 pub(crate) fn hidpi_scale_factor_changed(&self) {
256 let new_scale_factor = self.platform_window.hidpi_scale_factor();
257 for webview in self.webview_collection.borrow().values() {
258 webview.set_hidpi_scale_factor(new_scale_factor);
259 }
260 }
261
262 pub(crate) fn active_webview(&self) -> Option<WebView> {
263 self.webview_collection.borrow().active().cloned()
264 }
265
266 #[cfg_attr(
267 not(any(target_os = "android", target_env = "ohos")),
268 expect(dead_code)
269 )]
270 pub(crate) fn active_or_newest_webview(&self) -> Option<WebView> {
271 let webview_collection = self.webview_collection.borrow();
272 webview_collection
273 .active()
274 .or(webview_collection.newest())
275 .cloned()
276 }
277
278 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
280 pub(crate) fn take_pending_favicon_loads(&self) -> Vec<WebViewId> {
281 std::mem::take(&mut *self.pending_favicon_loads.borrow_mut())
282 }
283
284 pub(crate) fn show_embedder_control(
285 &self,
286 webview: WebView,
287 embedder_control: EmbedderControl,
288 ) {
289 self.platform_window
290 .show_embedder_control(webview.id(), embedder_control);
291 self.set_needs_update();
292 self.set_needs_repaint();
293 }
294
295 pub(crate) fn hide_embedder_control(
296 &self,
297 webview: WebView,
298 embedder_control: EmbedderControlId,
299 ) {
300 self.platform_window
301 .hide_embedder_control(webview.id(), embedder_control);
302 self.set_needs_update();
303 self.set_needs_repaint();
304 }
305
306 pub(crate) fn queue_user_interface_command(&self, command: UserInterfaceCommand) {
307 self.pending_commands.borrow_mut().push(command)
308 }
309
310 pub(crate) fn handle_interface_commands(
312 self: &Rc<Self>,
313 state: &Rc<RunningAppState>,
314 create_platform_window: Option<&dyn Fn(Url) -> Rc<dyn PlatformWindow>>,
315 ) {
316 let commands = std::mem::take(&mut *self.pending_commands.borrow_mut());
317 for event in commands {
318 match event {
319 UserInterfaceCommand::Go(location) => {
320 self.set_needs_update();
321 let Some(url) = location_bar_input_to_url(
322 &location.clone(),
323 &state.servoshell_preferences.searchpage,
324 ) else {
325 warn!("failed to parse location");
326 break;
327 };
328 if let Some(active_webview) = self.active_webview() {
329 active_webview.load(url.into_url());
330 }
331 },
332 UserInterfaceCommand::Back => {
333 if let Some(active_webview) = self.active_webview() {
334 active_webview.go_back(1);
335 }
336 },
337 UserInterfaceCommand::Forward => {
338 if let Some(active_webview) = self.active_webview() {
339 active_webview.go_forward(1);
340 }
341 },
342 UserInterfaceCommand::Reload => {
343 self.set_needs_update();
344 if let Some(active_webview) = self.active_webview() {
345 active_webview.reload();
346 }
347 },
348 UserInterfaceCommand::ReloadAll => {
349 for window in state.windows().values() {
350 window.set_needs_update();
351 for (_, webview) in window.webviews() {
352 webview.reload();
353 }
354 }
355 },
356 UserInterfaceCommand::NewWebView => {
357 self.set_needs_update();
358 let url = Url::parse("servo:newtab").expect("Should always be able to parse");
359 self.create_and_activate_toplevel_webview(state.clone(), url);
360 },
361 UserInterfaceCommand::CloseWebView(id) => {
362 self.set_needs_update();
363 self.close_webview(id);
364 },
365 UserInterfaceCommand::NewWindow => {
366 if let Some(create_platform_window) = create_platform_window {
367 let url = Url::parse("servo:newtab").unwrap();
368 let platform_window = create_platform_window(url.clone());
369 state.open_window(platform_window, url);
370 }
371 },
372 }
373 }
374 }
375}
376
377pub(crate) trait PlatformWindow {
381 fn id(&self) -> ServoShellWindowId;
382 fn screen_geometry(&self) -> ScreenGeometry;
383 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
384 fn device_hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel>;
385 fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel>;
386 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
387 fn get_fullscreen(&self) -> bool;
388 fn update_user_interface_state(&self, _: &RunningAppState, _: &ServoShellWindow) -> bool {
392 false
393 }
394 fn request_repaint(&self, _: &ServoShellWindow);
398 fn request_resize(&self, webview: &WebView, outer_size: DeviceIntSize)
401 -> Option<DeviceIntSize>;
402 fn set_position(&self, _point: DeviceIntPoint) {}
403 fn set_fullscreen(&self, _state: bool) {}
404 fn set_cursor(&self, _cursor: Cursor) {}
405 #[cfg(all(
406 feature = "webxr",
407 not(any(target_os = "android", target_env = "ohos"))
408 ))]
409 fn new_glwindow(
410 &self,
411 event_loop: &winit::event_loop::ActiveEventLoop,
412 ) -> Rc<dyn servo::webxr::GlWindow>;
413 fn rendering_context(&self) -> Rc<dyn RenderingContext>;
415 fn theme(&self) -> servo::Theme {
416 servo::Theme::Light
417 }
418 fn window_rect(&self) -> DeviceIndependentIntRect;
419 fn maximize(&self, _: &WebView) {}
420 fn focus(&self) {}
421 fn has_platform_focus(&self) -> bool {
422 true
423 }
424
425 fn show_embedder_control(&self, _: WebViewId, _: EmbedderControl) {}
426 fn hide_embedder_control(&self, _: WebViewId, _: EmbedderControlId) {}
427 fn dismiss_embedder_controls_for_webview(&self, _: WebViewId) {}
428 fn show_bluetooth_device_dialog(
429 &self,
430 _: WebViewId,
431 _request: BluetoothDeviceSelectionRequest,
432 ) {
433 }
434 fn show_permission_dialog(&self, _: WebViewId, _: PermissionRequest) {}
435 fn show_http_authentication_dialog(&self, _: WebViewId, _: AuthenticationRequest) {}
436
437 fn notify_input_event_handled(
438 &self,
439 _webview: &WebView,
440 _id: InputEventId,
441 _result: InputEventResult,
442 ) {
443 }
444
445 fn notify_media_session_event(&self, _: MediaSessionEvent) {}
446 fn notify_crashed(&self, _: WebView, _reason: String, _backtrace: Option<String>) {}
447 fn show_console_message(&self, _level: ConsoleLogLevel, _message: &str) {}
448
449 #[cfg(not(any(target_os = "android", target_env = "ohos")))]
450 fn as_headed_window(&self) -> Option<&crate::desktop::headed_window::HeadedWindow> {
452 None
453 }
454
455 #[cfg(any(target_os = "android", target_env = "ohos"))]
456 fn as_headed_window(&self) -> Option<&crate::egl::app::EmbeddedPlatformWindow> {
458 None
459 }
460
461 fn notify_accessibility_tree_update(&self, _: WebView, _: accesskit::TreeUpdate) {}
462}