mirror of
https://github.com/servo/servo
synced 2026-04-26 01:25:32 +02:00
This implements a GenericOneshot channel. The size in the crossbeam channel is restricted while in the Ipc case we use the same IpcChannel(for now). We also use these channels in WebDriver to prove they work. Testing: Unit Tests and compilation still works. Fixes: https://github.com/servo/servo/issues/39019 Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
352 lines
14 KiB
Rust
352 lines
14 KiB
Rust
/* 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 std::borrow::Cow;
|
|
use std::collections::{BTreeMap, HashMap};
|
|
|
|
use base::generic_channel;
|
|
use base::id::WebViewId;
|
|
use embedder_traits::{WebDriverCommandMsg, WebDriverUserPrompt, WebDriverUserPromptAction};
|
|
use serde_json::{Map, Value};
|
|
use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
|
|
use webdriver::response::{ValueResponse, WebDriverResponse};
|
|
|
|
use crate::{Handler, wait_for_oneshot_response};
|
|
|
|
const KNOWN_PROMPT_HANDLERS: [&str; 5] = [
|
|
"dismiss",
|
|
"accept",
|
|
"dismiss and notify",
|
|
"accept and notify",
|
|
"ignore",
|
|
];
|
|
|
|
const VALID_PROMPT_TYPES: [&str; 6] = [
|
|
"alert",
|
|
"beforeUnload",
|
|
"confirm",
|
|
"default",
|
|
"file",
|
|
"prompt",
|
|
];
|
|
|
|
/// <https://w3c.github.io/webdriver/#dfn-prompt-handler-configuration>
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct PromptHandlerConfiguration {
|
|
handler: WebDriverUserPromptAction,
|
|
notify: bool,
|
|
}
|
|
|
|
pub(crate) type UserPromptHandler = HashMap<WebDriverUserPrompt, PromptHandlerConfiguration>;
|
|
|
|
/// <https://w3c.github.io/webdriver/#dfn-deserialize-as-an-unhandled-prompt-behavior>
|
|
pub(crate) fn deserialize_unhandled_prompt_behaviour(
|
|
value_param: Value,
|
|
) -> Result<UserPromptHandler, WebDriverError> {
|
|
// Step 2-5.
|
|
let (value, is_string_value) = match value_param {
|
|
Value::Object(map) => (map, false),
|
|
Value::String(..) => {
|
|
let mut map = Map::new();
|
|
map.insert("fallbackDefault".to_string(), value_param);
|
|
(map, true)
|
|
},
|
|
_ => {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
"Expected an object or a string for unhandled prompt behavior.",
|
|
));
|
|
},
|
|
};
|
|
|
|
// Step 6. Let user prompt handler be a new empty map.
|
|
let mut user_prompt_handler = UserPromptHandler::new();
|
|
|
|
// Step 7. For each key-value pair in value:
|
|
for (prompt_type, handler) in value {
|
|
// Step 7.1. If `is_string_value` is false and prompt type is not one of
|
|
// the valid prompt types, return error with error code invalid argument.
|
|
if !is_string_value && !VALID_PROMPT_TYPES.contains(&prompt_type.as_str()) {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
format!("Invalid prompt type: {}", prompt_type),
|
|
));
|
|
}
|
|
|
|
// Step 7.2. If known prompt handlers does not contain an entry with
|
|
// handler key `handler` return error with error code invalid argument.
|
|
let handle_str = match handler {
|
|
Value::String(s) => s,
|
|
_ => {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
format!("Expected a string for handler, got: {:?}", handler),
|
|
));
|
|
},
|
|
};
|
|
if !KNOWN_PROMPT_HANDLERS.contains(&handle_str.as_str()) {
|
|
return Err(WebDriverError::new(
|
|
ErrorStatus::InvalidArgument,
|
|
format!("Unknown prompt handler: {}", handle_str),
|
|
));
|
|
}
|
|
|
|
// Step 7.3 - 7.6.
|
|
let (handler, notify) = match handle_str.as_str() {
|
|
"accept and notify" => (
|
|
WebDriverUserPromptAction::new_from_str("accept").unwrap(),
|
|
true,
|
|
),
|
|
"dismiss and notify" => (
|
|
WebDriverUserPromptAction::new_from_str("dismiss").unwrap(),
|
|
true,
|
|
),
|
|
"ignore" => (
|
|
WebDriverUserPromptAction::new_from_str("ignore").unwrap(),
|
|
false,
|
|
),
|
|
"accept" => (
|
|
WebDriverUserPromptAction::new_from_str("accept").unwrap(),
|
|
false,
|
|
),
|
|
"dismiss" => (
|
|
WebDriverUserPromptAction::new_from_str("dismiss").unwrap(),
|
|
false,
|
|
),
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
// Step 7.7 - 7.8.
|
|
user_prompt_handler.insert(
|
|
WebDriverUserPrompt::new_from_str(&prompt_type).unwrap(),
|
|
PromptHandlerConfiguration { handler, notify },
|
|
);
|
|
}
|
|
|
|
Ok(user_prompt_handler)
|
|
}
|
|
|
|
pub(crate) fn default_unhandled_prompt_behavior() -> &'static str {
|
|
"dismiss and notify"
|
|
}
|
|
|
|
/// <https://www.w3.org/TR/webdriver2/#dfn-get-the-prompt-handler>
|
|
fn get_user_prompt_handler(
|
|
user_prompt_handler: &UserPromptHandler,
|
|
prompt_type: WebDriverUserPrompt,
|
|
) -> PromptHandlerConfiguration {
|
|
// Step 2. If handlers contains type return handlers[type].
|
|
if let Some(handler) = user_prompt_handler.get(&prompt_type) {
|
|
return (*handler).clone();
|
|
}
|
|
|
|
// Step 3. If handlers contains default return handlers[default].
|
|
if let Some(handler) = user_prompt_handler.get(&WebDriverUserPrompt::Default) {
|
|
return (*handler).clone();
|
|
}
|
|
|
|
// Step 4. If prompt type is "beforeUnload" return a configuration with handler "accept" and notify false.
|
|
if prompt_type == WebDriverUserPrompt::BeforeUnload {
|
|
return PromptHandlerConfiguration {
|
|
handler: WebDriverUserPromptAction::Accept,
|
|
notify: false,
|
|
};
|
|
}
|
|
|
|
// Step 5. If handlers contains fallbackDefault return handlers[fallbackDefault].
|
|
if let Some(handler) = user_prompt_handler.get(&WebDriverUserPrompt::FallbackDefault) {
|
|
return (*handler).clone();
|
|
}
|
|
|
|
// Step 6. Return a configuration with handler "dismiss" and notify true.
|
|
PromptHandlerConfiguration {
|
|
handler: WebDriverUserPromptAction::Dismiss,
|
|
notify: true,
|
|
}
|
|
}
|
|
|
|
fn webdriver_response_single_data(
|
|
key: &'static str,
|
|
value: Value,
|
|
) -> Option<BTreeMap<Cow<'static, str>, Value>> {
|
|
Some([(Cow::Borrowed(key), value)].into_iter().collect())
|
|
}
|
|
|
|
impl Handler {
|
|
/// <https://w3c.github.io/webdriver/#dismiss-alert>
|
|
pub(crate) fn handle_dismiss_alert(&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.webview_id()?)?;
|
|
|
|
// Step 3. Dismiss the current user prompt.
|
|
let (sender, receiver) = generic_channel::oneshot().unwrap();
|
|
self.send_message_to_embedder(WebDriverCommandMsg::HandleUserPrompt(
|
|
self.verified_webview_id(),
|
|
WebDriverUserPromptAction::Dismiss,
|
|
sender,
|
|
))?;
|
|
|
|
match wait_for_oneshot_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(&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.webview_id()?)?;
|
|
|
|
// Step 3. Accept the current user prompt.
|
|
let (sender, receiver) = generic_channel::oneshot().unwrap();
|
|
self.send_message_to_embedder(WebDriverCommandMsg::HandleUserPrompt(
|
|
self.verified_webview_id(),
|
|
WebDriverUserPromptAction::Accept,
|
|
sender,
|
|
))?;
|
|
|
|
match wait_for_oneshot_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://www.w3.org/TR/webdriver2/#dfn-get-alert-text>
|
|
pub(crate) fn handle_get_alert_text(&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.webview_id()?)?;
|
|
|
|
let (sender, receiver) = generic_channel::oneshot().unwrap();
|
|
self.send_message_to_embedder(WebDriverCommandMsg::GetAlertText(
|
|
self.verified_webview_id(),
|
|
sender,
|
|
))?;
|
|
|
|
match wait_for_oneshot_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),
|
|
)
|
|
})?,
|
|
))),
|
|
}
|
|
}
|
|
|
|
/// <https://www.w3.org/TR/webdriver2/#dfn-send-alert-text>
|
|
pub(crate) fn handle_send_alert_text(
|
|
&self,
|
|
text: String,
|
|
) -> WebDriverResult<WebDriverResponse> {
|
|
let webview_id = self.webview_id()?;
|
|
|
|
// Step 3. 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)?;
|
|
|
|
let (sender, receiver) = generic_channel::oneshot().unwrap();
|
|
|
|
self.send_message_to_embedder(WebDriverCommandMsg::CurrentUserPrompt(webview_id, sender))?;
|
|
|
|
match wait_for_oneshot_response(receiver)? {
|
|
// Step 4. If the current user prompt is null, return error with error code no such alert.
|
|
None => Err(WebDriverError::new(
|
|
ErrorStatus::NoSuchAlert,
|
|
"No user prompt is currently active.",
|
|
)),
|
|
Some(prompt_type) => {
|
|
match prompt_type {
|
|
// Step 5. If the current user prompt is alert or confirm,
|
|
// return error with error code element not interactable.
|
|
WebDriverUserPrompt::Alert | WebDriverUserPrompt::Confirm => {
|
|
Err(WebDriverError::new(
|
|
ErrorStatus::ElementNotInteractable,
|
|
"Cannot send text to an alert or confirm prompt.",
|
|
))
|
|
},
|
|
// Step 5. If the current user prompt is prompt
|
|
WebDriverUserPrompt::Prompt => {
|
|
// Step 6. Send the text to the current user prompt.
|
|
self.send_message_to_embedder(WebDriverCommandMsg::SendAlertText(
|
|
webview_id, text,
|
|
))?;
|
|
|
|
Ok(WebDriverResponse::Void)
|
|
},
|
|
// Step 5. Otherwise, return error with error code unsupported operation.
|
|
_ => Err(WebDriverError::new(
|
|
ErrorStatus::UnsupportedOperation,
|
|
"Current user prompt type is not supported.",
|
|
)),
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
/// <https://w3c.github.io/webdriver/#dfn-handle-any-user-prompts>
|
|
pub(crate) fn handle_any_user_prompts(
|
|
&self,
|
|
webview_id: WebViewId,
|
|
) -> WebDriverResult<WebDriverResponse> {
|
|
let (sender, receiver) = generic_channel::oneshot().unwrap();
|
|
|
|
self.send_message_to_embedder(WebDriverCommandMsg::CurrentUserPrompt(webview_id, sender))?;
|
|
|
|
match wait_for_oneshot_response(receiver)? {
|
|
// Step 1. If the current user prompt is null, return success with data null.
|
|
None => Ok(WebDriverResponse::Void),
|
|
Some(prompt_type) => {
|
|
// Step 2 - 4. Get user prompt handler for the prompt type.
|
|
let handler =
|
|
get_user_prompt_handler(self.session()?.user_prompt_handler(), prompt_type);
|
|
|
|
// Step 5. Perform the substeps based on handler's handler
|
|
let (sender, receiver) = generic_channel::oneshot().unwrap();
|
|
self.send_message_to_embedder(WebDriverCommandMsg::HandleUserPrompt(
|
|
webview_id,
|
|
handler.handler.clone(),
|
|
sender,
|
|
))?;
|
|
|
|
if handler.notify || handler.handler == WebDriverUserPromptAction::Ignore {
|
|
// Step 6. If handler's notify is true, return annotated unexpected alert open error.
|
|
let alert_text = wait_for_oneshot_response(receiver)?.unwrap_or_default();
|
|
|
|
Err(WebDriverError::new_with_data(
|
|
ErrorStatus::UnexpectedAlertOpen,
|
|
"Handle any user prompt: Unexpected alert open.",
|
|
webdriver_response_single_data("text", Value::String(alert_text)),
|
|
None,
|
|
))
|
|
} else {
|
|
// Step 7. Return success with data null.
|
|
Ok(WebDriverResponse::Void)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|