diff options
author | Mukilan Thiyagarajan <mukilan@igalia.com> | 2025-02-06 19:51:29 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-06 14:21:29 +0000 |
commit | e0689c1f0b2c032c5259ee0446469f4935e6518e (patch) | |
tree | a00ceae8d5e6aa771b5b21516e25611d8ac6b9fa /ports/servoshell | |
parent | e7a6691628fa1c53baf714d844be19aa99c4bfec (diff) | |
download | servo-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.rs | 17 | ||||
-rw-r--r-- | ports/servoshell/egl/android/simpleservo.rs | 21 | ||||
-rw-r--r-- | ports/servoshell/egl/app_state.rs | 762 | ||||
-rw-r--r-- | ports/servoshell/egl/host_trait.rs | 2 | ||||
-rw-r--r-- | ports/servoshell/egl/mod.rs | 2 | ||||
-rw-r--r-- | ports/servoshell/egl/ohos.rs | 14 | ||||
-rw-r--r-- | ports/servoshell/egl/ohos/simpleservo.rs | 12 | ||||
-rw-r--r-- | ports/servoshell/egl/servo_glue.rs | 744 |
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); - } -} |