mirror of
https://github.com/servo/servo.git
synced 2025-08-23 06:15:35 +01:00
Move android port code to servoshell (#32533)
Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
parent
6f64a5afad
commit
24906e1c21
17 changed files with 156 additions and 241 deletions
|
@ -17,8 +17,16 @@ name = "servo"
|
|||
path = "main.rs"
|
||||
bench = false
|
||||
|
||||
# Some of these dependencies are only needed for a specific target os, but
|
||||
# since build-scripts can't detect the cargo target os at build-time, we
|
||||
# must unconditionally add these dependencies. See https://github.com/rust-lang/cargo/issues/4932
|
||||
[build-dependencies]
|
||||
vergen = { version = "8.3.1", features = ["git", "git2"] }
|
||||
# Android and OpenHarmony
|
||||
gl_generator = "0.14"
|
||||
# Android only
|
||||
serde_json = { workspace = true }
|
||||
# MacOS only
|
||||
cc = "1.0"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
|
@ -47,26 +55,37 @@ webgl_backtrace = ["libservo/webgl_backtrace"]
|
|||
xr-profile = ["libservo/xr-profile"]
|
||||
|
||||
[dependencies]
|
||||
# For optional feature servo_allocator/use-system-allocator
|
||||
servo_allocator = { path = "../../components/allocator" }
|
||||
libc = { workspace = true }
|
||||
libservo = { path = "../../components/servo" }
|
||||
cfg-if = { workspace = true }
|
||||
log = { workspace = true }
|
||||
url = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
getopts = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.14"
|
||||
ipc-channel = { workspace = true }
|
||||
jni = "0.21.1"
|
||||
libloading = "0.8"
|
||||
serde_json = { workspace = true }
|
||||
servo-media = { workspace = true }
|
||||
surfman = { workspace = true, features = ["sm-angle-default"] }
|
||||
webxr = { git = "https://github.com/servo/webxr" }
|
||||
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
backtrace = { workspace = true }
|
||||
getopts = { workspace = true }
|
||||
|
||||
[target.'cfg(target_env = "ohos")'.dependencies]
|
||||
# force inprocess, until libc-rs is updated to include `shm_open` and unlink.
|
||||
# force inprocess, until libc-rs 0.2.156 is released containing
|
||||
# https://github.com/rust-lang/libc/commit/9e248e212c5602cb4e98676e4c21ea0382663a12
|
||||
ipc-channel = { workspace = true, features = ["force-inprocess"] }
|
||||
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_env = "ohos")))'.dependencies]
|
||||
# For optional feature servo_allocator/use-system-allocator
|
||||
servo_allocator = { path = "../../components/allocator" }
|
||||
arboard = { version = "3" }
|
||||
egui = { version = "0.26.2" }
|
||||
egui_glow = { version = "0.26.2", features = ["winit"] }
|
||||
|
|
|
@ -3,8 +3,12 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use gl_generator::{Api, Fallbacks, Profile, Registry};
|
||||
use serde_json::Value;
|
||||
use vergen::EmitBuilder;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
@ -39,6 +43,29 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
cc::Build::new()
|
||||
.file("platform/macos/count_threads.c")
|
||||
.compile("count_threads");
|
||||
} else if target_os == "android" {
|
||||
// Generate GL bindings. For now, we only support EGL.
|
||||
let mut file = File::create(out.join("egl_bindings.rs")).unwrap();
|
||||
Registry::new(Api::Egl, (1, 5), Profile::Core, Fallbacks::All, [])
|
||||
.write_bindings(gl_generator::StaticStructGenerator, &mut file)
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-lib=EGL");
|
||||
|
||||
// FIXME: We need this workaround since jemalloc-sys still links
|
||||
// to libgcc instead of libunwind, but Android NDK 23c and above
|
||||
// don't have libgcc. We can't disable jemalloc for Android as
|
||||
// in 64-bit aarch builds, the system allocator uses tagged
|
||||
// pointers by default which causes the assertions in SM & mozjs
|
||||
// to fail. See https://github.com/servo/servo/issues/32175.
|
||||
let mut libgcc = File::create(out.join("libgcc.a")).unwrap();
|
||||
libgcc.write_all(b"INPUT(-lunwind)").unwrap();
|
||||
println!("cargo:rustc-link-search=native={}", out.display());
|
||||
|
||||
let mut default_prefs = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
default_prefs.push("../../resources/prefs.json");
|
||||
let prefs: Value = serde_json::from_reader(File::open(&default_prefs).unwrap()).unwrap();
|
||||
let file = File::create(out.join("prefs.json")).unwrap();
|
||||
serde_json::to_writer(file, &prefs).unwrap();
|
||||
}
|
||||
|
||||
if let Err(error) = EmitBuilder::builder()
|
||||
|
|
941
ports/servoshell/egl/android.rs
Normal file
941
ports/servoshell/egl/android.rs
Normal file
|
@ -0,0 +1,941 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
mod simpleservo;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::os::raw::{c_char, c_int, c_void};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use android_logger::{self, Config, FilterBuilder};
|
||||
use jni::objects::{GlobalRef, JClass, JObject, JString, JValue, JValueOwned};
|
||||
use jni::sys::{jboolean, jfloat, jint, jobject, jstring, JNI_TRUE};
|
||||
use jni::{JNIEnv, JavaVM};
|
||||
use libc::{dup2, pipe, read};
|
||||
use log::{debug, error, info, warn};
|
||||
use simpleservo::{
|
||||
Coordinates, DeviceIntRect, EventLoopWaker, HostTrait, InitOptions, InputMethodType,
|
||||
MediaSessionPlaybackState, PromptResult, ServoGlue, SERVO,
|
||||
};
|
||||
|
||||
use super::gl_glue;
|
||||
|
||||
struct HostCallbacks {
|
||||
callbacks: GlobalRef,
|
||||
jvm: JavaVM,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn ANativeWindow_fromSurface(env: *mut jni::sys::JNIEnv, surface: jobject) -> *mut c_void;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn android_main() {
|
||||
// FIXME(mukilan): this android_main is only present to stop
|
||||
// the java side 'System.loadLibrary('servoshell') call from
|
||||
// failing due to undefined reference to android_main introduced
|
||||
// by winit's android-activity crate. There is no way to disable
|
||||
// this currently.
|
||||
}
|
||||
|
||||
fn call<F>(env: &mut JNIEnv, f: F)
|
||||
where
|
||||
F: Fn(&mut ServoGlue) -> Result<(), &str>,
|
||||
{
|
||||
SERVO.with(|s| {
|
||||
if let Err(error) = match s.borrow_mut().as_mut() {
|
||||
Some(ref mut s) => (f)(s),
|
||||
None => Err("Servo not available in this thread"),
|
||||
} {
|
||||
throw(env, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_version<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
) -> JString<'local> {
|
||||
let v = crate::servo_version();
|
||||
env.new_string(&v)
|
||||
.unwrap_or_else(|_str| JObject::null().into())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_init<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
_activity: JObject<'local>,
|
||||
opts: JObject<'local>,
|
||||
callbacks_obj: JObject<'local>,
|
||||
surface: JObject<'local>,
|
||||
) {
|
||||
let (opts, log, log_str, _gst_debug_str) = match get_options(&mut env, &opts, &surface) {
|
||||
Ok((opts, log, log_str, gst_debug_str)) => (opts, log, log_str, gst_debug_str),
|
||||
Err(err) => {
|
||||
throw(&mut env, &err);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
if log {
|
||||
// Note: Android debug logs are stripped from a release build.
|
||||
// debug!() will only show in a debug build. Use info!() if logs
|
||||
// should show up in adb logcat with a release build.
|
||||
let filters = [
|
||||
"servo",
|
||||
"servoshell",
|
||||
"servoshell::egl:gl_glue",
|
||||
// Show JS errors by default.
|
||||
"script::dom::bindings::error",
|
||||
// Show GL errors by default.
|
||||
"canvas::webgl_thread",
|
||||
"compositing::compositor",
|
||||
"constellation::constellation",
|
||||
];
|
||||
let mut filter_builder = FilterBuilder::new();
|
||||
for &module in &filters {
|
||||
filter_builder.filter_module(module, log::LevelFilter::Debug);
|
||||
}
|
||||
if let Some(log_str) = log_str {
|
||||
for module in log_str.split(',') {
|
||||
filter_builder.filter_module(module, log::LevelFilter::Debug);
|
||||
}
|
||||
}
|
||||
|
||||
android_logger::init_once(
|
||||
Config::default()
|
||||
.with_max_level(log::LevelFilter::Debug)
|
||||
.with_filter(filter_builder.build())
|
||||
.with_tag("servoshell"),
|
||||
)
|
||||
}
|
||||
|
||||
info!("init");
|
||||
|
||||
redirect_stdout_to_logcat();
|
||||
|
||||
let callbacks_ref = match env.new_global_ref(callbacks_obj) {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
throw(
|
||||
&mut env,
|
||||
"Failed to get global reference of callback argument",
|
||||
);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let wakeup = Box::new(WakeupCallback::new(callbacks_ref.clone(), &env));
|
||||
let callbacks = Box::new(HostCallbacks::new(callbacks_ref, &env));
|
||||
|
||||
if let Err(err) = gl_glue::init()
|
||||
.and_then(|egl_init| simpleservo::init(opts, egl_init.gl_wrapper, wakeup, callbacks))
|
||||
{
|
||||
throw(&mut env, err)
|
||||
};
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_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]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_requestShutdown<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
) {
|
||||
debug!("requestShutdown");
|
||||
call(&mut env, |s| s.request_shutdown());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_deinit<'local>(
|
||||
_env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
) {
|
||||
debug!("deinit");
|
||||
simpleservo::deinit();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_resize<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
coordinates: JObject<'local>,
|
||||
) {
|
||||
let coords = jni_coords_to_rust_coords(&mut env, &coordinates);
|
||||
debug!("resize {:#?}", coords);
|
||||
match coords {
|
||||
Ok(coords) => call(&mut env, |s| s.resize(coords.clone())),
|
||||
Err(error) => throw(&mut env, &error),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_performUpdates<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
) {
|
||||
debug!("performUpdates");
|
||||
call(&mut env, |s| s.perform_updates());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_loadUri<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
url: JString<'local>,
|
||||
) {
|
||||
debug!("loadUri");
|
||||
match env.get_string(&url) {
|
||||
Ok(url) => {
|
||||
let url: String = url.into();
|
||||
call(&mut env, |s| s.load_uri(&url));
|
||||
},
|
||||
Err(_) => {
|
||||
throw(&mut env, "Failed to convert Java string");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_reload<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
) {
|
||||
debug!("reload");
|
||||
call(&mut env, |s| s.reload());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_stop<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
) {
|
||||
debug!("stop");
|
||||
call(&mut env, |s| s.stop());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_refresh<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
) {
|
||||
debug!("refresh");
|
||||
call(&mut env, |s| s.refresh());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_goBack<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
) {
|
||||
debug!("goBack");
|
||||
call(&mut env, |s| s.go_back());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_goForward<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
) {
|
||||
debug!("goForward");
|
||||
call(&mut env, |s| s.go_forward());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_scrollStart<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
dx: jint,
|
||||
dy: jint,
|
||||
x: jint,
|
||||
y: jint,
|
||||
) {
|
||||
debug!("scrollStart");
|
||||
call(&mut env, |s| {
|
||||
s.scroll_start(dx as f32, dy as f32, x as i32, y as i32)
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_scrollEnd<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
dx: jint,
|
||||
dy: jint,
|
||||
x: jint,
|
||||
y: jint,
|
||||
) {
|
||||
debug!("scrollEnd");
|
||||
call(&mut env, |s| {
|
||||
s.scroll_end(dx as f32, dy as f32, x as i32, y as i32)
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_scroll<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
dx: jint,
|
||||
dy: jint,
|
||||
x: jint,
|
||||
y: jint,
|
||||
) {
|
||||
debug!("scroll");
|
||||
call(&mut env, |s| {
|
||||
s.scroll(dx as f32, dy as f32, x as i32, y as i32)
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_touchDown<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
x: jfloat,
|
||||
y: jfloat,
|
||||
pointer_id: jint,
|
||||
) {
|
||||
debug!("touchDown");
|
||||
call(&mut env, |s| s.touch_down(x, y, pointer_id as i32));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_touchUp<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
x: jfloat,
|
||||
y: jfloat,
|
||||
pointer_id: jint,
|
||||
) {
|
||||
debug!("touchUp");
|
||||
call(&mut env, |s| s.touch_up(x, y, pointer_id as i32));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_touchMove<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
x: jfloat,
|
||||
y: jfloat,
|
||||
pointer_id: jint,
|
||||
) {
|
||||
debug!("touchMove");
|
||||
call(&mut env, |s| s.touch_move(x, y, pointer_id as i32));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_touchCancel<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
x: jfloat,
|
||||
y: jfloat,
|
||||
pointer_id: jint,
|
||||
) {
|
||||
debug!("touchCancel");
|
||||
call(&mut env, |s| s.touch_cancel(x, y, pointer_id as i32));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_pinchZoomStart<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
factor: jfloat,
|
||||
x: jint,
|
||||
y: jint,
|
||||
) {
|
||||
debug!("pinchZoomStart");
|
||||
call(&mut env, |s| {
|
||||
s.pinchzoom_start(factor as f32, x as u32, y as u32)
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_pinchZoom<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
factor: jfloat,
|
||||
x: jint,
|
||||
y: jint,
|
||||
) {
|
||||
debug!("pinchZoom");
|
||||
call(&mut env, |s| s.pinchzoom(factor as f32, x as u32, y as u32));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_pinchZoomEnd<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
factor: jfloat,
|
||||
x: jint,
|
||||
y: jint,
|
||||
) {
|
||||
debug!("pinchZoomEnd");
|
||||
call(&mut env, |s| {
|
||||
s.pinchzoom_end(factor as f32, x as u32, y as u32)
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_click<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
x: jfloat,
|
||||
y: jfloat,
|
||||
) {
|
||||
debug!("click");
|
||||
call(&mut env, |s| s.click(x as f32, y as f32));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_pauseCompositor<'local>(
|
||||
mut env: JNIEnv,
|
||||
_: JClass<'local>,
|
||||
) {
|
||||
debug!("pauseCompositor");
|
||||
call(&mut env, |s| s.pause_compositor());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_resumeCompositor<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
surface: JObject<'local>,
|
||||
coordinates: JObject<'local>,
|
||||
) {
|
||||
debug!("resumeCompositor");
|
||||
let widget = unsafe { ANativeWindow_fromSurface(env.get_native_interface(), surface.as_raw()) };
|
||||
let coords = jni_coords_to_rust_coords(&mut env, &coordinates);
|
||||
match coords {
|
||||
Ok(coords) => call(&mut env, |s| s.resume_compositor(widget, coords.clone())),
|
||||
Err(error) => throw(&mut env, &error),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_mozilla_servoview_JNIServo_mediaSessionAction<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_: JClass<'local>,
|
||||
action: jint,
|
||||
) {
|
||||
debug!("mediaSessionAction");
|
||||
call(&mut env, |s| s.media_session_action((action as i32).into()));
|
||||
}
|
||||
|
||||
pub struct WakeupCallback {
|
||||
callback: GlobalRef,
|
||||
jvm: Arc<JavaVM>,
|
||||
}
|
||||
|
||||
impl WakeupCallback {
|
||||
pub fn new(callback: GlobalRef, env: &JNIEnv) -> WakeupCallback {
|
||||
let jvm = Arc::new(env.get_java_vm().unwrap());
|
||||
WakeupCallback { callback, jvm }
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWaker for WakeupCallback {
|
||||
fn clone_box(&self) -> Box<dyn EventLoopWaker> {
|
||||
Box::new(WakeupCallback {
|
||||
callback: self.callback.clone(),
|
||||
jvm: self.jvm.clone(),
|
||||
})
|
||||
}
|
||||
fn wake(&self) {
|
||||
debug!("wakeup");
|
||||
let mut env = self.jvm.attach_current_thread().unwrap();
|
||||
env.call_method(self.callback.as_obj(), "wakeup", "()V", &[])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl HostCallbacks {
|
||||
pub fn new(callbacks: GlobalRef, env: &JNIEnv) -> HostCallbacks {
|
||||
let jvm = env.get_java_vm().unwrap();
|
||||
HostCallbacks { callbacks, jvm }
|
||||
}
|
||||
}
|
||||
|
||||
impl HostTrait for HostCallbacks {
|
||||
fn prompt_alert(&self, message: String, _trusted: bool) {
|
||||
debug!("prompt_alert");
|
||||
let mut env = self.jvm.get_env().unwrap();
|
||||
let Ok(string) = new_string_as_jvalue(&mut env, &message) else {
|
||||
return;
|
||||
};
|
||||
env.call_method(
|
||||
self.callbacks.as_obj(),
|
||||
"onAlert",
|
||||
"(Ljava/lang/String;)V",
|
||||
&[(&string).into()],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn prompt_ok_cancel(&self, message: String, _trusted: bool) -> PromptResult {
|
||||
warn!("Prompt not implemented. Cancelled. {}", message);
|
||||
PromptResult::Secondary
|
||||
}
|
||||
|
||||
fn prompt_yes_no(&self, message: String, _trusted: bool) -> PromptResult {
|
||||
warn!("Prompt not implemented. Cancelled. {}", message);
|
||||
PromptResult::Secondary
|
||||
}
|
||||
|
||||
fn prompt_input(&self, message: String, default: String, _trusted: bool) -> Option<String> {
|
||||
warn!("Input prompt not implemented. {}", message);
|
||||
Some(default)
|
||||
}
|
||||
|
||||
fn on_load_started(&self) {
|
||||
debug!("on_load_started");
|
||||
let mut env = self.jvm.get_env().unwrap();
|
||||
env.call_method(self.callbacks.as_obj(), "onLoadStarted", "()V", &[])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn on_load_ended(&self) {
|
||||
debug!("on_load_ended");
|
||||
let mut env = self.jvm.get_env().unwrap();
|
||||
env.call_method(self.callbacks.as_obj(), "onLoadEnded", "()V", &[])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn on_shutdown_complete(&self) {
|
||||
debug!("on_shutdown_complete");
|
||||
let mut env = self.jvm.get_env().unwrap();
|
||||
env.call_method(self.callbacks.as_obj(), "onShutdownComplete", "()V", &[])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn on_title_changed(&self, title: Option<String>) {
|
||||
debug!("on_title_changed");
|
||||
let mut env = self.jvm.get_env().unwrap();
|
||||
let title = title.unwrap_or_else(String::new);
|
||||
let Ok(title_string) = new_string_as_jvalue(&mut env, &title) else {
|
||||
return;
|
||||
};
|
||||
env.call_method(
|
||||
self.callbacks.as_obj(),
|
||||
"onTitleChanged",
|
||||
"(Ljava/lang/String;)V",
|
||||
&[(&title_string).into()],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn on_allow_navigation(&self, url: String) -> bool {
|
||||
debug!("on_allow_navigation");
|
||||
let mut env = self.jvm.get_env().unwrap();
|
||||
let Ok(url_string) = new_string_as_jvalue(&mut env, &url) else {
|
||||
return false;
|
||||
};
|
||||
let allow = env.call_method(
|
||||
self.callbacks.as_obj(),
|
||||
"onAllowNavigation",
|
||||
"(Ljava/lang/String;)Z",
|
||||
&[(&url_string).into()],
|
||||
);
|
||||
match allow {
|
||||
Ok(allow) => return allow.z().unwrap(),
|
||||
Err(_) => return true,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_url_changed(&self, url: String) {
|
||||
debug!("on_url_changed");
|
||||
let mut env = self.jvm.get_env().unwrap();
|
||||
let Ok(url_string) = new_string_as_jvalue(&mut env, &url) else {
|
||||
return;
|
||||
};
|
||||
env.call_method(
|
||||
self.callbacks.as_obj(),
|
||||
"onUrlChanged",
|
||||
"(Ljava/lang/String;)V",
|
||||
&[(&url_string).into()],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn on_history_changed(&self, can_go_back: bool, can_go_forward: bool) {
|
||||
debug!("on_history_changed");
|
||||
let mut env = self.jvm.get_env().unwrap();
|
||||
let can_go_back = JValue::Bool(can_go_back as jboolean);
|
||||
let can_go_forward = JValue::Bool(can_go_forward as jboolean);
|
||||
env.call_method(
|
||||
self.callbacks.as_obj(),
|
||||
"onHistoryChanged",
|
||||
"(ZZ)V",
|
||||
&[can_go_back, can_go_forward],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn on_animating_changed(&self, animating: bool) {
|
||||
debug!("on_animating_changed");
|
||||
let mut env = self.jvm.get_env().unwrap();
|
||||
let animating = JValue::Bool(animating as jboolean);
|
||||
env.call_method(
|
||||
self.callbacks.as_obj(),
|
||||
"onAnimatingChanged",
|
||||
"(Z)V",
|
||||
&[animating],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn on_ime_show(
|
||||
&self,
|
||||
_input_type: InputMethodType,
|
||||
_text: Option<(String, i32)>,
|
||||
_multiline: bool,
|
||||
_rect: DeviceIntRect,
|
||||
) {
|
||||
}
|
||||
fn on_ime_hide(&self) {}
|
||||
|
||||
fn get_clipboard_contents(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn set_clipboard_contents(&self, _contents: String) {}
|
||||
|
||||
fn on_media_session_metadata(&self, title: String, artist: String, album: String) {
|
||||
info!("on_media_session_metadata");
|
||||
let mut env = self.jvm.get_env().unwrap();
|
||||
let Ok(title) = new_string_as_jvalue(&mut env, &title) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(artist) = new_string_as_jvalue(&mut env, &artist) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(album) = new_string_as_jvalue(&mut env, &album) else {
|
||||
return;
|
||||
};
|
||||
|
||||
env.call_method(
|
||||
self.callbacks.as_obj(),
|
||||
"onMediaSessionMetadata",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
|
||||
&[(&title).into(), (&artist).into(), (&album).into()],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn on_media_session_playback_state_change(&self, state: MediaSessionPlaybackState) {
|
||||
info!("on_media_session_playback_state_change {:?}", state);
|
||||
let mut env = self.jvm.get_env().unwrap();
|
||||
let state = state as i32;
|
||||
let state = JValue::Int(state as jint);
|
||||
env.call_method(
|
||||
self.callbacks.as_obj(),
|
||||
"onMediaSessionPlaybackStateChange",
|
||||
"(I)V",
|
||||
&[state],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn on_media_session_set_position_state(
|
||||
&self,
|
||||
duration: f64,
|
||||
position: f64,
|
||||
playback_rate: f64,
|
||||
) {
|
||||
info!(
|
||||
"on_media_session_playback_state_change ({:?}, {:?}, {:?})",
|
||||
duration, position, playback_rate
|
||||
);
|
||||
let mut env = self.jvm.get_env().unwrap();
|
||||
let duration = JValue::Float(duration as jfloat);
|
||||
let position = JValue::Float(position as jfloat);
|
||||
let playback_rate = JValue::Float(playback_rate as jfloat);
|
||||
|
||||
env.call_method(
|
||||
self.callbacks.as_obj(),
|
||||
"onMediaSessionSetPositionState",
|
||||
"(FFF)V",
|
||||
&[duration, position, playback_rate],
|
||||
)
|
||||
.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>) {}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
|
||||
}
|
||||
|
||||
fn redirect_stdout_to_logcat() {
|
||||
// The first step is to redirect stdout and stderr to the logs.
|
||||
// We redirect stdout and stderr to a custom descriptor.
|
||||
let mut pfd: [c_int; 2] = [0, 0];
|
||||
unsafe {
|
||||
pipe(pfd.as_mut_ptr());
|
||||
dup2(pfd[1], 1);
|
||||
dup2(pfd[1], 2);
|
||||
}
|
||||
|
||||
let descriptor = pfd[0];
|
||||
|
||||
// Then we spawn a thread whose only job is to read from the other side of the
|
||||
// pipe and redirect to the logs.
|
||||
let _detached = thread::spawn(move || {
|
||||
const BUF_LENGTH: usize = 512;
|
||||
let mut buf = vec![b'\0' as c_char; BUF_LENGTH];
|
||||
|
||||
// Always keep at least one null terminator
|
||||
const BUF_AVAILABLE: usize = BUF_LENGTH - 1;
|
||||
let buf = &mut buf[..BUF_AVAILABLE];
|
||||
|
||||
let mut cursor = 0_usize;
|
||||
|
||||
let tag = b"servoshell\0".as_ptr() as _;
|
||||
|
||||
loop {
|
||||
let result = {
|
||||
let read_into = &mut buf[cursor..];
|
||||
unsafe {
|
||||
read(
|
||||
descriptor,
|
||||
read_into.as_mut_ptr() as *mut _,
|
||||
read_into.len(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let end = if result == 0 {
|
||||
return;
|
||||
} else if result < 0 {
|
||||
unsafe {
|
||||
__android_log_write(
|
||||
3,
|
||||
tag,
|
||||
b"error in log thread; closing\0".as_ptr() as *const _,
|
||||
);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
result as usize + cursor
|
||||
};
|
||||
|
||||
// Only modify the portion of the buffer that contains real data.
|
||||
let buf = &mut buf[0..end];
|
||||
|
||||
if let Some(last_newline_pos) = buf.iter().rposition(|&c| c == b'\n' as c_char) {
|
||||
buf[last_newline_pos] = b'\0' as c_char;
|
||||
unsafe {
|
||||
__android_log_write(3, tag, buf.as_ptr());
|
||||
}
|
||||
if last_newline_pos < buf.len() - 1 {
|
||||
let pos_after_newline = last_newline_pos + 1;
|
||||
let len_not_logged_yet = buf[pos_after_newline..].len();
|
||||
for j in 0..len_not_logged_yet as usize {
|
||||
buf[j] = buf[pos_after_newline + j];
|
||||
}
|
||||
cursor = len_not_logged_yet;
|
||||
} else {
|
||||
cursor = 0;
|
||||
}
|
||||
} else if end == BUF_AVAILABLE {
|
||||
// No newline found but the buffer is full, flush it anyway.
|
||||
// `buf.as_ptr()` is null-terminated by BUF_LENGTH being 1 less than BUF_AVAILABLE.
|
||||
unsafe {
|
||||
__android_log_write(3, tag, buf.as_ptr());
|
||||
}
|
||||
cursor = 0;
|
||||
} else {
|
||||
cursor = end;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn throw(env: &mut JNIEnv, err: &str) {
|
||||
if let Err(e) = env.throw(("java/lang/Exception", err)) {
|
||||
warn!(
|
||||
"Failed to throw Java exception: `{}`. Exception was: `{}`",
|
||||
e, err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn new_string(env: &mut JNIEnv, s: &str) -> Result<jstring, &'static str> {
|
||||
match env.new_string(s) {
|
||||
Ok(s) => Ok(s.into_raw()),
|
||||
Err(_) => {
|
||||
throw(env, "Couldn't create Java string");
|
||||
Err("Couldn't create Java String")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn new_string_as_jvalue<'local>(
|
||||
env: &mut JNIEnv<'local>,
|
||||
input_string: &str,
|
||||
) -> Result<JValueOwned<'local>, &'static str> {
|
||||
let jstring = match env.new_string(input_string) {
|
||||
Ok(jstring) => jstring,
|
||||
Err(_) => {
|
||||
throw(env, "Couldn't create Java string");
|
||||
return Err("Couldn't create Java string");
|
||||
},
|
||||
};
|
||||
Ok(JValueOwned::from(jstring))
|
||||
}
|
||||
|
||||
fn jni_coords_to_rust_coords<'local>(
|
||||
env: &mut JNIEnv<'local>,
|
||||
obj: &JObject<'local>,
|
||||
) -> Result<Coordinates, String> {
|
||||
let x = get_non_null_field(env, obj, "x", "I")?
|
||||
.i()
|
||||
.map_err(|_| "x not an int")? as i32;
|
||||
let y = get_non_null_field(env, obj, "y", "I")?
|
||||
.i()
|
||||
.map_err(|_| "y not an int")? as i32;
|
||||
let width = get_non_null_field(env, obj, "width", "I")?
|
||||
.i()
|
||||
.map_err(|_| "width not an int")? as i32;
|
||||
let height = get_non_null_field(env, obj, "height", "I")?
|
||||
.i()
|
||||
.map_err(|_| "height not an int")? as i32;
|
||||
let fb_width = get_non_null_field(env, obj, "fb_width", "I")?
|
||||
.i()
|
||||
.map_err(|_| "fb_width not an int")? as i32;
|
||||
let fb_height = get_non_null_field(env, obj, "fb_height", "I")?
|
||||
.i()
|
||||
.map_err(|_| "fb_height not an int")? as i32;
|
||||
Ok(Coordinates::new(x, y, width, height, fb_width, fb_height))
|
||||
}
|
||||
|
||||
fn get_field<'local>(
|
||||
env: &mut JNIEnv<'local>,
|
||||
obj: &JObject<'local>,
|
||||
field: &str,
|
||||
type_: &str,
|
||||
) -> Result<Option<JValueOwned<'local>>, String> {
|
||||
let Ok(class) = env.get_object_class(obj) else {
|
||||
return Err("Can't get object class".to_owned());
|
||||
};
|
||||
|
||||
if env.get_field_id(class, field, type_).is_err() {
|
||||
return Err(format!("Can't find `{}` field", field));
|
||||
}
|
||||
|
||||
env.get_field(obj, field, type_)
|
||||
.map(|value| Some(value))
|
||||
.or_else(|_| Err(format!("Can't find `{}` field", field)))
|
||||
}
|
||||
|
||||
fn get_non_null_field<'local>(
|
||||
env: &mut JNIEnv<'local>,
|
||||
obj: &JObject<'local>,
|
||||
field: &str,
|
||||
type_: &str,
|
||||
) -> Result<JValueOwned<'local>, String> {
|
||||
match get_field(env, obj, field, type_)? {
|
||||
None => Err(format!("Field {} is null", field)),
|
||||
Some(f) => Ok(f),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_field_as_string<'local>(
|
||||
env: &mut JNIEnv<'local>,
|
||||
obj: &JObject<'local>,
|
||||
field: &str,
|
||||
) -> Result<Option<String>, String> {
|
||||
let string = {
|
||||
let value = get_field(env, obj, field, "Ljava/lang/String;")?;
|
||||
let Some(value) = value else {
|
||||
return Ok(None);
|
||||
};
|
||||
value
|
||||
.l()
|
||||
.map_err(|_| format!("field `{}` is not an Object", field))?
|
||||
};
|
||||
|
||||
Ok(env.get_string((&string).into()).map(|s| s.into()).ok())
|
||||
}
|
||||
|
||||
fn get_options<'local>(
|
||||
env: &mut JNIEnv<'local>,
|
||||
opts: &JObject<'local>,
|
||||
surface: &JObject<'local>,
|
||||
) -> Result<(InitOptions, bool, Option<String>, Option<String>), String> {
|
||||
let args = get_field_as_string(env, opts, "args")?;
|
||||
let url = get_field_as_string(env, opts, "url")?;
|
||||
let log_str = get_field_as_string(env, opts, "logStr")?;
|
||||
let gst_debug_str = get_field_as_string(env, opts, "gstDebugStr")?;
|
||||
let density = get_non_null_field(env, opts, "density", "F")?
|
||||
.f()
|
||||
.map_err(|_| "density not a float")? as f32;
|
||||
let log = get_non_null_field(env, opts, "enableLogs", "Z")?
|
||||
.z()
|
||||
.map_err(|_| "enableLogs not a boolean")?;
|
||||
let coordinates = get_non_null_field(
|
||||
env,
|
||||
opts,
|
||||
"coordinates",
|
||||
"Lorg/mozilla/servoview/JNIServo$ServoCoordinates;",
|
||||
)?
|
||||
.l()
|
||||
.map_err(|_| "coordinates is not an object")?;
|
||||
let coordinates = jni_coords_to_rust_coords(env, &coordinates)?;
|
||||
|
||||
let args = match args {
|
||||
Some(args) => serde_json::from_str(&args)
|
||||
.map_err(|_| "Invalid arguments. Servo arguments must be formatted as a JSON array")?,
|
||||
None => None,
|
||||
};
|
||||
|
||||
let native_window =
|
||||
unsafe { ANativeWindow_fromSurface(env.get_native_interface(), surface.as_raw()) };
|
||||
|
||||
// FIXME: enable JIT compilation on 32-bit Android after the startup crash issue (#31134) is fixed.
|
||||
let prefs = if cfg!(target_pointer_width = "32") {
|
||||
let mut prefs = HashMap::new();
|
||||
prefs.insert("js.baseline_interpreter.enabled".to_string(), false.into());
|
||||
prefs.insert("js.baseline_jit.enabled".to_string(), false.into());
|
||||
prefs.insert("js.ion.enabled".to_string(), false.into());
|
||||
Some(prefs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let opts = InitOptions {
|
||||
args: args.unwrap_or(vec![]),
|
||||
url,
|
||||
coordinates,
|
||||
density,
|
||||
xr_discovery: None,
|
||||
surfman_integration: simpleservo::SurfmanIntegration::Widget(native_window),
|
||||
prefs,
|
||||
};
|
||||
|
||||
Ok((opts, log, log_str, gst_debug_str))
|
||||
}
|
953
ports/servoshell/egl/android/simpleservo.rs
Normal file
953
ports/servoshell/egl/android/simpleservo.rs
Normal file
|
@ -0,0 +1,953 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use getopts::Options;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use log::{debug, info, warn};
|
||||
use servo::base::id::WebViewId;
|
||||
use servo::compositing::windowing::{
|
||||
AnimationState, EmbedderCoordinates, EmbedderEvent, EmbedderMethods, MouseWindowEvent,
|
||||
WindowMethods,
|
||||
};
|
||||
use servo::compositing::CompositeTarget;
|
||||
use servo::config::prefs::pref_map;
|
||||
pub use servo::config::prefs::{add_user_prefs, PrefValue};
|
||||
use servo::embedder_traits::resources::{self, Resource, ResourceReaderMethods};
|
||||
pub use servo::embedder_traits::{
|
||||
ContextMenuResult, InputMethodType, MediaSessionPlaybackState, PermissionPrompt,
|
||||
PermissionRequest, PromptResult,
|
||||
};
|
||||
use servo::embedder_traits::{
|
||||
EmbedderMsg, EmbedderProxy, MediaSessionEvent, PromptDefinition, PromptOrigin,
|
||||
};
|
||||
use servo::euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
|
||||
use servo::keyboard_types::{Key, KeyState, KeyboardEvent};
|
||||
use servo::net_traits::pub_domains::is_reg_domain;
|
||||
pub use servo::script_traits::{MediaSessionActionType, MouseButton};
|
||||
use servo::script_traits::{TouchEventType, TouchId, TraversalDirection};
|
||||
use servo::servo_config::{opts, pref};
|
||||
use servo::servo_url::ServoUrl;
|
||||
pub use servo::webrender_api::units::DeviceIntRect;
|
||||
use servo::webrender_api::units::DevicePixel;
|
||||
use servo::webrender_api::ScrollLocation;
|
||||
use servo::webrender_traits::RenderingContext;
|
||||
use servo::{self, gl, Servo, TopLevelBrowsingContextId};
|
||||
use surfman::{Connection, SurfaceType};
|
||||
|
||||
thread_local! {
|
||||
pub static SERVO: RefCell<Option<ServoGlue>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
/// The EventLoopWaker::wake function will be called from any thread.
|
||||
/// It will be called to notify embedder that some events are available,
|
||||
/// and that perform_updates need to be called
|
||||
pub use servo::embedder_traits::EventLoopWaker;
|
||||
|
||||
pub struct InitOptions {
|
||||
pub args: Vec<String>,
|
||||
pub url: Option<String>,
|
||||
pub coordinates: Coordinates,
|
||||
pub density: f32,
|
||||
pub xr_discovery: Option<webxr::Discovery>,
|
||||
pub surfman_integration: SurfmanIntegration,
|
||||
pub prefs: Option<HashMap<String, PrefValue>>,
|
||||
}
|
||||
|
||||
/// Controls how this embedding's rendering will integrate with the embedder.
|
||||
pub enum SurfmanIntegration {
|
||||
/// Render directly to a provided native widget (see surfman::NativeWidget).
|
||||
Widget(*mut c_void),
|
||||
/// Render to an offscreen surface.
|
||||
Surface,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Coordinates {
|
||||
pub viewport: Rect<i32, DevicePixel>,
|
||||
pub framebuffer: Size2D<i32, DevicePixel>,
|
||||
}
|
||||
|
||||
impl Coordinates {
|
||||
pub fn new(
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
fb_width: i32,
|
||||
fb_height: i32,
|
||||
) -> Coordinates {
|
||||
Coordinates {
|
||||
viewport: Rect::new(Point2D::new(x, y), Size2D::new(width, height)),
|
||||
framebuffer: Size2D::new(fb_width, fb_height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Callbacks. Implemented by embedder. Called by Servo.
|
||||
pub trait HostTrait {
|
||||
/// Show alert.
|
||||
fn prompt_alert(&self, msg: String, trusted: bool);
|
||||
/// Ask Yes/No question.
|
||||
fn prompt_yes_no(&self, msg: String, trusted: bool) -> PromptResult;
|
||||
/// Ask Ok/Cancel question.
|
||||
fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult;
|
||||
/// Ask for string
|
||||
fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option<String>;
|
||||
/// Show context menu
|
||||
fn show_context_menu(&self, title: Option<String>, items: Vec<String>);
|
||||
/// Page starts loading.
|
||||
/// "Reload button" should be disabled.
|
||||
/// "Stop button" should be enabled.
|
||||
/// Throbber starts spinning.
|
||||
fn on_load_started(&self);
|
||||
/// Page has loaded.
|
||||
/// "Reload button" should be enabled.
|
||||
/// "Stop button" should be disabled.
|
||||
/// Throbber stops spinning.
|
||||
fn on_load_ended(&self);
|
||||
/// Page title has changed.
|
||||
fn on_title_changed(&self, title: Option<String>);
|
||||
/// Allow Navigation.
|
||||
fn on_allow_navigation(&self, url: String) -> bool;
|
||||
/// Page URL has changed.
|
||||
fn on_url_changed(&self, url: String);
|
||||
/// Back/forward state has changed.
|
||||
/// Back/forward buttons need to be disabled/enabled.
|
||||
fn on_history_changed(&self, can_go_back: bool, can_go_forward: bool);
|
||||
/// Page animation state has changed. If animating, it's recommended
|
||||
/// that the embedder doesn't wait for the wake function to be called
|
||||
/// to call perform_updates. Usually, it means doing:
|
||||
/// while true { servo.perform_updates() }. This will end up calling flush
|
||||
/// which will call swap_buffer which will be blocking long enough to limit
|
||||
/// drawing at 60 FPS.
|
||||
/// If not animating, call perform_updates only when needed (when the embedder
|
||||
/// has events for Servo, or Servo has woken up the embedder event loop via
|
||||
/// EventLoopWaker).
|
||||
fn on_animating_changed(&self, animating: bool);
|
||||
/// Servo finished shutting down.
|
||||
fn on_shutdown_complete(&self);
|
||||
/// A text input is focused.
|
||||
fn on_ime_show(
|
||||
&self,
|
||||
input_type: InputMethodType,
|
||||
text: Option<(String, i32)>,
|
||||
multiline: bool,
|
||||
bounds: DeviceIntRect,
|
||||
);
|
||||
/// Input lost focus
|
||||
fn on_ime_hide(&self);
|
||||
/// Gets sytem clipboard contents.
|
||||
fn get_clipboard_contents(&self) -> Option<String>;
|
||||
/// Sets system clipboard contents.
|
||||
fn set_clipboard_contents(&self, contents: String);
|
||||
/// Called when we get the media session metadata/
|
||||
fn on_media_session_metadata(&self, title: String, artist: String, album: String);
|
||||
/// Called when the media session playback state changes.
|
||||
fn on_media_session_playback_state_change(&self, state: MediaSessionPlaybackState);
|
||||
/// Called when the media session position state is set.
|
||||
fn on_media_session_set_position_state(&self, duration: f64, position: f64, playback_rate: f64);
|
||||
/// Called when devtools server is started
|
||||
fn on_devtools_started(&self, port: Result<u16, ()>, token: String);
|
||||
/// Called when we get a panic message from constellation
|
||||
fn on_panic(&self, reason: String, backtrace: Option<String>);
|
||||
}
|
||||
|
||||
pub struct ServoGlue {
|
||||
rendering_context: RenderingContext,
|
||||
servo: Servo<ServoWindowCallbacks>,
|
||||
batch_mode: bool,
|
||||
callbacks: Rc<ServoWindowCallbacks>,
|
||||
events: Vec<EmbedderEvent>,
|
||||
context_menu_sender: Option<IpcSender<ContextMenuResult>>,
|
||||
|
||||
/// List of top-level browsing contexts.
|
||||
/// Modified by EmbedderMsg::WebViewOpened and EmbedderMsg::WebViewClosed,
|
||||
/// and we exit if it ever becomes empty.
|
||||
webviews: HashMap<WebViewId, WebView>,
|
||||
|
||||
/// The order in which the webviews were created.
|
||||
creation_order: Vec<WebViewId>,
|
||||
|
||||
/// The webview that is currently focused.
|
||||
/// Modified by EmbedderMsg::WebViewFocused and EmbedderMsg::WebViewBlurred.
|
||||
focused_webview_id: Option<WebViewId>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WebView {}
|
||||
|
||||
/// Test if a url is valid.
|
||||
pub fn is_uri_valid(url: &str) -> bool {
|
||||
info!("load_uri: {}", url);
|
||||
ServoUrl::parse(url).is_ok()
|
||||
}
|
||||
|
||||
/// Retrieve a snapshot of the current preferences
|
||||
pub fn get_prefs() -> HashMap<String, (PrefValue, bool)> {
|
||||
pref_map()
|
||||
.iter()
|
||||
.map(|(key, value)| {
|
||||
let is_default = pref_map().is_default(&key).unwrap();
|
||||
(key, (value, is_default))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Retrieve a preference.
|
||||
pub fn get_pref(key: &str) -> (PrefValue, bool) {
|
||||
if let Ok(is_default) = pref_map().is_default(&key) {
|
||||
(pref_map().get(key), is_default)
|
||||
} else {
|
||||
(PrefValue::Missing, false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore a preference to its default value.
|
||||
pub fn reset_pref(key: &str) -> bool {
|
||||
pref_map().reset(key).is_ok()
|
||||
}
|
||||
|
||||
/// Restore all the preferences to their default values.
|
||||
pub fn reset_all_prefs() {
|
||||
pref_map().reset_all();
|
||||
}
|
||||
|
||||
/// Change the value of a preference.
|
||||
pub fn set_pref(key: &str, val: PrefValue) -> Result<(), &'static str> {
|
||||
pref_map()
|
||||
.set(key, val)
|
||||
.map(|_| ())
|
||||
.map_err(|_| "Pref set failed")
|
||||
}
|
||||
|
||||
/// Initialize Servo. At that point, we need a valid GL context.
|
||||
/// In the future, this will be done in multiple steps.
|
||||
pub fn init(
|
||||
mut init_opts: InitOptions,
|
||||
gl: Rc<dyn gl::Gl>,
|
||||
waker: Box<dyn EventLoopWaker>,
|
||||
callbacks: Box<dyn HostTrait>,
|
||||
) -> Result<(), &'static str> {
|
||||
resources::set(Box::new(ResourceReaderInstance::new()));
|
||||
|
||||
if let Some(prefs) = init_opts.prefs {
|
||||
add_user_prefs(prefs);
|
||||
}
|
||||
|
||||
let mut args = mem::replace(&mut init_opts.args, vec![]);
|
||||
// opts::from_cmdline_args expects the first argument to be the binary name.
|
||||
args.insert(0, "servo".to_string());
|
||||
opts::from_cmdline_args(Options::new(), &args);
|
||||
|
||||
let embedder_url = init_opts.url.as_ref().and_then(|s| ServoUrl::parse(s).ok());
|
||||
let pref_url = ServoUrl::parse(&pref!(shell.homepage)).ok();
|
||||
let blank_url = ServoUrl::parse("about:blank").ok();
|
||||
|
||||
let url = embedder_url.or(pref_url).or(blank_url).unwrap();
|
||||
|
||||
gl.clear_color(1.0, 1.0, 1.0, 1.0);
|
||||
gl.clear(gl::COLOR_BUFFER_BIT);
|
||||
gl.finish();
|
||||
|
||||
// Initialize surfman
|
||||
let connection = Connection::new().or(Err("Failed to create connection"))?;
|
||||
let adapter = connection
|
||||
.create_adapter()
|
||||
.or(Err("Failed to create adapter"))?;
|
||||
let surface_type = match init_opts.surfman_integration {
|
||||
SurfmanIntegration::Widget(native_widget) => {
|
||||
let native_widget = unsafe {
|
||||
connection.create_native_widget_from_ptr(
|
||||
native_widget,
|
||||
init_opts.coordinates.framebuffer.to_untyped(),
|
||||
)
|
||||
};
|
||||
SurfaceType::Widget { native_widget }
|
||||
},
|
||||
SurfmanIntegration::Surface => {
|
||||
let size = init_opts.coordinates.framebuffer.to_untyped();
|
||||
SurfaceType::Generic { size }
|
||||
},
|
||||
};
|
||||
let rendering_context = RenderingContext::create(&connection, &adapter, surface_type)
|
||||
.or(Err("Failed to create surface manager"))?;
|
||||
|
||||
let window_callbacks = Rc::new(ServoWindowCallbacks {
|
||||
host_callbacks: callbacks,
|
||||
coordinates: RefCell::new(init_opts.coordinates),
|
||||
density: init_opts.density,
|
||||
rendering_context: rendering_context.clone(),
|
||||
});
|
||||
|
||||
let embedder_callbacks = Box::new(ServoEmbedderCallbacks {
|
||||
xr_discovery: init_opts.xr_discovery,
|
||||
waker,
|
||||
gl: gl.clone(),
|
||||
});
|
||||
|
||||
let servo = Servo::new(
|
||||
embedder_callbacks,
|
||||
window_callbacks.clone(),
|
||||
None,
|
||||
CompositeTarget::Window,
|
||||
);
|
||||
|
||||
SERVO.with(|s| {
|
||||
let mut servo_glue = ServoGlue {
|
||||
rendering_context,
|
||||
servo: servo.servo,
|
||||
batch_mode: false,
|
||||
callbacks: window_callbacks,
|
||||
events: vec![],
|
||||
context_menu_sender: None,
|
||||
webviews: HashMap::default(),
|
||||
creation_order: vec![],
|
||||
focused_webview_id: None,
|
||||
};
|
||||
let _ = servo_glue.process_event(EmbedderEvent::NewWebView(url, servo.browser_id));
|
||||
*s.borrow_mut() = Some(servo_glue);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn deinit() {
|
||||
SERVO.with(|s| s.replace(None).unwrap().deinit());
|
||||
}
|
||||
|
||||
impl ServoGlue {
|
||||
fn get_browser_id(&self) -> Result<TopLevelBrowsingContextId, &'static str> {
|
||||
let webview_id = match self.focused_webview_id {
|
||||
Some(id) => id,
|
||||
None => return Err("No focused WebViewId yet."),
|
||||
};
|
||||
Ok(webview_id)
|
||||
}
|
||||
|
||||
/// Request shutdown. Will call on_shutdown_complete.
|
||||
pub fn request_shutdown(&mut self) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::Quit)
|
||||
}
|
||||
|
||||
/// Call after on_shutdown_complete
|
||||
pub fn deinit(self) {
|
||||
self.servo.deinit();
|
||||
}
|
||||
|
||||
/// Returns the webrender surface management integration interface.
|
||||
/// This provides the embedder access to the current front buffer.
|
||||
pub fn surfman(&self) -> RenderingContext {
|
||||
self.rendering_context.clone()
|
||||
}
|
||||
|
||||
/// This is the Servo heartbeat. This needs to be called
|
||||
/// everytime wakeup is called or when embedder wants Servo
|
||||
/// to act on its pending events.
|
||||
pub fn perform_updates(&mut self) -> Result<(), &'static str> {
|
||||
debug!("perform_updates");
|
||||
let events = mem::replace(&mut self.events, Vec::new());
|
||||
self.servo.handle_events(events);
|
||||
let r = self.handle_servo_events();
|
||||
debug!("done perform_updates");
|
||||
r
|
||||
}
|
||||
|
||||
/// In batch mode, Servo won't call perform_updates automatically.
|
||||
/// This can be useful when the embedder wants to control when Servo
|
||||
/// acts on its pending events. For example, if the embedder wants Servo
|
||||
/// to act on the scroll events only at a certain time, not everytime
|
||||
/// scroll() is called.
|
||||
pub fn set_batch_mode(&mut self, batch: bool) -> Result<(), &'static str> {
|
||||
debug!("set_batch_mode");
|
||||
self.batch_mode = batch;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load an URL. If this is not a valid URL, try to "fix" it by adding a scheme or if all else fails,
|
||||
/// interpret the string as a search term.
|
||||
pub fn load_uri(&mut self, request: &str) -> Result<(), &'static str> {
|
||||
info!("load_uri: {}", request);
|
||||
ServoUrl::parse(request)
|
||||
.or_else(|_| {
|
||||
if request.contains('/') || is_reg_domain(request) {
|
||||
ServoUrl::parse(&format!("https://{}", request))
|
||||
} else {
|
||||
let search_url = pref!(shell.searchpage).replace("%s", request);
|
||||
ServoUrl::parse(&search_url)
|
||||
}
|
||||
})
|
||||
.map_err(|_| "Can't parse URL")
|
||||
.and_then(|url| {
|
||||
let browser_id = self.get_browser_id()?;
|
||||
let event = EmbedderEvent::LoadUrl(browser_id, url);
|
||||
self.process_event(event)
|
||||
})
|
||||
}
|
||||
|
||||
/// Reload the page.
|
||||
pub fn clear_cache(&mut self) -> Result<(), &'static str> {
|
||||
info!("clear_cache");
|
||||
let event = EmbedderEvent::ClearCache;
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Reload the page.
|
||||
pub fn reload(&mut self) -> Result<(), &'static str> {
|
||||
info!("reload");
|
||||
let browser_id = self.get_browser_id()?;
|
||||
let event = EmbedderEvent::Reload(browser_id);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Redraw the page.
|
||||
pub fn refresh(&mut self) -> Result<(), &'static str> {
|
||||
info!("refresh");
|
||||
self.process_event(EmbedderEvent::Refresh)
|
||||
}
|
||||
|
||||
/// Stop loading the page.
|
||||
pub fn stop(&mut self) -> Result<(), &'static str> {
|
||||
warn!("TODO can't stop won't stop");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Go back in history.
|
||||
pub fn go_back(&mut self) -> Result<(), &'static str> {
|
||||
info!("go_back");
|
||||
let browser_id = self.get_browser_id()?;
|
||||
let event = EmbedderEvent::Navigation(browser_id, TraversalDirection::Back(1));
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Go forward in history.
|
||||
pub fn go_forward(&mut self) -> Result<(), &'static str> {
|
||||
info!("go_forward");
|
||||
let browser_id = self.get_browser_id()?;
|
||||
let event = EmbedderEvent::Navigation(browser_id, TraversalDirection::Forward(1));
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Let Servo know that the window has been resized.
|
||||
pub fn resize(&mut self, coordinates: Coordinates) -> Result<(), &'static str> {
|
||||
info!("resize");
|
||||
*self.callbacks.coordinates.borrow_mut() = coordinates;
|
||||
self.process_event(EmbedderEvent::WindowResize)
|
||||
}
|
||||
|
||||
/// Start scrolling.
|
||||
/// x/y are scroll coordinates.
|
||||
/// dx/dy are scroll deltas.
|
||||
pub fn scroll_start(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
|
||||
let delta = Vector2D::new(dx, dy);
|
||||
let scroll_location = ScrollLocation::Delta(delta);
|
||||
let event =
|
||||
EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Down);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Scroll.
|
||||
/// x/y are scroll coordinates.
|
||||
/// dx/dy are scroll deltas.
|
||||
pub fn scroll(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
|
||||
let delta = Vector2D::new(dx, dy);
|
||||
let scroll_location = ScrollLocation::Delta(delta);
|
||||
let event =
|
||||
EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Move);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// End scrolling.
|
||||
/// x/y are scroll coordinates.
|
||||
/// dx/dy are scroll deltas.
|
||||
pub fn scroll_end(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
|
||||
let delta = Vector2D::new(dx, dy);
|
||||
let scroll_location = ScrollLocation::Delta(delta);
|
||||
let event = EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Up);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Touch event: press down
|
||||
pub fn touch_down(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
|
||||
let event = EmbedderEvent::Touch(
|
||||
TouchEventType::Down,
|
||||
TouchId(pointer_id),
|
||||
Point2D::new(x as f32, y as f32),
|
||||
);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Touch event: move touching finger
|
||||
pub fn touch_move(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
|
||||
let event = EmbedderEvent::Touch(
|
||||
TouchEventType::Move,
|
||||
TouchId(pointer_id),
|
||||
Point2D::new(x as f32, y as f32),
|
||||
);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Touch event: Lift touching finger
|
||||
pub fn touch_up(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
|
||||
let event = EmbedderEvent::Touch(
|
||||
TouchEventType::Up,
|
||||
TouchId(pointer_id),
|
||||
Point2D::new(x as f32, y as f32),
|
||||
);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Cancel touch event
|
||||
pub fn touch_cancel(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
|
||||
let event = EmbedderEvent::Touch(
|
||||
TouchEventType::Cancel,
|
||||
TouchId(pointer_id),
|
||||
Point2D::new(x as f32, y as f32),
|
||||
);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Register a mouse movement.
|
||||
pub fn mouse_move(&mut self, x: f32, y: f32) -> Result<(), &'static str> {
|
||||
let point = Point2D::new(x, y);
|
||||
let event = EmbedderEvent::MouseWindowMoveEventClass(point);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Register a mouse button press.
|
||||
pub fn mouse_down(&mut self, x: f32, y: f32, button: MouseButton) -> Result<(), &'static str> {
|
||||
let point = Point2D::new(x, y);
|
||||
let event =
|
||||
EmbedderEvent::MouseWindowEventClass(MouseWindowEvent::MouseDown(button, point));
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Register a mouse button release.
|
||||
pub fn mouse_up(&mut self, x: f32, y: f32, button: MouseButton) -> Result<(), &'static str> {
|
||||
let point = Point2D::new(x, y);
|
||||
let event = EmbedderEvent::MouseWindowEventClass(MouseWindowEvent::MouseUp(button, point));
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Start pinchzoom.
|
||||
/// x/y are pinch origin coordinates.
|
||||
pub fn pinchzoom_start(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::PinchZoom(factor))
|
||||
}
|
||||
|
||||
/// Pinchzoom.
|
||||
/// x/y are pinch origin coordinates.
|
||||
pub fn pinchzoom(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::PinchZoom(factor))
|
||||
}
|
||||
|
||||
/// End pinchzoom.
|
||||
/// x/y are pinch origin coordinates.
|
||||
pub fn pinchzoom_end(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::PinchZoom(factor))
|
||||
}
|
||||
|
||||
/// Perform a click.
|
||||
pub fn click(&mut self, x: f32, y: f32) -> Result<(), &'static str> {
|
||||
let mouse_event = MouseWindowEvent::Click(MouseButton::Left, Point2D::new(x, y));
|
||||
let event = EmbedderEvent::MouseWindowEventClass(mouse_event);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
pub fn key_down(&mut self, key: Key) -> Result<(), &'static str> {
|
||||
let key_event = KeyboardEvent {
|
||||
state: KeyState::Down,
|
||||
key,
|
||||
..KeyboardEvent::default()
|
||||
};
|
||||
self.process_event(EmbedderEvent::Keyboard(key_event))
|
||||
}
|
||||
|
||||
pub fn key_up(&mut self, key: Key) -> Result<(), &'static str> {
|
||||
let key_event = KeyboardEvent {
|
||||
state: KeyState::Up,
|
||||
key,
|
||||
..KeyboardEvent::default()
|
||||
};
|
||||
self.process_event(EmbedderEvent::Keyboard(key_event))
|
||||
}
|
||||
|
||||
pub fn pause_compositor(&mut self) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::InvalidateNativeSurface)
|
||||
}
|
||||
|
||||
pub fn resume_compositor(
|
||||
&mut self,
|
||||
native_surface: *mut c_void,
|
||||
coords: Coordinates,
|
||||
) -> Result<(), &'static str> {
|
||||
if native_surface.is_null() {
|
||||
panic!("null passed for native_surface");
|
||||
}
|
||||
self.process_event(EmbedderEvent::ReplaceNativeSurface(
|
||||
native_surface,
|
||||
coords.framebuffer,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn media_session_action(
|
||||
&mut self,
|
||||
action: MediaSessionActionType,
|
||||
) -> Result<(), &'static str> {
|
||||
info!("Media session action {:?}", action);
|
||||
self.process_event(EmbedderEvent::MediaSessionAction(action))
|
||||
}
|
||||
|
||||
pub fn set_throttled(&mut self, throttled: bool) -> Result<(), &'static str> {
|
||||
info!("set_throttled");
|
||||
if let Ok(id) = self.get_browser_id() {
|
||||
let event = EmbedderEvent::SetWebViewThrottled(id, throttled);
|
||||
self.process_event(event)
|
||||
} else {
|
||||
// Ignore visibility change if no browser has been created yet.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ime_dismissed(&mut self) -> Result<(), &'static str> {
|
||||
info!("ime_dismissed");
|
||||
self.process_event(EmbedderEvent::IMEDismissed)
|
||||
}
|
||||
|
||||
pub fn on_context_menu_closed(
|
||||
&mut self,
|
||||
result: ContextMenuResult,
|
||||
) -> Result<(), &'static str> {
|
||||
if let Some(sender) = self.context_menu_sender.take() {
|
||||
let _ = sender.send(result);
|
||||
} else {
|
||||
warn!("Trying to close a context menu when no context menu is active");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: EmbedderEvent) -> Result<(), &'static str> {
|
||||
self.events.push(event);
|
||||
if !self.batch_mode {
|
||||
self.perform_updates()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_servo_events(&mut self) -> Result<(), &'static str> {
|
||||
let mut need_update = false;
|
||||
let mut need_present = false;
|
||||
for (browser_id, event) in self.servo.get_events() {
|
||||
match event {
|
||||
EmbedderMsg::ChangePageTitle(title) => {
|
||||
self.callbacks.host_callbacks.on_title_changed(title);
|
||||
},
|
||||
EmbedderMsg::AllowNavigationRequest(pipeline_id, url) => {
|
||||
if let Some(_browser_id) = browser_id {
|
||||
let data: bool = self
|
||||
.callbacks
|
||||
.host_callbacks
|
||||
.on_allow_navigation(url.to_string());
|
||||
let window_event =
|
||||
EmbedderEvent::AllowNavigationResponse(pipeline_id, data);
|
||||
self.events.push(window_event);
|
||||
need_update = true;
|
||||
}
|
||||
},
|
||||
EmbedderMsg::HistoryChanged(entries, current) => {
|
||||
let can_go_back = current > 0;
|
||||
let can_go_forward = current < entries.len() - 1;
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.on_history_changed(can_go_back, can_go_forward);
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.on_url_changed(entries[current].clone().to_string());
|
||||
},
|
||||
EmbedderMsg::LoadStart => {
|
||||
self.callbacks.host_callbacks.on_load_started();
|
||||
},
|
||||
EmbedderMsg::LoadComplete => {
|
||||
self.callbacks.host_callbacks.on_load_ended();
|
||||
},
|
||||
EmbedderMsg::GetSelectedBluetoothDevice(_, sender) => {
|
||||
let _ = sender.send(None);
|
||||
},
|
||||
EmbedderMsg::AllowUnload(sender) => {
|
||||
let _ = sender.send(true);
|
||||
},
|
||||
EmbedderMsg::ShowContextMenu(sender, title, items) => {
|
||||
if self.context_menu_sender.is_some() {
|
||||
warn!(
|
||||
"Trying to show a context menu when a context menu is already active"
|
||||
);
|
||||
let _ = sender.send(ContextMenuResult::Ignored);
|
||||
} else {
|
||||
self.context_menu_sender = Some(sender);
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.show_context_menu(title, items);
|
||||
}
|
||||
},
|
||||
EmbedderMsg::Prompt(definition, origin) => {
|
||||
let cb = &self.callbacks.host_callbacks;
|
||||
let trusted = origin == PromptOrigin::Trusted;
|
||||
let res = match definition {
|
||||
PromptDefinition::Alert(message, sender) => {
|
||||
sender.send(cb.prompt_alert(message, trusted))
|
||||
},
|
||||
PromptDefinition::OkCancel(message, sender) => {
|
||||
sender.send(cb.prompt_ok_cancel(message, trusted))
|
||||
},
|
||||
PromptDefinition::YesNo(message, sender) => {
|
||||
sender.send(cb.prompt_yes_no(message, trusted))
|
||||
},
|
||||
PromptDefinition::Input(message, default, sender) => {
|
||||
sender.send(cb.prompt_input(message, default, trusted))
|
||||
},
|
||||
};
|
||||
if let Err(e) = res {
|
||||
let reason = format!("Failed to send Prompt response: {}", e);
|
||||
self.events
|
||||
.push(EmbedderEvent::SendError(browser_id, reason));
|
||||
}
|
||||
},
|
||||
EmbedderMsg::AllowOpeningWebView(response_chan) => {
|
||||
// Note: would be a place to handle pop-ups config.
|
||||
// see Step 7 of #the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
|
||||
if let Err(e) = response_chan.send(true) {
|
||||
warn!("Failed to send AllowOpeningBrowser response: {}", e);
|
||||
};
|
||||
},
|
||||
EmbedderMsg::WebViewOpened(new_webview_id) => {
|
||||
self.webviews.insert(new_webview_id, WebView {});
|
||||
self.creation_order.push(new_webview_id);
|
||||
self.events
|
||||
.push(EmbedderEvent::FocusWebView(new_webview_id));
|
||||
},
|
||||
EmbedderMsg::WebViewClosed(webview_id) => {
|
||||
self.webviews.retain(|&id, _| id != webview_id);
|
||||
self.creation_order.retain(|&id| id != webview_id);
|
||||
self.focused_webview_id = None;
|
||||
if let Some(&newest_webview_id) = self.creation_order.last() {
|
||||
self.events
|
||||
.push(EmbedderEvent::FocusWebView(newest_webview_id));
|
||||
} else {
|
||||
self.events.push(EmbedderEvent::Quit);
|
||||
}
|
||||
},
|
||||
EmbedderMsg::WebViewFocused(webview_id) => {
|
||||
self.focused_webview_id = Some(webview_id);
|
||||
},
|
||||
EmbedderMsg::WebViewBlurred => {
|
||||
self.focused_webview_id = None;
|
||||
},
|
||||
EmbedderMsg::GetClipboardContents(sender) => {
|
||||
let contents = self.callbacks.host_callbacks.get_clipboard_contents();
|
||||
let _ = sender.send(contents.unwrap_or("".to_owned()));
|
||||
},
|
||||
EmbedderMsg::SetClipboardContents(text) => {
|
||||
self.callbacks.host_callbacks.set_clipboard_contents(text);
|
||||
},
|
||||
EmbedderMsg::Shutdown => {
|
||||
self.callbacks.host_callbacks.on_shutdown_complete();
|
||||
},
|
||||
EmbedderMsg::PromptPermission(prompt, sender) => {
|
||||
let message = match prompt {
|
||||
PermissionPrompt::Request(permission_name) => {
|
||||
format!("Do you want to grant permission for {:?}?", permission_name)
|
||||
},
|
||||
PermissionPrompt::Insecure(permission_name) => {
|
||||
format!(
|
||||
"The {:?} feature is only safe to use in secure context, but servo can't guarantee\n\
|
||||
that the current context is secure. Do you want to proceed and grant permission?",
|
||||
permission_name
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
let result = match self.callbacks.host_callbacks.prompt_yes_no(message, true) {
|
||||
PromptResult::Primary => PermissionRequest::Granted,
|
||||
PromptResult::Secondary | PromptResult::Dismissed => {
|
||||
PermissionRequest::Denied
|
||||
},
|
||||
};
|
||||
|
||||
let _ = sender.send(result);
|
||||
},
|
||||
EmbedderMsg::ShowIME(kind, text, multiline, bounds) => {
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.on_ime_show(kind, text, multiline, bounds);
|
||||
},
|
||||
EmbedderMsg::HideIME => {
|
||||
self.callbacks.host_callbacks.on_ime_hide();
|
||||
},
|
||||
EmbedderMsg::MediaSessionEvent(event) => {
|
||||
match event {
|
||||
MediaSessionEvent::SetMetadata(metadata) => {
|
||||
self.callbacks.host_callbacks.on_media_session_metadata(
|
||||
metadata.title,
|
||||
metadata.artist,
|
||||
metadata.album,
|
||||
)
|
||||
},
|
||||
MediaSessionEvent::PlaybackStateChange(state) => self
|
||||
.callbacks
|
||||
.host_callbacks
|
||||
.on_media_session_playback_state_change(state),
|
||||
MediaSessionEvent::SetPositionState(position_state) => self
|
||||
.callbacks
|
||||
.host_callbacks
|
||||
.on_media_session_set_position_state(
|
||||
position_state.duration,
|
||||
position_state.position,
|
||||
position_state.playback_rate,
|
||||
),
|
||||
};
|
||||
},
|
||||
EmbedderMsg::OnDevtoolsStarted(port, token) => {
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.on_devtools_started(port, token);
|
||||
},
|
||||
EmbedderMsg::Panic(reason, backtrace) => {
|
||||
self.callbacks.host_callbacks.on_panic(reason, backtrace);
|
||||
},
|
||||
EmbedderMsg::ReadyToPresent(_webview_ids) => {
|
||||
need_present = true;
|
||||
},
|
||||
EmbedderMsg::Status(..) |
|
||||
EmbedderMsg::SelectFiles(..) |
|
||||
EmbedderMsg::MoveTo(..) |
|
||||
EmbedderMsg::ResizeTo(..) |
|
||||
EmbedderMsg::Keyboard(..) |
|
||||
EmbedderMsg::SetCursor(..) |
|
||||
EmbedderMsg::NewFavicon(..) |
|
||||
EmbedderMsg::HeadParsed |
|
||||
EmbedderMsg::SetFullscreenState(..) |
|
||||
EmbedderMsg::ReportProfile(..) |
|
||||
EmbedderMsg::EventDelivered(..) => {},
|
||||
}
|
||||
}
|
||||
|
||||
if need_update {
|
||||
let _ = self.perform_updates();
|
||||
}
|
||||
if need_present {
|
||||
self.servo.present();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct ServoEmbedderCallbacks {
|
||||
waker: Box<dyn EventLoopWaker>,
|
||||
xr_discovery: Option<webxr::Discovery>,
|
||||
#[allow(unused)]
|
||||
gl: Rc<dyn gl::Gl>,
|
||||
}
|
||||
|
||||
struct ServoWindowCallbacks {
|
||||
host_callbacks: Box<dyn HostTrait>,
|
||||
coordinates: RefCell<Coordinates>,
|
||||
density: f32,
|
||||
rendering_context: RenderingContext,
|
||||
}
|
||||
|
||||
impl EmbedderMethods for ServoEmbedderCallbacks {
|
||||
fn register_webxr(
|
||||
&mut self,
|
||||
registry: &mut webxr::MainThreadRegistry,
|
||||
_embedder_proxy: EmbedderProxy,
|
||||
) {
|
||||
debug!("EmbedderMethods::register_xr");
|
||||
if let Some(discovery) = self.xr_discovery.take() {
|
||||
registry.register(discovery);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_event_loop_waker(&mut self) -> Box<dyn EventLoopWaker> {
|
||||
debug!("EmbedderMethods::create_event_loop_waker");
|
||||
self.waker.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowMethods for ServoWindowCallbacks {
|
||||
fn rendering_context(&self) -> RenderingContext {
|
||||
self.rendering_context.clone()
|
||||
}
|
||||
|
||||
fn set_animation_state(&self, state: AnimationState) {
|
||||
debug!("WindowMethods::set_animation_state: {:?}", state);
|
||||
self.host_callbacks
|
||||
.on_animating_changed(state == AnimationState::Animating);
|
||||
}
|
||||
|
||||
fn get_coordinates(&self) -> EmbedderCoordinates {
|
||||
let coords = self.coordinates.borrow();
|
||||
EmbedderCoordinates {
|
||||
viewport: coords.viewport.to_box2d(),
|
||||
framebuffer: coords.framebuffer,
|
||||
window: (coords.viewport.size, Point2D::new(0, 0)),
|
||||
screen: coords.viewport.size,
|
||||
screen_avail: coords.viewport.size,
|
||||
hidpi_factor: Scale::new(self.density),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ResourceReaderInstance;
|
||||
|
||||
impl ResourceReaderInstance {
|
||||
fn new() -> ResourceReaderInstance {
|
||||
ResourceReaderInstance
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceReaderMethods for ResourceReaderInstance {
|
||||
fn read(&self, res: Resource) -> Vec<u8> {
|
||||
Vec::from(match res {
|
||||
Resource::Preferences => &include_bytes!(concat!(env!("OUT_DIR"), "/prefs.json"))[..],
|
||||
Resource::HstsPreloadList => {
|
||||
&include_bytes!("../../../../resources/hsts_preload.json")[..]
|
||||
},
|
||||
Resource::BadCertHTML => &include_bytes!("../../../../resources/badcert.html")[..],
|
||||
Resource::NetErrorHTML => &include_bytes!("../../../../resources/neterror.html")[..],
|
||||
Resource::UserAgentCSS => &include_bytes!("../../../../resources/user-agent.css")[..],
|
||||
Resource::ServoCSS => &include_bytes!("../../../../resources/servo.css")[..],
|
||||
Resource::PresentationalHintsCSS => {
|
||||
&include_bytes!("../../../../resources/presentational-hints.css")[..]
|
||||
},
|
||||
Resource::QuirksModeCSS => &include_bytes!("../../../../resources/quirks-mode.css")[..],
|
||||
Resource::RippyPNG => &include_bytes!("../../../../resources/rippy.png")[..],
|
||||
Resource::DomainList => &include_bytes!("../../../../resources/public_domains.txt")[..],
|
||||
Resource::BluetoothBlocklist => {
|
||||
&include_bytes!("../../../../resources/gatt_blocklist.txt")[..]
|
||||
},
|
||||
Resource::MediaControlsCSS => {
|
||||
&include_bytes!("../../../../resources/media-controls.css")[..]
|
||||
},
|
||||
Resource::MediaControlsJS => {
|
||||
&include_bytes!("../../../../resources/media-controls.js")[..]
|
||||
},
|
||||
Resource::CrashHTML => &include_bytes!("../../../../resources/crash.html")[..],
|
||||
})
|
||||
}
|
||||
|
||||
fn sandbox_access_files(&self) -> Vec<PathBuf> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
|
||||
vec![]
|
||||
}
|
||||
}
|
53
ports/servoshell/egl/gl_glue.rs
Normal file
53
ports/servoshell/egl/gl_glue.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
pub type ServoGl = std::rc::Rc<dyn servo::gl::Gl>;
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use log::info;
|
||||
use servo::gl::GlesFns;
|
||||
|
||||
pub type EGLNativeWindowType = *const libc::c_void;
|
||||
pub type khronos_utime_nanoseconds_t = khronos_uint64_t;
|
||||
pub type khronos_uint64_t = u64;
|
||||
pub type khronos_ssize_t = libc::c_long;
|
||||
pub type EGLint = i32;
|
||||
pub type EGLContext = *const libc::c_void;
|
||||
pub type EGLNativeDisplayType = *const libc::c_void;
|
||||
pub type EGLNativePixmapType = *const libc::c_void;
|
||||
pub type NativeDisplayType = EGLNativeDisplayType;
|
||||
pub type NativePixmapType = EGLNativePixmapType;
|
||||
pub type NativeWindowType = EGLNativeWindowType;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs"));
|
||||
|
||||
pub struct EGLInitResult {
|
||||
pub gl_wrapper: ServoGl,
|
||||
pub gl_context: EGLContext,
|
||||
pub display: EGLNativeDisplayType,
|
||||
}
|
||||
|
||||
pub fn init() -> Result<EGLInitResult, &'static str> {
|
||||
info!("Loading EGL...");
|
||||
unsafe {
|
||||
let egl = Egl;
|
||||
let display = egl.GetCurrentDisplay();
|
||||
egl.SwapInterval(display, 1);
|
||||
let egl = GlesFns::load_with(|addr| {
|
||||
let addr = CString::new(addr.as_bytes()).unwrap();
|
||||
let addr = addr.as_ptr();
|
||||
let egl = Egl;
|
||||
egl.GetProcAddress(addr) as *const c_void
|
||||
});
|
||||
info!("EGL loaded");
|
||||
Ok(EGLInitResult {
|
||||
gl_wrapper: egl,
|
||||
gl_context: Egl.GetCurrentContext(),
|
||||
display,
|
||||
})
|
||||
}
|
||||
}
|
9
ports/servoshell/egl/mod.rs
Normal file
9
ports/servoshell/egl/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
/* 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/. */
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod gl_glue;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
mod android;
|
|
@ -2,9 +2,6 @@
|
|||
* 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/. */
|
||||
|
||||
// For Android, see /support/android/apk/ + /ports/jniapi/.
|
||||
#![cfg(not(target_os = "android"))]
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
#[macro_use]
|
||||
extern crate sig;
|
||||
|
@ -12,15 +9,19 @@ extern crate sig;
|
|||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
mod backtrace;
|
||||
mod crash_handler;
|
||||
#[cfg(not(any(target_os = "android", target_env = "ohos")))]
|
||||
pub(crate) mod desktop;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
mod panic_hook;
|
||||
mod parser;
|
||||
mod prefs;
|
||||
mod resources;
|
||||
|
||||
mod egl;
|
||||
|
||||
pub mod platform {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use crate::platform::macos::deinit;
|
||||
|
@ -41,7 +42,7 @@ pub fn main() {
|
|||
pub fn main() {
|
||||
println!(
|
||||
"Cannot start /ports/servoshell/ on Android. \
|
||||
Use /support/android/apk/ + /ports/jniapi/ instead"
|
||||
Use /support/android/apk/ + `libservoshell.so` instead"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue