Implement setting position through webdriver for headed window (#38209)

Previously, we pretend we are able to set position in response. Now we
can really do it.

Testing: Able to set position accurately when tested locally.
Fixes: Task 5 of #37804.

---------

Signed-off-by: Euclid Ye <euclid.ye@huawei.com>
This commit is contained in:
Euclid Ye 2025-07-23 02:26:37 +08:00 committed by GitHub
parent cff48d4910
commit 4ff6b1d4a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 64 additions and 36 deletions

1
Cargo.lock generated
View file

@ -9422,6 +9422,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"servo_config", "servo_config",
"servo_geometry",
"servo_url", "servo_url",
"stylo_traits", "stylo_traits",
"uuid", "uuid",

View file

@ -4461,7 +4461,7 @@ where
WebDriverCommandMsg::IsWebViewOpen(..) | WebDriverCommandMsg::IsWebViewOpen(..) |
WebDriverCommandMsg::GetWindowRect(..) | WebDriverCommandMsg::GetWindowRect(..) |
WebDriverCommandMsg::GetViewportSize(..) | WebDriverCommandMsg::GetViewportSize(..) |
WebDriverCommandMsg::SetWindowSize(..) | WebDriverCommandMsg::SetWindowRect(..) |
WebDriverCommandMsg::LoadUrl(..) | WebDriverCommandMsg::LoadUrl(..) |
WebDriverCommandMsg::Refresh(..) | WebDriverCommandMsg::Refresh(..) |
WebDriverCommandMsg::SendKeys(..) | WebDriverCommandMsg::SendKeys(..) |

View file

@ -16,7 +16,7 @@ use keyboard_types::KeyboardEvent;
use keyboard_types::webdriver::Event as WebDriverInputEvent; use keyboard_types::webdriver::Event as WebDriverInputEvent;
use pixels::RasterImage; use pixels::RasterImage;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use servo_geometry::{DeviceIndependentIntRect, DeviceIndependentIntSize, DeviceIndependentPixel}; use servo_geometry::DeviceIndependentIntRect;
use servo_url::ServoUrl; use servo_url::ServoUrl;
use style_traits::CSSPixel; use style_traits::CSSPixel;
use webdriver::common::{WebElement, WebFrame, WebWindow}; use webdriver::common::{WebElement, WebFrame, WebWindow};
@ -132,11 +132,11 @@ pub enum WebDriverCommandMsg {
// expect one response from constellation for each tick actions. // expect one response from constellation for each tick actions.
Option<WebDriverMessageId>, Option<WebDriverMessageId>,
), ),
/// Set the window size. /// Set the outer window rectangle.
SetWindowSize( SetWindowRect(
WebViewId, WebViewId,
DeviceIndependentIntSize, DeviceIndependentIntRect,
IpcSender<Size2D<i32, DeviceIndependentPixel>>, IpcSender<DeviceIndependentIntRect>,
), ),
/// Take a screenshot of the viewport. /// Take a screenshot of the viewport.
TakeScreenshot( TakeScreenshot(

View file

@ -28,6 +28,7 @@ pixels = { path = "../pixels" }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
servo_config = { path = "../config" } servo_config = { path = "../config" }
servo_geometry = { path = "../geometry" }
servo_url = { path = "../url" } servo_url = { path = "../url" }
stylo_traits = { workspace = true } stylo_traits = { workspace = true }
uuid = { workspace = true } uuid = { workspace = true }

View file

@ -28,7 +28,7 @@ use embedder_traits::{
WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, WebDriverMessageId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, WebDriverMessageId,
WebDriverScriptCommand, WebDriverScriptCommand,
}; };
use euclid::{Rect, Size2D}; use euclid::{Point2D, Rect, Size2D};
use http::method::Method; use http::method::Method;
use image::{DynamicImage, ImageFormat, RgbaImage}; use image::{DynamicImage, ImageFormat, RgbaImage};
use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
@ -41,6 +41,7 @@ use serde::ser::Serializer;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{Value, json}; use serde_json::{Value, json};
use servo_config::prefs::{self, PrefValue, Preferences}; use servo_config::prefs::{self, PrefValue, Preferences};
use servo_geometry::DeviceIndependentIntRect;
use servo_url::ServoUrl; use servo_url::ServoUrl;
use style_traits::CSSPixel; use style_traits::CSSPixel;
use uuid::Uuid; use uuid::Uuid;
@ -889,7 +890,7 @@ impl Handler {
} }
/// <https://w3c.github.io/webdriver/#set-window-rect> /// <https://w3c.github.io/webdriver/#set-window-rect>
fn handle_set_window_size( fn handle_set_window_rect(
&self, &self,
params: &WindowRectParameters, params: &WindowRectParameters,
) -> WebDriverResult<WebDriverResponse> { ) -> WebDriverResult<WebDriverResponse> {
@ -908,18 +909,10 @@ impl Handler {
// Step 13. Handle any user prompt. // Step 13. Handle any user prompt.
self.handle_any_user_prompts(webview_id)?; self.handle_any_user_prompts(webview_id)?;
// We don't current allow modifying the window x/y positions, so we can just
// return the current window rectangle if not changing dimension.
if params.width.is_none() && params.height.is_none() {
return self.handle_window_rect(VerifyBrowsingContextIsOpen::No);
}
// (TODO) Step 14. Fully exit fullscreen. // (TODO) Step 14. Fully exit fullscreen.
// (TODO) Step 15. Restore the window. // (TODO) Step 15. Restore the window.
let (sender, receiver) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap();
// Step 16 - 17. Set the width/height in CSS pixels.
// This should be done as long as one of width/height is not null.
let current = LazyCell::new(|| { let current = LazyCell::new(|| {
let WebDriverResponse::WindowRect(current) = self let WebDriverResponse::WindowRect(current) = self
.handle_window_rect(VerifyBrowsingContextIsOpen::No) .handle_window_rect(VerifyBrowsingContextIsOpen::No)
@ -930,24 +923,34 @@ impl Handler {
current current
}); });
let (width, height) = ( let (x, y, width, height) = (
params.x.unwrap_or_else(|| current.x),
params.y.unwrap_or_else(|| current.y),
params.width.unwrap_or_else(|| current.width), params.width.unwrap_or_else(|| current.width),
params.height.unwrap_or_else(|| current.height), params.height.unwrap_or_else(|| current.height),
); );
self.send_message_to_embedder(WebDriverCommandMsg::SetWindowSize( // Step 16 - 17. Set the width/height in CSS pixels.
// This should be done as long as one of width/height is not null.
// Step 18 - 19. Set the screen x/y in CSS pixels.
// This should be done as long as one of width/height is not null.
self.send_message_to_embedder(WebDriverCommandMsg::SetWindowRect(
webview_id, webview_id,
Size2D::new(width, height), DeviceIndependentIntRect::from_origin_and_size(
Point2D::new(x, y),
Size2D::new(width, height),
),
sender.clone(), sender.clone(),
))?; ))?;
let window_size = wait_for_script_response(receiver)?; let window_rect = wait_for_script_response(receiver)?;
debug!("window_size after resizing: {window_size:?}"); debug!("Result window_rect: {window_rect:?}");
let window_size_response = WindowRectResponse { let window_size_response = WindowRectResponse {
x: 0, x: window_rect.min.x,
y: 0, y: window_rect.min.y,
width: window_size.width, width: window_rect.width(),
height: window_size.height, height: window_rect.height(),
}; };
Ok(WebDriverResponse::WindowRect(window_size_response)) Ok(WebDriverResponse::WindowRect(window_size_response))
} }
@ -2449,7 +2452,7 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler {
WebDriverCommand::GetWindowRect => { WebDriverCommand::GetWindowRect => {
self.handle_window_rect(VerifyBrowsingContextIsOpen::Yes) self.handle_window_rect(VerifyBrowsingContextIsOpen::Yes)
}, },
WebDriverCommand::SetWindowRect(ref size) => self.handle_set_window_size(size), WebDriverCommand::SetWindowRect(ref size) => self.handle_set_window_rect(size),
WebDriverCommand::IsEnabled(ref element) => self.handle_is_enabled(element), WebDriverCommand::IsEnabled(ref element) => self.handle_is_enabled(element),
WebDriverCommand::IsSelected(ref element) => self.handle_is_selected(element), WebDriverCommand::IsSelected(ref element) => self.handle_is_selected(element),
WebDriverCommand::GoBack => self.handle_go_back(), WebDriverCommand::GoBack => self.handle_go_back(),

View file

@ -21,10 +21,11 @@ use log::{info, trace, warn};
use net::protocols::ProtocolRegistry; use net::protocols::ProtocolRegistry;
use servo::config::opts::Opts; use servo::config::opts::Opts;
use servo::config::prefs::Preferences; use servo::config::prefs::Preferences;
use servo::servo_geometry::convert_size_to_css_pixel; use servo::servo_geometry::convert_rect_to_css_pixel;
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::ScrollLocation; use servo::webrender_api::ScrollLocation;
use servo::webrender_api::units::DeviceIntRect;
use servo::{ use servo::{
EventLoopWaker, ImeEvent, InputEvent, KeyboardEvent, MouseButtonEvent, MouseMoveEvent, EventLoopWaker, ImeEvent, InputEvent, KeyboardEvent, MouseButtonEvent, MouseMoveEvent,
WebDriverCommandMsg, WebDriverScriptCommand, WebDriverUserPromptAction, WheelDelta, WheelEvent, WebDriverCommandMsg, WebDriverScriptCommand, WebDriverUserPromptAction, WheelDelta, WheelEvent,
@ -38,6 +39,7 @@ use winit::window::WindowId;
use super::app_state::AppState; use super::app_state::AppState;
use super::events_loop::{AppEvent, EventLoopProxy, EventsLoop}; use super::events_loop::{AppEvent, EventLoopProxy, EventsLoop};
use super::geometry::winit_position_to_euclid_point;
use super::minibrowser::{Minibrowser, MinibrowserEvent}; use super::minibrowser::{Minibrowser, MinibrowserEvent};
use super::{headed_window, headless_window}; use super::{headed_window, headless_window};
use crate::desktop::app_state::RunningAppState; use crate::desktop::app_state::RunningAppState;
@ -392,7 +394,7 @@ impl App {
warn!("Failed to send response of GetWindowSize: {error}"); warn!("Failed to send response of GetWindowSize: {error}");
} }
}, },
WebDriverCommandMsg::SetWindowSize(webview_id, requested_size, size_sender) => { WebDriverCommandMsg::SetWindowRect(webview_id, requested_rect, size_sender) => {
let Some(webview) = running_state.webview_by_id(webview_id) else { let Some(webview) = running_state.webview_by_id(webview_id) else {
continue; continue;
}; };
@ -404,20 +406,35 @@ impl App {
.expect("Should have at least one window in servoshell"); .expect("Should have at least one window in servoshell");
let scale = window.hidpi_scale_factor(); let scale = window.hidpi_scale_factor();
let requested_physical_size = let requested_physical_rect =
(requested_size.to_f32() * scale).round().to_i32(); (requested_rect.to_f32() * scale).round().to_i32();
// When None is returned, it means that the request went to the display system, // When None is returned, it means that the request went to the display system,
// and the actual size will be delivered later with the WindowEvent::Resized. // and the actual size will be delivered later with the WindowEvent::Resized.
let returned_size = window.request_resize(&webview, requested_physical_size); // Step 17. Set Width/Height.
let returned_size =
window.request_resize(&webview, requested_physical_rect.size());
// TODO: Handle None case. For now, we assume always succeed. // TODO: Handle None case. For now, we assume always succeed.
// In reality, the request may exceed available screen size. // In reality, the request may exceed available screen size.
if let Err(error) = size_sender.send( // Step 18. Set position of the window.
returned_size window.set_position(requested_physical_rect.min);
.map(|size| convert_size_to_css_pixel(size, scale))
.unwrap_or(requested_size), let result_physical_position = window
) { .winit_window()
.and_then(|window| window.outer_position().ok())
.map(winit_position_to_euclid_point)
.unwrap_or(requested_physical_rect.min);
let reply_rect = convert_rect_to_css_pixel(
DeviceIntRect::from_origin_and_size(
result_physical_position,
returned_size.unwrap_or(requested_physical_rect.size()),
),
scale,
);
if let Err(error) = size_sender.send(reply_rect) {
warn!("Failed to send window size: {error}"); warn!("Failed to send window size: {error}");
} }
}, },

View file

@ -480,6 +480,12 @@ impl WindowPortsMethods for Window {
fn request_resize(&self, _: &WebView, new_outer_size: DeviceIntSize) -> Option<DeviceIntSize> { fn request_resize(&self, _: &WebView, new_outer_size: DeviceIntSize) -> Option<DeviceIntSize> {
let outer_size = self.winit_window.outer_size(); let outer_size = self.winit_window.outer_size();
if outer_size.width == new_outer_size.width as u32 &&
outer_size.height == new_outer_size.height as u32
{
return Some(new_outer_size);
}
let inner_size = self.winit_window.inner_size(); let inner_size = self.winit_window.inner_size();
let decoration_height = outer_size.height - inner_size.height; let decoration_height = outer_size.height - inner_size.height;
let decoration_width = outer_size.width - inner_size.width; let decoration_width = outer_size.width - inner_size.width;