Auto-load native libraries of a module (reland)

This gets rid of the whitelist in Module.java. Going forward DFM authors
simply have to specify native_deps in the module_desc and the native
library will be automatically loaded when first accessing the module
(when using the autogenerated module class).

See go/native-dfm-load-v2 for more context and details.

This is a reland of 06c7b1cdbba6a581ef5f0fbd2712a9d0799d6acc No changes
have been made. 06c7b1cdbba6a581ef5f0fbd2712a9d0799d6acc was reverted
due to the revert of a9dcd64a13a3b0c0522f6fbd26e079e4874869b7 and that
CL has been fixed in c9901c6af04f9ee9e176e4a4b8c130c1998b8a58.

TBR: [email protected],[email protected]
Bug: 870055
Change-Id: Ib7939f43f2e3ca758c8ab039f91d6f78b6d84f22
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1891434
Reviewed-by: Tibor Goldschwendt <[email protected]>
Reviewed-by: Christopher Grant <[email protected]>
Cr-Commit-Position: refs/heads/master@{#711460}
diff --git a/chrome/android/chrome_common_shared_library.gni b/chrome/android/chrome_common_shared_library.gni
index cd407251..0927f56 100644
--- a/chrome/android/chrome_common_shared_library.gni
+++ b/chrome/android/chrome_common_shared_library.gni
@@ -49,8 +49,8 @@
   generate_linker_version_script(_linker_script_target) {
     linker_script = _linker_script
     export_java_symbols = _export_java_symbols
+    export_feature_registrations = true
     if (_generate_partitions) {
-      export_feature_registrations = true
       export_symbol_whitelist_files = []
       foreach(_module_desc, invoker.module_descs) {
         if (defined(_module_desc.native_entrypoints)) {
diff --git a/chrome/android/features/vr/vr_module.gni b/chrome/android/features/vr/vr_module.gni
index 5548640..87d57532 100644
--- a/chrome/android/features/vr/vr_module.gni
+++ b/chrome/android/features/vr/vr_module.gni
@@ -11,8 +11,6 @@
   name = "vr"
   java_deps = [ "//chrome/android/features/vr:java" ]
   android_manifest = "//chrome/android/features/vr/java/AndroidManifest.xml"
-  if (use_native_partitions) {
-    native_deps = [ "//chrome/browser/vr:vr_ui" ]
-    native_entrypoints = "//chrome/browser/vr/module_exports.lst"
-  }
+  native_deps = [ "//chrome/browser/vr:vr_ui" ]
+  native_entrypoints = "//chrome/browser/vr/module_exports.lst"
 }
diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn
index 387034a0..8192a811 100644
--- a/chrome/browser/vr/BUILD.gn
+++ b/chrome/browser/vr/BUILD.gn
@@ -201,6 +201,10 @@
     cflags = [ "-fsymbol-partition=libvr.so" ]
   }
 
+  if (is_android) {
+    sources += [ "jni_onload.cc" ]
+  }
+
   defines = [ "VR_UI_IMPLEMENTATION" ]
 
   if (use_command_buffer) {
diff --git a/chrome/browser/vr/jni_onload.cc b/chrome/browser/vr/jni_onload.cc
new file mode 100644
index 0000000..817645352
--- /dev/null
+++ b/chrome/browser/vr/jni_onload.cc
@@ -0,0 +1,13 @@
+// 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 "base/android/jni_generator/jni_generator_helper.h"
+
+// This method is required by the module loading backend. And it is supposed to
+// register VR's native JNI methods. However, since VR's Android-specific native
+// code still lives in the base module, VR's JNI registration is invoked
+// manually. Therefore, this function does nothing.
+JNI_GENERATOR_EXPORT bool JNI_OnLoad_vr(JNIEnv* env) {
+  return true;
+}
diff --git a/components/module_installer/android/java/src/org/chromium/components/module_installer/builder/Module.java b/components/module_installer/android/java/src/org/chromium/components/module_installer/builder/Module.java
index c27bd09..33ec630 100644
--- a/components/module_installer/android/java/src/org/chromium/components/module_installer/builder/Module.java
+++ b/components/module_installer/android/java/src/org/chromium/components/module_installer/builder/Module.java
@@ -132,22 +132,18 @@
     private static void loadNative(String name) {
         // Can only initialize native once per lifetime of Chrome.
         if (sInitializedModules.contains(name)) return;
-        // TODO(crbug.com/870055): Use |libraries| instead of whitelist to load
-        // native libraries.
         String[] libraries = loadModuleDescriptor(name).getLibraries();
         // TODO(crbug.com/986960): Automatically determine if module has native
         // resources instead of whitelisting.
-        boolean loadLibrary = false;
         boolean loadResources = false;
         if ("test_dummy".equals(name)) {
-            loadLibrary = true;
             loadResources = true;
         }
         if ("dev_ui".equals(name)) {
             loadResources = true;
         }
-        if (loadLibrary || loadResources) {
-            ModuleJni.get().loadNative(name, loadLibrary, loadResources);
+        if (libraries.length > 0 || loadResources) {
+            ModuleJni.get().loadNative(name, libraries, loadResources);
         }
         sInitializedModules.add(name);
     }
@@ -195,6 +191,6 @@
 
     @NativeMethods
     interface Natives {
-        void loadNative(String name, boolean loadLibrary, boolean loadResources);
+        void loadNative(String name, String[] libraries, boolean loadResources);
     }
 }
diff --git a/components/module_installer/android/module.cc b/components/module_installer/android/module.cc
index 73be75e..b51e91f 100644
--- a/components/module_installer/android/module.cc
+++ b/components/module_installer/android/module.cc
@@ -7,6 +7,7 @@
 
 #include "base/android/bundle_utils.h"
 #include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
 #include "base/logging.h"
 #include "base/strings/utf_string_conversions.h"
@@ -40,8 +41,8 @@
 
 typedef bool JniRegistrationFunction(JNIEnv* env);
 
-void LoadLibrary(JNIEnv* env, const std::string& library_name) {
-  JniRegistrationFunction* registration_function = nullptr;
+void* LoadLibrary(const std::string& library_name) {
+  void* library_handle = nullptr;
 
 #if defined(LOAD_FROM_PARTITIONS)
   // The partition library must be opened via native code (using
@@ -49,25 +50,28 @@
   // 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);
+  library_handle = BundleUtils::DlOpenModuleLibraryPartition(library_name);
 #elif defined(COMPONENT_BUILD)
-  const std::string lib_name = "lib" + library_name + ".cr.so";
-  void* library_handle = dlopen(lib_name.c_str(), RTLD_LOCAL);
+  const std::string lib_name = "lib" + library_name + ".so";
+  library_handle = dlopen(lib_name.c_str(), RTLD_LOCAL);
 #else
 #error "Unsupported configuration."
 #endif  // defined(COMPONENT_BUILD)
   CHECK(library_handle != nullptr)
-      << "Could not open feature library:" << dlerror();
+      << "Could not open feature library: " << dlerror();
 
-  const std::string registration_name = "JNI_OnLoad_" + library_name;
+  return library_handle;
+}
+
+void RegisterJni(JNIEnv* env, void* library_handle, const std::string& name) {
+  const std::string registration_name = "JNI_OnLoad_" + 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);
-  CHECK(registration_function(env))
-      << "JNI registration failed: " << library_name;
+  CHECK(symbol) << "Could not find JNI registration method '"
+                << registration_name << "' for '" << name << "': " << dlerror();
+  auto* registration_function =
+      reinterpret_cast<JniRegistrationFunction*>(symbol);
+  CHECK(registration_function(env)) << "JNI registration failed: " << name;
 }
 
 void LoadResources(const std::string& name) {
@@ -80,12 +84,22 @@
 static void JNI_Module_LoadNative(
     JNIEnv* env,
     const base::android::JavaParamRef<jstring>& jname,
-    jboolean load_library,
+    const base::android::JavaParamRef<jobjectArray>& jlibraries,
     jboolean load_resources) {
   std::string name;
   base::android::ConvertJavaStringToUTF8(env, jname, &name);
-  if (load_library) {
-    LoadLibrary(env, name);
+  std::vector<std::string> libraries;
+  base::android::AppendJavaStringArrayToStringVector(env, jlibraries,
+                                                     &libraries);
+  if (libraries.size() > 0) {
+    void* library_handle = nullptr;
+    for (const auto& library : libraries) {
+      library_handle = LoadLibrary(library);
+    }
+    // module libraries are ordered such that the root library will be the last
+    // item in the list. We expect this library to provide the JNI registration
+    // function.
+    RegisterJni(env, library_handle, name);
   }
   if (load_resources) {
     LoadResources(name);