From a3de3ffa7507fc6b1d72d61f7e4ea433ce0807a8 Mon Sep 17 00:00:00 2001 From: Euclid Ye Date: Sat, 26 Jul 2025 20:21:17 +0800 Subject: [PATCH] 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 --- components/constellation/constellation.rs | 1 + components/shared/embedder/webdriver.rs | 2 + components/webdriver_server/lib.rs | 38 +++++++++++-- ports/servoshell/desktop/app.rs | 16 ++++++ ports/servoshell/desktop/headed_window.rs | 9 ++-- ports/servoshell/desktop/headless_window.rs | 19 +++++-- ports/servoshell/desktop/window_trait.rs | 1 + .../classic/maximize_window/maximize.py.ini | 9 ---- .../classic/maximize_window/stress.py.ini | 15 ------ .../maximize_window/user_prompts.py.ini | 54 ------------------- .../tests/classic/set_window_rect/set.py.ini | 3 -- 11 files changed, 76 insertions(+), 91 deletions(-) delete mode 100644 tests/wpt/meta/webdriver/tests/classic/maximize_window/stress.py.ini delete mode 100644 tests/wpt/meta/webdriver/tests/classic/maximize_window/user_prompts.py.ini diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index e2ec2920d3d..a4790e19d0d 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -4422,6 +4422,7 @@ where WebDriverCommandMsg::GetWindowRect(..) | WebDriverCommandMsg::GetViewportSize(..) | WebDriverCommandMsg::SetWindowRect(..) | + WebDriverCommandMsg::MaximizeWebView(..) | WebDriverCommandMsg::LoadUrl(..) | WebDriverCommandMsg::Refresh(..) | WebDriverCommandMsg::SendKeys(..) | diff --git a/components/shared/embedder/webdriver.rs b/components/shared/embedder/webdriver.rs index 4d6aef37c29..bd952b03a7d 100644 --- a/components/shared/embedder/webdriver.rs +++ b/components/shared/embedder/webdriver.rs @@ -138,6 +138,8 @@ pub enum WebDriverCommandMsg { DeviceIndependentIntRect, IpcSender, ), + /// Maximize the window. Send back result window rectangle. + MaximizeWebView(WebViewId, IpcSender), /// Take a screenshot of the viewport. TakeScreenshot( WebViewId, diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index 2c969ce6122..cfae0f6bd3b 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -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)) } + /// + fn handle_maximize_window(&mut self) -> WebDriverResult { + // 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 { let (sender, receiver) = ipc::channel().unwrap(); @@ -2483,6 +2514,7 @@ impl WebDriverHandler 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) }, diff --git a/ports/servoshell/desktop/app.rs b/ports/servoshell/desktop/app.rs index 304969425ae..1c3df324194 100644 --- a/ports/servoshell/desktop/app.rs +++ b/ports/servoshell/desktop/app.rs @@ -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; diff --git a/ports/servoshell/desktop/headed_window.rs b/ports/servoshell/desktop/headed_window.rs index d1afa5c411d..14b8e4bd0cb 100644 --- a/ports/servoshell/desktop/headed_window.rs +++ b/ports/servoshell/desktop/headed_window.rs @@ -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 { diff --git a/ports/servoshell/desktop/headless_window.rs b/ports/servoshell/desktop/headless_window.rs index ba8e42350e9..47b20a64e5c 100644 --- a/ports/servoshell/desktop/headless_window.rs +++ b/ports/servoshell/desktop/headless_window.rs @@ -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 { let new_size = DeviceIntSize::new( @@ -167,4 +167,17 @@ impl WindowPortsMethods for Window { fn rendering_context(&self) -> Rc { 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, + )); + } } diff --git a/ports/servoshell/desktop/window_trait.rs b/ports/servoshell/desktop/window_trait.rs index ed8853e1141..2052f239439 100644 --- a/ports/servoshell/desktop/window_trait.rs +++ b/ports/servoshell/desktop/window_trait.rs @@ -65,4 +65,5 @@ pub trait WindowPortsMethods { servo::Theme::Light } fn window_rect(&self) -> DeviceIndependentIntRect; + fn maximize(&self, webview: &WebView); } diff --git a/tests/wpt/meta/webdriver/tests/classic/maximize_window/maximize.py.ini b/tests/wpt/meta/webdriver/tests/classic/maximize_window/maximize.py.ini index 83d5c2fcf84..a5b35e434c4 100644 --- a/tests/wpt/meta/webdriver/tests/classic/maximize_window/maximize.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/maximize_window/maximize.py.ini @@ -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 diff --git a/tests/wpt/meta/webdriver/tests/classic/maximize_window/stress.py.ini b/tests/wpt/meta/webdriver/tests/classic/maximize_window/stress.py.ini deleted file mode 100644 index a6c136ac885..00000000000 --- a/tests/wpt/meta/webdriver/tests/classic/maximize_window/stress.py.ini +++ /dev/null @@ -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 diff --git a/tests/wpt/meta/webdriver/tests/classic/maximize_window/user_prompts.py.ini b/tests/wpt/meta/webdriver/tests/classic/maximize_window/user_prompts.py.ini deleted file mode 100644 index a383edda800..00000000000 --- a/tests/wpt/meta/webdriver/tests/classic/maximize_window/user_prompts.py.ini +++ /dev/null @@ -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 diff --git a/tests/wpt/meta/webdriver/tests/classic/set_window_rect/set.py.ini b/tests/wpt/meta/webdriver/tests/classic/set_window_rect/set.py.ini index 9d873028e65..0e2c6199964 100644 --- a/tests/wpt/meta/webdriver/tests/classic/set_window_rect/set.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/set_window_rect/set.py.ini @@ -2,8 +2,5 @@ [test_restore_from_fullscreen] expected: FAIL - [test_restore_from_maximized] - expected: FAIL - [test_set_to_available_size] expected: FAIL