aboutsummaryrefslogtreecommitdiffstats
path: root/ports/servoshell
diff options
context:
space:
mode:
authorMukilan Thiyagarajan <mukilan@igalia.com>2025-02-06 19:51:29 +0530
committerGitHub <noreply@github.com>2025-02-06 14:21:29 +0000
commite0689c1f0b2c032c5259ee0446469f4935e6518e (patch)
treea00ceae8d5e6aa771b5b21516e25611d8ac6b9fa /ports/servoshell
parente7a6691628fa1c53baf714d844be19aa99c4bfec (diff)
downloadservo-e0689c1f0b2c032c5259ee0446469f4935e6518e.tar.gz
servo-e0689c1f0b2c032c5259ee0446469f4935e6518e.zip
Migrate Android and OHOS ports to the delegate API (#35315)
Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
Diffstat (limited to 'ports/servoshell')
-rw-r--r--ports/servoshell/egl/android.rs17
-rw-r--r--ports/servoshell/egl/android/simpleservo.rs21
-rw-r--r--ports/servoshell/egl/app_state.rs762
-rw-r--r--ports/servoshell/egl/host_trait.rs2
-rw-r--r--ports/servoshell/egl/mod.rs2
-rw-r--r--ports/servoshell/egl/ohos.rs14
-rw-r--r--ports/servoshell/egl/ohos/simpleservo.rs12
-rw-r--r--ports/servoshell/egl/servo_glue.rs744
8 files changed, 792 insertions, 782 deletions
diff --git a/ports/servoshell/egl/android.rs b/ports/servoshell/egl/android.rs
index 7039c387c0f..ea54155a7e2 100644
--- a/ports/servoshell/egl/android.rs
+++ b/ports/servoshell/egl/android.rs
@@ -18,11 +18,11 @@ use log::{debug, error, info, warn};
use servo::{LoadStatus, MediaSessionActionType};
use simpleservo::{
DeviceIntRect, EventLoopWaker, InitOptions, InputMethodType, MediaSessionPlaybackState,
- PromptResult, SERVO,
+ PromptResult, APP,
};
+use super::app_state::{Coordinates, RunningAppState};
use super::host_trait::HostTrait;
-use super::servo_glue::{Coordinates, ServoGlue};
struct HostCallbacks {
callbacks: GlobalRef,
@@ -44,10 +44,10 @@ pub extern "C" fn android_main() {
fn call<F>(env: &mut JNIEnv, f: F)
where
- F: Fn(&mut ServoGlue),
+ F: Fn(&RunningAppState),
{
- SERVO.with(|servo| match servo.borrow_mut().as_mut() {
- Some(ref mut servo) => (f)(servo),
+ APP.with(|app| match app.borrow().as_ref() {
+ Some(ref app_state) => (f)(app_state),
None => throw(env, "Servo not available in this thread"),
});
}
@@ -673,13 +673,6 @@ impl HostTrait for HostCallbacks {
.unwrap();
}
- fn on_devtools_started(&self, port: Result<u16, ()>, _token: String) {
- match port {
- Ok(p) => info!("Devtools Server running on port {}", p),
- Err(()) => error!("Error running devtools server"),
- }
- }
-
fn show_context_menu(&self, _title: Option<String>, _items: Vec<String>) {}
fn on_panic(&self, _reason: String, _backtrace: Option<String>) {}
diff --git a/ports/servoshell/egl/android/simpleservo.rs b/ports/servoshell/egl/android/simpleservo.rs
index 0c377d3c93c..04df03fb983 100644
--- a/ports/servoshell/egl/android/simpleservo.rs
+++ b/ports/servoshell/egl/android/simpleservo.rs
@@ -19,14 +19,14 @@ pub use servo::{InputMethodType, MediaSessionPlaybackState, PromptResult};
use surfman::{Connection, SurfaceType};
use crate::egl::android::resources::ResourceReaderInstance;
-use crate::egl::host_trait::HostTrait;
-use crate::egl::servo_glue::{
- Coordinates, ServoEmbedderCallbacks, ServoGlue, ServoWindowCallbacks,
+use crate::egl::app_state::{
+ Coordinates, RunningAppState, ServoEmbedderCallbacks, ServoWindowCallbacks,
};
+use crate::egl::host_trait::HostTrait;
use crate::prefs::{parse_command_line_arguments, ArgumentParsingResult};
thread_local! {
- pub static SERVO: RefCell<Option<ServoGlue>> = const { RefCell::new(None) };
+ pub static APP: RefCell<Option<Rc<RunningAppState>>> = const { RefCell::new(None) };
}
pub struct InitOptions {
@@ -116,20 +116,25 @@ pub fn init(
CompositeTarget::ContextFbo,
);
- SERVO.with(|s| {
- let servo_glue = ServoGlue::new(
+ APP.with(|app| {
+ let app_state = RunningAppState::new(
init_opts.url,
rendering_context,
servo,
window_callbacks,
servoshell_preferences,
);
- *s.borrow_mut() = Some(servo_glue);
+ *app.borrow_mut() = Some(app_state);
});
Ok(())
}
pub fn deinit() {
- SERVO.with(|s| s.replace(None).unwrap().deinit());
+ APP.with(|app| {
+ let app = app.replace(None).unwrap();
+ if let Some(app_state) = Rc::into_inner(app) {
+ app_state.deinit()
+ }
+ });
}
diff --git a/ports/servoshell/egl/app_state.rs b/ports/servoshell/egl/app_state.rs
new file mode 100644
index 00000000000..293810948a6
--- /dev/null
+++ b/ports/servoshell/egl/app_state.rs
@@ -0,0 +1,762 @@
+/* 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::{Ref, RefCell, RefMut};
+use std::collections::HashMap;
+use std::os::raw::c_void;
+use std::rc::Rc;
+
+use ipc_channel::ipc::IpcSender;
+use keyboard_types::{CompositionEvent, CompositionState};
+use log::{debug, error, info, warn};
+use servo::base::id::WebViewId;
+use servo::compositing::windowing::{
+ AnimationState, EmbedderCoordinates, EmbedderMethods, MouseWindowEvent, WindowMethods,
+};
+use servo::euclid::{Box2D, Point2D, Rect, Scale, Size2D, Vector2D};
+use servo::servo_geometry::DeviceIndependentPixel;
+use servo::webrender_api::units::{DeviceIntRect, DeviceIntSize, DevicePixel, DeviceRect};
+use servo::webrender_api::ScrollLocation;
+use servo::webrender_traits::SurfmanRenderingContext;
+use servo::{
+ AllowOrDenyRequest, ContextMenuResult, EmbedderProxy, EventLoopWaker, InputMethodType, Key,
+ KeyState, KeyboardEvent, LoadStatus, MediaSessionActionType, MediaSessionEvent, MouseButton,
+ NavigationRequest, PermissionPrompt, PermissionRequest, PromptDefinition, PromptOrigin,
+ PromptResult, Servo, ServoDelegate, ServoError, TouchEventType, TouchId, WebView,
+ WebViewDelegate,
+};
+use url::Url;
+
+use crate::egl::host_trait::HostTrait;
+use crate::prefs::ServoShellPreferences;
+
+#[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>,
+ hidpi_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
+}
+
+impl ServoWindowCallbacks {
+ pub(super) fn new(
+ host_callbacks: Box<dyn HostTrait>,
+ coordinates: RefCell<Coordinates>,
+ hidpi_factor: f32,
+ ) -> Self {
+ Self {
+ host_callbacks,
+ coordinates,
+ hidpi_factor: Scale::new(hidpi_factor),
+ }
+ }
+}
+
+pub struct RunningAppState {
+ servo: Servo,
+ rendering_context: SurfmanRenderingContext,
+ callbacks: Rc<ServoWindowCallbacks>,
+ inner: RefCell<RunningAppStateInner>,
+ /// servoshell specific preferences created during startup of the application.
+ servoshell_preferences: ServoShellPreferences,
+}
+
+struct RunningAppStateInner {
+ need_present: bool,
+ /// 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>,
+
+ context_menu_sender: Option<IpcSender<ContextMenuResult>>,
+}
+
+impl ServoDelegate for RunningAppState {
+ fn notify_devtools_server_started(&self, _servo: &Servo, port: u16, _token: String) {
+ info!("Devtools Server running on port {port}");
+ }
+
+ fn request_devtools_connection(&self, _servo: &Servo, request: AllowOrDenyRequest) {
+ request.allow();
+ }
+
+ fn notify_error(&self, _servo: &Servo, error: ServoError) {
+ error!("Saw Servo error: {error:?}!");
+ }
+}
+
+impl WebViewDelegate for RunningAppState {
+ fn notify_page_title_changed(&self, _webview: servo::WebView, title: Option<String>) {
+ self.callbacks.host_callbacks.on_title_changed(title);
+ }
+
+ fn notify_history_changed(&self, _webview: WebView, entries: Vec<Url>, current: usize) {
+ 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());
+ }
+
+ fn notify_load_status_changed(&self, _webview: WebView, load_status: LoadStatus) {
+ self.callbacks
+ .host_callbacks
+ .notify_load_status_changed(load_status);
+ }
+
+ fn notify_ready_to_show(&self, webview: WebView) {
+ webview.focus();
+ }
+
+ fn notify_closed(&self, webview: WebView) {
+ {
+ let mut inner_mut = self.inner_mut();
+ inner_mut.webviews.retain(|&id, _| id != webview.id());
+ inner_mut.creation_order.retain(|&id| id != webview.id());
+ inner_mut.focused_webview_id = None;
+ }
+
+ if let Some(newest_webview) = self.newest_webview() {
+ newest_webview.focus();
+ } else {
+ self.servo.start_shutting_down();
+ }
+ }
+
+ fn notify_focus_changed(&self, webview: WebView, focused: bool) {
+ if focused {
+ self.inner_mut().focused_webview_id = Some(webview.id());
+ webview.show(true);
+ } else if self.inner().focused_webview_id == Some(webview.id()) {
+ self.inner_mut().focused_webview_id = None;
+ }
+ }
+
+ fn notify_media_session_event(&self, _webview: WebView, event: MediaSessionEvent) {
+ 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,
+ ),
+ };
+ }
+
+ fn notify_crashed(&self, _webview: WebView, reason: String, backtrace: Option<String>) {
+ self.callbacks.host_callbacks.on_panic(reason, backtrace);
+ }
+
+ fn notify_new_frame_ready(&self, _webview: WebView) {
+ self.inner_mut().need_present = true;
+ }
+
+ fn request_navigation(&self, _webview: WebView, navigation_request: NavigationRequest) {
+ if self
+ .callbacks
+ .host_callbacks
+ .on_allow_navigation(navigation_request.url.to_string())
+ {
+ navigation_request.allow();
+ } else {
+ navigation_request.deny();
+ }
+ }
+
+ fn request_open_auxiliary_webview(&self, _parent_webview: WebView) -> Option<WebView> {
+ let new_webview = self.servo.new_auxiliary_webview();
+ self.add(new_webview.clone());
+ Some(new_webview)
+ }
+
+ fn request_permission(
+ &self,
+ _webview: WebView,
+ prompt: PermissionPrompt,
+ result_sender: IpcSender<PermissionRequest>,
+ ) {
+ 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 _ = result_sender.send(result);
+ }
+
+ fn request_resize_to(&self, _webview: WebView, size: DeviceIntSize) {
+ warn!("Received resize event (to {size:?}). Currently only the user can resize windows");
+ }
+
+ fn show_context_menu(
+ &self,
+ _webview: WebView,
+ result_sender: IpcSender<ContextMenuResult>,
+ title: Option<String>,
+ items: Vec<String>,
+ ) {
+ if self.inner().context_menu_sender.is_some() {
+ warn!("Trying to show a context menu when a context menu is already active");
+ let _ = result_sender.send(ContextMenuResult::Ignored);
+ } else {
+ self.inner_mut().context_menu_sender = Some(result_sender);
+ self.callbacks
+ .host_callbacks
+ .show_context_menu(title, items);
+ }
+ }
+
+ fn show_prompt(&self, _webview: WebView, prompt: PromptDefinition, origin: PromptOrigin) {
+ let cb = &self.callbacks.host_callbacks;
+ let trusted = origin == PromptOrigin::Trusted;
+ let _ = match prompt {
+ PromptDefinition::Alert(message, response_sender) => {
+ cb.prompt_alert(message, trusted);
+ response_sender.send(())
+ },
+ PromptDefinition::OkCancel(message, response_sender) => {
+ response_sender.send(cb.prompt_ok_cancel(message, trusted))
+ },
+ PromptDefinition::Input(message, default, response_sender) => {
+ response_sender.send(cb.prompt_input(message, default, trusted))
+ },
+ PromptDefinition::Credentials(response_sender) => {
+ warn!("implement credentials prompt for OpenHarmony OS and Android");
+ response_sender.send(Default::default())
+ },
+ };
+ }
+
+ fn show_ime(
+ &self,
+ _webview: WebView,
+ input_method_type: InputMethodType,
+ text: Option<(String, i32)>,
+ multiline: bool,
+ position: DeviceIntRect,
+ ) {
+ self.callbacks
+ .host_callbacks
+ .on_ime_show(input_method_type, text, multiline, position);
+ }
+
+ fn hide_ime(&self, _webview: WebView) {
+ self.callbacks.host_callbacks.on_ime_hide();
+ }
+
+ fn get_clipboard_contents(&self, _webview: WebView, sender: IpcSender<String>) {
+ let contents = self.callbacks.host_callbacks.get_clipboard_contents();
+ let _ = sender.send(contents.unwrap_or("".to_owned()));
+ }
+
+ fn set_clipboard_contents(&self, _webview: WebView, text: String) {
+ self.callbacks.host_callbacks.set_clipboard_contents(text);
+ }
+}
+
+#[allow(unused)]
+impl RunningAppState {
+ pub(super) fn new(
+ initial_url: Option<String>,
+ rendering_context: SurfmanRenderingContext,
+ servo: Servo,
+ callbacks: Rc<ServoWindowCallbacks>,
+ servoshell_preferences: ServoShellPreferences,
+ ) -> Rc<Self> {
+ let initial_url = initial_url.and_then(|string| Url::parse(&string).ok());
+ let initial_url = initial_url
+ .or_else(|| Url::parse(&servoshell_preferences.homepage).ok())
+ .or_else(|| Url::parse("about:blank").ok())
+ .unwrap();
+ let app_state = Rc::new(Self {
+ rendering_context,
+ servo,
+ callbacks,
+ servoshell_preferences,
+ inner: RefCell::new(RunningAppStateInner {
+ need_present: false,
+ context_menu_sender: None,
+ webviews: Default::default(),
+ creation_order: vec![],
+ focused_webview_id: None,
+ }),
+ });
+
+ app_state.new_toplevel_webview(initial_url);
+
+ app_state
+ }
+
+ pub(crate) fn new_toplevel_webview(self: &Rc<Self>, url: Url) {
+ let webview = self.servo.new_webview(url);
+ webview.set_delegate(self.clone());
+ self.add(webview.clone());
+ }
+
+ pub(crate) fn add(&self, webview: WebView) {
+ self.inner_mut().creation_order.push(webview.id());
+ self.inner_mut().webviews.insert(webview.id(), webview);
+ }
+
+ fn inner(&self) -> Ref<RunningAppStateInner> {
+ self.inner.borrow()
+ }
+
+ fn inner_mut(&self) -> RefMut<RunningAppStateInner> {
+ self.inner.borrow_mut()
+ }
+
+ fn get_browser_id(&self) -> Result<WebViewId, &'static str> {
+ let webview_id = match self.inner().focused_webview_id {
+ Some(id) => id,
+ None => return Err("No focused WebViewId yet."),
+ };
+ Ok(webview_id)
+ }
+
+ fn newest_webview(&self) -> Option<WebView> {
+ self.inner()
+ .creation_order
+ .last()
+ .and_then(|id| self.inner().webviews.get(id).cloned())
+ }
+
+ fn active_webview(&self) -> WebView {
+ self.inner()
+ .focused_webview_id
+ .and_then(|id| self.inner().webviews.get(&id).cloned())
+ .or(self.newest_webview())
+ .expect("Should always have an active WebView")
+ }
+
+ /// Request shutdown. Will call on_shutdown_complete.
+ pub fn request_shutdown(&self) {
+ self.servo.start_shutting_down();
+ self.perform_updates();
+ }
+
+ /// 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) -> SurfmanRenderingContext {
+ 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(&self) {
+ debug!("perform_updates");
+ let should_continue = self.servo.spin_event_loop();
+ if !should_continue {
+ self.callbacks.host_callbacks.on_shutdown_complete();
+ }
+ debug!("done perform_updates");
+ }
+
+ /// Load an URL.
+ pub fn load_uri(&self, url: &str) {
+ info!("load_uri: {}", url);
+
+ let Some(url) =
+ crate::parser::location_bar_input_to_url(url, &self.servoshell_preferences.searchpage)
+ else {
+ warn!("Cannot parse URL");
+ return;
+ };
+
+ self.active_webview().load(url.into_url());
+ }
+
+ /// Reload the page.
+ pub fn reload(&self) {
+ info!("reload");
+ self.active_webview().reload();
+ self.perform_updates();
+ }
+
+ /// Redraw the page.
+ pub fn refresh(&self) {
+ info!("refresh");
+ self.active_webview().composite();
+ self.perform_updates();
+ }
+
+ /// Stop loading the page.
+ pub fn stop(&self) {
+ warn!("TODO can't stop won't stop");
+ }
+
+ /// Go back in history.
+ pub fn go_back(&self) {
+ info!("go_back");
+ self.active_webview().go_back(1);
+ self.perform_updates();
+ }
+
+ /// Go forward in history.
+ pub fn go_forward(&self) {
+ info!("go_forward");
+ self.active_webview().go_forward(1);
+ self.perform_updates();
+ }
+
+ /// Let Servo know that the window has been resized.
+ pub fn resize(&self, coordinates: Coordinates) {
+ info!("resize to {:?}", coordinates);
+ let size = coordinates.viewport.size;
+ let _ = self
+ .rendering_context
+ .resize(Size2D::new(size.width, size.height))
+ .inspect_err(|e| error!("Failed to resize rendering context: {e:?}"));
+ *self.callbacks.coordinates.borrow_mut() = coordinates;
+ self.active_webview().notify_rendering_context_resized();
+ self.active_webview()
+ .move_resize(DeviceRect::from_size(size.to_f32()));
+ self.perform_updates();
+ }
+
+ /// Start scrolling.
+ /// x/y are scroll coordinates.
+ /// dx/dy are scroll deltas.
+ #[cfg(not(target_env = "ohos"))]
+ pub fn scroll_start(&self, dx: f32, dy: f32, x: i32, y: i32) {
+ let delta = Vector2D::new(dx, dy);
+ let scroll_location = ScrollLocation::Delta(delta);
+ self.active_webview().notify_scroll_event(
+ scroll_location,
+ Point2D::new(x, y),
+ TouchEventType::Down,
+ );
+ self.perform_updates();
+ }
+
+ /// Scroll.
+ /// x/y are scroll coordinates.
+ /// dx/dy are scroll deltas.
+ pub fn scroll(&self, dx: f32, dy: f32, x: i32, y: i32) {
+ let delta = Vector2D::new(dx, dy);
+ let scroll_location = ScrollLocation::Delta(delta);
+ self.active_webview().notify_scroll_event(
+ scroll_location,
+ Point2D::new(x, y),
+ TouchEventType::Move,
+ );
+ self.perform_updates();
+ }
+
+ /// End scrolling.
+ /// x/y are scroll coordinates.
+ /// dx/dy are scroll deltas.
+ #[cfg(not(target_env = "ohos"))]
+ pub fn scroll_end(&self, dx: f32, dy: f32, x: i32, y: i32) {
+ let delta = Vector2D::new(dx, dy);
+ let scroll_location = ScrollLocation::Delta(delta);
+ self.active_webview().notify_scroll_event(
+ scroll_location,
+ Point2D::new(x, y),
+ TouchEventType::Up,
+ );
+ self.perform_updates();
+ }
+
+ /// Touch event: press down
+ pub fn touch_down(&self, x: f32, y: f32, pointer_id: i32) {
+ self.active_webview().notify_touch_event(
+ TouchEventType::Down,
+ TouchId(pointer_id),
+ Point2D::new(x, y),
+ );
+ self.perform_updates();
+ }
+
+ /// Touch event: move touching finger
+ pub fn touch_move(&self, x: f32, y: f32, pointer_id: i32) {
+ self.active_webview().notify_touch_event(
+ TouchEventType::Move,
+ TouchId(pointer_id),
+ Point2D::new(x, y),
+ );
+ self.perform_updates();
+ }
+
+ /// Touch event: Lift touching finger
+ pub fn touch_up(&self, x: f32, y: f32, pointer_id: i32) {
+ self.active_webview().notify_touch_event(
+ TouchEventType::Up,
+ TouchId(pointer_id),
+ Point2D::new(x, y),
+ );
+ self.perform_updates();
+ }
+
+ /// Cancel touch event
+ pub fn touch_cancel(&self, x: f32, y: f32, pointer_id: i32) {
+ self.active_webview().notify_touch_event(
+ TouchEventType::Cancel,
+ TouchId(pointer_id),
+ Point2D::new(x, y),
+ );
+ self.perform_updates();
+ }
+
+ /// Register a mouse movement.
+ pub fn mouse_move(&self, x: f32, y: f32) {
+ self.active_webview()
+ .notify_pointer_move_event(Point2D::new(x, y));
+ self.perform_updates();
+ }
+
+ /// Register a mouse button press.
+ pub fn mouse_down(&self, x: f32, y: f32, button: MouseButton) {
+ self.active_webview()
+ .notify_pointer_button_event(MouseWindowEvent::MouseDown(button, Point2D::new(x, y)));
+ self.perform_updates();
+ }
+
+ /// Register a mouse button release.
+ pub fn mouse_up(&self, x: f32, y: f32, button: MouseButton) {
+ self.active_webview()
+ .notify_pointer_button_event(MouseWindowEvent::MouseUp(button, Point2D::new(x, y)));
+ self.perform_updates();
+ }
+
+ /// Start pinchzoom.
+ /// x/y are pinch origin coordinates.
+ pub fn pinchzoom_start(&self, factor: f32, _x: u32, _y: u32) {
+ self.active_webview().set_pinch_zoom(factor);
+ self.perform_updates();
+ }
+
+ /// Pinchzoom.
+ /// x/y are pinch origin coordinates.
+ pub fn pinchzoom(&self, factor: f32, _x: u32, _y: u32) {
+ self.active_webview().set_pinch_zoom(factor);
+ self.perform_updates();
+ }
+
+ /// End pinchzoom.
+ /// x/y are pinch origin coordinates.
+ pub fn pinchzoom_end(&self, factor: f32, _x: u32, _y: u32) {
+ self.active_webview().set_pinch_zoom(factor);
+ self.perform_updates();
+ }
+
+ /// Perform a click.
+ pub fn click(&self, x: f32, y: f32) {
+ self.active_webview()
+ .notify_pointer_button_event(MouseWindowEvent::Click(
+ MouseButton::Left,
+ Point2D::new(x, y),
+ ));
+ self.perform_updates();
+ }
+
+ pub fn key_down(&self, key: Key) {
+ let key_event = KeyboardEvent {
+ state: KeyState::Down,
+ key,
+ ..KeyboardEvent::default()
+ };
+ self.active_webview().notify_keyboard_event(key_event);
+ self.perform_updates();
+ }
+
+ pub fn key_up(&self, key: Key) {
+ let key_event = KeyboardEvent {
+ state: KeyState::Up,
+ key,
+ ..KeyboardEvent::default()
+ };
+ self.active_webview().notify_keyboard_event(key_event);
+ self.perform_updates();
+ }
+
+ pub fn ime_insert_text(&self, text: String) {
+ self.active_webview().notify_ime_event(CompositionEvent {
+ state: CompositionState::End,
+ data: text,
+ });
+ self.perform_updates();
+ }
+
+ pub fn notify_vsync(&self) {
+ self.active_webview().notify_vsync();
+ self.perform_updates();
+ }
+
+ pub fn pause_compositor(&self) {
+ if let Err(e) = self.rendering_context.unbind_native_surface_from_context() {
+ warn!("Unbinding native surface from context failed ({:?})", e);
+ }
+ self.perform_updates();
+ }
+
+ pub fn resume_compositor(&self, native_surface: *mut c_void, coords: Coordinates) {
+ if native_surface.is_null() {
+ panic!("null passed for native_surface");
+ }
+ let connection = self.rendering_context.connection();
+ let native_widget = unsafe {
+ connection
+ .create_native_widget_from_ptr(native_surface, coords.framebuffer.to_untyped())
+ };
+ if let Err(e) = self
+ .rendering_context
+ .bind_native_surface_to_context(native_widget)
+ {
+ warn!("Binding native surface to context failed ({:?})", e);
+ }
+ self.perform_updates();
+ }
+
+ pub fn media_session_action(&self, action: MediaSessionActionType) {
+ info!("Media session action {:?}", action);
+ self.active_webview()
+ .notify_media_session_action_event(action);
+ self.perform_updates();
+ }
+
+ pub fn set_throttled(&self, throttled: bool) {
+ info!("set_throttled");
+ self.active_webview().set_throttled(throttled);
+ self.perform_updates();
+ }
+
+ pub fn ime_dismissed(&self) {
+ info!("ime_dismissed");
+ self.active_webview().notify_ime_dismissed_event();
+ self.perform_updates();
+ }
+
+ pub fn on_context_menu_closed(&self, result: ContextMenuResult) -> Result<(), &'static str> {
+ if let Some(sender) = self.inner_mut().context_menu_sender.take() {
+ let _ = sender.send(result);
+ } else {
+ warn!("Trying to close a context menu when no context menu is active");
+ }
+ Ok(())
+ }
+
+ pub fn present_if_needed(&self) {
+ if self.inner().need_present {
+ self.inner_mut().need_present = false;
+ self.servo.present();
+ }
+ }
+}
+
+pub(super) struct ServoEmbedderCallbacks {
+ waker: Box<dyn EventLoopWaker>,
+ #[cfg(feature = "webxr")]
+ xr_discovery: Option<servo::webxr::Discovery>,
+}
+
+impl ServoEmbedderCallbacks {
+ pub(super) fn new(
+ waker: Box<dyn EventLoopWaker>,
+ #[cfg(feature = "webxr")] xr_discovery: Option<servo::webxr::Discovery>,
+ ) -> Self {
+ Self {
+ waker,
+ #[cfg(feature = "webxr")]
+ xr_discovery,
+ }
+ }
+}
+
+impl EmbedderMethods for ServoEmbedderCallbacks {
+ fn create_event_loop_waker(&mut self) -> Box<dyn EventLoopWaker> {
+ debug!("EmbedderMethods::create_event_loop_waker");
+ self.waker.clone()
+ }
+
+ #[cfg(feature = "webxr")]
+ fn register_webxr(
+ &mut self,
+ registry: &mut servo::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();
+ let screen_size = (coords.viewport.size.to_f32() / self.hidpi_factor).to_i32();
+ EmbedderCoordinates {
+ viewport: coords.viewport.to_box2d(),
+ framebuffer: coords.framebuffer,
+ window_rect: Box2D::from_origin_and_size(Point2D::zero(), screen_size),
+ screen_size,
+ available_screen_size: screen_size,
+ hidpi_factor: self.hidpi_factor,
+ }
+ }
+
+ fn set_animation_state(&self, state: AnimationState) {
+ debug!("WindowMethods::set_animation_state: {:?}", state);
+ self.host_callbacks
+ .on_animating_changed(state == AnimationState::Animating);
+ }
+}
diff --git a/ports/servoshell/egl/host_trait.rs b/ports/servoshell/egl/host_trait.rs
index 7d6ee078bea..eb80976ef88 100644
--- a/ports/servoshell/egl/host_trait.rs
+++ b/ports/servoshell/egl/host_trait.rs
@@ -74,8 +74,6 @@ pub trait HostTrait {
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 1f8a422d2ee..15010dc62c7 100644
--- a/ports/servoshell/egl/mod.rs
+++ b/ports/servoshell/egl/mod.rs
@@ -10,5 +10,5 @@ mod ohos;
mod log;
+mod app_state;
mod host_trait;
-mod servo_glue;
diff --git a/ports/servoshell/egl/ohos.rs b/ports/servoshell/egl/ohos.rs
index de32080d9bd..383a71af01f 100644
--- a/ports/servoshell/egl/ohos.rs
+++ b/ports/servoshell/egl/ohos.rs
@@ -32,8 +32,8 @@ use xcomponent_sys::{
OH_NativeXComponent_TouchEvent, OH_NativeXComponent_TouchEventType,
};
+use super::app_state::{Coordinates, RunningAppState};
use super::host_trait::HostTrait;
-use super::servo_glue::{Coordinates, ServoGlue};
mod resources;
mod simpleservo;
@@ -129,7 +129,7 @@ static PROMPT_TOAST: OnceLock<
impl ServoAction {
fn dispatch_touch_event(
- servo: &mut ServoGlue,
+ servo: &RunningAppState,
kind: TouchEventType,
x: f32,
y: f32,
@@ -145,7 +145,7 @@ impl ServoAction {
}
// todo: consider making this take `self`, so we don't need to needlessly clone.
- fn do_action(&self, servo: &mut ServoGlue) {
+ fn do_action(&self, servo: &RunningAppState) {
use ServoAction::*;
match self {
WakeUp => servo.perform_updates(),
@@ -244,7 +244,7 @@ extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window
} else {
panic!("Servos GL thread received another event before it was initialized")
};
- let mut servo = simpleservo::init(*init_opts, window.0, xc.0, wakeup, callbacks)
+ let servo = simpleservo::init(*init_opts, window.0, xc.0, wakeup, callbacks)
.expect("Servo initialization failed");
info!("Surface created!");
@@ -261,7 +261,7 @@ 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(&mut servo);
+ action.do_action(&servo);
}
info!("Sender disconnected - Terminating main surface thread");
@@ -844,10 +844,6 @@ impl HostTrait for HostCallbacks {
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 {
diff --git a/ports/servoshell/egl/ohos/simpleservo.rs b/ports/servoshell/egl/ohos/simpleservo.rs
index 3869e7cfdde..847664ece0d 100644
--- a/ports/servoshell/egl/ohos/simpleservo.rs
+++ b/ports/servoshell/egl/ohos/simpleservo.rs
@@ -18,12 +18,12 @@ use servo::{self, resources, Servo};
use surfman::{Connection, SurfaceType};
use xcomponent_sys::OH_NativeXComponent;
+use crate::egl::app_state::{
+ Coordinates, RunningAppState, ServoEmbedderCallbacks, ServoWindowCallbacks,
+};
use crate::egl::host_trait::HostTrait;
use crate::egl::ohos::resources::ResourceReaderInstance;
use crate::egl::ohos::InitOpts;
-use crate::egl::servo_glue::{
- Coordinates, ServoEmbedderCallbacks, ServoGlue, ServoWindowCallbacks,
-};
use crate::prefs::{parse_command_line_arguments, ArgumentParsingResult};
/// Initialize Servo. At that point, we need a valid GL context.
@@ -34,7 +34,7 @@ pub fn init(
xcomponent: *mut OH_NativeXComponent,
waker: Box<dyn EventLoopWaker>,
callbacks: Box<dyn HostTrait>,
-) -> Result<ServoGlue, &'static str> {
+) -> Result<Rc<RunningAppState>, &'static str> {
info!("Entered simpleservo init function");
crate::init_tracing();
crate::init_crypto();
@@ -117,7 +117,7 @@ pub fn init(
CompositeTarget::ContextFbo,
);
- let servo_glue = ServoGlue::new(
+ let app_state = RunningAppState::new(
Some(options.url),
rendering_context,
servo,
@@ -125,5 +125,5 @@ pub fn init(
servoshell_preferences,
);
- Ok(servo_glue)
+ Ok(app_state)
}
diff --git a/ports/servoshell/egl/servo_glue.rs b/ports/servoshell/egl/servo_glue.rs
deleted file mode 100644
index 7986388b35b..00000000000
--- a/ports/servoshell/egl/servo_glue.rs
+++ /dev/null
@@ -1,744 +0,0 @@
-/* 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::os::raw::c_void;
-use std::rc::Rc;
-
-use ipc_channel::ipc::IpcSender;
-use keyboard_types::{CompositionEvent, CompositionState};
-use log::{debug, error, info, warn};
-use servo::base::id::WebViewId;
-use servo::compositing::windowing::{
- AnimationState, EmbedderCoordinates, EmbedderMethods, MouseWindowEvent, WindowMethods,
-};
-use servo::euclid::{Box2D, Point2D, Rect, Scale, Size2D, Vector2D};
-use servo::servo_geometry::DeviceIndependentPixel;
-use servo::webrender_api::units::{DevicePixel, DeviceRect};
-use servo::webrender_api::ScrollLocation;
-use servo::webrender_traits::SurfmanRenderingContext;
-use servo::{
- ContextMenuResult, EmbedderMsg, EmbedderProxy, EventLoopWaker, Key, KeyState, KeyboardEvent,
- MediaSessionActionType, MediaSessionEvent, MouseButton, PermissionPrompt, PermissionRequest,
- PromptDefinition, PromptOrigin, PromptResult, Servo, TouchEventType, TouchId, WebView,
-};
-use url::Url;
-
-use crate::egl::host_trait::HostTrait;
-use crate::prefs::ServoShellPreferences;
-
-#[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>,
- hidpi_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
-}
-
-impl ServoWindowCallbacks {
- pub(super) fn new(
- host_callbacks: Box<dyn HostTrait>,
- coordinates: RefCell<Coordinates>,
- hidpi_factor: f32,
- ) -> Self {
- Self {
- host_callbacks,
- coordinates,
- hidpi_factor: Scale::new(hidpi_factor),
- }
- }
-}
-
-pub struct ServoGlue {
- rendering_context: SurfmanRenderingContext,
- servo: Servo,
- need_present: bool,
- callbacks: Rc<ServoWindowCallbacks>,
- 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>,
-
- /// servoshell specific preferences created during startup of the application.
- servoshell_preferences: ServoShellPreferences,
-}
-
-#[allow(unused)]
-impl ServoGlue {
- pub(super) fn new(
- initial_url: Option<String>,
- rendering_context: SurfmanRenderingContext,
- servo: Servo,
- callbacks: Rc<ServoWindowCallbacks>,
- servoshell_preferences: ServoShellPreferences,
- ) -> Self {
- let initial_url = initial_url.and_then(|string| Url::parse(&string).ok());
- let initial_url = initial_url
- .or_else(|| Url::parse(&servoshell_preferences.homepage).ok())
- .or_else(|| Url::parse("about:blank").ok())
- .unwrap();
-
- let webview = servo.new_webview(initial_url);
- let webview_id = webview.id();
- let webviews = [(webview_id, webview)].into();
-
- Self {
- rendering_context,
- servo,
- need_present: false,
- callbacks,
- context_menu_sender: None,
- webviews,
- creation_order: vec![],
- focused_webview_id: Some(webview_id),
- servoshell_preferences,
- }
- }
-
- fn get_browser_id(&self) -> Result<WebViewId, &'static str> {
- let webview_id = match self.focused_webview_id {
- Some(id) => id,
- None => return Err("No focused WebViewId yet."),
- };
- Ok(webview_id)
- }
-
- fn newest_webview(&self) -> Option<&WebView> {
- self.creation_order
- .last()
- .and_then(|id| self.webviews.get(id))
- }
-
- fn active_webview(&self) -> &WebView {
- self.focused_webview_id
- .and_then(|id| self.webviews.get(&id))
- .or(self.newest_webview())
- .expect("Should always have an active WebView")
- }
-
- /// Request shutdown. Will call on_shutdown_complete.
- pub fn request_shutdown(&mut self) {
- self.servo.start_shutting_down();
- self.maybe_perform_updates();
- }
-
- /// 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) -> SurfmanRenderingContext {
- 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) {
- debug!("perform_updates");
- self.servo.handle_events(vec![]);
- let _ = self.handle_servo_events();
- debug!("done perform_updates");
- }
-
- /// Load an URL.
- pub fn load_uri(&mut self, url: &str) {
- info!("load_uri: {}", url);
-
- let Some(url) =
- crate::parser::location_bar_input_to_url(url, &self.servoshell_preferences.searchpage)
- else {
- warn!("Cannot parse URL");
- return;
- };
-
- self.active_webview().load(url.into_url());
- }
-
- /// Reload the page.
- pub fn reload(&mut self) {
- info!("reload");
- self.active_webview().reload();
- self.maybe_perform_updates()
- }
-
- /// Redraw the page.
- pub fn refresh(&mut self) {
- info!("refresh");
- self.active_webview().composite();
- self.maybe_perform_updates()
- }
-
- /// Stop loading the page.
- pub fn stop(&mut self) {
- warn!("TODO can't stop won't stop");
- }
-
- /// Go back in history.
- pub fn go_back(&mut self) {
- info!("go_back");
- self.active_webview().go_back(1);
- self.maybe_perform_updates()
- }
-
- /// Go forward in history.
- pub fn go_forward(&mut self) {
- info!("go_forward");
- self.active_webview().go_forward(1);
- self.maybe_perform_updates()
- }
-
- /// Let Servo know that the window has been resized.
- pub fn resize(&mut self, coordinates: Coordinates) {
- info!("resize to {:?}", coordinates);
- let size = coordinates.viewport.size;
- let _ = self
- .rendering_context
- .resize(Size2D::new(size.width, size.height))
- .inspect_err(|e| error!("Failed to resize rendering context: {e:?}"));
- *self.callbacks.coordinates.borrow_mut() = coordinates;
- self.active_webview().notify_rendering_context_resized();
- self.active_webview()
- .move_resize(DeviceRect::from_size(size.to_f32()));
- self.maybe_perform_updates()
- }
-
- /// 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) {
- let delta = Vector2D::new(dx, dy);
- let scroll_location = ScrollLocation::Delta(delta);
- self.active_webview().notify_scroll_event(
- scroll_location,
- Point2D::new(x, y),
- TouchEventType::Down,
- );
- self.maybe_perform_updates()
- }
-
- /// Scroll.
- /// x/y are scroll coordinates.
- /// dx/dy are scroll deltas.
- pub fn scroll(&mut self, dx: f32, dy: f32, x: i32, y: i32) {
- let delta = Vector2D::new(dx, dy);
- let scroll_location = ScrollLocation::Delta(delta);
- self.active_webview().notify_scroll_event(
- scroll_location,
- Point2D::new(x, y),
- TouchEventType::Move,
- );
- self.maybe_perform_updates()
- }
-
- /// 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) {
- let delta = Vector2D::new(dx, dy);
- let scroll_location = ScrollLocation::Delta(delta);
- self.active_webview().notify_scroll_event(
- scroll_location,
- Point2D::new(x, y),
- TouchEventType::Up,
- );
- self.maybe_perform_updates()
- }
-
- /// Touch event: press down
- pub fn touch_down(&mut self, x: f32, y: f32, pointer_id: i32) {
- self.active_webview().notify_touch_event(
- TouchEventType::Down,
- TouchId(pointer_id),
- Point2D::new(x, y),
- );
- self.maybe_perform_updates()
- }
-
- /// Touch event: move touching finger
- pub fn touch_move(&mut self, x: f32, y: f32, pointer_id: i32) {
- self.active_webview().notify_touch_event(
- TouchEventType::Move,
- TouchId(pointer_id),
- Point2D::new(x, y),
- );
- self.maybe_perform_updates()
- }
-
- /// Touch event: Lift touching finger
- pub fn touch_up(&mut self, x: f32, y: f32, pointer_id: i32) {
- self.active_webview().notify_touch_event(
- TouchEventType::Up,
- TouchId(pointer_id),
- Point2D::new(x, y),
- );
- self.maybe_perform_updates()
- }
-
- /// Cancel touch event
- pub fn touch_cancel(&mut self, x: f32, y: f32, pointer_id: i32) {
- self.active_webview().notify_touch_event(
- TouchEventType::Cancel,
- TouchId(pointer_id),
- Point2D::new(x, y),
- );
- self.maybe_perform_updates()
- }
-
- /// Register a mouse movement.
- pub fn mouse_move(&mut self, x: f32, y: f32) {
- self.active_webview()
- .notify_pointer_move_event(Point2D::new(x, y));
- self.maybe_perform_updates()
- }
-
- /// Register a mouse button press.
- pub fn mouse_down(&mut self, x: f32, y: f32, button: MouseButton) {
- self.active_webview()
- .notify_pointer_button_event(MouseWindowEvent::MouseDown(button, Point2D::new(x, y)));
- self.maybe_perform_updates()
- }
-
- /// Register a mouse button release.
- pub fn mouse_up(&mut self, x: f32, y: f32, button: MouseButton) {
- self.active_webview()
- .notify_pointer_button_event(MouseWindowEvent::MouseUp(button, Point2D::new(x, y)));
- self.maybe_perform_updates()
- }
-
- /// Start pinchzoom.
- /// x/y are pinch origin coordinates.
- pub fn pinchzoom_start(&mut self, factor: f32, _x: u32, _y: u32) {
- self.active_webview().set_pinch_zoom(factor);
- self.maybe_perform_updates()
- }
-
- /// Pinchzoom.
- /// x/y are pinch origin coordinates.
- pub fn pinchzoom(&mut self, factor: f32, _x: u32, _y: u32) {
- self.active_webview().set_pinch_zoom(factor);
- self.maybe_perform_updates()
- }
-
- /// End pinchzoom.
- /// x/y are pinch origin coordinates.
- pub fn pinchzoom_end(&mut self, factor: f32, _x: u32, _y: u32) {
- self.active_webview().set_pinch_zoom(factor);
- self.maybe_perform_updates()
- }
-
- /// Perform a click.
- pub fn click(&mut self, x: f32, y: f32) {
- self.active_webview()
- .notify_pointer_button_event(MouseWindowEvent::Click(
- MouseButton::Left,
- Point2D::new(x, y),
- ));
- self.maybe_perform_updates()
- }
-
- pub fn key_down(&mut self, key: Key) {
- let key_event = KeyboardEvent {
- state: KeyState::Down,
- key,
- ..KeyboardEvent::default()
- };
- self.active_webview().notify_keyboard_event(key_event);
- self.maybe_perform_updates()
- }
-
- pub fn key_up(&mut self, key: Key) {
- let key_event = KeyboardEvent {
- state: KeyState::Up,
- key,
- ..KeyboardEvent::default()
- };
- self.active_webview().notify_keyboard_event(key_event);
- self.maybe_perform_updates()
- }
-
- pub fn ime_insert_text(&mut self, text: String) {
- self.active_webview().notify_ime_event(CompositionEvent {
- state: CompositionState::End,
- data: text,
- });
- self.maybe_perform_updates()
- }
-
- pub fn notify_vsync(&mut self) {
- self.active_webview().notify_vsync();
- self.maybe_perform_updates()
- }
-
- pub fn pause_compositor(&mut self) {
- if let Err(e) = self.rendering_context.unbind_native_surface_from_context() {
- warn!("Unbinding native surface from context failed ({:?})", e);
- }
- self.maybe_perform_updates();
- }
-
- pub fn resume_compositor(&mut self, native_surface: *mut c_void, coords: Coordinates) {
- if native_surface.is_null() {
- panic!("null passed for native_surface");
- }
- let connection = self.rendering_context.connection();
- let native_widget = unsafe {
- connection
- .create_native_widget_from_ptr(native_surface, coords.framebuffer.to_untyped())
- };
- if let Err(e) = self
- .rendering_context
- .bind_native_surface_to_context(native_widget)
- {
- warn!("Binding native surface to context failed ({:?})", e);
- }
- self.maybe_perform_updates()
- }
-
- pub fn media_session_action(&mut self, action: MediaSessionActionType) {
- info!("Media session action {:?}", action);
- self.active_webview()
- .notify_media_session_action_event(action);
- self.maybe_perform_updates()
- }
-
- pub fn set_throttled(&mut self, throttled: bool) {
- info!("set_throttled");
- self.active_webview().set_throttled(throttled);
- self.maybe_perform_updates()
- }
-
- pub fn ime_dismissed(&mut self) {
- info!("ime_dismissed");
- self.active_webview().notify_ime_dismissed_event();
- self.maybe_perform_updates()
- }
-
- 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 maybe_perform_updates(&mut self) {
- self.perform_updates();
- }
-
- fn handle_servo_events(&mut self) -> Result<(), &'static str> {
- let mut need_update = false;
- let messages = self.servo.get_events();
- for message in messages {
- match message {
- EmbedderMsg::ChangePageTitle(_, title) => {
- self.callbacks.host_callbacks.on_title_changed(title);
- },
- EmbedderMsg::AllowNavigationRequest(_, pipeline_id, url) => {
- let data: bool = self
- .callbacks
- .host_callbacks
- .on_allow_navigation(url.to_string());
- self.servo.allow_navigation_response(pipeline_id, data);
- 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::NotifyLoadStatusChanged(_, load_status) => {
- self.callbacks
- .host_callbacks
- .notify_load_status_changed(load_status);
- },
- 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) => {
- cb.prompt_alert(message, trusted);
- sender.send(())
- },
- PromptDefinition::OkCancel(message, sender) => {
- sender.send(cb.prompt_ok_cancel(message, trusted))
- },
- PromptDefinition::Input(message, default, sender) => {
- sender.send(cb.prompt_input(message, default, trusted))
- },
- PromptDefinition::Credentials(_) => {
- warn!("implement credentials prompt for OpenHarmony OS and Android");
- Ok(())
- },
- };
- if let Err(e) = res {
- self.active_webview()
- .send_error(format!("Failed to send Prompt response: {e}"));
- }
- },
- EmbedderMsg::AllowOpeningWebView(_, response_chan) => {
- let new_webview = self.servo.new_auxiliary_webview();
- let new_webview_id = new_webview.id();
- self.webviews.insert(new_webview_id, new_webview);
- self.creation_order.push(new_webview_id);
-
- if let Err(e) = response_chan.send(Some(new_webview_id)) {
- warn!("Failed to send AllowOpeningBrowser response: {}", e);
- };
- },
- EmbedderMsg::WebViewOpened(new_webview_id) => {
- if let Some(webview) = self.webviews.get(&new_webview_id) {
- webview.focus();
- }
- },
- 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) = self.newest_webview() {
- newest_webview.focus();
- } else {
- self.servo.start_shutting_down();
- }
- },
- EmbedderMsg::WebViewFocused(webview_id) => {
- self.focused_webview_id = Some(webview_id);
- if let Some(webview) = self.webviews.get(&webview_id) {
- webview.show(true);
- }
- },
- 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::RequestDevtoolsConnection(result_sender) => {
- result_sender.send(true);
- },
- EmbedderMsg::Panic(_, reason, backtrace) => {
- self.callbacks.host_callbacks.on_panic(reason, backtrace);
- },
- EmbedderMsg::ReadyToPresent(_webview_ids) => {
- self.need_present = true;
- },
- EmbedderMsg::ResizeTo(_, size) => {
- warn!("Received resize event (to {size:?}). Currently only the user can resize windows");
- },
- EmbedderMsg::Keyboard(..) |
- EmbedderMsg::Status(..) |
- EmbedderMsg::SelectFiles(..) |
- EmbedderMsg::MoveTo(..) |
- EmbedderMsg::SetCursor(..) |
- EmbedderMsg::NewFavicon(..) |
- EmbedderMsg::SetFullscreenState(..) |
- EmbedderMsg::ReportProfile(..) |
- EmbedderMsg::EventDelivered(..) |
- EmbedderMsg::PlayGamepadHapticEffect(..) |
- EmbedderMsg::StopGamepadHapticEffect(..) |
- EmbedderMsg::ClearClipboardContents(..) |
- EmbedderMsg::WebResourceRequested(..) => {},
- }
- }
-
- if need_update {
- self.perform_updates();
- }
- Ok(())
- }
-
- pub fn present_if_needed(&mut self) {
- if self.need_present {
- self.need_present = false;
- self.servo.present();
- }
- }
-}
-
-pub(super) struct ServoEmbedderCallbacks {
- waker: Box<dyn EventLoopWaker>,
- #[cfg(feature = "webxr")]
- xr_discovery: Option<servo::webxr::Discovery>,
-}
-
-impl ServoEmbedderCallbacks {
- pub(super) fn new(
- waker: Box<dyn EventLoopWaker>,
- #[cfg(feature = "webxr")] xr_discovery: Option<servo::webxr::Discovery>,
- ) -> Self {
- Self {
- waker,
- #[cfg(feature = "webxr")]
- xr_discovery,
- }
- }
-}
-
-impl EmbedderMethods for ServoEmbedderCallbacks {
- fn create_event_loop_waker(&mut self) -> Box<dyn EventLoopWaker> {
- debug!("EmbedderMethods::create_event_loop_waker");
- self.waker.clone()
- }
-
- #[cfg(feature = "webxr")]
- fn register_webxr(
- &mut self,
- registry: &mut servo::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();
- let screen_size = (coords.viewport.size.to_f32() / self.hidpi_factor).to_i32();
- EmbedderCoordinates {
- viewport: coords.viewport.to_box2d(),
- framebuffer: coords.framebuffer,
- window_rect: Box2D::from_origin_and_size(Point2D::zero(), screen_size),
- screen_size,
- available_screen_size: screen_size,
- hidpi_factor: self.hidpi_factor,
- }
- }
-
- fn set_animation_state(&self, state: AnimationState) {
- debug!("WindowMethods::set_animation_state: {:?}", state);
- self.host_callbacks
- .on_animating_changed(state == AnimationState::Animating);
- }
-}