Commit 6f5fc054 authored by Clark DuVall's avatar Clark DuVall Committed by Commit Bot

[WebLayer] Simplify asset and resource handling

This change makes it so we no longer need to add asset paths to the
applications context, and instead we wrap the context in a
ContextWrapper which overrides getAssets() and getResources() to
directly return the implementation assets and resources.

This means we can no longer use WebViewFactory.getProviderClass(),
since it adds asset paths internally. Instead we load the context from
the WebView provider using WVF.getWebViewContextAndSetProvider() on N+,
which takes care of signature verification, and load manually using
createPackageContext() below N. We also need to make sure the native
library is loaded so we can use the WebView shared relro implementation.

Change-Id: I082107a71d1bf71069e498843a9ecc00470e6cb8
Bug: 1032662
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1931617Reviewed-by: default avatarRichard Coles <torne@chromium.org>
Reviewed-by: default avatarBo <boliu@chromium.org>
Commit-Queue: Clark DuVall <cduvall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#723520}
parent c92e7ea2
......@@ -8,6 +8,8 @@ import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.view.LayoutInflater;
import org.chromium.base.ContextUtils;
......@@ -29,6 +31,16 @@ public class ClassLoaderContextWrapperFactory {
private static final WeakHashMap<Context, WeakReference<ClassLoaderContextWrapper>>
sCtxToWrapper = new WeakHashMap<>();
private static final Object sLock = new Object();
@SuppressWarnings("StaticFieldLeak")
private static Context sResourceOverrideContext;
/**
* Sets a context that will override the return values from getAssets(), getResources(), and
* getSystemService() when asking for layout inflater.
*/
public static void setResourceOverrideContext(Context context) {
sResourceOverrideContext = context;
}
public static Context get(Context ctx) {
// Avoid double-wrapping a context.
......@@ -76,6 +88,9 @@ public class ClassLoaderContextWrapperFactory {
@Override
public Object getSystemService(String name) {
if (Context.LAYOUT_INFLATER_SERVICE.equals(name)) {
if (sResourceOverrideContext != null) {
return LayoutInflater.from(sResourceOverrideContext);
}
LayoutInflater i = (LayoutInflater) getBaseContext().getSystemService(name);
return i.cloneInContext(this);
} else {
......@@ -120,5 +135,21 @@ public class ClassLoaderContextWrapperFactory {
super.startActivity(intent);
}
@Override
public AssetManager getAssets() {
if (sResourceOverrideContext != null) {
return sResourceOverrideContext.getAssets();
}
return getBaseContext().getAssets();
}
@Override
public Resources getResources() {
if (sResourceOverrideContext != null) {
return sResourceOverrideContext.getResources();
}
return getBaseContext().getResources();
}
}
}
......@@ -25,8 +25,15 @@ import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
public final class ChildProcessServiceImpl extends IChildProcessService.Stub {
private ChildProcessService mService;
// This should only be called in M80 or below.
@UsedByReflection("WebLayer")
public static IBinder create(Service service, Context appContext) {
return create(service, appContext, WebLayerImpl.createRemoteContextV80(appContext));
}
@UsedByReflection("WebLayer")
public static IBinder create(Service service, Context appContext, Context remoteContext) {
ClassLoaderContextWrapperFactory.setResourceOverrideContext(remoteContext);
// Wrap the app context so that it can be used to load WebLayer implementation classes.
appContext = ClassLoaderContextWrapperFactory.get(appContext);
return new ChildProcessServiceImpl(service, appContext);
......
......@@ -20,7 +20,6 @@ import org.chromium.components.crash.browser.ChildProcessCrashObserver;
import org.chromium.components.minidump_uploader.CrashFileManager;
import org.chromium.weblayer_private.interfaces.ICrashReporterController;
import org.chromium.weblayer_private.interfaces.ICrashReporterControllerClient;
import org.chromium.weblayer_private.interfaces.IObjectWrapper;
import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
import java.io.File;
......@@ -59,9 +58,7 @@ public final class CrashReporterControllerImpl extends ICrashReporterController.
});
}
public static CrashReporterControllerImpl getInstance(IObjectWrapper appContextWrapper) {
// This is a no-op if init has already happened.
WebLayerImpl.minimalInitForContext(appContextWrapper);
public static CrashReporterControllerImpl getInstance() {
return Holder.sInstance;
}
......
......@@ -13,21 +13,12 @@ import org.chromium.weblayer_private.interfaces.IProfile;
import org.chromium.weblayer_private.interfaces.IRemoteFragmentClient;
interface IWebLayer {
// Initializes WebLayer and starts loading.
//
// It is expected that either loadAsync or loadSync is called before anything else.
//
// @param appContext A Context that refers to the Application using WebLayer.
// @param loadedCallback A ValueCallback that will be called when load completes.
void loadAsync(in IObjectWrapper appContext,
in IObjectWrapper loadedCallback) = 1;
// Deprecated, use loadAsync().
void loadAsyncV80(in IObjectWrapper appContext,
in IObjectWrapper loadedCallback) = 1;
// Initializes WebLayer, starts loading and blocks until loading has completed.
//
// It is expected that either loadAsync or loadSync is called before anything else.
//
// @param appContext A Context that refers to the Application using WebLayer.
void loadSync(in IObjectWrapper appContext) = 2;
// Deprecated, use loadSync().
void loadSyncV80(in IObjectWrapper appContext) = 2;
// Creates the WebLayer counterpart to a BrowserFragment - a BrowserFragmentImpl
//
......@@ -47,8 +38,33 @@ interface IWebLayer {
// Returns whether or not the DevTools remote debugging server is enabled.
boolean isRemoteDebuggingEnabled() = 6;
// Deprecated, use getCrashReporterController().
ICrashReporterController getCrashReporterControllerV80(
in IObjectWrapper appContext) = 7;
// Initializes WebLayer and starts loading.
//
// It is expected that either loadAsync or loadSync is called before anything else.
//
// @param appContext A Context that refers to the Application using WebLayer.
// @param remoteContext A Context that refers to the WebLayer provider package.
// @param loadedCallback A ValueCallback that will be called when load completes.
void loadAsync(in IObjectWrapper appContext,
in IObjectWrapper remoteContext,
in IObjectWrapper loadedCallback) = 8;
// Initializes WebLayer, starts loading and blocks until loading has completed.
//
// It is expected that either loadAsync or loadSync is called before anything else.
//
// @param appContext A Context that refers to the Application using WebLayer.
// @param remoteContext A Context that refers to the WebLayer provider package.
void loadSync(in IObjectWrapper appContext,
in IObjectWrapper remoteContext) = 9;
// Returns the singleton crash reporter controller. If WebLayer has not been
// initialized, does the minimum initialization needed for the crash reporter.
ICrashReporterController getCrashReporterController(
in IObjectWrapper appContext) = 7;
in IObjectWrapper appContext,
in IObjectWrapper remoteContext) = 10;
}
......@@ -28,13 +28,20 @@ public abstract class ChildProcessService extends Service {
super.onCreate();
try {
Context appContext = getApplicationContext();
ClassLoader remoteClassLoader =
WebLayer.getOrCreateRemoteClassLoaderForChildProcess(appContext);
mImpl = IChildProcessService.Stub.asInterface(
(IBinder) remoteClassLoader
.loadClass("org.chromium.weblayer_private.ChildProcessServiceImpl")
.getMethod("create", Service.class, Context.class)
.invoke(null, this, appContext));
Context remoteContext = WebLayer.getOrCreateRemoteContext(appContext);
if (WebLayer.getSupportedMajorVersion(appContext) < 81) {
mImpl = IChildProcessService.Stub.asInterface(
(IBinder) remoteContext.getClassLoader()
.loadClass("org.chromium.weblayer_private.ChildProcessServiceImpl")
.getMethod("create", Service.class, Context.class)
.invoke(null, this, appContext));
} else {
mImpl = IChildProcessService.Stub.asInterface(
(IBinder) remoteContext.getClassLoader()
.loadClass("org.chromium.weblayer_private.ChildProcessServiceImpl")
.getMethod("create", Service.class, Context.class, Context.class)
.invoke(null, this, appContext, remoteContext));
}
mImpl.onCreate();
} catch (Exception e) {
throw new APICallException(e);
......
......@@ -134,10 +134,17 @@ public final class CrashReporterController {
return this;
}
try {
mImpl = WebLayer.getIWebLayer(appContext)
.getCrashReporterController(ObjectWrapper.wrap(appContext));
if (WebLayer.getSupportedMajorVersion(appContext) < 81) {
mImpl = WebLayer.getIWebLayer(appContext)
.getCrashReporterControllerV80(ObjectWrapper.wrap(appContext));
} else {
mImpl = WebLayer.getIWebLayer(appContext)
.getCrashReporterController(ObjectWrapper.wrap(appContext),
ObjectWrapper.wrap(
WebLayer.getOrCreateRemoteContext(appContext)));
}
mImpl.setClient(new CrashReporterControllerClientImpl());
} catch (RemoteException e) {
} catch (Exception e) {
throw new APICallException(e);
}
return this;
......
......@@ -10,7 +10,6 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.support.v4.app.Fragment;
import android.util.AndroidRuntimeException;
......@@ -44,8 +43,9 @@ public final class WebLayer {
// load the code. Do not set this in production APKs!
private static final String PACKAGE_MANIFEST_KEY = "org.chromium.weblayer.WebLayerPackage";
@SuppressWarnings("StaticFieldLeak")
@Nullable
private static ClassLoader sRemoteClassLoader;
private static Context sRemoteContext;
@Nullable
private static WebLayerLoader sLoader;
......@@ -168,7 +168,7 @@ public final class WebLayer {
int majorVersion = -1;
String version = "<unavailable>";
try {
remoteClassLoader = getOrCreateRemoteClassLoader(appContext);
remoteClassLoader = getOrCreateRemoteContext(appContext).getClassLoader();
Class factoryClass = remoteClassLoader.loadClass(
"org.chromium.weblayer_private.WebLayerFactoryImpl");
// NOTE: the 20 comes from the previous scheme of incrementing versioning. It must
......@@ -219,12 +219,21 @@ public final class WebLayer {
return;
}
try {
getIWebLayer(appContext)
.loadAsync(ObjectWrapper.wrap(appContext),
ObjectWrapper.wrap((ValueCallback<Boolean>) result -> {
onWebLayerReady();
}));
} catch (RemoteException e) {
if (getMajorVersion() < 81) {
getIWebLayer(appContext)
.loadAsyncV80(ObjectWrapper.wrap(appContext),
ObjectWrapper.wrap((ValueCallback<Boolean>) result -> {
onWebLayerReady();
}));
} else {
getIWebLayer(appContext)
.loadAsync(ObjectWrapper.wrap(appContext),
ObjectWrapper.wrap(getOrCreateRemoteContext(appContext)),
ObjectWrapper.wrap((ValueCallback<Boolean>) result -> {
onWebLayerReady();
}));
}
} catch (Exception e) {
throw new APICallException(e);
}
}
......@@ -239,10 +248,16 @@ public final class WebLayer {
return null;
}
try {
getIWebLayer(appContext).loadSync(ObjectWrapper.wrap(appContext));
if (getMajorVersion() < 81) {
getIWebLayer(appContext).loadSyncV80(ObjectWrapper.wrap(appContext));
} else {
getIWebLayer(appContext)
.loadSync(ObjectWrapper.wrap(appContext),
ObjectWrapper.wrap(getOrCreateRemoteContext(appContext)));
}
onWebLayerReady();
return mWebLayer;
} catch (RemoteException e) {
} catch (Exception e) {
throw new APICallException(e);
}
}
......@@ -350,53 +365,37 @@ public final class WebLayer {
return getWebLayerLoader(appContext).getIWebLayer(appContext);
}
@SuppressWarnings("NewApi")
static ClassLoader getOrCreateRemoteClassLoaderForChildProcess(Context appContext)
throws PackageManager.NameNotFoundException, ReflectiveOperationException {
if (sRemoteClassLoader != null) {
return sRemoteClassLoader;
}
if (getImplPackageName(appContext) == null && Process.isIsolated()
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
// In <= M, the WebView update service is not available in isolated processes. This
// causes a crash when trying to initialize WebView through the normal machinery, so we
// need to directly make the remote context here.
String packageName = (String) Class.forName("android.webkit.WebViewFactory")
.getMethod("getWebViewPackageName")
.invoke(null);
sRemoteClassLoader =
appContext
.createPackageContext(packageName,
Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE)
.getClassLoader();
return sRemoteClassLoader;
}
return getOrCreateRemoteClassLoader(appContext);
}
/**
* Creates a ClassLoader for the remote (weblayer implementation) side.
* Creates a Context for the remote (weblayer implementation) side.
*/
static ClassLoader getOrCreateRemoteClassLoader(Context appContext)
static Context getOrCreateRemoteContext(Context appContext)
throws PackageManager.NameNotFoundException, ReflectiveOperationException {
if (sRemoteClassLoader != null) {
return sRemoteClassLoader;
if (sRemoteContext != null) {
return sRemoteContext;
}
Class<?> webViewFactoryClass = Class.forName("android.webkit.WebViewFactory");
String implPackageName = getImplPackageName(appContext);
if (implPackageName == null) {
sRemoteClassLoader = createRemoteClassLoaderFromWebViewFactory(appContext);
if (implPackageName != null) {
sRemoteContext = createRemoteContextFromPackageName(appContext, implPackageName);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Method getContext =
webViewFactoryClass.getDeclaredMethod("getWebViewContextAndSetProvider");
getContext.setAccessible(true);
sRemoteContext = (Context) getContext.invoke(null);
} else {
sRemoteClassLoader = createRemoteClassLoaderFromPackage(appContext, implPackageName);
implPackageName =
(String) webViewFactoryClass.getMethod("getWebViewPackageName").invoke(null);
sRemoteContext = createRemoteContextFromPackageName(appContext, implPackageName);
}
return sRemoteClassLoader;
return sRemoteContext;
}
/**
* Creates a ClassLoader 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
* intended for testing, not production use.
*/
private static ClassLoader createRemoteClassLoaderFromPackage(
private static Context createRemoteContextFromPackageName(
Context appContext, String implPackageName)
throws PackageManager.NameNotFoundException, ReflectiveOperationException {
// Load the code for the target package.
......@@ -414,34 +413,7 @@ public final class WebLayer {
sPackageInfo.setAccessible(true);
sPackageInfo.set(null, implPackageInfo);
return remoteContext.getClassLoader();
}
/**
* Creates a ClassLoader for the remote (weblayer implementation) side
* using WebViewFactory to load the current WebView implementation.
*/
private static ClassLoader createRemoteClassLoaderFromWebViewFactory(Context appContext)
throws ReflectiveOperationException {
Class<?> webViewFactory = Class.forName("android.webkit.WebViewFactory");
Class<?> providerClass;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// In M+ this method loads the native library and the Java code, and adds the assets
// to the app.
Method getProviderClass = webViewFactory.getDeclaredMethod("getProviderClass");
getProviderClass.setAccessible(true);
providerClass = (Class) getProviderClass.invoke(null);
} else {
// In L we have to load the native library separately first.
Method loadNativeLibrary = webViewFactory.getDeclaredMethod("loadNativeLibrary");
loadNativeLibrary.setAccessible(true);
loadNativeLibrary.invoke(null);
// In L the method had a different name but still adds the assets to the app.
Method getFactoryClass = webViewFactory.getDeclaredMethod("getFactoryClass");
getFactoryClass.setAccessible(true);
providerClass = (Class) getFactoryClass.invoke(null);
}
return providerClass.getClassLoader();
return remoteContext;
}
private static String sanitizeProfileName(String profileName) {
......
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