Make JNI code more robust

This commit is contained in:
Paul Rouget 2018-10-05 12:44:27 +02:00
parent 021a24d5bb
commit e0ce73abb2
7 changed files with 248 additions and 150 deletions

View file

@ -28,6 +28,14 @@ thread_local! {
/// and that perform_updates need to be called /// and that perform_updates need to be called
pub use servo::embedder_traits::EventLoopWaker; pub use servo::embedder_traits::EventLoopWaker;
pub struct InitOptions {
pub args: Option<String>,
pub url: Option<String>,
pub width: u32,
pub height: u32,
pub density: f32,
}
/// Delegate resource file reading to the embedder. /// Delegate resource file reading to the embedder.
pub trait ReadFileTrait { pub trait ReadFileTrait {
fn readfile(&self, file: &str) -> Vec<u8>; fn readfile(&self, file: &str) -> Vec<u8>;
@ -93,20 +101,16 @@ pub fn servo_version() -> String {
/// Initialize Servo. At that point, we need a valid GL context. /// Initialize Servo. At that point, we need a valid GL context.
/// In the future, this will be done in multiple steps. /// In the future, this will be done in multiple steps.
pub fn init( pub fn init(
init_opts: InitOptions,
gl: Rc<gl::Gl>, gl: Rc<gl::Gl>,
argsline: String,
embedder_url: Option<String>,
waker: Box<EventLoopWaker>, waker: Box<EventLoopWaker>,
readfile: Box<ReadFileTrait + Send + Sync>, readfile: Box<ReadFileTrait + Send + Sync>,
callbacks: Box<HostTrait>, callbacks: Box<HostTrait>,
width: u32,
height: u32,
density: f32,
) -> Result<(), &'static str> { ) -> Result<(), &'static str> {
resources::set(Box::new(ResourceReader(readfile))); resources::set(Box::new(ResourceReader(readfile)));
if !argsline.is_empty() { if let Some(args) = init_opts.args {
let mut args: Vec<String> = serde_json::from_str(&argsline).map_err(|_| { let mut args: Vec<String> = serde_json::from_str(&args).map_err(|_| {
"Invalid arguments. Servo arguments must be formatted as a JSON array" "Invalid arguments. Servo arguments must be formatted as a JSON array"
})?; })?;
// opts::from_cmdline_args expects the first argument to be the binary name. // opts::from_cmdline_args expects the first argument to be the binary name.
@ -114,7 +118,7 @@ pub fn init(
opts::from_cmdline_args(&args); opts::from_cmdline_args(&args);
} }
let embedder_url = embedder_url.as_ref().and_then(|s| { let embedder_url = init_opts.url.as_ref().and_then(|s| {
ServoUrl::parse(s).ok() ServoUrl::parse(s).ok()
}); });
let cmdline_url = opts::get().url.clone(); let cmdline_url = opts::get().url.clone();
@ -135,9 +139,9 @@ pub fn init(
let callbacks = Rc::new(ServoCallbacks { let callbacks = Rc::new(ServoCallbacks {
gl: gl.clone(), gl: gl.clone(),
host_callbacks: callbacks, host_callbacks: callbacks,
width: Cell::new(width), width: Cell::new(init_opts.width),
height: Cell::new(height), height: Cell::new(init_opts.height),
density, density: init_opts.density,
waker, waker,
}); });

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{self, EventLoopWaker, ServoGlue, SERVO, HostTrait, ReadFileTrait}; use api::{self, EventLoopWaker, InitOptions, ServoGlue, SERVO, HostTrait, ReadFileTrait};
use gl_glue; use gl_glue;
use servo::gl; use servo::gl;
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
@ -37,6 +37,16 @@ pub struct CHostCallbacks {
pub on_animating_changed: extern fn(animating: bool), pub on_animating_changed: extern fn(animating: bool),
} }
/// Servo options
#[repr(C)]
pub struct CInitOptions {
pub args: *const c_char,
pub url: *const c_char,
pub width: u32,
pub height: u32,
pub density: f32,
}
/// The returned string is not freed. This will leak. /// The returned string is not freed. This will leak.
#[no_mangle] #[no_mangle]
pub extern "C" fn servo_version() -> *const c_char { pub extern "C" fn servo_version() -> *const c_char {
@ -48,66 +58,52 @@ pub extern "C" fn servo_version() -> *const c_char {
} }
fn init( fn init(
opts: CInitOptions,
gl: Rc<gl::Gl>, gl: Rc<gl::Gl>,
args: *const c_char,
url: *const c_char,
wakeup: extern fn(), wakeup: extern fn(),
readfile: extern fn(*const c_char) -> *const c_char, readfile: extern fn(*const c_char) -> *const c_char,
callbacks: CHostCallbacks, callbacks: CHostCallbacks) {
width: u32, let args = unsafe { CStr::from_ptr(opts.args) };
height: u32, let args = args.to_str().map(|s| s.to_string()).ok();
density: f32) {
let args = unsafe { CStr::from_ptr(args) };
let args = args.to_str().expect("Can't read string").to_string();
let url = unsafe { CStr::from_ptr(url) }; let url = unsafe { CStr::from_ptr(opts.url) };
let url = url.to_str().map(|s| s.to_string()); let url = url.to_str().map(|s| s.to_string()).ok();
let opts = InitOptions {
args,
url,
width: opts.width,
height: opts.height,
density: opts.density,
};
let wakeup = Box::new(WakeupCallback::new(wakeup)); let wakeup = Box::new(WakeupCallback::new(wakeup));
let readfile = Box::new(ReadFileCallback::new(readfile)); let readfile = Box::new(ReadFileCallback::new(readfile));
let callbacks = Box::new(HostCallbacks::new(callbacks)); let callbacks = Box::new(HostCallbacks::new(callbacks));
api::init( api::init(opts, gl, wakeup, readfile, callbacks).unwrap();
gl,
args,
url.ok(),
wakeup,
readfile,
callbacks,
width,
height,
density,
).unwrap();
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
#[no_mangle] #[no_mangle]
pub extern "C" fn init_with_egl( pub extern "C" fn init_with_egl(
args: *const c_char, opts: CInitOptions,
url: *const c_char,
wakeup: extern fn(), wakeup: extern fn(),
readfile: extern fn(*const c_char) -> *const c_char, readfile: extern fn(*const c_char) -> *const c_char,
callbacks: CHostCallbacks, callbacks: CHostCallbacks) {
width: u32,
height: u32,
density: f32) {
let gl = gl_glue::egl::init().unwrap(); let gl = gl_glue::egl::init().unwrap();
init(gl, args, url, wakeup, readfile, callbacks, width, height, density) init(opts, gl, wakeup, readfile, callbacks)
} }
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
#[no_mangle] #[no_mangle]
pub extern "C" fn init_with_gl( pub extern "C" fn init_with_gl(
args: *const c_char, opts: CInitOptions,
url: *const c_char,
wakeup: extern fn(), wakeup: extern fn(),
readfile: extern fn(*const c_char) -> *const c_char, readfile: extern fn(*const c_char) -> *const c_char,
callbacks: CHostCallbacks, callbacks: CHostCallbacks) {
width: u32,
height: u32,
density: f32) {
let gl = gl_glue::gl::init().unwrap(); let gl = gl_glue::gl::init().unwrap();
init(gl, args, url, wakeup, readfile, callbacks, width, height, density) init(opts, gl, wakeup, readfile, callbacks)
} }
#[no_mangle] #[no_mangle]

View file

@ -5,15 +5,14 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use android_logger::{self, Filter}; use android_logger::{self, Filter};
use api::{self, EventLoopWaker, ServoGlue, SERVO, HostTrait, ReadFileTrait}; use api::{self, EventLoopWaker, InitOptions, ServoGlue, SERVO, HostTrait, ReadFileTrait};
use gl_glue; use gl_glue;
use jni::{JNIEnv, JavaVM}; use jni::{errors, JNIEnv, JavaVM};
use jni::objects::{GlobalRef, JClass, JObject, JString, JValue}; use jni::objects::{GlobalRef, JClass, JObject, JString, JValue};
use jni::sys::{jboolean, jfloat, jint, jstring, JNI_TRUE}; use jni::sys::{jboolean, jfloat, jint, jstring, JNI_TRUE};
use libc::{pipe, dup2, read}; use libc::{pipe, dup2, read};
use log::Level; use log::Level;
use std; use std;
use std::borrow::Cow;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread; use std::thread;
@ -23,17 +22,16 @@ struct HostCallbacks {
jvm: JavaVM, jvm: JavaVM,
} }
fn call<F>(env: JNIEnv, f: F) fn call<F>(env: &JNIEnv, f: F)
where where
F: Fn(&mut ServoGlue) -> Result<(), &'static str>, F: Fn(&mut ServoGlue) -> Result<(), &str>,
{ {
SERVO.with(|s| { SERVO.with(|s| {
if let Err(error) = match s.borrow_mut().as_mut() { if let Err(error) = match s.borrow_mut().as_mut() {
Some(ref mut s) => (f)(s), Some(ref mut s) => (f)(s),
None => Err("Servo not available in this thread"), None => Err("Servo not available in this thread"),
} { } {
env.throw(("java/lang/Exception", error)) throw(env, error);
.expect("Error while throwing");
} }
}); });
} }
@ -41,8 +39,7 @@ where
#[no_mangle] #[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_version(env: JNIEnv, _class: JClass) -> jstring { pub fn Java_org_mozilla_servoview_JNIServo_version(env: JNIEnv, _class: JClass) -> jstring {
let v = api::servo_version(); let v = api::servo_version();
let output = env.new_string(v).expect("Couldn't create java string"); new_string(&env, &v).unwrap_or_else(|null| null)
output.into_inner()
} }
#[no_mangle] #[no_mangle]
@ -50,16 +47,18 @@ pub fn Java_org_mozilla_servoview_JNIServo_init(
env: JNIEnv, env: JNIEnv,
_: JClass, _: JClass,
activity: JObject, activity: JObject,
args: JString, opts: JObject,
url: JString,
log_str: JString,
callbacks_obj: JObject, callbacks_obj: JObject,
width: jint,
height: jint,
density: jfloat,
log: jboolean,
) { ) {
if log == JNI_TRUE { let (opts, log, log_str) = match get_options(&env, opts) {
Ok((opts, log, log_str)) => (opts, log, log_str),
Err(err) => {
throw(&env, &err);
return;
}
};
if log {
// Note: Android debug logs are stripped from a release build. // Note: Android debug logs are stripped from a release build.
// debug!() will only show in a debug build. Use info!() if logs // debug!() will only show in a debug build. Use info!() if logs
// should show up in adb logcat with a release build. // should show up in adb logcat with a release build.
@ -76,10 +75,10 @@ pub fn Java_org_mozilla_servoview_JNIServo_init(
for &module in &filters { for &module in &filters {
filter = filter.with_allowed_module_path(module); filter = filter.with_allowed_module_path(module);
} }
let log_str = env.get_string(log_str).ok(); if let Some(log_str) = log_str {
let log_str = log_str.as_ref().map_or(Cow::Borrowed(""), |s| s.to_string_lossy()); for module in log_str.split(',') {
for module in log_str.split(',') { filter = filter.with_allowed_module_path(module);
filter = filter.with_allowed_module_path(module); }
} }
android_logger::init_once(filter, Some("simpleservo")); android_logger::init_once(filter, Some("simpleservo"));
} }
@ -89,36 +88,23 @@ pub fn Java_org_mozilla_servoview_JNIServo_init(
initialize_android_glue(&env, activity); initialize_android_glue(&env, activity);
redirect_stdout_to_logcat(); redirect_stdout_to_logcat();
let args = env.get_string(args) let callbacks_ref = match env.new_global_ref(callbacks_obj) {
.expect("Couldn't get java string") Ok(r) => r,
.into(); Err(_) => {
throw(&env, "Failed to get global reference of callback argument");
let url = if url.is_null() { return;
None }
} else {
Some(env.get_string(url).expect("Couldn't get java string").into())
}; };
let callbacks_ref = env.new_global_ref(callbacks_obj).unwrap();
let wakeup = Box::new(WakeupCallback::new(callbacks_ref.clone(), &env)); let wakeup = Box::new(WakeupCallback::new(callbacks_ref.clone(), &env));
let readfile = Box::new(ReadFileCallback::new(callbacks_ref.clone(), &env)); let readfile = Box::new(ReadFileCallback::new(callbacks_ref.clone(), &env));
let callbacks = Box::new(HostCallbacks::new(callbacks_ref, &env)); let callbacks = Box::new(HostCallbacks::new(callbacks_ref, &env));
gl_glue::egl::init().and_then(|gl| { if let Err(err) =
api::init( gl_glue::egl::init().and_then(|gl| api::init(opts, gl, wakeup, readfile, callbacks))
gl, {
args, throw(&env, err)
url, };
wakeup,
readfile,
callbacks,
width as u32,
height as u32,
density as f32)
}).or_else(|err| {
env.throw(("java/lang/Exception", err))
}).unwrap();
} }
#[no_mangle] #[no_mangle]
@ -128,7 +114,7 @@ pub fn Java_org_mozilla_servoview_JNIServo_setBatchMode(
batch: jboolean, batch: jboolean,
) { ) {
debug!("setBatchMode"); debug!("setBatchMode");
call(env, |s| s.set_batch_mode(batch == JNI_TRUE)); call(&env, |s| s.set_batch_mode(batch == JNI_TRUE));
} }
#[no_mangle] #[no_mangle]
@ -139,50 +125,57 @@ pub fn Java_org_mozilla_servoview_JNIServo_resize(
height: jint, height: jint,
) { ) {
debug!("resize {}/{}", width, height); debug!("resize {}/{}", width, height);
call(env, |s| s.resize(width as u32, height as u32)); call(&env, |s| s.resize(width as u32, height as u32));
} }
#[no_mangle] #[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_performUpdates(env: JNIEnv, _class: JClass) { pub fn Java_org_mozilla_servoview_JNIServo_performUpdates(env: JNIEnv, _class: JClass) {
debug!("performUpdates"); debug!("performUpdates");
call(env, |s| s.perform_updates()); call(&env, |s| s.perform_updates());
} }
#[no_mangle] #[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_loadUri(env: JNIEnv, _class: JClass, url: JString) { pub fn Java_org_mozilla_servoview_JNIServo_loadUri(env: JNIEnv, _class: JClass, url: JString) {
debug!("loadUri"); debug!("loadUri");
let url: String = env.get_string(url).unwrap().into(); match env.get_string(url) {
call(env, |s| s.load_uri(&url)); Ok(url) => {
let url: String = url.into();
call(&env, |s| s.load_uri(&url));
},
Err(_) => {
throw(&env, "Failed to convert Java string");
}
};
} }
#[no_mangle] #[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_reload(env: JNIEnv, _class: JClass) { pub fn Java_org_mozilla_servoview_JNIServo_reload(env: JNIEnv, _class: JClass) {
debug!("reload"); debug!("reload");
call(env, |s| s.reload()); call(&env, |s| s.reload());
} }
#[no_mangle] #[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_stop(env: JNIEnv, _class: JClass) { pub fn Java_org_mozilla_servoview_JNIServo_stop(env: JNIEnv, _class: JClass) {
debug!("stop"); debug!("stop");
call(env, |s| s.stop()); call(&env, |s| s.stop());
} }
#[no_mangle] #[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_refresh(env: JNIEnv, _class: JClass) { pub fn Java_org_mozilla_servoview_JNIServo_refresh(env: JNIEnv, _class: JClass) {
debug!("refresh"); debug!("refresh");
call(env, |s| s.refresh()); call(&env, |s| s.refresh());
} }
#[no_mangle] #[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_goBack(env: JNIEnv, _class: JClass) { pub fn Java_org_mozilla_servoview_JNIServo_goBack(env: JNIEnv, _class: JClass) {
debug!("goBack"); debug!("goBack");
call(env, |s| s.go_back()); call(&env, |s| s.go_back());
} }
#[no_mangle] #[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_goForward(env: JNIEnv, _class: JClass) { pub fn Java_org_mozilla_servoview_JNIServo_goForward(env: JNIEnv, _class: JClass) {
debug!("goForward"); debug!("goForward");
call(env, |s| s.go_forward()); call(&env, |s| s.go_forward());
} }
#[no_mangle] #[no_mangle]
@ -195,7 +188,7 @@ pub fn Java_org_mozilla_servoview_JNIServo_scrollStart(
y: jint, y: jint,
) { ) {
debug!("scrollStart"); debug!("scrollStart");
call(env, |s| s.scroll_start(dx as i32, dy as i32, x as u32, y as u32)); call(&env, |s| s.scroll_start(dx as i32, dy as i32, x as u32, y as u32));
} }
#[no_mangle] #[no_mangle]
@ -208,7 +201,7 @@ pub fn Java_org_mozilla_servoview_JNIServo_scrollEnd(
y: jint, y: jint,
) { ) {
debug!("scrollEnd"); debug!("scrollEnd");
call(env, |s| s.scroll_end(dx as i32, dy as i32, x as u32, y as u32)); call(&env, |s| s.scroll_end(dx as i32, dy as i32, x as u32, y as u32));
} }
@ -222,7 +215,7 @@ pub fn Java_org_mozilla_servoview_JNIServo_scroll(
y: jint, y: jint,
) { ) {
debug!("scroll"); debug!("scroll");
call(env, |s| s.scroll(dx as i32, dy as i32, x as u32, y as u32)); call(&env, |s| s.scroll(dx as i32, dy as i32, x as u32, y as u32));
} }
#[no_mangle] #[no_mangle]
@ -234,7 +227,7 @@ pub fn Java_org_mozilla_servoview_JNIServo_pinchZoomStart(
y: jint, y: jint,
) { ) {
debug!("pinchZoomStart"); debug!("pinchZoomStart");
call(env, |s| s.pinchzoom_start(factor as f32, x as u32, y as u32)); call(&env, |s| s.pinchzoom_start(factor as f32, x as u32, y as u32));
} }
#[no_mangle] #[no_mangle]
@ -246,7 +239,7 @@ pub fn Java_org_mozilla_servoview_JNIServo_pinchZoom(
y: jint, y: jint,
) { ) {
debug!("pinchZoom"); debug!("pinchZoom");
call(env, |s| s.pinchzoom(factor as f32, x as u32, y as u32)); call(&env, |s| s.pinchzoom(factor as f32, x as u32, y as u32));
} }
#[no_mangle] #[no_mangle]
@ -258,14 +251,14 @@ pub fn Java_org_mozilla_servoview_JNIServo_pinchZoomEnd(
y: jint, y: jint,
) { ) {
debug!("pinchZoomEnd"); debug!("pinchZoomEnd");
call(env, |s| s.pinchzoom_end(factor as f32, x as u32, y as u32)); call(&env, |s| s.pinchzoom_end(factor as f32, x as u32, y as u32));
} }
#[no_mangle] #[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_click(env: JNIEnv, _: JClass, x: jint, y: jint) { pub fn Java_org_mozilla_servoview_JNIServo_click(env: JNIEnv, _: JClass, x: jint, y: jint) {
debug!("click"); debug!("click");
call(env, |s| s.click(x as u32, y as u32)); call(&env, |s| s.click(x as u32, y as u32));
} }
pub struct WakeupCallback { pub struct WakeupCallback {
@ -312,9 +305,10 @@ impl ReadFileTrait for ReadFileCallback {
fn readfile(&self, file: &str) -> Vec<u8> { fn readfile(&self, file: &str) -> Vec<u8> {
// FIXME: we'd rather use attach_current_thread but it detaches the VM too early. // FIXME: we'd rather use attach_current_thread but it detaches the VM too early.
let env = self.jvm.attach_current_thread_as_daemon().unwrap(); let env = self.jvm.attach_current_thread_as_daemon().unwrap();
let s = env.new_string(&file) let s = match new_string(&env, &file) {
.expect("Couldn't create java string") Ok(s) => s,
.into_inner(); Err(_) => return vec![],
};
let s = JValue::from(JObject::from(s)); let s = JValue::from(JObject::from(s));
let array = env.call_method( let array = env.call_method(
self.callback.lock().unwrap().as_obj(), self.callback.lock().unwrap().as_obj(),
@ -366,9 +360,10 @@ impl HostTrait for HostCallbacks {
fn on_title_changed(&self, title: String) { fn on_title_changed(&self, title: String) {
debug!("on_title_changed"); debug!("on_title_changed");
let env = self.jvm.get_env().unwrap(); let env = self.jvm.get_env().unwrap();
let s = env.new_string(&title) let s = match new_string(&env, &title) {
.expect("Couldn't create java string") Ok(s) => s,
.into_inner(); Err(_) => return,
};
let s = JValue::from(JObject::from(s)); let s = JValue::from(JObject::from(s));
env.call_method( env.call_method(
self.callbacks.as_obj(), self.callbacks.as_obj(),
@ -381,9 +376,10 @@ impl HostTrait for HostCallbacks {
fn on_url_changed(&self, url: String) { fn on_url_changed(&self, url: String) {
debug!("on_url_changed"); debug!("on_url_changed");
let env = self.jvm.get_env().unwrap(); let env = self.jvm.get_env().unwrap();
let s = env.new_string(&url) let s = match new_string(&env, &url) {
.expect("Couldn't create java string") Ok(s) => s,
.into_inner(); Err(_) => return,
};
let s = JValue::Object(JObject::from(s)); let s = JValue::Object(JObject::from(s));
env.call_method( env.call_method(
self.callbacks.as_obj(), self.callbacks.as_obj(),
@ -520,3 +516,92 @@ fn redirect_stdout_to_logcat() {
} }
}); });
} }
fn throw(env: &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: &JNIEnv, s: &str) -> Result<jstring, jstring> {
match env.new_string(s) {
Ok(s) => Ok(s.into_inner()),
Err(_) => {
throw(&env, "Couldn't create java string");
Err(JObject::null().into_inner())
},
}
}
fn get_field<'a>(
env: &'a JNIEnv,
obj: JObject,
field: &str,
type_: &str,
) -> Result<Option<JValue<'a>>, String> {
if env.get_field_id(obj, field, type_).is_err() {
return Err(format!("Can't find `{}` field", &field));
}
env.get_field(obj, field, type_)
.map(|value| Some(value))
.or_else(|e| match *e.kind() {
errors::ErrorKind::NullPtr(_) => Ok(None),
_ => Err(format!(
"Can't find `{}` field: {}",
&field,
e.description()
)),
})
}
fn get_non_null_field<'a>(
env: &'a JNIEnv,
obj: JObject,
field: &str,
type_: &str,
) -> Result<JValue<'a>, String> {
match get_field(env, obj, field, type_)? {
None => Err(format!("Field {} is null", field)),
Some(f) => Ok(f),
}
}
fn get_string(env: &JNIEnv, obj: JObject, field: &str) -> Result<Option<String>, String> {
let value = get_field(env, obj, field, "Ljava/lang/String;")?;
match value {
Some(value) => {
let string = value
.l()
.map_err(|_| format!("field `{}` is not an Object", field))?
.into();
Ok(env.get_string(string).map(|s| s.into()).ok())
},
None => Ok(None),
}
}
fn get_options(env: &JNIEnv, opts: JObject) -> Result<(InitOptions, bool, Option<String>), String> {
let args = get_string(env, opts, "args")?;
let url = get_string(env, opts, "url")?;
let log_str = get_string(env, opts, "logStr")?;
let width = get_non_null_field(env, opts, "width", "I")?
.i()
.map_err(|_| "width not an int")? as u32;
let height = get_non_null_field(env, opts, "height", "I")?
.i()
.map_err(|_| "height not an int")? as u32;
let density = get_non_null_field(env, opts, "density", "F")?
.f()
.map_err(|_| "densitiy not a float")? as f32;
let log = get_non_null_field(env, opts, "enableLogs", "Z")?
.z()
.map_err(|_| "enableLogs not a boolean")?;
let opts = InitOptions {
args,
url,
width,
height,
density,
};
Ok((opts, log, log_str))
}

View file

@ -20,13 +20,7 @@ public class JNIServo {
public native String version(); public native String version();
public native void init(Activity activity, public native void init(Activity activity, ServoOptions options, Callbacks callbacks);
String args,
String url,
String logstr,
Callbacks callbacks,
int width, int height, float density,
boolean log);
public native void setBatchMode(boolean mode); public native void setBatchMode(boolean mode);
@ -60,6 +54,16 @@ public class JNIServo {
public native void click(int x, int y); public native void click(int x, int y);
public static class ServoOptions {
public String args;
public String url;
public int width = 0;
public int height = 0;
public float density = 1;
public String logStr;
public boolean enableLogs = false;
}
public interface Callbacks { public interface Callbacks {
void wakeup(); void wakeup();

View file

@ -14,6 +14,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import org.freedesktop.gstreamer.GStreamer; import org.freedesktop.gstreamer.GStreamer;
import org.mozilla.servoview.JNIServo.ServoOptions;
public class Servo { public class Servo {
private static final String LOGTAG = "Servo"; private static final String LOGTAG = "Servo";
@ -23,15 +24,11 @@ public class Servo {
private boolean mSuspended; private boolean mSuspended;
public Servo( public Servo(
ServoOptions options,
RunCallback runCallback, RunCallback runCallback,
GfxCallbacks gfxcb, GfxCallbacks gfxcb,
Client client, Client client,
Activity activity, Activity activity) {
String args,
String url,
String logstr,
int width, int height,
float density, boolean log) {
mRunCallback = runCallback; mRunCallback = runCallback;
@ -40,7 +37,7 @@ public class Servo {
Callbacks cbs = new Callbacks(client, gfxcb); Callbacks cbs = new Callbacks(client, gfxcb);
mRunCallback.inGLThread(() -> { mRunCallback.inGLThread(() -> {
mJNI.init(activity, args, url, logstr, cbs, width, height, density, log); mJNI.init(activity, options, cbs);
}); });
try { try {

View file

@ -20,6 +20,7 @@ import android.os.Message;
import android.util.Log; import android.util.Log;
import android.view.Surface; import android.view.Surface;
import org.mozilla.servoview.JNIServo.ServoOptions;
import org.mozilla.servoview.Servo.Client; import org.mozilla.servoview.Servo.Client;
import org.mozilla.servoview.Servo.GfxCallbacks; import org.mozilla.servoview.Servo.GfxCallbacks;
import org.mozilla.servoview.Servo.RunCallback; import org.mozilla.servoview.Servo.RunCallback;
@ -37,9 +38,9 @@ public class ServoSurface {
private int mHeight; private int mHeight;
private Servo mServo; private Servo mServo;
private Client mClient = null; private Client mClient = null;
private String mServoArgs = ""; private String mServoArgs;
private String mServoLog = ""; private String mServoLog;
private String mInitialUri = null; private String mInitialUri;
private Activity mActivity; private Activity mActivity;
public ServoSurface(Surface surface, int width, int height) { public ServoSurface(Surface surface, int width, int height) {
@ -55,8 +56,8 @@ public class ServoSurface {
} }
public void setServoArgs(String args, String log) { public void setServoArgs(String args, String log) {
mServoArgs = args != null ? args : ""; mServoArgs = args;
mServoLog = log != null ? log : ""; mServoLog = log;
} }
public void setActivity(Activity activity) { public void setActivity(Activity activity) {
@ -204,9 +205,16 @@ public class ServoSurface {
}; };
inUIThread(() -> { inUIThread(() -> {
final boolean showLogs = true; ServoOptions options = new ServoOptions();
String uri = mInitialUri == null ? null : mInitialUri; options.args = mServoArgs;
mServo = new Servo(this, surface, mClient, mActivity, mServoArgs, uri, mServoLog, mWidth, mHeight, 1, showLogs); options.width = mWidth;
options.height = mHeight;
options.density = 1;
options.url = mInitialUri == null ? null : mInitialUri;
options.logStr = mServoLog;
options.enableLogs = true;
mServo = new Servo(options, this, surface, mClient, mActivity);
}); });
Looper.loop(); Looper.loop();

View file

@ -19,6 +19,7 @@ import android.view.MotionEvent;
import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector;
import android.widget.OverScroller; import android.widget.OverScroller;
import org.mozilla.servoview.JNIServo.ServoOptions;
import org.mozilla.servoview.Servo.Client; import org.mozilla.servoview.Servo.Client;
import org.mozilla.servoview.Servo.GfxCallbacks; import org.mozilla.servoview.Servo.GfxCallbacks;
import org.mozilla.servoview.Servo.RunCallback; import org.mozilla.servoview.Servo.RunCallback;
@ -41,8 +42,8 @@ public class ServoView extends GLSurfaceView
private Client mClient = null; private Client mClient = null;
private Uri mInitialUri = null; private Uri mInitialUri = null;
private boolean mAnimating; private boolean mAnimating;
private String mServoArgs = ""; private String mServoArgs;
private String mServoLog = ""; private String mServoLog;
private GestureDetector mGestureDetector; private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector; private ScaleGestureDetector mScaleGestureDetector;
@ -75,8 +76,8 @@ public class ServoView extends GLSurfaceView
} }
public void setServoArgs(String args, String log) { public void setServoArgs(String args, String log) {
mServoArgs = args != null ? args : ""; mServoArgs = args;
mServoLog = log != null ? log : ""; mServoLog = log;
} }
public void reload() { public void reload() {
@ -135,16 +136,19 @@ public class ServoView extends GLSurfaceView
} }
public void onGLReady() { public void onGLReady() {
final boolean showLogs = true; ServoOptions options = new ServoOptions();
int width = getWidth(); options.args = mServoArgs;
int height = getHeight(); options.width = getWidth();
options.height = getHeight();
options.enableLogs = true;
DisplayMetrics metrics = new DisplayMetrics(); DisplayMetrics metrics = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics); mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
float density = metrics.density; options.density = metrics.density;
inGLThread(() -> { inGLThread(() -> {
String uri = mInitialUri == null ? null : mInitialUri.toString(); String uri = mInitialUri == null ? null : mInitialUri.toString();
mServo = new Servo(this, this, mClient, mActivity, mServoArgs, uri, mServoLog, width, height, density, showLogs); options.url = uri;
options.logStr = mServoLog;
mServo = new Servo(options, this, this, mClient, mActivity);
}); });
} }