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