webdriver: Keep constellation alive and Open new top-level browsing context with new session request when none is open (#37410)

Keep Constellation alive even when all browsing context closed in
WebDriver mode. In this case, when creating a new session, we would open
a new top-level browsing context.

Fixes: #37408
Testing: `./mach test-wpt -r
.\tests\wpt\tests\webdriver\tests\classic\close_window\close.py
--product servodriver`

---------

Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
Signed-off-by: Euclid Ye <euclid.ye@huawei.com>
This commit is contained in:
Euclid Ye 2025-07-30 14:41:58 +08:00 committed by GitHub
parent 0e18057863
commit 8b3e7b1c6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 50 additions and 41 deletions

View file

@ -146,17 +146,16 @@ pub enum WebDriverCommandMsg {
Option<Rect<f32, CSSPixel>>, Option<Rect<f32, CSSPixel>>,
IpcSender<Option<RasterImage>>, IpcSender<Option<RasterImage>>,
), ),
/// Create a new webview that loads about:blank. The constellation will use /// Create a new webview that loads about:blank. The embedder will use
/// the provided channels to return the top level browsing context id /// the provided channels to return the top level browsing context id
/// associated with the new webview, and a notification when the initial /// associated with the new webview, and sets a "load status sender" if provided.
/// load is complete. NewWebView(IpcSender<WebViewId>, Option<IpcSender<WebDriverLoadStatus>>),
NewWebView(IpcSender<WebViewId>, IpcSender<WebDriverLoadStatus>),
/// Close the webview associated with the provided id. /// Close the webview associated with the provided id.
CloseWebView(WebViewId), CloseWebView(WebViewId),
/// Focus the webview associated with the provided id. /// Focus the webview associated with the provided id.
/// Sends back a bool indicating whether the focus was successfully set. /// Sends back a bool indicating whether the focus was successfully set.
FocusWebView(WebViewId, IpcSender<bool>), FocusWebView(WebViewId, IpcSender<bool>),
/// Get focused webview. /// Get focused webview. For now, this is only used when start new session.
GetFocusedWebView(IpcSender<Option<WebViewId>>), GetFocusedWebView(IpcSender<Option<WebViewId>>),
/// Check whether top-level browsing context is open. /// Check whether top-level browsing context is open.
IsWebViewOpen(WebViewId, IpcSender<bool>), IsWebViewOpen(WebViewId, IpcSender<bool>),

View file

@ -504,17 +504,11 @@ impl Handler {
self.session().unwrap().webview_id self.session().unwrap().webview_id
} }
fn focus_webview_id(&self) -> WebDriverResult<WebViewId> { fn focused_webview_id(&self) -> WebDriverResult<Option<WebViewId>> {
let (sender, receiver) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::GetFocusedWebView(sender.clone()))?; self.send_message_to_embedder(WebDriverCommandMsg::GetFocusedWebView(sender.clone()))?;
// Wait until the document is ready before returning the top-level browsing context id. // Wait until the document is ready before returning the top-level browsing context id.
match wait_for_script_response(receiver)? { wait_for_script_response(receiver)
Some(webview_id) => Ok(webview_id),
None => Err(WebDriverError::new(
ErrorStatus::NoSuchWindow,
"No focused webview found",
)),
}
} }
fn session(&self) -> WebDriverResult<&WebDriverSession> { fn session(&self) -> WebDriverResult<&WebDriverSession> {
@ -578,8 +572,22 @@ impl Handler {
// Step 6. Create a session // Step 6. Create a session
// Step 8. Set session' current top-level browsing context // Step 8. Set session' current top-level browsing context
let webview_id = self.focus_webview_id()?; let webview_id = match self.focused_webview_id()? {
Some(webview_id) => webview_id,
None => {
// This happens when there is no open webview.
// We need to create a new one. See https://github.com/servo/servo/issues/37408
let (sender, receiver) = ipc::channel().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::NewWebView(sender, None))?;
let webview_id = receiver
.recv()
.expect("IPC failure when creating new webview for new session");
self.focus_webview(webview_id)?;
webview_id
},
};
let browsing_context_id = BrowsingContextId::from(webview_id); let browsing_context_id = BrowsingContextId::from(webview_id);
// Create and append session to the handler // Create and append session to the handler
let session_id = self.create_session( let session_id = self.create_session(
&mut capabilities, &mut capabilities,
@ -1085,7 +1093,8 @@ impl Handler {
// Step 3. Handle any user prompt. // Step 3. Handle any user prompt.
self.handle_any_user_prompts(session.webview_id)?; self.handle_any_user_prompts(session.webview_id)?;
let cmd_msg = WebDriverCommandMsg::NewWebView(sender, self.load_status_sender.clone()); let cmd_msg =
WebDriverCommandMsg::NewWebView(sender, Some(self.load_status_sender.clone()));
// Step 5. Create a new top-level browsing context by running the window open steps. // Step 5. Create a new top-level browsing context by running the window open steps.
// This MUST be done without invoking the focusing steps. // This MUST be done without invoking the focusing steps.
self.send_message_to_embedder(cmd_msg)?; self.send_message_to_embedder(cmd_msg)?;
@ -1189,14 +1198,7 @@ impl Handler {
let webview_id = *webview_id; let webview_id = *webview_id;
session.webview_id = webview_id; session.webview_id = webview_id;
session.browsing_context_id = BrowsingContextId::from(webview_id); session.browsing_context_id = BrowsingContextId::from(webview_id);
let (sender, receiver) = ipc::channel().unwrap(); self.focus_webview(webview_id)?;
let msg = WebDriverCommandMsg::FocusWebView(webview_id, sender);
self.send_message_to_embedder(msg)?;
if wait_for_script_response(receiver)? {
debug!("Focus new webview successfully");
} else {
debug!("Focus new webview failed, it may not exist anymore");
}
Ok(WebDriverResponse::Void) Ok(WebDriverResponse::Void)
} else { } else {
Err(WebDriverError::new( Err(WebDriverError::new(
@ -2387,6 +2389,17 @@ impl Handler {
Ok(()) Ok(())
} }
} }
fn focus_webview(&self, webview_id: WebViewId) -> Result<(), WebDriverError> {
let (sender, receiver) = ipc::channel().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::FocusWebView(webview_id, sender))?;
if wait_for_script_response(receiver)? {
debug!("Focus new webview successfully");
} else {
debug!("Focus new webview failed, it may not exist anymore");
}
Ok(())
}
} }
impl WebDriverHandler<ServoExtensionRoute> for Handler { impl WebDriverHandler<ServoExtensionRoute> for Handler {

View file

@ -369,8 +369,9 @@ impl App {
if let Err(error) = response_sender.send(new_webview.id()) { if let Err(error) = response_sender.send(new_webview.id()) {
warn!("Failed to send response of NewWebview: {error}"); warn!("Failed to send response of NewWebview: {error}");
} }
if let Some(load_status_sender) = load_status_sender {
running_state.set_load_status_sender(new_webview.id(), load_status_sender); running_state.set_load_status_sender(new_webview.id(), load_status_sender);
}
}, },
WebDriverCommandMsg::CloseWebView(webview_id) => { WebDriverCommandMsg::CloseWebView(webview_id) => {
running_state.close_webview(webview_id); running_state.close_webview(webview_id);
@ -446,8 +447,10 @@ impl App {
warn!("Failed to send response of GetViewportSize: {error}"); warn!("Failed to send response of GetViewportSize: {error}");
} }
}, },
// This is only received when start new session.
WebDriverCommandMsg::GetFocusedWebView(sender) => { WebDriverCommandMsg::GetFocusedWebView(sender) => {
let focused_webview = running_state.focused_webview(); let focused_webview = running_state.focused_webview();
if let Err(error) = sender.send(focused_webview.map(|w| w.id())) { if let Err(error) = sender.send(focused_webview.map(|w| w.id())) {
warn!("Failed to send response of GetFocusedWebView: {error}"); warn!("Failed to send response of GetFocusedWebView: {error}");
}; };

View file

@ -272,7 +272,13 @@ impl RunningAppState {
Some(last_created_webview) => { Some(last_created_webview) => {
last_created_webview.focus(); last_created_webview.focus();
}, },
None => self.servo.start_shutting_down(), None if self.servoshell_preferences.webdriver_port.is_none() => {
self.servo.start_shutting_down()
},
None => {
// For WebDriver, don't shut down when last webview closed
// https://github.com/servo/servo/issues/37408
},
} }
} }

View file

@ -12,7 +12,7 @@
expected: FAIL expected: FAIL
[test_accept_prompt] [test_accept_prompt]
expected: ERROR expected: FAIL
[test_accept_in_popup_window] [test_accept_in_popup_window]
expected: FAIL expected: FAIL

View file

@ -1,6 +1,3 @@
[close.py] [close.py]
[test_close_last_browsing_context] [test_close_last_browsing_context]
expected: FAIL expected: FAIL
[test_element_usage_after_closing_browsing_context]
expected: ERROR

View file

@ -2,12 +2,6 @@
[test_no_top_browsing_context] [test_no_top_browsing_context]
expected: ERROR expected: ERROR
[test_no_browsing_context]
expected: ERROR
[test_dismiss_alert]
expected: FAIL
[test_dismiss_confirm] [test_dismiss_confirm]
expected: FAIL expected: FAIL

View file

@ -8,11 +8,8 @@
[test_single_file_appends_with_multiple_attribute] [test_single_file_appends_with_multiple_attribute]
expected: FAIL expected: FAIL
[test_focused]
expected: ERROR
[test_strict_hidden] [test_strict_hidden]
expected: ERROR expected: FAIL
[test_strict_display_none] [test_strict_display_none]
expected: ERROR expected: FAIL