webdriver: Implement maximize window for both headless&headed window (#38271)

- Implement [Maximize
Window](https://w3c.github.io/webdriver/#maximize-window)
- Previously, headless window screen size is same as inner size if not
specified in preference. We make it double as required by the test to
not have max window initially.
- Some other random cleanup.

Testing: webdriver Stress test for maximize window (headed + headless).

---------

Signed-off-by: Euclid Ye <euclid.ye@huawei.com>
This commit is contained in:
Euclid Ye 2025-07-26 20:21:17 +08:00 committed by GitHub
parent 9ef4d0c9d7
commit a3de3ffa75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 76 additions and 91 deletions

View file

@ -4422,6 +4422,7 @@ where
WebDriverCommandMsg::GetWindowRect(..) |
WebDriverCommandMsg::GetViewportSize(..) |
WebDriverCommandMsg::SetWindowRect(..) |
WebDriverCommandMsg::MaximizeWebView(..) |
WebDriverCommandMsg::LoadUrl(..) |
WebDriverCommandMsg::Refresh(..) |
WebDriverCommandMsg::SendKeys(..) |

View file

@ -138,6 +138,8 @@ pub enum WebDriverCommandMsg {
DeviceIndependentIntRect,
IpcSender<DeviceIndependentIntRect>,
),
/// Maximize the window. Send back result window rectangle.
MaximizeWebView(WebViewId, IpcSender<DeviceIndependentIntRect>),
/// Take a screenshot of the viewport.
TakeScreenshot(
WebViewId,

View file

@ -911,7 +911,6 @@ impl Handler {
// (TODO) Step 14. Fully exit fullscreen.
// (TODO) Step 15. Restore the window.
let (sender, receiver) = ipc::channel().unwrap();
let current = LazyCell::new(|| {
let WebDriverResponse::WindowRect(current) = self
@ -929,7 +928,7 @@ impl Handler {
params.width.unwrap_or_else(|| current.width),
params.height.unwrap_or_else(|| current.height),
);
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.
@ -941,7 +940,7 @@ impl Handler {
Point2D::new(x, y),
Size2D::new(width, height),
),
sender.clone(),
sender,
))?;
let window_rect = wait_for_script_response(receiver)?;
@ -955,6 +954,38 @@ impl Handler {
Ok(WebDriverResponse::WindowRect(window_size_response))
}
/// <https://w3c.github.io/webdriver/#maximize-window>
fn handle_maximize_window(&mut self) -> WebDriverResult<WebDriverResponse> {
// Step 1. If the remote end does not support the Maximize Window command for session's
// current top-level browsing context for any reason,
// return error with error code unsupported operation.
let webview_id = self.session()?.webview_id;
// Step 2. If session's current top-level browsing context is no longer open,
// return error with error code no such window.
self.verify_top_level_browsing_context_is_open(webview_id)?;
// Step 3. Try to handle any user prompts with session.
self.handle_any_user_prompts(self.session()?.webview_id)?;
// Step 4. (TODO) Fully exit fullscreen.
// Step 5. (TODO) Restore the window.
// Step 6. Maximize the window of session's current top-level browsing context.
let (sender, receiver) = ipc::channel().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::MaximizeWebView(webview_id, sender))?;
let window_rect = wait_for_script_response(receiver)?;
debug!("Result window_rect: {window_rect:?}");
let window_size_response = WindowRectResponse {
x: window_rect.min.x,
y: window_rect.min.y,
width: window_rect.width(),
height: window_rect.height(),
};
Ok(WebDriverResponse::WindowRect(window_size_response))
}
fn handle_is_enabled(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
let (sender, receiver) = ipc::channel().unwrap();
@ -2483,6 +2514,7 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler {
WebDriverCommand::GetWindowHandles => self.handle_window_handles(),
WebDriverCommand::NewWindow(ref parameters) => self.handle_new_window(parameters),
WebDriverCommand::CloseWindow => self.handle_close_window(),
WebDriverCommand::MaximizeWindow => self.handle_maximize_window(),
WebDriverCommand::SwitchToFrame(ref parameters) => {
self.handle_switch_to_frame(parameters)
},

View file

@ -392,6 +392,22 @@ impl App {
warn!("Failed to send response of GetWindowSize: {error}");
}
},
WebDriverCommandMsg::MaximizeWebView(webview_id, response_sender) => {
let window = self
.windows
.values()
.next()
.expect("Should have at least one window in servoshell");
window.maximize(
&running_state
.webview_by_id(webview_id)
.expect("Webview must exists as we just verified"),
);
if let Err(error) = response_sender.send(window.window_rect()) {
warn!("Failed to send response of GetWindowSize: {error}");
}
},
WebDriverCommandMsg::SetWindowRect(webview_id, requested_rect, size_sender) => {
let Some(webview) = running_state.webview_by_id(webview_id) else {
continue;

View file

@ -767,10 +767,7 @@ impl WindowPortsMethods for Window {
// this prevents a crash in the compositor due to invalid surface size
self.winit_window.set_min_inner_size(Some(PhysicalSize::new(
MIN_INNER_WIDTH,
i32::max(
MIN_INNER_HEIGHT,
(self.toolbar_height() * self.hidpi_scale_factor()).0 as i32,
),
MIN_INNER_HEIGHT.max((self.toolbar_height() * self.hidpi_scale_factor()).0 as i32),
)));
}
@ -808,6 +805,10 @@ impl WindowPortsMethods for Window {
Some(winit::window::Theme::Light) | None => servo::Theme::Light,
}
}
fn maximize(&self, _webview: &WebView) {
self.winit_window.set_maximized(true);
}
}
fn winit_phase_to_touch_event_type(phase: TouchPhase) -> TouchEventType {

View file

@ -13,7 +13,7 @@ use servo::servo_geometry::{
DeviceIndependentIntRect, DeviceIndependentPixel, convert_rect_to_css_pixel,
};
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel};
use servo::{RenderingContext, ScreenGeometry, SoftwareRenderingContext};
use servo::{RenderingContext, ScreenGeometry, SoftwareRenderingContext, WebView};
use winit::dpi::PhysicalSize;
use super::app_state::RunningAppState;
@ -47,7 +47,7 @@ impl Window {
let screen_size = servoshell_preferences
.screen_size_override
.map_or(inner_size, |screen_size_override| {
.map_or(inner_size * 2, |screen_size_override| {
(screen_size_override.to_f32() * hidpi_factor).to_i32()
});
@ -86,7 +86,7 @@ impl WindowPortsMethods for Window {
fn request_resize(
&self,
webview: &::servo::WebView,
webview: &WebView,
outer_size: DeviceIntSize,
) -> Option<DeviceIntSize> {
let new_size = DeviceIntSize::new(
@ -167,4 +167,17 @@ impl WindowPortsMethods for Window {
fn rendering_context(&self) -> Rc<dyn RenderingContext> {
self.rendering_context.clone()
}
fn maximize(&self, webview: &WebView) {
self.window_position.set(Point2D::zero());
self.inner_size.set(self.screen_size);
// Because we are managing the rendering surface ourselves, there will be no other
// notification (such as from the display manager) that it has changed size, so we
// must notify the compositor here.
webview.move_resize(self.screen_size.to_f32().into());
webview.resize(PhysicalSize::new(
self.screen_size.width as u32,
self.screen_size.height as u32,
));
}
}

View file

@ -65,4 +65,5 @@ pub trait WindowPortsMethods {
servo::Theme::Light
}
fn window_rect(&self) -> DeviceIndependentIntRect;
fn maximize(&self, webview: &WebView);
}

View file

@ -1,13 +1,4 @@
[maximize.py]
[test_no_top_browsing_context]
expected: FAIL
[test_no_browsing_context]
expected: FAIL
[test_response_payload]
expected: FAIL
[test_fully_exit_fullscreen]
expected: FAIL

View file

@ -1,15 +0,0 @@
[stress.py]
[test_stress[0\]]
expected: FAIL
[test_stress[1\]]
expected: FAIL
[test_stress[2\]]
expected: FAIL
[test_stress[3\]]
expected: FAIL
[test_stress[4\]]
expected: FAIL

View file

@ -1,54 +0,0 @@
[user_prompts.py]
[test_accept[alert-None\]]
expected: FAIL
[test_accept[confirm-True\]]
expected: FAIL
[test_accept[prompt-\]]
expected: FAIL
[test_accept_and_notify[alert-None\]]
expected: FAIL
[test_accept_and_notify[confirm-True\]]
expected: FAIL
[test_accept_and_notify[prompt-\]]
expected: FAIL
[test_dismiss[alert-None\]]
expected: FAIL
[test_dismiss[confirm-False\]]
expected: FAIL
[test_dismiss[prompt-None\]]
expected: FAIL
[test_dismiss_and_notify[alert-None\]]
expected: FAIL
[test_dismiss_and_notify[confirm-False\]]
expected: FAIL
[test_dismiss_and_notify[prompt-None\]]
expected: FAIL
[test_ignore[alert\]]
expected: FAIL
[test_ignore[confirm\]]
expected: FAIL
[test_ignore[prompt\]]
expected: FAIL
[test_default[alert-None\]]
expected: FAIL
[test_default[confirm-False\]]
expected: FAIL
[test_default[prompt-None\]]
expected: FAIL

View file

@ -2,8 +2,5 @@
[test_restore_from_fullscreen]
expected: FAIL
[test_restore_from_maximized]
expected: FAIL
[test_set_to_available_size]
expected: FAIL