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::GetWindowRect(..) |
WebDriverCommandMsg::GetViewportSize(..) | WebDriverCommandMsg::GetViewportSize(..) |
WebDriverCommandMsg::SetWindowRect(..) | WebDriverCommandMsg::SetWindowRect(..) |
WebDriverCommandMsg::MaximizeWebView(..) |
WebDriverCommandMsg::LoadUrl(..) | WebDriverCommandMsg::LoadUrl(..) |
WebDriverCommandMsg::Refresh(..) | WebDriverCommandMsg::Refresh(..) |
WebDriverCommandMsg::SendKeys(..) | WebDriverCommandMsg::SendKeys(..) |

View file

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

View file

@ -911,7 +911,6 @@ impl Handler {
// (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 current = LazyCell::new(|| { let current = LazyCell::new(|| {
let WebDriverResponse::WindowRect(current) = self let WebDriverResponse::WindowRect(current) = self
@ -929,7 +928,7 @@ impl Handler {
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),
); );
let (sender, receiver) = ipc::channel().unwrap();
// Step 16 - 17. Set the width/height in CSS pixels. // Step 16 - 17. Set the width/height in CSS pixels.
// This should be done as long as one of width/height is not null. // This should be done as long as one of width/height is not null.
@ -941,7 +940,7 @@ impl Handler {
Point2D::new(x, y), Point2D::new(x, y),
Size2D::new(width, height), Size2D::new(width, height),
), ),
sender.clone(), sender,
))?; ))?;
let window_rect = wait_for_script_response(receiver)?; let window_rect = wait_for_script_response(receiver)?;
@ -955,6 +954,38 @@ impl Handler {
Ok(WebDriverResponse::WindowRect(window_size_response)) 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> { fn handle_is_enabled(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
let (sender, receiver) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap();
@ -2483,6 +2514,7 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler {
WebDriverCommand::GetWindowHandles => self.handle_window_handles(), WebDriverCommand::GetWindowHandles => self.handle_window_handles(),
WebDriverCommand::NewWindow(ref parameters) => self.handle_new_window(parameters), WebDriverCommand::NewWindow(ref parameters) => self.handle_new_window(parameters),
WebDriverCommand::CloseWindow => self.handle_close_window(), WebDriverCommand::CloseWindow => self.handle_close_window(),
WebDriverCommand::MaximizeWindow => self.handle_maximize_window(),
WebDriverCommand::SwitchToFrame(ref parameters) => { WebDriverCommand::SwitchToFrame(ref parameters) => {
self.handle_switch_to_frame(parameters) self.handle_switch_to_frame(parameters)
}, },

View file

@ -392,6 +392,22 @@ impl App {
warn!("Failed to send response of GetWindowSize: {error}"); 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) => { 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;

View file

@ -767,10 +767,7 @@ impl WindowPortsMethods for Window {
// this prevents a crash in the compositor due to invalid surface size // this prevents a crash in the compositor due to invalid surface size
self.winit_window.set_min_inner_size(Some(PhysicalSize::new( self.winit_window.set_min_inner_size(Some(PhysicalSize::new(
MIN_INNER_WIDTH, MIN_INNER_WIDTH,
i32::max( MIN_INNER_HEIGHT.max((self.toolbar_height() * self.hidpi_scale_factor()).0 as i32),
MIN_INNER_HEIGHT,
(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, 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 { 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, DeviceIndependentIntRect, DeviceIndependentPixel, convert_rect_to_css_pixel,
}; };
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel}; 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 winit::dpi::PhysicalSize;
use super::app_state::RunningAppState; use super::app_state::RunningAppState;
@ -47,7 +47,7 @@ impl Window {
let screen_size = servoshell_preferences let screen_size = servoshell_preferences
.screen_size_override .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() (screen_size_override.to_f32() * hidpi_factor).to_i32()
}); });
@ -86,7 +86,7 @@ impl WindowPortsMethods for Window {
fn request_resize( fn request_resize(
&self, &self,
webview: &::servo::WebView, webview: &WebView,
outer_size: DeviceIntSize, outer_size: DeviceIntSize,
) -> Option<DeviceIntSize> { ) -> Option<DeviceIntSize> {
let new_size = DeviceIntSize::new( let new_size = DeviceIntSize::new(
@ -167,4 +167,17 @@ impl WindowPortsMethods for Window {
fn rendering_context(&self) -> Rc<dyn RenderingContext> { fn rendering_context(&self) -> Rc<dyn RenderingContext> {
self.rendering_context.clone() 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 servo::Theme::Light
} }
fn window_rect(&self) -> DeviceIndependentIntRect; fn window_rect(&self) -> DeviceIndependentIntRect;
fn maximize(&self, webview: &WebView);
} }

View file

@ -1,13 +1,4 @@
[maximize.py] [maximize.py]
[test_no_top_browsing_context]
expected: FAIL
[test_no_browsing_context]
expected: FAIL
[test_response_payload]
expected: FAIL
[test_fully_exit_fullscreen] [test_fully_exit_fullscreen]
expected: FAIL 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] [test_restore_from_fullscreen]
expected: FAIL expected: FAIL
[test_restore_from_maximized]
expected: FAIL
[test_set_to_available_size] [test_set_to_available_size]
expected: FAIL expected: FAIL