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(); .collect();
let _ = result_sender.send((font_keys, font_instance_keys)); 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) => { CompositorMsg::GetScreenSize(webview_id, response_sender) => {
let screen_size = self let screen_size = self
.webview_renderers .webview_renderers
@ -993,11 +981,6 @@ impl IOCompositor {
.collect(); .collect();
let _ = result_sender.send((font_keys, font_instance_keys)); 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) => { CompositorMsg::GetScreenSize(_, response_sender) => {
if let Err(error) = response_sender.send(Default::default()) { if let Err(error) = response_sender.send(Default::default()) {
warn!("Sending response to get client window failed ({error:?})."); warn!("Sending response to get client window failed ({error:?}).");

View file

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

View file

@ -19,14 +19,12 @@ use embedder_traits::{
MouseButtonEvent, MouseMoveEvent, ScrollEvent as EmbedderScrollEvent, ShutdownState, MouseButtonEvent, MouseMoveEvent, ScrollEvent as EmbedderScrollEvent, ShutdownState,
TouchEvent, TouchEventResult, TouchEventType, TouchId, ViewportDetails, TouchEvent, TouchEventResult, TouchEventType, TouchId, ViewportDetails,
}; };
use euclid::{Box2D, Point2D, Scale, Size2D, Vector2D}; use euclid::{Point2D, Scale, Size2D, Vector2D};
use fnv::FnvHashSet; use fnv::FnvHashSet;
use log::{debug, warn}; use log::{debug, warn};
use servo_geometry::DeviceIndependentPixel; use servo_geometry::DeviceIndependentPixel;
use style_traits::{CSSPixel, PinchZoomFactor}; use style_traits::{CSSPixel, PinchZoomFactor};
use webrender_api::units::{ use webrender_api::units::{DeviceIntPoint, DevicePixel, DevicePoint, DeviceRect, LayoutVector2D};
DeviceIntPoint, DeviceIntRect, DevicePixel, DevicePoint, DeviceRect, LayoutVector2D,
};
use webrender_api::{ExternalScrollId, HitTestFlags, ScrollLocation}; use webrender_api::{ExternalScrollId, HitTestFlags, ScrollLocation};
use crate::compositor::{HitTestError, PipelineDetails, ServoRenderer}; use crate::compositor::{HitTestError, PipelineDetails, ServoRenderer};
@ -1041,20 +1039,6 @@ impl WebViewRenderer {
old_rect != self.rect 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> { pub(crate) fn screen_size(&self) -> Size2D<i32, DeviceIndependentPixel> {
let screen_geometry = self.webview.screen_geometry().unwrap_or_default(); let screen_geometry = self.webview.screen_geometry().unwrap_or_default();
(screen_geometry.size.to_f32() / self.hidpi_scale_factor).to_i32() (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::SetCursor(..) => target_variant!("SetCursor"),
Self::NewFavicon(..) => target_variant!("NewFavicon"), Self::NewFavicon(..) => target_variant!("NewFavicon"),
Self::HistoryChanged(..) => target_variant!("HistoryChanged"), Self::HistoryChanged(..) => target_variant!("HistoryChanged"),
Self::GetWindowRect(..) => target_variant!("GetWindowRect"),
Self::NotifyFullscreenStateChanged(..) => { Self::NotifyFullscreenStateChanged(..) => {
target_variant!("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 // https://drafts.csswg.org/cssom-view/#dom-window-resizeby
fn ResizeBy(&self, x: i32, y: i32) { fn ResizeBy(&self, x: i32, y: i32) {
let (size, _) = self.client_window(); let size = self.client_window().size();
// Step 1 // Step 1
self.ResizeTo( self.ResizeTo(x + size.width, y + size.height)
x + size.width.to_i32().unwrap_or(1),
y + size.height.to_i32().unwrap_or(1),
)
} }
// https://drafts.csswg.org/cssom-view/#dom-window-moveto // 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 // https://drafts.csswg.org/cssom-view/#dom-window-moveby
fn MoveBy(&self, x: i32, y: i32) { fn MoveBy(&self, x: i32, y: i32) {
let (_, origin) = self.client_window(); let origin = self.client_window().min;
// Step 1 // Step 1
self.MoveTo(x + origin.x, y + origin.y) self.MoveTo(x + origin.x, y + origin.y)
} }
// https://drafts.csswg.org/cssom-view/#dom-window-screenx // https://drafts.csswg.org/cssom-view/#dom-window-screenx
fn ScreenX(&self) -> i32 { fn ScreenX(&self) -> i32 {
let (_, origin) = self.client_window(); self.client_window().min.x
origin.x
} }
// https://drafts.csswg.org/cssom-view/#dom-window-screeny // https://drafts.csswg.org/cssom-view/#dom-window-screeny
fn ScreenY(&self) -> i32 { fn ScreenY(&self) -> i32 {
let (_, origin) = self.client_window(); self.client_window().min.y
origin.y
} }
// https://drafts.csswg.org/cssom-view/#dom-window-outerheight // https://drafts.csswg.org/cssom-view/#dom-window-outerheight
fn OuterHeight(&self) -> i32 { fn OuterHeight(&self) -> i32 {
let (size, _) = self.client_window(); self.client_window().height()
size.height.to_i32().unwrap_or(1)
} }
// https://drafts.csswg.org/cssom-view/#dom-window-outerwidth // https://drafts.csswg.org/cssom-view/#dom-window-outerwidth
fn OuterWidth(&self) -> i32 { fn OuterWidth(&self) -> i32 {
let (size, _) = self.client_window(); self.client_window().width()
size.width.to_i32().unwrap_or(1)
} }
// https://drafts.csswg.org/cssom-view/#dom-window-devicepixelratio // https://drafts.csswg.org/cssom-view/#dom-window-devicepixelratio
@ -2151,18 +2144,12 @@ impl Window {
self.viewport_details.get().hidpi_scale_factor self.viewport_details.get().hidpi_scale_factor
} }
fn client_window(&self) -> (Size2D<u32, CSSPixel>, Point2D<i32, CSSPixel>) { fn client_window(&self) -> DeviceIndependentIntRect {
let timer_profile_chan = self.global().time_profiler_chan().clone(); let (sender, receiver) = ipc::channel().expect("Failed to create IPC channel!");
let (sender, receiver) =
ProfiledIpc::channel::<DeviceIndependentIntRect>(timer_profile_chan).unwrap(); self.send_to_embedder(EmbedderMsg::GetWindowRect(self.webview_id(), sender));
let _ = self.compositor_api.sender().send(
compositing_traits::CompositorMsg::GetClientWindowRect(self.webview_id(), sender), receiver.recv().unwrap_or_default()
);
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),
)
} }
/// Prepares to tick animations and then does a reflow which also advances the /// 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::prefs::Preferences;
use servo_config::{opts, pref, prefs}; use servo_config::{opts, pref, prefs};
use servo_delegate::DefaultServoDelegate; use servo_delegate::DefaultServoDelegate;
use servo_geometry::DeviceIndependentIntRect;
use servo_media::ServoMedia; use servo_media::ServoMedia;
use servo_media::player::context::GlContext; use servo_media::player::context::GlContext;
use servo_url::ServoUrl; use servo_url::ServoUrl;
@ -998,6 +999,25 @@ impl Servo {
webview.delegate().show_form_control(webview, form_control); 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 ipc_channel::ipc::{self, IpcSharedMemory};
use profile_traits::mem::{OpaqueSender, ReportsChan}; use profile_traits::mem::{OpaqueSender, ReportsChan};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use servo_geometry::{DeviceIndependentIntRect, DeviceIndependentIntSize}; use servo_geometry::DeviceIndependentIntSize;
use webrender_api::units::{DevicePoint, LayoutVector2D, TexelRect}; use webrender_api::units::{DevicePoint, LayoutVector2D, TexelRect};
use webrender_api::{ use webrender_api::{
BuiltDisplayList, BuiltDisplayListDescriptor, ExternalImage, ExternalImageData, BuiltDisplayList, BuiltDisplayListDescriptor, ExternalImage, ExternalImageData,
@ -152,9 +152,6 @@ pub enum CompositorMsg {
AddFontInstance(FontInstanceKey, FontKey, f32, FontInstanceFlags), AddFontInstance(FontInstanceKey, FontKey, f32, FontInstanceFlags),
/// Remove the given font resources from our WebRender instance. /// Remove the given font resources from our WebRender instance.
RemoveFonts(Vec<FontKey>, Vec<FontInstanceKey>), 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. /// Get the size of the screen that the client window inhabits.
GetScreenSize(WebViewId, IpcSender<DeviceIndependentIntSize>), GetScreenSize(WebViewId, IpcSender<DeviceIndependentIntSize>),
/// Get the available screen size, without system interface elements such as menus, docks, and /// 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 num_derive::FromPrimitive;
use pixels::RasterImage; use pixels::RasterImage;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use servo_geometry::DeviceIndependentIntRect;
use servo_url::ServoUrl; use servo_url::ServoUrl;
use strum_macros::IntoStaticStr; use strum_macros::IntoStaticStr;
use style::queries::values::PrefersColorScheme; use style::queries::values::PrefersColorScheme;
@ -363,6 +364,8 @@ pub enum EmbedderMsg {
NewFavicon(WebViewId, ServoUrl), NewFavicon(WebViewId, ServoUrl),
/// The history state has changed. /// The history state has changed.
HistoryChanged(WebViewId, Vec<ServoUrl>, usize), HistoryChanged(WebViewId, Vec<ServoUrl>, usize),
/// Get the device independent window rectangle.
GetWindowRect(WebViewId, IpcSender<DeviceIndependentIntRect>),
/// Entered or exited fullscreen. /// Entered or exited fullscreen.
NotifyFullscreenStateChanged(WebViewId, bool), NotifyFullscreenStateChanged(WebViewId, bool),
/// The [`LoadStatus`] of the Given `WebView` has changed. /// 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 /// Information about a `WebView`'s screen geometry and offset. This is used
/// for the [Screen](https://drafts.csswg.org/cssom-view/#the-screen-interface) /// for the [Screen](https://drafts.csswg.org/cssom-view/#the-screen-interface) CSSOM APIs
/// CSSOM APIs and `window.screenLeft` / `window.screenTop`. /// 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)] #[derive(Clone, Copy, Debug, Default)]
pub struct ScreenGeometry { pub struct ScreenGeometry {
/// The size of the screen in device pixels. This will be converted to /// The size of the screen in device pixels. This will be converted to
/// CSS pixels based on the pixel scaling of the `WebView`. /// CSS pixels based on the pixel scaling of the `WebView`.
pub size: DeviceIntSize, 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 /// available for web content on the screen, and should be `size` minus any system
/// toolbars, docks, and interface elements. This will be converted to /// toolbars, docks, and interface elements. This will be converted to
/// CSS pixels based on the pixel scaling of the `WebView`. /// CSS pixels based on the pixel scaling of the `WebView`.
pub available_size: DeviceIntSize, pub available_size: DeviceIntSize,
/// The offset of the `WebView` in device pixels for the purposes of the `window.screenLeft` /// The rectangle the `WebView`'s containing window in device pixels for the purposes of the
/// and `window.screenTop` APIs. This will be converted to CSS pixels based on the pixel scaling /// `window.screenLeft` and similar APIs. This will be converted to CSS pixels based
/// of the `WebView`. /// on the pixel scaling of the `WebView`.
pub offset: DeviceIntPoint, pub window_rect: DeviceIntRect,
} }
impl From<SelectElementOption> for SelectElementOptionOrOptgroup { 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_config::pref;
use servo::servo_geometry::{DeviceIndependentIntRect, DeviceIndependentPixel}; use servo::servo_geometry::{DeviceIndependentIntRect, DeviceIndependentPixel};
use servo::webrender_api::ScrollLocation; use servo::webrender_api::ScrollLocation;
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePixel}; use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, 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, MouseLeaveEvent, MouseMoveEvent, MouseButtonAction, MouseButtonEvent, MouseLeaveEvent, MouseMoveEvent,
@ -429,22 +429,22 @@ impl WindowPortsMethods for Window {
0.0, 0.0,
(self.toolbar_height.get() * self.hidpi_scale_factor()).0, (self.toolbar_height.get() * self.hidpi_scale_factor()).0,
); );
let screen_size = self.screen_size.to_f32() * hidpi_factor; let screen_size = self.screen_size.to_f32() * hidpi_factor;
// FIXME: In reality, this should subtract screen space used by the system interface // 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: // elements, but it is difficult to get this value with `winit` currently. See:
// See https://github.com/rust-windowing/winit/issues/2494 // See https://github.com/rust-windowing/winit/issues/2494
let available_screen_size = screen_size - toolbar_size; let available_screen_size = screen_size - toolbar_size;
// Offset the WebView origin by the toolbar so that it reflects the actual viewport and let window_rect = DeviceIntRect::from_origin_and_size(
// not the window origin. winit_position_to_euclid_point(self.winit_window.outer_position().unwrap_or_default()),
let window_origin = self.winit_window.outer_position().unwrap_or_default(); winit_size_to_euclid_size(self.winit_window.outer_size()).to_i32(),
let offset = winit_position_to_euclid_point(window_origin); );
ScreenGeometry { ScreenGeometry {
size: screen_size.to_i32(), size: screen_size.to_i32(),
available_size: available_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 { ScreenGeometry {
size: self.screen_size, size: self.screen_size,
available_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 { impl WebViewDelegate for RunningAppState {
fn screen_geometry(&self, _webview: WebView) -> Option<ScreenGeometry> { fn screen_geometry(&self, _webview: WebView) -> Option<ScreenGeometry> {
let coord = self.callbacks.coordinates.borrow(); let coord = self.callbacks.coordinates.borrow();
let offset = coord.origin();
let available_size = coord.size(); let available_size = coord.size();
let screen_size = coord.size(); let screen_size = coord.size();
Some(ScreenGeometry { Some(ScreenGeometry {
size: screen_size, size: screen_size,
available_size, available_size,
offset, window_rect: DeviceIntRect::from_origin_and_size(coord.origin(), coord.size()),
}) })
} }

View file

@ -1,9 +1,3 @@
[close.py] [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] [test_element_usage_after_closing_browsing_context]
expected: ERROR expected: ERROR

View file

@ -211,9 +211,3 @@
[test_resettable_element_does_not_satisfy_validation_constraints[range-foo\]] [test_resettable_element_does_not_satisfy_validation_constraints[range-foo\]]
expected: FAIL 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] [switch.py]
[test_finds_exising_user_prompt_after_tab_switch[alert\]]
expected: FAIL
[test_finds_exising_user_prompt_after_tab_switch[confirm\]] [test_finds_exising_user_prompt_after_tab_switch[confirm\]]
expected: ERROR expected: ERROR