script: Get the window rectangle from the WebViewDelegate instead of via the compositor (#37960)

Previously, `screenX`, `screenY`, `outerHeight`, `outerWidth`, `moveBy`,
`resizeBy` ask compositor for window rectangle, which then return
"inner" rectangle after consulting Embedder.

This PR 
1. removes `GetClientWindowRect` from compositor, and directly let
script ask embedder.
2. add `window_size` to `ScreenGeometry`
3. add a lot of docs to `ScreenGeometry`

Testing: `tests\wpt\mozilla\tests\mozilla\window_resizeTo.html` can now
pass for Headed Window.
Fixes: #37824

---------

Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Euclid Ye 2025-07-12 02:31:24 +08:00 committed by GitHub
parent d40e9f82a2
commit c5aeac3cea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 59 additions and 104 deletions

View file

@ -912,18 +912,6 @@ impl IOCompositor {
.collect();
let _ = result_sender.send((font_keys, font_instance_keys));
},
CompositorMsg::GetClientWindowRect(webview_id, response_sender) => {
let client_window_rect = self
.webview_renderers
.get(webview_id)
.map(|webview_renderer| {
webview_renderer.client_window_rect(self.rendering_context.size2d())
})
.unwrap_or_default();
if let Err(error) = response_sender.send(client_window_rect) {
warn!("Sending response to get client window failed ({error:?}).");
}
},
CompositorMsg::GetScreenSize(webview_id, response_sender) => {
let screen_size = self
.webview_renderers
@ -993,11 +981,6 @@ impl IOCompositor {
.collect();
let _ = result_sender.send((font_keys, font_instance_keys));
},
CompositorMsg::GetClientWindowRect(_, response_sender) => {
if let Err(error) = response_sender.send(Default::default()) {
warn!("Sending response to get client window failed ({error:?}).");
}
},
CompositorMsg::GetScreenSize(_, response_sender) => {
if let Err(error) = response_sender.send(Default::default()) {
warn!("Sending response to get client window failed ({error:?}).");

View file

@ -51,7 +51,6 @@ mod from_constellation {
Self::AddSystemFont(..) => target!("AddSystemFont"),
Self::AddFontInstance(..) => target!("AddFontInstance"),
Self::RemoveFonts(..) => target!("RemoveFonts"),
Self::GetClientWindowRect(..) => target!("GetClientWindowRect"),
Self::GetScreenSize(..) => target!("GetScreenSize"),
Self::GetAvailableScreenSize(..) => target!("GetAvailableScreenSize"),
Self::CollectMemoryReport(..) => target!("CollectMemoryReport"),

View file

@ -19,14 +19,12 @@ use embedder_traits::{
MouseButtonEvent, MouseMoveEvent, ScrollEvent as EmbedderScrollEvent, ShutdownState,
TouchEvent, TouchEventResult, TouchEventType, TouchId, ViewportDetails,
};
use euclid::{Box2D, Point2D, Scale, Size2D, Vector2D};
use euclid::{Point2D, Scale, Size2D, Vector2D};
use fnv::FnvHashSet;
use log::{debug, warn};
use servo_geometry::DeviceIndependentPixel;
use style_traits::{CSSPixel, PinchZoomFactor};
use webrender_api::units::{
DeviceIntPoint, DeviceIntRect, DevicePixel, DevicePoint, DeviceRect, LayoutVector2D,
};
use webrender_api::units::{DeviceIntPoint, DevicePixel, DevicePoint, DeviceRect, LayoutVector2D};
use webrender_api::{ExternalScrollId, HitTestFlags, ScrollLocation};
use crate::compositor::{HitTestError, PipelineDetails, ServoRenderer};
@ -1041,20 +1039,6 @@ impl WebViewRenderer {
old_rect != self.rect
}
pub(crate) fn client_window_rect(
&self,
rendering_context_size: Size2D<u32, DevicePixel>,
) -> Box2D<i32, DeviceIndependentPixel> {
let screen_geometry = self.webview.screen_geometry().unwrap_or_default();
let rect = DeviceIntRect::from_origin_and_size(
screen_geometry.offset,
rendering_context_size.to_i32(),
)
.to_f32() /
self.hidpi_scale_factor;
rect.to_i32()
}
pub(crate) fn screen_size(&self) -> Size2D<i32, DeviceIndependentPixel> {
let screen_geometry = self.webview.screen_geometry().unwrap_or_default();
(screen_geometry.size.to_f32() / self.hidpi_scale_factor).to_i32()

View file

@ -214,6 +214,7 @@ mod from_script {
Self::SetCursor(..) => target_variant!("SetCursor"),
Self::NewFavicon(..) => target_variant!("NewFavicon"),
Self::HistoryChanged(..) => target_variant!("HistoryChanged"),
Self::GetWindowRect(..) => target_variant!("GetWindowRect"),
Self::NotifyFullscreenStateChanged(..) => {
target_variant!("NotifyFullscreenStateChanged")
},

View file

@ -1686,12 +1686,9 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
// https://drafts.csswg.org/cssom-view/#dom-window-resizeby
fn ResizeBy(&self, x: i32, y: i32) {
let (size, _) = self.client_window();
let size = self.client_window().size();
// Step 1
self.ResizeTo(
x + size.width.to_i32().unwrap_or(1),
y + size.height.to_i32().unwrap_or(1),
)
self.ResizeTo(x + size.width, y + size.height)
}
// https://drafts.csswg.org/cssom-view/#dom-window-moveto
@ -1706,33 +1703,29 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
// https://drafts.csswg.org/cssom-view/#dom-window-moveby
fn MoveBy(&self, x: i32, y: i32) {
let (_, origin) = self.client_window();
let origin = self.client_window().min;
// Step 1
self.MoveTo(x + origin.x, y + origin.y)
}
// https://drafts.csswg.org/cssom-view/#dom-window-screenx
fn ScreenX(&self) -> i32 {
let (_, origin) = self.client_window();
origin.x
self.client_window().min.x
}
// https://drafts.csswg.org/cssom-view/#dom-window-screeny
fn ScreenY(&self) -> i32 {
let (_, origin) = self.client_window();
origin.y
self.client_window().min.y
}
// https://drafts.csswg.org/cssom-view/#dom-window-outerheight
fn OuterHeight(&self) -> i32 {
let (size, _) = self.client_window();
size.height.to_i32().unwrap_or(1)
self.client_window().height()
}
// https://drafts.csswg.org/cssom-view/#dom-window-outerwidth
fn OuterWidth(&self) -> i32 {
let (size, _) = self.client_window();
size.width.to_i32().unwrap_or(1)
self.client_window().width()
}
// https://drafts.csswg.org/cssom-view/#dom-window-devicepixelratio
@ -2151,18 +2144,12 @@ impl Window {
self.viewport_details.get().hidpi_scale_factor
}
fn client_window(&self) -> (Size2D<u32, CSSPixel>, Point2D<i32, CSSPixel>) {
let timer_profile_chan = self.global().time_profiler_chan().clone();
let (sender, receiver) =
ProfiledIpc::channel::<DeviceIndependentIntRect>(timer_profile_chan).unwrap();
let _ = self.compositor_api.sender().send(
compositing_traits::CompositorMsg::GetClientWindowRect(self.webview_id(), sender),
);
let rect = receiver.recv().unwrap_or_default();
(
Size2D::new(rect.size().width as u32, rect.size().height as u32),
Point2D::new(rect.min.x, rect.min.y),
)
fn client_window(&self) -> DeviceIndependentIntRect {
let (sender, receiver) = ipc::channel().expect("Failed to create IPC channel!");
self.send_to_embedder(EmbedderMsg::GetWindowRect(self.webview_id(), sender));
receiver.recv().unwrap_or_default()
}
/// Prepares to tick animations and then does a reflow which also advances the

View file

@ -100,6 +100,7 @@ use servo_config::opts::Opts;
use servo_config::prefs::Preferences;
use servo_config::{opts, pref, prefs};
use servo_delegate::DefaultServoDelegate;
use servo_geometry::DeviceIndependentIntRect;
use servo_media::ServoMedia;
use servo_media::player::context::GlContext;
use servo_url::ServoUrl;
@ -998,6 +999,25 @@ impl Servo {
webview.delegate().show_form_control(webview, form_control);
}
},
EmbedderMsg::GetWindowRect(webview_id, response_sender) => {
let window_rect = || {
let Some(webview) = self.get_webview_handle(webview_id) else {
return DeviceIndependentIntRect::default();
};
let hidpi_scale_factor = webview.hidpi_scale_factor();
let Some(screen_geometry) = webview.delegate().screen_geometry(webview) else {
return DeviceIndependentIntRect::default();
};
(screen_geometry.window_rect.to_f32() / hidpi_scale_factor)
.round()
.to_i32()
};
if let Err(error) = response_sender.send(window_rect()) {
warn!("Failed to respond to GetWindowRect: {error}");
}
},
}
}

View file

@ -33,7 +33,7 @@ use euclid::default::Size2D as UntypedSize2D;
use ipc_channel::ipc::{self, IpcSharedMemory};
use profile_traits::mem::{OpaqueSender, ReportsChan};
use serde::{Deserialize, Serialize};
use servo_geometry::{DeviceIndependentIntRect, DeviceIndependentIntSize};
use servo_geometry::DeviceIndependentIntSize;
use webrender_api::units::{DevicePoint, LayoutVector2D, TexelRect};
use webrender_api::{
BuiltDisplayList, BuiltDisplayListDescriptor, ExternalImage, ExternalImageData,
@ -152,9 +152,6 @@ pub enum CompositorMsg {
AddFontInstance(FontInstanceKey, FontKey, f32, FontInstanceFlags),
/// Remove the given font resources from our WebRender instance.
RemoveFonts(Vec<FontKey>, Vec<FontInstanceKey>),
/// Get the client window size and position.
GetClientWindowRect(WebViewId, IpcSender<DeviceIndependentIntRect>),
/// Get the size of the screen that the client window inhabits.
GetScreenSize(WebViewId, IpcSender<DeviceIndependentIntSize>),
/// Get the available screen size, without system interface elements such as menus, docks, and

View file

@ -31,6 +31,7 @@ use malloc_size_of_derive::MallocSizeOf;
use num_derive::FromPrimitive;
use pixels::RasterImage;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use servo_geometry::DeviceIndependentIntRect;
use servo_url::ServoUrl;
use strum_macros::IntoStaticStr;
use style::queries::values::PrefersColorScheme;
@ -363,6 +364,8 @@ pub enum EmbedderMsg {
NewFavicon(WebViewId, ServoUrl),
/// The history state has changed.
HistoryChanged(WebViewId, Vec<ServoUrl>, usize),
/// Get the device independent window rectangle.
GetWindowRect(WebViewId, IpcSender<DeviceIndependentIntRect>),
/// Entered or exited fullscreen.
NotifyFullscreenStateChanged(WebViewId, bool),
/// The [`LoadStatus`] of the Given `WebView` has changed.
@ -754,22 +757,25 @@ pub struct NotificationAction {
}
/// 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`.
/// for the [Screen](https://drafts.csswg.org/cssom-view/#the-screen-interface) CSSOM APIs
/// and `window.screenLeft` / `window.screenX` / `window.screenTop` / `window.screenY` /
/// `window.moveBy`/ `window.resizeBy` / `window.outerWidth` / `window.outerHeight` /
/// `window.screen.availHeight` / `window.screen.availWidth`.
#[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
/// The available size of the screen in device pixels for the purposes of
/// the `window.screen.availHeight` / `window.screen.availWidth`. This is the size
/// available for web content on the screen, and should be `size` minus any system
/// toolbars, docks, and interface elements. 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,
/// The rectangle the `WebView`'s containing window in device pixels for the purposes of the
/// `window.screenLeft` and similar APIs. This will be converted to CSS pixels based
/// on the pixel scaling of the `WebView`.
pub window_rect: DeviceIntRect,
}
impl From<SelectElementOption> for SelectElementOptionOrOptgroup {

View file

@ -17,7 +17,7 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};
use servo::servo_config::pref;
use servo::servo_geometry::{DeviceIndependentIntRect, DeviceIndependentPixel};
use servo::webrender_api::ScrollLocation;
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePixel};
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel};
use servo::{
Cursor, ImeEvent, InputEvent, Key, KeyState, KeyboardEvent, MouseButton as ServoMouseButton,
MouseButtonAction, MouseButtonEvent, MouseLeaveEvent, MouseMoveEvent,
@ -429,22 +429,22 @@ impl WindowPortsMethods for Window {
0.0,
(self.toolbar_height.get() * self.hidpi_scale_factor()).0,
);
let screen_size = self.screen_size.to_f32() * hidpi_factor;
// FIXME: In reality, this should subtract screen space used by the system interface
// elements, but it is difficult to get this value with `winit` currently. See:
// See https://github.com/rust-windowing/winit/issues/2494
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.outer_position().unwrap_or_default();
let offset = winit_position_to_euclid_point(window_origin);
let window_rect = DeviceIntRect::from_origin_and_size(
winit_position_to_euclid_point(self.winit_window.outer_position().unwrap_or_default()),
winit_size_to_euclid_size(self.winit_window.outer_size()).to_i32(),
);
ScreenGeometry {
size: screen_size.to_i32(),
available_size: available_screen_size.to_i32(),
offset,
window_rect,
}
}

View file

@ -68,7 +68,7 @@ impl WindowPortsMethods for Window {
ScreenGeometry {
size: self.screen_size,
available_size: self.screen_size,
offset: Default::default(),
window_rect: self.inner_size.get().into(),
}
}

View file

@ -124,13 +124,12 @@ impl ServoDelegate for ServoShellServoDelegate {
impl WebViewDelegate for RunningAppState {
fn screen_geometry(&self, _webview: WebView) -> Option<ScreenGeometry> {
let coord = self.callbacks.coordinates.borrow();
let offset = coord.origin();
let available_size = coord.size();
let screen_size = coord.size();
Some(ScreenGeometry {
size: screen_size,
available_size,
offset,
window_rect: DeviceIntRect::from_origin_and_size(coord.origin(), coord.size()),
})
}

View file

@ -1,9 +1,3 @@
[close.py]
[test_close_browsing_context_with_accepted_beforeunload_prompt[tab\]]
expected: FAIL
[test_close_browsing_context_with_accepted_beforeunload_prompt[window\]]
expected: FAIL
[test_element_usage_after_closing_browsing_context]
expected: ERROR

View file

@ -211,9 +211,3 @@
[test_resettable_element_does_not_satisfy_validation_constraints[range-foo\]]
expected: FAIL
[test_resettable_element_does_not_satisfy_validation_constraints[color-foo\]]
expected: FAIL
[test_resettable_element_does_not_satisfy_validation_constraints[datetime-foo\]]
expected: FAIL

View file

@ -1,3 +0,0 @@
[get.py]
[test_payload]
expected: FAIL

View file

@ -1,3 +0,0 @@
[alerts.py]
[test_retain_tab_modal_status]
expected: FAIL

View file

@ -1,7 +1,4 @@
[switch.py]
[test_finds_exising_user_prompt_after_tab_switch[alert\]]
expected: FAIL
[test_finds_exising_user_prompt_after_tab_switch[confirm\]]
expected: ERROR