servoshell/desktop/
headless_window.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! A headless window implementation.
6
7#![deny(clippy::panic)]
8#![deny(clippy::unwrap_used)]
9
10use std::cell::Cell;
11use std::rc::Rc;
12
13use euclid::{Point2D, Scale, Size2D};
14use servo::{
15    DeviceIndependentIntRect, DeviceIndependentPixel, DeviceIntPoint, DeviceIntRect, DeviceIntSize,
16    DevicePixel, RenderingContext, ScreenGeometry, SoftwareRenderingContext, WebView,
17    convert_rect_to_css_pixel,
18};
19use winit::dpi::PhysicalSize;
20
21use crate::prefs::ServoShellPreferences;
22use crate::window::{MIN_WINDOW_INNER_SIZE, PlatformWindow, ServoShellWindow, ServoShellWindowId};
23
24pub struct Window {
25    fullscreen: Cell<bool>,
26    device_pixel_ratio_override: Option<Scale<f32, DeviceIndependentPixel, DevicePixel>>,
27    inner_size: Cell<DeviceIntSize>,
28    screen_size: Size2D<i32, DevicePixel>,
29    // virtual top-left position of the window in device pixels.
30    window_position: Cell<Point2D<i32, DevicePixel>>,
31    rendering_context: Rc<SoftwareRenderingContext>,
32}
33
34impl Window {
35    pub fn new(servoshell_preferences: &ServoShellPreferences) -> Rc<Self> {
36        let size = servoshell_preferences.initial_window_size;
37
38        let device_pixel_ratio_override = servoshell_preferences.device_pixel_ratio_override;
39        let device_pixel_ratio_override: Option<Scale<f32, DeviceIndependentPixel, DevicePixel>> =
40            device_pixel_ratio_override.map(Scale::new);
41        let hidpi_factor = device_pixel_ratio_override.unwrap_or_else(Scale::identity);
42
43        let inner_size = (size.to_f32() * hidpi_factor).to_i32();
44        let physical_size = PhysicalSize::new(inner_size.width as u32, inner_size.height as u32);
45        let rendering_context =
46            SoftwareRenderingContext::new(physical_size).expect("Failed to create WR surfman");
47
48        let screen_size = servoshell_preferences
49            .screen_size_override
50            .map_or(inner_size * 2, |screen_size_override| {
51                (screen_size_override.to_f32() * hidpi_factor).to_i32()
52            });
53
54        let window = Window {
55            fullscreen: Cell::new(false),
56            device_pixel_ratio_override,
57            inner_size: Cell::new(inner_size),
58            screen_size,
59            window_position: Cell::new(Point2D::zero()),
60            rendering_context: Rc::new(rendering_context),
61        };
62
63        Rc::new(window)
64    }
65}
66
67impl PlatformWindow for Window {
68    fn id(&self) -> ServoShellWindowId {
69        0.into()
70    }
71
72    fn screen_geometry(&self) -> servo::ScreenGeometry {
73        ScreenGeometry {
74            size: self.screen_size,
75            available_size: self.screen_size,
76            window_rect: DeviceIntRect::from_origin_and_size(
77                self.window_position.get(),
78                self.inner_size.get(),
79            ),
80        }
81    }
82
83    fn set_position(&self, point: DeviceIntPoint) {
84        self.window_position.set(point);
85    }
86
87    fn request_repaint(&self, window: &ServoShellWindow) {
88        window.repaint_webviews();
89    }
90
91    fn request_resize(&self, webview: &WebView, new_size: DeviceIntSize) -> Option<DeviceIntSize> {
92        // Do not let the window size get smaller than `MIN_WINDOW_INNER_SIZE` or larger
93        // than twice the screen size.
94        let new_size = new_size.clamp(MIN_WINDOW_INNER_SIZE, self.screen_size * 2);
95        if self.inner_size.get() == new_size {
96            return Some(new_size);
97        }
98
99        self.inner_size.set(new_size);
100
101        // Because we are managing the rendering surface ourselves, there will be no other
102        // notification (such as from the display manager) that it has changed size, so we
103        // must notify the compositor here.
104        webview.resize(PhysicalSize::new(
105            new_size.width as u32,
106            new_size.height as u32,
107        ));
108
109        Some(new_size)
110    }
111
112    fn device_hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
113        Scale::new(1.0)
114    }
115
116    fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
117        self.device_pixel_ratio_override
118            .unwrap_or_else(|| self.device_hidpi_scale_factor())
119    }
120
121    fn set_fullscreen(&self, state: bool) {
122        self.fullscreen.set(state);
123    }
124
125    fn get_fullscreen(&self) -> bool {
126        self.fullscreen.get()
127    }
128
129    #[cfg(feature = "webxr")]
130    fn new_glwindow(
131        &self,
132        _event_loop: &winit::event_loop::ActiveEventLoop,
133    ) -> Rc<dyn servo::webxr::GlWindow> {
134        unimplemented!()
135    }
136
137    fn window_rect(&self) -> DeviceIndependentIntRect {
138        convert_rect_to_css_pixel(
139            DeviceIntRect::from_origin_and_size(self.window_position.get(), self.inner_size.get()),
140            self.hidpi_scale_factor(),
141        )
142    }
143
144    fn rendering_context(&self) -> Rc<dyn RenderingContext> {
145        self.rendering_context.clone()
146    }
147
148    fn maximize(&self, webview: &WebView) {
149        self.window_position.set(Point2D::zero());
150        self.inner_size.set(self.screen_size);
151        // Because we are managing the rendering surface ourselves, there will be no other
152        // notification (such as from the display manager) that it has changed size, so we
153        // must notify the compositor here.
154        webview.resize(PhysicalSize::new(
155            self.screen_size.width as u32,
156            self.screen_size.height as u32,
157        ));
158    }
159
160    fn focused(&self) -> bool {
161        true
162    }
163}