servoshell: Port Android / OHOS servoshell to use the WebView API (#35192)

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-01-29 12:09:10 +01:00 committed by GitHub
parent a6218b42ea
commit c633ca1cde
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 213 additions and 243 deletions

View file

@ -291,6 +291,10 @@ impl WebView {
self.0.compositor.borrow_mut().invalidate_native_surface(); self.0.compositor.borrow_mut().invalidate_native_surface();
} }
pub fn composite(&self) {
self.0.compositor.borrow_mut().composite();
}
pub fn replace_native_surface(&self, native_widget: *mut c_void, size: DeviceIntSize) { pub fn replace_native_surface(&self, native_widget: *mut c_void, size: DeviceIntSize) {
self.0 self.0
.compositor .compositor

View file

@ -12,7 +12,7 @@ use std::sync::Arc;
use android_logger::{self, Config, FilterBuilder}; use android_logger::{self, Config, FilterBuilder};
use jni::objects::{GlobalRef, JClass, JObject, JString, JValue, JValueOwned}; use jni::objects::{GlobalRef, JClass, JObject, JString, JValue, JValueOwned};
use jni::sys::{jboolean, jfloat, jint, jobject, JNI_TRUE}; use jni::sys::{jboolean, jfloat, jint, jobject};
use jni::{JNIEnv, JavaVM}; use jni::{JNIEnv, JavaVM};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use simpleservo::{ use simpleservo::{
@ -43,15 +43,11 @@ pub extern "C" fn android_main() {
fn call<F>(env: &mut JNIEnv, f: F) fn call<F>(env: &mut JNIEnv, f: F)
where where
F: Fn(&mut ServoGlue) -> Result<(), &str>, F: Fn(&mut ServoGlue),
{ {
SERVO.with(|s| { SERVO.with(|servo| match servo.borrow_mut().as_mut() {
if let Err(error) = match s.borrow_mut().as_mut() { Some(ref mut servo) => (f)(servo),
Some(ref mut s) => (f)(s), None => throw(env, "Servo not available in this thread"),
None => Err("Servo not available in this thread"),
} {
throw(env, error);
}
}); });
} }
@ -145,16 +141,6 @@ pub extern "C" fn Java_org_servo_servoview_JNIServo_init<'local>(
}; };
} }
#[no_mangle]
pub extern "C" fn Java_org_servo_servoview_JNIServo_setBatchMode<'local>(
mut env: JNIEnv<'local>,
_: JClass<'local>,
batch: jboolean,
) {
debug!("setBatchMode");
call(&mut env, |s| s.set_batch_mode(batch == JNI_TRUE));
}
#[no_mangle] #[no_mangle]
pub extern "C" fn Java_org_servo_servoview_JNIServo_requestShutdown<'local>( pub extern "C" fn Java_org_servo_servoview_JNIServo_requestShutdown<'local>(
mut env: JNIEnv<'local>, mut env: JNIEnv<'local>,
@ -194,9 +180,8 @@ pub extern "C" fn Java_org_servo_servoview_JNIServo_performUpdates<'local>(
) { ) {
debug!("performUpdates"); debug!("performUpdates");
call(&mut env, |s| { call(&mut env, |s| {
s.perform_updates()?; s.perform_updates();
s.present_if_needed(); s.present_if_needed();
Ok(())
}); });
} }

View file

@ -7,10 +7,7 @@ use std::mem;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::rc::Rc; use std::rc::Rc;
use servo::base::id::WebViewId;
use servo::compositing::windowing::EmbedderEvent;
use servo::compositing::CompositeTarget; use servo::compositing::CompositeTarget;
use servo::servo_url::ServoUrl;
pub use servo::webrender_api::units::DeviceIntRect; pub use servo::webrender_api::units::DeviceIntRect;
use servo::webrender_traits::SurfmanRenderingContext; use servo::webrender_traits::SurfmanRenderingContext;
/// The EventLoopWaker::wake function will be called from any thread. /// The EventLoopWaker::wake function will be called from any thread.
@ -72,15 +69,6 @@ pub fn init(
}, },
}; };
let embedder_url = init_opts.url.as_ref().and_then(|s| ServoUrl::parse(s).ok());
let pref_url = servoshell_preferences
.url
.as_ref()
.and_then(|s| ServoUrl::parse(s).ok());
let blank_url = ServoUrl::parse("about:blank").ok();
let url = embedder_url.or(pref_url).or(blank_url).unwrap();
// Initialize surfman // Initialize surfman
let connection = Connection::new().or(Err("Failed to create connection"))?; let connection = Connection::new().or(Err("Failed to create connection"))?;
let adapter = connection let adapter = connection
@ -129,13 +117,13 @@ pub fn init(
); );
SERVO.with(|s| { SERVO.with(|s| {
let mut servo_glue = ServoGlue::new( let servo_glue = ServoGlue::new(
init_opts.url,
rendering_context, rendering_context,
servo, servo,
window_callbacks, window_callbacks,
servoshell_preferences, servoshell_preferences,
); );
let _ = servo_glue.process_event(EmbedderEvent::NewWebView(url, WebViewId::new()));
*s.borrow_mut() = Some(servo_glue); *s.borrow_mut() = Some(servo_glue);
}); });

View file

@ -20,7 +20,6 @@ use napi_ohos::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallM
use napi_ohos::{Env, JsObject, JsString, NapiRaw}; use napi_ohos::{Env, JsObject, JsString, NapiRaw};
use ohos_ime::{AttachOptions, Ime, ImeProxy, RawTextEditorProxy}; use ohos_ime::{AttachOptions, Ime, ImeProxy, RawTextEditorProxy};
use ohos_ime_sys::types::InputMethod_EnterKeyType; use ohos_ime_sys::types::InputMethod_EnterKeyType;
use servo::compositing::windowing::EmbedderEvent;
use servo::style::Zero; use servo::style::Zero;
use servo::{InputMethodType, MediaSessionPlaybackState, PromptResult}; use servo::{InputMethodType, MediaSessionPlaybackState, PromptResult};
use simpleservo::EventLoopWaker; use simpleservo::EventLoopWaker;
@ -131,20 +130,20 @@ impl ServoAction {
x: f32, x: f32,
y: f32, y: f32,
pointer_id: i32, pointer_id: i32,
) -> Result<(), &'static str> { ) {
match kind { match kind {
TouchEventType::Down => servo.touch_down(x, y, pointer_id), TouchEventType::Down => servo.touch_down(x, y, pointer_id),
TouchEventType::Up => servo.touch_up(x, y, pointer_id), TouchEventType::Up => servo.touch_up(x, y, pointer_id),
TouchEventType::Move => servo.touch_move(x, y, pointer_id), TouchEventType::Move => servo.touch_move(x, y, pointer_id),
TouchEventType::Cancel => servo.touch_cancel(x, y, pointer_id), TouchEventType::Cancel => servo.touch_cancel(x, y, pointer_id),
TouchEventType::Unknown => Err("Can't dispatch Unknown Touch Event"), TouchEventType::Unknown => warn!("Can't dispatch Unknown Touch Event"),
} }
} }
// todo: consider making this take `self`, so we don't need to needlessly clone. // 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: &mut ServoGlue) {
use ServoAction::*; use ServoAction::*;
let res = match self { match self {
WakeUp => servo.perform_updates(), WakeUp => servo.perform_updates(),
LoadUrl(url) => servo.load_uri(url.as_str()), LoadUrl(url) => servo.load_uri(url.as_str()),
GoBack => servo.go_back(), GoBack => servo.go_back(),
@ -160,33 +159,28 @@ impl ServoAction {
InsertText(text) => servo.ime_insert_text(text.clone()), InsertText(text) => servo.ime_insert_text(text.clone()),
ImeDeleteForward(len) => { ImeDeleteForward(len) => {
for _ in 0..*len { for _ in 0..*len {
let _ = servo.key_down(Key::Delete); servo.key_down(Key::Delete);
let _ = servo.key_up(Key::Delete); servo.key_up(Key::Delete);
} }
Ok(())
}, },
ImeDeleteBackward(len) => { ImeDeleteBackward(len) => {
for _ in 0..*len { for _ in 0..*len {
let _ = servo.key_down(Key::Backspace); servo.key_down(Key::Backspace);
let _ = servo.key_up(Key::Backspace); servo.key_up(Key::Backspace);
} }
Ok(())
}, },
ImeSendEnter => servo ImeSendEnter => {
.key_down(Key::Enter) servo.key_down(Key::Enter);
.and_then(|()| servo.key_up(Key::Enter)), servo.key_up(Key::Enter);
},
Initialize(_init_opts) => { Initialize(_init_opts) => {
panic!("Received Initialize event, even though servo is already initialized") panic!("Received Initialize event, even though servo is already initialized")
}, },
Vsync => servo Vsync => {
.process_event(EmbedderEvent::Vsync) servo.notify_vsync();
.and_then(|()| servo.perform_updates()) servo.present_if_needed();
.map(|()| servo.present_if_needed()), },
}; };
if let Err(e) = res {
error!("Failed to do {self:?} with error {e}");
}
} }
} }

View file

@ -7,12 +7,9 @@ use std::os::raw::c_void;
use std::path::PathBuf; use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
use log::{debug, error, info}; use log::{debug, info};
use servo::base::id::WebViewId;
use servo::compositing::windowing::EmbedderEvent;
use servo::compositing::CompositeTarget; use servo::compositing::CompositeTarget;
use servo::euclid::Size2D; use servo::euclid::Size2D;
use servo::servo_url::ServoUrl;
use servo::webrender_traits::SurfmanRenderingContext; use servo::webrender_traits::SurfmanRenderingContext;
/// The EventLoopWaker::wake function will be called from any thread. /// The EventLoopWaker::wake function will be called from any thread.
/// It will be called to notify embedder that some events are available, /// It will be called to notify embedder that some events are available,
@ -125,19 +122,13 @@ pub fn init(
CompositeTarget::Window, CompositeTarget::Window,
); );
let mut servo_glue = ServoGlue::new( let servo_glue = ServoGlue::new(
Some(options.url),
rendering_context, rendering_context,
servo, servo,
window_callbacks, window_callbacks,
servoshell_preferences, servoshell_preferences,
); );
let initial_url = ServoUrl::parse(options.url.as_str())
.inspect_err(|e| error!("Invalid initial Servo URL `{}`. Error: {e:?}", options.url))
.ok()
.unwrap_or_else(|| ServoUrl::parse("about:blank").expect("Infallible"));
let _ = servo_glue.process_event(EmbedderEvent::NewWebView(initial_url, WebViewId::new()));
Ok(servo_glue) Ok(servo_glue)
} }

View file

@ -3,7 +3,6 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::mem;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::rc::Rc; use std::rc::Rc;
@ -12,8 +11,7 @@ use keyboard_types::{CompositionEvent, CompositionState};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use servo::base::id::WebViewId; use servo::base::id::WebViewId;
use servo::compositing::windowing::{ use servo::compositing::windowing::{
AnimationState, EmbedderCoordinates, EmbedderEvent, EmbedderMethods, MouseWindowEvent, AnimationState, EmbedderCoordinates, EmbedderMethods, MouseWindowEvent, WindowMethods,
WindowMethods,
}; };
use servo::euclid::{Box2D, Point2D, Rect, Scale, Size2D, Vector2D}; use servo::euclid::{Box2D, Point2D, Rect, Scale, Size2D, Vector2D};
use servo::servo_geometry::DeviceIndependentPixel; use servo::servo_geometry::DeviceIndependentPixel;
@ -23,9 +21,9 @@ use servo::webrender_traits::SurfmanRenderingContext;
use servo::{ use servo::{
ContextMenuResult, EmbedderMsg, EmbedderProxy, EventLoopWaker, Key, KeyState, KeyboardEvent, ContextMenuResult, EmbedderMsg, EmbedderProxy, EventLoopWaker, Key, KeyState, KeyboardEvent,
MediaSessionActionType, MediaSessionEvent, MouseButton, PermissionPrompt, PermissionRequest, MediaSessionActionType, MediaSessionEvent, MouseButton, PermissionPrompt, PermissionRequest,
PromptDefinition, PromptOrigin, PromptResult, Servo, TopLevelBrowsingContextId, TouchEventType, PromptDefinition, PromptOrigin, PromptResult, Servo, TouchEventType, TouchId, WebView,
TouchId, TraversalDirection,
}; };
use url::Url;
use crate::egl::host_trait::HostTrait; use crate::egl::host_trait::HostTrait;
use crate::prefs::ServoShellPreferences; use crate::prefs::ServoShellPreferences;
@ -72,16 +70,11 @@ impl ServoWindowCallbacks {
} }
} }
#[derive(Debug)]
pub struct WebView {}
pub struct ServoGlue { pub struct ServoGlue {
rendering_context: SurfmanRenderingContext, rendering_context: SurfmanRenderingContext,
servo: Servo, servo: Servo,
batch_mode: bool,
need_present: bool, need_present: bool,
callbacks: Rc<ServoWindowCallbacks>, callbacks: Rc<ServoWindowCallbacks>,
events: Vec<EmbedderEvent>,
context_menu_sender: Option<IpcSender<ContextMenuResult>>, context_menu_sender: Option<IpcSender<ContextMenuResult>>,
/// List of top-level browsing contexts. /// List of top-level browsing contexts.
@ -103,27 +96,36 @@ pub struct ServoGlue {
#[allow(unused)] #[allow(unused)]
impl ServoGlue { impl ServoGlue {
pub(super) fn new( pub(super) fn new(
initial_url: Option<String>,
rendering_context: SurfmanRenderingContext, rendering_context: SurfmanRenderingContext,
servo: Servo, servo: Servo,
callbacks: Rc<ServoWindowCallbacks>, callbacks: Rc<ServoWindowCallbacks>,
servoshell_preferences: ServoShellPreferences, servoshell_preferences: ServoShellPreferences,
) -> Self { ) -> 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 { Self {
rendering_context, rendering_context,
servo, servo,
batch_mode: false,
need_present: false, need_present: false,
callbacks, callbacks,
events: vec![],
context_menu_sender: None, context_menu_sender: None,
webviews: HashMap::default(), webviews,
creation_order: vec![], creation_order: vec![],
focused_webview_id: None, focused_webview_id: Some(webview_id),
servoshell_preferences, servoshell_preferences,
} }
} }
fn get_browser_id(&self) -> Result<TopLevelBrowsingContextId, &'static str> { fn get_browser_id(&self) -> Result<WebViewId, &'static str> {
let webview_id = match self.focused_webview_id { let webview_id = match self.focused_webview_id {
Some(id) => id, Some(id) => id,
None => return Err("No focused WebViewId yet."), None => return Err("No focused WebViewId yet."),
@ -131,9 +133,23 @@ impl ServoGlue {
Ok(webview_id) 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. /// Request shutdown. Will call on_shutdown_complete.
pub fn request_shutdown(&mut self) -> Result<(), &'static str> { pub fn request_shutdown(&mut self) {
self.process_event(EmbedderEvent::Quit) self.servo.start_shutting_down();
self.maybe_perform_updates();
} }
/// Call after on_shutdown_complete /// Call after on_shutdown_complete
@ -150,271 +166,267 @@ impl ServoGlue {
/// This is the Servo heartbeat. This needs to be called /// This is the Servo heartbeat. This needs to be called
/// everytime wakeup is called or when embedder wants Servo /// everytime wakeup is called or when embedder wants Servo
/// to act on its pending events. /// to act on its pending events.
pub fn perform_updates(&mut self) -> Result<(), &'static str> { pub fn perform_updates(&mut self) {
debug!("perform_updates"); debug!("perform_updates");
let events = mem::take(&mut self.events); self.servo.handle_events(vec![]);
self.servo.handle_events(events); let _ = self.handle_servo_events();
let r = self.handle_servo_events();
debug!("done perform_updates"); debug!("done perform_updates");
r
}
/// In batch mode, Servo won't call perform_updates automatically.
/// This can be useful when the embedder wants to control when Servo
/// acts on its pending events. For example, if the embedder wants Servo
/// to act on the scroll events only at a certain time, not everytime
/// scroll() is called.
pub fn set_batch_mode(&mut self, batch: bool) -> Result<(), &'static str> {
debug!("set_batch_mode");
self.batch_mode = batch;
Ok(())
} }
/// Load an URL. /// Load an URL.
pub fn load_uri(&mut self, url: &str) -> Result<(), &'static str> { pub fn load_uri(&mut self, url: &str) {
info!("load_uri: {}", url); info!("load_uri: {}", url);
crate::parser::location_bar_input_to_url(url, &self.servoshell_preferences.searchpage)
.ok_or("Can't parse URL") let Some(url) =
.and_then(|url| { crate::parser::location_bar_input_to_url(url, &self.servoshell_preferences.searchpage)
let browser_id = self.get_browser_id()?; else {
let event = EmbedderEvent::LoadUrl(browser_id, url); warn!("Cannot parse URL");
self.process_event(event) return;
}) };
self.active_webview().load(url.into_url());
} }
/// Reload the page. /// Reload the page.
pub fn clear_cache(&mut self) -> Result<(), &'static str> { pub fn reload(&mut self) {
info!("clear_cache");
let event = EmbedderEvent::ClearCache;
self.process_event(event)
}
/// Reload the page.
pub fn reload(&mut self) -> Result<(), &'static str> {
info!("reload"); info!("reload");
let browser_id = self.get_browser_id()?; self.active_webview().reload();
let event = EmbedderEvent::Reload(browser_id); self.maybe_perform_updates()
self.process_event(event)
} }
/// Redraw the page. /// Redraw the page.
pub fn refresh(&mut self) -> Result<(), &'static str> { pub fn refresh(&mut self) {
info!("refresh"); info!("refresh");
self.process_event(EmbedderEvent::Refresh) self.active_webview().composite();
self.maybe_perform_updates()
} }
/// Stop loading the page. /// Stop loading the page.
pub fn stop(&mut self) -> Result<(), &'static str> { pub fn stop(&mut self) {
warn!("TODO can't stop won't stop"); warn!("TODO can't stop won't stop");
Ok(())
} }
/// Go back in history. /// Go back in history.
pub fn go_back(&mut self) -> Result<(), &'static str> { pub fn go_back(&mut self) {
info!("go_back"); info!("go_back");
let browser_id = self.get_browser_id()?; self.active_webview().go_back(1);
let event = EmbedderEvent::Navigation(browser_id, TraversalDirection::Back(1)); self.maybe_perform_updates()
self.process_event(event)
} }
/// Go forward in history. /// Go forward in history.
pub fn go_forward(&mut self) -> Result<(), &'static str> { pub fn go_forward(&mut self) {
info!("go_forward"); info!("go_forward");
let browser_id = self.get_browser_id()?; self.active_webview().go_forward(1);
let event = EmbedderEvent::Navigation(browser_id, TraversalDirection::Forward(1)); self.maybe_perform_updates()
self.process_event(event)
} }
/// Let Servo know that the window has been resized. /// Let Servo know that the window has been resized.
pub fn resize(&mut self, coordinates: Coordinates) -> Result<(), &'static str> { pub fn resize(&mut self, coordinates: Coordinates) {
info!("resize"); info!("resize");
*self.callbacks.coordinates.borrow_mut() = coordinates; self.active_webview().notify_rendering_context_resized();
self.process_event(EmbedderEvent::WindowResize) self.maybe_perform_updates()
} }
/// Start scrolling. /// Start scrolling.
/// x/y are scroll coordinates. /// x/y are scroll coordinates.
/// dx/dy are scroll deltas. /// dx/dy are scroll deltas.
#[cfg(not(target_env = "ohos"))] #[cfg(not(target_env = "ohos"))]
pub fn scroll_start(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> { pub fn scroll_start(&mut self, dx: f32, dy: f32, x: i32, y: i32) {
let delta = Vector2D::new(dx, dy); let delta = Vector2D::new(dx, dy);
let scroll_location = ScrollLocation::Delta(delta); let scroll_location = ScrollLocation::Delta(delta);
let event = self.active_webview().notify_scroll_event(
EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Down); scroll_location,
self.process_event(event) Point2D::new(x, y),
TouchEventType::Down,
);
self.maybe_perform_updates()
} }
/// Scroll. /// Scroll.
/// x/y are scroll coordinates. /// x/y are scroll coordinates.
/// dx/dy are scroll deltas. /// dx/dy are scroll deltas.
pub fn scroll(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> { pub fn scroll(&mut self, dx: f32, dy: f32, x: i32, y: i32) {
let delta = Vector2D::new(dx, dy); let delta = Vector2D::new(dx, dy);
let scroll_location = ScrollLocation::Delta(delta); let scroll_location = ScrollLocation::Delta(delta);
let event = self.active_webview().notify_scroll_event(
EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Move); scroll_location,
self.process_event(event) Point2D::new(x, y),
TouchEventType::Move,
);
self.maybe_perform_updates()
} }
/// End scrolling. /// End scrolling.
/// x/y are scroll coordinates. /// x/y are scroll coordinates.
/// dx/dy are scroll deltas. /// dx/dy are scroll deltas.
#[cfg(not(target_env = "ohos"))] #[cfg(not(target_env = "ohos"))]
pub fn scroll_end(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> { pub fn scroll_end(&mut self, dx: f32, dy: f32, x: i32, y: i32) {
let delta = Vector2D::new(dx, dy); let delta = Vector2D::new(dx, dy);
let scroll_location = ScrollLocation::Delta(delta); let scroll_location = ScrollLocation::Delta(delta);
let event = EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Up); self.active_webview().notify_scroll_event(
self.process_event(event) scroll_location,
Point2D::new(x, y),
TouchEventType::Up,
);
self.maybe_perform_updates()
} }
/// Touch event: press down /// Touch event: press down
pub fn touch_down(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> { pub fn touch_down(&mut self, x: f32, y: f32, pointer_id: i32) {
let event = EmbedderEvent::Touch( self.active_webview().notify_touch_event(
TouchEventType::Down, TouchEventType::Down,
TouchId(pointer_id), TouchId(pointer_id),
Point2D::new(x, y), Point2D::new(x, y),
); );
self.process_event(event) self.maybe_perform_updates()
} }
/// Touch event: move touching finger /// Touch event: move touching finger
pub fn touch_move(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> { pub fn touch_move(&mut self, x: f32, y: f32, pointer_id: i32) {
let event = EmbedderEvent::Touch( self.active_webview().notify_touch_event(
TouchEventType::Move, TouchEventType::Move,
TouchId(pointer_id), TouchId(pointer_id),
Point2D::new(x, y), Point2D::new(x, y),
); );
self.process_event(event) self.maybe_perform_updates()
} }
/// Touch event: Lift touching finger /// Touch event: Lift touching finger
pub fn touch_up(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> { pub fn touch_up(&mut self, x: f32, y: f32, pointer_id: i32) {
let event = self.active_webview().notify_touch_event(
EmbedderEvent::Touch(TouchEventType::Up, TouchId(pointer_id), Point2D::new(x, y)); TouchEventType::Up,
self.process_event(event) TouchId(pointer_id),
Point2D::new(x, y),
);
self.maybe_perform_updates()
} }
/// Cancel touch event /// Cancel touch event
pub fn touch_cancel(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> { pub fn touch_cancel(&mut self, x: f32, y: f32, pointer_id: i32) {
let event = EmbedderEvent::Touch( self.active_webview().notify_touch_event(
TouchEventType::Cancel, TouchEventType::Cancel,
TouchId(pointer_id), TouchId(pointer_id),
Point2D::new(x, y), Point2D::new(x, y),
); );
self.process_event(event) self.maybe_perform_updates()
} }
/// Register a mouse movement. /// Register a mouse movement.
pub fn mouse_move(&mut self, x: f32, y: f32) -> Result<(), &'static str> { pub fn mouse_move(&mut self, x: f32, y: f32) {
let point = Point2D::new(x, y); self.active_webview()
let event = EmbedderEvent::MouseWindowMoveEventClass(point); .notify_pointer_move_event(Point2D::new(x, y));
self.process_event(event) self.maybe_perform_updates()
} }
/// Register a mouse button press. /// Register a mouse button press.
pub fn mouse_down(&mut self, x: f32, y: f32, button: MouseButton) -> Result<(), &'static str> { pub fn mouse_down(&mut self, x: f32, y: f32, button: MouseButton) {
let point = Point2D::new(x, y); self.active_webview()
let event = .notify_pointer_button_event(MouseWindowEvent::MouseDown(button, Point2D::new(x, y)));
EmbedderEvent::MouseWindowEventClass(MouseWindowEvent::MouseDown(button, point)); self.maybe_perform_updates()
self.process_event(event)
} }
/// Register a mouse button release. /// Register a mouse button release.
pub fn mouse_up(&mut self, x: f32, y: f32, button: MouseButton) -> Result<(), &'static str> { pub fn mouse_up(&mut self, x: f32, y: f32, button: MouseButton) {
let point = Point2D::new(x, y); self.active_webview()
let event = EmbedderEvent::MouseWindowEventClass(MouseWindowEvent::MouseUp(button, point)); .notify_pointer_button_event(MouseWindowEvent::MouseUp(button, Point2D::new(x, y)));
self.process_event(event) self.maybe_perform_updates()
} }
/// Start pinchzoom. /// Start pinchzoom.
/// x/y are pinch origin coordinates. /// x/y are pinch origin coordinates.
pub fn pinchzoom_start(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> { pub fn pinchzoom_start(&mut self, factor: f32, _x: u32, _y: u32) {
self.process_event(EmbedderEvent::PinchZoom(factor)) self.active_webview().set_pinch_zoom(factor);
self.maybe_perform_updates()
} }
/// Pinchzoom. /// Pinchzoom.
/// x/y are pinch origin coordinates. /// x/y are pinch origin coordinates.
pub fn pinchzoom(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> { pub fn pinchzoom(&mut self, factor: f32, _x: u32, _y: u32) {
self.process_event(EmbedderEvent::PinchZoom(factor)) self.active_webview().set_pinch_zoom(factor);
self.maybe_perform_updates()
} }
/// End pinchzoom. /// End pinchzoom.
/// x/y are pinch origin coordinates. /// x/y are pinch origin coordinates.
pub fn pinchzoom_end(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> { pub fn pinchzoom_end(&mut self, factor: f32, _x: u32, _y: u32) {
self.process_event(EmbedderEvent::PinchZoom(factor)) self.active_webview().set_pinch_zoom(factor);
self.maybe_perform_updates()
} }
/// Perform a click. /// Perform a click.
pub fn click(&mut self, x: f32, y: f32) -> Result<(), &'static str> { pub fn click(&mut self, x: f32, y: f32) {
let mouse_event = MouseWindowEvent::Click(MouseButton::Left, Point2D::new(x, y)); self.active_webview()
let event = EmbedderEvent::MouseWindowEventClass(mouse_event); .notify_pointer_button_event(MouseWindowEvent::Click(
self.process_event(event) MouseButton::Left,
Point2D::new(x, y),
));
self.maybe_perform_updates()
} }
pub fn key_down(&mut self, key: Key) -> Result<(), &'static str> { pub fn key_down(&mut self, key: Key) {
let key_event = KeyboardEvent { let key_event = KeyboardEvent {
state: KeyState::Down, state: KeyState::Down,
key, key,
..KeyboardEvent::default() ..KeyboardEvent::default()
}; };
self.process_event(EmbedderEvent::Keyboard(key_event)) self.active_webview().notify_keyboard_event(key_event);
self.maybe_perform_updates()
} }
pub fn key_up(&mut self, key: Key) -> Result<(), &'static str> { pub fn key_up(&mut self, key: Key) {
let key_event = KeyboardEvent { let key_event = KeyboardEvent {
state: KeyState::Up, state: KeyState::Up,
key, key,
..KeyboardEvent::default() ..KeyboardEvent::default()
}; };
self.process_event(EmbedderEvent::Keyboard(key_event)) self.active_webview().notify_keyboard_event(key_event);
self.maybe_perform_updates()
} }
pub fn ime_insert_text(&mut self, text: String) -> Result<(), &'static str> { pub fn ime_insert_text(&mut self, text: String) {
self.process_event(EmbedderEvent::IMEComposition(CompositionEvent { self.active_webview().notify_ime_event(CompositionEvent {
state: CompositionState::End, state: CompositionState::End,
data: text, data: text,
})) });
self.maybe_perform_updates()
} }
pub fn pause_compositor(&mut self) -> Result<(), &'static str> { pub fn notify_vsync(&mut self) {
self.process_event(EmbedderEvent::InvalidateNativeSurface) self.active_webview().notify_vsync();
self.maybe_perform_updates()
} }
pub fn resume_compositor( pub fn pause_compositor(&mut self) {
&mut self, self.active_webview().invalidate_native_surface();
native_surface: *mut c_void, self.maybe_perform_updates();
coords: Coordinates, }
) -> Result<(), &'static str> {
pub fn resume_compositor(&mut self, native_surface: *mut c_void, coords: Coordinates) {
if native_surface.is_null() { if native_surface.is_null() {
panic!("null passed for native_surface"); panic!("null passed for native_surface");
} }
self.process_event(EmbedderEvent::ReplaceNativeSurface( self.active_webview()
native_surface, .replace_native_surface(native_surface, coords.framebuffer);
coords.framebuffer, self.maybe_perform_updates()
))
} }
pub fn media_session_action( pub fn media_session_action(&mut self, action: MediaSessionActionType) {
&mut self,
action: MediaSessionActionType,
) -> Result<(), &'static str> {
info!("Media session action {:?}", action); info!("Media session action {:?}", action);
self.process_event(EmbedderEvent::MediaSessionAction(action)) self.active_webview()
.notify_media_session_action_event(action);
self.maybe_perform_updates()
} }
pub fn set_throttled(&mut self, throttled: bool) -> Result<(), &'static str> { pub fn set_throttled(&mut self, throttled: bool) {
info!("set_throttled"); info!("set_throttled");
if let Ok(id) = self.get_browser_id() { self.active_webview().set_throttled(throttled);
let event = EmbedderEvent::SetWebViewThrottled(id, throttled); self.maybe_perform_updates()
self.process_event(event)
} else {
// Ignore visibility change if no browser has been created yet.
Ok(())
}
} }
pub fn ime_dismissed(&mut self) -> Result<(), &'static str> { pub fn ime_dismissed(&mut self) {
info!("ime_dismissed"); info!("ime_dismissed");
self.process_event(EmbedderEvent::IMEDismissed) self.active_webview().notify_ime_dismissed_event();
self.maybe_perform_updates()
} }
pub fn on_context_menu_closed( pub fn on_context_menu_closed(
@ -429,18 +441,14 @@ impl ServoGlue {
Ok(()) Ok(())
} }
pub(super) fn process_event(&mut self, event: EmbedderEvent) -> Result<(), &'static str> { fn maybe_perform_updates(&mut self) {
self.events.push(event); self.perform_updates();
if !self.batch_mode {
self.perform_updates()
} else {
Ok(())
}
} }
fn handle_servo_events(&mut self) -> Result<(), &'static str> { fn handle_servo_events(&mut self) -> Result<(), &'static str> {
let mut need_update = false; let mut need_update = false;
for (browser_id, event) in self.servo.get_events() { let events: Vec<_> = self.servo.get_events().collect();
for (browser_id, event) in events {
match event { match event {
EmbedderMsg::ChangePageTitle(title) => { EmbedderMsg::ChangePageTitle(title) => {
self.callbacks.host_callbacks.on_title_changed(title); self.callbacks.host_callbacks.on_title_changed(title);
@ -451,9 +459,7 @@ impl ServoGlue {
.callbacks .callbacks
.host_callbacks .host_callbacks
.on_allow_navigation(url.to_string()); .on_allow_navigation(url.to_string());
let window_event = self.servo.allow_navigation_response(pipeline_id, data);
EmbedderEvent::AllowNavigationResponse(pipeline_id, data);
self.events.push(window_event);
need_update = true; need_update = true;
} }
}, },
@ -515,39 +521,41 @@ impl ServoGlue {
}, },
}; };
if let Err(e) = res { if let Err(e) = res {
let reason = format!("Failed to send Prompt response: {}", e); self.active_webview()
self.events .send_error(format!("Failed to send Prompt response: {e}"));
.push(EmbedderEvent::SendError(browser_id, reason));
} }
}, },
EmbedderMsg::AllowOpeningWebView(response_chan) => { EmbedderMsg::AllowOpeningWebView(response_chan) => {
// Note: would be a place to handle pop-ups config. let new_webview = self.servo.new_auxiliary_webview();
// see Step 7 of #the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name let new_webview_id = new_webview.id();
if let Err(e) = response_chan.send(Some(WebViewId::new())) { 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); warn!("Failed to send AllowOpeningBrowser response: {}", e);
}; };
}, },
EmbedderMsg::WebViewOpened(new_webview_id) => { EmbedderMsg::WebViewOpened(new_webview_id) => {
self.webviews.insert(new_webview_id, WebView {}); if let Some(webview) = self.webviews.get(&new_webview_id) {
self.creation_order.push(new_webview_id); webview.focus();
self.events }
.push(EmbedderEvent::FocusWebView(new_webview_id));
}, },
EmbedderMsg::WebViewClosed(webview_id) => { EmbedderMsg::WebViewClosed(webview_id) => {
self.webviews.retain(|&id, _| id != webview_id); self.webviews.retain(|&id, _| id != webview_id);
self.creation_order.retain(|&id| id != webview_id); self.creation_order.retain(|&id| id != webview_id);
self.focused_webview_id = None; self.focused_webview_id = None;
if let Some(&newest_webview_id) = self.creation_order.last() {
self.events if let Some(newest_webview) = self.newest_webview() {
.push(EmbedderEvent::FocusWebView(newest_webview_id)); newest_webview.focus();
} else { } else {
self.events.push(EmbedderEvent::Quit); self.servo.start_shutting_down();
} }
}, },
EmbedderMsg::WebViewFocused(webview_id) => { EmbedderMsg::WebViewFocused(webview_id) => {
self.focused_webview_id = Some(webview_id); self.focused_webview_id = Some(webview_id);
self.events if let Some(webview) = self.webviews.get(&webview_id) {
.push(EmbedderEvent::ShowWebView(webview_id, true)); webview.show(true);
}
}, },
EmbedderMsg::WebViewBlurred => { EmbedderMsg::WebViewBlurred => {
self.focused_webview_id = None; self.focused_webview_id = None;
@ -648,7 +656,7 @@ impl ServoGlue {
} }
if need_update { if need_update {
let _ = self.perform_updates(); self.perform_updates();
} }
Ok(()) Ok(())
} }