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