webdriver: Port WebDriverLoadStatus to Generic Channel (#38915)

Ports the channel for WebDriverLoadStatus to GenericChannel.

Testing: No functional changes - Covered by existing webdriver tests
Part of #38912

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
Jonathan Schwender 2025-08-25 20:11:03 +02:00 committed by GitHub
parent 4a19f66c31
commit ebf8a35c84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 37 additions and 26 deletions

View file

@ -418,7 +418,7 @@ pub struct Constellation<STF, SWF> {
next_pipeline_namespace_id: PipelineNamespaceId, next_pipeline_namespace_id: PipelineNamespaceId,
/// An [`IpcSender`] to notify navigation events to webdriver. /// An [`IpcSender`] to notify navigation events to webdriver.
webdriver_load_status_sender: Option<(IpcSender<WebDriverLoadStatus>, PipelineId)>, webdriver_load_status_sender: Option<(GenericSender<WebDriverLoadStatus>, PipelineId)>,
/// An [`IpcSender`] to forward responses from the `ScriptThread` to the WebDriver server. /// An [`IpcSender`] to forward responses from the `ScriptThread` to the WebDriver server.
webdriver_input_command_reponse_sender: Option<IpcSender<WebDriverCommandResponse>>, webdriver_input_command_reponse_sender: Option<IpcSender<WebDriverCommandResponse>>,

View file

@ -326,7 +326,7 @@ pub(crate) struct Window {
/// A channel to notify webdriver if there is a navigation /// A channel to notify webdriver if there is a navigation
#[no_trace] #[no_trace]
webdriver_load_status_sender: RefCell<Option<IpcSender<WebDriverLoadStatus>>>, webdriver_load_status_sender: RefCell<Option<GenericSender<WebDriverLoadStatus>>>,
/// The current state of the window object /// The current state of the window object
current_state: Cell<WindowState>, current_state: Cell<WindowState>,
@ -2863,7 +2863,7 @@ impl Window {
pub(crate) fn set_webdriver_load_status_sender( pub(crate) fn set_webdriver_load_status_sender(
&self, &self,
sender: Option<IpcSender<WebDriverLoadStatus>>, sender: Option<GenericSender<WebDriverLoadStatus>>,
) { ) {
*self.webdriver_load_status_sender.borrow_mut() = sender; *self.webdriver_load_status_sender.borrow_mut() = sender;
} }

View file

@ -6,6 +6,7 @@ use std::collections::{HashMap, HashSet};
use std::ffi::CString; use std::ffi::CString;
use std::ptr::NonNull; use std::ptr::NonNull;
use base::generic_channel::GenericSender;
use base::id::{BrowsingContextId, PipelineId}; use base::id::{BrowsingContextId, PipelineId};
use cookie::Cookie; use cookie::Cookie;
use embedder_traits::{ use embedder_traits::{
@ -2066,7 +2067,7 @@ pub(crate) fn handle_is_selected(
pub(crate) fn handle_add_load_status_sender( pub(crate) fn handle_add_load_status_sender(
documents: &DocumentCollection, documents: &DocumentCollection,
pipeline: PipelineId, pipeline: PipelineId,
reply: IpcSender<WebDriverLoadStatus>, reply: GenericSender<WebDriverLoadStatus>,
) { ) {
if let Some(document) = documents.find_document(pipeline) { if let Some(document) = documents.find_document(pipeline) {
let window = document.window(); let window = document.window();

View file

@ -6,6 +6,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use base::generic_channel::GenericSender;
use base::id::{BrowsingContextId, WebViewId}; use base::id::{BrowsingContextId, WebViewId};
use cookie::Cookie; use cookie::Cookie;
use euclid::default::Rect as UntypedRect; use euclid::default::Rect as UntypedRect;
@ -81,13 +82,13 @@ pub enum WebDriverCommandMsg {
/// Get the viewport size. /// Get the viewport size.
GetViewportSize(WebViewId, IpcSender<Size2D<u32, DevicePixel>>), GetViewportSize(WebViewId, IpcSender<Size2D<u32, DevicePixel>>),
/// Load a URL in the top-level browsing context with the given ID. /// Load a URL in the top-level browsing context with the given ID.
LoadUrl(WebViewId, ServoUrl, IpcSender<WebDriverLoadStatus>), LoadUrl(WebViewId, ServoUrl, GenericSender<WebDriverLoadStatus>),
/// Refresh the top-level browsing context with the given ID. /// Refresh the top-level browsing context with the given ID.
Refresh(WebViewId, IpcSender<WebDriverLoadStatus>), Refresh(WebViewId, GenericSender<WebDriverLoadStatus>),
/// Navigate the webview with the given ID to the previous page in the browsing context's history. /// Navigate the webview with the given ID to the previous page in the browsing context's history.
GoBack(WebViewId, IpcSender<WebDriverLoadStatus>), GoBack(WebViewId, GenericSender<WebDriverLoadStatus>),
/// Navigate the webview with the given ID to the next page in the browsing context's history. /// Navigate the webview with the given ID to the next page in the browsing context's history.
GoForward(WebViewId, IpcSender<WebDriverLoadStatus>), GoForward(WebViewId, GenericSender<WebDriverLoadStatus>),
/// Pass a webdriver command to the script thread of the current pipeline /// Pass a webdriver command to the script thread of the current pipeline
/// of a browsing context. /// of a browsing context.
ScriptCommand(BrowsingContextId, WebDriverScriptCommand), ScriptCommand(BrowsingContextId, WebDriverScriptCommand),
@ -147,7 +148,10 @@ pub enum WebDriverCommandMsg {
/// Create a new webview that loads about:blank. The embedder will use /// Create a new webview that loads about:blank. The embedder will use
/// the provided channels to return the top level browsing context id /// the provided channels to return the top level browsing context id
/// associated with the new webview, and sets a "load status sender" if provided. /// associated with the new webview, and sets a "load status sender" if provided.
NewWebView(IpcSender<WebViewId>, Option<IpcSender<WebDriverLoadStatus>>), NewWebView(
IpcSender<WebViewId>,
Option<GenericSender<WebDriverLoadStatus>>,
),
/// Close the webview associated with the provided id. /// Close the webview associated with the provided id.
CloseWebView(WebViewId, IpcSender<()>), CloseWebView(WebViewId, IpcSender<()>),
/// Focus the webview associated with the provided id. /// Focus the webview associated with the provided id.
@ -243,7 +247,7 @@ pub enum WebDriverScriptCommand {
GetTitle(IpcSender<String>), GetTitle(IpcSender<String>),
/// Deal with the case of input element for Element Send Keys, which does not send keys. /// Deal with the case of input element for Element Send Keys, which does not send keys.
WillSendKeys(String, String, bool, IpcSender<Result<bool, ErrorStatus>>), WillSendKeys(String, String, bool, IpcSender<Result<bool, ErrorStatus>>),
AddLoadStatusSender(WebViewId, IpcSender<WebDriverLoadStatus>), AddLoadStatusSender(WebViewId, GenericSender<WebDriverLoadStatus>),
RemoveLoadStatusSender(WebViewId), RemoveLoadStatusSender(WebViewId),
} }
@ -289,8 +293,8 @@ pub enum WebDriverLoadStatus {
/// to a WebDriver server with information about application state. /// to a WebDriver server with information about application state.
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct WebDriverSenders { pub struct WebDriverSenders {
pub load_status_senders: HashMap<WebViewId, IpcSender<WebDriverLoadStatus>>, pub load_status_senders: HashMap<WebViewId, GenericSender<WebDriverLoadStatus>>,
pub script_evaluation_interrupt_sender: Option<IpcSender<WebDriverJSResult>>, pub script_evaluation_interrupt_sender: Option<IpcSender<WebDriverJSResult>>,
pub pending_traversals: HashMap<TraversalId, IpcSender<WebDriverLoadStatus>>, pub pending_traversals: HashMap<TraversalId, GenericSender<WebDriverLoadStatus>>,
pub pending_focus: HashMap<FocusId, IpcSender<bool>>, pub pending_focus: HashMap<FocusId, IpcSender<bool>>,
} }

View file

@ -21,11 +21,12 @@ use std::net::{SocketAddr, SocketAddrV4};
use std::time::Duration; use std::time::Duration;
use std::{env, fmt, process, thread}; use std::{env, fmt, process, thread};
use base::generic_channel::{self, GenericSender, RoutedReceiver};
use base::id::{BrowsingContextId, WebViewId}; use base::id::{BrowsingContextId, WebViewId};
use base64::Engine; use base64::Engine;
use capabilities::ServoCapabilities; use capabilities::ServoCapabilities;
use cookie::{CookieBuilder, Expiration, SameSite}; use cookie::{CookieBuilder, Expiration, SameSite};
use crossbeam_channel::{Receiver, Sender, after, select, unbounded}; use crossbeam_channel::{Sender, after, select};
use embedder_traits::{ use embedder_traits::{
EventLoopWaker, JSValue, MouseButton, WebDriverCommandMsg, WebDriverCommandResponse, EventLoopWaker, JSValue, MouseButton, WebDriverCommandMsg, WebDriverCommandResponse,
WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverLoadStatus, WebDriverMessageId, WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverLoadStatus, WebDriverMessageId,
@ -34,8 +35,7 @@ use embedder_traits::{
use euclid::{Point2D, Rect, Size2D}; use euclid::{Point2D, Rect, Size2D};
use http::method::Method; use http::method::Method;
use image::{DynamicImage, ImageFormat, RgbaImage}; use image::{DynamicImage, ImageFormat, RgbaImage};
use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::ipc::{self, IpcReceiver};
use ipc_channel::router::ROUTER;
use keyboard_types::webdriver::{Event as DispatchStringEvent, KeyInputState, send_keys}; use keyboard_types::webdriver::{Event as DispatchStringEvent, KeyInputState, send_keys};
use keyboard_types::{Code, Key, KeyState, KeyboardEvent, Location, NamedKey}; use keyboard_types::{Code, Key, KeyState, KeyboardEvent, Location, NamedKey};
use log::{debug, error, info}; use log::{debug, error, info};
@ -166,11 +166,11 @@ struct Handler {
/// The threaded receiver on which we can block for a load-status. /// The threaded receiver on which we can block for a load-status.
/// It will receive messages sent on the load_status_sender, /// It will receive messages sent on the load_status_sender,
/// and forwarded by the IPC router. /// and forwarded by the IPC router.
load_status_receiver: Receiver<WebDriverLoadStatus>, load_status_receiver: RoutedReceiver<WebDriverLoadStatus>,
/// The IPC sender which we can clone and pass along to the constellation, /// The IPC sender which we can clone and pass along to the constellation,
/// for it to send us a load-status. Messages sent on it /// for it to send us a load-status. Messages sent on it
/// will be forwarded to the load_status_receiver. /// will be forwarded to the load_status_receiver.
load_status_sender: IpcSender<WebDriverLoadStatus>, load_status_sender: GenericSender<WebDriverLoadStatus>,
session: Option<WebDriverSession>, session: Option<WebDriverSession>,
@ -415,9 +415,8 @@ impl Handler {
// and keep a threaded receiver to block on an incoming load-status. // and keep a threaded receiver to block on an incoming load-status.
// Pass the others to the IPC router so that IPC messages are forwarded to the threaded receiver. // Pass the others to the IPC router so that IPC messages are forwarded to the threaded receiver.
// We need to use the router because IPC does not come with a timeout on receive/select. // We need to use the router because IPC does not come with a timeout on receive/select.
let (load_status_sender, receiver) = ipc::channel().unwrap(); let (load_status_sender, receiver) = generic_channel::channel().unwrap();
let (sender, load_status_receiver) = unbounded(); let load_status_receiver = receiver.route_preserving_errors();
ROUTER.route_ipc_receiver_to_crossbeam_sender(receiver, sender);
Handler { Handler {
load_status_sender, load_status_sender,
@ -708,7 +707,7 @@ impl Handler {
recv(self.load_status_receiver) -> res => { recv(self.load_status_receiver) -> res => {
match res { match res {
// If the navigation is navigation to IFrame, no document state event is fired. // If the navigation is navigation to IFrame, no document state event is fired.
Ok(WebDriverLoadStatus::Blocked) => { Ok(Ok(WebDriverLoadStatus::Blocked)) => {
// TODO: evaluate the correctness later // TODO: evaluate the correctness later
// Load status is block means an user prompt is shown. // Load status is block means an user prompt is shown.
// Alot of tests expect this to return success // Alot of tests expect this to return success
@ -717,8 +716,8 @@ impl Handler {
// an error anyway. // an error anyway.
Ok(WebDriverResponse::Void) Ok(WebDriverResponse::Void)
}, },
Ok(WebDriverLoadStatus::Complete) | Ok(Ok(WebDriverLoadStatus::Complete)) |
Ok(WebDriverLoadStatus::NavigationStop) => Ok(Ok(WebDriverLoadStatus::NavigationStop)) =>
Ok(WebDriverResponse::Void) Ok(WebDriverResponse::Void)
, ,
_ => Err(WebDriverError::new( _ => Err(WebDriverError::new(
@ -738,7 +737,7 @@ impl Handler {
/// <https://w3c.github.io/webdriver/#dfn-wait-for-navigation-to-complete> /// <https://w3c.github.io/webdriver/#dfn-wait-for-navigation-to-complete>
fn wait_for_navigation(&self) -> WebDriverResult<WebDriverResponse> { fn wait_for_navigation(&self) -> WebDriverResult<WebDriverResponse> {
let navigation_status = match self.load_status_receiver.try_recv() { let navigation_status = match self.load_status_receiver.try_recv() {
Ok(status) => status, Ok(Ok(status)) => status,
// Empty channel means no navigation started. Nothing to wait for. // Empty channel means no navigation started. Nothing to wait for.
Err(crossbeam_channel::TryRecvError::Empty) => { Err(crossbeam_channel::TryRecvError::Empty) => {
return Ok(WebDriverResponse::Void); return Ok(WebDriverResponse::Void);
@ -749,6 +748,12 @@ impl Handler {
"Load status channel disconnected", "Load status channel disconnected",
)); ));
}, },
Ok(Err(ipc_error)) => {
return Err(WebDriverError::new(
ErrorStatus::UnknownError,
format!("Load status channel ipc error: {ipc_error}"),
));
},
}; };
match navigation_status { match navigation_status {

View file

@ -13,6 +13,7 @@ use embedder_traits::webdriver::WebDriverSenders;
use euclid::Vector2D; use euclid::Vector2D;
use keyboard_types::{Key, Modifiers, NamedKey, ShortcutMatcher}; use keyboard_types::{Key, Modifiers, NamedKey, ShortcutMatcher};
use log::{error, info}; use log::{error, info};
use servo::base::generic_channel::GenericSender;
use servo::base::id::WebViewId; use servo::base::id::WebViewId;
use servo::config::pref; use servo::config::pref;
use servo::ipc_channel::ipc::IpcSender; use servo::ipc_channel::ipc::IpcSender;
@ -456,7 +457,7 @@ impl RunningAppState {
pub(crate) fn set_pending_traversal( pub(crate) fn set_pending_traversal(
&self, &self,
traversal_id: TraversalId, traversal_id: TraversalId,
sender: IpcSender<WebDriverLoadStatus>, sender: GenericSender<WebDriverLoadStatus>,
) { ) {
self.webdriver_senders self.webdriver_senders
.borrow_mut() .borrow_mut()
@ -467,7 +468,7 @@ impl RunningAppState {
pub(crate) fn set_load_status_sender( pub(crate) fn set_load_status_sender(
&self, &self,
webview_id: WebViewId, webview_id: WebViewId,
sender: IpcSender<WebDriverLoadStatus>, sender: GenericSender<WebDriverLoadStatus>,
) { ) {
self.webdriver_senders self.webdriver_senders
.borrow_mut() .borrow_mut()