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) { ...@@ -629,6 +629,7 @@ if (is_android) {
"//components/infobars/core", "//components/infobars/core",
"//components/translate/core/browser", "//components/translate/core/browser",
"//content/public/browser", "//content/public/browser",
"//content/public/test/android:content_native_test_support",
"//content/test:test_support", "//content/test:test_support",
"//testing/gtest", "//testing/gtest",
"//weblayer/browser/java:test_jni", "//weblayer/browser/java:test_jni",
...@@ -701,11 +702,15 @@ if (is_android) { ...@@ -701,11 +702,15 @@ if (is_android) {
shared_library("libweblayer_test") { shared_library("libweblayer_test") {
testonly = true testonly = true
sources = [ "app/entry_point.cc" ] sources = [
"$target_gen_dir/browser/java/test_weblayer_jni_registration.h",
"app/entry_point.cc",
]
deps = [ deps = [
":weblayer_lib_webview_test", ":weblayer_lib_webview_test",
"//base", "//base",
"//content/public/app", "//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_onload" ]
configs += [ "//build/config/android:hide_all_but_jni" ] configs += [ "//build/config/android:hide_all_but_jni" ]
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#include "base/android/jni_android.h" #include "base/android/jni_android.h"
#include "base/android/library_loader/library_loader_hooks.h" #include "base/android/library_loader/library_loader_hooks.h"
#include "weblayer/app/jni_onload.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 { namespace {
...@@ -16,6 +18,12 @@ bool NativeInit(base::android::LibraryProcessType) { ...@@ -16,6 +18,12 @@ bool NativeInit(base::android::LibraryProcessType) {
JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
base::android::InitVM(vm); 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); base::android::SetNativeInitializationHook(&NativeInit);
return JNI_VERSION_1_4; return JNI_VERSION_1_4;
} }
...@@ -256,6 +256,13 @@ android_library("test_java") { ...@@ -256,6 +256,13 @@ android_library("test_java") {
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] 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") { generate_jni("test_jni") {
testonly = true testonly = true
sources = [ sources = [
......
...@@ -27,6 +27,7 @@ public abstract class ChildProcessService extends Service { ...@@ -27,6 +27,7 @@ public abstract class ChildProcessService extends Service {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
try { try {
WebLayer.disableWebViewCompatibilityMode();
Context appContext = getApplicationContext(); Context appContext = getApplicationContext();
Context remoteContext = WebLayer.getOrCreateRemoteContext(appContext); Context remoteContext = WebLayer.getOrCreateRemoteContext(appContext);
if (WebLayer.getSupportedMajorVersion(appContext) < 81) { if (WebLayer.getSupportedMajorVersion(appContext) < 81) {
......
...@@ -14,7 +14,6 @@ import android.os.IBinder; ...@@ -14,7 +14,6 @@ import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.AndroidRuntimeException; import android.util.AndroidRuntimeException;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
...@@ -38,7 +37,6 @@ import java.lang.reflect.Field; ...@@ -38,7 +37,6 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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. * WebLayer is responsible for initializing state necessary to use any of the classes in web layer.
...@@ -55,17 +53,20 @@ public class WebLayer { ...@@ -55,17 +53,20 @@ public class WebLayer {
@Nullable @Nullable
private static Context sRemoteContext; private static Context sRemoteContext;
@Nullable
private static ClassLoader sRemoteClassLoader;
@Nullable @Nullable
private static Context sAppContext; private static Context sAppContext;
@Nullable @Nullable
private static WebLayerLoader sLoader; private static WebLayerLoader sLoader;
private static boolean sDisableWebViewCompatibilityMode;
@NonNull @NonNull
private final IWebLayer mImpl; private final IWebLayer mImpl;
private static Callable<ClassLoader> sWebViewCompatClassLoaderGetter;
/** The result of calling {@link #initializeWebViewCompatibilityMode}. */ /** The result of calling {@link #initializeWebViewCompatibilityMode}. */
public enum WebViewCompatibilityResult { public enum WebViewCompatibilityResult {
/** Compatibility mode has been successfully set up. */ /** Compatibility mode has been successfully set up. */
...@@ -100,31 +101,13 @@ public class WebLayer { ...@@ -100,31 +101,13 @@ public class WebLayer {
} }
/** /**
* Performs initialization needed to run WebView and WebLayer in the same process. * 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.
* @param appContext The hosting application's Context.
*/ */
public static WebViewCompatibilityResult initializeWebViewCompatibilityMode( public static WebViewCompatibilityResult initializeWebViewCompatibilityMode(
@NonNull Context appContext) { @NonNull Context appContext) {
ThreadCheck.ensureOnUiThread(); ThreadCheck.ensureOnUiThread();
if (sWebViewCompatClassLoaderGetter != null) { return WebViewCompatibilityResult.SUCCESS;
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;
}
} }
/** /**
...@@ -266,19 +249,13 @@ public class WebLayer { ...@@ -266,19 +249,13 @@ public class WebLayer {
* Creates WebLayerLoader. This does a minimal amount of loading * Creates WebLayerLoader. This does a minimal amount of loading
*/ */
public WebLayerLoader(@NonNull Context appContext) { public WebLayerLoader(@NonNull Context appContext) {
ClassLoader remoteClassLoader = null;
boolean available = false; boolean available = false;
int majorVersion = -1; int majorVersion = -1;
String version = "<unavailable>"; String version = "<unavailable>";
try { try {
if (sWebViewCompatClassLoaderGetter != null) { Class factoryClass =
remoteClassLoader = sWebViewCompatClassLoaderGetter.call(); getOrCreateRemoteClassLoader(appContext)
} .loadClass("org.chromium.weblayer_private.WebLayerFactoryImpl");
if (remoteClassLoader == null) {
remoteClassLoader = getOrCreateRemoteContext(appContext).getClassLoader();
}
Class factoryClass = remoteClassLoader.loadClass(
"org.chromium.weblayer_private.WebLayerFactoryImpl");
mFactory = IWebLayerFactory.Stub.asInterface( mFactory = IWebLayerFactory.Stub.asInterface(
(IBinder) factoryClass (IBinder) factoryClass
.getMethod("create", String.class, int.class, int.class) .getMethod("create", String.class, int.class, int.class)
...@@ -606,6 +583,25 @@ public class WebLayer { ...@@ -606,6 +583,25 @@ public class WebLayer {
sRemoteContext = remoteContext; 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. * Creates a Context for the remote (weblayer implementation) side.
*/ */
...@@ -632,6 +628,10 @@ public class WebLayer { ...@@ -632,6 +628,10 @@ public class WebLayer {
return sRemoteContext; return sRemoteContext;
} }
/* package */ static void disableWebViewCompatibilityMode() {
sDisableWebViewCompatibilityMode = true;
}
/** /**
* Creates a Context for the remote (weblayer implementation) side * Creates a Context for the remote (weblayer implementation) side
* using a specified package name as the implementation. This is only * using a specified package name as the implementation. This is only
......
...@@ -11,7 +11,6 @@ import android.content.pm.PackageManager; ...@@ -11,7 +11,6 @@ import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.os.StrictMode; import android.os.StrictMode;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import dalvik.system.BaseDexClassLoader; import dalvik.system.BaseDexClassLoader;
import dalvik.system.PathClassLoader; import dalvik.system.PathClassLoader;
...@@ -19,25 +18,19 @@ import dalvik.system.PathClassLoader; ...@@ -19,25 +18,19 @@ import dalvik.system.PathClassLoader;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable;
/** Helper class which performs initialization needed for WebView compatibility. */ /** Helper class which performs initialization needed for WebView compatibility. */
final class WebViewCompatibilityHelper { final class WebViewCompatibilityHelper {
/** Creates a the ClassLoader to use for WebView compatibility. */ /** Creates a the ClassLoader to use for WebView compatibility. */
static Pair<Callable<ClassLoader>, WebLayer.WebViewCompatibilityResult> initialize( static ClassLoader initialize(Context appContext)
Context appContext)
throws PackageManager.NameNotFoundException, ReflectiveOperationException { throws PackageManager.NameNotFoundException, ReflectiveOperationException {
Context remoteContext = WebLayer.getOrCreateRemoteContext(appContext); Context remoteContext = WebLayer.getOrCreateRemoteContext(appContext);
PackageInfo info = PackageInfo info =
appContext.getPackageManager().getPackageInfo(remoteContext.getPackageName(), appContext.getPackageManager().getPackageInfo(remoteContext.getPackageName(),
PackageManager.GET_SHARED_LIBRARY_FILES PackageManager.GET_SHARED_LIBRARY_FILES
| PackageManager.MATCH_UNINSTALLED_PACKAGES); | PackageManager.MATCH_UNINSTALLED_PACKAGES);
int majorVersion = parseMajorVersion(info.versionName);
if (!isSupportedVersion(majorVersion)) { if (parseMajorVersion(info.versionName) >= 84) {
return Pair.create(
null, WebLayer.WebViewCompatibilityResult.FAILURE_UNSUPPORTED_VERSION);
}
if (majorVersion >= 84) {
// Recreate the context without code to avoid wasting memory by accidentally using the // Recreate the context without code to avoid wasting memory by accidentally using the
// class loader. // class loader.
remoteContext = appContext.createPackageContext( remoteContext = appContext.createPackageContext(
...@@ -65,31 +58,27 @@ final class WebViewCompatibilityHelper { ...@@ -65,31 +58,27 @@ final class WebViewCompatibilityHelper {
String dexPath = getAllApkPaths(info.applicationInfo); String dexPath = getAllApkPaths(info.applicationInfo);
String librarySearchPath = TextUtils.join(File.pathSeparator, libraryPaths); String librarySearchPath = TextUtils.join(File.pathSeparator, libraryPaths);
Callable<ClassLoader> classLoaderGetter = () -> { // TODO(cduvall): PathClassLoader may call stat on the library paths, consider moving
// TODO(cduvall): PathClassLoader may call stat on the library paths, consider moving // this to a background thread.
// this to a background thread. StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); try {
try { return new PathClassLoader(
return new PathClassLoader( dexPath, librarySearchPath, ClassLoader.getSystemClassLoader()) {
dexPath, librarySearchPath, ClassLoader.getSystemClassLoader()); @Override
} finally { public Class<?> loadClass(String name) throws ClassNotFoundException {
StrictMode.setThreadPolicy(oldPolicy); // TODO(crbug.com/1112001): Investigate why loading classes causes strict mode
} // violations in some situations.
}; StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
return Pair.create(classLoaderGetter, WebLayer.WebViewCompatibilityResult.SUCCESS); try {
} return super.loadClass(name);
} finally {
/** StrictMode.setThreadPolicy(oldPolicy);
* 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. };
*/ } finally {
static boolean isSupportedVersion(int majorVersion) { StrictMode.setThreadPolicy(oldPolicy);
// 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;
} }
return majorVersion >= 81;
} }
/** Parses the version name into an integer version number. */ /** Parses the version name into an integer version number. */
......
...@@ -7,7 +7,6 @@ package org.chromium.weblayer; ...@@ -7,7 +7,6 @@ package org.chromium.weblayer;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
import android.util.Pair;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
...@@ -18,8 +17,6 @@ import org.junit.runner.RunWith; ...@@ -18,8 +17,6 @@ import org.junit.runner.RunWith;
import org.chromium.base.test.BaseJUnit4ClassRunner; import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.MinAndroidSdkLevel; import org.chromium.base.test.util.MinAndroidSdkLevel;
import java.util.concurrent.Callable;
/** /**
* Tests for (@link WebViewCompatibilityHelper}. * Tests for (@link WebViewCompatibilityHelper}.
*/ */
...@@ -30,34 +27,10 @@ public class WebViewCompatibilityHelperTest { ...@@ -30,34 +27,10 @@ public class WebViewCompatibilityHelperTest {
@MinAndroidSdkLevel(Build.VERSION_CODES.N) @MinAndroidSdkLevel(Build.VERSION_CODES.N)
public void testLibraryPaths() throws Exception { public void testLibraryPaths() throws Exception {
Context appContext = InstrumentationRegistry.getTargetContext(); Context appContext = InstrumentationRegistry.getTargetContext();
Pair<Callable<ClassLoader>, WebLayer.WebViewCompatibilityResult> result = ClassLoader classLoader = WebViewCompatibilityHelper.initialize(appContext);
WebViewCompatibilityHelper.initialize(appContext); String[] libraryPaths = WebViewCompatibilityHelper.getLibraryPaths(classLoader);
Assert.assertEquals(result.second, WebLayer.WebViewCompatibilityResult.SUCCESS);
String[] libraryPaths = WebViewCompatibilityHelper.getLibraryPaths(result.first.call());
for (String path : libraryPaths) { for (String path : libraryPaths) {
Assert.assertTrue(path.startsWith("/./")); 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 { ...@@ -34,9 +34,8 @@ public final class TestWebLayer {
} }
private TestWebLayer(@NonNull Context appContext) { private TestWebLayer(@NonNull Context appContext) {
ClassLoader remoteClassLoader;
try { try {
remoteClassLoader = WebLayer.getOrCreateRemoteContext(appContext).getClassLoader(); ClassLoader remoteClassLoader = WebLayer.getOrCreateRemoteClassLoader(appContext);
Class TestWebLayerClass = remoteClassLoader.loadClass( Class TestWebLayerClass = remoteClassLoader.loadClass(
"org.chromium.weblayer_private.test.TestWebLayerImpl"); "org.chromium.weblayer_private.test.TestWebLayerImpl");
mITestWebLayer = ITestWebLayer.Stub.asInterface( mITestWebLayer = ITestWebLayer.Stub.asInterface(
...@@ -118,4 +117,8 @@ public final class TestWebLayer { ...@@ -118,4 +117,8 @@ public final class TestWebLayer {
public String getDisplayedUrl(View urlBarView) throws RemoteException { public String getDisplayedUrl(View urlBarView) throws RemoteException {
return mITestWebLayer.getDisplayedUrl(ObjectWrapper.wrap(urlBarView)); return mITestWebLayer.getDisplayedUrl(ObjectWrapper.wrap(urlBarView));
} }
public static void disableWebViewCompatibilityMode() {
WebLayer.disableWebViewCompatibilityMode();
}
} }
...@@ -202,6 +202,11 @@ android_apk("weblayer_support_apk") { ...@@ -202,6 +202,11 @@ android_apk("weblayer_support_apk") {
# default upstream safebrowsing related classes # default upstream safebrowsing related classes
deps += [ "//weblayer/browser/java:gms_bridge_upstream_impl_java" ] 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" apk_name = "WebLayerSupport"
android_manifest = weblayer_support_manifest android_manifest = weblayer_support_manifest
min_sdk_version = 21 min_sdk_version = 21
......
...@@ -25,6 +25,7 @@ import org.chromium.weblayer.NewTabType; ...@@ -25,6 +25,7 @@ import org.chromium.weblayer.NewTabType;
import org.chromium.weblayer.Profile; import org.chromium.weblayer.Profile;
import org.chromium.weblayer.Tab; import org.chromium.weblayer.Tab;
import org.chromium.weblayer.TabCallback; import org.chromium.weblayer.TabCallback;
import org.chromium.weblayer.TestWebLayer;
import org.chromium.weblayer.WebLayer; import org.chromium.weblayer.WebLayer;
import java.io.File; import java.io.File;
...@@ -50,6 +51,9 @@ public class WebLayerBrowserTestsActivity extends NativeBrowserTestActivity { ...@@ -50,6 +51,9 @@ public class WebLayerBrowserTestsActivity extends NativeBrowserTestActivity {
}); });
try { 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 -> { WebLayer.loadAsync(getApplication(), webLayer -> {
mWebLayer = webLayer; mWebLayer = webLayer;
createShell(); createShell();
......
...@@ -47,6 +47,7 @@ if (is_android) { ...@@ -47,6 +47,7 @@ if (is_android) {
"//weblayer/browser/java", "//weblayer/browser/java",
"//weblayer/browser/java:gms_bridge_java", "//weblayer/browser/java:gms_bridge_java",
"//weblayer/public/java", "//weblayer/public/java",
"//weblayer/public/javatestutil:test_java",
] ]
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] 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