Commit 7ca339e3 authored by Christopher Grant's avatar Christopher Grant Committed by Commit Bot

Modules: Introduce a generic DFM native library loading system

DFMs that include both Java and native code will probably use JNI. This
change introduces a reusable mechanism to load the native library and
register its JNI methods.

Key points:

- There are 3 build variants supported:
  1. Production builds with lld-generated partitions.
  2. Component build (with feature code in a component).
  3. Fallback release build (feature code in the main library)

- For consistency, explicit JNI registration is done in both
  ChromeModern and Monochrome.

- The Test Dummy module is moved over to use this new system. VR will
  join the fun in a follow-on change.

- Currently, modules must supply an explicit JNI registration method.
  Common code will locate this method and call it, at the appropriate
  time, on behalf of the module.

Bug: 870055
Change-Id: Id400ff00b4be65f71536a4acffdfcf93cc3bea58
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1822846
Commit-Queue: Christopher Grant <cjgrant@chromium.org>
Reviewed-by: default avatarTibor Goldschwendt <tiborg@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/master@{#700276}
parent aea9431a
...@@ -62,12 +62,6 @@ bool BundleUtils::IsBundle() { ...@@ -62,12 +62,6 @@ bool BundleUtils::IsBundle() {
return Java_BundleUtils_isBundle(base::android::AttachCurrentThread()); return Java_BundleUtils_isBundle(base::android::AttachCurrentThread());
} }
// static
void* BundleUtils::DlOpenModuleLibrary(const std::string& library_name) {
std::string library_path = ResolveLibraryPath(library_name);
return dlopen(library_path.c_str(), RTLD_LOCAL);
}
// static // static
void* BundleUtils::DlOpenModuleLibraryPartition( void* BundleUtils::DlOpenModuleLibraryPartition(
const std::string& library_name) { const std::string& library_name) {
......
...@@ -18,9 +18,6 @@ class BASE_EXPORT BundleUtils { ...@@ -18,9 +18,6 @@ class BASE_EXPORT BundleUtils {
// Returns true if the current build is a bundle. // Returns true if the current build is a bundle.
static bool IsBundle(); static bool IsBundle();
// dlopen wrapper that works for native libraries in dynamic feature modules.
static void* DlOpenModuleLibrary(const std::string& libary_name);
// dlopen wrapper that works for partitioned native libraries in dynamic // dlopen wrapper that works for partitioned native libraries in dynamic
// feature modules. This routine looks up the partition's address space in a // feature modules. This routine looks up the partition's address space in a
// table of main library symbols, and uses it when loading the feature // table of main library symbols, and uses it when loading the feature
......
...@@ -42,6 +42,10 @@ def main(): ...@@ -42,6 +42,10 @@ def main():
dest='whitelists', dest='whitelists',
help='Path to an input file containing a whitelist of extra symbols to ' help='Path to an input file containing a whitelist of extra symbols to '
'export, one symbol per line. Multiple files may be specified.') 'export, one symbol per line. Multiple files may be specified.')
parser.add_argument(
'--export-feature-registrations',
action='store_true',
help='Export JNI_OnLoad_* methods')
options = parser.parse_args() options = parser.parse_args()
# JNI_OnLoad is always exported. # JNI_OnLoad is always exported.
...@@ -52,6 +56,9 @@ def main(): ...@@ -52,6 +56,9 @@ def main():
if options.export_java_symbols: if options.export_java_symbols:
symbol_list.append('Java_*') symbol_list.append('Java_*')
if options.export_feature_registrations:
symbol_list.append('JNI_OnLoad_*')
for whitelist in options.whitelists: for whitelist in options.whitelists:
with open(whitelist, 'rt') as f: with open(whitelist, 'rt') as f:
for line in f: for line in f:
......
...@@ -27,6 +27,11 @@ template("generate_linker_version_script") { ...@@ -27,6 +27,11 @@ template("generate_linker_version_script") {
args += [ "--export-java-symbols" ] args += [ "--export-java-symbols" ]
} }
if (defined(invoker.export_feature_registrations) &&
invoker.export_feature_registrations) {
args += [ "--export-feature-registrations" ]
}
if (defined(invoker.export_symbol_whitelist_files)) { if (defined(invoker.export_symbol_whitelist_files)) {
foreach(file_, invoker.export_symbol_whitelist_files) { foreach(file_, invoker.export_symbol_whitelist_files) {
inputs += [ file_ ] inputs += [ file_ ]
......
...@@ -1195,7 +1195,6 @@ chrome_common_shared_library("libchrome") { ...@@ -1195,7 +1195,6 @@ chrome_common_shared_library("libchrome") {
if (enable_vr) { if (enable_vr) {
deps += [ "//chrome/browser/android/vr:module_factory" ] deps += [ "//chrome/browser/android/vr:module_factory" ]
} }
deps += [ "//chrome/android/features/test_dummy/internal:base_module_native" ]
allow_partitions = true allow_partitions = true
module_descs = chrome_modern_module_descs module_descs = chrome_modern_module_descs
...@@ -1212,7 +1211,6 @@ chrome_common_shared_library("libchromefortest") { ...@@ -1212,7 +1211,6 @@ chrome_common_shared_library("libchromefortest") {
":chrome_jni_for_test_registration($default_toolchain)", ":chrome_jni_for_test_registration($default_toolchain)",
"//base/test:test_support", "//base/test:test_support",
"//chrome:chrome_android_core", "//chrome:chrome_android_core",
"//chrome/android/features/test_dummy/internal:base_module_native",
"//chrome/browser/android/metrics:ukm_utils_for_test", "//chrome/browser/android/metrics:ukm_utils_for_test",
"//components/autofill_assistant/browser:test_support", "//components/autofill_assistant/browser:test_support",
"//components/crash/android:crash_android", "//components/crash/android:crash_android",
...@@ -1226,6 +1224,10 @@ chrome_common_shared_library("libchromefortest") { ...@@ -1226,6 +1224,10 @@ chrome_common_shared_library("libchromefortest") {
if (enable_vr) { if (enable_vr) {
deps += [ "//chrome/browser/android/vr:test_support" ] deps += [ "//chrome/browser/android/vr:test_support" ]
} }
# TODO(http://crbug.com/1008123): Include module native code by enumerating
# module descriptors.
deps += [ "//chrome/android/modules/test_dummy/internal:native" ]
} }
# Ensure that .pak files are built only once (build them in the default # Ensure that .pak files are built only once (build them in the default
...@@ -1461,8 +1463,6 @@ template("libmonochrome_apk_or_bundle_tmpl") { ...@@ -1461,8 +1463,6 @@ template("libmonochrome_apk_or_bundle_tmpl") {
if (enable_vr) { if (enable_vr) {
deps += [ "//chrome/browser/android/vr:module_factory" ] deps += [ "//chrome/browser/android/vr:module_factory" ]
} }
deps +=
[ "//chrome/android/features/test_dummy/internal:base_module_native" ]
is_monochrome = true is_monochrome = true
allow_partitions = true allow_partitions = true
......
...@@ -40,8 +40,9 @@ template("chrome_common_shared_library") { ...@@ -40,8 +40,9 @@ template("chrome_common_shared_library") {
# Create a partitioned libraries if the build config supports it, and the # Create a partitioned libraries if the build config supports it, and the
# invoker wants partitions. # invoker wants partitions.
_generate_partitions = defined(invoker.allow_partitions) && _allow_partitions =
invoker.allow_partitions && use_native_modules defined(invoker.allow_partitions) && invoker.allow_partitions
_generate_partitions = _allow_partitions && use_native_modules
if (_generate_partitions) { if (_generate_partitions) {
assert(defined(invoker.module_descs)) assert(defined(invoker.module_descs))
} else { } else {
...@@ -55,6 +56,7 @@ template("chrome_common_shared_library") { ...@@ -55,6 +56,7 @@ template("chrome_common_shared_library") {
linker_script = _linker_script linker_script = _linker_script
export_java_symbols = _export_java_symbols export_java_symbols = _export_java_symbols
if (_generate_partitions) { if (_generate_partitions) {
export_feature_registrations = true
export_symbol_whitelist_files = [] export_symbol_whitelist_files = []
foreach(_module_desc, invoker.module_descs) { foreach(_module_desc, invoker.module_descs) {
if (defined(_module_desc.native_entrypoints)) { if (defined(_module_desc.native_entrypoints)) {
...@@ -105,7 +107,7 @@ template("chrome_common_shared_library") { ...@@ -105,7 +107,7 @@ template("chrome_common_shared_library") {
} }
} }
if (_generate_partitions) { if (_allow_partitions) {
partitions = [] partitions = []
foreach(_module_desc, invoker.module_descs) { foreach(_module_desc, invoker.module_descs) {
if (defined(_module_desc.native_deps)) { if (defined(_module_desc.native_deps)) {
......
...@@ -22,16 +22,19 @@ android_library("java") { ...@@ -22,16 +22,19 @@ android_library("java") {
] ]
java_files = java_files =
[ "java/src/org/chromium/chrome/features/test_dummy/TestDummyImpl.java" ] [ "java/src/org/chromium/chrome/features/test_dummy/TestDummyImpl.java" ]
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
deps += [ "//base:jni_java" ]
} }
source_set("native") { source_set("native") {
sources = [ sources = [
"test_dummy.cc", "test_dummy_impl.cc",
] ]
deps = [ deps = [
":jni_headers",
"//base", "//base",
"//chrome/android/features/test_dummy/public:native",
] ]
} }
...@@ -48,33 +51,11 @@ android_library("base_module_java") { ...@@ -48,33 +51,11 @@ android_library("base_module_java") {
] ]
java_files = [ java_files = [
"java/src/org/chromium/chrome/features/test_dummy/TestDummyActivity.java", "java/src/org/chromium/chrome/features/test_dummy/TestDummyActivity.java",
"java/src/org/chromium/chrome/features/test_dummy/TestDummySupport.java",
]
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
deps += [ "//base:jni_java" ]
}
buildflag_header("buildflags") {
header = "buildflags.h"
flags = [ "USE_NATIVE_MODULES=$use_native_modules" ]
}
source_set("base_module_native") {
sources = [
"test_dummy_client.cc",
]
deps = [
":test_dummy_jni_headers",
"//base",
]
public_deps = [
":buildflags",
] ]
} }
generate_jni("test_dummy_jni_headers") { generate_jni("jni_headers") {
sources = [ sources = [
"java/src/org/chromium/chrome/features/test_dummy/TestDummySupport.java", "java/src/org/chromium/chrome/features/test_dummy/TestDummyImpl.java",
] ]
} }
...@@ -11,6 +11,7 @@ import android.support.v7.app.AlertDialog; ...@@ -11,6 +11,7 @@ import android.support.v7.app.AlertDialog;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.annotations.NativeMethods;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
...@@ -49,6 +50,11 @@ public class TestDummyImpl implements TestDummy { ...@@ -49,6 +50,11 @@ public class TestDummyImpl implements TestDummy {
} }
} }
@NativeMethods
interface Natives {
int execute();
}
private void showDoneDialog(Activity activity, @TestCase int testCase, boolean pass) { private void showDoneDialog(Activity activity, @TestCase int testCase, boolean pass) {
String message = "Test Case %d: " + (pass ? "pass" : "fail"); String message = "Test Case %d: " + (pass ? "pass" : "fail");
AlertDialog.Builder builder = new AlertDialog.Builder(activity); AlertDialog.Builder builder = new AlertDialog.Builder(activity);
...@@ -63,8 +69,9 @@ public class TestDummyImpl implements TestDummy { ...@@ -63,8 +69,9 @@ public class TestDummyImpl implements TestDummy {
} }
private void executeNative(Activity activity) { private void executeNative(Activity activity) {
boolean result = TestDummySupport.openAndVerifyNativeLibrary(); int value = TestDummyImplJni.get().execute();
showDoneDialog(activity, TestCase.EXECUTE_NATIVE, result); boolean pass = (value == 123);
showDoneDialog(activity, TestCase.EXECUTE_NATIVE, pass);
} }
private void loadJavaResource(Activity activity) { private void loadJavaResource(Activity activity) {
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.features.test_dummy;
import org.chromium.base.annotations.NativeMethods;
/** Support class to proxy JNI calls from module Java to module native. */
//@MainDex
public class TestDummySupport {
/** JNI calls for exercising native parts of the DFM. */
@NativeMethods
public interface Natives {
boolean openAndVerifyNativeLibrary();
}
public static boolean openAndVerifyNativeLibrary() {
return TestDummySupportJni.get().openAndVerifyNativeLibrary();
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <android/dlext.h>
#include <dlfcn.h>
#include "base/android/bundle_utils.h"
#include "base/logging.h"
#include "chrome/android/features/test_dummy/internal/buildflags.h"
#include "chrome/android/features/test_dummy/internal/test_dummy_jni_headers/TestDummySupport_jni.h"
static jboolean JNI_TestDummySupport_OpenAndVerifyNativeLibrary(JNIEnv* env) {
#if BUILDFLAG(USE_NATIVE_MODULES)
// Note that the library opened here is not closed. dlclosing() libraries has
// proven to be problematic. See https://crbug.com/994029.
void* handle =
base::android::BundleUtils::DlOpenModuleLibraryPartition("test_dummy");
if (handle == nullptr) {
LOG(ERROR) << "Cannot open test library: " << dlerror();
return false;
}
void* symbol = dlsym(handle, "TestDummyEntrypoint");
if (symbol == nullptr) {
LOG(ERROR) << "Cannot find test library symbol";
return false;
}
typedef int TestFunction();
TestFunction* test_function = reinterpret_cast<TestFunction*>(symbol);
if (test_function() != 123) {
LOG(ERROR) << "Unexpected value from test library";
return false;
}
#endif
return true;
}
...@@ -2,17 +2,10 @@ ...@@ -2,17 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "chrome/android/features/test_dummy/public/test_dummy.h"
#include "base/logging.h" #include "base/logging.h"
#include "chrome/android/features/test_dummy/internal/jni_headers/TestDummyImpl_jni.h"
namespace test_dummy { static int JNI_TestDummyImpl_Execute(JNIEnv* env) {
LOG(INFO) << "Running test dummy native library";
int TestDummy() {
// Log something to utilize base library code. This is necessary to ensure
// that calls to base code are linked properly.
LOG(WARNING) << "Running test dummy native library.";
return 123; return 123;
} }
} // namespace test_dummy
...@@ -8,9 +8,3 @@ android_library("java") { ...@@ -8,9 +8,3 @@ android_library("java") {
java_files = java_files =
[ "java/src/org/chromium/chrome/features/test_dummy/TestDummy.java" ] [ "java/src/org/chromium/chrome/features/test_dummy/TestDummy.java" ]
} }
source_set("native") {
sources = [
"test_dummy.h",
]
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_ANDROID_FEATURES_TEST_DUMMY_PUBLIC_TEST_DUMMY_H_
#define CHROME_ANDROID_FEATURES_TEST_DUMMY_PUBLIC_TEST_DUMMY_H_
namespace test_dummy {
// The test_dummy feature's token native entrypoint.
int TestDummy();
} // namespace test_dummy
#endif // CHROME_ANDROID_FEATURES_TEST_DUMMY_PUBLIC_TEST_DUMMY_H_
...@@ -162,6 +162,7 @@ import org.chromium.chrome.browser.widget.textbubble.TextBubble; ...@@ -162,6 +162,7 @@ import org.chromium.chrome.browser.widget.textbubble.TextBubble;
import org.chromium.components.bookmarks.BookmarkId; import org.chromium.components.bookmarks.BookmarkId;
import org.chromium.components.feature_engagement.EventConstants; import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.Tracker; import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.module_installer.Module;
import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.SelectionPopupController; import org.chromium.content_public.browser.SelectionPopupController;
import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContents;
...@@ -1391,6 +1392,9 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent> ...@@ -1391,6 +1392,9 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
maybeRemoveWindowBackground(); maybeRemoveWindowBackground();
DownloadManagerService.getDownloadManagerService().onActivityLaunched(); DownloadManagerService.getDownloadManagerService().onActivityLaunched();
// TODO(https://crbug.com/1008162): Have the Module system register its own observer.
Module.doDeferredNativeRegistrations();
VrModuleProvider.maybeInit(); VrModuleProvider.maybeInit();
VrModuleProvider.getDelegate().onNativeLibraryAvailable(); VrModuleProvider.getDelegate().onNativeLibraryAvailable();
ArDelegate arDelegate = ArDelegateProvider.getDelegate(); ArDelegate arDelegate = ArDelegateProvider.getDelegate();
......
...@@ -65,7 +65,8 @@ template("chrome_feature_module") { ...@@ -65,7 +65,8 @@ template("chrome_feature_module") {
_loadable_modules_64_bit += _module_desc.loadable_modules_64_bit _loadable_modules_64_bit += _module_desc.loadable_modules_64_bit
} }
if (defined(_module_desc.native_deps) && _module_desc.native_deps != []) { if (use_native_modules && defined(_module_desc.native_deps) &&
_module_desc.native_deps != []) {
_arch = "" _arch = ""
_toolchain = "" _toolchain = ""
_root_out_dir = root_out_dir _root_out_dir = root_out_dir
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# found in the LICENSE file. # found in the LICENSE file.
import("//build/config/android/rules.gni") import("//build/config/android/rules.gni")
import("//chrome/android/modules/buildflags.gni")
android_library("java") { android_library("java") {
java_files = [ "java/src/org/chromium/chrome/modules/test_dummy/TestDummyProviderImpl.java" ] java_files = [ "java/src/org/chromium/chrome/modules/test_dummy/TestDummyProviderImpl.java" ]
...@@ -14,15 +15,66 @@ android_library("java") { ...@@ -14,15 +15,66 @@ android_library("java") {
] ]
} }
source_set("native") { # This group is effectively alias representing the module's native code,
# allowing it to be named "native" for clarity in module descriptors. The
# component target must be named according to the feature, so that the component
# build's .cr.co library is named properly (ie. libtest_dummy.cr.so).
group("native") {
deps = [
":test_dummy",
]
}
component("test_dummy") {
sources = [ sources = [
"entrypoints.cc", "entrypoints.cc",
] ]
deps = [ deps = [
"//chrome/android/features/test_dummy/public:native", "//base",
"//chrome/android/features/test_dummy/internal:native",
] ]
# Test dummy native entrypoints belong in the partition. # Test dummy native entrypoints belong in the partition.
cflags = [ "-fsymbol-partition=libtest_dummy.so" ] if (use_native_modules) {
cflags = [ "-fsymbol-partition=libtest_dummy.so" ]
}
if (current_toolchain != default_toolchain) {
deps += [ ":jni_registration_secondary($default_toolchain)" ]
} else {
deps += [ ":jni_registration($default_toolchain)" ]
}
}
# TODO(https://crbug.com/1008109): Adapt JNI registration to point at a Java
# target, instead of an APK/module target. This JNI registration target
# points at ChromeModern's module, but it's used by Monochrome as well, since
# both variants do explicit JNI registration in DFMs (for consistency).
#
# Alternatively, move to lazy JNI init for DFMs in Monochrome, by conditionally
# including a registration stub, as Chrome's base library does. That requires
# two sets of registration targets, so that the feature module template can
# selectively pull in the real version or a stub.
if (current_toolchain == default_toolchain) {
generate_jni_registration("jni_registration") {
target =
"//chrome/android:chrome_modern_public_bundle__test_dummy_bundle_module"
header_output = "$target_gen_dir/jni_registration.h"
namespace = "test_dummy"
}
# Note also: We cannot currently build JNI registration on secondary ABI
# toolchain (dependency issues). Therefore, for Monochrome's 32-bit library,
# we need to use the 64-bit side registration header that ChromeModern uses.
# However, it's in the 64-bit output directory. We need to also generate a copy
# for the 32-bit directory.
if (android_64bit_target_cpu) {
generate_jni_registration("jni_registration_secondary") {
target = "//chrome/android:chrome_modern_public_bundle__test_dummy_bundle_module"
header_output =
get_label_info(":native($android_secondary_abi_toolchain)",
"target_gen_dir") + "/jni_registration.h"
namespace = "test_dummy"
}
}
} }
...@@ -2,12 +2,21 @@ ...@@ -2,12 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "chrome/android/features/test_dummy/public/test_dummy.h" #include "base/android/jni_utils.h"
#include "chrome/android/modules/test_dummy/internal/jni_registration.h"
extern "C" { extern "C" {
// This JNI registration method is found and called by module framework code.
__attribute__((visibility("default"))) int TestDummyEntrypoint() { // Ideally, this code would be generated by the build.
return test_dummy::TestDummy(); __attribute__((visibility("default"))) bool JNI_OnLoad_test_dummy(JNIEnv* env) {
if (!base::android::IsSelectiveJniRegistrationEnabled(env) &&
!test_dummy::RegisterNonMainDexNatives(env)) {
return false;
}
if (!test_dummy::RegisterMainDexNatives(env)) {
return false;
}
return true;
} }
} // extern "C" } // extern "C"
...@@ -13,12 +13,8 @@ test_dummy_module_desc = { ...@@ -13,12 +13,8 @@ test_dummy_module_desc = {
"//chrome/android/modules/test_dummy/internal:java", "//chrome/android/modules/test_dummy/internal:java",
] ]
if (use_native_modules) { native_deps = [
native_deps = [ "//chrome/android/features/test_dummy/internal:native",
"//chrome/android/features/test_dummy/internal:native", "//chrome/android/modules/test_dummy/internal:native",
"//chrome/android/modules/test_dummy/internal:native", ]
]
native_entrypoints =
"//chrome/android/modules/test_dummy/internal/entrypoints.lst"
}
} }
...@@ -2911,6 +2911,7 @@ jumbo_split_static_library("browser") { ...@@ -2911,6 +2911,7 @@ jumbo_split_static_library("browser") {
"//components/feed:feature_list", "//components/feed:feature_list",
"//components/invalidation/impl:feature_list", "//components/invalidation/impl:feature_list",
"//components/language/android:language_bridge", "//components/language/android:language_bridge",
"//components/module_installer/android:native",
"//components/omnibox/browser", "//components/omnibox/browser",
"//components/page_load_metrics/browser", "//components/page_load_metrics/browser",
"//components/payments/content/android", "//components/payments/content/android",
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# found in the LICENSE file. # found in the LICENSE file.
import("//build/config/android/rules.gni") import("//build/config/android/rules.gni")
import("//chrome/android/modules/buildflags.gni")
android_library("module_installer_java") { android_library("module_installer_java") {
java_files = [ java_files = [
...@@ -22,6 +23,7 @@ android_library("module_installer_java") { ...@@ -22,6 +23,7 @@ android_library("module_installer_java") {
deps = [ deps = [
"//base:base_java", "//base:base_java",
"//base:jni_java",
"//components/crash/android:java", "//components/crash/android:java",
"//third_party/google_android_play_core:com_google_android_play_core_java", "//third_party/google_android_play_core:com_google_android_play_core_java",
] ]
...@@ -29,6 +31,8 @@ android_library("module_installer_java") { ...@@ -29,6 +31,8 @@ android_library("module_installer_java") {
srcjar_deps = [ ":module_installer_build_config" ] srcjar_deps = [ ":module_installer_build_config" ]
jar_excluded_patterns = [ "*/ModuleInstallerConfig.class" ] jar_excluded_patterns = [ "*/ModuleInstallerConfig.class" ]
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
} }
android_library("module_installer_test_java") { android_library("module_installer_test_java") {
...@@ -94,3 +98,29 @@ java_cpp_template("module_installer_apk_build_config") { ...@@ -94,3 +98,29 @@ java_cpp_template("module_installer_apk_build_config") {
] ]
defines = [ "IS_APK_BUILD" ] defines = [ "IS_APK_BUILD" ]
} }
source_set("native") {
sources = [
"module.cc",
]
deps = [
":jni_headers",
"//base",
]
# The method used to load and register JNI for native libraries depends
# heavily on build type.
if (use_native_modules) {
defines = [ "LOAD_FROM_PARTITIONS" ]
} else if (is_component_build) {
defines = [ "LOAD_FROM_COMPONENTS" ]
} else {
defines = [ "LOAD_FROM_BASE_LIBRARY" ]
}
}
generate_jni("jni_headers") {
sources = [
"java/src/org/chromium/components/module_installer/Module.java",
]
}
...@@ -6,6 +6,8 @@ package org.chromium.components.module_installer; ...@@ -6,6 +6,8 @@ package org.chromium.components.module_installer;
import org.chromium.base.StrictModeContext; import org.chromium.base.StrictModeContext;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
...@@ -17,9 +19,12 @@ import java.util.Set; ...@@ -17,9 +19,12 @@ import java.util.Set;
* *
* @param <T> The interface of the module/ * @param <T> The interface of the module/
*/ */
@JNINamespace("module_installer")
public class Module<T> { public class Module<T> {
private static final Set<String> sInstantiatedModuleNames = new HashSet<>(); private static final Set<String> sInstantiatedModuleNames = new HashSet<>();
private static final Set<String> sModulesUninstalledForTesting = new HashSet<>(); private static final Set<String> sModulesUninstalledForTesting = new HashSet<>();
private static final Set<String> sPendingNativeRegistrations = new HashSet<>();
private static boolean sNativeInitialized;
private final String mName; private final String mName;
private final Class<T> mInterfaceClass; private final Class<T> mInterfaceClass;
private final String mImplClassName; private final String mImplClassName;
...@@ -34,13 +39,31 @@ public class Module<T> { ...@@ -34,13 +39,31 @@ public class Module<T> {
sModulesUninstalledForTesting.add(moduleName); sModulesUninstalledForTesting.add(moduleName);
} }
@NativeMethods
interface Natives {
void loadNativeLibrary(String name);
}
/**
* To be called after the main native library has been loaded. Any module instances
* created before the native library is loaded have their native component queued
* for loading and registration. Calling this methed completes that process.
**/
public static void doDeferredNativeRegistrations() {
for (String name : sPendingNativeRegistrations) {
loadNativeLibrary(name);
}
sPendingNativeRegistrations.clear();
sNativeInitialized = true;
}
/** /**
* Instantiates a module. * Instantiates a module.
* *
* @param name The module's name as used with {@link ModuleInstaller}. * @param name The module's name as used with {@link ModuleInstaller}.
* @param interfaceClass {@link Class} object of the module interface. * @param interfaceClass {@link Class} object of the module interface.
* @param implClassName fully qualified class name of the implementation of the module's * @param implClassName fully qualified class name of the implementation of the module's
*interface. * interface.
**/ **/
public Module(String name, Class<T> interfaceClass, String implClassName) { public Module(String name, Class<T> interfaceClass, String implClassName) {
mName = name; mName = name;
...@@ -97,8 +120,24 @@ public class Module<T> { ...@@ -97,8 +120,24 @@ public class Module<T> {
| IllegalArgumentException e) { | IllegalArgumentException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
// Load the module's native library if there's one present, and the Chrome native
// library itself has been loaded.
if (sNativeInitialized) {
loadNativeLibrary(mName);
} else {
sPendingNativeRegistrations.add(mName);
}
} }
return mImpl; return mImpl;
} }
} }
private static void loadNativeLibrary(String name) {
// TODO(https://crbug.com/870055): Whitelist modules, until each module explicitly indicates
// its need for library loading through this system.
if (!"test_dummy".equals(name)) return;
ModuleJni.get().loadNativeLibrary(name);
}
} }
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <dlfcn.h>
#include "base/android/bundle_utils.h"
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "components/module_installer/android/jni_headers/Module_jni.h"
using base::android::BundleUtils;
#if defined(LOAD_FROM_BASE_LIBRARY)
extern "C" {
// These methods are forward-declared here for the build case where
// partitioned libraries are disabled, and module code is pulled into the main
// library. In that case, there is no current way of dlsym()'ing for this
// JNI registration method, and hence it's referred to directly.
// This list could be auto-generated in the future.
extern bool JNI_OnLoad_test_dummy(JNIEnv* env);
} // extern "C"
#endif // LOAD_FROM_BASE_LIBRARY
namespace module_installer {
typedef bool JniRegistrationFunction(JNIEnv* env);
static void JNI_Module_LoadNativeLibrary(
JNIEnv* env,
const base::android::JavaParamRef<jstring>& jtext) {
std::string library_name;
base::android::ConvertJavaStringToUTF8(env, jtext, &library_name);
JniRegistrationFunction* registration_function = nullptr;
#if defined(LOAD_FROM_PARTITIONS) || defined(LOAD_FROM_COMPONENTS)
#if defined(LOAD_FROM_PARTITIONS)
// The partition library must be opened via native code (using
// android_dlopen_ext() under the hood). There is no need to repeat the
// operation on the Java side, because JNI registration is done explicitly
// (hence there is no reason for the Java ClassLoader to be aware of the
// library, for lazy JNI registration).
void* library_handle =
BundleUtils::DlOpenModuleLibraryPartition(library_name);
#else // defined(LOAD_FROM_PARTITIONS)
const std::string lib_name = "lib" + library_name + ".cr.so";
void* library_handle = dlopen(lib_name.c_str(), RTLD_LOCAL);
#endif // defined(LOAD_FROM_PARTITIONS)
CHECK(library_handle != nullptr)
<< "Could not open feature library:" << dlerror();
const std::string registration_name = "JNI_OnLoad_" + library_name;
// Find the module's JNI registration method from the feature library.
void* symbol = dlsym(library_handle, registration_name.c_str());
CHECK(symbol != nullptr) << "Could not find JNI registration method: "
<< dlerror();
registration_function = reinterpret_cast<JniRegistrationFunction*>(symbol);
#else // defined(LOAD_FROM_PARTITIONS) || defined(LOAD_FROM_COMPONENTS)
// TODO(https://crbug.com/870055): Similar to the declarations above, this map
// could be auto-generated.
const std::map<std::string, JniRegistrationFunction*> modules = {
{"test_dummy", JNI_OnLoad_test_dummy}};
registration_function = modules.at(library_name);
#endif // defined(LOAD_FROM_PARTITIONS) || defined(LOAD_FROM_COMPONENTS)
CHECK(registration_function(env))
<< "JNI registration failed: " << library_name;
}
} // namespace module_installer
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment