Files
servo/components/shared/embedder/webdriver.rs
Martin Robinson 6d4937b5d6 servoshell: Add architectural support for opening multiple windows (#40883)
This change is a major re-architecture of servoshell to support multiple
windows. Unfortunately it was not possible to do this incrementally, but
@mukilan and I did this together so we feel more confident about these
changes.

The main change here is that now the `HashMap` of windows that `App`
has can be filled with more than one `ServoShellWindow`.
`ServoShellWindow` is a wrapper around a `PlatformWindow` which can
either be headed, headless, or for embedded platforms. Embedded
platforms (Android and OHOS) are only expected to have a single window,
but there is no reason that more windows cannot be added.

There is still a little bit more work to be done in order to fully
enable
mulitple windows, so this change is just the architectural preparation.

This change enables the embedded and desktop versions of servoshell to
start to be fully integrated so that the entire `RunningAppState` is
shared between them.


Testing: servoshell is the test harness so these changes are covered
by the WPT tests.

---------

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
2025-11-27 10:41:32 +00:00

256 lines
10 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/. */
#![allow(missing_docs)]
use std::collections::HashMap;
use base::generic_channel::GenericSender;
use base::id::{BrowsingContextId, WebViewId};
use cookie::Cookie;
use crossbeam_channel::Sender;
use euclid::default::Rect as UntypedRect;
use euclid::{Rect, Size2D};
use hyper_serde::Serde;
use image::RgbaImage;
use ipc_channel::ipc::IpcSender;
use malloc_size_of_derive::MallocSizeOf;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use servo_geometry::DeviceIndependentIntRect;
use style_traits::CSSPixel;
use url::Url;
use webdriver::error::ErrorStatus;
use webrender_api::units::DevicePixel;
use crate::{InputEvent, JSValue, JavaScriptEvaluationError, ScreenshotCaptureError, TraversalId};
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum WebDriverUserPrompt {
Alert,
BeforeUnload,
Confirm,
Default,
File,
Prompt,
FallbackDefault,
}
impl WebDriverUserPrompt {
pub fn new_from_str(s: &str) -> Option<Self> {
match s {
"alert" => Some(WebDriverUserPrompt::Alert),
"beforeUnload" => Some(WebDriverUserPrompt::BeforeUnload),
"confirm" => Some(WebDriverUserPrompt::Confirm),
"default" => Some(WebDriverUserPrompt::Default),
"file" => Some(WebDriverUserPrompt::File),
"prompt" => Some(WebDriverUserPrompt::Prompt),
"fallbackDefault" => Some(WebDriverUserPrompt::FallbackDefault),
_ => None,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum WebDriverUserPromptAction {
Accept,
Dismiss,
Ignore,
}
impl WebDriverUserPromptAction {
pub fn new_from_str(s: &str) -> Option<Self> {
match s {
"accept" => Some(WebDriverUserPromptAction::Accept),
"dismiss" => Some(WebDriverUserPromptAction::Dismiss),
"ignore" => Some(WebDriverUserPromptAction::Ignore),
_ => None,
}
}
}
/// <https://html.spec.whatwg.org/multipage/#registerprotocolhandler()-automation-mode>
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
pub enum CustomHandlersAutomationMode {
AutoAccept,
AutoReject,
#[default]
None,
}
/// Messages to the constellation originating from the WebDriver server.
#[derive(Debug)]
pub enum WebDriverCommandMsg {
/// Get the window rectangle.
GetWindowRect(WebViewId, IpcSender<DeviceIndependentIntRect>),
/// Get the viewport size.
GetViewportSize(WebViewId, IpcSender<Size2D<u32, DevicePixel>>),
/// Load a URL in the top-level browsing context with the given ID.
LoadUrl(WebViewId, Url, GenericSender<WebDriverLoadStatus>),
/// Refresh the top-level browsing context with the given ID.
Refresh(WebViewId, GenericSender<WebDriverLoadStatus>),
/// Navigate the webview with the given ID to the previous page in the browsing context's history.
GoBack(WebViewId, GenericSender<WebDriverLoadStatus>),
/// Navigate the webview with the given ID to the next page in the browsing context's history.
GoForward(WebViewId, GenericSender<WebDriverLoadStatus>),
/// Pass a webdriver command to the script thread of the current pipeline
/// of a browsing context.
ScriptCommand(BrowsingContextId, WebDriverScriptCommand),
/// Dispatch an input event to the given [`WebView`]. Once the event has been handled in the
/// page DOM a single message should be sent through the [`Sender`], if provided, informing the
/// WebDriver server that the inpute event has been handled.
InputEvent(WebViewId, InputEvent, Option<Sender<()>>),
/// Set the outer window rectangle.
SetWindowRect(
WebViewId,
DeviceIndependentIntRect,
IpcSender<DeviceIndependentIntRect>,
),
/// Maximize the window. Send back result window rectangle.
MaximizeWebView(WebViewId, IpcSender<DeviceIndependentIntRect>),
/// Take a screenshot of the viewport.
TakeScreenshot(
WebViewId,
Option<Rect<f32, CSSPixel>>,
Sender<Result<RgbaImage, ScreenshotCaptureError>>,
),
/// Create a new webview that loads about:blank. The embedder will use
/// the provided channels to return the top level browsing context id
/// associated with the new webview, and sets a "load status sender" if provided.
NewWebView(
IpcSender<WebViewId>,
Option<GenericSender<WebDriverLoadStatus>>,
),
/// Close the webview associated with the provided id.
CloseWebView(WebViewId, IpcSender<()>),
/// Focus the webview associated with the provided id.
FocusWebView(WebViewId),
/// Get focused webview. For now, this is only used when start new session.
GetFocusedWebView(IpcSender<Option<WebViewId>>),
/// Get webviews state
GetAllWebViews(IpcSender<Vec<WebViewId>>),
/// Check whether top-level browsing context is open.
IsWebViewOpen(WebViewId, IpcSender<bool>),
/// Check whether browsing context is open.
IsBrowsingContextOpen(BrowsingContextId, IpcSender<bool>),
CurrentUserPrompt(WebViewId, IpcSender<Option<WebDriverUserPrompt>>),
HandleUserPrompt(
WebViewId,
WebDriverUserPromptAction,
IpcSender<Result<String, ()>>,
),
GetAlertText(WebViewId, IpcSender<Result<String, ()>>),
SendAlertText(WebViewId, String),
FocusBrowsingContext(BrowsingContextId),
Shutdown,
}
#[derive(Debug, Deserialize, Serialize)]
pub enum WebDriverScriptCommand {
AddCookie(
#[serde(
deserialize_with = "::hyper_serde::deserialize",
serialize_with = "::hyper_serde::serialize"
)]
Cookie<'static>,
IpcSender<Result<(), ErrorStatus>>,
),
DeleteCookies(IpcSender<Result<(), ErrorStatus>>),
DeleteCookie(String, IpcSender<Result<(), ErrorStatus>>),
ElementClear(String, IpcSender<Result<(), ErrorStatus>>),
ExecuteScript(String, IpcSender<WebDriverJSResult>),
ExecuteAsyncScript(String, IpcSender<WebDriverJSResult>),
FindElementsCSSSelector(String, IpcSender<Result<Vec<String>, ErrorStatus>>),
FindElementsLinkText(String, bool, IpcSender<Result<Vec<String>, ErrorStatus>>),
FindElementsTagName(String, IpcSender<Result<Vec<String>, ErrorStatus>>),
FindElementsXpathSelector(String, IpcSender<Result<Vec<String>, ErrorStatus>>),
FindElementElementsCSSSelector(String, String, IpcSender<Result<Vec<String>, ErrorStatus>>),
FindElementElementsLinkText(
String,
String,
bool,
IpcSender<Result<Vec<String>, ErrorStatus>>,
),
FindElementElementsTagName(String, String, IpcSender<Result<Vec<String>, ErrorStatus>>),
FindElementElementsXPathSelector(String, String, IpcSender<Result<Vec<String>, ErrorStatus>>),
FindShadowElementsCSSSelector(String, String, IpcSender<Result<Vec<String>, ErrorStatus>>),
FindShadowElementsLinkText(
String,
String,
bool,
IpcSender<Result<Vec<String>, ErrorStatus>>,
),
FindShadowElementsTagName(String, String, IpcSender<Result<Vec<String>, ErrorStatus>>),
FindShadowElementsXPathSelector(String, String, IpcSender<Result<Vec<String>, ErrorStatus>>),
GetElementShadowRoot(String, IpcSender<Result<Option<String>, ErrorStatus>>),
ElementClick(String, IpcSender<Result<Option<String>, ErrorStatus>>),
GetKnownElement(String, IpcSender<Result<(), ErrorStatus>>),
GetKnownShadowRoot(String, IpcSender<Result<(), ErrorStatus>>),
GetKnownWindow(String, IpcSender<Result<(), ErrorStatus>>),
GetActiveElement(IpcSender<Option<String>>),
GetComputedRole(String, IpcSender<Result<Option<String>, ErrorStatus>>),
GetCookie(
String,
IpcSender<Result<Vec<Serde<Cookie<'static>>>, ErrorStatus>>,
),
GetCookies(IpcSender<Result<Vec<Serde<Cookie<'static>>>, ErrorStatus>>),
GetElementAttribute(
String,
String,
IpcSender<Result<Option<String>, ErrorStatus>>,
),
GetElementProperty(String, String, IpcSender<Result<JSValue, ErrorStatus>>),
GetElementCSS(String, String, IpcSender<Result<String, ErrorStatus>>),
GetElementRect(String, IpcSender<Result<UntypedRect<f64>, ErrorStatus>>),
GetElementTagName(String, IpcSender<Result<String, ErrorStatus>>),
GetElementText(String, IpcSender<Result<String, ErrorStatus>>),
GetElementInViewCenterPoint(String, IpcSender<Result<Option<(i64, i64)>, ErrorStatus>>),
ScrollAndGetBoundingClientRect(String, IpcSender<Result<UntypedRect<f32>, ErrorStatus>>),
GetBrowsingContextId(
WebDriverFrameId,
IpcSender<Result<BrowsingContextId, ErrorStatus>>,
),
GetParentFrameId(IpcSender<Result<BrowsingContextId, ErrorStatus>>),
GetUrl(IpcSender<String>),
GetPageSource(IpcSender<Result<String, ErrorStatus>>),
IsEnabled(String, IpcSender<Result<bool, ErrorStatus>>),
IsSelected(String, IpcSender<Result<bool, ErrorStatus>>),
GetTitle(IpcSender<String>),
/// Deal with the case of input element for Element Send Keys, which does not send keys.
WillSendKeys(String, String, bool, IpcSender<Result<bool, ErrorStatus>>),
AddLoadStatusSender(WebViewId, GenericSender<WebDriverLoadStatus>),
RemoveLoadStatusSender(WebViewId),
SetProtocolHandlerAutomationMode(CustomHandlersAutomationMode),
}
pub type WebDriverJSResult = Result<JSValue, JavaScriptEvaluationError>;
#[derive(Debug, Deserialize, Serialize)]
pub enum WebDriverFrameId {
Short(u16),
Element(String),
}
#[derive(Debug, Deserialize, Serialize)]
pub enum WebDriverLoadStatus {
NavigationStart,
// Navigation stops for any reason
NavigationStop,
// Document ready state is complete
Complete,
// Load timeout
Timeout,
// Navigation is blocked by a user prompt
Blocked,
}
/// A collection of [`IpcSender`]s that are used to asynchronously communicate
/// to a WebDriver server with information about application state.
#[derive(Clone, Default)]
pub struct WebDriverSenders {
pub load_status_senders: FxHashMap<WebViewId, GenericSender<WebDriverLoadStatus>>,
pub script_evaluation_interrupt_sender: Option<IpcSender<WebDriverJSResult>>,
pub pending_traversals: HashMap<TraversalId, GenericSender<WebDriverLoadStatus>>,
}