diff --git a/.cargo/config b/.cargo/config index 374ecf7ff53..18115efa62c 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,17 +1,17 @@ [target.arm-linux-androideabi] -linker = "./ports/servo/fake-ld-arm.sh" +linker = "./support/android/fakeld/fake-ld-arm.sh" ar = "arm-linux-androideabi-ar" [target.armv7-linux-androideabi] -linker = "./ports/servo/fake-ld-armv7.sh" +linker = "./support/android/fakeld/fake-ld-armv7.sh" ar = "arm-linux-androideabi-ar" [target.aarch64-linux-android] -linker = "./ports/servo/fake-ld-arm64.sh" +linker = "./support/android/fakeld/fake-ld-arm64.sh" ar = "aarch64-linux-android-ar" [target.i686-linux-android] -linker = "./ports/servo/fake-ld-x86.sh" +linker = "./support/android/fakeld/fake-ld-x86.sh" ar = "i686-linux-android-ar" [target.arm-unknown-linux-gnueabihf] @@ -23,4 +23,4 @@ linker = "aarch64-linux-gnu-gcc" ar = "aarch64-linux-gnu-ar" [target.'cfg(target_os=windows)'] -linker = "./ports/servo/fake-ld.cmd" \ No newline at end of file +linker = "./support/android/fakeld/fake-ld.cmd" diff --git a/.travis.yml b/.travis.yml index 3c1e3649fce..668265395f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ matrix: script: # see https://github.com/servo/servo/issues/20664 #- ./mach cargo check -p compositing --no-default-features - - ./mach build -d --verbose + - ./mach build -d --verbose -p servo - ./mach test-unit - ./mach clean - bash etc/ci/lockfile_changed.sh diff --git a/Cargo.lock b/Cargo.lock index 54022a2bd90..3884f680c33 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" @@ -61,6 +76,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" @@ -353,6 +373,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" @@ -454,6 +479,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" @@ -1617,6 +1651,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" @@ -1844,6 +1896,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" @@ -3075,7 +3143,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)", @@ -3087,7 +3154,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)", @@ -3242,7 +3308,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)", @@ -4172,11 +4237,14 @@ 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 array-init 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c3cc8456d0ae81a8c76f59e384683a601548c38949a4bfcb65dd31ded5c75ff3" "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" @@ -4205,6 +4273,7 @@ dependencies = [ "checksum bzip2-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2c5162604199bbb17690ede847eaa6120a3f33d5ab4dcc8e7c25b16d849ae79b" "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" @@ -4216,6 +4285,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" @@ -4312,6 +4382,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/etc/ci/buildbot_steps.yml b/etc/ci/buildbot_steps.yml index dff02df1e77..8fb1cc9f030 100644 --- a/etc/ci/buildbot_steps.yml +++ b/etc/ci/buildbot_steps.yml @@ -76,14 +76,14 @@ linux-rel-intermittent: - ./mach clean-nightlies --keep 3 --force - ./mach clean-cargo-cache --keep 3 --force - ./etc/ci/clean_build_artifacts.sh - - ./mach build --release + - ./mach build --release -p servo - ./etc/ci/check_intermittents.sh --log-raw intermittents.log linux-rel-nogate: - ./mach clean-nightlies --keep 3 --force - ./mach clean-cargo-cache --keep 3 --force - ./etc/ci/clean_build_artifacts.sh - - ./mach build --release + - ./mach build --release -p servo - python ./etc/ci/chaos_monkey_test.py - env RUSTFLAGS= bash ./etc/ci/mutation_test.sh @@ -103,11 +103,11 @@ linux-dev: - ./etc/ci/clean_build_artifacts.sh - ./mach test-tidy --no-progress --all - ./mach test-tidy --no-progress --self-test - - ./mach build --dev + - ./mach build --dev -p servo - ./mach test-unit - python ./etc/memory_reports_over_time.py --test - ./mach package --dev - - ./mach build --dev --no-default-features --features default-except-unstable + - ./mach build --dev --no-default-features --features default-except-unstable -p servo - bash ./etc/ci/lockfile_changed.sh - bash ./etc/ci/check_no_panic.sh - ./etc/ci/clean_build_artifacts.sh @@ -120,7 +120,7 @@ linux-rel-wpt: - ./mach clean-nightlies --keep 3 --force - ./mach clean-cargo-cache --keep 3 --force - ./etc/ci/clean_build_artifacts.sh - - ./mach build --release --with-debug-assertions + - ./mach build --release --with-debug-assertions -p servo - ./mach test-wpt-failure - ./mach test-wpt --release --processes 24 --total-chunks 2 --this-chunk 1 --log-raw test-wpt.log --log-errorsummary wpt-errorsummary.log --always-succeed - ./mach filter-intermittents wpt-errorsummary.log --log-intermittents intermittents.log --log-filteredsummary filtered-wpt-errorsummary.log --tracker-api default --reporter-api default @@ -134,7 +134,7 @@ linux-rel-css: - ./mach clean-nightlies --keep 3 --force - ./mach clean-cargo-cache --keep 3 --force - ./etc/ci/clean_build_artifacts.sh - - ./mach build --release --with-debug-assertions + - ./mach build --release --with-debug-assertions -p servo - ./mach test-wpt --release --processes 24 --total-chunks 2 --this-chunk 2 --log-raw test-wpt.log --log-errorsummary wpt-errorsummary.log --always-succeed - ./mach filter-intermittents wpt-errorsummary.log --log-intermittents intermittents.log --log-filteredsummary filtered-wpt-errorsummary.log --tracker-api default --reporter-api default - bash ./etc/ci/lockfile_changed.sh @@ -144,7 +144,7 @@ linux-nightly: - ./mach clean-nightlies --keep 3 --force - ./mach clean-cargo-cache --keep 3 --force - ./etc/ci/clean_build_artifacts.sh - - ./mach build --release + - ./mach build --release -p servo - ./mach package --release - ./mach upload-nightly linux - ./mach test-perf diff --git a/etc/ci/check_dynamic_symbols.py b/etc/ci/check_dynamic_symbols.py index ab7839390b0..5f13e7d347a 100644 --- a/etc/ci/check_dynamic_symbols.py +++ b/etc/ci/check_dynamic_symbols.py @@ -23,7 +23,7 @@ objdump_output = subprocess.check_output([ 'android-toolchains', 'ndk', 'toolchains', 'arm-linux-androideabi-4.9', 'prebuilt', 'linux-x86_64', 'bin', 'arm-linux-androideabi-objdump'), '-T', - 'target/armv7-linux-androideabi/debug/libservo.so'] + 'target/armv7-linux-androideabi/debug/libsimpleservo.so'] ).split(b'\n') for line in objdump_output: diff --git a/etc/run_in_headless_android_emulator.py b/etc/run_in_headless_android_emulator.py index af8034b204d..1d2959e4f06 100755 --- a/etc/run_in_headless_android_emulator.py +++ b/etc/run_in_headless_android_emulator.py @@ -10,6 +10,7 @@ # except according to those terms. import contextlib +import json import os import signal import subprocess @@ -55,16 +56,17 @@ def main(avd_name, apk_path, *args): args = list(args) write_user_stylesheets(adb, args) write_hosts_file(adb) - write_args(adb, args) - check_call(adb + ["shell", "am start com.mozilla.servo/com.mozilla.servo.MainActivity"], - stdout=sys.stderr) + json_params = shell_quote(json.dumps(args)) + extra = "-e servoargs " + json_params + cmd = "am start " + extra + " com.mozilla.servo/com.mozilla.servo.MainActivity" + check_call(adb + ["shell", cmd], stdout=sys.stderr) # Start showing logs as soon as the application starts, # in case they say something useful while we wait in subsequent steps. logcat_args = [ "--format=raw", # Print no metadata, only log messages - "RustAndroidGlueStdouterr:D", # Show (debug level) Rust stdio + "simpleservo:D", # Show (debug level) Rust stdio "*:S", # Hide everything else ] with terminate_on_exit(adb + ["logcat"] + logcat_args) as logcat: @@ -125,16 +127,6 @@ def check_call(*args, **kwargs): sys.exit(exit_code) -def write_args(adb, args): - data_dir = "/sdcard/Android/data/com.mozilla.servo/files" - params_file = data_dir + "/android_params" - - check_call(adb + ["shell", "mkdir -p %s" % data_dir]) - check_call(adb + ["shell", "echo 'servo' > %s" % params_file]) - for arg in args: - check_call(adb + ["shell", "echo %s >> %s" % (shell_quote(arg), params_file)]) - - def write_user_stylesheets(adb, args): data_dir = "/sdcard/Android/data/com.mozilla.servo/files" check_call(adb + ["shell", "mkdir -p %s" % data_dir]) 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 aa77bcfe6aa..db2ad95e792 100644 --- a/ports/servo/Cargo.toml +++ b/ports/servo/Cargo.toml @@ -29,11 +29,9 @@ 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"] -[dependencies] +[target.'cfg(not(target_os = "android"))'.dependencies] backtrace = "0.3" bitflags = "1.0" euclid = "0.18" @@ -44,8 +42,6 @@ libservo = {path = "../../components/servo"} log = "0.4" tinyfiledialogs = "3.0" winit = {version = "0.16", features = ["icon_loading"]} - -[target.'cfg(not(target_os = "android"))'.dependencies] sig = "0.1" [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] @@ -54,10 +50,6 @@ osmesa-sys = "0.1.2" [target.'cfg(target_os = "linux")'.dependencies] x11 = "2.0.0" -[target.'cfg(target_os = "android")'.dependencies] -android_injected_glue = "0.2" -servo-egl = "0.2" - [target.'cfg(target_os = "windows")'.dependencies] winapi = "0.2" user32-sys = "0.2" diff --git a/ports/servo/browser.rs b/ports/servo/browser.rs index f2a8d5709ac..277d98ba537 100644 --- a/ports/servo/browser.rs +++ b/ports/servo/browser.rs @@ -17,9 +17,7 @@ use servo::servo_url::ServoUrl; use servo::webrender_api::ScrollLocation; use std::mem; use std::rc::Rc; -#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] use std::thread; -#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] use tinyfiledialogs::{self, MessageBoxIcon}; pub struct Browser { @@ -100,7 +98,8 @@ impl Browser { String::from("") }; let title = "URL or search query"; - if let Some(input) = get_url_input(title, &url) { + let input = tinyfiledialogs::input_box(title, title, &url); + if let Some(input) = input { if let Some(url) = sanitize_url(&input) { self.event_queue.push(WindowEvent::LoadUrl(id, url)); } @@ -263,7 +262,11 @@ impl Browser { self.window.set_inner_size(size); } EmbedderMsg::Alert(message, sender) => { - display_alert_dialog(message.to_owned()); + if !opts::get().headless { + let _ = thread::Builder::new().name("display alert dialog".to_owned()).spawn(move || { + tinyfiledialogs::message_box_ok("Alert!", &message, MessageBoxIcon::Warning); + }).unwrap().join().expect("Thread spawning failed"); + } if let Err(e) = sender.send(()) { let reason = format!("Failed to send Alert response: {}", e); self.event_queue.push(WindowEvent::SendError(browser_id, reason)); @@ -324,8 +327,7 @@ impl Browser { }; }, EmbedderMsg::SelectFiles(patterns, multiple_files, sender) => { - let res = match (opts::get().headless, - platform_get_selected_files(patterns, multiple_files)) { + let res = match (opts::get().headless, get_selected_files(patterns, multiple_files)) { (true, _) | (false, None) => sender.send(None), (false, Some(files)) => sender.send(Some(files)) }; @@ -346,31 +348,6 @@ impl Browser { } -#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] -fn display_alert_dialog(message: String) { - if !opts::get().headless { - let _ = thread::Builder::new().name("display alert dialog".to_owned()).spawn(move || { - tinyfiledialogs::message_box_ok("Alert!", &message, MessageBoxIcon::Warning); - }).unwrap().join().expect("Thread spawning failed"); - } -} - -#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] -fn display_alert_dialog(_message: String) { - // tinyfiledialogs not supported on Android -} - -#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] -fn get_url_input(_title: &str, _url: &str) -> Option { - // tinyfiledialogs not supported on Android - None -} - -#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] -fn get_url_input(title: &str, url: &str) -> Option { - tinyfiledialogs::input_box(title, title, url) -} - #[cfg(target_os = "linux")] fn platform_get_selected_devices(devices: Vec) -> Option { let picker_name = "Choose a device"; @@ -403,10 +380,7 @@ fn platform_get_selected_devices(devices: Vec) -> Option { None } -#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] -fn platform_get_selected_files(patterns: Vec, - multiple_files: bool) - -> Option> { +fn get_selected_files(patterns: Vec, multiple_files: bool) -> Option> { let picker_name = if multiple_files { "Pick files" } else { "Pick a file" }; thread::Builder::new().name(picker_name.to_owned()).spawn(move || { let mut filters = vec![]; @@ -426,14 +400,6 @@ fn platform_get_selected_files(patterns: Vec, }).unwrap().join().expect("Thread spawning failed") } -#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] -fn platform_get_selected_files(_patterns: Vec, - _multiple_files: bool) - -> Option> { - warn!("File picker not implemented"); - None -} - fn sanitize_url(request: &str) -> Option { let request = request.trim(); ServoUrl::parse(&request).ok() diff --git a/ports/servo/build.rs b/ports/servo/build.rs index 378f5e8b4c5..f7e7f49c919 100644 --- a/ports/servo/build.rs +++ b/ports/servo/build.rs @@ -5,18 +5,7 @@ #[cfg(windows)] extern crate winres; -use std::env; -use std::path::Path; -use std::process; -use std::process::{Command, Stdio}; - fn main() { - // build.rs is not platform-specific, so we have to check the target here. - let target = env::var("TARGET").unwrap(); - if target.contains("android") { - android_main() - } - #[cfg(windows)] { let mut res = winres::WindowsResource::new(); @@ -25,89 +14,3 @@ fn main() { res.compile().unwrap(); } } - -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); - - // Build up the path to the NDK compilers - // Options for host are: "linux-x86_64" "linux-x86" "darwin-x86_64" "darwin-x86" - // per: https://android.googlesource.com/platform/ndk/+/ics-mr0/docs/STANDALONE-TOOLCHAIN.html - - let host = env::var("HOST").unwrap(); - let google_host = match host.as_ref() { - "i686-unknown-linux-gnu" => "linux-x86", - "x86_64-apple-darwin" => "darwin-x86_64", - "x86_64-unknown-linux-gnu" => "linux-x86_64", - _ => panic!("Unknown support android cross-compile host: {}", host) - }; - - 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" - }; - - let (toolchain, prefix) = if target.contains("armv7") { - let toolchain = "arm-linux-androideabi"; - (toolchain.into(), toolchain.into()) - } else if target.contains("i686") { - ("x86".into(), target) - } else { - (target.clone(), target) - }; - - let toolchain_path = ndk_path.join("toolchains").join(format!("{}-4.9", toolchain)).join("prebuilt"). - join(google_host); - println!("toolchain path is: {}", toolchain_path.to_str().unwrap()); - - // Get the output directory. - let out_dir = env::var("OUT_DIR").expect("Cargo should have set the OUT_DIR environment variable"); - let directory = Path::new(&out_dir); - - // compiling android_native_app_glue.c - if Command::new(toolchain_path.join("bin").join(format!("{}-gcc", prefix))) - .arg(ndk_path.join("sources").join("android").join("native_app_glue").join("android_native_app_glue.c")) - .arg("-c") - .arg("-o").arg(directory.join("android_native_app_glue.o")) - .arg("--sysroot").arg(ndk_path.join("platforms").join(platform).join(arch)) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .status().unwrap().code().unwrap() != 0 - { - println!("Error while executing gcc"); - process::exit(1) - } - - // compiling libandroid_native_app_glue.a - if Command::new(toolchain_path.join("bin").join(format!("{}-ar", prefix))) - .arg("rcs") - .arg(directory.join("libandroid_native_app_glue.a")) - .arg(directory.join("android_native_app_glue.o")) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .status().unwrap().code().unwrap() != 0 - { - println!("Error while executing ar"); - process::exit(1) - } - - 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"); -} diff --git a/ports/servo/glutin_app/window.rs b/ports/servo/glutin_app/window.rs index e1a50f8cf55..0837b689c41 100644 --- a/ports/servo/glutin_app/window.rs +++ b/ports/servo/glutin_app/window.rs @@ -401,12 +401,12 @@ impl Window { } } - #[cfg(not(any(target_arch = "arm", target_arch = "aarch64", target_os = "android")))] + #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] fn gl_version() -> GlRequest { return GlRequest::Specific(Api::OpenGl, (3, 2)); } - #[cfg(any(target_arch = "arm", target_arch = "aarch64", target_os = "android"))] + #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] fn gl_version() -> GlRequest { GlRequest::Specific(Api::OpenGlEs, (3, 0)) } @@ -634,7 +634,6 @@ impl Window { } } - /// Has no effect on Android. pub fn set_cursor(&self, cursor: CursorKind) { match self.kind { WindowKind::Window(ref window, ..) => { diff --git a/ports/servo/main.rs b/ports/servo/main.rs index 5060f87ed48..5d9174d9e73 100644 --- a/ports/servo/main.rs +++ b/ports/servo/main.rs @@ -17,316 +17,10 @@ #![cfg_attr(feature = "unstable", feature(core_intrinsics))] -#[cfg(target_os = "android")] -extern crate android_injected_glue; -extern crate backtrace; -#[macro_use] extern crate bitflags; -extern crate euclid; -#[cfg(target_os = "windows")] extern crate gdi32; -extern crate gleam; -extern crate glutin; -#[cfg(not(target_os = "android"))] -#[macro_use] extern crate lazy_static; -// The window backed by glutin -#[macro_use] extern crate log; -#[cfg(any(target_os = "linux", target_os = "macos"))] extern crate osmesa_sys; -extern crate servo; -#[cfg(all(feature = "unstable", not(target_os = "android")))] -#[macro_use] -extern crate sig; -#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] -extern crate tinyfiledialogs; -extern crate winit; -#[cfg(target_os = "windows")] extern crate winapi; -#[cfg(target_os = "windows")] extern crate user32; - -mod glutin_app; -mod resources; - -use backtrace::Backtrace; -use servo::Servo; -use servo::compositing::windowing::WindowEvent; -use servo::config; -use servo::config::opts::{self, ArgumentParsingResult, parse_url_or_filename}; -use servo::config::servo_version; -use servo::ipc_channel::ipc; -use servo::servo_config::prefs::PREFS; -use servo::servo_url::ServoUrl; -use std::env; -use std::panic; -use std::process; -use std::thread; - -mod browser; - -pub mod platform { - #[cfg(target_os = "macos")] - pub use platform::macos::deinit; - - #[cfg(target_os = "macos")] - pub mod macos; - - #[cfg(not(target_os = "macos"))] - pub fn deinit() {} -} - -#[cfg(all(feature = "unstable", not(target_os = "android")))] -fn install_crash_handler() { - use backtrace::Backtrace; - use sig::ffi::Sig; - use std::intrinsics::abort; - use std::thread; - - fn handler(_sig: i32) { - let name = thread::current() - .name() - .map(|n| format!(" for thread \"{}\"", n)) - .unwrap_or("".to_owned()); - println!("Stack trace{}\n{:?}", name, Backtrace::new()); - unsafe { - // N.B. Using process::abort() here causes the crash handler to be - // triggered recursively. - abort(); - } - } - - signal!(Sig::SEGV, handler); // handle segfaults - signal!(Sig::ILL, handler); // handle stack overflow and unsupported CPUs - signal!(Sig::IOT, handler); // handle double panics - signal!(Sig::BUS, handler); // handle invalid memory access -} - -#[cfg(any(not(feature = "unstable"), target_os = "android"))] -fn install_crash_handler() {} - -fn main() { - install_crash_handler(); - - resources::init(); - - if cfg!(target_os = "android") && env::var_os("HOST_FILE").is_none() { - let mut path = config::basedir::default_config_dir(); - path.push("android_hosts"); - env::set_var("HOST_FILE", path); - } - - // Parse the command line options and store them globally - let opts_result = opts::from_cmdline_args(&*args()); - - let content_process_token = if let ArgumentParsingResult::ContentProcess(token) = opts_result { - Some(token) - } else { - if opts::get().is_running_problem_test && env::var("RUST_LOG").is_err() { - env::set_var("RUST_LOG", "compositing::constellation"); - } - - None - }; - - // TODO: once log-panics is released, can this be replaced by - // log_panics::init()? - panic::set_hook(Box::new(|info| { - warn!("Panic hook called."); - let msg = match info.payload().downcast_ref::<&'static str>() { - Some(s) => *s, - None => { - match info.payload().downcast_ref::() { - Some(s) => &**s, - None => "Box", - } - }, - }; - let current_thread = thread::current(); - let name = current_thread.name().unwrap_or(""); - if let Some(location) = info.location() { - println!("{} (thread {}, at {}:{})", - msg, - name, - location.file(), - location.line()); - } else { - println!("{} (thread {})", msg, name); - } - if env::var("RUST_BACKTRACE").is_ok() { - println!("{:?}", Backtrace::new()); - } - - error!("{}", msg); - })); - - setup_logging(); - - if let Some(token) = content_process_token { - return servo::run_content_process(token); - } - - if opts::get().is_printing_version { - println!("{}", servo_version()); - process::exit(0); - } - - let window = glutin_app::create_window(); - - let mut browser = browser::Browser::new(window.clone()); - - // If the url is not provided, we fallback to the homepage in PREFS, - // or a blank page in case the homepage is not set either. - let cwd = env::current_dir().unwrap(); - let cmdline_url = opts::get().url.clone(); - let pref_url = PREFS.get("shell.homepage").as_string() - .and_then(|str| parse_url_or_filename(&cwd, str).ok()); - let blank_url = ServoUrl::parse("about:blank").ok(); - - let target_url = cmdline_url.or(pref_url).or(blank_url).unwrap(); - - let mut servo = Servo::new(window.clone()); - - let (sender, receiver) = ipc::channel().unwrap(); - servo.handle_events(vec![WindowEvent::NewBrowser(target_url, sender)]); - let browser_id = receiver.recv().unwrap(); - browser.set_browser_id(browser_id); - servo.handle_events(vec![WindowEvent::SelectBrowser(browser_id)]); - - servo.setup_logging(); - - window.run(|| { - let win_events = window.get_events(); - - // FIXME: this could be handled by Servo. We don't need - // a repaint_synchronously function exposed. - let need_resize = win_events.iter().any(|e| match *e { - WindowEvent::Resize => true, - _ => false, - }); - - browser.handle_window_events(win_events); - - let mut servo_events = servo.get_events(); - loop { - browser.handle_servo_events(servo_events); - servo.handle_events(browser.get_events()); - if browser.shutdown_requested() { - return true; - } - servo_events = servo.get_events(); - if servo_events.is_empty() { - break; - } - } - - if need_resize { - servo.repaint_synchronously(); - } - false - }); - - servo.deinit(); - - platform::deinit() -} +#[cfg(not(target_os = "android"))] include!("non_android_main.rs"); #[cfg(target_os = "android")] -fn setup_logging() { - // Piping logs from stdout/stderr to logcat happens in android_injected_glue. - env::set_var("RUST_LOG", "error"); - - unsafe { android_injected_glue::ffi::app_dummy() }; -} - -#[cfg(not(target_os = "android"))] -fn setup_logging() {} - -#[cfg(target_os = "android")] -/// Attempt to read parameters from a file since they are not passed to us in Android environments. -/// The first line should be the "servo" argument and the last should be the URL to load. -/// Blank lines and those beginning with a '#' are ignored. -/// Each line should be a separate parameter as would be parsed by the shell. -/// For example, "servo -p 10 http://en.wikipedia.org/wiki/Rust" would take 4 lines. -fn args() -> Vec { - use std::error::Error; - use std::fs::File; - use std::io::{BufRead, BufReader}; - - let mut params_file = config::basedir::default_config_dir(); - params_file.push("android_params"); - match File::open(params_file.to_str().unwrap()) { - Ok(f) => { - let mut vec = Vec::new(); - let file = BufReader::new(&f); - for line in file.lines() { - let l = line.unwrap().trim().to_owned(); - // ignore blank lines and those that start with a '#' - match l.is_empty() || l.as_bytes()[0] == b'#' { - true => (), - false => vec.push(l), - } - } - vec - }, - Err(e) => { - debug!("Failed to open params file '{}': {}", - params_file.to_str().unwrap(), - Error::description(&e)); - vec!["servo".to_owned(), "http://en.wikipedia.org/wiki/Rust".to_owned()] - }, - } -} - -#[cfg(not(target_os = "android"))] -fn args() -> Vec { - env::args().collect() -} - - -#[cfg(target_os = "android")] -#[no_mangle] -#[inline(never)] -#[allow(non_snake_case)] -pub extern "C" fn android_main(app: *mut ()) { - android_injected_glue::android_main2(app as *mut _, move |_, _| main()); -} - -// These functions aren't actually called. They are here as a link -// hack because Skia references them. - -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn glBindVertexArrayOES(_array: usize) -{ - unimplemented!() -} - -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn glDeleteVertexArraysOES(_n: isize, _arrays: *const ()) -{ - unimplemented!() -} - -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn glGenVertexArraysOES(_n: isize, _arrays: *const ()) -{ - unimplemented!() -} - -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn glRenderbufferStorageMultisampleIMG(_: isize, _: isize, _: isize, _: isize, _: isize) -{ - unimplemented!() -} - -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn glFramebufferTexture2DMultisampleIMG(_: isize, _: isize, _: isize, _: isize, _: isize, _: isize) -{ - unimplemented!() -} - -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn glDiscardFramebufferEXT(_: isize, _: isize, _: *const ()) -{ - unimplemented!() +pub fn main() { + println!("Cannot start /ports/servo/ on Android. \ + Use /support/android/apk/ + /ports/libsimpleservo/ instead"); } diff --git a/ports/servo/non_android_main.rs b/ports/servo/non_android_main.rs new file mode 100644 index 00000000000..b4a50fc8112 --- /dev/null +++ b/ports/servo/non_android_main.rs @@ -0,0 +1,243 @@ +/* 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 backtrace; +#[macro_use] extern crate bitflags; +extern crate euclid; +#[cfg(target_os = "windows")] extern crate gdi32; +extern crate gleam; +extern crate glutin; +#[macro_use] extern crate lazy_static; +// The window backed by glutin +#[macro_use] extern crate log; +#[cfg(any(target_os = "linux", target_os = "macos"))] extern crate osmesa_sys; +extern crate servo; +#[cfg(feature = "unstable")] +#[macro_use] +extern crate sig; +#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] +extern crate tinyfiledialogs; +extern crate winit; +#[cfg(target_os = "windows")] extern crate winapi; +#[cfg(target_os = "windows")] extern crate user32; + +mod glutin_app; +mod resources; + +use backtrace::Backtrace; +use servo::Servo; +use servo::compositing::windowing::WindowEvent; +use servo::config::opts::{self, ArgumentParsingResult, parse_url_or_filename}; +use servo::config::servo_version; +use servo::ipc_channel::ipc; +use servo::servo_config::prefs::PREFS; +use servo::servo_url::ServoUrl; +use std::env; +use std::panic; +use std::process; +use std::thread; + +mod browser; + +pub mod platform { + #[cfg(target_os = "macos")] + pub use platform::macos::deinit; + + #[cfg(target_os = "macos")] + pub mod macos; + + #[cfg(not(target_os = "macos"))] + pub fn deinit() {} +} + +#[cfg(feature = "unstable")] +fn install_crash_handler() { + use backtrace::Backtrace; + use sig::ffi::Sig; + use std::intrinsics::abort; + use std::thread; + + fn handler(_sig: i32) { + let name = thread::current() + .name() + .map(|n| format!(" for thread \"{}\"", n)) + .unwrap_or("".to_owned()); + println!("Stack trace{}\n{:?}", name, Backtrace::new()); + unsafe { + // N.B. Using process::abort() here causes the crash handler to be + // triggered recursively. + abort(); + } + } + + signal!(Sig::SEGV, handler); // handle segfaults + signal!(Sig::ILL, handler); // handle stack overflow and unsupported CPUs + signal!(Sig::IOT, handler); // handle double panics + signal!(Sig::BUS, handler); // handle invalid memory access +} + +pub fn main() { + install_crash_handler(); + + resources::init(); + + // Parse the command line options and store them globally + let args: Vec = env::args().collect(); + let opts_result = opts::from_cmdline_args(&args); + + let content_process_token = if let ArgumentParsingResult::ContentProcess(token) = opts_result { + Some(token) + } else { + if opts::get().is_running_problem_test && env::var("RUST_LOG").is_err() { + env::set_var("RUST_LOG", "compositing::constellation"); + } + + None + }; + + // TODO: once log-panics is released, can this be replaced by + // log_panics::init()? + panic::set_hook(Box::new(|info| { + warn!("Panic hook called."); + let msg = match info.payload().downcast_ref::<&'static str>() { + Some(s) => *s, + None => { + match info.payload().downcast_ref::() { + Some(s) => &**s, + None => "Box", + } + }, + }; + let current_thread = thread::current(); + let name = current_thread.name().unwrap_or(""); + if let Some(location) = info.location() { + println!("{} (thread {}, at {}:{})", + msg, + name, + location.file(), + location.line()); + } else { + println!("{} (thread {})", msg, name); + } + if env::var("RUST_BACKTRACE").is_ok() { + println!("{:?}", Backtrace::new()); + } + + error!("{}", msg); + })); + + if let Some(token) = content_process_token { + return servo::run_content_process(token); + } + + if opts::get().is_printing_version { + println!("{}", servo_version()); + process::exit(0); + } + + let window = glutin_app::create_window(); + + let mut browser = browser::Browser::new(window.clone()); + + // If the url is not provided, we fallback to the homepage in PREFS, + // or a blank page in case the homepage is not set either. + let cwd = env::current_dir().unwrap(); + let cmdline_url = opts::get().url.clone(); + let pref_url = PREFS.get("shell.homepage").as_string() + .and_then(|str| parse_url_or_filename(&cwd, str).ok()); + let blank_url = ServoUrl::parse("about:blank").ok(); + + let target_url = cmdline_url.or(pref_url).or(blank_url).unwrap(); + + let mut servo = Servo::new(window.clone()); + + let (sender, receiver) = ipc::channel().unwrap(); + servo.handle_events(vec![WindowEvent::NewBrowser(target_url, sender)]); + let browser_id = receiver.recv().unwrap(); + browser.set_browser_id(browser_id); + servo.handle_events(vec![WindowEvent::SelectBrowser(browser_id)]); + + servo.setup_logging(); + + window.run(|| { + let win_events = window.get_events(); + + // FIXME: this could be handled by Servo. We don't need + // a repaint_synchronously function exposed. + let need_resize = win_events.iter().any(|e| match *e { + WindowEvent::Resize => true, + _ => false, + }); + + browser.handle_window_events(win_events); + + let mut servo_events = servo.get_events(); + loop { + browser.handle_servo_events(servo_events); + servo.handle_events(browser.get_events()); + if browser.shutdown_requested() { + return true; + } + servo_events = servo.get_events(); + if servo_events.is_empty() { + break; + } + } + + if need_resize { + servo.repaint_synchronously(); + } + false + }); + + servo.deinit(); + + platform::deinit() +} + +// These functions aren't actually called. They are here as a link +// hack because Skia references them. + +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn glBindVertexArrayOES(_array: usize) +{ + unimplemented!() +} + +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn glDeleteVertexArraysOES(_n: isize, _arrays: *const ()) +{ + unimplemented!() +} + +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn glGenVertexArraysOES(_n: isize, _arrays: *const ()) +{ + unimplemented!() +} + +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn glRenderbufferStorageMultisampleIMG(_: isize, _: isize, _: isize, _: isize, _: isize) +{ + unimplemented!() +} + +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn glFramebufferTexture2DMultisampleIMG(_: isize, _: isize, _: isize, _: isize, _: isize, _: isize) +{ + unimplemented!() +} + +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn glDiscardFramebufferEXT(_: isize, _: isize, _: *const ()) +{ + unimplemented!() +} + diff --git a/ports/servo/resources.rs b/ports/servo/resources.rs index 2738a8a0eb4..4417881dfcf 100644 --- a/ports/servo/resources.rs +++ b/ports/servo/resources.rs @@ -3,6 +3,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use servo::embedder_traits::resources::{self, Resource}; +use std::env; +use std::fs; +use std::io; +use std::path::PathBuf; +use std::sync::Mutex; + +lazy_static! { + static ref CMD_RESOURCE_DIR: Mutex> = Mutex::new(None); +} struct ResourceReader; @@ -27,86 +36,50 @@ pub fn init() { resources::set(Box::new(ResourceReader)); } -#[cfg(not(target_os = "android"))] -mod not_android { - use servo::embedder_traits::resources::{self, Resource}; - use std::env; - use std::fs; - use std::io; - use std::path::PathBuf; - use std::sync::Mutex; - - lazy_static! { - static ref CMD_RESOURCE_DIR: Mutex> = Mutex::new(None); +fn resources_dir_path() -> io::Result { + // This needs to be called before the process is sandboxed + // as we only give permission to read inside the resources directory, + // not the permissions the "search" for the resources directory. + let mut dir = CMD_RESOURCE_DIR.lock().unwrap(); + if let Some(ref path) = *dir { + return Ok(PathBuf::from(path)); } - impl resources::ResourceReaderMethods for super::ResourceReader { - fn read(&self, file: Resource) -> Vec { - let file = super::filename(file); - let mut path = resources_dir_path().expect("Can't find resources directory"); - path.push(file); - fs::read(path).expect("Can't read file") + // FIXME: Find a way to not rely on the executable being + // under `[/$target_triple]/target/debug` + // or `[/$target_triple]/target/release`. + let mut path = env::current_exe()?; + // Follow symlink + path = path.canonicalize()?; + + while path.pop() { + path.push("resources"); + if path.is_dir() { + break; } - fn sandbox_access_files_dirs(&self) -> Vec { - vec![resources_dir_path().expect("Can't find resources directory")] - } - fn sandbox_access_files(&self) -> Vec { - vec![] + path.pop(); + // Check for Resources on mac when using a case sensitive filesystem. + path.push("Resources"); + if path.is_dir() { + break; } + path.pop(); } - - fn resources_dir_path() -> io::Result { - // This needs to be called before the process is sandboxed - // as we only give permission to read inside the resources directory, - // not the permissions the "search" for the resources directory. - let mut dir = CMD_RESOURCE_DIR.lock().unwrap(); - if let Some(ref path) = *dir { - return Ok(PathBuf::from(path)); - } - - // FIXME: Find a way to not rely on the executable being - // under `[/$target_triple]/target/debug` - // or `[/$target_triple]/target/release`. - let mut path = env::current_exe()?; - // Follow symlink - path = path.canonicalize()?; - - while path.pop() { - path.push("resources"); - if path.is_dir() { - break; - } - path.pop(); - // Check for Resources on mac when using a case sensitive filesystem. - path.push("Resources"); - if path.is_dir() { - break; - } - path.pop(); - } - *dir = Some(path.to_str().unwrap().to_owned()); - Ok(path) - } + *dir = Some(path.to_str().unwrap().to_owned()); + Ok(path) } -#[cfg(target_os = "android")] -mod android { - use android_injected_glue::load_asset; - use servo::embedder_traits::resources::{self, Resource}; - use std::path::PathBuf; - - impl resources::ResourceReaderMethods for super::ResourceReader { - fn read(&self, file: Resource) -> Vec { - let file = super::filename(file); - load_asset(file).unwrap_or_else(|_| { - panic!("Can't load asset"); - }) - } - fn sandbox_access_files_dirs(&self) -> Vec { - vec![] - } - fn sandbox_access_files(&self) -> Vec { - vec![] - } +impl resources::ResourceReaderMethods for ResourceReader { + fn read(&self, file: Resource) -> Vec { + let file = filename(file); + let mut path = resources_dir_path().expect("Can't find resources directory"); + path.push(file); + fs::read(path).expect("Can't read file") + } + fn sandbox_access_files_dirs(&self) -> Vec { + vec![resources_dir_path().expect("Can't find resources directory")] + } + fn sandbox_access_files(&self) -> Vec { + vec![] } } diff --git a/python/servo/bootstrap_commands.py b/python/servo/bootstrap_commands.py index 092d6653d2d..481a6f174e1 100644 --- a/python/servo/bootstrap_commands.py +++ b/python/servo/bootstrap_commands.py @@ -63,7 +63,7 @@ class MachCommands(CommandBase): ndk = "android-ndk-r12b-{system}-{arch}" tools = "sdk-tools-{system}-4333796" - sdk_build_tools = "25.0.2" + sdk_build_tools = "27.0.3" emulator_images = [ ("servo-arm", "25", "google_apis;armeabi-v7a"), ("servo-x86", "28", "google_apis;x86"), diff --git a/python/servo/build_commands.py b/python/servo/build_commands.py index 13d31c7ac8a..fb2a61681ac 100644 --- a/python/servo/build_commands.py +++ b/python/servo/build_commands.py @@ -185,7 +185,6 @@ class MachCommands(CommandBase): debug_mozjs=False, params=None, with_debug_assertions=False): opts = params or [] - opts += ["--manifest-path", self.servo_manifest()] if android is None: android = self.config["build"]["android"] @@ -420,4 +419,4 @@ class MachCommands(CommandBase): opts += ["-v"] opts += params return check_call(["cargo", "clean"] + opts, - env=self.build_env(), cwd=self.servo_crate(), verbose=verbose) + env=self.build_env(), cwd=self.ports_servo_crate(), verbose=verbose) diff --git a/python/servo/command_base.py b/python/servo/command_base.py index 0963242226b..13049409752 100644 --- a/python/servo/command_base.py +++ b/python/servo/command_base.py @@ -330,15 +330,24 @@ class CommandBase(object): else: return path.join(self.context.topdir, "target") + def get_apk_path(self, release): + base_path = self.get_target_dir() + base_path = path.join(base_path, self.config["android"]["target"]) + apk_name = "servoapp.apk" + build_type = "release" if release else "debug" + return path.join(base_path, build_type, apk_name) + def get_binary_path(self, release, dev, android=False): # TODO(autrilla): this function could still use work - it shouldn't # handle quitting, or printing. It should return the path, or an error. base_path = self.get_target_dir() + binary_name = "servo" + BIN_SUFFIX + if android: base_path = path.join(base_path, self.config["android"]["target"]) + binary_name = "libsimpleservo.so" - binary_name = "servo" + BIN_SUFFIX release_path = path.join(base_path, "release", binary_name) dev_path = path.join(base_path, "debug", binary_name) @@ -594,10 +603,10 @@ class CommandBase(object): return env - def servo_crate(self): + def ports_servo_crate(self): return path.join(self.context.topdir, "ports", "servo") - def servo_manifest(self): + def ports_servo_manifest(self): return path.join(self.context.topdir, "ports", "servo", "Cargo.toml") def servo_features(self): diff --git a/python/servo/package_commands.py b/python/servo/package_commands.py index 03c2390e308..57fd986fe1a 100644 --- a/python/servo/package_commands.py +++ b/python/servo/package_commands.py @@ -42,7 +42,7 @@ from servo.util import delete PACKAGES = { 'android': [ - 'target/armv7-linux-androideabi/release/servo.apk', + 'target/armv7-linux-androideabi/release/servoapp.apk', ], 'linux': [ 'target/release/servo-tech-demo.tar.gz', @@ -421,7 +421,7 @@ class PackageCommands(CommandBase): return 1 if android: - pkg_path = binary_path + ".apk" + pkg_path = self.get_apk_path(release) exec_command = [self.android_adb_path(env)] if emulator and usb: print("Cannot install to both emulator and USB at the same time.") diff --git a/python/servo/post_build_commands.py b/python/servo/post_build_commands.py index 0132d2ea4b0..5b29015e8ff 100644 --- a/python/servo/post_build_commands.py +++ b/python/servo/post_build_commands.py @@ -9,6 +9,7 @@ from __future__ import print_function, unicode_literals +import json import os import os.path as path import subprocess @@ -34,6 +35,13 @@ def read_file(filename, if_exists=False): return f.read() +# Copied from Python 3.3+'s shlex.quote() +def shell_quote(arg): + # use single quotes, and put single quotes into double quotes + # the string $'b is then quoted as '$'"'"'b' + return "'" + arg.replace("'", "'\"'\"'") + "'" + + @CommandProvider class PostBuildCommands(CommandBase): @Command('run', @@ -88,15 +96,11 @@ class PostBuildCommands(CommandBase): return script = [ "am force-stop com.mozilla.servo", - "echo servo >/sdcard/Android/data/com.mozilla.servo/files/android_params" ] - for param in params: - script += [ - "echo '%s' >>/sdcard/Android/data/com.mozilla.servo/files/android_params" - % param.replace("'", "\\'") - ] + json_params = shell_quote(json.dumps(params)) + extra = "-e servoargs " + json_params script += [ - "am start com.mozilla.servo/com.mozilla.servo.MainActivity", + "am start " + extra + " com.mozilla.servo/com.mozilla.servo.MainActivity", "sleep 0.5", "echo Servo PID: $(pidof com.mozilla.servo)", "exit" @@ -257,7 +261,7 @@ class PostBuildCommands(CommandBase): copy2(full_name, destination) return self.call_rustup_run( - ["cargo", "doc", "--manifest-path", self.servo_manifest()] + params, + ["cargo", "doc", "--manifest-path", self.ports_servo_manifest()] + params, env=self.build_env() ) diff --git a/python/servo/testing_commands.py b/python/servo/testing_commands.py index db369a82b2d..1cfddcc1ac7 100644 --- a/python/servo/testing_commands.py +++ b/python/servo/testing_commands.py @@ -278,7 +278,7 @@ class MachCommands(CommandBase): features = self.servo_features() if len(packages) > 0 or len(in_crate_packages) > 0: - args = ["cargo", "bench" if bench else "test", "--manifest-path", self.servo_manifest()] + args = ["cargo", "bench" if bench else "test", "--manifest-path", self.ports_servo_manifest()] for crate in packages: args += ["-p", "%s_tests" % crate] for crate in in_crate_packages: @@ -576,7 +576,7 @@ class MachCommands(CommandBase): def test_android_startup(self, release, dev): html = """ """ url = "data:text/html;base64," + html.encode("base64").replace("\n", "") @@ -607,8 +607,7 @@ class MachCommands(CommandBase): env = self.build_env(target=target) os.environ["PATH"] = env["PATH"] assert self.handle_android_target(target) - binary_path = self.get_binary_path(release, dev, android=True) - apk = binary_path + ".apk" + apk = self.get_apk_path(release) py = path.join(self.context.topdir, "etc", "run_in_headless_android_emulator.py") return [py, avd, apk] diff --git a/support/android/apk/app/src/main/java/com/mozilla/servo/MainActivity.java b/support/android/apk/app/src/main/java/com/mozilla/servo/MainActivity.java deleted file mode 100644 index e2ca624bc08..00000000000 --- a/support/android/apk/app/src/main/java/com/mozilla/servo/MainActivity.java +++ /dev/null @@ -1,248 +0,0 @@ -package com.mozilla.servo; -import android.annotation.TargetApi; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageInfo; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.PowerManager; -import android.preference.PreferenceManager; -import android.util.Log; -import android.view.SurfaceView; -import android.view.View; -import android.view.WindowManager; -import android.webkit.URLUtil; -import android.widget.FrameLayout; - -import com.mozilla.servo.BuildConfig; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PrintStream; -import java.lang.System; -import java.util.Enumeration; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - - -public class MainActivity extends android.app.NativeActivity { - private static final String LOGTAG = "Servo"; - private boolean mFullScreen = false; - private static final String PREF_KEY_RESOURCES_SYNC = "res_sync_v"; - - static { - Log.i(LOGTAG, "Loading the NativeActivity"); - - // Libaries should be loaded in reverse dependency order - System.loadLibrary("c++_shared"); - System.loadLibrary("servo"); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - final Intent intent = getIntent(); - if (intent != null && intent.getAction().equals(Intent.ACTION_VIEW)) { - final String url = intent.getDataString(); - if (url != null && URLUtil.isValidUrl(url)) { - Log.d(LOGTAG, "Received url "+url); - set_url(url); - } - } - - JSONObject preferences = loadPreferences(); - - boolean keepScreenOn = false; - - if (BuildConfig.FLAVOR.contains("vr")) { - // Force fullscreen mode and keep screen on for VR experiences. - keepScreenOn = true; - mFullScreen = true; - } - else { - keepScreenOn = preferences.optBoolean("shell.keep_screen_on.enabled", false); - mFullScreen = !preferences.optBoolean("shell.native-titlebar.enabled", false); - - String orientation = preferences.optString("shell.native-orientation", "both"); - - // Handle orientation preference - if (orientation.equalsIgnoreCase("portrait")) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - else if (orientation.equalsIgnoreCase("landscape")) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - } - } - - super.onCreate(savedInstanceState); - - // NativeActivity ignores the Android view hierarchy because it’s designed - // to take over the surface from the window to directly draw to it. - // Inject a custom SurfaceView in order to support adding views on top of the browser. - // (e.g. Native Banners, Daydream GVRLayout or other native views) - getWindow().takeSurface(null); - FrameLayout layout = new FrameLayout(this); - layout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT)); - SurfaceView nativeSurface = new SurfaceView(this); - nativeSurface.getHolder().addCallback(this); - layout.addView(nativeSurface, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); - setContentView(layout); - - // Handle keep screen on preference - if (keepScreenOn) { - keepScreenOn(); - } - - // Handle full screen preference - if (mFullScreen) { - addFullScreenListener(); - } - } - - @Override - protected void onStop() { - Log.d(LOGTAG, "onStop"); - super.onStop(); - } - - @Override - protected void onPause() { - Log.d(LOGTAG, "onPause"); - super.onPause(); - } - - @Override - protected void onResume() { - Log.d(LOGTAG, "onPause"); - if (mFullScreen) { - setFullScreen(); - } - super.onResume(); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus && mFullScreen) { - setFullScreen(); - } - } - - // keep the device's screen turned on and bright. - private void keepScreenOn() { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - - // Dim toolbar and make the view fullscreen - private void setFullScreen() { - int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // Hides navigation bar - | View.SYSTEM_UI_FLAG_FULLSCREEN; // Hides status bar - if( android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { - flags |= getImmersiveFlag(); - } else { - flags |= View.SYSTEM_UI_FLAG_LOW_PROFILE; - } - getWindow().getDecorView().setSystemUiVisibility(flags); - } - - @TargetApi(19) - private int getImmersiveFlag() { - return View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - } - - private void addFullScreenListener() { - View decorView = getWindow().getDecorView(); - decorView.setOnSystemUiVisibilityChangeListener( - new View.OnSystemUiVisibilityChangeListener() { - public void onSystemUiVisibilityChange(int visibility) { - if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { - setFullScreen(); - } - } - }); - } - - private String loadAsset(String file) { - InputStream is = null; - BufferedReader reader = null; - try { - is = getAssets().open(file); - reader = new BufferedReader(new InputStreamReader(is)); - StringBuilder result = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - result.append(line).append('\n'); - } - return result.toString(); - } catch (IOException e) { - Log.e(LOGTAG, Log.getStackTraceString(e)); - return null; - } - finally { - try { - if (reader != null) { - reader.close(); - } - if (is != null) { - is.close(); - } - } catch (Exception e) { - Log.e(LOGTAG, Log.getStackTraceString(e)); - } - } - } - - private JSONObject loadPreferences() { - String json = loadAsset("prefs.json"); - try { - return new JSONObject(json); - } catch (JSONException e) { - Log.e(LOGTAG, Log.getStackTraceString(e)); - return new JSONObject(); - } - } - - private File getAppDataDir() { - File file = getExternalFilesDir(null); - return file != null ? file : getFilesDir(); - } - - private void set_url(String url) { - try { - File file = new File(getAppDataDir() + "/android_params"); - if (!file.exists()) { - file.createNewFile(); - } - PrintStream out = new PrintStream(new FileOutputStream(file, false)); - out.println("# The first line here should be the \"servo\" argument (without quotes) and the"); - out.println("# last should be the URL to load."); - out.println("# Blank lines and those beginning with a '#' are ignored."); - out.println("# Each line should be a separate parameter as would be parsed by the shell."); - out.println("# For example, \"servo -p 10 http://en.wikipedia.org/wiki/Rust\" would take 4"); - out.println("# lines (the \"-p\" and \"10\" are separate even though they are related)."); - out.println("servo"); - out.println("-w"); - String absUrl = url.replace("file:///storage/emulated/0/", "/sdcard/"); - out.println(absUrl); - out.flush(); - out.close(); - } catch (Exception e) { - Log.e(LOGTAG, Log.getStackTraceString(e)); - } - } -} diff --git a/support/android/apk/build.gradle b/support/android/apk/build.gradle index 1db454a0be9..8a93ba9b041 100644 --- a/support/android/apk/build.gradle +++ b/support/android/apk/build.gradle @@ -2,9 +2,10 @@ buildscript { repositories { jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:3.1.3' } } @@ -14,6 +15,7 @@ allprojects { flatDir { dirs rootDir.absolutePath + "/../../../target/android_aar" } + google() } buildDir = rootDir.absolutePath + "/../../../target/gradle" diff --git a/support/android/apk/gradle/wrapper/gradle-wrapper.properties b/support/android/apk/gradle/wrapper/gradle-wrapper.properties index dbdc05d274d..915f189a922 100644 --- a/support/android/apk/gradle/wrapper/gradle-wrapper.properties +++ b/support/android/apk/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Wed Jul 11 13:23:08 CEST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/support/android/apk/jni/Android.mk b/support/android/apk/jni/Android.mk index c3e5ecdd29b..b16da56f57e 100644 --- a/support/android/apk/jni/Android.mk +++ b/support/android/apk/jni/Android.mk @@ -18,5 +18,5 @@ MY_LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_PATH:= $(SERVO_TARGET_DIR) LOCAL_MODULE := servo -LOCAL_SRC_FILES := libservo.so +LOCAL_SRC_FILES := libsimpleservo.so include $(PREBUILT_SHARED_LIBRARY) diff --git a/support/android/apk/app/build.gradle b/support/android/apk/servoapp/build.gradle similarity index 80% rename from support/android/apk/app/build.gradle rename to support/android/apk/servoapp/build.gradle index d4e3530311e..5568bd9e065 100644 --- a/support/android/apk/app/build.gradle +++ b/support/android/apk/servoapp/build.gradle @@ -6,18 +6,15 @@ import java.util.regex.Matcher import java.util.regex.Pattern android { - compileSdkVersion 25 - buildToolsVersion "25.0.2" + compileSdkVersion 27 + buildToolsVersion '27.0.3' defaultConfig { applicationId "com.mozilla.servo" minSdkVersion 18 - targetSdkVersion 25 + targetSdkVersion 27 versionCode 1 versionName "1.0.0" - jackOptions { - enabled true - } } compileOptions { @@ -33,6 +30,13 @@ android { } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + flavorDimensions "default" + productFlavors { main { } @@ -144,25 +148,27 @@ android { variant.setIgnore(true); } } - + // Define apk output directory applicationVariants.all { variant -> - variant.outputs.each { output -> - def name = variant.buildType.name - output.outputFile = new File(getApkPath(isDebug(name), getArch(name))) + variant.outputs.all { output -> + Pattern pattern = Pattern.compile(/^[\w\d]+([A-Z][\w\d]+)(Debug|Release)/) + Matcher matcher = pattern.matcher(variant.name) + if (!matcher.find()) { + throw "Invalid variant name for output" + } + def arch = matcher.group(1) + def debug = variant.name.contains("Debug") + def path = "../../../../../" + getSubTargetDir(debug, arch) + "/servoapp.apk"; + outputFileName = new File(path) } } // Call our custom NDK Build task using flavor parameters tasks.all { compileTask -> - // Parse architecture name from gradle task name: - // Examples: transformJackWithJackForMainArmv7Release, transformJackWithJackForOculusvrArmv7Release - Pattern pattern = Pattern.compile(/^transformJackWithJackFor[A-Z][\w\d]+([A-Z][\w\d]+)(Debug|Release)/); - Matcher matcher = pattern.matcher(compileTask.name); - // You can use this alternative pattern when jackCompiler is disabled - // Pattern pattern = Pattern.compile(/^compile([\w\d]+)(Debug|Release)/); - // Matcher matcher = pattern.matcher(compileTask.name); + Pattern pattern = Pattern.compile(/^compile[A-Z][\w\d]+([A-Z][\w\d]+)(Debug|Release)/) + Matcher matcher = pattern.matcher(compileTask.name) if (!matcher.find()) { return } @@ -188,18 +194,17 @@ android { dependencies { //Dependency list def deps = [ - new ServoDependency("blurdroid.jar", "blurdroid") + new ServoDependency("blurdroid.jar", "blurdroid") ] - // Iterate all build types and dependencies - // For each dependency call the proper compile command and set the correct dependency path + // For each dependency call the proper implementation command and set the correct dependency path def list = ['arm', 'armv7', 'arm64', 'x86'] for (arch in list) { for (debug in [true, false]) { String basePath = getTargetDir(debug, arch) + "/build" - String cmd = arch + (debug ? "Debug" : "Release") + "Compile" + String cmd = arch + (debug ? "Debug" : "Release") + "Implementation" - for (ServoDependency dep: deps) { + for (ServoDependency dep : deps) { String path = findDependencyPath(basePath, dep.fileName, dep.folderFilter) if (path) { "${cmd}" files(path) @@ -207,35 +212,27 @@ dependencies { } } } - - googlevrCompile 'com.google.vr:sdk-base:1.70.0' - googlevrCompile(name:'GVRService', ext:'aar') - oculusvrCompile(name:'OVRService', ext:'aar') + googlevrImplementation 'com.google.vr:sdk-base:1.140.0' + googlevrImplementation(name: 'GVRService', ext: 'aar') + oculusvrImplementation(name: 'OVRService', ext: 'aar') + implementation 'com.android.support.constraint:constraint-layout:1.1.2' } // Utility methods String getTargetDir(boolean debug, String arch) { def basePath = project.rootDir.getParentFile().getParentFile().getParentFile().absolutePath - return basePath + '/target/' + getRustTarget(arch) + '/' + (debug ? 'debug' : 'release') + return basePath + '/target/' + getSubTargetDir(debug, arch) } -String getApkPath(boolean debug, String arch) { - return getTargetDir(debug, arch) + '/servo.apk' +String getSubTargetDir(boolean debug, String arch) { + return getRustTarget(arch) + '/' + (debug ? 'debug' : 'release') } String getJniLibsPath(boolean debug, String arch) { return getTargetDir(debug, arch) + '/apk/jniLibs' } -String getArch(String buildType) { - return buildType.replaceAll(/(Debug|Release)/, '') -} - -boolean isDebug(String buildType) { - return buildType.contains("Debug") -} - -String getRustTarget(String arch) { +static String getRustTarget(String arch) { switch (arch.toLowerCase()) { case 'arm' : return 'arm-linux-androideabi' case 'armv7' : return 'armv7-linux-androideabi' @@ -245,7 +242,7 @@ String getRustTarget(String arch) { } } -String getNDKAbi(String arch) { +static String getNDKAbi(String arch) { switch (arch.toLowerCase()) { case 'arm' : return 'armeabi' case 'armv7' : return 'armeabi-v7a' @@ -283,7 +280,7 @@ String getNdkDir() { } // folderFilter can be used to improve search performance -String findDependencyPath(String basePath, String filename, String folderFilter) { +static String findDependencyPath(String basePath, String filename, String folderFilter) { File path = new File(basePath); if (!path.exists()) { return '' @@ -307,10 +304,10 @@ String findDependencyPath(String basePath, String filename, String folderFilter) } class ServoDependency { - public ServoDependency(String fileName, String folderFilter = null) { + ServoDependency(String fileName, String folderFilter = null) { this.fileName = fileName; this.folderFilter = folderFilter; } public String fileName; public String folderFilter; -} +} \ No newline at end of file diff --git a/support/android/apk/app/src/googlevr/AndroidManifest.xml b/support/android/apk/servoapp/src/googlevr/AndroidManifest.xml similarity index 100% rename from support/android/apk/app/src/googlevr/AndroidManifest.xml rename to support/android/apk/servoapp/src/googlevr/AndroidManifest.xml diff --git a/support/android/apk/app/src/main/AndroidManifest.xml b/support/android/apk/servoapp/src/main/AndroidManifest.xml similarity index 97% rename from support/android/apk/app/src/main/AndroidManifest.xml rename to support/android/apk/servoapp/src/main/AndroidManifest.xml index 355c8d4efc4..727b68871ec 100644 --- a/support/android/apk/app/src/main/AndroidManifest.xml +++ b/support/android/apk/servoapp/src/main/AndroidManifest.xml @@ -3,13 +3,13 @@ - + - - + + = android.os.Build.VERSION_CODES.O) { + File sdcard = getExternalFilesDir(""); + String host = sdcard.toPath().resolve("android_hosts").toString(); + try { + Os.setenv("HOST_FILE", host, false); + } catch (ErrnoException e) { + e.printStackTrace(); + } + } + + String args = getIntent().getStringExtra("servoargs"); + if (args != null) { + mServoView.setServoArgs(args); + } + + setupUrlField(); + } + + private void setupUrlField() { + mUrlField.setOnEditorActionListener((v, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) { + loadUrlFromField(); + mServoView.requestFocus(); + return true; + } + return false; + }); + mUrlField.setOnFocusChangeListener((v, hasFocus) -> { + if(v.getId() == R.id.urlfield && !hasFocus) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + assert imm != null; + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + }); + } + + private void loadUrlFromField() { + String text = mUrlField.getText().toString(); + text = text.trim(); + String uri; + + if (text.contains(" ") || !text.contains(".")) { + uri = URLUtil.composeSearchUrl(text, "https://duckduckgo.com/html/?q=%s", "%s"); + } else { + uri = URLUtil.guessUrl(text); + } + + mServoView.loadUri(Uri.parse(uri)); + } + + public void onReloadClicked(View v) { + mServoView.reload(); + } + + public void onBackClicked(View v) { + mServoView.goBack(); + } + + public void onForwardClicked(View v) { + mServoView.goForward(); + } + + public void onStopClicked(View v) { + mServoView.stop(); + } + + @Override + public void onLoadStarted() { + mReloadButton.setEnabled(false); + mStopButton.setEnabled(true); + mReloadButton.setVisibility(View.GONE); + mStopButton.setVisibility(View.VISIBLE); + mProgressBar.setVisibility(View.VISIBLE); + } + + @Override + public void onLoadEnded() { + mReloadButton.setEnabled(true); + mStopButton.setEnabled(false); + mReloadButton.setVisibility(View.VISIBLE); + mStopButton.setVisibility(View.GONE); + mProgressBar.setVisibility(View.INVISIBLE); + } + + @Override + public void onTitleChanged(String title) { + } + + @Override + public void onUrlChanged(String url) { + mUrlField.setText(url); + } + + @Override + public void onHistoryChanged(boolean canGoBack, boolean canGoForward) { + mBackButton.setEnabled(canGoBack); + mFwdButton.setEnabled(canGoForward); + } + +} diff --git a/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/NativeServo.java b/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/NativeServo.java new file mode 100644 index 00000000000..51f27b24d6f --- /dev/null +++ b/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/NativeServo.java @@ -0,0 +1,57 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * 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/. */ + +package com.mozilla.servoview; + +import android.app.Activity; + +/** + * Maps /ports/libsimpleservo API + */ +public class NativeServo { + public native String version(); + public native void init(Activity activity, + String args, + String url, + WakeupCallback wakeup, + ReadFileCallback readfile, + ServoCallbacks callbacks, + int width, int height, boolean log); + public native void setBatchMode(boolean mode); + public native void performUpdates(); + public native void resize(int width, int height); + public native void reload(); + public native void stop(); + public native void goBack(); + public native void goForward(); + public native void loadUri(String uri); + public native void scrollStart(int dx, int dy, int x, int y); + public native void scroll(int dx, int dy, int x, int y); + public native void scrollEnd(int dx, int dy, int x, int y); + public native void click(int x, int y); + + NativeServo() { + System.loadLibrary("c++_shared"); + System.loadLibrary("simpleservo"); + } + + public interface ReadFileCallback { + byte[] readfile(String file); + } + + public interface WakeupCallback { + void wakeup(); + } + + public interface ServoCallbacks { + void flush(); + void onLoadStarted(); + void onLoadEnded(); + void onTitleChanged(String title); + void onUrlChanged(String url); + void onHistoryChanged(boolean canGoBack, boolean canGoForward); + void onAnimatingChanged(boolean animating); + } +} diff --git a/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/ServoGLRenderer.java b/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/ServoGLRenderer.java new file mode 100644 index 00000000000..dc06c14e512 --- /dev/null +++ b/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/ServoGLRenderer.java @@ -0,0 +1,32 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * 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/. */ + +package com.mozilla.servoview; + +import android.opengl.GLES31; +import android.opengl.GLSurfaceView; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +public class ServoGLRenderer implements GLSurfaceView.Renderer { + + private final ServoView mView; + + ServoGLRenderer(ServoView view) { + mView = view; + } + + public void onSurfaceCreated(GL10 unused, EGLConfig config) { + mView.onGLReady(); + } + + public void onDrawFrame(GL10 unused) { + } + + public void onSurfaceChanged(GL10 unused, int width, int height) { + GLES31.glViewport(0, 0, width, height); + mView.onSurfaceResized(width, height); + } +} diff --git a/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/ServoView.java b/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/ServoView.java new file mode 100644 index 00000000000..2cf2c257a78 --- /dev/null +++ b/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/ServoView.java @@ -0,0 +1,281 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * 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/. */ + +package com.mozilla.servoview; + +import android.app.Activity; +import android.os.Build; +import android.content.Context; +import android.content.res.AssetManager; +import android.net.Uri; +import android.opengl.GLSurfaceView; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Choreographer; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.widget.OverScroller; +import java.io.IOException; +import java.io.InputStream; + +public class ServoView extends GLSurfaceView implements GestureDetector.OnGestureListener, Choreographer.FrameCallback { + + private static final String LOGTAG = "ServoView"; + + private Activity mActivity; + private NativeServo mServo; + private Client mClient = null; + private Uri mInitialUri = null; + private boolean mAnimating; + private String mServoArgs = ""; + + public ServoView(Context context, AttributeSet attrs) { + super(context, attrs); + mActivity = (Activity) context; + setFocusable(true); + setFocusableInTouchMode(true); + setWillNotCacheDrawing(false); + setEGLContextClientVersion(3); + setEGLConfigChooser(8, 8, 8, 8, 24, 0); + ServoGLRenderer mRenderer = new ServoGLRenderer(this); + setRenderer(mRenderer); + mServo = new NativeServo(); + setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + initGestures(context); + } + + public void setServoArgs(String args) { + mServoArgs = args; + } + + public void reload() { + queueEvent(() -> mServo.reload()); + } + + public void goBack() { + queueEvent(() -> mServo.goBack()); + } + + public void goForward() { + queueEvent(() -> mServo.goForward()); + } + + public void stop() { + queueEvent(() -> mServo.stop()); + } + + public void onSurfaceResized(int width, int height) { + queueEvent(() -> mServo.resize(width, height)); + } + + public void loadUri(Uri uri) { + if (mServo != null) { + queueEvent(() -> mServo.loadUri(uri.toString())); + } else { + mInitialUri = uri; + } + } + + class WakeupCallback implements NativeServo.WakeupCallback { + public void wakeup() { + queueEvent(() -> mServo.performUpdates()); + }; + } + + class ReadFileCallback implements NativeServo.ReadFileCallback { + public byte[] readfile(String file) { + try { + AssetManager assetMgr = getContext().getResources().getAssets(); + InputStream stream = assetMgr.open(file); + byte[] bytes = new byte[stream.available()]; + stream.read(bytes); + stream.close(); + return bytes; + } catch (IOException e) { + Log.e(LOGTAG, e.getMessage()); + return null; + } + } + } + + class ServoCallbacks implements NativeServo.ServoCallbacks { + public void flush() { + requestRender(); + } + + public void onLoadStarted() { + if (mClient != null) { + post(() -> mClient.onLoadStarted()); + } + } + + public void onLoadEnded() { + if (mClient != null) { + post(() -> mClient.onLoadEnded()); + } + } + + public void onTitleChanged(final String title) { + if (mClient != null) { + post(() -> mClient.onTitleChanged(title)); + } + } + + public void onUrlChanged(final String url) { + if (mClient != null) { + post(() -> mClient.onUrlChanged(url)); + } + } + + public void onHistoryChanged(final boolean canGoBack, final boolean canGoForward) { + if (mClient != null) { + post(() -> mClient.onHistoryChanged(canGoBack, canGoForward)); + } + } + + public void onAnimatingChanged(final boolean animating) { + if (!mAnimating && animating) { + post(() -> Choreographer.getInstance().postFrameCallback(ServoView.this)); + } + mAnimating = animating; + } + } + + public void onGLReady() { + final WakeupCallback c1 = new WakeupCallback(); + final ReadFileCallback c2 = new ReadFileCallback(); + final ServoCallbacks c3 = new ServoCallbacks(); + final boolean showLogs = true; + int width = getWidth(); + int height = getHeight(); + queueEvent(() -> { + String uri = mInitialUri == null ? null : mInitialUri.toString(); + mServo.init(mActivity, mServoArgs, uri, c1, c2, c3, width, height, showLogs); + }); + } + + public interface Client { + void onLoadStarted(); + void onLoadEnded(); + void onTitleChanged(String title); + void onUrlChanged(String url); + void onHistoryChanged(boolean canGoBack, boolean canGoForward); + } + + public void setClient(Client client) { + mClient = client; + } + + // Scroll and click + + private GestureDetector mGestureDetector; + private OverScroller mScroller; + private int mLastX = 0; + private int mCurX = 0; + private int mLastY = 0; + private int mCurY = 0; + private boolean mFlinging; + + private void initGestures(Context context) { + mGestureDetector = new GestureDetector(context, this); + mScroller = new OverScroller(context); + } + + @Override + public void doFrame(long frameTimeNanos) { + + if (mScroller.isFinished() && mFlinging) { + mFlinging = false; + queueEvent(() -> mServo.scrollEnd(0, 0, mCurX, mCurY)); + if (!mAnimating) { + // Not scrolling. Not animating. We don't need to schedule + // another frame. + return; + } + } + + if (mFlinging) { + mScroller.computeScrollOffset(); + mCurX = mScroller.getCurrX(); + mCurY = mScroller.getCurrY(); + } + + int dx = mCurX - mLastX; + int dy = mCurY - mLastY; + + mLastX = mCurX; + mLastY = mCurY; + + if (dx != 0 || dy != 0) { + queueEvent(() -> mServo.scroll(dx, dy, mCurX, mCurY)); + } else { + if (mAnimating) { + requestRender(); + } + } + + Choreographer.getInstance().postFrameCallback(this); + } + + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + mFlinging = true; + + // FIXME: magic values + // https://github.com/servo/servo/issues/20361 + int mPageWidth = 80000; + int mPageHeight = 80000; + mCurX = velocityX < 0 ? mPageWidth : 0; + mLastX = mCurX; + mCurY = velocityY < 0 ? mPageHeight : 0; + mLastY = mCurY; + mScroller.fling(mCurX, mCurY, (int)velocityX, (int)velocityY, 0, mPageWidth, 0, mPageHeight); + return true; + } + + public boolean onDown(MotionEvent e) { + mScroller.forceFinished(true); + return true; + } + + public boolean onTouchEvent(final MotionEvent e) { + mGestureDetector.onTouchEvent(e); + + int action = e.getActionMasked(); + switch(action) { + case (MotionEvent.ACTION_DOWN): + mCurX = (int)e.getX(); + mLastX = mCurX; + mCurY = (int)e.getY(); + mLastY = mCurY; + mScroller.forceFinished(true); + queueEvent(() -> mServo.scrollStart(0, 0, mCurX, mCurY)); + Choreographer.getInstance().postFrameCallback(this); + return true; + case (MotionEvent.ACTION_MOVE): + mCurX = (int)e.getX(); + mCurY = (int)e.getY(); + return true; + case (MotionEvent.ACTION_UP): + case (MotionEvent.ACTION_CANCEL): + if (!mFlinging) { + queueEvent(() -> mServo.scrollEnd(0, 0, mCurX, mCurY)); + Choreographer.getInstance().removeFrameCallback(this); + } + return true; + default: + return true; + } + } + + public boolean onSingleTapUp(MotionEvent e) { + queueEvent(() -> mServo.click((int)e.getX(), (int)e.getY())); + return false; + } + + public void onLongPress(MotionEvent e) { } + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return true; } + public void onShowPress(MotionEvent e) { } + +} diff --git a/support/android/apk/servoapp/src/main/res/layout/activity_main.xml b/support/android/apk/servoapp/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000000..2d8fab9c663 --- /dev/null +++ b/support/android/apk/servoapp/src/main/res/layout/activity_main.xml @@ -0,0 +1,94 @@ + + + + + + + +