diff --git a/Cargo.lock b/Cargo.lock index 1367157b91f..cba86e7e831 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7752,6 +7752,7 @@ dependencies = [ "egui-file-dialog", "egui-winit", "egui_glow", + "embedder_traits", "env_filter", "euclid", "getopts", diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs index 8758a724016..3c415dcaa7a 100644 --- a/components/shared/embedder/lib.rs +++ b/components/shared/embedder/lib.rs @@ -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; diff --git a/components/shared/embedder/webdriver.rs b/components/shared/embedder/webdriver.rs index f490b8e7a80..3b1afb3c0e4 100644 --- a/components/shared/embedder/webdriver.rs +++ b/components/shared/embedder/webdriver.rs @@ -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>, + pub script_evaluation_interrupt_sender: Option>, + pub pending_traversals: HashMap>, + pub pending_focus: HashMap>, +} diff --git a/ports/servoshell/Cargo.toml b/ports/servoshell/Cargo.toml index 1b63d001d4a..5b9cc4a7419 100644 --- a/ports/servoshell/Cargo.toml +++ b/ports/servoshell/Cargo.toml @@ -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 } diff --git a/ports/servoshell/desktop/app_state.rs b/ports/servoshell/desktop/app_state.rs index 6bb3c227784..49e55b17d7b 100644 --- a/ports/servoshell/desktop/app_state.rs +++ b/ports/servoshell/desktop/app_state.rs @@ -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>, - pub script_evaluation_interrupt_sender: Option>, - pub pending_traversals: HashMap>, - pub pending_focus: HashMap>, -} - 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, diff --git a/ports/servoshell/egl/android/simpleservo.rs b/ports/servoshell/egl/android/simpleservo.rs index 4f7e35d3923..afcaf71312b 100644 --- a/ports/servoshell/egl/android/simpleservo.rs +++ b/ports/servoshell/egl/android/simpleservo.rs @@ -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); }); diff --git a/ports/servoshell/egl/app_state.rs b/ports/servoshell/egl/app_state.rs index febc3a32220..c6c8da94a22 100644 --- a/ports/servoshell/egl/app_state.rs +++ b/ports/servoshell/egl/app_state.rs @@ -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, /// 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>, + webdriver_senders: RefCell, } struct RunningAppStateInner { @@ -307,6 +313,7 @@ impl RunningAppState { servo: Servo, callbacks: Rc, servoshell_preferences: ServoShellPreferences, + webdriver_receiver: Option>, ) -> Rc { 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> { + self.webdriver_receiver.as_ref() + } + + pub fn handle_webdriver_messages(self: &Rc) { + 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() diff --git a/ports/servoshell/egl/ohos.rs b/ports/servoshell/egl/ohos.rs index bfba764923f..8c6925396b3 100644 --- a/ports/servoshell/egl/ohos.rs +++ b/ports/servoshell/egl/ohos.rs @@ -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"); diff --git a/ports/servoshell/egl/ohos/simpleservo.rs b/ports/servoshell/egl/ohos/simpleservo.rs index dfeed744f4e..af2a2056eaa 100644 --- a/ports/servoshell/egl/ohos/simpleservo.rs +++ b/ports/servoshell/egl/ohos/simpleservo.rs @@ -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)