Commit c4fea12e authored by Clark DuVall's avatar Clark DuVall Committed by Commit Bot

[WebLayer] Enable WebView compatibility mode by default

This is how WebLayer will be run in the field, and there are some edge
cases where it's hard for the client app to call
initializeWebViewCompatibilityMode() before WebLayer is used.

This also removes some of the version checking logic which was needed
when we supported versions < 82. Now 82 is our minimum version, so this
should no longer be needed.

Bug: 1111511
Change-Id: Iedce8bc31b82d1a38d5910dc1c3d3b6c5edd6258
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2333056Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarRichard Coles <torne@chromium.org>
Commit-Queue: Clark DuVall <cduvall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#794248}
parent 3e7c24e4
......@@ -629,6 +629,7 @@ if (is_android) {
"//components/infobars/core",
"//components/translate/core/browser",
"//content/public/browser",
"//content/public/test/android:content_native_test_support",
"//content/test:test_support",
"//testing/gtest",
"//weblayer/browser/java:test_jni",
......@@ -701,11 +702,15 @@ if (is_android) {
shared_library("libweblayer_test") {
testonly = true
sources = [ "app/entry_point.cc" ]
sources = [
"$target_gen_dir/browser/java/test_weblayer_jni_registration.h",
"app/entry_point.cc",
]
deps = [
":weblayer_lib_webview_test",
"//base",
"//content/public/app",
"//weblayer/browser/java:test_weblayer_jni_registration",
]
configs -= [ "//build/config/android:hide_all_but_jni_onload" ]
configs += [ "//build/config/android:hide_all_but_jni" ]
......
......@@ -5,6 +5,8 @@
#include "base/android/jni_android.h"
#include "base/android/library_loader/library_loader_hooks.h"
#include "weblayer/app/jni_onload.h"
#include "weblayer/browser/java/test_weblayer_jni_registration.h"
#include "weblayer/browser/web_view_compatibility_helper_impl.h"
namespace {
......@@ -16,6 +18,12 @@ bool NativeInit(base::android::LibraryProcessType) {
JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
base::android::InitVM(vm);
JNIEnv* env = base::android::AttachCurrentThread();
if (!weblayer_test::RegisterNonMainDexNatives(env) ||
!weblayer_test::RegisterMainDexNatives(env) ||
!weblayer::MaybeRegisterNatives()) {
return -1;
}
base::android::SetNativeInitializationHook(&NativeInit);
return JNI_VERSION_1_4;
}
......@@ -256,6 +256,13 @@ android_library("test_java") {
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
}
generate_jni_registration("test_weblayer_jni_registration") {
testonly = true
targets = [ ":test_java" ]
header_output = "$target_gen_dir/$target_name.h"
namespace = "weblayer_test"
}
generate_jni("test_jni") {
testonly = true
sources = [
......
......@@ -27,6 +27,7 @@ public abstract class ChildProcessService extends Service {
public void onCreate() {
super.onCreate();
try {
WebLayer.disableWebViewCompatibilityMode();
Context appContext = getApplicationContext();
Context remoteContext = WebLayer.getOrCreateRemoteContext(appContext);
if (WebLayer.getSupportedMajorVersion(appContext) < 81) {
......
......@@ -14,7 +14,6 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.AndroidRuntimeException;
import android.util.Log;
import android.util.Pair;
import android.webkit.ValueCallback;
import androidx.annotation.NonNull;
......@@ -38,7 +37,6 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
/**
* WebLayer is responsible for initializing state necessary to use any of the classes in web layer.
......@@ -55,17 +53,20 @@ public class WebLayer {
@Nullable
private static Context sRemoteContext;
@Nullable
private static ClassLoader sRemoteClassLoader;
@Nullable
private static Context sAppContext;
@Nullable
private static WebLayerLoader sLoader;
private static boolean sDisableWebViewCompatibilityMode;
@NonNull
private final IWebLayer mImpl;
private static Callable<ClassLoader> sWebViewCompatClassLoaderGetter;
/** The result of calling {@link #initializeWebViewCompatibilityMode}. */
public enum WebViewCompatibilityResult {
/** Compatibility mode has been successfully set up. */
......@@ -100,31 +101,13 @@ public class WebLayer {
}
/**
* Performs initialization needed to run WebView and WebLayer in the same process.
*
* @param appContext The hosting application's Context.
* Deprecated. This is no longer necessary since WebView compatibility mode is now enabled by
* default. This will be removed once the client app is updated.
*/
public static WebViewCompatibilityResult initializeWebViewCompatibilityMode(
@NonNull Context appContext) {
ThreadCheck.ensureOnUiThread();
if (sWebViewCompatClassLoaderGetter != null) {
throw new AndroidRuntimeException(
"initializeWebViewCompatibilityMode() has already been called.");
}
if (sLoader != null) {
throw new AndroidRuntimeException(
"initializeWebViewCompatibilityMode() must be called before WebLayer is "
+ "loaded.");
}
try {
Pair<Callable<ClassLoader>, WebLayer.WebViewCompatibilityResult> result =
WebViewCompatibilityHelper.initialize(appContext);
sWebViewCompatClassLoaderGetter = result.first;
return result.second;
} catch (Exception e) {
Log.e(TAG, "Unable to initialize WebView compatibility", e);
return WebViewCompatibilityResult.FAILURE_OTHER;
}
return WebViewCompatibilityResult.SUCCESS;
}
/**
......@@ -266,19 +249,13 @@ public class WebLayer {
* Creates WebLayerLoader. This does a minimal amount of loading
*/
public WebLayerLoader(@NonNull Context appContext) {
ClassLoader remoteClassLoader = null;
boolean available = false;
int majorVersion = -1;
String version = "<unavailable>";
try {
if (sWebViewCompatClassLoaderGetter != null) {
remoteClassLoader = sWebViewCompatClassLoaderGetter.call();
}
if (remoteClassLoader == null) {
remoteClassLoader = getOrCreateRemoteContext(appContext).getClassLoader();
}
Class factoryClass = remoteClassLoader.loadClass(
"org.chromium.weblayer_private.WebLayerFactoryImpl");
Class factoryClass =
getOrCreateRemoteClassLoader(appContext)
.loadClass("org.chromium.weblayer_private.WebLayerFactoryImpl");
mFactory = IWebLayerFactory.Stub.asInterface(
(IBinder) factoryClass
.getMethod("create", String.class, int.class, int.class)
......@@ -606,6 +583,25 @@ public class WebLayer {
sRemoteContext = remoteContext;
}
/**
* Creates a ClassLoader for the remote (weblayer implementation) side.
*/
static ClassLoader getOrCreateRemoteClassLoader(Context appContext)
throws PackageManager.NameNotFoundException, ReflectiveOperationException {
if (sRemoteClassLoader != null) {
return sRemoteClassLoader;
}
// Child processes do not need WebView compatibility since there is no chance
// WebView will run in the same process.
if (sDisableWebViewCompatibilityMode) {
sRemoteClassLoader = getOrCreateRemoteContext(appContext).getClassLoader();
} else {
sRemoteClassLoader = WebViewCompatibilityHelper.initialize(appContext);
}
return sRemoteClassLoader;
}
/**
* Creates a Context for the remote (weblayer implementation) side.
*/
......@@ -632,6 +628,10 @@ public class WebLayer {
return sRemoteContext;
}
/* package */ static void disableWebViewCompatibilityMode() {
sDisableWebViewCompatibilityMode = true;
}
/**
* Creates a Context for the remote (weblayer implementation) side
* using a specified package name as the implementation. This is only
......
......@@ -11,7 +11,6 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.os.StrictMode;
import android.text.TextUtils;
import android.util.Pair;
import dalvik.system.BaseDexClassLoader;
import dalvik.system.PathClassLoader;
......@@ -19,25 +18,19 @@ import dalvik.system.PathClassLoader;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
/** Helper class which performs initialization needed for WebView compatibility. */
final class WebViewCompatibilityHelper {
/** Creates a the ClassLoader to use for WebView compatibility. */
static Pair<Callable<ClassLoader>, WebLayer.WebViewCompatibilityResult> initialize(
Context appContext)
static ClassLoader initialize(Context appContext)
throws PackageManager.NameNotFoundException, ReflectiveOperationException {
Context remoteContext = WebLayer.getOrCreateRemoteContext(appContext);
PackageInfo info =
appContext.getPackageManager().getPackageInfo(remoteContext.getPackageName(),
PackageManager.GET_SHARED_LIBRARY_FILES
| PackageManager.MATCH_UNINSTALLED_PACKAGES);
int majorVersion = parseMajorVersion(info.versionName);
if (!isSupportedVersion(majorVersion)) {
return Pair.create(
null, WebLayer.WebViewCompatibilityResult.FAILURE_UNSUPPORTED_VERSION);
}
if (majorVersion >= 84) {
if (parseMajorVersion(info.versionName) >= 84) {
// Recreate the context without code to avoid wasting memory by accidentally using the
// class loader.
remoteContext = appContext.createPackageContext(
......@@ -65,31 +58,27 @@ final class WebViewCompatibilityHelper {
String dexPath = getAllApkPaths(info.applicationInfo);
String librarySearchPath = TextUtils.join(File.pathSeparator, libraryPaths);
Callable<ClassLoader> classLoaderGetter = () -> {
// TODO(cduvall): PathClassLoader may call stat on the library paths, consider moving
// this to a background thread.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
return new PathClassLoader(
dexPath, librarySearchPath, ClassLoader.getSystemClassLoader());
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
};
return Pair.create(classLoaderGetter, WebLayer.WebViewCompatibilityResult.SUCCESS);
}
/**
* Returns if the version of the WebLayer implementation supports WebView compatibility. We
* can't use WebLayer.getSupportedMajorVersion() here because the loader depends on
* WebView compatibility already being set up.
*/
static boolean isSupportedVersion(int majorVersion) {
// M- only supports WebView compat via copying the libraries on 81, so only support 82+.
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
return majorVersion >= 82;
// TODO(cduvall): PathClassLoader may call stat on the library paths, consider moving
// this to a background thread.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
return new PathClassLoader(
dexPath, librarySearchPath, ClassLoader.getSystemClassLoader()) {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// TODO(crbug.com/1112001): Investigate why loading classes causes strict mode
// violations in some situations.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
return super.loadClass(name);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
};
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
return majorVersion >= 81;
}
/** Parses the version name into an integer version number. */
......
......@@ -7,7 +7,6 @@ package org.chromium.weblayer;
import android.content.Context;
import android.os.Build;
import android.support.test.InstrumentationRegistry;
import android.util.Pair;
import androidx.test.filters.SmallTest;
......@@ -18,8 +17,6 @@ import org.junit.runner.RunWith;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import java.util.concurrent.Callable;
/**
* Tests for (@link WebViewCompatibilityHelper}.
*/
......@@ -30,34 +27,10 @@ public class WebViewCompatibilityHelperTest {
@MinAndroidSdkLevel(Build.VERSION_CODES.N)
public void testLibraryPaths() throws Exception {
Context appContext = InstrumentationRegistry.getTargetContext();
Pair<Callable<ClassLoader>, WebLayer.WebViewCompatibilityResult> result =
WebViewCompatibilityHelper.initialize(appContext);
Assert.assertEquals(result.second, WebLayer.WebViewCompatibilityResult.SUCCESS);
String[] libraryPaths = WebViewCompatibilityHelper.getLibraryPaths(result.first.call());
ClassLoader classLoader = WebViewCompatibilityHelper.initialize(appContext);
String[] libraryPaths = WebViewCompatibilityHelper.getLibraryPaths(classLoader);
for (String path : libraryPaths) {
Assert.assertTrue(path.startsWith("/./"));
}
}
@Test
@SmallTest
public void testSupportedVersion() throws Exception {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion(
WebViewCompatibilityHelper.parseMajorVersion("81.0.2.5")));
} else {
Assert.assertTrue(WebViewCompatibilityHelper.isSupportedVersion(
WebViewCompatibilityHelper.parseMajorVersion("81.0.2.5")));
}
Assert.assertTrue(WebViewCompatibilityHelper.isSupportedVersion(
WebViewCompatibilityHelper.parseMajorVersion("82.0.2.5")));
Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion(
WebViewCompatibilityHelper.parseMajorVersion("80.0.2.5")));
Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion(
WebViewCompatibilityHelper.parseMajorVersion("")));
Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion(
WebViewCompatibilityHelper.parseMajorVersion("82.0")));
Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion(
WebViewCompatibilityHelper.parseMajorVersion(null)));
}
}
......@@ -34,9 +34,8 @@ public final class TestWebLayer {
}
private TestWebLayer(@NonNull Context appContext) {
ClassLoader remoteClassLoader;
try {
remoteClassLoader = WebLayer.getOrCreateRemoteContext(appContext).getClassLoader();
ClassLoader remoteClassLoader = WebLayer.getOrCreateRemoteClassLoader(appContext);
Class TestWebLayerClass = remoteClassLoader.loadClass(
"org.chromium.weblayer_private.test.TestWebLayerImpl");
mITestWebLayer = ITestWebLayer.Stub.asInterface(
......@@ -118,4 +117,8 @@ public final class TestWebLayer {
public String getDisplayedUrl(View urlBarView) throws RemoteException {
return mITestWebLayer.getDisplayedUrl(ObjectWrapper.wrap(urlBarView));
}
public static void disableWebViewCompatibilityMode() {
WebLayer.disableWebViewCompatibilityMode();
}
}
......@@ -202,6 +202,11 @@ android_apk("weblayer_support_apk") {
# default upstream safebrowsing related classes
deps += [ "//weblayer/browser/java:gms_bridge_upstream_impl_java" ]
# Add the Chromium linker for WebView compatibility support on L-M.
deps += [ "//base/android/linker:chromium_android_linker" ]
loadable_modules =
[ "$root_out_dir/libchromium_android_linker$shlib_extension" ]
apk_name = "WebLayerSupport"
android_manifest = weblayer_support_manifest
min_sdk_version = 21
......
......@@ -25,6 +25,7 @@ import org.chromium.weblayer.NewTabType;
import org.chromium.weblayer.Profile;
import org.chromium.weblayer.Tab;
import org.chromium.weblayer.TabCallback;
import org.chromium.weblayer.TestWebLayer;
import org.chromium.weblayer.WebLayer;
import java.io.File;
......@@ -50,6 +51,9 @@ public class WebLayerBrowserTestsActivity extends NativeBrowserTestActivity {
});
try {
// Browser tests cannot be run in WebView compatibility mode since the class loader
// WebLayer uses needs to match the class loader used for setup.
TestWebLayer.disableWebViewCompatibilityMode();
WebLayer.loadAsync(getApplication(), webLayer -> {
mWebLayer = webLayer;
createShell();
......
......@@ -47,6 +47,7 @@ if (is_android) {
"//weblayer/browser/java",
"//weblayer/browser/java:gms_bridge_java",
"//weblayer/public/java",
"//weblayer/public/javatestutil:test_java",
]
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
......
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