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,
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(&self, state: Rc<RunningAppState>, url: Url) -> WebView {
101 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(unused_mut))]
102 let mut webview_builder =
103 WebViewBuilder::new(state.servo(), self.platform_window.rendering_context())
104 .url(url)
105 .hidpi_scale_factor(self.platform_window.hidpi_scale_factor())
106 .user_content_manager(state.user_content_manager.clone())
107 .delegate(state.clone());
108
109 #[cfg(all(
110 feature = "gamepad",
111 not(any(target_os = "android", target_env = "ohos"))
112 ))]
113 if let Some(gamepad_delegate) = state.gamepad_delegate() {
114 webview_builder = webview_builder.gamepad_delegate(gamepad_delegate);
115 }
116
117 let webview = webview_builder.build();
118 webview.notify_theme_change(self.platform_window.theme());
119 self.add_webview(webview.clone());
120 if state.accessibility_active() {
122 webview.set_accessibility_active(true);
125 }
126 webview
127 }
128
129 pub(crate) fn repaint_webviews(&self) {
131 let Some(webview) = self.active_webview() else {
132 return;
133 };
134
135 self.platform_window()
136 .rendering_context()
137 .make_current()
138 .expect("Could not make PlatformWindow RenderingContext current");
139 webview.paint();
140 self.platform_window().rendering_context().present();
141 }
142
143 pub(crate) fn should_close(&self) -> bool {
145 self.webview_collection.borrow().is_empty() || self.close_scheduled.get()
146 }
147
148 pub(crate) fn contains_webview(&self, id: WebViewId) -> bool {
149 self.webview_collection.borrow().contains(id)
150 }
151
152 pub(crate) fn webview_by_id(&self, id: WebViewId) -> Option<WebView> {
153 self.webview_collection.borrow().get(id).cloned()
154 }
155
156 pub(crate) fn set_needs_update(&self) {
157 self.needs_update.set(true);
158 }
159
160 pub(crate) fn set_needs_repaint(&self) {
161 self.needs_repaint.set(true)
162 }
163
164 #[cfg_attr(target_os = "android", expect(dead_code))]
165 pub(crate) fn schedule_close(&self) {
166 self.close_scheduled.set(true)
167 }
168
169 pub(crate) fn platform_window(&self) -> Rc<dyn PlatformWindow> {
170 self.platform_window.clone()
171 }
172
173 pub(crate) fn focus(&self) {
174 self.platform_window.focus()
175 }
176
177 pub(crate) fn add_webview(&self, webview: WebView) {
178 self.webview_collection.borrow_mut().add(webview);
179 self.set_needs_update();
180 self.set_needs_repaint();
181 }
182
183 pub(crate) fn webview_ids(&self) -> Vec<WebViewId> {
184 self.webview_collection.borrow().creation_order.clone()
185 }
186
187 pub(crate) fn webviews(&self) -> Vec<(WebViewId, WebView)> {
189 self.webview_collection
190 .borrow()
191 .all_in_creation_order()
192 .map(|(id, webview)| (id, webview.clone()))
193 .collect()
194 }
195
196 pub(crate) fn activate_webview(&self, webview_id: WebViewId) {
197 self.webview_collection
198 .borrow_mut()
199 .activate_webview(webview_id);
200 self.set_needs_update();
201 }
202
203 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
204 pub(crate) fn activate_webview_by_index(&self, index_to_activate: usize) {
205 self.webview_collection
206 .borrow_mut()
207 .activate_webview_by_index(index_to_activate);
208 self.set_needs_update();
209 }
210
211 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
212 pub(crate) fn get_active_webview_index(&self) -> Option<usize> {
213 let active_id = self.webview_collection.borrow().active_id()?;
214 self.webviews()
215 .iter()
216 .position(|webview| webview.0 == active_id)
217 }
218
219 pub(crate) fn update_and_request_repaint_if_necessary(&self, state: &RunningAppState) {
220 let updated_user_interface = self.needs_update.take() &&
221 self.platform_window
222 .update_user_interface_state(state, self);
223
224 let needs_repaint = self.needs_repaint.take();
227 if updated_user_interface || needs_repaint {
228 self.platform_window.request_repaint(self);
229 }
230 }
231
232 pub(crate) fn close_webview(&self, webview_id: WebViewId) {
237 let mut webview_collection = self.webview_collection.borrow_mut();
238 if webview_collection.remove(webview_id).is_none() {
239 return;
240 }
241 self.platform_window
242 .dismiss_embedder_controls_for_webview(webview_id);
243
244 self.set_needs_update();
245 self.set_needs_repaint();
246 }
247
248 pub(crate) fn notify_favicon_changed(&self, webview: WebView) {
249 self.pending_favicon_loads.borrow_mut().push(webview.id());
250 self.set_needs_repaint();
251 }
252
253 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
254 pub(crate) fn hidpi_scale_factor_changed(&self) {
255 let new_scale_factor = self.platform_window.hidpi_scale_factor();
256 for webview in self.webview_collection.borrow().values() {
257 webview.set_hidpi_scale_factor(new_scale_factor);
258 }
259 }
260
261 pub(crate) fn active_webview(&self) -> Option<WebView> {
262 self.webview_collection.borrow().active().cloned()
263 }
264
265 #[cfg_attr(
266 not(any(target_os = "android", target_env = "ohos")),
267 expect(dead_code)
268 )]
269 pub(crate) fn active_or_newest_webview(&self) -> Option<WebView> {
270 let webview_collection = self.webview_collection.borrow();
271 webview_collection
272 .active()
273 .or(webview_collection.newest())
274 .cloned()
275 }
276
277 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
279 pub(crate) fn take_pending_favicon_loads(&self) -> Vec<WebViewId> {
280 std::mem::take(&mut *self.pending_favicon_loads.borrow_mut())
281 }
282
283 pub(crate) fn show_embedder_control(
284 &self,
285 webview: WebView,
286 embedder_control: EmbedderControl,
287 ) {
288 self.platform_window
289 .show_embedder_control(webview.id(), embedder_control);
290 self.set_needs_update();
291 self.set_needs_repaint();
292 }
293
294 pub(crate) fn hide_embedder_control(
295 &self,
296 webview: WebView,
297 embedder_control: EmbedderControlId,
298 ) {
299 self.platform_window
300 .hide_embedder_control(webview.id(), embedder_control);
301 self.set_needs_update();
302 self.set_needs_repaint();
303 }
304
305 pub(crate) fn queue_user_interface_command(&self, command: UserInterfaceCommand) {
306 self.pending_commands.borrow_mut().push(command)
307 }
308
309 pub(crate) fn handle_interface_commands(
311 &self,
312 state: &Rc<RunningAppState>,
313 create_platform_window: Option<&dyn Fn(Url) -> Rc<dyn PlatformWindow>>,
314 ) {
315 let commands = std::mem::take(&mut *self.pending_commands.borrow_mut());
316 for event in commands {
317 match event {
318 UserInterfaceCommand::Go(location) => {
319 self.set_needs_update();
320 let Some(url) = location_bar_input_to_url(
321 &location.clone(),
322 &state.servoshell_preferences.searchpage,
323 ) else {
324 warn!("failed to parse location");
325 break;
326 };
327 if let Some(active_webview) = self.active_webview() {
328 active_webview.load(url.into_url());
329 }
330 },
331 UserInterfaceCommand::Back => {
332 if let Some(active_webview) = self.active_webview() {
333 active_webview.go_back(1);
334 }
335 },
336 UserInterfaceCommand::Forward => {
337 if let Some(active_webview) = self.active_webview() {
338 active_webview.go_forward(1);
339 }
340 },
341 UserInterfaceCommand::Reload => {
342 self.set_needs_update();
343 if let Some(active_webview) = self.active_webview() {
344 active_webview.reload();
345 }
346 },
347 UserInterfaceCommand::ReloadAll => {
348 for window in state.windows().values() {
349 window.set_needs_update();
350 for (_, webview) in window.webviews() {
351 webview.reload();
352 }
353 }
354 },
355 UserInterfaceCommand::NewWebView => {
356 self.set_needs_update();
357 let url = Url::parse("servo:newtab").expect("Should always be able to parse");
358 self.create_and_activate_toplevel_webview(state.clone(), url);
359 },
360 UserInterfaceCommand::CloseWebView(id) => {
361 self.set_needs_update();
362 self.close_webview(id);
363 },
364 UserInterfaceCommand::NewWindow => {
365 if let Some(create_platform_window) = create_platform_window {
366 let url = Url::parse("servo:newtab").unwrap();
367 let platform_window = create_platform_window(url.clone());
368 state.open_window(platform_window, url);
369 }
370 },
371 }
372 }
373 }
374}
375
376pub(crate) trait PlatformWindow {
380 fn id(&self) -> ServoShellWindowId;
381 fn screen_geometry(&self) -> ScreenGeometry;
382 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
383 fn device_hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel>;
384 fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel>;
385 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
386 fn get_fullscreen(&self) -> bool;
387 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
391 fn rebuild_user_interface(&self, _: &RunningAppState, _: &ServoShellWindow) {}
392 fn update_user_interface_state(&self, _: &RunningAppState, _: &ServoShellWindow) -> bool {
396 false
397 }
398 fn request_repaint(&self, _: &ServoShellWindow);
402 fn request_resize(&self, webview: &WebView, outer_size: DeviceIntSize)
405 -> Option<DeviceIntSize>;
406 fn set_position(&self, _point: DeviceIntPoint) {}
407 fn set_fullscreen(&self, _state: bool) {}
408 fn set_cursor(&self, _cursor: Cursor) {}
409 #[cfg(all(
410 feature = "webxr",
411 not(any(target_os = "android", target_env = "ohos"))
412 ))]
413 fn new_glwindow(
414 &self,
415 event_loop: &winit::event_loop::ActiveEventLoop,
416 ) -> Rc<dyn servo::webxr::GlWindow>;
417 fn rendering_context(&self) -> Rc<dyn RenderingContext>;
419 fn theme(&self) -> servo::Theme {
420 servo::Theme::Light
421 }
422 fn window_rect(&self) -> DeviceIndependentIntRect;
423 fn maximize(&self, _: &WebView) {}
424 fn focus(&self) {}
425 fn has_platform_focus(&self) -> bool {
426 true
427 }
428
429 fn show_embedder_control(&self, _: WebViewId, _: EmbedderControl) {}
430 fn hide_embedder_control(&self, _: WebViewId, _: EmbedderControlId) {}
431 fn dismiss_embedder_controls_for_webview(&self, _: WebViewId) {}
432 fn show_bluetooth_device_dialog(
433 &self,
434 _: WebViewId,
435 _request: BluetoothDeviceSelectionRequest,
436 ) {
437 }
438 fn show_permission_dialog(&self, _: WebViewId, _: PermissionRequest) {}
439 fn show_http_authentication_dialog(&self, _: WebViewId, _: AuthenticationRequest) {}
440
441 fn notify_input_event_handled(
442 &self,
443 _webview: &WebView,
444 _id: InputEventId,
445 _result: InputEventResult,
446 ) {
447 }
448
449 fn notify_media_session_event(&self, _: MediaSessionEvent) {}
450 fn notify_crashed(&self, _: WebView, _reason: String, _backtrace: Option<String>) {}
451 fn show_console_message(&self, _level: ConsoleLogLevel, _message: &str) {}
452
453 #[cfg(not(any(target_os = "android", target_env = "ohos")))]
454 fn as_headed_window(&self) -> Option<&crate::desktop::headed_window::HeadedWindow> {
456 None
457 }
458
459 #[cfg(any(target_os = "android", target_env = "ohos"))]
460 fn as_headed_window(&self) -> Option<&crate::egl::app::EmbeddedPlatformWindow> {
462 None
463 }
464
465 fn notify_accessibility_tree_update(&self, _: WebView, _: accesskit::TreeUpdate) {}
466}