Cherry-pick "Don't register JNI methods for the android_webview."

> Rather than registering all jni bindings at startup, only get references
> to the class object for those files which require bindings. All others
> are satisfied by exporting symbols which can be found automatically by
> the VM.
>
> BUG=402003
>
> Review URL: https://codereview.chromium.org/454923002

BUG: 16870075
Change-Id: I8d7617c87ac10cb9833cdb370bc6718bd58fd587
diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py
index 1196f76..3f640d8 100755
--- a/base/android/jni_generator/jni_generator.py
+++ b/base/android/jni_generator/jni_generator.py
@@ -805,6 +805,8 @@
     for native in self.natives:
       if native.type != 'method':
         ret += [self.GetForwardDeclaration(native)]
+    if self.options.native_exports and ret:
+      return '\nextern "C" {\n' + "\n".join(ret) + '\n};  // extern "C"'
     return '\n'.join(ret)
 
   def GetConstantFieldsString(self):
@@ -826,6 +828,9 @@
       ret += self.GetEagerCalledByNativeMethodStubs()
     else:
       ret += self.GetLazyCalledByNativeMethodStubs()
+
+    if self.options.native_exports and ret:
+      return '\nextern "C" {\n' + "\n".join(ret) + '\n};  // extern "C"'
     return '\n'.join(ret)
 
   def GetLazyCalledByNativeMethodStubs(self):
@@ -871,6 +876,8 @@
 
   def GetJNINativeMethodsString(self):
     """Returns the implementation of the array of native methods."""
+    if self.options.native_exports:
+      return ''
     template = Template("""\
 static const JNINativeMethod kMethods${JAVA_CLASS}[] = {
 ${KMETHODS}
@@ -925,6 +932,9 @@
 
   def GetRegisterNativesImplString(self):
     """Returns the shared implementation for RegisterNatives."""
+    if self.options.native_exports:
+      return ''
+
     template = Template("""\
   const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS});
 
@@ -949,11 +959,17 @@
   return ${NAMESPACE}RegisterNativesImpl(env, clazz);
 }
 """)
-    fully_qualified_class = self.fully_qualified_class.replace('/', '_')
+
+    if self.options.native_exports:
+      java_name = JniParams.RemapClassName(self.fully_qualified_class)
+      java_name = java_name.replace('_', '_1').replace('/', '_')
+    else:
+      java_name = self.fully_qualified_class.replace('/', '_')
+
     namespace = ''
     if self.namespace:
       namespace = self.namespace + '::'
-    values = {'FULLY_QUALIFIED_CLASS': fully_qualified_class,
+    values = {'FULLY_QUALIFIED_CLASS': java_name,
               'INIT_NATIVE_NAME': 'native' + self.init_native.name,
               'NAMESPACE': namespace,
               'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString()
@@ -1007,23 +1023,52 @@
         for param in called_by_native.params])
 
   def GetForwardDeclaration(self, native):
-    template = Template("""
+    template_str = """
 static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS});
-""")
+"""
+    if self.options.native_exports:
+      template_str += """
+__attribute__((visibility("default")))
+${RETURN} Java_${JAVA_NAME}_native${NAME}(JNIEnv* env, ${PARAMS}) {
+  return ${NAME}(${PARAMS_IN_CALL});
+}
+"""
+    template = Template(template_str)
+    params_in_call = []
+    if not self.options.pure_native_methods:
+      params_in_call = ['env', 'jcaller']
+    params_in_call = ', '.join(params_in_call + [p.name for p in native.params])
+
+    java_name = JniParams.RemapClassName(self.fully_qualified_class)
+    java_name = java_name.replace('_', '_1').replace('/', '_')
+    if native.java_class_name:
+      java_name += '_00024' + native.java_class_name
+
     values = {'RETURN': JavaDataTypeToC(native.return_type),
               'NAME': native.name,
-              'PARAMS': self.GetParamsInDeclaration(native)}
+              'JAVA_NAME': java_name,
+              'PARAMS': self.GetParamsInDeclaration(native),
+              'PARAMS_IN_CALL': params_in_call}
     return template.substitute(values)
 
   def GetNativeMethodStubString(self, native):
     """Returns stubs for native methods."""
-    template = Template("""\
-static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {
+    if self.options.native_exports:
+      template_str = """\
+__attribute__((visibility("default")))
+${RETURN} Java_${JAVA_NAME}_native${NAME}(JNIEnv* env,
+    ${PARAMS_IN_DECLARATION}) {"""
+    else:
+      template_str = """\
+static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) {"""
+    template_str += """
   ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
   CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN});
   return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL};
 }
-""")
+"""
+
+    template = Template(template_str)
     params = []
     if not self.options.pure_native_methods:
       params = ['env', 'jcaller']
@@ -1036,9 +1081,19 @@
     post_call = ''
     if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type):
       post_call = '.Release()'
+
+    if self.options.native_exports:
+      java_name = JniParams.RemapClassName(self.fully_qualified_class)
+      java_name = java_name.replace('_', '_1').replace('/', '_')
+      if native.java_class_name:
+        java_name += '_00024' + native.java_class_name
+    else:
+      java_name = ''
+
     values = {
         'RETURN': return_type,
         'OPTIONAL_ERROR_RETURN': optional_error_return,
+        'JAVA_NAME': java_name,
         'NAME': native.name,
         'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native),
         'PARAM0_NAME': native.params[0].name,
@@ -1186,8 +1241,12 @@
 const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""")
     native_classes = self.GetUniqueClasses(self.natives)
     called_by_native_classes = self.GetUniqueClasses(self.called_by_natives)
-    all_classes = native_classes
-    all_classes.update(called_by_native_classes)
+    if self.options.native_exports:
+      all_classes = called_by_native_classes
+    else:
+      all_classes = native_classes
+      all_classes.update(called_by_native_classes)
+
     for clazz in all_classes:
       values = {
           'JAVA_CLASS': clazz,
@@ -1406,6 +1465,9 @@
                            help='The path to cpp command.')
   option_parser.add_option('--javap', default='javap',
                            help='The path to javap command.')
+  option_parser.add_option('--native_exports', action='store_true',
+                           help='Native method registration through .so '
+                           'exports.')
   options, args = option_parser.parse_args(argv)
   if options.jar_file:
     input_file = ExtractJarInputFile(options.jar_file, options.input_file,
diff --git a/base/android/jni_generator/jni_generator_tests.py b/base/android/jni_generator/jni_generator_tests.py
index 7db9a10..a7ce537 100755
--- a/base/android/jni_generator/jni_generator_tests.py
+++ b/base/android/jni_generator/jni_generator_tests.py
@@ -42,7 +42,7 @@
     self.eager_called_by_natives = False
     self.cpp = 'cpp'
     self.javap = 'javap'
-
+    self.native_exports = False
 
 class TestGenerator(unittest.TestCase):
   def assertObjEquals(self, first, second):
@@ -1005,6 +1005,45 @@
         test_data, 'org/chromium/example/jni_generator/Test', options)
     self.assertGoldenTextEquals(jni_from_java.GetContent())
 
+  def testNativeExportsOption(self):
+    test_data = """
+    package org.chromium.example.jni_generator;
+
+    /** The pointer to the native Test. */
+    long nativeTest;
+
+    class Test {
+        private static native boolean nativeInitNativeClass();
+        private static native int nativeStaticMethod(long nativeTest, int arg1);
+        private native int nativeMethod(long nativeTest, int arg1);
+        @CalledByNative
+        private void testMethodWithParam(int iParam);
+        @CalledByNative
+        private String testMethodWithParamAndReturn(int iParam);
+        @CalledByNative
+        private static int testStaticMethodWithParam(int iParam);
+        @CalledByNative
+        private static double testMethodWithNoParam();
+        @CalledByNative
+        private static String testStaticMethodWithNoParam();
+
+        class MyInnerClass {
+          @NativeCall("MyInnerClass")
+          private native int nativeInit();
+        }
+        class MyOtherInnerClass {
+          @NativeCall("MyOtherInnerClass")
+          private native int nativeInit();
+        }
+    }
+    """
+    options = TestOptions()
+    options.jni_init_native_name = 'nativeInitNativeClass'
+    options.native_exports = True
+    jni_from_java = jni_generator.JNIFromJavaSource(
+        test_data, 'org/chromium/example/jni_generator/SampleForTests', options)
+    self.assertGoldenTextEquals(jni_from_java.GetContent())
+
   def testOuterInnerRaises(self):
     test_data = """
     package org.chromium.media;
diff --git a/base/android/jni_generator/testNativeExportsOption.golden b/base/android/jni_generator/testNativeExportsOption.golden
new file mode 100644
index 0000000..231be7c
--- /dev/null
+++ b/base/android/jni_generator/testNativeExportsOption.golden
@@ -0,0 +1,216 @@
+// Copyright 2014 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.
+
+// This file is autogenerated by
+//     base/android/jni_generator/jni_generator.py
+// For
+//     org/chromium/example/jni_generator/SampleForTests
+
+#ifndef org_chromium_example_jni_generator_SampleForTests_JNI
+#define org_chromium_example_jni_generator_SampleForTests_JNI
+
+#include <jni.h>
+
+#include "base/android/jni_generator/jni_generator_helper.h"
+
+#include "base/android/jni_int_wrapper.h"
+
+// Step 1: forward declarations.
+namespace {
+const char kSampleForTestsClassPath[] =
+    "org/chromium/example/jni_generator/SampleForTests";
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+jclass g_SampleForTests_clazz = NULL;
+
+}  // namespace
+
+extern "C" {
+
+static jint Init(JNIEnv* env, jobject jcaller);
+
+__attribute__((visibility("default")))
+jint
+    Java_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_nativeInit(JNIEnv*
+    env, jobject jcaller) {
+  return Init(env, jcaller);
+}
+
+static jint Init(JNIEnv* env, jobject jcaller);
+
+__attribute__((visibility("default")))
+jint
+    Java_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_nativeInit(JNIEnv*
+    env, jobject jcaller) {
+  return Init(env, jcaller);
+}
+
+};  // extern "C"
+
+// Step 2: method stubs.
+
+extern "C" {
+__attribute__((visibility("default")))
+jint
+    Java_org_chromium_example_jni_1generator_SampleForTests_nativeStaticMethod(JNIEnv*
+    env,
+    jobject jcaller,
+    jlong nativeTest,
+    jint arg1) {
+  Test* native = reinterpret_cast<Test*>(nativeTest);
+  CHECK_NATIVE_PTR(env, jcaller, native, "StaticMethod", 0);
+  return native->StaticMethod(env, jcaller, arg1);
+}
+
+__attribute__((visibility("default")))
+jint
+    Java_org_chromium_example_jni_1generator_SampleForTests_nativeMethod(JNIEnv*
+    env,
+    jobject jcaller,
+    jlong nativeTest,
+    jint arg1) {
+  Test* native = reinterpret_cast<Test*>(nativeTest);
+  CHECK_NATIVE_PTR(env, jcaller, native, "Method", 0);
+  return native->Method(env, jcaller, arg1);
+}
+
+static base::subtle::AtomicWord g_SampleForTests_testMethodWithParam = 0;
+static void Java_SampleForTests_testMethodWithParam(JNIEnv* env, jobject obj,
+    JniIntWrapper iParam) {
+  /* Must call RegisterNativesImpl()  */
+  CHECK_CLAZZ(env, obj,
+      g_SampleForTests_clazz);
+  jmethodID method_id =
+      base::android::MethodID::LazyGet<
+      base::android::MethodID::TYPE_INSTANCE>(
+      env, g_SampleForTests_clazz,
+      "testMethodWithParam",
+
+"("
+"I"
+")"
+"V",
+      &g_SampleForTests_testMethodWithParam);
+
+     env->CallVoidMethod(obj,
+          method_id, as_jint(iParam));
+  jni_generator::CheckException(env);
+
+}
+
+static base::subtle::AtomicWord g_SampleForTests_testMethodWithParamAndReturn =
+    0;
+static base::android::ScopedJavaLocalRef<jstring>
+    Java_SampleForTests_testMethodWithParamAndReturn(JNIEnv* env, jobject obj,
+    JniIntWrapper iParam) {
+  /* Must call RegisterNativesImpl()  */
+  CHECK_CLAZZ(env, obj,
+      g_SampleForTests_clazz, NULL);
+  jmethodID method_id =
+      base::android::MethodID::LazyGet<
+      base::android::MethodID::TYPE_INSTANCE>(
+      env, g_SampleForTests_clazz,
+      "testMethodWithParamAndReturn",
+
+"("
+"I"
+")"
+"Ljava/lang/String;",
+      &g_SampleForTests_testMethodWithParamAndReturn);
+
+  jstring ret =
+      static_cast<jstring>(env->CallObjectMethod(obj,
+          method_id, as_jint(iParam)));
+  jni_generator::CheckException(env);
+  return base::android::ScopedJavaLocalRef<jstring>(env, ret);
+}
+
+static base::subtle::AtomicWord g_SampleForTests_testStaticMethodWithParam = 0;
+static jint Java_SampleForTests_testStaticMethodWithParam(JNIEnv* env,
+    JniIntWrapper iParam) {
+  /* Must call RegisterNativesImpl()  */
+  CHECK_CLAZZ(env, g_SampleForTests_clazz,
+      g_SampleForTests_clazz, 0);
+  jmethodID method_id =
+      base::android::MethodID::LazyGet<
+      base::android::MethodID::TYPE_STATIC>(
+      env, g_SampleForTests_clazz,
+      "testStaticMethodWithParam",
+
+"("
+"I"
+")"
+"I",
+      &g_SampleForTests_testStaticMethodWithParam);
+
+  jint ret =
+      env->CallStaticIntMethod(g_SampleForTests_clazz,
+          method_id, as_jint(iParam));
+  jni_generator::CheckException(env);
+  return ret;
+}
+
+static base::subtle::AtomicWord g_SampleForTests_testMethodWithNoParam = 0;
+static jdouble Java_SampleForTests_testMethodWithNoParam(JNIEnv* env) {
+  /* Must call RegisterNativesImpl()  */
+  CHECK_CLAZZ(env, g_SampleForTests_clazz,
+      g_SampleForTests_clazz, 0);
+  jmethodID method_id =
+      base::android::MethodID::LazyGet<
+      base::android::MethodID::TYPE_STATIC>(
+      env, g_SampleForTests_clazz,
+      "testMethodWithNoParam",
+
+"("
+")"
+"D",
+      &g_SampleForTests_testMethodWithNoParam);
+
+  jdouble ret =
+      env->CallStaticDoubleMethod(g_SampleForTests_clazz,
+          method_id);
+  jni_generator::CheckException(env);
+  return ret;
+}
+
+static base::subtle::AtomicWord g_SampleForTests_testStaticMethodWithNoParam =
+    0;
+static base::android::ScopedJavaLocalRef<jstring>
+    Java_SampleForTests_testStaticMethodWithNoParam(JNIEnv* env) {
+  /* Must call RegisterNativesImpl()  */
+  CHECK_CLAZZ(env, g_SampleForTests_clazz,
+      g_SampleForTests_clazz, NULL);
+  jmethodID method_id =
+      base::android::MethodID::LazyGet<
+      base::android::MethodID::TYPE_STATIC>(
+      env, g_SampleForTests_clazz,
+      "testStaticMethodWithNoParam",
+
+"("
+")"
+"Ljava/lang/String;",
+      &g_SampleForTests_testStaticMethodWithNoParam);
+
+  jstring ret =
+      static_cast<jstring>(env->CallStaticObjectMethod(g_SampleForTests_clazz,
+          method_id));
+  jni_generator::CheckException(env);
+  return base::android::ScopedJavaLocalRef<jstring>(env, ret);
+}
+};  // extern "C"
+
+// Step 3: RegisterNatives.
+
+static bool RegisterNativesImpl(JNIEnv* env, jclass clazz) {
+  g_SampleForTests_clazz = static_cast<jclass>(env->NewWeakGlobalRef(clazz));
+
+  return true;
+}
+
+extern "C" JNIEXPORT bool JNICALL
+Java_org_chromium_example_jni_1generator_SampleForTests_nativeInitNativeClass(JNIEnv*
+    env, jclass clazz) {
+  return RegisterNativesImpl(env, clazz);
+}
+
+#endif  // org_chromium_example_jni_generator_SampleForTests_JNI
diff --git a/build/android/android_exports.lst b/build/android/android_exports.lst
new file mode 100644
index 0000000..820d6ec
--- /dev/null
+++ b/build/android/android_exports.lst
@@ -0,0 +1,14 @@
+# Copyright 2014 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.
+
+# Default exports specification for chromium shared libraries on android.
+# Check ld version script manual:
+# https://sourceware.org/binutils/docs-2.24/ld/VERSION.html#VERSION
+
+{
+  global: 
+    Java_*_native*;
+    JNI_OnLoad;
+  local: *;
+};
diff --git a/build/common.gypi b/build/common.gypi
index cbfbe5d..e51e5cf 100644
--- a/build/common.gypi
+++ b/build/common.gypi
@@ -1743,6 +1743,9 @@
 
         # Copy it out one scope.
         'android_webview_build%': '<(android_webview_build)',
+
+        # Default android linker script for shared library exports.
+        'android_linker_script%': '<(SHARED_INTERMEDIATE_DIR)/android_exports.lst',
       }],  # OS=="android"
       ['android_webview_build==1', {
         # When building the WebView in the Android tree, jarjar will remap all
@@ -4201,7 +4204,7 @@
         },
         'target_conditions': [
           ['_type=="shared_library"', {
-           'product_extension': '<(android_product_extension)',
+            'product_extension': '<(android_product_extension)',
           }],
 
           # Settings for building device targets using Android's toolchain.
@@ -4267,8 +4270,6 @@
             'ldflags': [
               '-nostdlib',
               '-Wl,--no-undefined',
-              # Don't export symbols from statically linked libraries.
-              '-Wl,--exclude-libs=ALL',
             ],
             'libraries': [
               '-l<(android_stlport_library)',
@@ -4279,8 +4280,8 @@
               '-lm',
             ],
             'conditions': [
-              ['component=="shared_library"', {
-                'ldflags!': [
+              ['component=="static_library"', {
+                'ldflags': [
                   '-Wl,--exclude-libs=ALL',
                 ],
               }],
diff --git a/build/jar_file_jni_generator.gypi b/build/jar_file_jni_generator.gypi
index dc43c49..0c989b5 100644
--- a/build/jar_file_jni_generator.gypi
+++ b/build/jar_file_jni_generator.gypi
@@ -26,6 +26,7 @@
     'jni_generator_includes%': (
         'base/android/jni_generator/jni_generator_helper.h'
     ),
+    'native_exports%': '',
   },
   'actions': [
     {
@@ -54,6 +55,7 @@
         '<(jni_generator_includes)',
         '--optimize_generation',
         '<(optimize_jni_generation)',
+        '<(native_exports)',
       ],
       'message': 'Generating JNI bindings from  <(input_jar_file)/<(input_java_class)',
       'process_outputs_as_sources': 1,
@@ -62,4 +64,27 @@
   # This target exports a hard dependency because it generates header
   # files.
   'hard_dependency': 1,
+  'conditions': [
+    ['android_webview_build==1', {
+      'variables': {
+        'native_exports%': '--native_exports',
+      },
+      'dependencies': [
+        '<(DEPTH)/build/linker_script_copy.gyp:linker_script_copy',
+      ],
+      'conditions': [
+        ['component=="static_library"', {
+          'link_settings': {
+            'ldflags': [
+              # Only export symbols that are specified in version script.
+              '-Wl,--version-script=<(android_linker_script)',
+            ],
+            'ldflags!': [
+              '-Wl,--exclude-libs=ALL',
+            ],
+          },
+        }],
+      ],
+    }],
+  ],
 }
diff --git a/build/jni_generator.gypi b/build/jni_generator.gypi
index da99331..48ec24e 100644
--- a/build/jni_generator.gypi
+++ b/build/jni_generator.gypi
@@ -36,7 +36,11 @@
     'jni_generator_includes%': (
         'base/android/jni_generator/jni_generator_helper.h'
     ),
+    'native_exports%': '',
   },
+  'dependencies': [
+    '<(DEPTH)/build/linker_script_copy.gyp:linker_script_copy',
+  ],
   'rules': [
     {
       'rule_name': 'generate_jni_headers',
@@ -61,6 +65,7 @@
         '<(jni_generator_jarjar_file)',
         '--ptr_type',
         '<(jni_generator_ptr_type)',
+        '<(native_exports)',
       ],
       'message': 'Generating JNI bindings from <(RULE_INPUT_PATH)',
       'process_outputs_as_sources': 1,
@@ -81,4 +86,28 @@
   # This target exports a hard dependency because it generates header
   # files.
   'hard_dependency': 1,
+  'conditions': [
+    ['android_webview_build==1', {
+      'variables': {
+        'native_exports%': '--native_exports',
+      },
+      'dependencies': [
+        '<(DEPTH)/build/linker_script_copy.gyp:linker_script_copy',
+      ],
+      'conditions': [
+        ['component=="static_library"', {
+          'link_settings': {
+            'ldflags': [
+              # Only export symbols that are specified in version script.
+              '-Wl,--version-script=<(android_linker_script)',
+            ],
+            'ldflags!': [
+              '-Wl,--exclude-libs=ALL',
+            ],
+          },
+        }],
+      ],
+    }],
+  ],
 }
+
diff --git a/build/linker_script_copy.gyp b/build/linker_script_copy.gyp
new file mode 100644
index 0000000..8962725
--- /dev/null
+++ b/build/linker_script_copy.gyp
@@ -0,0 +1,26 @@
+# Copyright 2014 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.
+
+{
+  'targets': [
+    {
+      'target_name': 'linker_script_copy',
+      'type': 'none',
+      'inputs': [
+        '<(DEPTH)/build/android/android_exports.lst',
+      ],
+      'outputs': [
+        '<(android_linker_script)',
+      ],
+      'copies': [
+        {
+          'destination': '<(SHARED_INTERMEDIATE_DIR)',
+          'files': [
+            '<@(_inputs)',
+         ],
+        },
+      ],
+    },
+  ],
+}