Commit 9b660407 authored by Clark DuVall's avatar Clark DuVall Committed by Commit Bot

[WebLayer] Use crazy linker for M- and "/." trick for N+

Instead of copying libraries on M-, this changes to using the crazy
linker, which loads the library without any knowledge of what libraries
have been loaded by the system linker. This requires manual JNI
registration. To make sure we only register WebLayer JNI when needed,
a minimal set of JNI methods is registered, and the full set is
registered later if we require WebView compatibility.

In N+, we switch to prepending "/." to library paths instead of using
symlinks, which allows us to avoid writing any files to the apps data
directory. This simplifies the interface quite a bit, allowing
initializeWebViewCompatibilityMode() to be sync instead of async. I will
follow up with the Android team to make sure we have a better solution
for future versions of Android.

The API has not been used in the client app yet, so it should be safe
to remove M- support for the copy library approach in the
implementation. The old version of initializeWebViewCompatibilityMode()
was kept so the WebLayer API version compat tests would still pass.

TBR=agrieve@chromium.org

Bug: 1051358
Change-Id: I2cd5ae0e025feaeb5c41cf9ddfafc91cd09fbad7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2090024
Commit-Queue: Clark DuVall <cduvall@chromium.org>
Reviewed-by: default avatarRichard Coles <torne@chromium.org>
Cr-Commit-Position: refs/heads/master@{#749973}
parent 43548e91
......@@ -63,6 +63,6 @@ source_set("webview_entry_point") {
if (webview_includes_weblayer) {
defines = [ "WEBVIEW_INCLUDES_WEBLAYER" ]
deps += [ "//weblayer:weblayer_lib" ]
deps += [ "//weblayer:weblayer_lib_webview" ]
}
}
......@@ -32,6 +32,10 @@ bool NativeInit(base::android::LibraryProcessType library_process_type) {
// Most of the initialization is done in LibraryLoadedOnMainThread(), not here.
JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
base::android::InitVM(vm);
#if defined(WEBVIEW_INCLUDES_WEBLAYER)
if (!weblayer::RegisterMinimalNatives())
return -1;
#endif
base::android::SetNativeInitializationHook(&NativeInit);
return JNI_VERSION_1_4;
}
......@@ -132,6 +132,11 @@ template("system_webview_apk_or_module_tmpl") {
"//third_party/crashpad/crashpad/handler:crashpad_handler_trampoline",
]
loadable_modules = [ "$root_out_dir/libcrashpad_handler_trampoline.so" ]
if (webview_includes_weblayer) {
deps += [ "//base/android/linker:chromium_android_linker" ]
loadable_modules +=
[ "$root_out_dir/libchromium_android_linker$shlib_extension" ]
}
}
if (_include_secondary_support) {
_trampoline = "//third_party/crashpad/crashpad/handler:crashpad_handler_trampoline($android_secondary_abi_toolchain)"
......@@ -142,6 +147,11 @@ template("system_webview_apk_or_module_tmpl") {
_secondary_out_dir = get_label_info(_trampoline, "root_out_dir")
secondary_abi_loadable_modules =
[ "$_secondary_out_dir/libcrashpad_handler_trampoline.so" ]
if (webview_includes_weblayer) {
deps += [ "//base/android/linker:chromium_android_linker($android_secondary_abi_toolchain)" ]
secondary_abi_loadable_modules +=
[ "$_secondary_out_dir/libchromium_android_linker$shlib_extension" ]
}
}
if (!_use_trichrome_library || android_64bit_target_cpu) {
......
......@@ -124,6 +124,9 @@ public class LibraryLoader {
// Whether the configuration has been set.
private boolean mConfigurationSet;
// Runs after all native libraries have been loaded.
private Runnable mPostLoadHook;
@IntDef({LoadState.NOT_LOADED, LoadState.MAIN_DEX_LOADED, LoadState.LOADED})
@Retention(RetentionPolicy.SOURCE)
private @interface LoadState {
......@@ -147,10 +150,6 @@ public class LibraryLoader {
// will be reported via UMA. Set once when the libraries are done loading.
private long mLibraryLoadTimeMs;
// The suffix to add when loading the library. This is useful if a library with the same name is
// loaded in the same process, as can be the case with WebView/WebLayer.
private String mLibrarySuffix = "";
/**
* Call this method to determine if the chromium project must load the library
* directly from a zip file.
......@@ -186,13 +185,10 @@ public class LibraryLoader {
mLibraryProcessType = type;
}
/**
* Sets a suffix to use when loading the library. This will be appended to the library names
* for calls to System.loadLibrary().
*/
public void setLibrarySuffix(String suffix) {
/** Sets a function to run after all native libraries have been loaded. */
public void setPostLoadHook(Runnable postLoadHook) {
assert mLoadState == LoadState.NOT_LOADED;
mLibrarySuffix = suffix;
mPostLoadHook = postLoadHook;
}
/**
......@@ -492,7 +488,7 @@ public class LibraryLoader {
// Load libraries using the system linker.
for (String library : NativeLibraries.LIBRARIES) {
if (!isInZipFile()) {
System.loadLibrary(library + mLibrarySuffix);
System.loadLibrary(library);
} else {
// Load directly from the APK.
boolean is64Bit = ApiHelperForM.isProcess64Bit();
......@@ -540,6 +536,10 @@ public class LibraryLoader {
} catch (UnsatisfiedLinkError e) {
throw new ProcessInitException(LoaderErrors.NATIVE_LIBRARY_LOAD_FAILED, e);
}
if (mPostLoadHook != null) {
mPostLoadHook.run();
}
}
@VisibleForTesting
......
......@@ -61,6 +61,7 @@ void SetNativeInitializationHook(
void SetNonMainDexJniRegistrationHook(
NonMainDexJniRegistrationHook jni_registration_hook) {
DCHECK(!g_jni_registration_hook);
g_jni_registration_hook = jni_registration_hook;
}
......
......@@ -40,6 +40,7 @@ CHROME_SPECIFIC = BuildFileMatchRegex(
# WebView specific files which are not in Monochrome.apk
WEBVIEW_SPECIFIC = BuildFileMatchRegex(
r'lib/.*/libwebviewchromium\.so',
r'lib/.*/libchromium_android_linker\.so',
r'assets/webview_licenses.notice',
r'res/.*/icon_webview(.webp)?',
r'META-INF/.*',
......
......@@ -86,7 +86,7 @@ if (is_android) {
}
}
static_library("weblayer_lib") {
source_set("weblayer_lib_base") {
sources = [
"app/content_main_delegate_impl.cc",
"app/content_main_delegate_impl.h",
......@@ -303,6 +303,7 @@ static_library("weblayer_lib") {
if (is_android) {
sources += [
"$target_gen_dir/browser/java/weblayer_minimal_jni_registration.h",
"app/jni_onload.cc",
"app/jni_onload.h",
"browser/android/exception_filter.cc",
......@@ -344,13 +345,16 @@ static_library("weblayer_lib") {
"//android_webview:generate_aw_strings",
"//components/android_system_error_page",
"//components/autofill/android:provider",
"//components/crash/android:crash_android",
"//components/crash/android:crashpad_main",
"//components/embedder_support/android:context_menu",
"//components/embedder_support/android:util",
"//components/embedder_support/android:web_contents_delegate",
"//components/embedder_support/android/metrics",
"//components/javascript_dialogs",
"//components/metrics",
"//components/minidump_uploader",
"//components/permissions/android:native",
"//components/safe_browsing/content/common:interfaces",
"//components/safe_browsing/content/renderer:throttles",
"//components/safe_browsing/core/common",
......@@ -358,6 +362,7 @@ static_library("weblayer_lib") {
"//services/resource_coordinator/public/cpp/memory_instrumentation:browser",
"//ui/android",
"//weblayer/browser/java:jni",
"//weblayer/browser/java:weblayer_minimal_jni_registration",
"//weblayer/browser/safe_browsing:safe_browsing",
]
} else {
......@@ -399,6 +404,45 @@ static_library("weblayer_lib") {
}
}
if (is_android) {
# Lib used in standalone WebView which allows manual JNI registration.
static_library("weblayer_lib_webview") {
public_deps = [ ":weblayer_lib_base" ]
deps = [
"//base",
"//weblayer/browser/java:jni",
"//weblayer/browser/java:weblayer_jni_registration",
]
sources = [
"$target_gen_dir/browser/java/weblayer_jni_registration.h",
"browser/web_view_compatibility_helper_impl.cc",
]
defines = [ "WEBLAYER_MANUAL_JNI_REGISTRATION" ]
# Explicit dependency required for JNI registration to be able to
# find the native side functions.
if (is_component_build) {
deps += [
"//device/gamepad",
"//media/midi",
"//ui/events/devices",
]
}
}
# Lib used in Monochrome which does not support manual JNI registration.
# Separate from the standalone WebView version to reduce APK size.
static_library("weblayer_lib") {
public_deps = [ ":weblayer_lib_base" ]
deps = [ "//weblayer/browser/java:jni" ]
sources = [ "browser/web_view_compatibility_helper_impl.cc" ]
}
} else {
static_library("weblayer_lib") {
public_deps = [ ":weblayer_lib_base" ]
}
}
grit("resources") {
source = "weblayer_resources.grd"
......@@ -420,7 +464,7 @@ if (is_android) {
shared_library("libweblayer") {
sources = [ "app/entry_point.cc" ]
deps = [
":weblayer_lib",
":weblayer_lib_webview",
"//base",
"//content/public/app:both",
]
......
......@@ -9,6 +9,7 @@
#include "content/public/app/content_jni_onload.h"
#include "content/public/app/content_main.h"
#include "weblayer/app/content_main_delegate_impl.h"
#include "weblayer/browser/java/weblayer_minimal_jni_registration.h"
namespace weblayer {
......@@ -34,4 +35,9 @@ bool OnJNIOnLoadInit() {
return true;
}
bool RegisterMinimalNatives() {
return weblayer_minimal::RegisterMainDexNatives(
base::android::AttachCurrentThread());
}
} // namespace weblayer
......@@ -11,6 +11,10 @@ namespace weblayer {
bool OnJNIOnLoadInit();
// Registers the minimal set of natives needed to later register the full set of
// natives if WebView compatibility is needed.
bool RegisterMinimalNatives();
} // namespace weblayer
#endif // WEBLAYER_APP_JNI_ONLOAD_H_
......@@ -32,8 +32,7 @@ public class WebViewCompatibilityTest {
public void testBothLoadPage() throws Exception {
TestThreadUtils.runOnUiThreadBlocking(() -> {
WebLayer.initializeWebViewCompatibilityMode(
InstrumentationRegistry.getTargetContext().getApplicationContext(),
InstrumentationRegistry.getTargetContext().getCacheDir(), null);
InstrumentationRegistry.getTargetContext().getApplicationContext());
});
mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestDataURL("simple_page.html"));
WebView webView = TestThreadUtils.runOnUiThreadBlocking(
......
......@@ -32,6 +32,18 @@ java_cpp_enum("generated_enums") {
]
}
_webview_compat_sources =
[ "org/chromium/weblayer_private/WebViewCompatibilityHelperImpl.java" ]
android_library("webview_compat_java") {
sources = _webview_compat_sources
deps = [
"//base:base_java",
"//base:jni_java",
]
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
}
android_library("java") {
sources = [
"org/chromium/weblayer_private/ActionModeCallback.java",
......@@ -75,6 +87,7 @@ android_library("java") {
":gms_bridge_java",
":interfaces_java",
":weblayer_resources",
":webview_compat_java",
"//base:base_java",
"//base:jni_java",
"//components/autofill/android:provider_java",
......@@ -117,6 +130,20 @@ android_library("java") {
"//third_party/android_sdk:public_framework_system_java"
}
generate_jni_registration("weblayer_jni_registration") {
targets = [ ":java" ]
header_output = "$target_gen_dir/$target_name.h"
sources_blacklist = _webview_compat_sources
namespace = "weblayer"
}
generate_jni_registration("weblayer_minimal_jni_registration") {
targets = [ ":webview_compat_java" ]
header_output = "$target_gen_dir/$target_name.h"
namespace = "weblayer_minimal"
no_transitive_deps = true
}
android_resources("weblayer_test_resources") {
resource_dirs = [ "res_test" ]
custom_package = "org.chromium.weblayer_private.test"
......@@ -153,6 +180,7 @@ generate_jni("jni") {
"org/chromium/weblayer_private/UrlBarControllerImpl.java",
"org/chromium/weblayer_private/WebLayerExceptionFilter.java",
"org/chromium/weblayer_private/WebLayerImpl.java",
"org/chromium/weblayer_private/WebViewCompatibilityHelperImpl.java",
"org/chromium/weblayer_private/metrics/MetricsServiceClient.java",
"org/chromium/weblayer_private/metrics/UmaUtils.java",
"org/chromium/weblayer_private/resources/ResourceMapper.java",
......
......@@ -190,9 +190,10 @@ public final class WebLayerImpl extends IWebLayer.Stub {
mIsWebViewCompatMode = remoteContext != null
&& !remoteContext.getClassLoader().equals(WebLayerImpl.class.getClassLoader());
if (mIsWebViewCompatMode && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
// We need to change the library name for Android M and below, otherwise the system will
// load the version loaded for WebView.
LibraryLoader.getInstance().setLibrarySuffix("-weblayer");
// Load the library with the crazy linker.
LibraryLoader.getInstance().setLinkerImplementation(true, false);
LibraryLoader.getInstance().setPostLoadHook(
WebViewCompatibilityHelperImpl::registerJni);
}
Context appContext = minimalInitForContext(appContextWrapper, remoteContextWrapper);
......
// Copyright 2020 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.weblayer_private;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.MainDex;
import org.chromium.base.annotations.NativeMethods;
/**
* Minimal native interface to allow registering all natives if WebView compatibility is needed.
*/
@MainDex
@JNINamespace("weblayer")
public final class WebViewCompatibilityHelperImpl {
public static void registerJni() {
WebViewCompatibilityHelperImplJni.get().registerJni();
}
@NativeMethods
interface Natives {
void registerJni();
}
}
// Copyright 2020 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 "weblayer/browser/java/jni/WebViewCompatibilityHelperImpl_jni.h"
#if defined(WEBLAYER_MANUAL_JNI_REGISTRATION)
#include "base/android/library_loader/library_loader_hooks.h" // nogncheck
#include "weblayer/browser/java/weblayer_jni_registration.h" // nogncheck
#endif
namespace weblayer {
namespace {
#if defined(WEBLAYER_MANUAL_JNI_REGISTRATION)
void RegisterNonMainDexNativesHook() {
RegisterNonMainDexNatives(base::android::AttachCurrentThread());
}
#endif
} // namespace
void JNI_WebViewCompatibilityHelperImpl_RegisterJni(JNIEnv* env) {
#if defined(WEBLAYER_MANUAL_JNI_REGISTRATION)
RegisterMainDexNatives(base::android::AttachCurrentThread());
base::android::SetNonMainDexJniRegistrationHook(
RegisterNonMainDexNativesHook);
#endif
}
} // namespace weblayer
......@@ -7,9 +7,12 @@
#include "base/bind.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h"
#include "components/version_info/android/channel_getter.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#if defined(OS_ANDROID)
#include "components/version_info/android/channel_getter.h"
#endif
using version_info::Channel;
namespace weblayer {
......@@ -47,7 +50,11 @@ WebLayerVariationsServiceClient::GetNetworkTimeTracker() {
}
Channel WebLayerVariationsServiceClient::GetChannel() {
#if defined(OS_ANDROID)
return version_info::android::GetChannel();
#else
return version_info::Channel::UNKNOWN;
#endif
}
bool WebLayerVariationsServiceClient::OverridesRestrictParameter(
......
......@@ -14,6 +14,7 @@ import android.os.RemoteException;
import android.support.v4.app.Fragment;
import android.util.AndroidRuntimeException;
import android.util.Log;
import android.util.Pair;
import android.webkit.ValueCallback;
import androidx.annotation.NonNull;
......@@ -33,7 +34,6 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
/**
* WebLayer is responsible for initializing state necessary to use any of the classes in web layer.
......@@ -59,18 +59,12 @@ public class WebLayer {
@NonNull
private final IWebLayer mImpl;
private static WebViewCompatibilityHelper sWebViewCompatHelper;
private static ClassLoader sWebViewCompatClassLoader;
/** The result of calling {@link #initializeWebViewCompatibilityMode}. */
public enum WebViewCompatibilityResult {
/** Native libs were copied to data directory. */
SUCCESS_COPIED,
/** Correct libs have already been copied, or symlinks were used. */
SUCCESS_CACHED,
/** IOException was thrown, could mean there is not enough disk space. */
FAILURE_IO_ERROR,
/** Compatibility mode has been successfully set up. */
SUCCESS,
/** This version of the WebLayer implementation does not support WebView compatibility. */
FAILURE_UNSUPPORTED_VERSION,
......@@ -100,18 +94,24 @@ public class WebLayer {
}
}
/** Deprecated. Use initializeWebViewCompatibilityMode(Context) instead. */
public static void initializeWebViewCompatibilityMode(@NonNull Context appContext,
@NonNull File baseDir, @NonNull Callback<WebViewCompatibilityResult> callback) {
WebViewCompatibilityResult result = initializeWebViewCompatibilityMode(appContext);
if (callback != null) {
callback.onResult(result);
}
}
/**
* Performs initialization needed to run WebView and WebLayer in the same process. This should
* be called as early as possible if this functionality is needed.
* Performs initialization needed to run WebView and WebLayer in the same process.
*
* @param appContext The hosting application's Context.
* @param baseDir The directory to copy any necessary files into.
* @param callback Callback called on success or failure.
*/
public static void initializeWebViewCompatibilityMode(@NonNull Context appContext,
@NonNull File baseDir, @NonNull Callback<WebViewCompatibilityResult> callback) {
public static WebViewCompatibilityResult initializeWebViewCompatibilityMode(
@NonNull Context appContext) {
ThreadCheck.ensureOnUiThread();
if (sWebViewCompatHelper != null) {
if (sWebViewCompatClassLoader != null) {
throw new AndroidRuntimeException(
"initializeWebViewCompatibilityMode() has already been called.");
}
......@@ -121,13 +121,14 @@ public class WebLayer {
+ "loaded.");
}
try {
sWebViewCompatHelper = WebViewCompatibilityHelper.initialize(
appContext, getOrCreateRemoteContext(appContext), baseDir, callback);
Pair<ClassLoader, WebLayer.WebViewCompatibilityResult> result =
WebViewCompatibilityHelper.initialize(
appContext, getOrCreateRemoteContext(appContext));
sWebViewCompatClassLoader = result.first;
return result.second;
} catch (Exception e) {
if (callback != null) {
callback.onResult(WebViewCompatibilityResult.FAILURE_OTHER);
}
Log.e(TAG, "Unable to initialize WebView compatibility", e);
return WebViewCompatibilityResult.FAILURE_OTHER;
}
}
......@@ -265,8 +266,8 @@ public class WebLayer {
int majorVersion = -1;
String version = "<unavailable>";
try {
if (sWebViewCompatHelper != null) {
remoteClassLoader = sWebViewCompatHelper.getWebLayerClassLoader();
if (sWebViewCompatClassLoader != null) {
remoteClassLoader = sWebViewCompatClassLoader;
}
if (remoteClassLoader == null) {
remoteClassLoader = getOrCreateRemoteContext(appContext).getClassLoader();
......@@ -285,7 +286,7 @@ public class WebLayer {
majorVersion = mFactory.getImplementationMajorVersion();
version = mFactory.getImplementationVersion();
} catch (PackageManager.NameNotFoundException | ReflectiveOperationException
| RemoteException | ExecutionException | InterruptedException e) {
| RemoteException e) {
Log.e(TAG, "Unable to create WebLayerFactory", e);
}
mAvailable = available;
......
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