diff --git a/Cargo.lock b/Cargo.lock index 5d67aab91de..a7cff00f340 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,21 @@ name = "android_injected_glue" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "android_log-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "android_logger" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "android_log-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ansi_term" version = "0.10.2" @@ -53,6 +68,11 @@ dependencies = [ "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ascii" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "atomic_refcell" version = "0.1.0" @@ -317,6 +337,11 @@ name = "cc" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cexpr" version = "0.2.0" @@ -418,6 +443,15 @@ name = "color_quant" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "combine" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ascii 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "compositing" version = "0.0.1" @@ -1360,6 +1394,24 @@ dependencies = [ "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "jni" +version = "0.10.2" +source = "git+https://github.com/paulrouget/jni-rs?branch=return_javavm#27a18dbc01b723055b60726c43d43c0f8c26448e" +dependencies = [ + "cesu8 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "combine 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jni-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "jpeg-decoder" version = "0.1.14" @@ -1587,6 +1639,22 @@ dependencies = [ "webvr_traits 0.0.1", ] +[[package]] +name = "libsimpleservo" +version = "0.0.1" +dependencies = [ + "android_injected_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "android_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "gl_generator 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jni 0.10.2 (git+https://github.com/paulrouget/jni-rs?branch=return_javavm)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "libservo 0.0.1", + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libz-sys" version = "1.0.18" @@ -2764,7 +2832,6 @@ dependencies = [ name = "servo" version = "0.0.1" dependencies = [ - "android_injected_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2776,7 +2843,6 @@ dependencies = [ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "osmesa-src 18.1.0-devel (git+https://github.com/servo/osmesa-src)", "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "sig 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "tinyfiledialogs 3.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2886,7 +2952,6 @@ dependencies = [ name = "servo_config" version = "0.0.1" dependencies = [ - "android_injected_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "embedder_traits 0.0.1", "env_logger 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3795,10 +3860,13 @@ dependencies = [ "checksum alloc-no-stdlib 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b21f6ad9c9957eb5d70c3dee16d31c092b3cab339628f821766b05e6833d72b8" "checksum android_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" "checksum android_injected_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "80b9e34fcbf29c0563547cb2ecce9b49504597cad6166769b1e4efb45c6c2951" +"checksum android_log-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b8052e2d8aabbb8d556d6abbcce2a22b9590996c5f849b9c7ce4544a2e3b984e" +"checksum android_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bad99185bc195e796e1591740c26716667b58ac9210a48731f71f803fc6ca43a" "checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455" "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" "checksum app_units 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c4720c83543de184d9f6add2fdb8e8031543497b8506620884c16e125b493c09" "checksum arrayvec 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2f0ef4a9820019a0c91d918918c93dc71d469f581a49b47ddc1d285d4270bbe2" +"checksum ascii 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae7d751998c189c1d4468cf0a39bb2eae052a9c58d50ebb3b9591ee3813ad50" "checksum atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb2dcb6e6d35f20276943cc04bb98e538b348d525a04ac79c10021561d202f21" "checksum atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "21e50800ec991574876040fff8ee46b136a53e985286fbe6a3bdfe6421b78860" "checksum audio-video-metadata 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "71536082079f5ba92c274fba7c2dcd4e2f9d5c13ce6d7f8fe9acbbb258916d18" @@ -3823,6 +3891,7 @@ dependencies = [ "checksum bytes 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1b7db437d718977f6dc9b2e3fd6fc343c02ac6b899b73fdd2179163447bd9ce9" "checksum caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "808dab3318747be122cb31d36de18d4d1c81277a76f8332a02b81a3d73463d7f" "checksum cc 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9be26b24e988625409b19736d130f0c7d224f01d06454b5f81d8d23d6c1a618f" +"checksum cesu8 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" "checksum cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "393a5f0088efbe41f9d1fcd062f24e83c278608420e62109feb2c8abee07de7d" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" "checksum cgl 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "55e7ec0b74fe5897894cbc207092c577e87c52f8a59e8ca8d97ef37551f60a49" @@ -3834,6 +3903,7 @@ dependencies = [ "checksum cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "56d741ea7a69e577f6d06b36b7dff4738f680593dc27a701ffa8506b73ce28bb" "checksum cocoa 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b44bd25bd275e9d74a5dff8ca55f2fb66c9ad5e12170d58697701df21a56e0e" "checksum color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a475fc4af42d83d28adf72968d9bcfaf035a1a9381642d8e85d8a04957767b0d" +"checksum combine 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1645a65a99c7c8d345761f4b75a6ffe5be3b3b27a93ee731fccc5050ba6be97c" "checksum cookie 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "746858cae4eae40fff37e1998320068df317bc247dc91a67c6cfa053afdc2abb" "checksum core-foundation 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7caa6cb9e76ddddbea09a03266d6b3bc98cd41e9fb9b017c473e7cca593ec25" "checksum core-foundation-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b2a53cce0ddcf7e7e1f998738d757d5a3bf08bf799a180e50ebe50d298f52f5a" @@ -3913,6 +3983,8 @@ dependencies = [ "checksum itertools 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b07332223953b5051bceb67e8c4700aa65291535568e1f12408c43c4a42c0394" "checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682" "checksum jemalloc-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "479294d130502fada93c7a957e8d059b632b03d6204aca37af557dee947f30a9" +"checksum jni 0.10.2 (git+https://github.com/paulrouget/jni-rs?branch=return_javavm)" = "" +"checksum jni-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" "checksum jpeg-decoder 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "0dfe27a6c0dabd772d0f9b9f8701c4ca12c4d1eebcadf2be1f6f70396f6a1434" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum khronos_api 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9ef23fcc4059260c5936f638c9805ebfc87cb172fa6661d130cba7f97d58f55" diff --git a/Cargo.toml b/Cargo.toml index 4f107203f28..069fd75fc96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,12 @@ [workspace] members = [ "ports/servo", + "ports/libsimpleservo/", "tests/unit/*", ] default-members = [ "ports/servo", + "ports/libsimpleservo/", "tests/unit/*", ] exclude = [".cargo"] diff --git a/components/config/Cargo.toml b/components/config/Cargo.toml index dce1cd07ecc..c3a34160e8c 100644 --- a/components/config/Cargo.toml +++ b/components/config/Cargo.toml @@ -30,6 +30,3 @@ embedder_traits = { path = "../embedder_traits", features = ["tests"] } [target.'cfg(not(target_os = "android"))'.dependencies] dirs = "1.0" - -[target.'cfg(target_os = "android")'.dependencies] -android_injected_glue = "0.2" diff --git a/components/config/basedir.rs b/components/config/basedir.rs index f3dd0ca7f67..c0970ce9770 100644 --- a/components/config/basedir.rs +++ b/components/config/basedir.rs @@ -6,41 +6,33 @@ //! For linux based platforms, it uses the XDG base directory spec but provides //! similar abstractions for non-linux platforms. -#[cfg(target_os = "android")] -use android_injected_glue; -#[cfg(target_os = "android")] -use std::ffi::CStr; use std::path::PathBuf; #[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))] -pub fn default_config_dir() -> PathBuf { +pub fn default_config_dir() -> Option { let mut config_dir = ::dirs::config_dir().unwrap(); config_dir.push("servo"); config_dir.push("default"); - config_dir + Some(config_dir) } #[cfg(target_os = "android")] -#[allow(unsafe_code)] -pub fn default_config_dir() -> PathBuf { - let dir = unsafe { - CStr::from_ptr((*android_injected_glue::get_app().activity).externalDataPath) - }; - PathBuf::from(dir.to_str().unwrap()) +pub fn default_config_dir() -> Option { + None } #[cfg(target_os = "macos")] -pub fn default_config_dir() -> PathBuf { +pub fn default_config_dir() -> Option { // FIXME: use `config_dir()` ($HOME/Library/Preferences) // instead of `data_dir()` ($HOME/Library/Application Support) ? let mut config_dir = ::dirs::data_dir().unwrap(); config_dir.push("Servo"); - config_dir + Some(config_dir) } #[cfg(target_os = "windows")] -pub fn default_config_dir() -> PathBuf { +pub fn default_config_dir() -> Option { let mut config_dir = ::dirs::config_dir().unwrap(); config_dir.push("Servo"); - config_dir + Some(config_dir) } diff --git a/components/config/lib.rs b/components/config/lib.rs index 7c094bd2d26..c5aac6e9f70 100644 --- a/components/config/lib.rs +++ b/components/config/lib.rs @@ -4,8 +4,6 @@ #![deny(unsafe_code)] -#[cfg(target_os = "android")] -extern crate android_injected_glue; #[cfg(not(target_os = "android"))] extern crate dirs; extern crate embedder_traits; diff --git a/components/config/prefs.rs b/components/config/prefs.rs index 69c8277ce8f..e5019ea4fcb 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -183,9 +183,10 @@ pub fn add_user_prefs() { init_user_prefs(&mut path); } None => { - let mut path = default_config_dir(); - if path.join("prefs.json").exists() { - init_user_prefs(&mut path); + if let Some(mut path) = default_config_dir() { + if path.join("prefs.json").exists() { + init_user_prefs(&mut path); + } } } } diff --git a/components/config/tests/prefs.rs b/components/config/tests/prefs.rs index 0b50d51ac8e..68188600ee8 100644 --- a/components/config/tests/prefs.rs +++ b/components/config/tests/prefs.rs @@ -48,6 +48,7 @@ fn test_get_set_reset_extend() { assert_eq!(*PREFS.get("extra.stuff"), PrefValue::Boolean(false)); } +#[cfg(not(target_os = "android"))] #[test] fn test_default_config_dir_create_read_write() { let json_str = "{\ @@ -56,7 +57,7 @@ fn test_default_config_dir_create_read_write() { \"shell.homepage\": \"https://google.com\"\ }"; let mut expected_json = String::new(); - let config_path = basedir::default_config_dir(); + let config_path = basedir::default_config_dir().unwrap(); if !config_path.exists() { fs::create_dir_all(&config_path).unwrap(); diff --git a/ports/libsimpleservo/Cargo.toml b/ports/libsimpleservo/Cargo.toml new file mode 100644 index 00000000000..9a64a4fb245 --- /dev/null +++ b/ports/libsimpleservo/Cargo.toml @@ -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"] diff --git a/ports/libsimpleservo/README.md b/ports/libsimpleservo/README.md new file mode 100644 index 00000000000..76a5df44039 --- /dev/null +++ b/ports/libsimpleservo/README.md @@ -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). diff --git a/ports/libsimpleservo/build.rs b/ports/libsimpleservo/build.rs new file mode 100644 index 00000000000..c684cc36554 --- /dev/null +++ b/ports/libsimpleservo/build.rs @@ -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(); + }; + } +} diff --git a/ports/libsimpleservo/src/api.rs b/ports/libsimpleservo/src/api.rs new file mode 100644 index 00000000000..a271dda59e4 --- /dev/null +++ b/ports/libsimpleservo/src/api.rs @@ -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> = 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; +} + +/// 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, + batch_mode: bool, + callbacks: Rc, + browser_id: BrowserId, + events: Vec, + current_url: Option, +} + +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, + argsline: String, + embedder_url: Option, + waker: Box, + readfile: Box, + callbacks: Box, + width: u32, + height: u32, +) -> Result<(), &'static str> { + resources::set(Box::new(ResourceReader(readfile))); + + let mut args: Vec = 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, + gl: Rc, + host_callbacks: Box, + width: Cell, + height: Cell, +} + +impl WindowMethods for ServoCallbacks { + fn prepare_for_composite( + &self, + _width: Length, + _height: Length, + ) -> 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 { + debug!("WindowMethods::create_event_loop_waker"); + self.waker.clone() + } + + fn gl(&self) -> Rc { + 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); + +impl resources::ResourceReaderMethods for ResourceReader { + fn read(&self, file: Resource) -> Vec { + 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 { + vec![] + } + fn sandbox_access_files(&self) -> Vec { + vec![] + } +} diff --git a/ports/libsimpleservo/src/capi.rs b/ports/libsimpleservo/src/capi.rs new file mode 100644 index 00000000000..cbd4f36108c --- /dev/null +++ b/ports/libsimpleservo/src/capi.rs @@ -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) 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, + 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 { + 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 { + 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); + } +} diff --git a/ports/libsimpleservo/src/gl_glue.rs b/ports/libsimpleservo/src/gl_glue.rs new file mode 100644 index 00000000000..cfb57522819 --- /dev/null +++ b/ports/libsimpleservo/src/gl_glue.rs @@ -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, &'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, &'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, &'static str> { + // FIXME: Add an OpenGL version + unimplemented!() + } +} diff --git a/ports/libsimpleservo/src/jniapi.rs b/ports/libsimpleservo/src/jniapi.rs new file mode 100644 index 00000000000..84649659e81 --- /dev/null +++ b/ports/libsimpleservo/src/jniapi.rs @@ -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(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, +} + +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 { + 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, + 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 { + // 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)); + } +} diff --git a/ports/libsimpleservo/src/lib.rs b/ports/libsimpleservo/src/lib.rs new file mode 100644 index 00000000000..06feb5d5ee0 --- /dev/null +++ b/ports/libsimpleservo/src/lib.rs @@ -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::*; diff --git a/ports/servo/Cargo.toml b/ports/servo/Cargo.toml index 7c1d5a1b8d3..db2ad95e792 100644 --- a/ports/servo/Cargo.toml +++ b/ports/servo/Cargo.toml @@ -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]