WebDriver: Wait focus to complete when switching window (#38160)

Previously the webdriver do not wait for focus to complete, which can
cause some instability.

No matter interact as human or webdriver, the focus chain always goes
as: Embedder forwards -> Constellation (do some updates) -> Embedder (do
some updates).

---------

Signed-off-by: Euclid Ye <euclid.ye@huawei.com>
This commit is contained in:
Euclid Ye 2025-07-21 12:18:21 +08:00 committed by GitHub
parent 7c96084298
commit b0a29393a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 48 additions and 23 deletions

View file

@ -1366,8 +1366,8 @@ where
} }
self.handle_panic(webview_id, error, None); self.handle_panic(webview_id, error, None);
}, },
EmbedderToConstellationMessage::FocusWebView(webview_id) => { EmbedderToConstellationMessage::FocusWebView(webview_id, response_sender) => {
self.handle_focus_web_view(webview_id); self.handle_focus_web_view(webview_id, response_sender);
}, },
EmbedderToConstellationMessage::BlurWebView => { EmbedderToConstellationMessage::BlurWebView => {
self.webviews.unfocus(); self.webviews.unfocus();
@ -2774,13 +2774,20 @@ where
} }
#[servo_tracing::instrument(skip_all)] #[servo_tracing::instrument(skip_all)]
fn handle_focus_web_view(&mut self, webview_id: WebViewId) { fn handle_focus_web_view(
&mut self,
webview_id: WebViewId,
response_sender: Option<IpcSender<bool>>,
) {
if self.webviews.get(webview_id).is_none() { if self.webviews.get(webview_id).is_none() {
if let Some(response_sender) = response_sender {
let _ = response_sender.send(false);
}
return warn!("{webview_id}: FocusWebView on unknown top-level browsing context"); return warn!("{webview_id}: FocusWebView on unknown top-level browsing context");
} }
self.webviews.focus(webview_id); self.webviews.focus(webview_id);
self.embedder_proxy self.embedder_proxy
.send(EmbedderMsg::WebViewFocused(webview_id)); .send(EmbedderMsg::WebViewFocused(webview_id, response_sender));
} }
#[servo_tracing::instrument(skip_all)] #[servo_tracing::instrument(skip_all)]
@ -4152,7 +4159,7 @@ where
// Focus the top-level browsing context. // Focus the top-level browsing context.
self.webviews.focus(webview_id); self.webviews.focus(webview_id);
self.embedder_proxy self.embedder_proxy
.send(EmbedderMsg::WebViewFocused(webview_id)); .send(EmbedderMsg::WebViewFocused(webview_id, None));
// If a container with a non-null nested browsing context is focused, // If a container with a non-null nested browsing context is focused,
// the nested browsing context's active document becomes the focused // the nested browsing context's active document becomes the focused

View file

@ -732,13 +732,16 @@ impl Servo {
webview.delegate().notify_closed(webview); webview.delegate().notify_closed(webview);
} }
}, },
EmbedderMsg::WebViewFocused(webview_id) => { EmbedderMsg::WebViewFocused(webview_id, response_sender) => {
for id in self.webviews.borrow().keys() { for id in self.webviews.borrow().keys() {
if let Some(webview) = self.get_webview_handle(*id) { if let Some(webview) = self.get_webview_handle(*id) {
let focused = webview.id() == webview_id; let focused = webview.id() == webview_id;
webview.set_focused(focused); webview.set_focused(focused);
} }
} }
if let Some(response_sender) = response_sender {
let _ = response_sender.send(true);
}
}, },
EmbedderMsg::WebViewBlurred => { EmbedderMsg::WebViewBlurred => {
for id in self.webviews.borrow().keys() { for id in self.webviews.borrow().keys() {

View file

@ -25,7 +25,7 @@ use webrender_api::units::{DeviceIntPoint, DevicePixel, DeviceRect};
use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate}; use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate};
use crate::javascript_evaluator::JavaScriptEvaluator; use crate::javascript_evaluator::JavaScriptEvaluator;
use crate::webview_delegate::{DefaultWebViewDelegate, WebViewDelegate}; use crate::webview_delegate::{DefaultWebViewDelegate, WebViewDelegate};
use crate::{ConstellationProxy, Servo, WebRenderDebugOption}; use crate::{ConstellationProxy, IpcSender, Servo, WebRenderDebugOption};
/// A handle to a Servo webview. If you clone this handle, it does not create a new webview, /// A handle to a Servo webview. If you clone this handle, it does not create a new webview,
/// but instead creates a new handle to the webview. Once the last handle is dropped, Servo /// but instead creates a new handle to the webview. Once the last handle is dropped, Servo
@ -308,7 +308,19 @@ impl WebView {
pub fn focus(&self) { pub fn focus(&self) {
self.inner() self.inner()
.constellation_proxy .constellation_proxy
.send(EmbedderToConstellationMessage::FocusWebView(self.id())); .send(EmbedderToConstellationMessage::FocusWebView(
self.id(),
None,
));
}
pub fn focus_from_webdriver(&self, response_sender: IpcSender<bool>) {
self.inner()
.constellation_proxy
.send(EmbedderToConstellationMessage::FocusWebView(
self.id(),
Some(response_sender),
));
} }
pub fn blur(&self) { pub fn blur(&self) {

View file

@ -68,8 +68,9 @@ pub enum EmbedderToConstellationMessage {
CloseWebView(WebViewId), CloseWebView(WebViewId),
/// Panic a top level browsing context. /// Panic a top level browsing context.
SendError(Option<WebViewId>, String), SendError(Option<WebViewId>, String),
/// Make a webview focused. /// Make a webview focused. If sender is provided, it will be used to send back a
FocusWebView(WebViewId), /// bool indicating whether the focus was successfully set in EmbedderMsg::WebViewFocused.
FocusWebView(WebViewId, Option<IpcSender<bool>>),
/// Make none of the webviews focused. /// Make none of the webviews focused.
BlurWebView, BlurWebView,
/// Forward an input event to an appropriate ScriptTask. /// Forward an input event to an appropriate ScriptTask.

View file

@ -373,7 +373,9 @@ pub enum EmbedderMsg {
/// A webview was destroyed. /// A webview was destroyed.
WebViewClosed(WebViewId), WebViewClosed(WebViewId),
/// A webview gained focus for keyboard events /// A webview gained focus for keyboard events
WebViewFocused(WebViewId), /// If sender is provided, it will be used to send back a
/// bool indicating whether the focus was successfully set.
WebViewFocused(WebViewId, Option<IpcSender<bool>>),
/// All webviews lost focus for keyboard events. /// All webviews lost focus for keyboard events.
WebViewBlurred, WebViewBlurred,
/// Wether or not to unload a document /// Wether or not to unload a document

View file

@ -152,7 +152,8 @@ pub enum WebDriverCommandMsg {
/// 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.
FocusWebView(WebViewId), /// Sends back a bool indicating whether the focus was successfully set.
FocusWebView(WebViewId, IpcSender<bool>),
/// Get focused webview. /// Get focused webview.
GetFocusedWebView(IpcSender<Option<WebViewId>>), GetFocusedWebView(IpcSender<Option<WebViewId>>),
/// Check whether top-level browsing context is open. /// Check whether top-level browsing context is open.

View file

@ -1266,9 +1266,14 @@ 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();
let msg = WebDriverCommandMsg::FocusWebView(webview_id); let msg = WebDriverCommandMsg::FocusWebView(webview_id, sender);
self.send_message_to_embedder(msg)?; 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(

View file

@ -377,13 +377,10 @@ impl App {
WebDriverCommandMsg::CloseWebView(webview_id) => { WebDriverCommandMsg::CloseWebView(webview_id) => {
running_state.close_webview(webview_id); running_state.close_webview(webview_id);
}, },
WebDriverCommandMsg::FocusWebView(webview_id) => { WebDriverCommandMsg::FocusWebView(webview_id, response_sender) => {
if let Some(webview) = running_state.webview_by_id(webview_id) { if let Some(webview) = running_state.webview_by_id(webview_id) {
webview.focus(); webview.focus_from_webdriver(response_sender);
} }
// TODO: send a response to the WebDriver
// so it knows when the focus has finished.
}, },
WebDriverCommandMsg::GetWindowRect(_webview_id, response_sender) => { WebDriverCommandMsg::GetWindowRect(_webview_id, response_sender) => {
let window = self let window = self

View file

@ -3,7 +3,7 @@
expected: ERROR expected: ERROR
[test_dismiss_confirm] [test_dismiss_confirm]
expected: ERROR expected: FAIL
[test_dismiss_prompt] [test_dismiss_prompt]
expected: FAIL expected: FAIL

View file

@ -1,7 +1,4 @@
[get.py] [get.py]
[test_get_alert_text]
expected: FAIL
[test_get_confirm_text] [test_get_confirm_text]
expected: FAIL expected: FAIL