aboutsummaryrefslogtreecommitdiffstats
path: root/ports
diff options
context:
space:
mode:
Diffstat (limited to 'ports')
-rw-r--r--ports/servoshell/Cargo.toml11
-rw-r--r--ports/servoshell/backtrace.rs11
-rw-r--r--ports/servoshell/build.rs20
-rw-r--r--ports/servoshell/egl/android.rs6
-rw-r--r--ports/servoshell/egl/android/simpleservo.rs814
-rw-r--r--ports/servoshell/egl/gl_glue.rs1
-rw-r--r--ports/servoshell/egl/host_trait.rs75
-rw-r--r--ports/servoshell/egl/mod.rs9
-rw-r--r--ports/servoshell/egl/ohos.rs619
-rw-r--r--ports/servoshell/egl/ohos/simpleservo.rs110
-rw-r--r--ports/servoshell/egl/resources.rs56
-rw-r--r--ports/servoshell/egl/servo_glue.rs701
-rw-r--r--ports/servoshell/lib.rs5
-rw-r--r--ports/servoshell/main.rs9
14 files changed, 1643 insertions, 804 deletions
diff --git a/ports/servoshell/Cargo.toml b/ports/servoshell/Cargo.toml
index 33c5fa32d5c..8ba4692907a 100644
--- a/ports/servoshell/Cargo.toml
+++ b/ports/servoshell/Cargo.toml
@@ -60,6 +60,8 @@ log = { workspace = true }
lazy_static = { workspace = true }
getopts = { workspace = true }
url = { workspace = true }
+servo-media = { workspace = true }
+
[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.14"
@@ -67,7 +69,6 @@ ipc-channel = { workspace = true }
jni = "0.21.1"
libloading = "0.8"
serde_json = { workspace = true }
-servo-media = { workspace = true }
surfman = { workspace = true, features = ["sm-angle-default"] }
webxr = { git = "https://github.com/servo/webxr" }
@@ -79,6 +80,13 @@ backtrace = { workspace = true }
# force inprocess, until libc-rs 0.2.156 is released containing
# https://github.com/rust-lang/libc/commit/9e248e212c5602cb4e98676e4c21ea0382663a12
ipc-channel = { workspace = true, features = ["force-inprocess"] }
+hilog = "0.1.0"
+napi-derive-ohos = "0.0.9"
+napi-ohos = "0.1"
+serde_json = { workspace = true }
+surfman = { workspace = true, features = ["sm-angle-default"] }
+webxr = { git = "https://github.com/servo/webxr" }
+ohos-sys = { git = "https://github.com/jschwe/ohos-sys.git" }
[target.'cfg(not(any(target_os = "android", target_env = "ohos")))'.dependencies]
@@ -94,7 +102,6 @@ gleam = { workspace = true }
glow = "0.13.1"
keyboard-types = { workspace = true }
raw-window-handle = "0.6"
-servo-media = { workspace = true }
shellwords = "1.0.0"
surfman = { workspace = true, features = ["sm-x11", "sm-raw-window-handle-06"] }
tinyfiledialogs = "3.0"
diff --git a/ports/servoshell/backtrace.rs b/ports/servoshell/backtrace.rs
index 7b7deeb1580..cfa1ada625a 100644
--- a/ports/servoshell/backtrace.rs
+++ b/ports/servoshell/backtrace.rs
@@ -24,6 +24,17 @@ pub(crate) fn print(w: &mut dyn std::io::Write) -> Result<(), std::io::Error> {
)
}
+#[cfg(target_env = "ohos")]
+pub(crate) fn print_ohos() {
+ // Print to `hilog`
+ log::error!(
+ "{:?}",
+ Print {
+ print_fn_address: print as usize,
+ }
+ )
+}
+
struct Print {
print_fn_address: usize,
}
diff --git a/ports/servoshell/build.rs b/ports/servoshell/build.rs
index f226f2094b7..c593c3f8a88 100644
--- a/ports/servoshell/build.rs
+++ b/ports/servoshell/build.rs
@@ -10,6 +10,16 @@ use std::path::Path;
use gl_generator::{Api, Fallbacks, Profile, Registry};
use vergen::EmitBuilder;
+// We can make this configurable in the future if different platforms start to have
+// different needs.
+fn generate_egl_bindings(out_dir: &Path) {
+ let mut file = File::create(out_dir.join("egl_bindings.rs")).unwrap();
+ Registry::new(Api::Egl, (1, 5), Profile::Core, Fallbacks::All, [])
+ .write_bindings(gl_generator::StaticStructGenerator, &mut file)
+ .unwrap();
+ println!("cargo:rustc-link-lib=EGL");
+}
+
fn main() -> Result<(), Box<dyn Error>> {
// Cargo does not expose the profile name to crates or their build scripts,
// but we can extract it from OUT_DIR and set a custom cfg() ourselves.
@@ -27,6 +37,7 @@ fn main() -> Result<(), Box<dyn Error>> {
// Note: We can't use `#[cfg(windows)]`, since that would check the host platform
// and not the target platform
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
+ let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
if target_os == "windows" {
#[cfg(windows)]
@@ -43,12 +54,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.file("platform/macos/count_threads.c")
.compile("count_threads");
} else if target_os == "android" {
- // Generate GL bindings. For now, we only support EGL.
- let mut file = File::create(out.join("egl_bindings.rs")).unwrap();
- Registry::new(Api::Egl, (1, 5), Profile::Core, Fallbacks::All, [])
- .write_bindings(gl_generator::StaticStructGenerator, &mut file)
- .unwrap();
- println!("cargo:rustc-link-lib=EGL");
+ generate_egl_bindings(out);
// FIXME: We need this workaround since jemalloc-sys still links
// to libgcc instead of libunwind, but Android NDK 23c and above
@@ -59,6 +65,8 @@ fn main() -> Result<(), Box<dyn Error>> {
let mut libgcc = File::create(out.join("libgcc.a")).unwrap();
libgcc.write_all(b"INPUT(-lunwind)").unwrap();
println!("cargo:rustc-link-search=native={}", out.display());
+ } else if target_env == "ohos" {
+ generate_egl_bindings(out);
}
if let Err(error) = EmitBuilder::builder()
diff --git a/ports/servoshell/egl/android.rs b/ports/servoshell/egl/android.rs
index eb8d08c8057..7c331febfee 100644
--- a/ports/servoshell/egl/android.rs
+++ b/ports/servoshell/egl/android.rs
@@ -18,11 +18,13 @@ use jni::{JNIEnv, JavaVM};
use libc::{dup2, pipe, read};
use log::{debug, error, info, warn};
use simpleservo::{
- Coordinates, DeviceIntRect, EventLoopWaker, HostTrait, InitOptions, InputMethodType,
- MediaSessionPlaybackState, PromptResult, ServoGlue, SERVO,
+ DeviceIntRect, EventLoopWaker, InitOptions, InputMethodType, MediaSessionPlaybackState,
+ PromptResult, SERVO,
};
use super::gl_glue;
+use super::host_trait::HostTrait;
+use super::servo_glue::{Coordinates, ServoGlue};
struct HostCallbacks {
callbacks: GlobalRef,
diff --git a/ports/servoshell/egl/android/simpleservo.rs b/ports/servoshell/egl/android/simpleservo.rs
index 2e3824068a0..d12146382f5 100644
--- a/ports/servoshell/egl/android/simpleservo.rs
+++ b/ports/servoshell/egl/android/simpleservo.rs
@@ -6,51 +6,37 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::mem;
use std::os::raw::c_void;
-use std::path::PathBuf;
use std::rc::Rc;
use getopts::Options;
-use ipc_channel::ipc::IpcSender;
-use log::{debug, info, warn};
-use servo::base::id::WebViewId;
-use servo::compositing::windowing::{
- AnimationState, EmbedderCoordinates, EmbedderEvent, EmbedderMethods, MouseWindowEvent,
- WindowMethods,
-};
+use log::info;
+use servo::compositing::windowing::EmbedderEvent;
use servo::compositing::CompositeTarget;
use servo::config::prefs::pref_map;
pub use servo::config::prefs::{add_user_prefs, PrefValue};
-use servo::embedder_traits::resources::{self, Resource, ResourceReaderMethods};
-pub use servo::embedder_traits::{
- ContextMenuResult, InputMethodType, MediaSessionPlaybackState, PermissionPrompt,
- PermissionRequest, PromptResult,
-};
-use servo::embedder_traits::{
- EmbedderMsg, EmbedderProxy, MediaSessionEvent, PromptDefinition, PromptOrigin,
-};
-use servo::euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
-use servo::keyboard_types::{Key, KeyState, KeyboardEvent};
-use servo::net_traits::pub_domains::is_reg_domain;
-pub use servo::script_traits::{MediaSessionActionType, MouseButton};
-use servo::script_traits::{TouchEventType, TouchId, TraversalDirection};
+use servo::embedder_traits::resources;
+/// The EventLoopWaker::wake function will be called from any thread.
+/// It will be called to notify embedder that some events are available,
+/// and that perform_updates need to be called
+pub use servo::embedder_traits::EventLoopWaker;
+pub use servo::embedder_traits::{InputMethodType, MediaSessionPlaybackState, PromptResult};
use servo::servo_config::{opts, pref};
use servo::servo_url::ServoUrl;
pub use servo::webrender_api::units::DeviceIntRect;
-use servo::webrender_api::units::DevicePixel;
-use servo::webrender_api::ScrollLocation;
use servo::webrender_traits::RenderingContext;
-use servo::{self, gl, Servo, TopLevelBrowsingContextId};
+use servo::{self, gl, Servo};
use surfman::{Connection, SurfaceType};
+use crate::egl::host_trait::HostTrait;
+use crate::egl::resources::ResourceReaderInstance;
+use crate::egl::servo_glue::{
+ Coordinates, ServoEmbedderCallbacks, ServoGlue, ServoWindowCallbacks,
+};
+
thread_local! {
pub static SERVO: RefCell<Option<ServoGlue>> = RefCell::new(None);
}
-/// The EventLoopWaker::wake function will be called from any thread.
-/// It will be called to notify embedder that some events are available,
-/// and that perform_updates need to be called
-pub use servo::embedder_traits::EventLoopWaker;
-
pub struct InitOptions {
pub args: Vec<String>,
pub url: Option<String>,
@@ -69,121 +55,6 @@ pub enum SurfmanIntegration {
Surface,
}
-#[derive(Clone, Debug)]
-pub struct Coordinates {
- pub viewport: Rect<i32, DevicePixel>,
- pub framebuffer: Size2D<i32, DevicePixel>,
-}
-
-impl Coordinates {
- pub fn new(
- x: i32,
- y: i32,
- width: i32,
- height: i32,
- fb_width: i32,
- fb_height: i32,
- ) -> Coordinates {
- Coordinates {
- viewport: Rect::new(Point2D::new(x, y), Size2D::new(width, height)),
- framebuffer: Size2D::new(fb_width, fb_height),
- }
- }
-}
-
-/// Callbacks. Implemented by embedder. Called by Servo.
-pub trait HostTrait {
- /// Show alert.
- fn prompt_alert(&self, msg: String, trusted: bool);
- /// Ask Yes/No question.
- fn prompt_yes_no(&self, msg: String, trusted: bool) -> PromptResult;
- /// Ask Ok/Cancel question.
- fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult;
- /// Ask for string
- fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option<String>;
- /// Show context menu
- fn show_context_menu(&self, title: Option<String>, items: Vec<String>);
- /// Page starts loading.
- /// "Reload button" should be disabled.
- /// "Stop button" should be enabled.
- /// Throbber starts spinning.
- fn on_load_started(&self);
- /// Page has loaded.
- /// "Reload button" should be enabled.
- /// "Stop button" should be disabled.
- /// Throbber stops spinning.
- fn on_load_ended(&self);
- /// Page title has changed.
- fn on_title_changed(&self, title: Option<String>);
- /// Allow Navigation.
- fn on_allow_navigation(&self, url: String) -> bool;
- /// Page URL has changed.
- fn on_url_changed(&self, url: String);
- /// Back/forward state has changed.
- /// Back/forward buttons need to be disabled/enabled.
- fn on_history_changed(&self, can_go_back: bool, can_go_forward: bool);
- /// Page animation state has changed. If animating, it's recommended
- /// that the embedder doesn't wait for the wake function to be called
- /// to call perform_updates. Usually, it means doing:
- /// while true { servo.perform_updates() }. This will end up calling flush
- /// which will call swap_buffer which will be blocking long enough to limit
- /// drawing at 60 FPS.
- /// If not animating, call perform_updates only when needed (when the embedder
- /// has events for Servo, or Servo has woken up the embedder event loop via
- /// EventLoopWaker).
- fn on_animating_changed(&self, animating: bool);
- /// Servo finished shutting down.
- fn on_shutdown_complete(&self);
- /// A text input is focused.
- fn on_ime_show(
- &self,
- input_type: InputMethodType,
- text: Option<(String, i32)>,
- multiline: bool,
- bounds: DeviceIntRect,
- );
- /// Input lost focus
- fn on_ime_hide(&self);
- /// Gets sytem clipboard contents.
- fn get_clipboard_contents(&self) -> Option<String>;
- /// Sets system clipboard contents.
- fn set_clipboard_contents(&self, contents: String);
- /// Called when we get the media session metadata/
- fn on_media_session_metadata(&self, title: String, artist: String, album: String);
- /// Called when the media session playback state changes.
- fn on_media_session_playback_state_change(&self, state: MediaSessionPlaybackState);
- /// Called when the media session position state is set.
- fn on_media_session_set_position_state(&self, duration: f64, position: f64, playback_rate: f64);
- /// Called when devtools server is started
- fn on_devtools_started(&self, port: Result<u16, ()>, token: String);
- /// Called when we get a panic message from constellation
- fn on_panic(&self, reason: String, backtrace: Option<String>);
-}
-
-pub struct ServoGlue {
- rendering_context: RenderingContext,
- servo: Servo<ServoWindowCallbacks>,
- batch_mode: bool,
- callbacks: Rc<ServoWindowCallbacks>,
- events: Vec<EmbedderEvent>,
- context_menu_sender: Option<IpcSender<ContextMenuResult>>,
-
- /// List of top-level browsing contexts.
- /// Modified by EmbedderMsg::WebViewOpened and EmbedderMsg::WebViewClosed,
- /// and we exit if it ever becomes empty.
- webviews: HashMap<WebViewId, WebView>,
-
- /// The order in which the webviews were created.
- creation_order: Vec<WebViewId>,
-
- /// The webview that is currently focused.
- /// Modified by EmbedderMsg::WebViewFocused and EmbedderMsg::WebViewBlurred.
- focused_webview_id: Option<WebViewId>,
-}
-
-#[derive(Debug)]
-pub struct WebView {}
-
/// Test if a url is valid.
pub fn is_uri_valid(url: &str) -> bool {
info!("load_uri: {}", url);
@@ -280,18 +151,18 @@ pub fn init(
let rendering_context = RenderingContext::create(&connection, &adapter, surface_type)
.or(Err("Failed to create surface manager"))?;
- let window_callbacks = Rc::new(ServoWindowCallbacks {
- host_callbacks: callbacks,
- coordinates: RefCell::new(init_opts.coordinates),
- density: init_opts.density,
- rendering_context: rendering_context.clone(),
- });
+ let window_callbacks = Rc::new(ServoWindowCallbacks::new(
+ callbacks,
+ RefCell::new(init_opts.coordinates),
+ init_opts.density,
+ rendering_context.clone(),
+ ));
- let embedder_callbacks = Box::new(ServoEmbedderCallbacks {
- xr_discovery: init_opts.xr_discovery,
+ let embedder_callbacks = Box::new(ServoEmbedderCallbacks::new(
waker,
- gl: gl.clone(),
- });
+ init_opts.xr_discovery,
+ gl.clone(),
+ ));
let servo = Servo::new(
embedder_callbacks,
@@ -301,17 +172,7 @@ pub fn init(
);
SERVO.with(|s| {
- let mut servo_glue = ServoGlue {
- rendering_context,
- servo: servo.servo,
- batch_mode: false,
- callbacks: window_callbacks,
- events: vec![],
- context_menu_sender: None,
- webviews: HashMap::default(),
- creation_order: vec![],
- focused_webview_id: None,
- };
+ let mut servo_glue = ServoGlue::new(rendering_context, servo.servo, window_callbacks);
let _ = servo_glue.process_event(EmbedderEvent::NewWebView(url, servo.browser_id));
*s.borrow_mut() = Some(servo_glue);
});
@@ -322,626 +183,3 @@ pub fn init(
pub fn deinit() {
SERVO.with(|s| s.replace(None).unwrap().deinit());
}
-
-impl ServoGlue {
- fn get_browser_id(&self) -> Result<TopLevelBrowsingContextId, &'static str> {
- let webview_id = match self.focused_webview_id {
- Some(id) => id,
- None => return Err("No focused WebViewId yet."),
- };
- Ok(webview_id)
- }
-
- /// Request shutdown. Will call on_shutdown_complete.
- pub fn request_shutdown(&mut self) -> Result<(), &'static str> {
- self.process_event(EmbedderEvent::Quit)
- }
-
- /// Call after on_shutdown_complete
- pub fn deinit(self) {
- self.servo.deinit();
- }
-
- /// Returns the webrender surface management integration interface.
- /// This provides the embedder access to the current front buffer.
- pub fn surfman(&self) -> RenderingContext {
- self.rendering_context.clone()
- }
-
- /// This is the Servo heartbeat. This needs to be called
- /// everytime wakeup is called or when embedder wants Servo
- /// to act on its pending events.
- pub fn perform_updates(&mut self) -> Result<(), &'static str> {
- debug!("perform_updates");
- let events = mem::replace(&mut self.events, Vec::new());
- self.servo.handle_events(events);
- let r = self.handle_servo_events();
- debug!("done perform_updates");
- r
- }
-
- /// In batch mode, Servo won't call perform_updates automatically.
- /// This can be useful when the embedder wants to control when Servo
- /// acts on its pending events. For example, if the embedder wants Servo
- /// to act on the scroll events only at a certain time, not everytime
- /// scroll() is called.
- pub fn set_batch_mode(&mut self, batch: bool) -> Result<(), &'static str> {
- debug!("set_batch_mode");
- self.batch_mode = batch;
- Ok(())
- }
-
- /// Load an URL.
- pub fn load_uri(&mut self, url: &str) -> Result<(), &'static str> {
- info!("load_uri: {}", url);
- crate::parser::location_bar_input_to_url(url)
- .ok_or("Can't parse URL")
- .and_then(|url| {
- let browser_id = self.get_browser_id()?;
- let event = EmbedderEvent::LoadUrl(browser_id, url);
- self.process_event(event)
- })
- }
-
- /// Reload the page.
- pub fn clear_cache(&mut self) -> Result<(), &'static str> {
- info!("clear_cache");
- let event = EmbedderEvent::ClearCache;
- self.process_event(event)
- }
-
- /// Reload the page.
- pub fn reload(&mut self) -> Result<(), &'static str> {
- info!("reload");
- let browser_id = self.get_browser_id()?;
- let event = EmbedderEvent::Reload(browser_id);
- self.process_event(event)
- }
-
- /// Redraw the page.
- pub fn refresh(&mut self) -> Result<(), &'static str> {
- info!("refresh");
- self.process_event(EmbedderEvent::Refresh)
- }
-
- /// Stop loading the page.
- pub fn stop(&mut self) -> Result<(), &'static str> {
- warn!("TODO can't stop won't stop");
- Ok(())
- }
-
- /// Go back in history.
- pub fn go_back(&mut self) -> Result<(), &'static str> {
- info!("go_back");
- let browser_id = self.get_browser_id()?;
- let event = EmbedderEvent::Navigation(browser_id, TraversalDirection::Back(1));
- self.process_event(event)
- }
-
- /// Go forward in history.
- pub fn go_forward(&mut self) -> Result<(), &'static str> {
- info!("go_forward");
- let browser_id = self.get_browser_id()?;
- let event = EmbedderEvent::Navigation(browser_id, TraversalDirection::Forward(1));
- self.process_event(event)
- }
-
- /// Let Servo know that the window has been resized.
- pub fn resize(&mut self, coordinates: Coordinates) -> Result<(), &'static str> {
- info!("resize");
- *self.callbacks.coordinates.borrow_mut() = coordinates;
- self.process_event(EmbedderEvent::WindowResize)
- }
-
- /// Start scrolling.
- /// x/y are scroll coordinates.
- /// dx/dy are scroll deltas.
- pub fn scroll_start(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
- let delta = Vector2D::new(dx, dy);
- let scroll_location = ScrollLocation::Delta(delta);
- let event =
- EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Down);
- self.process_event(event)
- }
-
- /// Scroll.
- /// x/y are scroll coordinates.
- /// dx/dy are scroll deltas.
- pub fn scroll(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
- let delta = Vector2D::new(dx, dy);
- let scroll_location = ScrollLocation::Delta(delta);
- let event =
- EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Move);
- self.process_event(event)
- }
-
- /// End scrolling.
- /// x/y are scroll coordinates.
- /// dx/dy are scroll deltas.
- pub fn scroll_end(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
- let delta = Vector2D::new(dx, dy);
- let scroll_location = ScrollLocation::Delta(delta);
- let event = EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Up);
- self.process_event(event)
- }
-
- /// Touch event: press down
- pub fn touch_down(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
- let event = EmbedderEvent::Touch(
- TouchEventType::Down,
- TouchId(pointer_id),
- Point2D::new(x as f32, y as f32),
- );
- self.process_event(event)
- }
-
- /// Touch event: move touching finger
- pub fn touch_move(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
- let event = EmbedderEvent::Touch(
- TouchEventType::Move,
- TouchId(pointer_id),
- Point2D::new(x as f32, y as f32),
- );
- self.process_event(event)
- }
-
- /// Touch event: Lift touching finger
- pub fn touch_up(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
- let event = EmbedderEvent::Touch(
- TouchEventType::Up,
- TouchId(pointer_id),
- Point2D::new(x as f32, y as f32),
- );
- self.process_event(event)
- }
-
- /// Cancel touch event
- pub fn touch_cancel(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
- let event = EmbedderEvent::Touch(
- TouchEventType::Cancel,
- TouchId(pointer_id),
- Point2D::new(x as f32, y as f32),
- );
- self.process_event(event)
- }
-
- /// Register a mouse movement.
- pub fn mouse_move(&mut self, x: f32, y: f32) -> Result<(), &'static str> {
- let point = Point2D::new(x, y);
- let event = EmbedderEvent::MouseWindowMoveEventClass(point);
- self.process_event(event)
- }
-
- /// Register a mouse button press.
- pub fn mouse_down(&mut self, x: f32, y: f32, button: MouseButton) -> Result<(), &'static str> {
- let point = Point2D::new(x, y);
- let event =
- EmbedderEvent::MouseWindowEventClass(MouseWindowEvent::MouseDown(button, point));
- self.process_event(event)
- }
-
- /// Register a mouse button release.
- pub fn mouse_up(&mut self, x: f32, y: f32, button: MouseButton) -> Result<(), &'static str> {
- let point = Point2D::new(x, y);
- let event = EmbedderEvent::MouseWindowEventClass(MouseWindowEvent::MouseUp(button, point));
- self.process_event(event)
- }
-
- /// Start pinchzoom.
- /// x/y are pinch origin coordinates.
- pub fn pinchzoom_start(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
- self.process_event(EmbedderEvent::PinchZoom(factor))
- }
-
- /// Pinchzoom.
- /// x/y are pinch origin coordinates.
- pub fn pinchzoom(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
- self.process_event(EmbedderEvent::PinchZoom(factor))
- }
-
- /// End pinchzoom.
- /// x/y are pinch origin coordinates.
- pub fn pinchzoom_end(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
- self.process_event(EmbedderEvent::PinchZoom(factor))
- }
-
- /// Perform a click.
- pub fn click(&mut self, x: f32, y: f32) -> Result<(), &'static str> {
- let mouse_event = MouseWindowEvent::Click(MouseButton::Left, Point2D::new(x, y));
- let event = EmbedderEvent::MouseWindowEventClass(mouse_event);
- self.process_event(event)
- }
-
- pub fn key_down(&mut self, key: Key) -> Result<(), &'static str> {
- let key_event = KeyboardEvent {
- state: KeyState::Down,
- key,
- ..KeyboardEvent::default()
- };
- self.process_event(EmbedderEvent::Keyboard(key_event))
- }
-
- pub fn key_up(&mut self, key: Key) -> Result<(), &'static str> {
- let key_event = KeyboardEvent {
- state: KeyState::Up,
- key,
- ..KeyboardEvent::default()
- };
- self.process_event(EmbedderEvent::Keyboard(key_event))
- }
-
- pub fn pause_compositor(&mut self) -> Result<(), &'static str> {
- self.process_event(EmbedderEvent::InvalidateNativeSurface)
- }
-
- pub fn resume_compositor(
- &mut self,
- native_surface: *mut c_void,
- coords: Coordinates,
- ) -> Result<(), &'static str> {
- if native_surface.is_null() {
- panic!("null passed for native_surface");
- }
- self.process_event(EmbedderEvent::ReplaceNativeSurface(
- native_surface,
- coords.framebuffer,
- ))
- }
-
- pub fn media_session_action(
- &mut self,
- action: MediaSessionActionType,
- ) -> Result<(), &'static str> {
- info!("Media session action {:?}", action);
- self.process_event(EmbedderEvent::MediaSessionAction(action))
- }
-
- pub fn set_throttled(&mut self, throttled: bool) -> Result<(), &'static str> {
- info!("set_throttled");
- if let Ok(id) = self.get_browser_id() {
- let event = EmbedderEvent::SetWebViewThrottled(id, throttled);
- self.process_event(event)
- } else {
- // Ignore visibility change if no browser has been created yet.
- Ok(())
- }
- }
-
- pub fn ime_dismissed(&mut self) -> Result<(), &'static str> {
- info!("ime_dismissed");
- self.process_event(EmbedderEvent::IMEDismissed)
- }
-
- pub fn on_context_menu_closed(
- &mut self,
- result: ContextMenuResult,
- ) -> Result<(), &'static str> {
- if let Some(sender) = self.context_menu_sender.take() {
- let _ = sender.send(result);
- } else {
- warn!("Trying to close a context menu when no context menu is active");
- }
- Ok(())
- }
-
- fn process_event(&mut self, event: EmbedderEvent) -> Result<(), &'static str> {
- self.events.push(event);
- if !self.batch_mode {
- self.perform_updates()
- } else {
- Ok(())
- }
- }
-
- fn handle_servo_events(&mut self) -> Result<(), &'static str> {
- let mut need_update = false;
- let mut need_present = false;
- for (browser_id, event) in self.servo.get_events() {
- match event {
- EmbedderMsg::ChangePageTitle(title) => {
- self.callbacks.host_callbacks.on_title_changed(title);
- },
- EmbedderMsg::AllowNavigationRequest(pipeline_id, url) => {
- if let Some(_browser_id) = browser_id {
- let data: bool = self
- .callbacks
- .host_callbacks
- .on_allow_navigation(url.to_string());
- let window_event =
- EmbedderEvent::AllowNavigationResponse(pipeline_id, data);
- self.events.push(window_event);
- need_update = true;
- }
- },
- EmbedderMsg::HistoryChanged(entries, current) => {
- let can_go_back = current > 0;
- let can_go_forward = current < entries.len() - 1;
- self.callbacks
- .host_callbacks
- .on_history_changed(can_go_back, can_go_forward);
- self.callbacks
- .host_callbacks
- .on_url_changed(entries[current].clone().to_string());
- },
- EmbedderMsg::LoadStart => {
- self.callbacks.host_callbacks.on_load_started();
- },
- EmbedderMsg::LoadComplete => {
- self.callbacks.host_callbacks.on_load_ended();
- },
- EmbedderMsg::GetSelectedBluetoothDevice(_, sender) => {
- let _ = sender.send(None);
- },
- EmbedderMsg::AllowUnload(sender) => {
- let _ = sender.send(true);
- },
- EmbedderMsg::ShowContextMenu(sender, title, items) => {
- if self.context_menu_sender.is_some() {
- warn!(
- "Trying to show a context menu when a context menu is already active"
- );
- let _ = sender.send(ContextMenuResult::Ignored);
- } else {
- self.context_menu_sender = Some(sender);
- self.callbacks
- .host_callbacks
- .show_context_menu(title, items);
- }
- },
- EmbedderMsg::Prompt(definition, origin) => {
- let cb = &self.callbacks.host_callbacks;
- let trusted = origin == PromptOrigin::Trusted;
- let res = match definition {
- PromptDefinition::Alert(message, sender) => {
- sender.send(cb.prompt_alert(message, trusted))
- },
- PromptDefinition::OkCancel(message, sender) => {
- sender.send(cb.prompt_ok_cancel(message, trusted))
- },
- PromptDefinition::YesNo(message, sender) => {
- sender.send(cb.prompt_yes_no(message, trusted))
- },
- PromptDefinition::Input(message, default, sender) => {
- sender.send(cb.prompt_input(message, default, trusted))
- },
- };
- if let Err(e) = res {
- let reason = format!("Failed to send Prompt response: {}", e);
- self.events
- .push(EmbedderEvent::SendError(browser_id, reason));
- }
- },
- EmbedderMsg::AllowOpeningWebView(response_chan) => {
- // Note: would be a place to handle pop-ups config.
- // see Step 7 of #the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
- if let Err(e) = response_chan.send(true) {
- warn!("Failed to send AllowOpeningBrowser response: {}", e);
- };
- },
- EmbedderMsg::WebViewOpened(new_webview_id) => {
- self.webviews.insert(new_webview_id, WebView {});
- self.creation_order.push(new_webview_id);
- self.events
- .push(EmbedderEvent::FocusWebView(new_webview_id));
- },
- EmbedderMsg::WebViewClosed(webview_id) => {
- self.webviews.retain(|&id, _| id != webview_id);
- self.creation_order.retain(|&id| id != webview_id);
- self.focused_webview_id = None;
- if let Some(&newest_webview_id) = self.creation_order.last() {
- self.events
- .push(EmbedderEvent::FocusWebView(newest_webview_id));
- } else {
- self.events.push(EmbedderEvent::Quit);
- }
- },
- EmbedderMsg::WebViewFocused(webview_id) => {
- self.focused_webview_id = Some(webview_id);
- },
- EmbedderMsg::WebViewBlurred => {
- self.focused_webview_id = None;
- },
- EmbedderMsg::GetClipboardContents(sender) => {
- let contents = self.callbacks.host_callbacks.get_clipboard_contents();
- let _ = sender.send(contents.unwrap_or("".to_owned()));
- },
- EmbedderMsg::SetClipboardContents(text) => {
- self.callbacks.host_callbacks.set_clipboard_contents(text);
- },
- EmbedderMsg::Shutdown => {
- self.callbacks.host_callbacks.on_shutdown_complete();
- },
- EmbedderMsg::PromptPermission(prompt, sender) => {
- let message = match prompt {
- PermissionPrompt::Request(permission_name) => {
- format!("Do you want to grant permission for {:?}?", permission_name)
- },
- PermissionPrompt::Insecure(permission_name) => {
- format!(
- "The {:?} feature is only safe to use in secure context, but servo can't guarantee\n\
- that the current context is secure. Do you want to proceed and grant permission?",
- permission_name
- )
- },
- };
-
- let result = match self.callbacks.host_callbacks.prompt_yes_no(message, true) {
- PromptResult::Primary => PermissionRequest::Granted,
- PromptResult::Secondary | PromptResult::Dismissed => {
- PermissionRequest::Denied
- },
- };
-
- let _ = sender.send(result);
- },
- EmbedderMsg::ShowIME(kind, text, multiline, bounds) => {
- self.callbacks
- .host_callbacks
- .on_ime_show(kind, text, multiline, bounds);
- },
- EmbedderMsg::HideIME => {
- self.callbacks.host_callbacks.on_ime_hide();
- },
- EmbedderMsg::MediaSessionEvent(event) => {
- match event {
- MediaSessionEvent::SetMetadata(metadata) => {
- self.callbacks.host_callbacks.on_media_session_metadata(
- metadata.title,
- metadata.artist,
- metadata.album,
- )
- },
- MediaSessionEvent::PlaybackStateChange(state) => self
- .callbacks
- .host_callbacks
- .on_media_session_playback_state_change(state),
- MediaSessionEvent::SetPositionState(position_state) => self
- .callbacks
- .host_callbacks
- .on_media_session_set_position_state(
- position_state.duration,
- position_state.position,
- position_state.playback_rate,
- ),
- };
- },
- EmbedderMsg::OnDevtoolsStarted(port, token) => {
- self.callbacks
- .host_callbacks
- .on_devtools_started(port, token);
- },
- EmbedderMsg::Panic(reason, backtrace) => {
- self.callbacks.host_callbacks.on_panic(reason, backtrace);
- },
- EmbedderMsg::ReadyToPresent(_webview_ids) => {
- need_present = true;
- },
- EmbedderMsg::Status(..) |
- EmbedderMsg::SelectFiles(..) |
- EmbedderMsg::MoveTo(..) |
- EmbedderMsg::ResizeTo(..) |
- EmbedderMsg::Keyboard(..) |
- EmbedderMsg::SetCursor(..) |
- EmbedderMsg::NewFavicon(..) |
- EmbedderMsg::HeadParsed |
- EmbedderMsg::SetFullscreenState(..) |
- EmbedderMsg::ReportProfile(..) |
- EmbedderMsg::EventDelivered(..) => {},
- }
- }
-
- if need_update {
- let _ = self.perform_updates();
- }
- if need_present {
- self.servo.present();
- }
- Ok(())
- }
-}
-
-struct ServoEmbedderCallbacks {
- waker: Box<dyn EventLoopWaker>,
- xr_discovery: Option<webxr::Discovery>,
- #[allow(unused)]
- gl: Rc<dyn gl::Gl>,
-}
-
-struct ServoWindowCallbacks {
- host_callbacks: Box<dyn HostTrait>,
- coordinates: RefCell<Coordinates>,
- density: f32,
- rendering_context: RenderingContext,
-}
-
-impl EmbedderMethods for ServoEmbedderCallbacks {
- fn register_webxr(
- &mut self,
- registry: &mut webxr::MainThreadRegistry,
- _embedder_proxy: EmbedderProxy,
- ) {
- debug!("EmbedderMethods::register_xr");
- if let Some(discovery) = self.xr_discovery.take() {
- registry.register(discovery);
- }
- }
-
- fn create_event_loop_waker(&mut self) -> Box<dyn EventLoopWaker> {
- debug!("EmbedderMethods::create_event_loop_waker");
- self.waker.clone()
- }
-}
-
-impl WindowMethods for ServoWindowCallbacks {
- fn rendering_context(&self) -> RenderingContext {
- self.rendering_context.clone()
- }
-
- fn set_animation_state(&self, state: AnimationState) {
- debug!("WindowMethods::set_animation_state: {:?}", state);
- self.host_callbacks
- .on_animating_changed(state == AnimationState::Animating);
- }
-
- fn get_coordinates(&self) -> EmbedderCoordinates {
- let coords = self.coordinates.borrow();
- EmbedderCoordinates {
- viewport: coords.viewport.to_box2d(),
- framebuffer: coords.framebuffer,
- window: (coords.viewport.size, Point2D::new(0, 0)),
- screen: coords.viewport.size,
- screen_avail: coords.viewport.size,
- hidpi_factor: Scale::new(self.density),
- }
- }
-}
-
-struct ResourceReaderInstance;
-
-impl ResourceReaderInstance {
- fn new() -> ResourceReaderInstance {
- ResourceReaderInstance
- }
-}
-
-impl ResourceReaderMethods for ResourceReaderInstance {
- fn read(&self, res: Resource) -> Vec<u8> {
- Vec::from(match res {
- Resource::Preferences => &include_bytes!("../../../../resources/prefs.json")[..],
- Resource::HstsPreloadList => {
- &include_bytes!("../../../../resources/hsts_preload.json")[..]
- },
- Resource::BadCertHTML => &include_bytes!("../../../../resources/badcert.html")[..],
- Resource::NetErrorHTML => &include_bytes!("../../../../resources/neterror.html")[..],
- Resource::UserAgentCSS => &include_bytes!("../../../../resources/user-agent.css")[..],
- Resource::ServoCSS => &include_bytes!("../../../../resources/servo.css")[..],
- Resource::PresentationalHintsCSS => {
- &include_bytes!("../../../../resources/presentational-hints.css")[..]
- },
- Resource::QuirksModeCSS => &include_bytes!("../../../../resources/quirks-mode.css")[..],
- Resource::RippyPNG => &include_bytes!("../../../../resources/rippy.png")[..],
- Resource::DomainList => &include_bytes!("../../../../resources/public_domains.txt")[..],
- Resource::BluetoothBlocklist => {
- &include_bytes!("../../../../resources/gatt_blocklist.txt")[..]
- },
- Resource::MediaControlsCSS => {
- &include_bytes!("../../../../resources/media-controls.css")[..]
- },
- Resource::MediaControlsJS => {
- &include_bytes!("../../../../resources/media-controls.js")[..]
- },
- Resource::CrashHTML => &include_bytes!("../../../../resources/crash.html")[..],
- Resource::DirectoryListingHTML => {
- &include_bytes!("../../../../resources/directory-listing.html")[..]
- },
- })
- }
-
- fn sandbox_access_files(&self) -> Vec<PathBuf> {
- vec![]
- }
-
- fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
- vec![]
- }
-}
diff --git a/ports/servoshell/egl/gl_glue.rs b/ports/servoshell/egl/gl_glue.rs
index 78a44592c7a..e0c9a3c564c 100644
--- a/ports/servoshell/egl/gl_glue.rs
+++ b/ports/servoshell/egl/gl_glue.rs
@@ -2,6 +2,7 @@
* 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(non_camel_case_types)]
+#![allow(unused_imports)]
pub type ServoGl = std::rc::Rc<dyn servo::gl::Gl>;
diff --git a/ports/servoshell/egl/host_trait.rs b/ports/servoshell/egl/host_trait.rs
new file mode 100644
index 00000000000..361d431d237
--- /dev/null
+++ b/ports/servoshell/egl/host_trait.rs
@@ -0,0 +1,75 @@
+/* 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/. */
+
+use servo::embedder_traits::{InputMethodType, MediaSessionPlaybackState, PromptResult};
+use servo::webrender_api::units::DeviceIntRect;
+
+/// Callbacks. Implemented by embedder. Called by Servo.
+pub trait HostTrait {
+ /// Show alert.
+ fn prompt_alert(&self, msg: String, trusted: bool);
+ /// Ask Yes/No question.
+ fn prompt_yes_no(&self, msg: String, trusted: bool) -> PromptResult;
+ /// Ask Ok/Cancel question.
+ fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult;
+ /// Ask for string
+ fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option<String>;
+ /// Show context menu
+ fn show_context_menu(&self, title: Option<String>, items: Vec<String>);
+ /// Page starts loading.
+ /// "Reload button" should be disabled.
+ /// "Stop button" should be enabled.
+ /// Throbber starts spinning.
+ fn on_load_started(&self);
+ /// Page has loaded.
+ /// "Reload button" should be enabled.
+ /// "Stop button" should be disabled.
+ /// Throbber stops spinning.
+ fn on_load_ended(&self);
+ /// Page title has changed.
+ fn on_title_changed(&self, title: Option<String>);
+ /// Allow Navigation.
+ fn on_allow_navigation(&self, url: String) -> bool;
+ /// Page URL has changed.
+ fn on_url_changed(&self, url: String);
+ /// Back/forward state has changed.
+ /// Back/forward buttons need to be disabled/enabled.
+ fn on_history_changed(&self, can_go_back: bool, can_go_forward: bool);
+ /// Page animation state has changed. If animating, it's recommended
+ /// that the embedder doesn't wait for the wake function to be called
+ /// to call perform_updates. Usually, it means doing:
+ /// while true { servo.perform_updates() }. This will end up calling flush
+ /// which will call swap_buffer which will be blocking long enough to limit
+ /// drawing at 60 FPS.
+ /// If not animating, call perform_updates only when needed (when the embedder
+ /// has events for Servo, or Servo has woken up the embedder event loop via
+ /// EventLoopWaker).
+ fn on_animating_changed(&self, animating: bool);
+ /// Servo finished shutting down.
+ fn on_shutdown_complete(&self);
+ /// A text input is focused.
+ fn on_ime_show(
+ &self,
+ input_type: InputMethodType,
+ text: Option<(String, i32)>,
+ multiline: bool,
+ bounds: DeviceIntRect,
+ );
+ /// Input lost focus
+ fn on_ime_hide(&self);
+ /// Gets sytem clipboard contents.
+ fn get_clipboard_contents(&self) -> Option<String>;
+ /// Sets system clipboard contents.
+ fn set_clipboard_contents(&self, contents: String);
+ /// Called when we get the media session metadata/
+ fn on_media_session_metadata(&self, title: String, artist: String, album: String);
+ /// Called when the media session playback state changes.
+ fn on_media_session_playback_state_change(&self, state: MediaSessionPlaybackState);
+ /// Called when the media session position state is set.
+ fn on_media_session_set_position_state(&self, duration: f64, position: f64, playback_rate: f64);
+ /// Called when devtools server is started
+ fn on_devtools_started(&self, port: Result<u16, ()>, token: String);
+ /// Called when we get a panic message from constellation
+ fn on_panic(&self, reason: String, backtrace: Option<String>);
+}
diff --git a/ports/servoshell/egl/mod.rs b/ports/servoshell/egl/mod.rs
index 80c34a5bbb9..23c856d2dee 100644
--- a/ports/servoshell/egl/mod.rs
+++ b/ports/servoshell/egl/mod.rs
@@ -2,8 +2,15 @@
* 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/. */
-#[cfg(target_os = "android")]
+#[cfg(any(target_os = "android", target_env = "ohos"))]
pub mod gl_glue;
#[cfg(target_os = "android")]
mod android;
+
+#[cfg(target_env = "ohos")]
+mod ohos;
+
+mod host_trait;
+mod resources;
+mod servo_glue;
diff --git a/ports/servoshell/egl/ohos.rs b/ports/servoshell/egl/ohos.rs
new file mode 100644
index 00000000000..3b7caa5c633
--- /dev/null
+++ b/ports/servoshell/egl/ohos.rs
@@ -0,0 +1,619 @@
+/* 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(non_snake_case)]
+
+use std::mem::MaybeUninit;
+use std::os::raw::c_void;
+use std::sync::mpsc::{Receiver, Sender};
+use std::sync::{mpsc, Once, OnceLock};
+use std::thread;
+use std::thread::sleep;
+use std::time::Duration;
+
+use log::{debug, error, info, warn, LevelFilter};
+use napi_derive_ohos::{module_exports, napi};
+use napi_ohos::bindgen_prelude::Undefined;
+use napi_ohos::threadsafe_function::{
+ ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode,
+};
+use napi_ohos::{Env, JsFunction, JsObject, JsString, NapiRaw};
+use ohos_sys::ace::xcomponent::native_interface_xcomponent::{
+ OH_NativeXComponent, OH_NativeXComponent_Callback, OH_NativeXComponent_GetTouchEvent,
+ OH_NativeXComponent_TouchEvent, OH_NativeXComponent_TouchEventType,
+};
+use servo::embedder_traits::PromptResult;
+use servo::euclid::Point2D;
+use servo::style::Zero;
+use simpleservo::EventLoopWaker;
+
+use super::gl_glue;
+use super::host_trait::HostTrait;
+use super::servo_glue::ServoGlue;
+
+mod simpleservo;
+
+// Todo: in the future these libraries should be added by Rust sys-crates
+#[link(name = "ace_napi.z")]
+#[link(name = "ace_ndk.z")]
+#[link(name = "hilog_ndk.z")]
+#[link(name = "native_window")]
+#[link(name = "clang_rt.builtins", kind = "static")]
+extern "C" {}
+
+#[napi(object)]
+#[derive(Debug)]
+pub struct InitOpts {
+ pub url: String,
+ pub device_type: String,
+ pub os_full_name: String,
+ pub display_density: f64,
+}
+
+#[derive(Debug)]
+enum CallError {
+ ChannelNotInitialized,
+ ChannelDied,
+}
+
+fn call(action: ServoAction) -> Result<(), CallError> {
+ let tx = SERVO_CHANNEL
+ .get()
+ .ok_or(CallError::ChannelNotInitialized)?;
+ tx.send(action).map_err(|_| CallError::ChannelDied)?;
+ Ok(())
+}
+
+#[repr(transparent)]
+struct XComponentWrapper(*mut OH_NativeXComponent);
+#[repr(transparent)]
+struct WindowWrapper(*mut c_void);
+unsafe impl Send for XComponentWrapper {}
+unsafe impl Send for WindowWrapper {}
+
+#[derive(Clone, Copy, Debug)]
+enum TouchEventType {
+ Down,
+ Up,
+ Move,
+ Scroll { dx: f32, dy: f32 },
+ Cancel,
+ Unknown,
+}
+
+#[derive(Debug)]
+enum ServoAction {
+ WakeUp,
+ LoadUrl(String),
+ TouchEvent {
+ kind: TouchEventType,
+ x: f32,
+ y: f32,
+ pointer_id: i32,
+ },
+ Initialize(Box<InitOpts>),
+}
+
+#[derive(Clone, Copy, Debug, Default)]
+enum Direction2D {
+ Horizontal,
+ Vertical,
+ #[default]
+ Free,
+}
+#[derive(Clone, Debug)]
+struct TouchTracker {
+ last_position: Point2D<f32, f32>,
+}
+
+impl TouchTracker {
+ fn new(first_point: Point2D<f32, f32>) -> Self {
+ TouchTracker {
+ last_position: first_point,
+ }
+ }
+}
+
+// Todo: Need to check if OnceLock is suitable, or if the TS function can be destroyed, e.g.
+// if the activity gets suspended.
+static SET_URL_BAR_CB: OnceLock<ThreadsafeFunction<String, ErrorStrategy::Fatal>> = OnceLock::new();
+
+struct TsThreadState {
+ // last_touch_event: Option<OH_NativeXComponent_TouchEvent>,
+ velocity_tracker: Option<TouchTracker>,
+}
+
+impl TsThreadState {
+ const fn new() -> Self {
+ Self {
+ velocity_tracker: None,
+ }
+ }
+}
+
+static mut TS_THREAD_STATE: TsThreadState = TsThreadState::new();
+
+impl ServoAction {
+ fn dispatch_touch_event(
+ servo: &mut ServoGlue,
+ kind: TouchEventType,
+ x: f32,
+ y: f32,
+ pointer_id: i32,
+ ) -> Result<(), &'static str> {
+ match kind {
+ TouchEventType::Down => servo.touch_down(x, y, pointer_id),
+ TouchEventType::Up => servo.touch_up(x, y, pointer_id),
+ TouchEventType::Scroll { dx, dy } => servo.scroll(dx, dy, x as i32, y as i32),
+ TouchEventType::Move => servo.touch_move(x, y, pointer_id),
+ TouchEventType::Cancel => servo.touch_cancel(x, y, pointer_id),
+ TouchEventType::Unknown => Err("Can't dispatch Unknown Touch Event"),
+ }
+ }
+
+ fn do_action(&self, servo: &mut ServoGlue) {
+ use ServoAction::*;
+ let res = match self {
+ WakeUp => servo.perform_updates(),
+ LoadUrl(url) => servo.load_uri(url.as_str()),
+ TouchEvent {
+ kind,
+ x,
+ y,
+ pointer_id,
+ } => Self::dispatch_touch_event(servo, *kind, *x, *y, *pointer_id),
+ Initialize(_init_opts) => {
+ panic!("Received Initialize event, even though servo is already initialized")
+ },
+ };
+ if let Err(e) = res {
+ error!("Failed to do {self:?} with error {e}");
+ }
+ }
+}
+
+static SERVO_CHANNEL: OnceLock<Sender<ServoAction>> = OnceLock::new();
+
+#[no_mangle]
+pub extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window: *mut c_void) {
+ info!("on_surface_created_cb");
+
+ let xc_wrapper = XComponentWrapper(xcomponent);
+ let window_wrapper = WindowWrapper(window);
+
+ // Each thread will send its id via the channel
+ let _main_surface_thread = thread::spawn(move || {
+ let (tx, rx): (Sender<ServoAction>, Receiver<ServoAction>) = mpsc::channel();
+
+ SERVO_CHANNEL
+ .set(tx.clone())
+ .expect("Servo channel already initialized");
+
+ let wakeup = Box::new(WakeupCallback::new(tx));
+ let callbacks = Box::new(HostCallbacks::new());
+
+ let egl_init = gl_glue::init().expect("egl::init() failed");
+ let xc = xc_wrapper;
+ let window = window_wrapper;
+ let init_opts = if let Ok(ServoAction::Initialize(init_opts)) = rx.recv() {
+ init_opts
+ } else {
+ panic!("Servos GL thread received another event before it was initialized")
+ };
+ let mut servo = simpleservo::init(
+ *init_opts,
+ window.0,
+ xc.0,
+ egl_init.gl_wrapper,
+ wakeup,
+ callbacks,
+ )
+ .expect("Servo initialization failed");
+
+ info!("Surface created!");
+
+ while let Ok(action) = rx.recv() {
+ info!("Wakeup message received!");
+ action.do_action(&mut servo);
+ }
+
+ info!("Sender disconnected - Terminating main surface thread");
+ });
+
+ info!("Returning from on_surface_created_cb");
+}
+
+// Todo: Probably we need to block here, until the main thread has processed the change.
+pub extern "C" fn on_surface_changed_cb(
+ _component: *mut OH_NativeXComponent,
+ _window: *mut c_void,
+) {
+ error!("on_surface_changed_cb is currently not implemented!");
+}
+
+pub extern "C" fn on_surface_destroyed_cb(
+ _component: *mut OH_NativeXComponent,
+ _window: *mut c_void,
+) {
+ error!("on_surface_destroyed_cb is currently not implemented");
+}
+
+pub extern "C" fn on_dispatch_touch_event_cb(
+ component: *mut OH_NativeXComponent,
+ window: *mut c_void,
+) {
+ info!("DispatchTouchEvent");
+ let mut touch_event: MaybeUninit<OH_NativeXComponent_TouchEvent> = MaybeUninit::uninit();
+ let res =
+ unsafe { OH_NativeXComponent_GetTouchEvent(component, window, touch_event.as_mut_ptr()) };
+ if res != 0 {
+ error!("OH_NativeXComponent_GetTouchEvent failed with {res}");
+ return;
+ }
+ let touch_event = unsafe { touch_event.assume_init() };
+ let kind: TouchEventType =
+ match touch_event.type_ {
+ OH_NativeXComponent_TouchEventType::OH_NATIVEXCOMPONENT_DOWN => {
+ if touch_event.id == 0 {
+ unsafe {
+ let old = TS_THREAD_STATE.velocity_tracker.replace(TouchTracker::new(
+ Point2D::new(touch_event.x, touch_event.y),
+ ));
+ assert!(old.is_none());
+ }
+ }
+ TouchEventType::Down
+ },
+ OH_NativeXComponent_TouchEventType::OH_NATIVEXCOMPONENT_UP => {
+ if touch_event.id == 0 {
+ unsafe {
+ let old = TS_THREAD_STATE.velocity_tracker.take();
+ assert!(old.is_some());
+ }
+ }
+ TouchEventType::Up
+ },
+ OH_NativeXComponent_TouchEventType::OH_NATIVEXCOMPONENT_MOVE => {
+ // SAFETY: We only access TS_THREAD_STATE from the main TS thread.
+ if touch_event.id == 0 {
+ let (lastX, lastY) = unsafe {
+ if let Some(last_event) = &mut TS_THREAD_STATE.velocity_tracker {
+ let touch_point = last_event.last_position;
+ last_event.last_position = Point2D::new(touch_event.x, touch_event.y);
+ (touch_point.x, touch_point.y)
+ } else {
+ error!("Move Event received, but no previous touch event was stored!");
+ // todo: handle this error case
+ panic!("Move Event received, but no previous touch event was stored!");
+ }
+ };
+ let dx = touch_event.x - lastX;
+ let dy = touch_event.y - lastY;
+ TouchEventType::Scroll { dx, dy }
+ } else {
+ TouchEventType::Move
+ }
+ },
+ OH_NativeXComponent_TouchEventType::OH_NATIVEXCOMPONENT_CANCEL => {
+ if touch_event.id == 0 {
+ unsafe {
+ let old = TS_THREAD_STATE.velocity_tracker.take();
+ assert!(old.is_some());
+ }
+ }
+ TouchEventType::Cancel
+ },
+ _ => {
+ error!(
+ "Failed to dispatch call for touch Event {:?}",
+ touch_event.type_
+ );
+ TouchEventType::Unknown
+ },
+ };
+ if let Err(e) = call(ServoAction::TouchEvent {
+ kind,
+ x: touch_event.x,
+ y: touch_event.y,
+ pointer_id: touch_event.id,
+ }) {
+ error!("Failed to dispatch call for touch Event {kind:?}: {e:?}");
+ }
+}
+
+fn initialize_logging_once() {
+ static ONCE: Once = Once::new();
+ ONCE.call_once(|| {
+ let mut builder = hilog::Builder::new();
+ builder.filter_level(LevelFilter::Debug).init();
+
+ info!("Servo Register callback called!");
+
+ std::panic::set_hook(Box::new(|info| {
+ error!("Panic in Rust code");
+ error!("PanicInfo: {info}");
+ let msg = match info.payload().downcast_ref::<&'static str>() {
+ Some(s) => *s,
+ None => match info.payload().downcast_ref::<String>() {
+ Some(s) => &**s,
+ None => "Box<Any>",
+ },
+ };
+ let current_thread = thread::current();
+ let name = current_thread.name().unwrap_or("<unnamed>");
+ if let Some(location) = info.location() {
+ let _ = error!(
+ "{} (thread {}, at {}:{})",
+ msg,
+ name,
+ location.file(),
+ location.line()
+ );
+ } else {
+ let _ = error!("{} (thread {})", msg, name);
+ }
+
+ let _ = crate::backtrace::print_ohos();
+ }));
+ })
+}
+
+fn register_xcomponent_callbacks(env: &Env, xcomponent: &JsObject) -> napi_ohos::Result<()> {
+ info!("napi_get_named_property call successfull");
+ let raw = unsafe { xcomponent.raw() };
+ let raw_env = env.raw();
+ let mut nativeXComponent: *mut OH_NativeXComponent = core::ptr::null_mut();
+ unsafe {
+ let res = napi_ohos::sys::napi_unwrap(
+ raw_env,
+ raw,
+ &mut nativeXComponent as *mut *mut OH_NativeXComponent as *mut *mut c_void,
+ );
+ assert!(res.is_zero());
+ }
+ info!("Got nativeXComponent!");
+ let cbs = Box::new(OH_NativeXComponent_Callback {
+ OnSurfaceCreated: Some(on_surface_created_cb),
+ OnSurfaceChanged: Some(on_surface_changed_cb),
+ OnSurfaceDestroyed: Some(on_surface_destroyed_cb),
+ DispatchTouchEvent: Some(on_dispatch_touch_event_cb),
+ });
+ use ohos_sys::ace::xcomponent::native_interface_xcomponent::OH_NativeXComponent_RegisterCallback;
+ let res =
+ unsafe { OH_NativeXComponent_RegisterCallback(nativeXComponent, Box::leak(cbs) as *mut _) };
+ if res != 0 {
+ error!("Failed to register callbacks");
+ } else {
+ info!("Registerd callbacks successfully");
+ }
+ Ok(())
+}
+
+#[allow(unused)]
+fn debug_jsobject(obj: &JsObject, obj_name: &str) -> napi_ohos::Result<()> {
+ let names = obj.get_property_names()?;
+ error!("Getting property names of object {obj_name}");
+ let len = names.get_array_length()?;
+ error!("{obj_name} has {len} elements");
+ for i in 0..len {
+ let name: JsString = names.get_element(i)?;
+ let name = name.into_utf8()?;
+ error!("{obj_name} property {i}: {}", name.as_str()?)
+ }
+ Ok(())
+}
+
+#[module_exports]
+fn init(exports: JsObject, env: Env) -> napi_ohos::Result<()> {
+ initialize_logging_once();
+ info!("simpleservo init function called");
+ if let Ok(xcomponent) = exports.get_named_property::<JsObject>("__NATIVE_XCOMPONENT_OBJ__") {
+ register_xcomponent_callbacks(&env, &xcomponent)?;
+ }
+
+ info!("Finished init");
+ Ok(())
+}
+
+#[napi(js_name = "loadURL")]
+pub fn load_url(url: String) -> Undefined {
+ debug!("load url");
+ call(ServoAction::LoadUrl(url)).expect("Failed to load url");
+}
+
+#[napi(js_name = "registerURLcallback")]
+pub fn register_url_callback(cb: JsFunction) -> napi_ohos::Result<()> {
+ info!("register_url_callback called!");
+ let tsfn: ThreadsafeFunction<String, ErrorStrategy::Fatal> =
+ cb.create_threadsafe_function(1, |ctx| {
+ debug!(
+ "url callback argument transformer called with arg {}",
+ ctx.value
+ );
+ let s = ctx
+ .env
+ .create_string_from_std(ctx.value)
+ .inspect_err(|e| error!("Failed to create JsString: {e:?}"))?;
+ Ok(vec![s])
+ })?;
+ // We ignore any error for now - but probably we should propagate it back to the TS layer.
+ let _ = SET_URL_BAR_CB
+ .set(tsfn)
+ .inspect_err(|_| warn!("Failed to set URL callback - register_url_callback called twice?"));
+ Ok(())
+}
+
+#[napi]
+pub fn init_servo(init_opts: InitOpts) -> napi_ohos::Result<()> {
+ info!("Servo is being initialised with the following Options: ");
+ info!(
+ "Device Type: {}, DisplayDensity: {}",
+ init_opts.device_type, init_opts.display_density
+ );
+ info!("Initial URL: {}", init_opts.url);
+ let channel = if let Some(channel) = SERVO_CHANNEL.get() {
+ channel
+ } else {
+ warn!("Servo GL thread has not initialized yet. Retrying.....");
+ let mut iter_count = 0;
+ loop {
+ if let Some(channel) = SERVO_CHANNEL.get() {
+ break channel;
+ } else {
+ iter_count += 1;
+ if iter_count > 10 {
+ error!("Servo GL thread not reachable");
+ panic!("Servo GL thread not reachable");
+ }
+ sleep(Duration::from_millis(100));
+ }
+ }
+ };
+ channel
+ .send(ServoAction::Initialize(Box::new(init_opts)))
+ .expect("Failed to connect to servo GL thread");
+ Ok(())
+}
+
+#[derive(Clone)]
+pub struct WakeupCallback {
+ chan: Sender<ServoAction>,
+}
+
+impl WakeupCallback {
+ pub(crate) fn new(chan: Sender<ServoAction>) -> Self {
+ WakeupCallback { chan }
+ }
+}
+
+impl EventLoopWaker for WakeupCallback {
+ fn clone_box(&self) -> Box<dyn EventLoopWaker> {
+ Box::new(self.clone())
+ }
+
+ fn wake(&self) {
+ info!("wake called!");
+ self.chan.send(ServoAction::WakeUp).unwrap_or_else(|e| {
+ error!("Failed to send wake message with: {e}");
+ });
+ }
+}
+
+struct HostCallbacks {}
+
+impl HostCallbacks {
+ pub fn new() -> Self {
+ HostCallbacks {}
+ }
+}
+
+#[allow(unused)]
+impl HostTrait for HostCallbacks {
+ fn prompt_alert(&self, msg: String, trusted: bool) {
+ warn!("prompt_alert not implemented. Cancelled. {}", msg);
+ }
+
+ fn prompt_yes_no(&self, msg: String, trusted: bool) -> PromptResult {
+ warn!("Prompt not implemented. Cancelled. {}", msg);
+ PromptResult::Secondary
+ }
+
+ fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult {
+ warn!("Prompt not implemented. Cancelled. {}", msg);
+ PromptResult::Secondary
+ }
+
+ fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option<String> {
+ warn!("Input prompt not implemented. Cancelled. {}", msg);
+ Some(default)
+ }
+
+ fn show_context_menu(&self, title: Option<String>, items: Vec<String>) {
+ warn!("show_context_menu not implemented")
+ }
+
+ fn on_load_started(&self) {
+ warn!("on_load_started not implemented")
+ }
+
+ fn on_load_ended(&self) {
+ warn!("on_load_ended not implemented")
+ }
+
+ fn on_title_changed(&self, title: Option<String>) {
+ warn!("on_title_changed not implemented")
+ }
+
+ fn on_allow_navigation(&self, url: String) -> bool {
+ true
+ }
+
+ fn on_url_changed(&self, url: String) {
+ debug!("Hosttrait `on_url_changed` called with new url: {url}");
+ if let Some(cb) = SET_URL_BAR_CB.get() {
+ cb.call(url, ThreadsafeFunctionCallMode::Blocking);
+ } else {
+ warn!("`on_url_changed` called without a registered callback")
+ }
+ }
+
+ fn on_history_changed(&self, can_go_back: bool, can_go_forward: bool) {}
+
+ fn on_animating_changed(&self, animating: bool) {}
+
+ fn on_shutdown_complete(&self) {}
+
+ fn on_ime_show(
+ &self,
+ input_type: servo::embedder_traits::InputMethodType,
+ text: Option<(String, i32)>,
+ multiline: bool,
+ bounds: servo::webrender_api::units::DeviceIntRect,
+ ) {
+ warn!("on_title_changed not implemented")
+ }
+
+ fn on_ime_hide(&self) {
+ warn!("on_title_changed not implemented")
+ }
+
+ fn get_clipboard_contents(&self) -> Option<String> {
+ warn!("get_clipboard_contents not implemented");
+ None
+ }
+
+ fn set_clipboard_contents(&self, contents: String) {
+ warn!("set_clipboard_contents not implemented");
+ }
+
+ fn on_media_session_metadata(&self, title: String, artist: String, album: String) {
+ warn!("on_media_session_metadata not implemented");
+ }
+
+ fn on_media_session_playback_state_change(
+ &self,
+ state: servo::embedder_traits::MediaSessionPlaybackState,
+ ) {
+ warn!("on_media_session_playback_state_change not implemented");
+ }
+
+ fn on_media_session_set_position_state(
+ &self,
+ duration: f64,
+ position: f64,
+ playback_rate: f64,
+ ) {
+ warn!("on_media_session_set_position_state not implemented");
+ }
+
+ fn on_devtools_started(&self, port: Result<u16, ()>, token: String) {
+ warn!("on_devtools_started not implemented");
+ }
+
+ fn on_panic(&self, reason: String, backtrace: Option<String>) {
+ error!("Panic: {reason},");
+ if let Some(bt) = backtrace {
+ error!("Backtrace: {bt:?}")
+ }
+ }
+}
diff --git a/ports/servoshell/egl/ohos/simpleservo.rs b/ports/servoshell/egl/ohos/simpleservo.rs
new file mode 100644
index 00000000000..4bda080a693
--- /dev/null
+++ b/ports/servoshell/egl/ohos/simpleservo.rs
@@ -0,0 +1,110 @@
+/* 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/. */
+use std::cell::RefCell;
+use std::convert::TryInto;
+use std::os::raw::c_void;
+use std::path::PathBuf;
+use std::rc::Rc;
+
+use log::{debug, error, info};
+use ohos_sys::ace::xcomponent::native_interface_xcomponent::{
+ OH_NativeXComponent, OH_NativeXComponent_GetXComponentSize,
+};
+use servo::compositing::windowing::EmbedderEvent;
+use servo::compositing::CompositeTarget;
+use servo::embedder_traits::resources;
+/// The EventLoopWaker::wake function will be called from any thread.
+/// It will be called to notify embedder that some events are available,
+/// and that perform_updates need to be called
+pub use servo::embedder_traits::EventLoopWaker;
+use servo::euclid::Size2D;
+use servo::servo_url::ServoUrl;
+use servo::webrender_traits::RenderingContext;
+use servo::{self, gl, Servo};
+use surfman::{Connection, SurfaceType};
+
+use crate::egl::host_trait::HostTrait;
+use crate::egl::ohos::InitOpts;
+use crate::egl::resources::ResourceReaderInstance;
+use crate::egl::servo_glue::{
+ Coordinates, ServoEmbedderCallbacks, ServoGlue, ServoWindowCallbacks,
+};
+
+/// Initialize Servo. At that point, we need a valid GL context.
+/// In the future, this will be done in multiple steps.
+pub fn init(
+ options: InitOpts,
+ native_window: *mut c_void,
+ xcomponent: *mut OH_NativeXComponent,
+ gl: Rc<dyn gl::Gl>,
+ waker: Box<dyn EventLoopWaker>,
+ callbacks: Box<dyn HostTrait>,
+) -> Result<ServoGlue, &'static str> {
+ info!("Entered simpleservo init function");
+ resources::set(Box::new(ResourceReaderInstance::new()));
+
+ gl.clear_color(1.0, 1.0, 1.0, 1.0);
+ gl.clear(gl::COLOR_BUFFER_BIT);
+ gl.finish();
+
+ // Initialize surfman
+ let connection = Connection::new().or(Err("Failed to create connection"))?;
+ let adapter = connection
+ .create_adapter()
+ .or(Err("Failed to create adapter"))?;
+
+ let mut width: u64 = 0;
+ let mut height: u64 = 0;
+ let res = unsafe {
+ OH_NativeXComponent_GetXComponentSize(
+ xcomponent,
+ native_window,
+ &mut width as *mut _,
+ &mut height as *mut _,
+ )
+ };
+ assert_eq!(res, 0, "OH_NativeXComponent_GetXComponentSize failed");
+ let width: i32 = width.try_into().expect("Width too large");
+ let height: i32 = height.try_into().expect("Height too large");
+
+ debug!("Creating surfman widget with width {width} and height {height}");
+ let native_widget = unsafe {
+ connection.create_native_widget_from_ptr(native_window, Size2D::new(width, height))
+ };
+ let surface_type = SurfaceType::Widget { native_widget };
+
+ info!("Creating rendering context");
+ let rendering_context = RenderingContext::create(&connection, &adapter, surface_type)
+ .or(Err("Failed to create surface manager"))?;
+
+ info!("before ServoWindowCallbacks...");
+
+ let window_callbacks = Rc::new(ServoWindowCallbacks::new(
+ callbacks,
+ RefCell::new(Coordinates::new(0, 0, width, height, width, height)),
+ options.display_density as f32,
+ rendering_context.clone(),
+ ));
+
+ let embedder_callbacks = Box::new(ServoEmbedderCallbacks::new(waker, None, gl.clone()));
+
+ let servo = Servo::new(
+ embedder_callbacks,
+ window_callbacks.clone(),
+ // User agent: Mozilla/5.0 (<Phone|PC|Tablet>; HarmonyOS 5.0) bla bla
+ None,
+ CompositeTarget::Window,
+ );
+
+ let mut servo_glue = ServoGlue::new(rendering_context, servo.servo, window_callbacks);
+
+ let initial_url = ServoUrl::parse(options.url.as_str())
+ .inspect_err(|e| error!("Invalid initial Servo URL `{}`. Error: {e:?}", options.url))
+ .ok()
+ .unwrap_or_else(|| ServoUrl::parse("about:blank").expect("Infallible"));
+
+ let _ = servo_glue.process_event(EmbedderEvent::NewWebView(initial_url, servo.browser_id));
+
+ Ok(servo_glue)
+}
diff --git a/ports/servoshell/egl/resources.rs b/ports/servoshell/egl/resources.rs
new file mode 100644
index 00000000000..9ada47afdb4
--- /dev/null
+++ b/ports/servoshell/egl/resources.rs
@@ -0,0 +1,56 @@
+/* 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/. */
+use std::path::PathBuf;
+
+use servo::embedder_traits::resources::{Resource, ResourceReaderMethods};
+
+pub(crate) struct ResourceReaderInstance;
+
+impl ResourceReaderInstance {
+ pub(crate) fn new() -> ResourceReaderInstance {
+ ResourceReaderInstance
+ }
+}
+
+impl ResourceReaderMethods for ResourceReaderInstance {
+ fn read(&self, res: Resource) -> Vec<u8> {
+ Vec::from(match res {
+ Resource::Preferences => &include_bytes!("../../../resources/prefs.json")[..],
+ Resource::HstsPreloadList => {
+ &include_bytes!("../../../resources/hsts_preload.json")[..]
+ },
+ Resource::BadCertHTML => &include_bytes!("../../../resources/badcert.html")[..],
+ Resource::NetErrorHTML => &include_bytes!("../../../resources/neterror.html")[..],
+ Resource::UserAgentCSS => &include_bytes!("../../../resources/user-agent.css")[..],
+ Resource::ServoCSS => &include_bytes!("../../../resources/servo.css")[..],
+ Resource::PresentationalHintsCSS => {
+ &include_bytes!("../../../resources/presentational-hints.css")[..]
+ },
+ Resource::QuirksModeCSS => &include_bytes!("../../../resources/quirks-mode.css")[..],
+ Resource::RippyPNG => &include_bytes!("../../../resources/rippy.png")[..],
+ Resource::DomainList => &include_bytes!("../../../resources/public_domains.txt")[..],
+ Resource::BluetoothBlocklist => {
+ &include_bytes!("../../../resources/gatt_blocklist.txt")[..]
+ },
+ Resource::MediaControlsCSS => {
+ &include_bytes!("../../../resources/media-controls.css")[..]
+ },
+ Resource::MediaControlsJS => {
+ &include_bytes!("../../../resources/media-controls.js")[..]
+ },
+ Resource::CrashHTML => &include_bytes!("../../../resources/crash.html")[..],
+ Resource::DirectoryListingHTML => {
+ &include_bytes!("../../../resources/directory-listing.html")[..]
+ },
+ })
+ }
+
+ fn sandbox_access_files(&self) -> Vec<PathBuf> {
+ vec![]
+ }
+
+ fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
+ vec![]
+ }
+}
diff --git a/ports/servoshell/egl/servo_glue.rs b/ports/servoshell/egl/servo_glue.rs
new file mode 100644
index 00000000000..882451a4196
--- /dev/null
+++ b/ports/servoshell/egl/servo_glue.rs
@@ -0,0 +1,701 @@
+/* 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/. */
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::mem;
+use std::os::raw::c_void;
+use std::rc::Rc;
+
+use ipc_channel::ipc::IpcSender;
+use log::{debug, info, warn};
+use servo::base::id::WebViewId;
+use servo::compositing::windowing::{
+ AnimationState, EmbedderCoordinates, EmbedderEvent, EmbedderMethods, MouseWindowEvent,
+ WindowMethods,
+};
+use servo::embedder_traits::{
+ ContextMenuResult, EmbedderMsg, EmbedderProxy, EventLoopWaker, MediaSessionEvent,
+ PermissionPrompt, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult,
+};
+use servo::euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
+use servo::keyboard_types::{Key, KeyState, KeyboardEvent};
+use servo::script_traits::{
+ MediaSessionActionType, MouseButton, TouchEventType, TouchId, TraversalDirection,
+};
+use servo::style_traits::DevicePixel;
+use servo::webrender_api::ScrollLocation;
+use servo::webrender_traits::RenderingContext;
+use servo::{gl, Servo, TopLevelBrowsingContextId};
+
+use crate::egl::host_trait::HostTrait;
+
+#[derive(Clone, Debug)]
+pub struct Coordinates {
+ pub viewport: Rect<i32, DevicePixel>,
+ pub framebuffer: Size2D<i32, DevicePixel>,
+}
+
+impl Coordinates {
+ pub fn new(
+ x: i32,
+ y: i32,
+ width: i32,
+ height: i32,
+ fb_width: i32,
+ fb_height: i32,
+ ) -> Coordinates {
+ Coordinates {
+ viewport: Rect::new(Point2D::new(x, y), Size2D::new(width, height)),
+ framebuffer: Size2D::new(fb_width, fb_height),
+ }
+ }
+}
+
+pub(super) struct ServoWindowCallbacks {
+ host_callbacks: Box<dyn HostTrait>,
+ coordinates: RefCell<Coordinates>,
+ density: f32,
+ rendering_context: RenderingContext,
+}
+
+impl ServoWindowCallbacks {
+ pub(super) fn new(
+ host_callbacks: Box<dyn HostTrait>,
+ coordinates: RefCell<Coordinates>,
+ density: f32,
+ rendering_context: RenderingContext,
+ ) -> Self {
+ Self {
+ host_callbacks,
+ coordinates,
+ density,
+ rendering_context,
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct WebView {}
+
+pub struct ServoGlue {
+ rendering_context: RenderingContext,
+ servo: Servo<ServoWindowCallbacks>,
+ batch_mode: bool,
+ callbacks: Rc<ServoWindowCallbacks>,
+ events: Vec<EmbedderEvent>,
+ context_menu_sender: Option<IpcSender<ContextMenuResult>>,
+
+ /// List of top-level browsing contexts.
+ /// Modified by EmbedderMsg::WebViewOpened and EmbedderMsg::WebViewClosed,
+ /// and we exit if it ever becomes empty.
+ webviews: HashMap<WebViewId, WebView>,
+
+ /// The order in which the webviews were created.
+ creation_order: Vec<WebViewId>,
+
+ /// The webview that is currently focused.
+ /// Modified by EmbedderMsg::WebViewFocused and EmbedderMsg::WebViewBlurred.
+ focused_webview_id: Option<WebViewId>,
+}
+
+#[allow(unused)]
+impl ServoGlue {
+ pub(super) fn new(
+ rendering_context: RenderingContext,
+ servo: Servo<ServoWindowCallbacks>,
+ callbacks: Rc<ServoWindowCallbacks>,
+ ) -> Self {
+ Self {
+ rendering_context,
+ servo,
+ batch_mode: false,
+ callbacks,
+ events: vec![],
+ context_menu_sender: None,
+ webviews: HashMap::default(),
+ creation_order: vec![],
+ focused_webview_id: None,
+ }
+ }
+
+ fn get_browser_id(&self) -> Result<TopLevelBrowsingContextId, &'static str> {
+ let webview_id = match self.focused_webview_id {
+ Some(id) => id,
+ None => return Err("No focused WebViewId yet."),
+ };
+ Ok(webview_id)
+ }
+
+ /// Request shutdown. Will call on_shutdown_complete.
+ pub fn request_shutdown(&mut self) -> Result<(), &'static str> {
+ self.process_event(EmbedderEvent::Quit)
+ }
+
+ /// Call after on_shutdown_complete
+ pub fn deinit(self) {
+ self.servo.deinit();
+ }
+
+ /// Returns the webrender surface management integration interface.
+ /// This provides the embedder access to the current front buffer.
+ pub fn surfman(&self) -> RenderingContext {
+ self.rendering_context.clone()
+ }
+
+ /// This is the Servo heartbeat. This needs to be called
+ /// everytime wakeup is called or when embedder wants Servo
+ /// to act on its pending events.
+ pub fn perform_updates(&mut self) -> Result<(), &'static str> {
+ debug!("perform_updates");
+ let events = mem::replace(&mut self.events, Vec::new());
+ self.servo.handle_events(events);
+ let r = self.handle_servo_events();
+ debug!("done perform_updates");
+ r
+ }
+
+ /// In batch mode, Servo won't call perform_updates automatically.
+ /// This can be useful when the embedder wants to control when Servo
+ /// acts on its pending events. For example, if the embedder wants Servo
+ /// to act on the scroll events only at a certain time, not everytime
+ /// scroll() is called.
+ pub fn set_batch_mode(&mut self, batch: bool) -> Result<(), &'static str> {
+ debug!("set_batch_mode");
+ self.batch_mode = batch;
+ Ok(())
+ }
+
+ /// Load an URL.
+ pub fn load_uri(&mut self, url: &str) -> Result<(), &'static str> {
+ info!("load_uri: {}", url);
+ crate::parser::location_bar_input_to_url(url)
+ .ok_or("Can't parse URL")
+ .and_then(|url| {
+ let browser_id = self.get_browser_id()?;
+ let event = EmbedderEvent::LoadUrl(browser_id, url);
+ self.process_event(event)
+ })
+ }
+
+ /// Reload the page.
+ pub fn clear_cache(&mut self) -> Result<(), &'static str> {
+ info!("clear_cache");
+ let event = EmbedderEvent::ClearCache;
+ self.process_event(event)
+ }
+
+ /// Reload the page.
+ pub fn reload(&mut self) -> Result<(), &'static str> {
+ info!("reload");
+ let browser_id = self.get_browser_id()?;
+ let event = EmbedderEvent::Reload(browser_id);
+ self.process_event(event)
+ }
+
+ /// Redraw the page.
+ pub fn refresh(&mut self) -> Result<(), &'static str> {
+ info!("refresh");
+ self.process_event(EmbedderEvent::Refresh)
+ }
+
+ /// Stop loading the page.
+ pub fn stop(&mut self) -> Result<(), &'static str> {
+ warn!("TODO can't stop won't stop");
+ Ok(())
+ }
+
+ /// Go back in history.
+ pub fn go_back(&mut self) -> Result<(), &'static str> {
+ info!("go_back");
+ let browser_id = self.get_browser_id()?;
+ let event = EmbedderEvent::Navigation(browser_id, TraversalDirection::Back(1));
+ self.process_event(event)
+ }
+
+ /// Go forward in history.
+ pub fn go_forward(&mut self) -> Result<(), &'static str> {
+ info!("go_forward");
+ let browser_id = self.get_browser_id()?;
+ let event = EmbedderEvent::Navigation(browser_id, TraversalDirection::Forward(1));
+ self.process_event(event)
+ }
+
+ /// Let Servo know that the window has been resized.
+ pub fn resize(&mut self, coordinates: Coordinates) -> Result<(), &'static str> {
+ info!("resize");
+ *self.callbacks.coordinates.borrow_mut() = coordinates;
+ self.process_event(EmbedderEvent::WindowResize)
+ }
+
+ /// Start scrolling.
+ /// x/y are scroll coordinates.
+ /// dx/dy are scroll deltas.
+ #[cfg(not(target_env = "ohos"))]
+ pub fn scroll_start(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
+ let delta = Vector2D::new(dx, dy);
+ let scroll_location = ScrollLocation::Delta(delta);
+ let event =
+ EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Down);
+ self.process_event(event)
+ }
+
+ /// Scroll.
+ /// x/y are scroll coordinates.
+ /// dx/dy are scroll deltas.
+ pub fn scroll(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
+ let delta = Vector2D::new(dx, dy);
+ let scroll_location = ScrollLocation::Delta(delta);
+ let event =
+ EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Move);
+ self.process_event(event)
+ }
+
+ /// End scrolling.
+ /// x/y are scroll coordinates.
+ /// dx/dy are scroll deltas.
+ #[cfg(not(target_env = "ohos"))]
+ pub fn scroll_end(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
+ let delta = Vector2D::new(dx, dy);
+ let scroll_location = ScrollLocation::Delta(delta);
+ let event = EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Up);
+ self.process_event(event)
+ }
+
+ /// Touch event: press down
+ pub fn touch_down(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
+ let event = EmbedderEvent::Touch(
+ TouchEventType::Down,
+ TouchId(pointer_id),
+ Point2D::new(x as f32, y as f32),
+ );
+ self.process_event(event)
+ }
+
+ /// Touch event: move touching finger
+ pub fn touch_move(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
+ let event = EmbedderEvent::Touch(
+ TouchEventType::Move,
+ TouchId(pointer_id),
+ Point2D::new(x as f32, y as f32),
+ );
+ self.process_event(event)
+ }
+
+ /// Touch event: Lift touching finger
+ pub fn touch_up(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
+ let event = EmbedderEvent::Touch(
+ TouchEventType::Up,
+ TouchId(pointer_id),
+ Point2D::new(x as f32, y as f32),
+ );
+ self.process_event(event)
+ }
+
+ /// Cancel touch event
+ pub fn touch_cancel(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
+ let event = EmbedderEvent::Touch(
+ TouchEventType::Cancel,
+ TouchId(pointer_id),
+ Point2D::new(x as f32, y as f32),
+ );
+ self.process_event(event)
+ }
+
+ /// Register a mouse movement.
+ pub fn mouse_move(&mut self, x: f32, y: f32) -> Result<(), &'static str> {
+ let point = Point2D::new(x, y);
+ let event = EmbedderEvent::MouseWindowMoveEventClass(point);
+ self.process_event(event)
+ }
+
+ /// Register a mouse button press.
+ pub fn mouse_down(&mut self, x: f32, y: f32, button: MouseButton) -> Result<(), &'static str> {
+ let point = Point2D::new(x, y);
+ let event =
+ EmbedderEvent::MouseWindowEventClass(MouseWindowEvent::MouseDown(button, point));
+ self.process_event(event)
+ }
+
+ /// Register a mouse button release.
+ pub fn mouse_up(&mut self, x: f32, y: f32, button: MouseButton) -> Result<(), &'static str> {
+ let point = Point2D::new(x, y);
+ let event = EmbedderEvent::MouseWindowEventClass(MouseWindowEvent::MouseUp(button, point));
+ self.process_event(event)
+ }
+
+ /// Start pinchzoom.
+ /// x/y are pinch origin coordinates.
+ pub fn pinchzoom_start(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
+ self.process_event(EmbedderEvent::PinchZoom(factor))
+ }
+
+ /// Pinchzoom.
+ /// x/y are pinch origin coordinates.
+ pub fn pinchzoom(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
+ self.process_event(EmbedderEvent::PinchZoom(factor))
+ }
+
+ /// End pinchzoom.
+ /// x/y are pinch origin coordinates.
+ pub fn pinchzoom_end(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
+ self.process_event(EmbedderEvent::PinchZoom(factor))
+ }
+
+ /// Perform a click.
+ pub fn click(&mut self, x: f32, y: f32) -> Result<(), &'static str> {
+ let mouse_event = MouseWindowEvent::Click(MouseButton::Left, Point2D::new(x, y));
+ let event = EmbedderEvent::MouseWindowEventClass(mouse_event);
+ self.process_event(event)
+ }
+
+ pub fn key_down(&mut self, key: Key) -> Result<(), &'static str> {
+ let key_event = KeyboardEvent {
+ state: KeyState::Down,
+ key,
+ ..KeyboardEvent::default()
+ };
+ self.process_event(EmbedderEvent::Keyboard(key_event))
+ }
+
+ pub fn key_up(&mut self, key: Key) -> Result<(), &'static str> {
+ let key_event = KeyboardEvent {
+ state: KeyState::Up,
+ key,
+ ..KeyboardEvent::default()
+ };
+ self.process_event(EmbedderEvent::Keyboard(key_event))
+ }
+
+ pub fn pause_compositor(&mut self) -> Result<(), &'static str> {
+ self.process_event(EmbedderEvent::InvalidateNativeSurface)
+ }
+
+ pub fn resume_compositor(
+ &mut self,
+ native_surface: *mut c_void,
+ coords: Coordinates,
+ ) -> Result<(), &'static str> {
+ if native_surface.is_null() {
+ panic!("null passed for native_surface");
+ }
+ self.process_event(EmbedderEvent::ReplaceNativeSurface(
+ native_surface,
+ coords.framebuffer,
+ ))
+ }
+
+ pub fn media_session_action(
+ &mut self,
+ action: MediaSessionActionType,
+ ) -> Result<(), &'static str> {
+ info!("Media session action {:?}", action);
+ self.process_event(EmbedderEvent::MediaSessionAction(action))
+ }
+
+ pub fn set_throttled(&mut self, throttled: bool) -> Result<(), &'static str> {
+ info!("set_throttled");
+ if let Ok(id) = self.get_browser_id() {
+ let event = EmbedderEvent::SetWebViewThrottled(id, throttled);
+ self.process_event(event)
+ } else {
+ // Ignore visibility change if no browser has been created yet.
+ Ok(())
+ }
+ }
+
+ pub fn ime_dismissed(&mut self) -> Result<(), &'static str> {
+ info!("ime_dismissed");
+ self.process_event(EmbedderEvent::IMEDismissed)
+ }
+
+ pub fn on_context_menu_closed(
+ &mut self,
+ result: ContextMenuResult,
+ ) -> Result<(), &'static str> {
+ if let Some(sender) = self.context_menu_sender.take() {
+ let _ = sender.send(result);
+ } else {
+ warn!("Trying to close a context menu when no context menu is active");
+ }
+ Ok(())
+ }
+
+ pub(super) fn process_event(&mut self, event: EmbedderEvent) -> Result<(), &'static str> {
+ self.events.push(event);
+ if !self.batch_mode {
+ self.perform_updates()
+ } else {
+ Ok(())
+ }
+ }
+
+ fn handle_servo_events(&mut self) -> Result<(), &'static str> {
+ let mut need_update = false;
+ let mut need_present = false;
+ for (browser_id, event) in self.servo.get_events() {
+ match event {
+ EmbedderMsg::ChangePageTitle(title) => {
+ self.callbacks.host_callbacks.on_title_changed(title);
+ },
+ EmbedderMsg::AllowNavigationRequest(pipeline_id, url) => {
+ if let Some(_browser_id) = browser_id {
+ let data: bool = self
+ .callbacks
+ .host_callbacks
+ .on_allow_navigation(url.to_string());
+ let window_event =
+ EmbedderEvent::AllowNavigationResponse(pipeline_id, data);
+ self.events.push(window_event);
+ need_update = true;
+ }
+ },
+ EmbedderMsg::HistoryChanged(entries, current) => {
+ let can_go_back = current > 0;
+ let can_go_forward = current < entries.len() - 1;
+ self.callbacks
+ .host_callbacks
+ .on_history_changed(can_go_back, can_go_forward);
+ self.callbacks
+ .host_callbacks
+ .on_url_changed(entries[current].clone().to_string());
+ },
+ EmbedderMsg::LoadStart => {
+ self.callbacks.host_callbacks.on_load_started();
+ },
+ EmbedderMsg::LoadComplete => {
+ self.callbacks.host_callbacks.on_load_ended();
+ },
+ EmbedderMsg::GetSelectedBluetoothDevice(_, sender) => {
+ let _ = sender.send(None);
+ },
+ EmbedderMsg::AllowUnload(sender) => {
+ let _ = sender.send(true);
+ },
+ EmbedderMsg::ShowContextMenu(sender, title, items) => {
+ if self.context_menu_sender.is_some() {
+ warn!(
+ "Trying to show a context menu when a context menu is already active"
+ );
+ let _ = sender.send(ContextMenuResult::Ignored);
+ } else {
+ self.context_menu_sender = Some(sender);
+ self.callbacks
+ .host_callbacks
+ .show_context_menu(title, items);
+ }
+ },
+ EmbedderMsg::Prompt(definition, origin) => {
+ let cb = &self.callbacks.host_callbacks;
+ let trusted = origin == PromptOrigin::Trusted;
+ let res = match definition {
+ PromptDefinition::Alert(message, sender) => {
+ sender.send(cb.prompt_alert(message, trusted))
+ },
+ PromptDefinition::OkCancel(message, sender) => {
+ sender.send(cb.prompt_ok_cancel(message, trusted))
+ },
+ PromptDefinition::YesNo(message, sender) => {
+ sender.send(cb.prompt_yes_no(message, trusted))
+ },
+ PromptDefinition::Input(message, default, sender) => {
+ sender.send(cb.prompt_input(message, default, trusted))
+ },
+ };
+ if let Err(e) = res {
+ let reason = format!("Failed to send Prompt response: {}", e);
+ self.events
+ .push(EmbedderEvent::SendError(browser_id, reason));
+ }
+ },
+ EmbedderMsg::AllowOpeningWebView(response_chan) => {
+ // Note: would be a place to handle pop-ups config.
+ // see Step 7 of #the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
+ if let Err(e) = response_chan.send(true) {
+ warn!("Failed to send AllowOpeningBrowser response: {}", e);
+ };
+ },
+ EmbedderMsg::WebViewOpened(new_webview_id) => {
+ self.webviews.insert(new_webview_id, WebView {});
+ self.creation_order.push(new_webview_id);
+ self.events
+ .push(EmbedderEvent::FocusWebView(new_webview_id));
+ },
+ EmbedderMsg::WebViewClosed(webview_id) => {
+ self.webviews.retain(|&id, _| id != webview_id);
+ self.creation_order.retain(|&id| id != webview_id);
+ self.focused_webview_id = None;
+ if let Some(&newest_webview_id) = self.creation_order.last() {
+ self.events
+ .push(EmbedderEvent::FocusWebView(newest_webview_id));
+ } else {
+ self.events.push(EmbedderEvent::Quit);
+ }
+ },
+ EmbedderMsg::WebViewFocused(webview_id) => {
+ self.focused_webview_id = Some(webview_id);
+ },
+ EmbedderMsg::WebViewBlurred => {
+ self.focused_webview_id = None;
+ },
+ EmbedderMsg::GetClipboardContents(sender) => {
+ let contents = self.callbacks.host_callbacks.get_clipboard_contents();
+ let _ = sender.send(contents.unwrap_or("".to_owned()));
+ },
+ EmbedderMsg::SetClipboardContents(text) => {
+ self.callbacks.host_callbacks.set_clipboard_contents(text);
+ },
+ EmbedderMsg::Shutdown => {
+ self.callbacks.host_callbacks.on_shutdown_complete();
+ },
+ EmbedderMsg::PromptPermission(prompt, sender) => {
+ let message = match prompt {
+ PermissionPrompt::Request(permission_name) => {
+ format!("Do you want to grant permission for {:?}?", permission_name)
+ },
+ PermissionPrompt::Insecure(permission_name) => {
+ format!(
+ "The {:?} feature is only safe to use in secure context, but servo can't guarantee\n\
+ that the current context is secure. Do you want to proceed and grant permission?",
+ permission_name
+ )
+ },
+ };
+
+ let result = match self.callbacks.host_callbacks.prompt_yes_no(message, true) {
+ PromptResult::Primary => PermissionRequest::Granted,
+ PromptResult::Secondary | PromptResult::Dismissed => {
+ PermissionRequest::Denied
+ },
+ };
+
+ let _ = sender.send(result);
+ },
+ EmbedderMsg::ShowIME(kind, text, multiline, bounds) => {
+ self.callbacks
+ .host_callbacks
+ .on_ime_show(kind, text, multiline, bounds);
+ },
+ EmbedderMsg::HideIME => {
+ self.callbacks.host_callbacks.on_ime_hide();
+ },
+ EmbedderMsg::MediaSessionEvent(event) => {
+ match event {
+ MediaSessionEvent::SetMetadata(metadata) => {
+ self.callbacks.host_callbacks.on_media_session_metadata(
+ metadata.title,
+ metadata.artist,
+ metadata.album,
+ )
+ },
+ MediaSessionEvent::PlaybackStateChange(state) => self
+ .callbacks
+ .host_callbacks
+ .on_media_session_playback_state_change(state),
+ MediaSessionEvent::SetPositionState(position_state) => self
+ .callbacks
+ .host_callbacks
+ .on_media_session_set_position_state(
+ position_state.duration,
+ position_state.position,
+ position_state.playback_rate,
+ ),
+ };
+ },
+ EmbedderMsg::OnDevtoolsStarted(port, token) => {
+ self.callbacks
+ .host_callbacks
+ .on_devtools_started(port, token);
+ },
+ EmbedderMsg::Panic(reason, backtrace) => {
+ self.callbacks.host_callbacks.on_panic(reason, backtrace);
+ },
+ EmbedderMsg::ReadyToPresent(_webview_ids) => {
+ need_present = true;
+ },
+ EmbedderMsg::Status(..) |
+ EmbedderMsg::SelectFiles(..) |
+ EmbedderMsg::MoveTo(..) |
+ EmbedderMsg::ResizeTo(..) |
+ EmbedderMsg::Keyboard(..) |
+ EmbedderMsg::SetCursor(..) |
+ EmbedderMsg::NewFavicon(..) |
+ EmbedderMsg::HeadParsed |
+ EmbedderMsg::SetFullscreenState(..) |
+ EmbedderMsg::ReportProfile(..) |
+ EmbedderMsg::EventDelivered(..) => {},
+ }
+ }
+
+ if need_update {
+ let _ = self.perform_updates();
+ }
+ if need_present {
+ self.servo.present();
+ }
+ Ok(())
+ }
+}
+
+pub(super) struct ServoEmbedderCallbacks {
+ waker: Box<dyn EventLoopWaker>,
+ xr_discovery: Option<webxr::Discovery>,
+ #[allow(unused)]
+ gl: Rc<dyn gl::Gl>,
+}
+
+impl ServoEmbedderCallbacks {
+ pub(super) fn new(
+ waker: Box<dyn EventLoopWaker>,
+ xr_discovery: Option<webxr::Discovery>,
+ gl: Rc<dyn gl::Gl>,
+ ) -> Self {
+ Self {
+ waker,
+ xr_discovery,
+ gl,
+ }
+ }
+}
+
+impl EmbedderMethods for ServoEmbedderCallbacks {
+ fn create_event_loop_waker(&mut self) -> Box<dyn EventLoopWaker> {
+ debug!("EmbedderMethods::create_event_loop_waker");
+ self.waker.clone()
+ }
+
+ fn register_webxr(
+ &mut self,
+ registry: &mut webxr::MainThreadRegistry,
+ _embedder_proxy: EmbedderProxy,
+ ) {
+ debug!("EmbedderMethods::register_xr");
+ if let Some(discovery) = self.xr_discovery.take() {
+ registry.register(discovery);
+ }
+ }
+}
+
+impl WindowMethods for ServoWindowCallbacks {
+ fn get_coordinates(&self) -> EmbedderCoordinates {
+ let coords = self.coordinates.borrow();
+ EmbedderCoordinates {
+ viewport: coords.viewport.to_box2d(),
+ framebuffer: coords.framebuffer,
+ window: (coords.viewport.size, Point2D::new(0, 0)),
+ screen: coords.viewport.size,
+ screen_avail: coords.viewport.size,
+ hidpi_factor: Scale::new(self.density),
+ }
+ }
+
+ fn set_animation_state(&self, state: AnimationState) {
+ debug!("WindowMethods::set_animation_state: {:?}", state);
+ self.host_callbacks
+ .on_animating_changed(state == AnimationState::Animating);
+ }
+
+ fn rendering_context(&self) -> RenderingContext {
+ self.rendering_context.clone()
+ }
+}
diff --git a/ports/servoshell/lib.rs b/ports/servoshell/lib.rs
index 82e30d28467..8789f1a67c7 100644
--- a/ports/servoshell/lib.rs
+++ b/ports/servoshell/lib.rs
@@ -14,14 +14,15 @@ mod backtrace;
mod crash_handler;
#[cfg(not(any(target_os = "android", target_env = "ohos")))]
pub(crate) mod desktop;
+#[cfg(any(target_os = "android", target_env = "ohos"))]
+mod egl;
#[cfg(not(target_os = "android"))]
mod panic_hook;
mod parser;
mod prefs;
+#[cfg(not(any(target_os = "android", target_env = "ohos")))]
mod resources;
-mod egl;
-
pub mod platform {
#[cfg(target_os = "macos")]
pub use crate::platform::macos::deinit;
diff --git a/ports/servoshell/main.rs b/ports/servoshell/main.rs
index 84d878eea71..08fef65b91f 100644
--- a/ports/servoshell/main.rs
+++ b/ports/servoshell/main.rs
@@ -22,12 +22,15 @@
fn main() {
cfg_if::cfg_if! {
- if #[cfg(not(target_os = "android"))] {
+ if #[cfg(not(any(target_os = "android", target_env = "ohos")))] {
servoshell::main()
} else {
println!(
- "Cannot start /ports/servoshell/ on Android. \
- Use /support/android/apk/ + /ports/jniapi/ instead"
+ "Cannot run the servoshell `bin` executable on platforms such as \
+ Android or OpenHarmony. On these platforms you need to compile \
+ the servoshell library as a `cdylib` and integrate it with the \
+ platform app code into an `apk` (android) or `hap` (OpenHarmony).\
+ For Android `mach build` will do these steps automatically for you."
);
}
}