[webdriver] Move Webdriver to ServoShell (#36714)

Moving `webdriver` to `servoshell`:

- Let `servoshell` manage lifecycle of `webdriver`
- One by one, move the handling of webdriver commands from
`constellation` to `embedder`

Partially fix: https://github.com/servo/servo/issues/37370
Partially fix webdriver test timeout with `no_top_browsing_context`

cc: @xiaochengh

---------

Signed-off-by: batu_hoang <longvatrong111@gmail.com>
This commit is contained in:
batu_hoang 2025-06-19 17:52:01 +08:00 committed by GitHub
parent d55e2c4c90
commit d0100797e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 156 additions and 35 deletions

View file

@ -4536,8 +4536,8 @@ where
// Find the script channel for the given parent pipeline,
// and pass the event to that script thread.
match msg {
WebDriverCommandMsg::CloseWebView(webview_id) => {
self.handle_close_top_level_browsing_context(webview_id);
WebDriverCommandMsg::CloseWebView(..) => {
unreachable!("This command should be send directly to the embedder.");
},
WebDriverCommandMsg::NewWebView(
originating_webview_id,
@ -4575,9 +4575,8 @@ where
WebDriverCommandMsg::FocusWebView(webview_id) => {
self.handle_focus_web_view(webview_id);
},
WebDriverCommandMsg::IsWebViewOpen(webview_id, response_sender) => {
let is_open = self.webviews.get(webview_id).is_some();
let _ = response_sender.send(is_open);
WebDriverCommandMsg::IsWebViewOpen(..) => {
unreachable!("This command should be send directly to the embedder.");
},
WebDriverCommandMsg::IsBrowsingContextOpen(browsing_context_id, response_sender) => {
let is_open = self.browsing_contexts.contains_key(&browsing_context_id);

View file

@ -247,6 +247,7 @@ mod from_script {
Self::FinishJavaScriptEvaluation(..) => {
target_variant!("FinishJavaScriptEvaluation")
},
Self::WebDriverCommand(..) => target_variant!("WebDriverCommand"),
}
}
}

View file

@ -138,9 +138,6 @@ fn webdriver(port: u16, constellation: Sender<EmbedderToConstellationMessage>) {
webdriver_server::start_server(port, constellation);
}
#[cfg(not(feature = "webdriver"))]
fn webdriver(_port: u16, _constellation: Sender<EmbedderToConstellationMessage>) {}
#[cfg(feature = "media-gstreamer")]
mod media_platform {
#[cfg(any(windows, target_os = "macos"))]
@ -468,12 +465,6 @@ impl Servo {
builder.user_content_manager,
);
if cfg!(feature = "webdriver") {
if let Some(port) = opts.webdriver_port {
webdriver(port, constellation_chan.clone());
}
}
// The compositor coordinates with the client window to create the final
// rendered page and display it somewhere.
let shutdown_state = Rc::new(Cell::new(ShutdownState::NotShuttingDown));
@ -1008,8 +999,18 @@ impl Servo {
webview.delegate().show_form_control(webview, form_control);
}
},
_ => {},
}
}
pub fn constellation_sender(&self) -> Sender<EmbedderToConstellationMessage> {
self.constellation_proxy.sender()
}
pub fn execute_webdriver_command(&self, command: WebDriverCommandMsg) {
self.constellation_proxy
.send(EmbedderToConstellationMessage::WebDriverCommand(command));
}
}
fn create_embedder_channel(

View file

@ -372,6 +372,7 @@ pub enum EmbedderMsg {
JavaScriptEvaluationId,
Result<JSValue, JavaScriptEvaluationError>,
),
WebDriverCommand(WebDriverCommandMsg),
}
impl Debug for EmbedderMsg {

View file

@ -24,8 +24,8 @@ use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection};
use cookie::{CookieBuilder, Expiration};
use crossbeam_channel::{Receiver, Sender, after, select, unbounded};
use embedder_traits::{
MouseButton, WebDriverCommandMsg, WebDriverCommandResponse, WebDriverFrameId, WebDriverJSError,
WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, WebDriverMessageId,
EventLoopWaker, MouseButton, WebDriverCommandMsg, WebDriverCommandResponse, WebDriverFrameId,
WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, WebDriverMessageId,
WebDriverScriptCommand,
};
use euclid::{Rect, Size2D};
@ -122,8 +122,17 @@ fn cookie_msg_to_cookie(cookie: cookie::Cookie) -> Cookie {
}
}
pub fn start_server(port: u16, constellation_chan: Sender<EmbedderToConstellationMessage>) {
let handler = Handler::new(constellation_chan);
pub fn start_server(
port: u16,
constellation_chan_deprecated: Sender<EmbedderToConstellationMessage>,
embedder_sender: Sender<WebDriverCommandMsg>,
event_loop_waker: Box<dyn EventLoopWaker>,
) {
let handler = Handler::new(
constellation_chan_deprecated,
embedder_sender,
event_loop_waker,
);
thread::Builder::new()
.name("WebDriverHttpServer".to_owned())
.spawn(move || {
@ -223,10 +232,20 @@ struct Handler {
session: Option<WebDriverSession>,
/// A [`Sender`] that sends messages to the embedder that this `WebDriver instance controls.
/// In addition to sending a message, we must always wake up the embedder's event loop so it
/// knows that more messages are available for processing.
embedder_sender: Sender<WebDriverCommandMsg>,
/// An [`EventLoopWaker`] which is used to wake up the embedder event loop.
event_loop_waker: Box<dyn EventLoopWaker>,
/// The channel for sending Webdriver messages to the constellation.
/// TODO: change name to constellation_sender
constellation_chan: Sender<EmbedderToConstellationMessage>,
/// The IPC sender which we can clone and pass along to the constellation
/// TODO: change name to webdriver_response_sender
constellation_sender: IpcSender<WebDriverCommandResponse>,
/// Receiver notification from the constellation when a command is completed
@ -450,7 +469,11 @@ impl<'de> Visitor<'de> for TupleVecMapVisitor {
}
impl Handler {
pub fn new(constellation_chan: Sender<EmbedderToConstellationMessage>) -> Handler {
pub fn new(
constellation_chan: Sender<EmbedderToConstellationMessage>,
embedder_sender: Sender<WebDriverCommandMsg>,
event_loop_waker: Box<dyn EventLoopWaker>,
) -> Handler {
// Create a pair of both an IPC and a threaded channel,
// keep the IPC sender to clone and pass to the constellation for each load,
// and keep a threaded receiver to block on an incoming load-status.
@ -466,6 +489,8 @@ impl Handler {
load_status_sender,
load_status_receiver,
session: None,
embedder_sender,
event_loop_waker,
constellation_chan,
constellation_sender,
constellation_receiver,
@ -482,6 +507,17 @@ impl Handler {
.set(self.num_pending_actions.get() + 1);
}
fn send_message_to_embedder(&self, msg: WebDriverCommandMsg) -> WebDriverResult<()> {
self.embedder_sender.send(msg).map_err(|_| {
WebDriverError::new(
ErrorStatus::UnknownError,
"Failed to send message to embedder",
)
})?;
self.event_loop_waker.wake();
Ok(())
}
fn focus_webview_id(&self) -> WebDriverResult<WebViewId> {
debug!("Getting focused context.");
let (sender, receiver) = ipc::channel().unwrap();
@ -1001,9 +1037,7 @@ impl Handler {
// Step 3. Close session's current top-level browsing context.
session.window_handles.remove(&webview_id);
let cmd_msg = WebDriverCommandMsg::CloseWebView(session.webview_id);
self.constellation_chan
.send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg))
.unwrap();
self.send_message_to_embedder(cmd_msg)?;
let window_handles: Vec<String> = self
.session()
.unwrap()
@ -1015,6 +1049,7 @@ impl Handler {
if window_handles.is_empty() {
self.session = None;
}
// Step 5. Return the result of running the remote end steps for the Get Window Handles command
Ok(WebDriverResponse::CloseWindow(CloseWindowResponse(
window_handles,
@ -2052,11 +2087,7 @@ impl Handler {
webview_id: WebViewId,
) -> Result<(), WebDriverError> {
let (sender, receiver) = ipc::channel().unwrap();
self.constellation_chan
.send(EmbedderToConstellationMessage::WebDriverCommand(
WebDriverCommandMsg::IsWebViewOpen(webview_id, sender),
))
.unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::IsWebViewOpen(webview_id, sender))?;
if !receiver.recv().unwrap_or(false) {
Err(WebDriverError::new(
ErrorStatus::NoSuchWindow,
@ -2072,11 +2103,10 @@ impl Handler {
browsing_context_id: BrowsingContextId,
) -> Result<(), WebDriverError> {
let (sender, receiver) = ipc::channel().unwrap();
self.constellation_chan
.send(EmbedderToConstellationMessage::WebDriverCommand(
WebDriverCommandMsg::IsBrowsingContextOpen(browsing_context_id, sender),
))
.unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::IsBrowsingContextOpen(
browsing_context_id,
sender,
))?;
if !receiver.recv().unwrap_or(false) {
Err(WebDriverError::new(
ErrorStatus::NoSuchWindow,