libservo: Start moving WindowMethods to WebViewDelegate (#36223)

`WindowMethods` is used by the embedding layer to get information from
the embedder. This change moves the functionality for getting screen
size and `WebView` offsets to `WebViewDelegate`.

This is important because `WebView`s might be on different screens or
have different offsets on the screen itself, so it makes sense for this
to be per-`WebView` and not global to the embedder. HiDPI and animation
state functionality will move to the embedder in subsequent changes.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>

<!-- Please describe your changes on the following line: -->


---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by
`[X]` when the step is complete, and replace `___` with appropriate
data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes do not require tests because they just modify the
`WebView` API surface a bit.

<!-- Also, please make sure that "Allow edits from maintainers" checkbox
is checked, so that we can help you if you get stuck somewhere along the
way.-->

<!-- Pull requests that do not address these steps are welcome, but they
will require additional verification as part of the review process. -->

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-04-02 13:17:24 +02:00 committed by GitHub
parent 520a7f7bc5
commit b925c31424
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 235 additions and 183 deletions

View file

@ -26,7 +26,8 @@ use constellation_traits::{
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use dpi::PhysicalSize; use dpi::PhysicalSize;
use embedder_traits::{ use embedder_traits::{
Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent, ShutdownState, TouchEventType, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent, ScreenGeometry, ShutdownState,
TouchEventType,
}; };
use euclid::{Box2D, Point2D, Rect, Scale, Size2D, Transform3D}; use euclid::{Box2D, Point2D, Rect, Scale, Size2D, Transform3D};
use fnv::FnvHashMap; use fnv::FnvHashMap;
@ -54,11 +55,11 @@ use webrender_api::{
}; };
use webrender_traits::display_list::{HitTestInfo, ScrollTree}; use webrender_traits::display_list::{HitTestInfo, ScrollTree};
use webrender_traits::rendering_context::RenderingContext; use webrender_traits::rendering_context::RenderingContext;
use webrender_traits::{CrossProcessCompositorMessage, ImageUpdate}; use webrender_traits::{CrossProcessCompositorMessage, ImageUpdate, RendererWebView};
use crate::InitialCompositorState; use crate::InitialCompositorState;
use crate::webview::{UnknownWebView, WebView, WebViewManager}; use crate::webview::{UnknownWebView, WebView, WebViewManager};
use crate::windowing::{self, EmbedderCoordinates, WebRenderDebugOption, WindowMethods}; use crate::windowing::{self, WebRenderDebugOption, WindowMethods};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
enum UnableToComposite { enum UnableToComposite {
@ -167,8 +168,9 @@ pub struct IOCompositor {
/// The surfman instance that webrender targets /// The surfman instance that webrender targets
rendering_context: Rc<dyn RenderingContext>, rendering_context: Rc<dyn RenderingContext>,
/// The coordinates of the native window, its view and the screen. /// The HighDPI factor of the native window, its view and the screen.
embedder_coordinates: EmbedderCoordinates, /// TODO: Eventually this should be a property of the `WebView`.
hidpi_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
/// The number of frames pending to receive from WebRender. /// The number of frames pending to receive from WebRender.
pending_frames: usize, pending_frames: usize,
@ -442,7 +444,7 @@ impl IOCompositor {
cursor_pos: DevicePoint::new(0.0, 0.0), cursor_pos: DevicePoint::new(0.0, 0.0),
})), })),
webviews: WebViewManager::default(), webviews: WebViewManager::default(),
embedder_coordinates: window.get_coordinates(), hidpi_factor: window.hidpi_factor(),
window, window,
needs_repaint: Cell::default(), needs_repaint: Cell::default(),
page_zoom: Scale::new(1.0), page_zoom: Scale::new(1.0),
@ -895,27 +897,46 @@ impl IOCompositor {
.collect(); .collect();
let _ = result_sender.send((font_keys, font_instance_keys)); let _ = result_sender.send((font_keys, font_instance_keys));
}, },
CrossProcessCompositorMessage::GetClientWindowRect(req) => { CrossProcessCompositorMessage::GetClientWindowRect(webview_id, response_sender) => {
if let Err(e) = req.send(self.embedder_coordinates.window_rect) { let screen_geometry = self.webview_screen_geometry(webview_id);
warn!("Sending response to get client window failed ({:?}).", e); let rect = DeviceIntRect::from_origin_and_size(
screen_geometry.offset,
self.rendering_context.size2d().to_i32(),
)
.to_f32() /
self.hidpi_factor;
if let Err(error) = response_sender.send(rect.to_i32()) {
warn!("Sending response to get client window failed ({error:?}).");
} }
}, },
CrossProcessCompositorMessage::GetScreenSize(req) => { CrossProcessCompositorMessage::GetScreenSize(webview_id, response_sender) => {
if let Err(e) = req.send(self.embedder_coordinates.screen_size) { let screen_geometry = self.webview_screen_geometry(webview_id);
warn!("Sending response to get screen size failed ({:?}).", e); let screen_size = screen_geometry.size.to_f32() / self.hidpi_factor;
if let Err(error) = response_sender.send(screen_size.to_i32()) {
warn!("Sending response to get screen size failed ({error:?}).");
} }
}, },
CrossProcessCompositorMessage::GetAvailableScreenSize(req) => { CrossProcessCompositorMessage::GetAvailableScreenSize(webview_id, response_sender) => {
if let Err(e) = req.send(self.embedder_coordinates.available_screen_size) { let screen_geometry = self.webview_screen_geometry(webview_id);
warn!( let available_screen_size =
"Sending response to get screen avail size failed ({:?}).", screen_geometry.available_size.to_f32() / self.hidpi_factor;
e
); if let Err(error) = response_sender.send(available_screen_size.to_i32()) {
warn!("Sending response to get screen size failed ({error:?}).");
} }
}, },
} }
} }
fn webview_screen_geometry(&self, webview_id: WebViewId) -> ScreenGeometry {
self.webviews
.get(webview_id)
.and_then(|webview| webview.renderer_webview.screen_geometry())
.unwrap_or_default()
}
/// Handle messages sent to the compositor during the shutdown process. In general, /// Handle messages sent to the compositor during the shutdown process. In general,
/// the things the compositor can do in this state are limited. It's very important to /// the things the compositor can do in this state are limited. It's very important to
/// answer any synchronous messages though as other threads might be waiting on the /// answer any synchronous messages though as other threads might be waiting on the
@ -961,25 +982,27 @@ impl IOCompositor {
let _ = result_sender.send((font_keys, font_instance_keys)); let _ = result_sender.send((font_keys, font_instance_keys));
}, },
CompositorMsg::CrossProcess(CrossProcessCompositorMessage::GetClientWindowRect( CompositorMsg::CrossProcess(CrossProcessCompositorMessage::GetClientWindowRect(
req, _,
response_sender,
)) => { )) => {
if let Err(e) = req.send(self.embedder_coordinates.window_rect) { if let Err(error) = response_sender.send(Default::default()) {
warn!("Sending response to get client window failed ({:?}).", e); warn!("Sending response to get client window failed ({error:?}).");
} }
}, },
CompositorMsg::CrossProcess(CrossProcessCompositorMessage::GetScreenSize(req)) => { CompositorMsg::CrossProcess(CrossProcessCompositorMessage::GetScreenSize(
if let Err(e) = req.send(self.embedder_coordinates.screen_size) { _,
warn!("Sending response to get screen size failed ({:?}).", e); response_sender,
)) => {
if let Err(error) = response_sender.send(Default::default()) {
warn!("Sending response to get client window failed ({error:?}).");
} }
}, },
CompositorMsg::CrossProcess(CrossProcessCompositorMessage::GetAvailableScreenSize( CompositorMsg::CrossProcess(CrossProcessCompositorMessage::GetAvailableScreenSize(
req, _,
response_sender,
)) => { )) => {
if let Err(e) = req.send(self.embedder_coordinates.available_screen_size) { if let Err(error) = response_sender.send(Default::default()) {
warn!( warn!("Sending response to get client window failed ({error:?}).");
"Sending response to get screen avail size failed ({:?}).",
e
);
} }
}, },
CompositorMsg::NewWebRenderFrameReady(..) => { CompositorMsg::NewWebRenderFrameReady(..) => {
@ -1102,10 +1125,10 @@ impl IOCompositor {
} }
} }
pub fn add_webview(&mut self, webview_id: WebViewId) { pub fn add_webview(&mut self, webview: Box<dyn RendererWebView>) {
let size = self.rendering_context.size2d().to_f32(); let size = self.rendering_context.size2d().to_f32();
self.webviews.entry(webview_id).or_insert(WebView::new( self.webviews.entry(webview.id()).or_insert(WebView::new(
webview_id, webview,
Box2D::from_origin_and_size(Point2D::origin(), size), Box2D::from_origin_and_size(Point2D::origin(), size),
self.global.clone(), self.global.clone(),
)); ));
@ -1239,20 +1262,14 @@ impl IOCompositor {
} }
} }
pub fn on_embedder_window_moved(&mut self) {
self.embedder_coordinates = self.window.get_coordinates();
}
pub fn resize_rendering_context(&mut self, new_size: PhysicalSize<u32>) -> bool { pub fn resize_rendering_context(&mut self, new_size: PhysicalSize<u32>) -> bool {
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
return false; return false;
} }
let old_hidpi_factor = self.embedder_coordinates.hidpi_factor; let old_hidpi_factor = self.hidpi_factor;
self.embedder_coordinates = self.window.get_coordinates(); self.hidpi_factor = self.window.hidpi_factor();
if self.embedder_coordinates.hidpi_factor == old_hidpi_factor && if self.hidpi_factor == old_hidpi_factor && self.rendering_context.size() == new_size {
self.rendering_context.size() == new_size
{
return false; return false;
} }
@ -1303,10 +1320,6 @@ impl IOCompositor {
self.window.set_animation_state(animation_state); self.window.set_animation_state(animation_state);
} }
fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
self.embedder_coordinates.hidpi_factor
}
pub(crate) fn device_pixels_per_page_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> { pub(crate) fn device_pixels_per_page_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
self.device_pixels_per_page_pixel_not_including_page_zoom() * self.pinch_zoom_level() self.device_pixels_per_page_pixel_not_including_page_zoom() * self.pinch_zoom_level()
} }
@ -1314,7 +1327,7 @@ impl IOCompositor {
fn device_pixels_per_page_pixel_not_including_page_zoom( fn device_pixels_per_page_pixel_not_including_page_zoom(
&self, &self,
) -> Scale<f32, CSSPixel, DevicePixel> { ) -> Scale<f32, CSSPixel, DevicePixel> {
self.page_zoom * self.hidpi_factor() self.page_zoom * self.hidpi_factor
} }
pub fn on_zoom_reset_window_event(&mut self) { pub fn on_zoom_reset_window_event(&mut self) {

View file

@ -23,6 +23,7 @@ use webrender_api::units::{DeviceIntPoint, DevicePoint, DeviceRect, LayoutVector
use webrender_api::{ use webrender_api::{
ExternalScrollId, HitTestFlags, RenderReasons, SampledScrollOffset, ScrollLocation, ExternalScrollId, HitTestFlags, RenderReasons, SampledScrollOffset, ScrollLocation,
}; };
use webrender_traits::RendererWebView;
use crate::IOCompositor; use crate::IOCompositor;
use crate::compositor::{PipelineDetails, ServoRenderer}; use crate::compositor::{PipelineDetails, ServoRenderer};
@ -50,6 +51,10 @@ enum ScrollZoomEvent {
pub(crate) struct WebView { pub(crate) struct WebView {
/// The [`WebViewId`] of the `WebView` associated with this [`WebViewDetails`]. /// The [`WebViewId`] of the `WebView` associated with this [`WebViewDetails`].
pub id: WebViewId, pub id: WebViewId,
/// The renderer's view of the embedding layer `WebView` as a trait implementation,
/// so that the renderer doesn't need to depend on the embedding layer. This avoids
/// a dependency cycle.
pub renderer_webview: Box<dyn RendererWebView>,
/// The root [`PipelineId`] of the currently displayed page in this WebView. /// The root [`PipelineId`] of the currently displayed page in this WebView.
pub root_pipeline_id: Option<PipelineId>, pub root_pipeline_id: Option<PipelineId>,
pub rect: DeviceRect, pub rect: DeviceRect,
@ -73,9 +78,14 @@ impl Drop for WebView {
} }
impl WebView { impl WebView {
pub(crate) fn new(id: WebViewId, rect: DeviceRect, global: Rc<RefCell<ServoRenderer>>) -> Self { pub(crate) fn new(
renderer_webview: Box<dyn RendererWebView>,
rect: DeviceRect,
global: Rc<RefCell<ServoRenderer>>,
) -> Self {
Self { Self {
id, id: renderer_webview.id(),
renderer_webview,
root_pipeline_id: None, root_pipeline_id: None,
rect, rect,
pipelines: Default::default(), pipelines: Default::default(),

View file

@ -9,7 +9,7 @@ use std::fmt::Debug;
use embedder_traits::{EventLoopWaker, MouseButton}; use embedder_traits::{EventLoopWaker, MouseButton};
use euclid::Scale; use euclid::Scale;
use net::protocols::ProtocolRegistry; use net::protocols::ProtocolRegistry;
use servo_geometry::{DeviceIndependentIntRect, DeviceIndependentIntSize, DeviceIndependentPixel}; use servo_geometry::DeviceIndependentPixel;
use webrender_api::units::{DevicePixel, DevicePoint}; use webrender_api::units::{DevicePixel, DevicePoint};
#[derive(Clone)] #[derive(Clone)]
@ -37,12 +37,14 @@ pub enum AnimationState {
// for creating the GL context, making it current, buffer // for creating the GL context, making it current, buffer
// swapping, etc. Really that should all be done by surfman. // swapping, etc. Really that should all be done by surfman.
pub trait WindowMethods { pub trait WindowMethods {
/// Get the coordinates of the native window, the screen and the framebuffer. /// Get the HighDPI factor of the native window, the screen and the framebuffer.
fn get_coordinates(&self) -> EmbedderCoordinates; /// TODO(martin): Move this to `RendererWebView` when possible.
fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel>;
/// Set whether the application is currently animating. /// Set whether the application is currently animating.
/// Typically, when animations are active, the window /// Typically, when animations are active, the window
/// will want to avoid blocking on UI events, and just /// will want to avoid blocking on UI events, and just
/// run the event loop at the vsync interval. /// run the event loop at the vsync interval.
/// TODO(martin): Move this to `RendererWebView` when possible.
fn set_animation_state(&self, _state: AnimationState); fn set_animation_state(&self, _state: AnimationState);
} }
@ -65,15 +67,3 @@ pub trait EmbedderMethods {
ProtocolRegistry::default() ProtocolRegistry::default()
} }
} }
#[derive(Clone, Copy, Debug)]
pub struct EmbedderCoordinates {
/// The pixel density of the display.
pub hidpi_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
/// Size of the screen.
pub screen_size: DeviceIndependentIntSize,
/// Size of the available screen space (screen without toolbars and docks).
pub available_screen_size: DeviceIndependentIntSize,
/// Position and size of the native window.
pub window_rect: DeviceIndependentIntRect,
}

View file

@ -35,28 +35,34 @@ impl Screen {
} }
fn screen_size(&self) -> Size2D<u32, CSSPixel> { fn screen_size(&self) -> Size2D<u32, CSSPixel> {
let (send, recv) = let (sender, receiver) =
ipc::channel::<DeviceIndependentIntSize>(self.global().time_profiler_chan().clone()) ipc::channel::<DeviceIndependentIntSize>(self.global().time_profiler_chan().clone())
.unwrap(); .unwrap();
self.window self.window
.compositor_api() .compositor_api()
.sender() .sender()
.send(CrossProcessCompositorMessage::GetScreenSize(send)) .send(CrossProcessCompositorMessage::GetScreenSize(
self.window.webview_id(),
sender,
))
.unwrap(); .unwrap();
let size = recv.recv().unwrap_or(Size2D::zero()).to_u32(); let size = receiver.recv().unwrap_or(Size2D::zero()).to_u32();
Size2D::new(size.width, size.height) Size2D::new(size.width, size.height)
} }
fn screen_avail_size(&self) -> Size2D<u32, CSSPixel> { fn screen_avail_size(&self) -> Size2D<u32, CSSPixel> {
let (send, recv) = let (sender, receiver) =
ipc::channel::<DeviceIndependentIntSize>(self.global().time_profiler_chan().clone()) ipc::channel::<DeviceIndependentIntSize>(self.global().time_profiler_chan().clone())
.unwrap(); .unwrap();
self.window self.window
.compositor_api() .compositor_api()
.sender() .sender()
.send(CrossProcessCompositorMessage::GetAvailableScreenSize(send)) .send(CrossProcessCompositorMessage::GetAvailableScreenSize(
self.window.webview_id(),
sender,
))
.unwrap(); .unwrap();
let size = recv.recv().unwrap_or(Size2D::zero()).to_u32(); let size = receiver.recv().unwrap_or(Size2D::zero()).to_u32();
Size2D::new(size.width, size.height) Size2D::new(size.width, size.height)
} }
} }

View file

@ -1859,13 +1859,15 @@ impl Window {
fn client_window(&self) -> (Size2D<u32, CSSPixel>, Point2D<i32, CSSPixel>) { fn client_window(&self) -> (Size2D<u32, CSSPixel>, Point2D<i32, CSSPixel>) {
let timer_profile_chan = self.global().time_profiler_chan().clone(); let timer_profile_chan = self.global().time_profiler_chan().clone();
let (send, recv) = let (sender, receiver) =
ProfiledIpc::channel::<DeviceIndependentIntRect>(timer_profile_chan).unwrap(); ProfiledIpc::channel::<DeviceIndependentIntRect>(timer_profile_chan).unwrap();
let _ = self let _ = self.compositor_api.sender().send(
.compositor_api webrender_traits::CrossProcessCompositorMessage::GetClientWindowRect(
.sender() self.webview_id(),
.send(webrender_traits::CrossProcessCompositorMessage::GetClientWindowRect(send)); sender,
let rect = recv.recv().unwrap_or_default(); ),
);
let rect = receiver.recv().unwrap_or_default();
( (
Size2D::new(rect.size().width as u32, rect.size().height as u32), Size2D::new(rect.size().width as u32, rect.size().height as u32),
Point2D::new(rect.min.x, rect.min.y), Point2D::new(rect.min.x, rect.min.y),

View file

@ -1,6 +1,5 @@
/* This Source Code Form is subject to the terms of the Mozilla Public /* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::error::Error; use std::error::Error;
@ -13,7 +12,7 @@ use servo_geometry::DeviceIndependentPixel;
use tracing::warn; use tracing::warn;
use url::Url; use url::Url;
use webrender_api::ScrollLocation; use webrender_api::ScrollLocation;
use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DevicePixel, LayoutVector2D}; use webrender_api::units::{DeviceIntPoint, DevicePixel, LayoutVector2D};
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::dpi::{PhysicalPosition, PhysicalSize}; use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::event::{MouseScrollDelta, WindowEvent}; use winit::event::{MouseScrollDelta, WindowEvent};
@ -250,26 +249,8 @@ impl WindowDelegate {
} }
impl WindowMethods for WindowDelegate { impl WindowMethods for WindowDelegate {
fn get_coordinates(&self) -> compositing::windowing::EmbedderCoordinates { fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
let monitor = self Scale::new(self.window.scale_factor() as f32)
.window
.current_monitor()
.or_else(|| self.window.available_monitors().nth(0))
.expect("Failed to get winit monitor");
let scale =
Scale::<f64, DeviceIndependentPixel, DevicePixel>::new(self.window.scale_factor());
let window_size = winit_size_to_euclid_size(self.window.outer_size()).to_i32();
let window_origin = self.window.outer_position().unwrap_or_default();
let window_origin = winit_position_to_euclid_point(window_origin).to_i32();
let window_rect = DeviceIntRect::from_origin_and_size(window_origin, window_size);
compositing::windowing::EmbedderCoordinates {
hidpi_factor: Scale::new(self.window.scale_factor() as f32),
screen_size: (winit_size_to_euclid_size(monitor.size()).to_f64() / scale).to_i32(),
available_screen_size: (winit_size_to_euclid_size(monitor.size()).to_f64() / scale)
.to_i32(),
window_rect: (window_rect.to_f64() / scale).to_i32(),
}
} }
fn set_animation_state(&self, state: compositing::windowing::AnimationState) { fn set_animation_state(&self, state: compositing::windowing::AnimationState) {

View file

@ -321,8 +321,7 @@ impl Servo {
None None
}; };
let coordinates: compositing::windowing::EmbedderCoordinates = window.get_coordinates(); let device_pixel_ratio = window.hidpi_factor().get();
let device_pixel_ratio = coordinates.hidpi_factor.get();
let viewport_size = rendering_context.size2d(); let viewport_size = rendering_context.size2d();
let (mut webrender, webrender_api_sender) = { let (mut webrender, webrender_api_sender) = {

View file

@ -13,11 +13,12 @@ use compositing::windowing::WebRenderDebugOption;
use constellation_traits::{ConstellationMsg, TraversalDirection}; use constellation_traits::{ConstellationMsg, TraversalDirection};
use dpi::PhysicalSize; use dpi::PhysicalSize;
use embedder_traits::{ use embedder_traits::{
Cursor, InputEvent, LoadStatus, MediaSessionActionType, Theme, TouchEventType, Cursor, InputEvent, LoadStatus, MediaSessionActionType, ScreenGeometry, Theme, TouchEventType,
}; };
use url::Url; use url::Url;
use webrender_api::ScrollLocation; use webrender_api::ScrollLocation;
use webrender_api::units::{DeviceIntPoint, DeviceRect}; use webrender_api::units::{DeviceIntPoint, DeviceRect};
use webrender_traits::RendererWebView;
use crate::ConstellationProxy; use crate::ConstellationProxy;
use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate}; use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate};
@ -96,11 +97,10 @@ impl WebView {
compositor: Rc<RefCell<IOCompositor>>, compositor: Rc<RefCell<IOCompositor>>,
) -> Self { ) -> Self {
let id = WebViewId::new(); let id = WebViewId::new();
compositor.borrow_mut().add_webview(id); let webview = Self(Rc::new(RefCell::new(WebViewInner {
Self(Rc::new(RefCell::new(WebViewInner {
id, id,
constellation_proxy: constellation_proxy.clone(), constellation_proxy: constellation_proxy.clone(),
compositor, compositor: compositor.clone(),
delegate: Rc::new(DefaultWebViewDelegate), delegate: Rc::new(DefaultWebViewDelegate),
clipboard_delegate: Rc::new(DefaultClipboardDelegate), clipboard_delegate: Rc::new(DefaultClipboardDelegate),
rect: DeviceRect::zero(), rect: DeviceRect::zero(),
@ -111,7 +111,16 @@ impl WebView {
favicon_url: None, favicon_url: None,
focused: false, focused: false,
cursor: Cursor::Pointer, cursor: Cursor::Pointer,
}))) })));
compositor
.borrow_mut()
.add_webview(Box::new(ServoRendererWebView {
weak_handle: webview.weak_handle(),
id,
}));
webview
} }
fn inner(&self) -> Ref<'_, WebViewInner> { fn inner(&self) -> Ref<'_, WebViewInner> {
@ -382,13 +391,6 @@ impl WebView {
.resize_rendering_context(new_size); .resize_rendering_context(new_size);
} }
pub fn notify_embedder_window_moved(&self) {
self.inner()
.compositor
.borrow_mut()
.on_embedder_window_moved();
}
pub fn set_zoom(&self, new_zoom: f32) { pub fn set_zoom(&self, new_zoom: f32) {
self.inner() self.inner()
.compositor .compositor
@ -452,3 +454,21 @@ impl WebView {
self.inner().compositor.borrow_mut().render() self.inner().compositor.borrow_mut().render()
} }
} }
/// A structure used to expose a view of the [`WebView`] to the Servo
/// renderer, without having the Servo renderer depend on the embedding layer.
struct ServoRendererWebView {
id: WebViewId,
weak_handle: Weak<RefCell<WebViewInner>>,
}
impl RendererWebView for ServoRendererWebView {
fn screen_geometry(&self) -> Option<ScreenGeometry> {
let webview = WebView::from_weak_handle(&self.weak_handle)?;
webview.delegate().screen_geometry(webview)
}
fn id(&self) -> WebViewId {
self.id
}
}

View file

@ -9,7 +9,7 @@ use constellation_traits::ConstellationMsg;
use embedder_traits::{ use embedder_traits::{
AllowOrDeny, AuthenticationResponse, ContextMenuResult, Cursor, FilterPattern, AllowOrDeny, AuthenticationResponse, ContextMenuResult, Cursor, FilterPattern,
GamepadHapticEffectType, InputMethodType, LoadStatus, MediaSessionEvent, Notification, GamepadHapticEffectType, InputMethodType, LoadStatus, MediaSessionEvent, Notification,
PermissionFeature, SimpleDialog, WebResourceRequest, WebResourceResponse, PermissionFeature, ScreenGeometry, SimpleDialog, WebResourceRequest, WebResourceResponse,
WebResourceResponseMsg, WebResourceResponseMsg,
}; };
use ipc_channel::ipc::IpcSender; use ipc_channel::ipc::IpcSender;
@ -297,6 +297,12 @@ impl Drop for InterceptedWebResourceLoad {
} }
pub trait WebViewDelegate { pub trait WebViewDelegate {
/// Get the [`ScreenGeometry`] for this [`WebView`]. If this is unimplemented or returns `None`
/// the screen will have the size of the [`WebView`]'s `RenderingContext` and `WebView` will be
/// considered to be positioned at the screen's origin.
fn screen_geometry(&self, _webview: WebView) -> Option<ScreenGeometry> {
None
}
/// The URL of the currently loaded page in this [`WebView`] has changed. The new /// The URL of the currently loaded page in this [`WebView`] has changed. The new
/// URL can accessed via [`WebView::url`]. /// URL can accessed via [`WebView::url`].
fn notify_url_changed(&self, _webview: WebView, _url: Url) {} fn notify_url_changed(&self, _webview: WebView, _url: Url) {}

View file

@ -636,3 +636,22 @@ pub struct NotificationAction {
/// Icon's raw image data and metadata. /// Icon's raw image data and metadata.
pub icon_resource: Option<Arc<Image>>, pub icon_resource: Option<Arc<Image>>,
} }
/// Information about a `WebView`'s screen geometry and offset. This is used
/// for the [Screen](https://drafts.csswg.org/cssom-view/#the-screen-interface)
/// CSSOM APIs and `window.screenLeft` / `window.screenTop`.
#[derive(Clone, Copy, Debug, Default)]
pub struct ScreenGeometry {
/// The size of the screen in device pixels. This will be converted to
/// CSS pixels based on the pixel scaling of the `WebView`.
pub size: DeviceIntSize,
/// The available size of the screen in device pixels. This size is the size
/// available for web content on the screen, and should be `size` minus any system
/// toolbars, docks, and interface elements of the browser. This will be converted to
/// CSS pixels based on the pixel scaling of the `WebView`.
pub available_size: DeviceIntSize,
/// The offset of the `WebView` in device pixels for the purposes of the `window.screenLeft`
/// and `window.screenTop` APIs. This will be converted to CSS pixels based on the pixel scaling
/// of the `WebView`.
pub offset: DeviceIntPoint,
}

View file

@ -14,6 +14,7 @@ use std::sync::{Arc, Mutex};
use base::id::WebViewId; use base::id::WebViewId;
use constellation_traits::CompositorHitTestResult; use constellation_traits::CompositorHitTestResult;
use display_list::CompositorDisplayListInfo; use display_list::CompositorDisplayListInfo;
use embedder_traits::ScreenGeometry;
use euclid::default::Size2D as UntypedSize2D; use euclid::default::Size2D as UntypedSize2D;
use ipc_channel::ipc::{self, IpcSender, IpcSharedMemory}; use ipc_channel::ipc::{self, IpcSender, IpcSharedMemory};
use log::warn; use log::warn;
@ -82,12 +83,12 @@ pub enum CrossProcessCompositorMessage {
RemoveFonts(Vec<FontKey>, Vec<FontInstanceKey>), RemoveFonts(Vec<FontKey>, Vec<FontInstanceKey>),
/// Get the client window size and position. /// Get the client window size and position.
GetClientWindowRect(IpcSender<DeviceIndependentIntRect>), GetClientWindowRect(WebViewId, IpcSender<DeviceIndependentIntRect>),
/// Get the size of the screen that the client window inhabits. /// Get the size of the screen that the client window inhabits.
GetScreenSize(IpcSender<DeviceIndependentIntSize>), GetScreenSize(WebViewId, IpcSender<DeviceIndependentIntSize>),
/// Get the available screen size (without toolbars and docks) for the screen /// Get the available screen size (without toolbars and docks) for the screen
/// the client window inhabits. /// the client window inhabits.
GetAvailableScreenSize(IpcSender<DeviceIndependentIntSize>), GetAvailableScreenSize(WebViewId, IpcSender<DeviceIndependentIntSize>),
} }
impl fmt::Debug for CrossProcessCompositorMessage { impl fmt::Debug for CrossProcessCompositorMessage {
@ -478,3 +479,11 @@ impl From<SerializableImageData> for ImageData {
} }
} }
} }
/// A trait that exposes the embedding layer's `WebView` to the Servo renderer.
/// This is to prevent a dependency cycle between the renderer and the embedding
/// layer.
pub trait RendererWebView {
fn id(&self) -> WebViewId;
fn screen_geometry(&self) -> Option<ScreenGeometry>;
}

View file

@ -11,13 +11,16 @@ use std::rc::Rc;
use std::time::Instant; use std::time::Instant;
use std::{env, fs}; use std::{env, fs};
use euclid::Scale;
use log::{info, trace, warn}; use log::{info, trace, warn};
use servo::compositing::windowing::{AnimationState, WindowMethods}; use servo::compositing::windowing::{AnimationState, WindowMethods};
use servo::config::opts::Opts; use servo::config::opts::Opts;
use servo::config::prefs::Preferences; use servo::config::prefs::Preferences;
use servo::servo_config::pref; use servo::servo_config::pref;
use servo::servo_geometry::DeviceIndependentPixel;
use servo::servo_url::ServoUrl; use servo::servo_url::ServoUrl;
use servo::user_content_manager::{UserContentManager, UserScript}; use servo::user_content_manager::{UserContentManager, UserScript};
use servo::webrender_api::units::DevicePixel;
use servo::webxr::glwindow::GlWindowDiscovery; use servo::webxr::glwindow::GlWindowDiscovery;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use servo::webxr::openxr::{AppInfo, OpenXrDiscovery}; use servo::webxr::openxr::{AppInfo, OpenXrDiscovery};
@ -140,8 +143,8 @@ impl App {
// <https://github.com/rust-lang/rust/issues/65991> // <https://github.com/rust-lang/rust/issues/65991>
struct UpcastedWindow(Rc<dyn WindowPortsMethods>); struct UpcastedWindow(Rc<dyn WindowPortsMethods>);
impl WindowMethods for UpcastedWindow { impl WindowMethods for UpcastedWindow {
fn get_coordinates(&self) -> servo::compositing::windowing::EmbedderCoordinates { fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
self.0.get_coordinates() self.0.hidpi_factor()
} }
fn set_animation_state(&self, state: AnimationState) { fn set_animation_state(&self, state: AnimationState) {
self.0.set_animation_state(state); self.0.set_animation_state(state);

View file

@ -392,6 +392,10 @@ impl ServoDelegate for ServoShellServoDelegate {
} }
impl WebViewDelegate for RunningAppState { impl WebViewDelegate for RunningAppState {
fn screen_geometry(&self, _webview: WebView) -> Option<servo::ScreenGeometry> {
Some(self.inner().window.screen_geometry())
}
fn notify_status_text_changed(&self, _webview: servo::WebView, _status: Option<String>) { fn notify_status_text_changed(&self, _webview: servo::WebView, _status: Option<String>) {
self.inner_mut().need_update = true; self.inner_mut().need_update = true;
} }

View file

@ -14,18 +14,16 @@ use euclid::{Angle, Length, Point2D, Rotation3D, Scale, Size2D, UnknownUnit, Vec
use keyboard_types::{Modifiers, ShortcutMatcher}; use keyboard_types::{Modifiers, ShortcutMatcher};
use log::{debug, info}; use log::{debug, info};
use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};
use servo::compositing::windowing::{ use servo::compositing::windowing::{AnimationState, WebRenderDebugOption, WindowMethods};
AnimationState, EmbedderCoordinates, WebRenderDebugOption, WindowMethods,
};
use servo::servo_config::pref; use servo::servo_config::pref;
use servo::servo_geometry::DeviceIndependentPixel; use servo::servo_geometry::DeviceIndependentPixel;
use servo::webrender_api::ScrollLocation; use servo::webrender_api::ScrollLocation;
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel}; use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePixel};
use servo::{ use servo::{
Cursor, ImeEvent, InputEvent, Key, KeyState, KeyboardEvent, MouseButton as ServoMouseButton, Cursor, ImeEvent, InputEvent, Key, KeyState, KeyboardEvent, MouseButton as ServoMouseButton,
MouseButtonAction, MouseButtonEvent, MouseMoveEvent, OffscreenRenderingContext, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, OffscreenRenderingContext,
RenderingContext, Theme, TouchEvent, TouchEventType, TouchId, WebView, WheelDelta, WheelEvent, RenderingContext, ScreenGeometry, Theme, TouchEvent, TouchEventType, TouchId, WebView,
WheelMode, WindowRenderingContext, WheelDelta, WheelEvent, WheelMode, WindowRenderingContext,
}; };
use surfman::{Context, Device}; use surfman::{Context, Device};
use url::Url; use url::Url;
@ -114,7 +112,7 @@ impl Window {
.expect("No monitor detected"); .expect("No monitor detected");
let (screen_size, screen_scale) = servoshell_preferences.screen_size_override.map_or_else( let (screen_size, screen_scale) = servoshell_preferences.screen_size_override.map_or_else(
|| (monitor.size(), monitor.scale_factor()), || (monitor.size(), winit_window.scale_factor()),
|size| (PhysicalSize::new(size.width, size.height), 1.0), |size| (PhysicalSize::new(size.width, size.height), 1.0),
); );
let screen_scale: Scale<f64, DeviceIndependentPixel, DevicePixel> = let screen_scale: Scale<f64, DeviceIndependentPixel, DevicePixel> =
@ -446,6 +444,26 @@ impl Window {
} }
impl WindowPortsMethods for Window { impl WindowPortsMethods for Window {
fn screen_geometry(&self) -> ScreenGeometry {
let hidpi_factor = self.hidpi_factor();
let toolbar_size = Size2D::new(0.0, (self.toolbar_height.get() * self.hidpi_factor()).0);
let screen_size = self.screen_size.to_f32() * hidpi_factor;
let available_screen_size = screen_size - toolbar_size;
// Offset the WebView origin by the toolbar so that it reflects the actual viewport and
// not the window origin.
let window_origin = self.winit_window.inner_position().unwrap_or_default();
let window_origin = winit_position_to_euclid_point(window_origin).to_f32();
let offset = window_origin + toolbar_size;
ScreenGeometry {
size: screen_size.to_i32(),
available_size: available_screen_size.to_i32(),
offset: offset.to_i32(),
}
}
fn device_hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> { fn device_hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
Scale::new(self.winit_window.scale_factor() as f32) Scale::new(self.winit_window.scale_factor() as f32)
} }
@ -646,9 +664,6 @@ impl WindowPortsMethods for Window {
winit::window::Theme::Dark => Theme::Dark, winit::window::Theme::Dark => Theme::Dark,
}); });
}, },
WindowEvent::Moved(_new_position) => {
webview.notify_embedder_window_moved();
},
WindowEvent::Ime(ime) => match ime { WindowEvent::Ime(ime) => match ime {
Ime::Enabled => { Ime::Enabled => {
webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition( webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition(
@ -753,23 +768,9 @@ impl WindowPortsMethods for Window {
} }
impl WindowMethods for Window { impl WindowMethods for Window {
fn get_coordinates(&self) -> EmbedderCoordinates { fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
let window_size = winit_size_to_euclid_size(self.winit_window.outer_size()).to_i32(); self.device_pixel_ratio_override()
let window_origin = self.winit_window.outer_position().unwrap_or_default(); .unwrap_or_else(|| self.device_hidpi_factor())
let window_origin = winit_position_to_euclid_point(window_origin).to_i32();
let window_rect = DeviceIntRect::from_origin_and_size(window_origin, window_size);
let window_scale: Scale<f64, DeviceIndependentPixel, DevicePixel> =
Scale::new(self.winit_window.scale_factor());
let window_rect = (window_rect.to_f64() / window_scale).to_i32();
let screen_size = self.screen_size.to_i32();
EmbedderCoordinates {
window_rect,
screen_size,
// FIXME: Winit doesn't have API for available size. Fallback to screen size
available_screen_size: screen_size,
hidpi_factor: self.hidpi_factor(),
}
} }
fn set_animation_state(&self, state: AnimationState) { fn set_animation_state(&self, state: AnimationState) {

View file

@ -8,11 +8,11 @@ use std::cell::Cell;
use std::rc::Rc; use std::rc::Rc;
use euclid::num::Zero; use euclid::num::Zero;
use euclid::{Box2D, Length, Point2D, Scale, Size2D}; use euclid::{Length, Scale, Size2D};
use servo::compositing::windowing::{AnimationState, EmbedderCoordinates, WindowMethods}; use servo::compositing::windowing::{AnimationState, WindowMethods};
use servo::servo_geometry::DeviceIndependentPixel; use servo::servo_geometry::DeviceIndependentPixel;
use servo::webrender_api::units::{DeviceIntSize, DevicePixel}; use servo::webrender_api::units::{DeviceIntSize, DevicePixel};
use servo::{RenderingContext, SoftwareRenderingContext}; use servo::{RenderingContext, ScreenGeometry, SoftwareRenderingContext};
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
use super::app_state::RunningAppState; use super::app_state::RunningAppState;
@ -24,8 +24,7 @@ pub struct Window {
fullscreen: Cell<bool>, fullscreen: Cell<bool>,
device_pixel_ratio_override: Option<Scale<f32, DeviceIndependentPixel, DevicePixel>>, device_pixel_ratio_override: Option<Scale<f32, DeviceIndependentPixel, DevicePixel>>,
inner_size: Cell<DeviceIntSize>, inner_size: Cell<DeviceIntSize>,
screen_size: Size2D<i32, DeviceIndependentPixel>, screen_size: Size2D<i32, DevicePixel>,
window_rect: Box2D<i32, DeviceIndependentPixel>,
rendering_context: Rc<SoftwareRenderingContext>, rendering_context: Rc<SoftwareRenderingContext>,
} }
@ -44,12 +43,11 @@ impl Window {
let rendering_context = let rendering_context =
SoftwareRenderingContext::new(physical_size).expect("Failed to create WR surfman"); SoftwareRenderingContext::new(physical_size).expect("Failed to create WR surfman");
let window_rect = Box2D::from_origin_and_size(Point2D::zero(), size.to_i32()); let screen_size = servoshell_preferences
.screen_size_override
let screen_size = servoshell_preferences.screen_size_override.map_or_else( .map_or(inner_size, |screen_size_override| {
|| window_rect.size(), (screen_size_override.to_f32() * hidpi_factor).to_i32()
|screen_size_override| screen_size_override.to_i32(), });
);
let window = Window { let window = Window {
animation_state: Cell::new(AnimationState::Idle), animation_state: Cell::new(AnimationState::Idle),
@ -57,7 +55,6 @@ impl Window {
device_pixel_ratio_override, device_pixel_ratio_override,
inner_size: Cell::new(inner_size), inner_size: Cell::new(inner_size),
screen_size, screen_size,
window_rect,
rendering_context: Rc::new(rendering_context), rendering_context: Rc::new(rendering_context),
}; };
@ -70,6 +67,14 @@ impl WindowPortsMethods for Window {
winit::window::WindowId::dummy() winit::window::WindowId::dummy()
} }
fn screen_geometry(&self) -> servo::ScreenGeometry {
ScreenGeometry {
size: self.screen_size,
available_size: self.screen_size,
offset: Default::default(),
}
}
fn request_resize( fn request_resize(
&self, &self,
webview: &::servo::WebView, webview: &::servo::WebView,
@ -148,13 +153,9 @@ impl WindowPortsMethods for Window {
} }
impl WindowMethods for Window { impl WindowMethods for Window {
fn get_coordinates(&self) -> EmbedderCoordinates { fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
EmbedderCoordinates { self.device_pixel_ratio_override()
window_rect: self.window_rect, .unwrap_or_else(|| self.device_hidpi_factor())
screen_size: self.screen_size,
available_screen_size: self.screen_size,
hidpi_factor: self.hidpi_factor(),
}
} }
fn set_animation_state(&self, state: AnimationState) { fn set_animation_state(&self, state: AnimationState) {

View file

@ -11,7 +11,7 @@ use euclid::{Length, Scale};
use servo::compositing::windowing::WindowMethods; use servo::compositing::windowing::WindowMethods;
use servo::servo_geometry::DeviceIndependentPixel; use servo::servo_geometry::DeviceIndependentPixel;
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePixel}; use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePixel};
use servo::{Cursor, RenderingContext, WebView}; use servo::{Cursor, RenderingContext, ScreenGeometry, WebView};
use super::app_state::RunningAppState; use super::app_state::RunningAppState;
@ -20,10 +20,7 @@ pub const LINE_HEIGHT: f32 = 38.0;
pub trait WindowPortsMethods: WindowMethods { pub trait WindowPortsMethods: WindowMethods {
fn id(&self) -> winit::window::WindowId; fn id(&self) -> winit::window::WindowId;
fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> { fn screen_geometry(&self) -> ScreenGeometry;
self.device_pixel_ratio_override()
.unwrap_or_else(|| self.device_hidpi_factor())
}
fn device_hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel>; fn device_hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel>;
fn device_pixel_ratio_override( fn device_pixel_ratio_override(
&self, &self,

View file

@ -11,10 +11,8 @@ use keyboard_types::{CompositionEvent, CompositionState};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use raw_window_handle::{RawWindowHandle, WindowHandle}; use raw_window_handle::{RawWindowHandle, WindowHandle};
use servo::base::id::WebViewId; use servo::base::id::WebViewId;
use servo::compositing::windowing::{ use servo::compositing::windowing::{AnimationState, EmbedderMethods, WindowMethods};
AnimationState, EmbedderCoordinates, EmbedderMethods, WindowMethods, use servo::euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
};
use servo::euclid::{Box2D, Point2D, Rect, Scale, Size2D, Vector2D};
use servo::servo_geometry::DeviceIndependentPixel; use servo::servo_geometry::DeviceIndependentPixel;
use servo::webrender_api::ScrollLocation; use servo::webrender_api::ScrollLocation;
use servo::webrender_api::units::{DeviceIntRect, DeviceIntSize, DevicePixel}; use servo::webrender_api::units::{DeviceIntRect, DeviceIntSize, DevicePixel};
@ -683,15 +681,8 @@ impl EmbedderMethods for ServoEmbedderCallbacks {
} }
impl WindowMethods for ServoWindowCallbacks { impl WindowMethods for ServoWindowCallbacks {
fn get_coordinates(&self) -> EmbedderCoordinates { fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
let coords = self.coordinates.borrow(); self.hidpi_factor
let screen_size = (coords.viewport.size.to_f32() / self.hidpi_factor).to_i32();
EmbedderCoordinates {
window_rect: Box2D::from_origin_and_size(Point2D::zero(), screen_size),
screen_size,
available_screen_size: screen_size,
hidpi_factor: self.hidpi_factor,
}
} }
fn set_animation_state(&self, state: AnimationState) { fn set_animation_state(&self, state: AnimationState) {