webdriver: Evaluate script commands via the WebView API in servoshell (#37663)

Let `WebDriverCommandMsg::ScriptCommand` goes through embedder first.
Give `embedder` the ability to release `webdriver` from waiting for a
response of `ExecuteScript`.

Tests: https://github.com/longvatrong111/servo/actions/runs/16071375821
No regression compared to CI run on main branch.

Fixes: https://github.com/servo/servo/issues/37370

cc: @xiaochengh

---------

Signed-off-by: batu_hoang <longvatrong111@gmail.com>
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
batu_hoang 2025-07-09 22:05:39 +08:00 committed by GitHub
parent 562d9e4a21
commit 4499fdeb2b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 75 additions and 150 deletions

View file

@ -19,7 +19,8 @@ use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize};
use servo::{
AllowOrDenyRequest, AuthenticationRequest, FilterPattern, FormControl, GamepadHapticEffectType,
KeyboardEvent, LoadStatus, PermissionRequest, Servo, ServoDelegate, ServoError, SimpleDialog,
WebDriverCommandMsg, WebDriverLoadStatus, WebView, WebViewBuilder, WebViewDelegate,
WebDriverCommandMsg, WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, WebView,
WebViewBuilder, WebViewDelegate,
};
use url::Url;
@ -42,6 +43,7 @@ pub(crate) enum AppState {
#[derive(Clone, Default)]
struct WebDriverSenders {
pub load_status_senders: HashMap<WebViewId, IpcSender<WebDriverLoadStatus>>,
pub script_evaluation_interrupt_sender: Option<IpcSender<WebDriverJSResult>>,
}
pub(crate) struct RunningAppState {
@ -406,6 +408,36 @@ impl RunningAppState {
.load_status_senders
.insert(webview_id, sender);
}
pub(crate) fn set_script_command_interrupt_sender(
&self,
sender: Option<IpcSender<WebDriverJSResult>>,
) {
self.webdriver_senders
.borrow_mut()
.script_evaluation_interrupt_sender = sender;
}
/// Interrupt any ongoing WebDriver-based script evaluation.
///
/// From <https://w3c.github.io/webdriver/#dfn-execute-a-function-body>:
/// > The rules to execute a function body are as follows. The algorithm returns
/// > an ECMAScript completion record.
/// >
/// > If at any point during the algorithm a user prompt appears, immediately return
/// > Completion { Type: normal, Value: null, Target: empty }, but continue to run the
/// > other steps of this algorithm in parallel.
fn interrupt_webdriver_script_evaluation(&self) {
if let Some(sender) = &self
.webdriver_senders
.borrow()
.script_evaluation_interrupt_sender
{
sender.send(Ok(WebDriverJSValue::Null)).unwrap_or_else(|err| {
info!("Notify dialog appear failed. Maybe the channel to webdriver is closed: {err}");
});
}
}
}
struct ServoShellServoDelegate;
@ -452,6 +484,8 @@ impl WebViewDelegate for RunningAppState {
}
fn show_simple_dialog(&self, webview: servo::WebView, dialog: SimpleDialog) {
self.interrupt_webdriver_script_evaluation();
if self.servoshell_preferences.headless &&
self.servoshell_preferences.webdriver_port.is_none()
{