Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 04526703 authored by Torne (Richard Coles)'s avatar Torne (Richard Coles) Committed by Richard Coles
Browse files

Support loading a stub WebView using a donor package.

Support loading a WebView package which specifies the name of a "donor"
that provides missing files. This allows a preinstalled stub WebView to
function by loading its code and assets from the preinstalled Monochrome
implementation, as long as the versions are close enough that the
manifest contents are compatible, which should be fine since
preinstalled versions will match.

To do this, we replace the stub's code paths in AppplicationInfo with
the donor's, so that all Java and native code and resources are loaded
from the donor APK at runtime instead of from the (mostly empty) stub.

To get the ClassLoader with the modified path cached as if it was the
regular path, we introduce a new "cacheKey" parameter in
ApplicationLoaders.

Bug: 21643067
Test: build "new" stub WebView upstream in chromium and test loading
Change-Id: I08cc9122b1c9def3e1206974f3e0e8973cca3419
parent d70f423d
Loading
Loading
Loading
Loading
+23 −10
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.app;

import android.os.Build;
import android.os.Trace;
import android.text.TextUtils;
import android.util.ArrayMap;
import com.android.internal.os.PathClassLoaderFactory;
import dalvik.system.PathClassLoader;
@@ -31,6 +32,14 @@ public class ApplicationLoaders {
    ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
                               String librarySearchPath, String libraryPermittedPath,
                               ClassLoader parent) {
        // For normal usage the cache key used is the same as the zip path.
        return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
                              libraryPermittedPath, parent, zip);
    }

    private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
                                       String librarySearchPath, String libraryPermittedPath,
                                       ClassLoader parent, String cacheKey) {
        /*
         * This is the parent we use if they pass "null" in.  In theory
         * this should be the "system" class loader; in practice we
@@ -50,7 +59,7 @@ public class ApplicationLoaders {
             * new ClassLoader for the zip archive.
             */
            if (parent == baseParent) {
                ClassLoader loader = mLoaders.get(zip);
                ClassLoader loader = mLoaders.get(cacheKey);
                if (loader != null) {
                    return loader;
                }
@@ -71,7 +80,7 @@ public class ApplicationLoaders {
                setupVulkanLayerPath(pathClassloader, librarySearchPath);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

                mLoaders.put(zip, pathClassloader);
                mLoaders.put(cacheKey, pathClassloader);
                return pathClassloader;
            }

@@ -87,12 +96,16 @@ public class ApplicationLoaders {
     * by this class. This is used in the WebView zygote, where its presence in the cache speeds up
     * startup and enables memory sharing.
     */
    public ClassLoader createAndCacheWebViewClassLoader(String packagePath, String libsPath) {
    public ClassLoader createAndCacheWebViewClassLoader(String packagePath, String libsPath,
                                                        String cacheKey) {
        // The correct paths are calculated by WebViewZygote in the system server and passed to
        // us here. We hardcode the other parameters: WebView always targets the current SDK,
        // does not need to use non-public system libraries, and uses the base classloader as its
        // parent to permit usage of the cache.
      return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null);
        // The cache key is passed separately to enable the stub WebView to be cached under the
        // stub's APK path, when the actual package path is the donor APK.
        return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null,
                              cacheKey);
    }

    private static native void setupVulkanLayerPath(ClassLoader classLoader, String librarySearchPath);
+6 −3
Original line number Diff line number Diff line
@@ -487,11 +487,11 @@ public class ZygoteProcess {
     * Instructs the zygote to pre-load the classes and native libraries at the given paths
     * for the specified abi. Not all zygotes support this function.
     */
    public void preloadPackageForAbi(String packagePath, String libsPath, String abi)
            throws ZygoteStartFailedEx, IOException {
    public void preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
                                     String abi) throws ZygoteStartFailedEx, IOException {
        synchronized(mLock) {
            ZygoteState state = openZygoteSocketIfNeeded(abi);
            state.writer.write("3");
            state.writer.write("4");
            state.writer.newLine();

            state.writer.write("--preload-package");
@@ -503,6 +503,9 @@ public class ZygoteProcess {
            state.writer.write(libsPath);
            state.writer.newLine();

            state.writer.write(cacheKey);
            state.writer.newLine();

            state.writer.flush();
        }
    }
+49 −3
Original line number Diff line number Diff line
@@ -280,6 +280,44 @@ public final class WebViewFactory {
        }
    }

    /**
     * If the ApplicationInfo provided is for a stub WebView, fix up the object to include the
     * required values from the donor package. If the ApplicationInfo is for a full WebView,
     * leave it alone. Throws MissingWebViewPackageException if the donor is missing.
     */
    private static void fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm) {
        String donorPackageName = null;
        if (ai.metaData != null) {
            donorPackageName = ai.metaData.getString("com.android.webview.WebViewDonorPackage");
        }
        if (donorPackageName != null) {
            PackageInfo donorPackage;
            try {
                donorPackage = pm.getPackageInfo(
                        donorPackageName,
                        PackageManager.GET_SHARED_LIBRARY_FILES
                        | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
                        | PackageManager.MATCH_UNINSTALLED_PACKAGES
                        | PackageManager.MATCH_FACTORY_ONLY);
            } catch (PackageManager.NameNotFoundException e) {
                throw new MissingWebViewPackageException("Failed to find donor package: " +
                                                         donorPackageName);
            }
            ApplicationInfo donorInfo = donorPackage.applicationInfo;

            // Replace the stub's code locations with the donor's.
            ai.sourceDir = donorInfo.sourceDir;
            ai.splitSourceDirs = donorInfo.splitSourceDirs;
            ai.nativeLibraryDir = donorInfo.nativeLibraryDir;
            ai.secondaryNativeLibraryDir = donorInfo.secondaryNativeLibraryDir;

            // Copy the donor's primary and secondary ABIs, since the stub doesn't have native code
            // and so they are unset.
            ai.primaryCpuAbi = donorInfo.primaryCpuAbi;
            ai.secondaryCpuAbi = donorInfo.secondaryCpuAbi;
        }
    }

    private static Context getWebViewContextAndSetProvider() {
        Application initialApplication = AppGlobals.getInitialApplication();
        try {
@@ -307,9 +345,10 @@ public final class WebViewFactory {
            }
            // Fetch package info and verify it against the chosen package
            PackageInfo newPackageInfo = null;
            PackageManager pm = initialApplication.getPackageManager();
            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "PackageManager.getPackageInfo()");
            try {
                newPackageInfo = initialApplication.getPackageManager().getPackageInfo(
                newPackageInfo = pm.getPackageInfo(
                    response.packageInfo.packageName,
                    PackageManager.GET_SHARED_LIBRARY_FILES
                    | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
@@ -328,12 +367,15 @@ public final class WebViewFactory {
            // failure
            verifyPackageInfo(response.packageInfo, newPackageInfo);

            ApplicationInfo ai = newPackageInfo.applicationInfo;
            fixupStubApplicationInfo(ai, pm);

            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
                    "initialApplication.createApplicationContext");
            try {
                // Construct an app context to load the Java code into the current app.
                Context webViewContext = initialApplication.createApplicationContext(
                        newPackageInfo.applicationInfo,
                        ai,
                        Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
                sPackageInfo = newPackageInfo;
                return webViewContext;
@@ -449,7 +491,11 @@ public final class WebViewFactory {
     */
    public static int onWebViewProviderChanged(PackageInfo packageInfo) {
        String[] nativeLibs = null;
        String originalSourceDir = packageInfo.applicationInfo.sourceDir;
        try {
            fixupStubApplicationInfo(packageInfo.applicationInfo,
                                     AppGlobals.getInitialApplication().getPackageManager());

            nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths(packageInfo);
            if (nativeLibs != null) {
                long newVmSize = 0L;
@@ -498,7 +544,7 @@ public final class WebViewFactory {
            Log.e(LOGTAG, "error preparing webview native library", t);
        }

        WebViewZygote.onWebViewProviderChanged(packageInfo);
        WebViewZygote.onWebViewProviderChanged(packageInfo, originalSourceDir);

        return prepareWebViewInSystemServer(nativeLibs);
    }
+11 −2
Original line number Diff line number Diff line
@@ -66,6 +66,13 @@ public class WebViewZygote {
    @GuardedBy("sLock")
    private static PackageInfo sPackage;

    /**
     * Cache key for the selected WebView package's classloader. This is set from
     * #onWebViewProviderChanged().
     */
    @GuardedBy("sLock")
    private static String sPackageCacheKey;

    /**
     * Flag for whether multi-process WebView is enabled. If this is false, the zygote
     * will not be started.
@@ -118,9 +125,10 @@ public class WebViewZygote {
        }
    }

    public static void onWebViewProviderChanged(PackageInfo packageInfo) {
    public static void onWebViewProviderChanged(PackageInfo packageInfo, String cacheKey) {
        synchronized (sLock) {
            sPackage = packageInfo;
            sPackageCacheKey = cacheKey;

            // If multi-process is not enabled, then do not start the zygote service.
            if (!sMultiprocessEnabled) {
@@ -210,7 +218,8 @@ public class WebViewZygote {
                    TextUtils.join(File.pathSeparator, zipPaths);

            Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
            sZygote.preloadPackageForAbi(zip, librarySearchPath, Build.SUPPORTED_ABIS[0]);
            sZygote.preloadPackageForAbi(zip, librarySearchPath, sPackageCacheKey,
                                         Build.SUPPORTED_ABIS[0]);
        } catch (Exception e) {
            Log.e(LOGTAG, "Error connecting to " + serviceName, e);
            sZygote = null;
+8 −3
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.util.Log;
import android.webkit.WebViewFactory;
import android.webkit.WebViewFactoryProvider;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

@@ -67,16 +68,20 @@ class WebViewZygoteInit {
        }

        @Override
        protected boolean handlePreloadPackage(String packagePath, String libsPath) {
        protected boolean handlePreloadPackage(String packagePath, String libsPath,
                                               String cacheKey) {
            // Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
            // our children will reuse the same classloader instead of creating their own.
            // This enables us to preload Java and native code in the webview zygote process and
            // have the preloaded versions actually be used post-fork.
            ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
                    packagePath, libsPath);
                    packagePath, libsPath, cacheKey);

            // Add the APK to the Zygote's list of allowed files for children.
            Zygote.nativeAllowFileAcrossFork(packagePath);
            String[] packageList = TextUtils.split(packagePath, File.pathSeparator);
            for (String packageEntry : packageList) {
                Zygote.nativeAllowFileAcrossFork(packageEntry);
            }

            // Once we have the classloader, look up the WebViewFactoryProvider implementation and
            // call preloadInZygote() on it to give it the opportunity to preload the native library
Loading