servoshell/desktop/
headless_window.rs1#![deny(clippy::panic)]
8#![deny(clippy::unwrap_used)]
9
10use std::cell::Cell;
11use std::rc::Rc;
12use std::sync::atomic::AtomicU64;
13
14use euclid::{Point2D, Scale, Size2D};
15use log::error;
16use servo::{
17 DeviceIndependentIntRect, DeviceIndependentPixel, DeviceIntPoint, DeviceIntRect, DeviceIntSize,
18 DevicePixel, RenderingContext, ScreenGeometry, SoftwareRenderingContext, WebView,
19 convert_rect_to_css_pixel,
20};
21use winit::dpi::PhysicalSize;
22
23use crate::prefs::ServoShellPreferences;
24use crate::window::{MIN_WINDOW_INNER_SIZE, PlatformWindow, ServoShellWindow, ServoShellWindowId};
25
26pub struct HeadlessWindow {
27 id: ServoShellWindowId,
28 fullscreen: Cell<bool>,
29 device_pixel_ratio_override: Option<Scale<f32, DeviceIndependentPixel, DevicePixel>>,
30 inner_size: Cell<DeviceIntSize>,
31 screen_size: Size2D<i32, DevicePixel>,
32 window_position: Cell<Point2D<i32, DevicePixel>>,
34 rendering_context: Rc<SoftwareRenderingContext>,
35}
36
37impl HeadlessWindow {
38 #[servo::servo_tracing::instrument(level = "debug", name = "HeadlessWindow::new", skip_all)]
39 pub fn new(servoshell_preferences: &ServoShellPreferences) -> Rc<Self> {
40 let size = servoshell_preferences.initial_window_size;
41
42 let device_pixel_ratio_override = servoshell_preferences.device_pixel_ratio_override;
43 let device_pixel_ratio_override: Option<Scale<f32, DeviceIndependentPixel, DevicePixel>> =
44 device_pixel_ratio_override.map(Scale::new);
45 let hidpi_factor = device_pixel_ratio_override.unwrap_or_else(Scale::identity);
46
47 let inner_size = (size.to_f32() * hidpi_factor).to_i32();
48 let physical_size = PhysicalSize::new(inner_size.width as u32, inner_size.height as u32);
49 let rendering_context =
50 SoftwareRenderingContext::new(physical_size).expect("Failed to create WR surfman");
51
52 let screen_size = servoshell_preferences
53 .screen_size_override
54 .map_or(inner_size * 2, |screen_size_override| {
55 (screen_size_override.to_f32() * hidpi_factor).to_i32()
56 });
57
58 static CURRENT_WINDOW_ID: AtomicU64 = AtomicU64::new(0);
59 let window = HeadlessWindow {
60 id: CURRENT_WINDOW_ID
61 .fetch_add(1, std::sync::atomic::Ordering::Relaxed)
62 .into(),
63 fullscreen: Cell::new(false),
64 device_pixel_ratio_override,
65 inner_size: Cell::new(inner_size),
66 screen_size,
67 window_position: Cell::new(Point2D::zero()),
68 rendering_context: Rc::new(rendering_context),
69 };
70
71 Rc::new(window)
72 }
73}
74
75impl Drop for HeadlessWindow {
76 fn drop(&mut self) {
77 if let Err(error) = self.rendering_context.make_current() {
78 error!("Failed to make the rendering context current: {error:?}");
79 }
80 }
81}
82
83impl PlatformWindow for HeadlessWindow {
84 fn id(&self) -> ServoShellWindowId {
85 self.id
86 }
87
88 fn screen_geometry(&self) -> servo::ScreenGeometry {
89 ScreenGeometry {
90 size: self.screen_size,
91 available_size: self.screen_size,
92 window_rect: DeviceIntRect::from_origin_and_size(
93 self.window_position.get(),
94 self.inner_size.get(),
95 ),
96 }
97 }
98
99 fn set_position(&self, point: DeviceIntPoint) {
100 self.window_position.set(point);
101 }
102
103 fn request_repaint(&self, window: &ServoShellWindow) {
104 window.repaint_webviews();
105 }
106
107 fn request_resize(&self, webview: &WebView, new_size: DeviceIntSize) -> Option<DeviceIntSize> {
108 let new_size = new_size.clamp(MIN_WINDOW_INNER_SIZE, self.screen_size * 2);
111 if self.inner_size.get() == new_size {
112 return Some(new_size);
113 }
114
115 self.inner_size.set(new_size);
116
117 webview.resize(PhysicalSize::new(
121 new_size.width as u32,
122 new_size.height as u32,
123 ));
124
125 Some(new_size)
126 }
127
128 fn device_hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
129 Scale::new(1.0)
130 }
131
132 fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
133 self.device_pixel_ratio_override
134 .unwrap_or_else(|| self.device_hidpi_scale_factor())
135 }
136
137 fn set_fullscreen(&self, state: bool) {
138 self.fullscreen.set(state);
139 }
140
141 fn get_fullscreen(&self) -> bool {
142 self.fullscreen.get()
143 }
144
145 #[cfg(feature = "webxr")]
146 fn new_glwindow(
147 &self,
148 _event_loop: &winit::event_loop::ActiveEventLoop,
149 ) -> Rc<dyn servo::webxr::GlWindow> {
150 unimplemented!()
151 }
152
153 fn window_rect(&self) -> DeviceIndependentIntRect {
154 convert_rect_to_css_pixel(
155 DeviceIntRect::from_origin_and_size(self.window_position.get(), self.inner_size.get()),
156 self.hidpi_scale_factor(),
157 )
158 }
159
160 fn rendering_context(&self) -> Rc<dyn RenderingContext> {
161 self.rendering_context.clone()
162 }
163
164 fn maximize(&self, webview: &WebView) {
165 self.window_position.set(Point2D::zero());
166 self.inner_size.set(self.screen_size);
167 webview.resize(PhysicalSize::new(
171 self.screen_size.width as u32,
172 self.screen_size.height as u32,
173 ));
174 }
175
176 fn show_console_message(&self, level: servo::ConsoleLogLevel, message: &str) {
177 println!("{message}");
178 log::log!(level.into(), "{message}");
179 }
180}