new android port: introduce a simple servo library

This commit is contained in:
Paul Rouget 2018-07-04 16:43:38 +08:00
parent cbaf19c65c
commit baf6635a4c
16 changed files with 1398 additions and 30 deletions

View file

@ -0,0 +1,46 @@
[package]
name = "libsimpleservo"
version = "0.0.1"
build = "build.rs"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
publish = false
[lib]
name = "simpleservo"
crate-type = ["cdylib"]
test = false
bench = false
[dependencies]
libservo = { path = "../../components/servo" }
log = "0.4"
serde_json = "1.0"
[target.'cfg(target_os = "android")'.dependencies]
android_injected_glue = "0.2"
android_logger = "0.6"
# FIXME: use `jni = "0.10.2"` once
# https://github.com/prevoty/jni-rs/pull/98 lands and is published
jni = { git = "https://github.com/paulrouget/jni-rs", branch = "return_javavm" }
[target.'cfg(not(target_os = "macos"))'.dependencies]
libc = "0.2"
[target.'cfg(target_os = "windows")'.dependencies]
winapi = "0.3.2"
[build-dependencies]
gl_generator = "0.9"
cc = "1.0"
[features]
default = ["unstable", "default-except-unstable"]
default-except-unstable = ["webdriver", "max_log_level"]
max_log_level = ["log/release_max_level_info"]
webdriver = ["libservo/webdriver"]
energy-profiling = ["libservo/energy-profiling"]
debugmozjs = ["libservo/debugmozjs"]
unstable = ["libservo/unstable"]
googlevr = ["libservo/googlevr"]
oculusvr = ["libservo/oculusvr"]

View file

@ -0,0 +1,3 @@
This is a basic wrapper around Servo. While libservo itself (/components/servo/) offers a lot of flexibility,
libsimpleservo (/ports/libsimpleservo/) tries to make it easier to embed Servo, without much configuration needed.
It is limited to only one view (no tabs, no multiple rendering area).

View file

@ -0,0 +1,81 @@
/* 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 http://mozilla.org/MPL/2.0/. */
extern crate cc;
extern crate gl_generator;
use gl_generator::{Api, Fallbacks, Profile, Registry};
use std::env;
use std::fs::File;
use std::path::Path;
fn main() {
let target = env::var("TARGET").unwrap();
if target.contains("android") {
android_main()
}
generate_gl_bindings(&target);
}
fn android_main() {
// Get the NDK path from NDK_HOME env.
let ndk_path = env::var_os("ANDROID_NDK").expect("Please set the ANDROID_NDK environment variable");
let ndk_path = Path::new(&ndk_path);
let target = env::var("TARGET").unwrap();
let arch = if target.contains("arm") {
"arch-arm"
} else if target.contains("aarch64") {
"arch-arm64"
} else if target.contains("x86") || target.contains("i686") {
"arch-x86"
} else if target.contains("mips") {
"arch-mips"
} else {
panic!("Invalid target architecture {}", target);
};
let platform = if target.contains("aarch64") {
"android-21"
} else {
"android-18"
};
// compiling android_native_app_glue.c
let c_file = ndk_path.join("sources").join("android").join("native_app_glue").join("android_native_app_glue.c");
let sysroot = ndk_path.join("platforms").join(platform).join(arch);
cc::Build::new()
.file(c_file)
.flag("--sysroot")
.flag(sysroot.to_str().unwrap())
.warnings(false)
.compile("android_native_app_glue");
// Get the output directory.
let out_dir = env::var("OUT_DIR").expect("Cargo should have set the OUT_DIR environment variable");
println!("cargo:rustc-link-lib=static=android_native_app_glue");
println!("cargo:rustc-link-search=native={}", out_dir);
println!("cargo:rustc-link-lib=log");
println!("cargo:rustc-link-lib=android");
}
fn generate_gl_bindings(target: &str) {
// For now, we only support EGL, and only on Windows and Android.
if target.contains("android") || target.contains("windows") {
let dest = env::var("OUT_DIR").unwrap();
let mut file = File::create(&Path::new(&dest).join("egl_bindings.rs")).unwrap();
if target.contains("android") {
Registry::new(Api::Egl, (1, 5), Profile::Core, Fallbacks::All, [])
.write_bindings(gl_generator::StaticStructGenerator, &mut file)
.unwrap();
}
if target.contains("windows") {
Registry::new(Api::Egl, (1, 5), Profile::Core, Fallbacks::All, [])
.write_bindings(gl_generator::StructGenerator, &mut file)
.unwrap();
};
}
}

View file

@ -0,0 +1,424 @@
/* 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 http://mozilla.org/MPL/2.0/. */
use serde_json;
use servo::{self, gl, webrender_api, BrowserId, Servo};
use servo::compositing::windowing::{AnimationState, EmbedderCoordinates, MouseWindowEvent, WindowEvent, WindowMethods};
use servo::embedder_traits::EmbedderMsg;
use servo::embedder_traits::resources::{self, Resource};
use servo::euclid::{Length, TypedPoint2D, TypedScale, TypedSize2D, TypedVector2D};
use servo::ipc_channel::ipc;
use servo::msg::constellation_msg::TraversalDirection;
use servo::script_traits::{MouseButton, TouchEventType};
use servo::servo_config::opts;
use servo::servo_config::prefs::PREFS;
use servo::servo_url::ServoUrl;
use servo::style_traits::DevicePixel;
use std::cell::{Cell, RefCell};
use std::mem;
use std::path::PathBuf;
use std::rc::Rc;
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;
/// Delegate resource file reading to the embedder.
pub trait ReadFileTrait {
fn readfile(&self, file: &str) -> Vec<u8>;
}
/// Callbacks. Implemented by embedder. Called by Servo.
pub trait HostTrait {
/// Will be called from the thread used for the init call.
/// Will be called when the GL buffer has been updated.
fn flush(&self);
/// 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: String);
/// 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);
}
pub struct ServoGlue {
servo: Servo<ServoCallbacks>,
batch_mode: bool,
callbacks: Rc<ServoCallbacks>,
browser_id: BrowserId,
events: Vec<WindowEvent>,
current_url: Option<ServoUrl>,
}
pub fn servo_version() -> String {
servo::config::servo_version()
}
/// Initialize Servo. At that point, we need a valid GL context.
/// In the future, this will be done in multiple steps.
pub fn init(
gl: Rc<gl::Gl>,
argsline: String,
embedder_url: Option<String>,
waker: Box<EventLoopWaker>,
readfile: Box<ReadFileTrait + Send + Sync>,
callbacks: Box<HostTrait>,
width: u32,
height: u32,
) -> Result<(), &'static str> {
resources::set(Box::new(ResourceReader(readfile)));
let mut args: Vec<String> = serde_json::from_str(&argsline).map_err(|_| {
"Invalid arguments. Servo arguments must be formatted as a JSON array"
})?;
// opts::from_cmdline_args expects the first argument to be the binary name.
args.insert(0, "servo".to_string());
opts::from_cmdline_args(&args);
let embedder_url = embedder_url.as_ref().and_then(|s| {
ServoUrl::parse(s).ok()
});
let cmdline_url = opts::get().url.clone();
let pref_url = PREFS.get("shell.homepage").as_string().and_then(|s| {
ServoUrl::parse(s).ok()
});
let blank_url = ServoUrl::parse("about:blank").ok();
let url = embedder_url
.or(cmdline_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();
let callbacks = Rc::new(ServoCallbacks {
gl: gl.clone(),
host_callbacks: callbacks,
width: Cell::new(width),
height: Cell::new(height),
waker,
});
let mut servo = Servo::new(callbacks.clone());
let (sender, receiver) = ipc::channel().map_err(|_| "Can't create ipc::channel")?;
servo.handle_events(vec![WindowEvent::NewBrowser(url.clone(), sender)]);
let browser_id = receiver.recv().map_err(|_| "Can't receive browser_id")?;
servo.handle_events(vec![WindowEvent::SelectBrowser(browser_id)]);
SERVO.with(|s| {
*s.borrow_mut() = Some(ServoGlue {
servo,
batch_mode: false,
callbacks,
browser_id,
events: vec![],
current_url: Some(url),
});
});
Ok(())
}
impl ServoGlue {
/// 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);
self.handle_servo_events()
}
/// 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. This needs to be a valid url.
pub fn load_uri(&mut self, url: &str) -> Result<(), &'static str> {
debug!("load_uri: {}", url);
ServoUrl::parse(url)
.map_err(|_| "Can't parse URL")
.and_then(|url| {
let event = WindowEvent::LoadUrl(self.browser_id, url);
self.process_event(event)
})
}
/// Reload the page.
pub fn reload(&mut self) -> Result<(), &'static str> {
debug!("reload");
let event = WindowEvent::Reload(self.browser_id);
self.process_event(event)
}
/// Go back in history.
pub fn go_back(&mut self) -> Result<(), &'static str> {
debug!("go_back");
let event = WindowEvent::Navigation(self.browser_id, TraversalDirection::Back(1));
self.process_event(event)
}
/// Go forward in history.
pub fn go_forward(&mut self) -> Result<(), &'static str> {
debug!("go_forward");
let event = WindowEvent::Navigation(self.browser_id, TraversalDirection::Forward(1));
self.process_event(event)
}
/// Let Servo know that the window has been resized.
pub fn resize(&mut self, width: u32, height: u32) -> Result<(), &'static str> {
debug!("resize");
self.callbacks.width.set(width);
self.callbacks.height.set(height);
self.process_event(WindowEvent::Resize)
}
/// Start scrolling.
/// x/y are scroll coordinates.
/// dx/dy are scroll deltas.
pub fn scroll_start(&mut self, dx: i32, dy: i32, x: u32, y: u32) -> Result<(), &'static str> {
let delta = TypedVector2D::new(dx as f32, dy as f32);
let scroll_location = webrender_api::ScrollLocation::Delta(delta);
let event = WindowEvent::Scroll(
scroll_location,
TypedPoint2D::new(x as i32, y as i32),
TouchEventType::Down,
);
self.process_event(event)
}
/// Scroll.
/// x/y are scroll coordinates.
/// dx/dy are scroll deltas.
pub fn scroll(&mut self, dx: i32, dy: i32, x: u32, y: u32) -> Result<(), &'static str> {
let delta = TypedVector2D::new(dx as f32, dy as f32);
let scroll_location = webrender_api::ScrollLocation::Delta(delta);
let event = WindowEvent::Scroll(
scroll_location,
TypedPoint2D::new(x as i32, y as i32),
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: i32, dy: i32, x: u32, y: u32) -> Result<(), &'static str> {
let delta = TypedVector2D::new(dx as f32, dy as f32);
let scroll_location = webrender_api::ScrollLocation::Delta(delta);
let event = WindowEvent::Scroll(
scroll_location,
TypedPoint2D::new(x as i32, y as i32),
TouchEventType::Up,
);
self.process_event(event)
}
/// Perform a click.
pub fn click(&mut self, x: u32, y: u32) -> Result<(), &'static str> {
let mouse_event =
MouseWindowEvent::Click(MouseButton::Left, TypedPoint2D::new(x as f32, y as f32));
let event = WindowEvent::MouseWindowEventClass(mouse_event);
self.process_event(event)
}
fn process_event(&mut self, event: WindowEvent) -> 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> {
for (_browser_id, event) in self.servo.get_events() {
match event {
EmbedderMsg::ChangePageTitle(title) => {
let fallback_title: String = if let Some(ref current_url) = self.current_url {
current_url.to_string()
} else {
String::from("Untitled")
};
let title = match title {
Some(ref title) if title.len() > 0 => &**title,
_ => &fallback_title,
};
let title = format!("{} - Servo", title);
self.callbacks.host_callbacks.on_title_changed(title);
},
EmbedderMsg::AllowNavigation(_url, response_chan) => {
if let Err(e) = response_chan.send(true) {
warn!("Failed to send allow_navigation() response: {}", e);
};
},
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());
self.current_url = Some(entries[current].clone());
},
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::Alert(message, sender) => {
info!("Alert: {}", message);
let _ = sender.send(());
},
EmbedderMsg::CloseBrowser |
EmbedderMsg::Status(..) |
EmbedderMsg::SelectFiles(..) |
EmbedderMsg::MoveTo(..) |
EmbedderMsg::ResizeTo(..) |
EmbedderMsg::KeyEvent(..) |
EmbedderMsg::SetCursor(..) |
EmbedderMsg::NewFavicon(..) |
EmbedderMsg::HeadParsed |
EmbedderMsg::SetFullscreenState(..) |
EmbedderMsg::ShowIME(..) |
EmbedderMsg::HideIME |
EmbedderMsg::Shutdown |
EmbedderMsg::Panic(..) => {},
}
}
Ok(())
}
}
struct ServoCallbacks {
waker: Box<EventLoopWaker>,
gl: Rc<gl::Gl>,
host_callbacks: Box<HostTrait>,
width: Cell<u32>,
height: Cell<u32>,
}
impl WindowMethods for ServoCallbacks {
fn prepare_for_composite(
&self,
_width: Length<u32, DevicePixel>,
_height: Length<u32, DevicePixel>,
) -> bool {
debug!("WindowMethods::prepare_for_composite");
true
}
fn present(&self) {
debug!("WindowMethods::present");
self.host_callbacks.flush();
}
fn supports_clipboard(&self) -> bool {
debug!("WindowMethods::supports_clipboard");
false
}
fn create_event_loop_waker(&self) -> Box<EventLoopWaker> {
debug!("WindowMethods::create_event_loop_waker");
self.waker.clone()
}
fn gl(&self) -> Rc<gl::Gl> {
debug!("WindowMethods::gl");
self.gl.clone()
}
fn set_animation_state(&self, state: AnimationState) {
debug!("WindowMethods::set_animation_state");
self.host_callbacks.on_animating_changed(state == AnimationState::Animating);
}
fn get_coordinates(&self) -> EmbedderCoordinates {
let size = TypedSize2D::new(self.width.get(), self.height.get());
EmbedderCoordinates {
viewport: webrender_api::DeviceUintRect::new(TypedPoint2D::zero(), size),
framebuffer: size,
window: (size, TypedPoint2D::new(0, 0)),
screen: size,
screen_avail: size,
hidpi_factor: TypedScale::new(2.0),
}
}
}
struct ResourceReader(Box<ReadFileTrait + Send + Sync>);
impl resources::ResourceReaderMethods for ResourceReader {
fn read(&self, file: Resource) -> Vec<u8> {
let file = match file {
Resource::Preferences => "prefs.json",
Resource::BluetoothBlocklist => "gatt_blocklist.txt",
Resource::DomainList => "public_domains.txt",
Resource::HstsPreloadList => "hsts_preload.json",
Resource::SSLCertificates => "certs",
Resource::BadCertHTML => "badcert.html",
Resource::NetErrorHTML => "neterror.html",
Resource::UserAgentCSS => "user-agent.css",
Resource::ServoCSS => "servo.css",
Resource::PresentationalHintsCSS => "presentational-hints.css",
Resource::QuirksModeCSS => "quirks-mode.css",
Resource::RippyPNG => "rippy.png",
};
debug!("ResourceReader::read({})", file);
self.0.readfile(file)
}
fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
vec![]
}
fn sandbox_access_files(&self) -> Vec<PathBuf> {
vec![]
}
}

View file

@ -0,0 +1,261 @@
/* 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 http://mozilla.org/MPL/2.0/. */
use api::{self, EventLoopWaker, ServoGlue, SERVO, HostTrait, ReadFileTrait};
use gl_glue;
use servo::gl;
use std::ffi::{CStr, CString};
use std::mem;
use std::os::raw::c_char;
use std::rc::Rc;
fn call<F>(f: F) where F: Fn(&mut ServoGlue) -> Result<(), &'static 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"),
} {
// FIXME: All C calls should have a have generic Result-like
// return type. For now, we just panic instead of notifying
// the embedder.
panic!(error);
}
});
}
/// Callback used by Servo internals
#[repr(C)]
pub struct CHostCallbacks {
pub flush: extern fn(),
pub on_load_started: extern fn(),
pub on_load_ended: extern fn(),
pub on_title_changed: extern fn(title: *const c_char),
pub on_url_changed: extern fn(url: *const c_char),
pub on_history_changed: extern fn(can_go_back: bool, can_go_forward: bool),
pub on_animating_changed: extern fn(animating: bool),
}
/// The returned string is not freed. This will leak.
#[no_mangle]
pub extern "C" fn servo_version() -> *const c_char {
let v = api::servo_version();
let text = CString::new(v).expect("Can't create string");
let ptr = text.as_ptr();
mem::forget(text);
ptr
}
fn init(
gl: Rc<gl::Gl>,
args: *const c_char,
url: *const c_char,
wakeup: extern fn(),
readfile: extern fn(*const c_char) -> *const c_char,
callbacks: CHostCallbacks,
width: u32,
height: u32) {
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 = url.to_str().map(|s| s.to_string());
let wakeup = Box::new(WakeupCallback::new(wakeup));
let readfile = Box::new(ReadFileCallback::new(readfile));
let callbacks = Box::new(HostCallbacks::new(callbacks));
api::init(
gl,
args,
url.ok(),
wakeup,
readfile,
callbacks,
width,
height,
).unwrap();
}
#[cfg(target_os = "windows")]
#[no_mangle]
pub extern "C" fn init_with_egl(
args: *const c_char,
url: *const c_char,
wakeup: extern fn(),
readfile: extern fn(*const c_char) -> *const c_char,
callbacks: CHostCallbacks,
width: u32,
height: u32) {
let gl = gl_glue::egl::init().unwrap();
init(gl, args, url, wakeup, readfile, callbacks, width, height)
}
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
#[no_mangle]
pub extern "C" fn init_with_gl(
args: *const c_char,
url: *const c_char,
wakeup: extern fn(),
readfile: extern fn(*const c_char) -> *const c_char,
callbacks: CHostCallbacks,
width: u32,
height: u32) {
let gl = gl_glue::gl::init().unwrap();
init(gl, args, url, wakeup, readfile, callbacks, width, height)
}
#[no_mangle]
pub extern "C" fn set_batch_mode(batch: bool) {
debug!("set_batch_mode");
call(|s| s.set_batch_mode(batch));
}
#[no_mangle]
pub extern "C" fn resize(width: u32, height: u32) {
debug!("resize {}/{}", width, height);
call(|s| s.resize(width, height));
}
#[no_mangle]
pub extern "C" fn perform_updates() {
debug!("perform_updates");
call(|s| s.perform_updates());
}
#[no_mangle]
pub extern "C" fn load_uri(url: *const c_char) {
debug!("load_url");
let url = unsafe { CStr::from_ptr(url) };
let url = url.to_str().expect("Can't read string");
call(|s| s.load_uri(url));
}
#[no_mangle]
pub extern "C" fn reload() {
debug!("reload");
call(|s| s.reload());
}
#[no_mangle]
pub extern "C" fn go_back() {
debug!("go_back");
call(|s| s.go_back());
}
#[no_mangle]
pub extern "C" fn go_forward() {
debug!("go_forward");
call(|s| s.go_forward());
}
#[no_mangle]
pub extern "C" fn scroll_start(dx: i32, dy: i32, x: i32, y: i32) {
debug!("scroll_start");
call(|s| s.scroll_start(dx as i32, dy as i32, x as u32, y as u32));
}
#[no_mangle]
pub extern "C" fn scroll_end(dx: i32, dy: i32, x: i32, y: i32) {
debug!("scroll_end");
call(|s| s.scroll_end(dx as i32, dy as i32, x as u32, y as u32));
}
#[no_mangle]
pub extern "C" fn scroll(dx: i32, dy: i32, x: i32, y: i32) {
debug!("scroll");
call(|s| s.scroll(dx as i32, dy as i32, x as u32, y as u32));
}
#[no_mangle]
pub extern "C" fn click(x: i32, y: i32) {
debug!("click");
call(|s| s.click(x as u32, y as u32));
}
pub struct WakeupCallback(extern fn());
impl WakeupCallback {
fn new(callback: extern fn()) -> WakeupCallback {
WakeupCallback(callback)
}
}
impl EventLoopWaker for WakeupCallback {
fn clone(&self) -> Box<EventLoopWaker + Send> {
Box::new(WakeupCallback(self.0))
}
fn wake(&self) {
(self.0)();
}
}
pub struct ReadFileCallback(extern fn(*const c_char) -> *const c_char);
impl ReadFileCallback {
fn new(callback: extern fn(*const c_char) -> *const c_char) -> ReadFileCallback {
ReadFileCallback(callback)
}
}
impl ReadFileTrait for ReadFileCallback {
fn readfile(&self, file: &str) -> Vec<u8> {
debug!("readfile: {}", file);
let file = CString::new(file).expect("Can't create string");
let file_ptr = file.as_ptr();
let content = (self.0)(file_ptr);
let content = unsafe { CStr::from_ptr(content) };
content.to_bytes().to_owned()
}
}
struct HostCallbacks(CHostCallbacks);
impl HostCallbacks {
fn new(callback: CHostCallbacks) -> HostCallbacks {
HostCallbacks(callback)
}
}
impl HostTrait for HostCallbacks {
fn flush(&self) {
debug!("flush");
(self.0.flush)();
}
fn on_load_started(&self) {
debug!("on_load_ended");
(self.0.on_load_started)();
}
fn on_load_ended(&self) {
debug!("on_load_ended");
(self.0.on_load_ended)();
}
fn on_title_changed(&self, title: String) {
debug!("on_title_changed");
let title = CString::new(title).expect("Can't create string");
let title_ptr = title.as_ptr();
mem::forget(title);
(self.0.on_title_changed)(title_ptr);
}
fn on_url_changed(&self, url: String) {
debug!("on_url_changed");
let url = CString::new(url).expect("Can't create string");
let url_ptr = url.as_ptr();
mem::forget(url);
(self.0.on_url_changed)(url_ptr);
}
fn on_history_changed(&self, can_go_back: bool, can_go_forward: bool) {
debug!("on_history_changed");
(self.0.on_history_changed)(can_go_back, can_go_forward);
}
fn on_animating_changed(&self, animating: bool) {
debug!("on_animating_changed");
(self.0.on_animating_changed)(animating);
}
}

View file

@ -0,0 +1,84 @@
/* 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 http://mozilla.org/MPL/2.0/. */
#[cfg(any(target_os = "android", target_os = "windows"))]
#[allow(non_camel_case_types)]
pub mod egl {
use libc;
use servo::gl::{Gl, GlesFns};
use std::ffi::CString;
#[cfg(not(target_os = "windows"))]
use std::os::raw::c_void;
use std::rc::Rc;
#[cfg(target_os = "windows")]
use winapi;
#[cfg(target_os = "windows")]
use winapi::um::libloaderapi::{GetProcAddress, LoadLibraryA};
#[cfg(target_os = "windows")]
pub type EGLNativeWindowType = winapi::shared::windef::HWND;
#[cfg(target_os = "linux")]
pub type EGLNativeWindowType = *const libc::c_void;
#[cfg(target_os = "android")]
pub type EGLNativeWindowType = *const libc::c_void;
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
pub type EGLNativeWindowType = *const libc::c_void;
pub type khronos_utime_nanoseconds_t = khronos_uint64_t;
pub type khronos_uint64_t = libc::uint64_t;
pub type khronos_ssize_t = libc::c_long;
pub type EGLint = libc::int32_t;
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"));
#[cfg(target_os = "android")]
pub fn init() -> Result<Rc<Gl>, &'static str> {
debug!("init_egl");
unsafe {
let egl = Egl;
let d = egl.GetCurrentDisplay();
egl.SwapInterval(d, 1);
Ok(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
}))
}
}
#[cfg(target_os = "windows")]
pub fn init() -> Result<Rc<Gl>, &'static str> {
debug!("init_egl");
let dll = b"libEGL.dll\0" as &[u8];
let dll = unsafe { LoadLibraryA(dll.as_ptr() as *const _) };
if dll.is_null() {
Err("Can't find libEGL.dll")
} else {
unsafe {
Ok(GlesFns::load_with(|addr| {
let addr = CString::new(addr.as_bytes()).unwrap();
let addr = addr.as_ptr();
GetProcAddress(dll, addr) as *const _
}))
}
}
}
}
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
pub mod gl {
use servo::gl::Gl;
use std::rc::Rc;
pub fn init() -> Result<Rc<Gl>, &'static str> {
// FIXME: Add an OpenGL version
unimplemented!()
}
}

View file

@ -0,0 +1,375 @@
/* 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 http://mozilla.org/MPL/2.0/. */
#![allow(non_snake_case)]
use android_logger::{self, Filter};
use api::{self, EventLoopWaker, ServoGlue, SERVO, HostTrait, ReadFileTrait};
use gl_glue;
use jni::{JNIEnv, JavaVM};
use jni::objects::{GlobalRef, JClass, JObject, JString, JValue};
use jni::sys::{jboolean, jint, jstring, JNI_TRUE};
use log::Level;
use std;
use std::os::raw::c_void;
use std::sync::{Arc, Mutex};
struct HostCallbacks {
callbacks: GlobalRef,
jvm: JavaVM,
}
fn call<F>(env: JNIEnv, f: F)
where
F: Fn(&mut ServoGlue) -> Result<(), &'static 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"),
} {
env.throw(("java/lang/Exception", error))
.expect("Error while throwing");
}
});
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_version(env: JNIEnv, _class: JClass) -> jstring {
let v = api::servo_version();
let output = env.new_string(v).expect("Couldn't create java string");
output.into_inner()
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_init(
env: JNIEnv,
_: JClass,
activity: JObject,
args: JString,
url: JString,
wakeup_obj: JObject,
readfile_obj: JObject,
callbacks_obj: JObject,
width: jint,
height: jint,
log: jboolean,
) {
if log == JNI_TRUE {
android_logger::init_once(
Filter::default()
.with_min_level(Level::Debug)
.with_allowed_module_path("simpleservo::api")
.with_allowed_module_path("simpleservo::jniapi"),
Some("simpleservo")
);
}
debug!("init");
initialize_android_glue(&env, activity);
let args = env.get_string(args)
.expect("Couldn't get java string")
.into();
let url = if url.is_null() {
None
} else {
Some(env.get_string(url).expect("Couldn't get java string").into())
};
let wakeup = Box::new(WakeupCallback::new(wakeup_obj, &env));
let readfile = Box::new(ReadFileCallback::new(readfile_obj, &env));
let callbacks = Box::new(HostCallbacks::new(callbacks_obj, &env));
gl_glue::egl::init().and_then(|gl| {
api::init(
gl,
args,
url,
wakeup,
readfile,
callbacks,
width as u32,
height as u32)
}).or_else(|err| {
env.throw(("java/lang/Exception", err))
}).unwrap();
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_setBatchMode(
env: JNIEnv,
_: JClass,
batch: jboolean,
) {
debug!("setBatchMode");
call(env, |s| s.set_batch_mode(batch == JNI_TRUE));
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_resize(
env: JNIEnv,
_: JClass,
width: jint,
height: jint,
) {
debug!("resize {}/{}", width, height);
call(env, |s| s.resize(width as u32, height as u32));
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_performUpdates(env: JNIEnv, _class: JClass) {
debug!("performUpdates");
call(env, |s| s.perform_updates());
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_loadUri(env: JNIEnv, _class: JClass, url: JString) {
debug!("loadUri");
let url: String = env.get_string(url).unwrap().into();
call(env, |s| s.load_uri(&url));
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_reload(env: JNIEnv, _class: JClass) {
debug!("reload");
call(env, |s| s.reload());
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_goBack(env: JNIEnv, _class: JClass) {
debug!("goBack");
call(env, |s| s.go_back());
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_goForward(env: JNIEnv, _class: JClass) {
debug!("goForward");
call(env, |s| s.go_forward());
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_scrollStart(
env: JNIEnv,
_: JClass,
dx: jint,
dy: jint,
x: jint,
y: jint,
) {
debug!("scrollStart");
call(env, |s| s.scroll_start(dx as i32, dy as i32, x as u32, y as u32));
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_scrollEnd(
env: JNIEnv,
_: JClass,
dx: jint,
dy: jint,
x: jint,
y: jint,
) {
debug!("scrollEnd");
call(env, |s| s.scroll_end(dx as i32, dy as i32, x as u32, y as u32));
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_scroll(
env: JNIEnv,
_: JClass,
dx: jint,
dy: jint,
x: jint,
y: jint,
) {
debug!("scroll");
call(env, |s| s.scroll(dx as i32, dy as i32, x as u32, y as u32));
}
#[no_mangle]
pub fn Java_com_mozilla_servoview_NativeServo_click(env: JNIEnv, _: JClass, x: jint, y: jint) {
debug!("click");
call(env, |s| s.click(x as u32, y as u32));
}
pub struct WakeupCallback {
callback: GlobalRef,
jvm: Arc<JavaVM>,
}
impl WakeupCallback {
pub fn new(jobject: JObject, env: &JNIEnv) -> WakeupCallback {
let jvm = Arc::new(env.get_java_vm().unwrap());
WakeupCallback {
callback: env.new_global_ref(jobject).unwrap(),
jvm,
}
}
}
impl EventLoopWaker for WakeupCallback {
fn clone(&self) -> Box<EventLoopWaker + Send> {
Box::new(WakeupCallback {
callback: self.callback.clone(),
jvm: self.jvm.clone(),
})
}
fn wake(&self) {
debug!("wakeup");
let env = self.jvm.attach_current_thread().unwrap();
env.call_method(self.callback.as_obj(), "wakeup", "()V", &[])
.unwrap();
}
}
pub struct ReadFileCallback {
callback: Mutex<GlobalRef>,
jvm: JavaVM,
}
impl ReadFileCallback {
pub fn new(jobject: JObject, env: &JNIEnv) -> ReadFileCallback {
let jvm = env.get_java_vm().unwrap();
let callback = Mutex::new(env.new_global_ref(jobject).unwrap());
ReadFileCallback { callback, jvm }
}
}
impl ReadFileTrait for ReadFileCallback {
fn readfile(&self, file: &str) -> Vec<u8> {
// 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 s = env.new_string(&file)
.expect("Couldn't create java string")
.into_inner();
let s = JValue::from(JObject::from(s));
let array = env.call_method(
self.callback.lock().unwrap().as_obj(),
"readfile",
"(Ljava/lang/String;)[B",
&[s],
);
let array = array.unwrap().l().unwrap().into_inner();
env.convert_byte_array(array).unwrap()
}
}
impl HostCallbacks {
pub fn new(jobject: JObject, env: &JNIEnv) -> HostCallbacks {
let jvm = env.get_java_vm().unwrap();
HostCallbacks {
callbacks: env.new_global_ref(jobject).unwrap(),
jvm,
}
}
}
impl HostTrait for HostCallbacks {
fn flush(&self) {
debug!("flush");
let env = self.jvm.get_env().unwrap();
env.call_method(self.callbacks.as_obj(), "flush", "()V", &[])
.unwrap();
}
fn on_load_started(&self) {
debug!("on_load_started");
let 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 env = self.jvm.get_env().unwrap();
env.call_method(self.callbacks.as_obj(), "onLoadEnded", "()V", &[])
.unwrap();
}
fn on_title_changed(&self, title: String) {
debug!("on_title_changed");
let env = self.jvm.get_env().unwrap();
let s = env.new_string(&title)
.expect("Couldn't create java string")
.into_inner();
let s = JValue::from(JObject::from(s));
env.call_method(
self.callbacks.as_obj(),
"onTitleChanged",
"(Ljava/lang/String;)V",
&[s],
).unwrap();
}
fn on_url_changed(&self, url: String) {
debug!("on_url_changed");
let env = self.jvm.get_env().unwrap();
let s = env.new_string(&url)
.expect("Couldn't create java string")
.into_inner();
let s = JValue::Object(JObject::from(s));
env.call_method(
self.callbacks.as_obj(),
"onUrlChanged",
"(Ljava/lang/String;)V",
&[s],
).unwrap();
}
fn on_history_changed(&self, can_go_back: bool, can_go_forward: bool) {
debug!("on_history_changed");
let 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 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 initialize_android_glue(env: &JNIEnv, activity: JObject) {
use android_injected_glue::{ANDROID_APP, ffi};
// From jni-rs to android_injected_glue
let mut app: ffi::android_app = unsafe {
std::mem::zeroed()
};
let mut native_activity: ffi::ANativeActivity = unsafe {
std::mem::zeroed()
};
let clazz = Box::into_raw(Box::new(env.new_global_ref(activity).unwrap()));
native_activity.clazz = unsafe {
(*clazz).as_obj().into_inner() as *mut c_void
};
let vm = env.get_java_vm().unwrap().get_java_vm_pointer();
native_activity.vm = vm as *mut ffi::_JavaVM;
app.activity = Box::into_raw(Box::new(native_activity));
unsafe {
ANDROID_APP = Box::into_raw(Box::new(app));
}
}

View file

@ -0,0 +1,33 @@
/* 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 http://mozilla.org/MPL/2.0/. */
#[cfg(target_os = "android")]
extern crate android_injected_glue;
#[cfg(target_os = "android")]
extern crate android_logger;
#[cfg(target_os = "android")]
extern crate jni;
#[cfg(any(target_os = "android", target_os = "windows"))]
extern crate libc;
#[macro_use]
extern crate log;
extern crate serde_json;
extern crate servo;
#[cfg(target_os = "windows")]
extern crate winapi;
mod api;
mod gl_glue;
// If not Android, expose the C-API
#[cfg(not(target_os = "android"))]
mod capi;
#[cfg(not(target_os = "android"))]
pub use capi::*;
// If Android, expose the JNI-API
#[cfg(target_os = "android")]
mod jniapi;
#[cfg(target_os = "android")]
pub use jniapi::*;

View file

@ -29,8 +29,6 @@ max_log_level = ["log/release_max_level_info"]
webdriver = ["libservo/webdriver"]
energy-profiling = ["libservo/energy-profiling"]
debugmozjs = ["libservo/debugmozjs"]
googlevr = ["libservo/googlevr"]
oculusvr = ["libservo/oculusvr"]
unstable = ["libservo/unstable"]
[target.'cfg(not(target_os = "android"))'.dependencies]