webdriver: Implement support for simple dialogs (#37913)

Implement webdriver user prompt: accept alert, dismiss alert, get alert
text.

Tests:
https://github.com/longvatrong111/servo/actions/runs/16175408035
https://github.com/longvatrong111/servo/actions/runs/16175409545

Signed-off-by: batu_hoang <longvatrong111@gmail.com>
This commit is contained in:
batu_hoang 2025-07-10 11:15:46 +08:00 committed by GitHub
parent 84f0cd5801
commit 2e44aba753
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 296 additions and 42 deletions

View file

@ -160,6 +160,59 @@ pub enum SimpleDialog {
},
}
impl SimpleDialog {
/// Returns the message of the dialog.
pub fn message(&self) -> &str {
match self {
SimpleDialog::Alert { message, .. } => message,
SimpleDialog::Confirm { message, .. } => message,
SimpleDialog::Prompt { message, .. } => message,
}
}
pub fn dismiss(&self) {
match self {
SimpleDialog::Alert {
response_sender, ..
} => {
let _ = response_sender.send(AlertResponse::Ok);
},
SimpleDialog::Confirm {
response_sender, ..
} => {
let _ = response_sender.send(ConfirmResponse::Cancel);
},
SimpleDialog::Prompt {
response_sender, ..
} => {
let _ = response_sender.send(PromptResponse::Cancel);
},
}
}
pub fn accept(&self) {
match self {
SimpleDialog::Alert {
response_sender, ..
} => {
let _ = response_sender.send(AlertResponse::Ok);
},
SimpleDialog::Confirm {
response_sender, ..
} => {
let _ = response_sender.send(ConfirmResponse::Ok);
},
SimpleDialog::Prompt {
default,
response_sender,
..
} => {
let _ = response_sender.send(PromptResponse::Ok(default.clone()));
},
}
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct AuthenticationResponse {
/// Username for http request authentication

View file

@ -28,6 +28,12 @@ use crate::{MouseButton, MouseButtonAction};
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct WebDriverMessageId(pub usize);
#[derive(Debug, Deserialize, Serialize)]
pub enum WebDriverUserPromptAction {
Accept,
Dismiss,
}
/// Messages to the constellation originating from the WebDriver server.
#[derive(Debug, Deserialize, Serialize)]
pub enum WebDriverCommandMsg {
@ -115,6 +121,12 @@ pub enum WebDriverCommandMsg {
IsWebViewOpen(WebViewId, IpcSender<bool>),
/// Check whether browsing context is open.
IsBrowsingContextOpen(BrowsingContextId, IpcSender<bool>),
HandleUserPrompt(
WebViewId,
WebDriverUserPromptAction,
IpcSender<Result<(), ()>>,
),
GetAlertText(WebViewId, IpcSender<Result<String, ()>>),
}
#[derive(Debug, Deserialize, Serialize)]
@ -237,4 +249,5 @@ pub enum WebDriverLoadStatus {
Complete,
Timeout,
Canceled,
Blocked,
}

View file

@ -8,6 +8,7 @@
mod actions;
mod capabilities;
mod user_prompt;
use std::borrow::ToOwned;
use std::cell::{Cell, LazyCell, RefCell};
@ -515,6 +516,11 @@ impl Handler {
Ok(())
}
// This function is called only if session and webview are verified.
fn verified_webview_id(&self) -> WebViewId {
self.session().unwrap().webview_id
}
fn focus_webview_id(&self) -> WebDriverResult<WebViewId> {
let (sender, receiver) = ipc::channel().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::GetFocusedWebView(sender.clone()))?;
@ -782,7 +788,19 @@ impl Handler {
debug!("waiting for load");
let timeout = self.session()?.load_timeout;
let result = select! {
recv(self.load_status_receiver) -> _ => Ok(WebDriverResponse::Void),
recv(self.load_status_receiver) -> res => {
match res {
Ok(WebDriverLoadStatus::Blocked) => {
Err(WebDriverError::new(
ErrorStatus::UnexpectedAlertOpen,
"Load is blocked",
))
}
_ => {
Ok(WebDriverResponse::Void)
}
}
},
recv(after(Duration::from_millis(timeout))) -> _ => Err(
WebDriverError::new(ErrorStatus::Timeout, "Load timed out")
),
@ -1605,15 +1623,6 @@ impl Handler {
}
}
// https://w3c.github.io/webdriver/#dismiss-alert
fn handle_dismiss_alert(&mut self) -> WebDriverResult<WebDriverResponse> {
// Step 1. 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(self.session()?.webview_id)?;
// Since user prompts are not yet implement this will always succeed
Ok(WebDriverResponse::Void)
}
fn handle_get_timeouts(&mut self) -> WebDriverResult<WebDriverResponse> {
let session = self
.session
@ -2231,6 +2240,8 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler {
},
WebDriverCommand::ElementClick(ref element) => self.handle_element_click(element),
WebDriverCommand::DismissAlert => self.handle_dismiss_alert(),
WebDriverCommand::AcceptAlert => self.handle_accept_alert(),
WebDriverCommand::GetAlertText => self.handle_get_alert_text(),
WebDriverCommand::DeleteCookies => self.handle_delete_cookies(),
WebDriverCommand::DeleteCookie(name) => self.handle_delete_cookie(name),
WebDriverCommand::GetTimeouts => self.handle_get_timeouts(),

View file

@ -0,0 +1,93 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use embedder_traits::{WebDriverCommandMsg, WebDriverUserPromptAction};
use ipc_channel::ipc;
use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
use webdriver::response::{ValueResponse, WebDriverResponse};
use crate::{Handler, wait_for_script_response};
impl Handler {
/// <https://w3c.github.io/webdriver/#dismiss-alert>
pub(crate) fn handle_dismiss_alert(&mut self) -> WebDriverResult<WebDriverResponse> {
// Step 1. 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(self.session()?.webview_id)?;
// Step 3. Dismiss the current user prompt.
let (sender, receiver) = ipc::channel().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::HandleUserPrompt(
self.verified_webview_id(),
WebDriverUserPromptAction::Dismiss,
sender,
))?;
match wait_for_script_response(receiver)? {
// Step 2. If the current user prompt is null, return error with error code no such alert.
Err(()) => Err(WebDriverError::new(
ErrorStatus::NoSuchAlert,
"No user prompt is currently active.",
)),
// Step 4. Return success with data null.
Ok(()) => Ok(WebDriverResponse::Void),
}
}
/// <https://w3c.github.io/webdriver/#accept-alert>
pub(crate) fn handle_accept_alert(&mut self) -> WebDriverResult<WebDriverResponse> {
// Step 1. 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(self.session()?.webview_id)?;
// Step 3. Accept the current user prompt.
let (sender, receiver) = ipc::channel().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::HandleUserPrompt(
self.verified_webview_id(),
WebDriverUserPromptAction::Accept,
sender,
))?;
match wait_for_script_response(receiver)? {
// Step 2. If the current user prompt is null, return error with error code no such alert.
Err(()) => Err(WebDriverError::new(
ErrorStatus::NoSuchAlert,
"No user prompt is currently active.",
)),
// Step 4. Return success with data null.
Ok(()) => Ok(WebDriverResponse::Void),
}
}
pub(crate) fn handle_get_alert_text(&mut self) -> WebDriverResult<WebDriverResponse> {
// Step 1. 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(self.session()?.webview_id)?;
let (sender, receiver) = ipc::channel().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::GetAlertText(
self.verified_webview_id(),
sender,
))?;
match wait_for_script_response(receiver)? {
// Step 2. If the current user prompt is null, return error with error code no such alert.
Err(()) => Err(WebDriverError::new(
ErrorStatus::NoSuchAlert,
"No user prompt is currently active.",
)),
// Step 3. Let message be the text message associated with the current user prompt
// or otherwise be null
// Step 4. Return success with data message.
Ok(message) => Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(message).map_err(|e| {
WebDriverError::new(
ErrorStatus::UnknownError,
format!("Failed to serialize alert text: {}", e),
)
})?,
))),
}
}
}