Basic webdriver support to servoshell on ohos (#38386)

Adding basic webdriver support to servoshell on ohos and basic
communication between the ohos device and script.

Testing: Manual testing and tracing logs

Signed-off-by: abdelrahman1234567 <abdelrahman.hossameldin.awadalla@huawei.com>
This commit is contained in:
Abdelrahman Hossam 2025-08-12 14:26:53 +08:00 committed by GitHub
parent f5b631e270
commit 2e2bfc6067
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 113 additions and 17 deletions

1
Cargo.lock generated
View file

@ -7752,6 +7752,7 @@ dependencies = [
"egui-file-dialog",
"egui-winit",
"egui_glow",
"embedder_traits",
"env_filter",
"euclid",
"getopts",

View file

@ -11,7 +11,7 @@
pub mod input_events;
pub mod resources;
pub mod user_content_manager;
mod webdriver;
pub mod webdriver;
use std::collections::HashMap;
use std::ffi::c_void;

View file

@ -23,7 +23,7 @@ use webdriver::common::{WebElement, WebFrame, WebWindow};
use webdriver::error::ErrorStatus;
use webrender_api::units::DevicePixel;
use crate::{MouseButton, MouseButtonAction};
use crate::{FocusId, MouseButton, MouseButtonAction, TraversalId};
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct WebDriverMessageId(pub usize);
@ -301,3 +301,13 @@ pub enum WebDriverLoadStatus {
// 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: HashMap<WebViewId, IpcSender<WebDriverLoadStatus>>,
pub script_evaluation_interrupt_sender: Option<IpcSender<WebDriverJSResult>>,
pub pending_traversals: HashMap<TraversalId, IpcSender<WebDriverLoadStatus>>,
pub pending_focus: HashMap<FocusId, IpcSender<bool>>,
}

View file

@ -60,6 +60,7 @@ cfg-if = { workspace = true }
constellation_traits = { workspace = true }
crossbeam-channel = { workspace = true }
dpi = { workspace = true }
embedder_traits = { path = "../../components/shared/embedder" }
euclid = { workspace = true }
getopts = { workspace = true }
hitrace = { workspace = true, optional = true }

View file

@ -9,6 +9,7 @@ use std::path::PathBuf;
use std::rc::Rc;
use crossbeam_channel::Receiver;
use embedder_traits::webdriver::WebDriverSenders;
use euclid::Vector2D;
use keyboard_types::{Key, Modifiers, NamedKey, ShortcutMatcher};
use log::{error, info};
@ -40,16 +41,6 @@ pub(crate) enum AppState {
ShuttingDown,
}
/// A collection of [`IpcSender`]s that are used to asynchronously communicate
/// to a WebDriver server with information about application state.
#[derive(Clone, Default)]
struct WebDriverSenders {
pub load_status_senders: HashMap<WebViewId, IpcSender<WebDriverLoadStatus>>,
pub script_evaluation_interrupt_sender: Option<IpcSender<WebDriverJSResult>>,
pub pending_traversals: HashMap<TraversalId, IpcSender<WebDriverLoadStatus>>,
pub pending_focus: HashMap<FocusId, IpcSender<bool>>,
}
pub(crate) struct RunningAppState {
/// A handle to the Servo instance of the [`RunningAppState`]. This is not stored inside
/// `inner` so that we can keep a reference to Servo in order to spin the event loop,

View file

@ -6,8 +6,11 @@ use std::cell::RefCell;
use std::mem;
use std::rc::Rc;
use constellation_traits::EmbedderToConstellationMessage;
use crossbeam_channel::unbounded;
use dpi::PhysicalSize;
use raw_window_handle::{DisplayHandle, RawDisplayHandle, RawWindowHandle, WindowHandle};
use servo::ipc_channel::ipc;
pub use servo::webrender_api::units::DeviceIntRect;
use servo::{self, EventLoopWaker, ServoBuilder, resources};
pub use servo::{InputMethodType, MediaSessionPlaybackState, WindowRenderingContext};
@ -84,21 +87,43 @@ pub fn init(
let servo_builder = ServoBuilder::new(rendering_context.clone())
.opts(opts)
.preferences(preferences)
.event_loop_waker(waker);
.event_loop_waker(waker.clone());
#[cfg(feature = "webxr")]
let servo_builder = servo_builder.webxr_registry(Box::new(XrDiscoveryWebXrRegistry::new(
init_opts.xr_discovery,
)));
let servo = servo_builder.build();
// Initialize WebDriver server if port is specified
let webdriver_receiver = servoshell_preferences.webdriver_port.map(|port| {
let (embedder_sender, embedder_receiver) = unbounded();
let (webdriver_response_sender, webdriver_response_receiver) = ipc::channel().unwrap();
// Set the WebDriver response sender to constellation
servo
.constellation_sender()
.send(EmbedderToConstellationMessage::SetWebDriverResponseSender(
webdriver_response_sender,
))
.expect("Failed to set WebDriver response sender in constellation");
webdriver_server::start_server(port, embedder_sender, waker, webdriver_response_receiver);
log::info!("WebDriver server started on port {}", port);
embedder_receiver
});
APP.with(|app| {
let app_state = RunningAppState::new(
init_opts.url,
init_opts.density,
rendering_context,
servo_builder.build(),
servo,
window_callbacks,
servoshell_preferences,
webdriver_receiver,
);
*app.borrow_mut() = Some(app_state);
});

View file

@ -5,7 +5,9 @@ use std::cell::{Cell, Ref, RefCell, RefMut};
use std::collections::HashMap;
use std::rc::Rc;
use crossbeam_channel::Receiver;
use dpi::PhysicalSize;
use embedder_traits::webdriver::WebDriverSenders;
use ipc_channel::ipc::IpcSender;
use log::{debug, error, info, warn};
use raw_window_handle::{RawWindowHandle, WindowHandle};
@ -19,8 +21,8 @@ use servo::{
InputEvent, InputMethodType, Key, KeyState, KeyboardEvent, LoadStatus, MediaSessionActionType,
MediaSessionEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, NamedKey,
NavigationRequest, PermissionRequest, RenderingContext, ScreenGeometry, Servo, ServoDelegate,
ServoError, SimpleDialog, TouchEvent, TouchEventType, TouchId, WebView, WebViewBuilder,
WebViewDelegate, WindowRenderingContext,
ServoError, SimpleDialog, TouchEvent, TouchEventType, TouchId, WebDriverCommandMsg, WebView,
WebViewBuilder, WebViewDelegate, WindowRenderingContext,
};
use url::Url;
@ -73,6 +75,10 @@ pub struct RunningAppState {
inner: RefCell<RunningAppStateInner>,
/// servoshell specific preferences created during startup of the application.
servoshell_preferences: ServoShellPreferences,
/// A [`Receiver`] for receiving commands from a running WebDriver server, if WebDriver
/// was enabled.
webdriver_receiver: Option<Receiver<WebDriverCommandMsg>>,
webdriver_senders: RefCell<WebDriverSenders>,
}
struct RunningAppStateInner {
@ -307,6 +313,7 @@ impl RunningAppState {
servo: Servo,
callbacks: Rc<ServoWindowCallbacks>,
servoshell_preferences: ServoShellPreferences,
webdriver_receiver: Option<Receiver<WebDriverCommandMsg>>,
) -> Rc<Self> {
let initial_url = initial_url.and_then(|string| Url::parse(&string).ok());
let initial_url = initial_url
@ -324,6 +331,8 @@ impl RunningAppState {
servo,
callbacks,
servoshell_preferences,
webdriver_receiver,
webdriver_senders: RefCell::default(),
inner: RefCell::new(RunningAppStateInner {
need_present: false,
context_menu_sender: None,
@ -484,6 +493,35 @@ impl RunningAppState {
self.perform_updates();
}
/// WebDriver message handling methods
pub(crate) fn webdriver_receiver(&self) -> Option<&Receiver<WebDriverCommandMsg>> {
self.webdriver_receiver.as_ref()
}
pub fn handle_webdriver_messages(self: &Rc<Self>) {
if let Some(webdriver_receiver) = &self.webdriver_receiver {
while let Ok(msg) = webdriver_receiver.try_recv() {
match msg {
WebDriverCommandMsg::LoadUrl(webview_id, url, load_status_sender) => {
info!(
"(Not Implemented) Loading URL in webview {}: {}",
webview_id, url
);
},
WebDriverCommandMsg::NewWebView(response_sender, load_status_sender) => {
info!("(Not Implemented) Creating new webview");
},
WebDriverCommandMsg::FocusWebView(webview_id, response_sender) => {
info!("(Not Implemented) Focusing webview {}", webview_id);
},
_ => {
info!("(Not Implemented) Received WebDriver command: {:?}", msg);
},
}
}
}
}
/// Touch event: press down
pub fn touch_down(&self, x: f32, y: f32, pointer_id: i32) {
self.active_webview()

View file

@ -345,6 +345,8 @@ extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window
while let Ok(action) = rx.recv() {
trace!("Wakeup message received!");
action.do_action(&servo);
// Also handle any pending WebDriver messages
servo.handle_webdriver_messages();
}
info!("Sender disconnected - Terminating main surface thread");

View file

@ -8,6 +8,8 @@ use std::path::PathBuf;
use std::ptr::NonNull;
use std::rc::Rc;
use constellation_traits::EmbedderToConstellationMessage;
use crossbeam_channel::unbounded;
use dpi::PhysicalSize;
use log::{debug, info, warn};
use ohos_abilitykit_sys::runtime::application_context;
@ -16,6 +18,7 @@ use raw_window_handle::{
DisplayHandle, OhosDisplayHandle, OhosNdkWindowHandle, RawDisplayHandle, RawWindowHandle,
WindowHandle,
};
use servo::ipc_channel::ipc;
use servo::{self, EventLoopWaker, ServoBuilder, WindowRenderingContext, resources};
use xcomponent_sys::OH_NativeXComponent;
@ -191,9 +194,33 @@ pub fn init(
let servo = ServoBuilder::new(rendering_context.clone())
.opts(opts)
.preferences(preferences)
.event_loop_waker(waker)
.event_loop_waker(waker.clone())
.build();
// Initialize WebDriver server if port is specified
let webdriver_receiver = servoshell_preferences.webdriver_port.map(|port| {
let (embedder_sender, embedder_receiver) = unbounded();
let (webdriver_response_sender, webdriver_response_receiver) = ipc::channel().unwrap();
// Set the WebDriver response sender to constellation
servo
.constellation_sender()
.send(EmbedderToConstellationMessage::SetWebDriverResponseSender(
webdriver_response_sender,
))
.expect("Failed to set WebDriver response sender in constellation when init Servo");
webdriver_server::start_server(
port,
embedder_sender,
waker.clone(),
webdriver_response_receiver,
);
info!("WebDriver server started on port {}", port);
embedder_receiver
});
let app_state = RunningAppState::new(
Some(options.url),
native_values.display_density as f32,
@ -201,6 +228,7 @@ pub fn init(
servo,
window_callbacks,
servoshell_preferences,
webdriver_receiver,
);
Ok(app_state)