tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 17856cb36eb1a2048b471e35e5f5529ae2a48f4d
parent a9df923a22e771b8ba02609154e844cb2215614a
Author: Olivia Hall <ohall@mozilla.com>
Date:   Mon, 20 Oct 2025 21:37:56 +0000

Bug 1991512 - Enable App Zygote Preloading on Runtime r=geckoview-reviewers,geckoview-api-reviewers,kaya,m_kato

Patch introduces a way to turn on app Zygote preloading on GeckoView.
Adds:
* --enable-isolated-zygote-process w/ MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_WITH_ZYGOTE environment flag
  * For managing turning on/off elements in CI.
* GeckoRuntimeSettings API of `appZygoteProcessEnabled` and `getAppZygoteProcessEnabled` to manage runtime initialization of app Zygote process.
* Some utilities to determine what is a content process.

Differential Revision: https://phabricator.services.mozilla.com/D266901

Diffstat:
Mmobile/android/geckoview/api.txt | 2++
Mmobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/IsolatedProcessTest.kt | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Environment.java | 4++++
Mmobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java | 1+
Mmobile/android/geckoview/src/main/AndroidManifest_overlay.jinja | 16++++++++++++++--
Mmobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java | 6++++++
Mmobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoChildProcessServices.jinja | 1+
Mmobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java | 30+++++++++++++++++++-----------
Mmobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessType.java | 24+++++++++++++++++++++++-
Mmobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java | 9++++++++-
Mmobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ServiceAllocator.java | 10+++++++---
Mmobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ServiceUtils.java | 6+++---
Amobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ZygotePreload.java | 30++++++++++++++++++++++++++++++
Mmobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java | 4++++
Mmobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java | 35+++++++++++++++++++++++++++++++++++
Mmobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md | 7+++++--
Mmobile/android/moz.configure | 19+++++++++++++++++++
Mmobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java | 6+++++-
Mmobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/XpcshellTestRunnerService.java | 4++++
19 files changed, 340 insertions(+), 24 deletions(-)

diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt @@ -995,6 +995,7 @@ package org.mozilla.geckoview { @AnyThread public final class GeckoRuntimeSettings extends RuntimeSettings { method public boolean getAboutConfigEnabled(); method public int getAllowInsecureConnections(); + method public boolean getAppZygoteProcessEnabled(); method @NonNull public String[] getArguments(); method public boolean getAutomaticFontSizeAdjustment(); method @NonNull public String getBannedPorts(); @@ -1131,6 +1132,7 @@ package org.mozilla.geckoview { ctor public Builder(); method @NonNull public GeckoRuntimeSettings.Builder aboutConfigEnabled(boolean); method @NonNull public GeckoRuntimeSettings.Builder allowInsecureConnections(int); + method @NonNull public GeckoRuntimeSettings.Builder appZygoteProcessEnabled(boolean); method @NonNull public GeckoRuntimeSettings.Builder arguments(@NonNull String[]); method @NonNull public GeckoRuntimeSettings.Builder automaticFontSizeAdjustment(boolean); method @NonNull public GeckoRuntimeSettings.Builder configFilePath(@Nullable String); diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/IsolatedProcessTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/IsolatedProcessTest.kt @@ -3,9 +3,14 @@ package org.mozilla.geckoview.test +import android.os.Build +import android.os.ParcelFileDescriptor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest +import androidx.test.filters.SdkSuppress +import androidx.test.platform.app.InstrumentationRegistry import junit.framework.TestCase.assertFalse +import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertTrue import org.hamcrest.Matchers.equalTo import org.junit.Assume.assumeThat @@ -16,6 +21,38 @@ import org.mozilla.geckoview.GeckoRuntimeSettings @RunWith(AndroidJUnit4::class) @MediumTest class IsolatedProcessTest : BaseSessionTest() { + private val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation + + /** + * Structure to hold ps command data. + */ + data class Process(val user: String, val pid: String) + + /** + * Get results from ps command. Filtered on org.mozilla.geckoview.test. + * + * Key is process name. + */ + fun getTestRunnerProcesses(): Map<String, Process> { + val shellCommand = uiAutomation.executeShellCommand( + "ps -A -o USER,PID,NAME", + ) + val result = mutableMapOf<String, Process>() + ParcelFileDescriptor.AutoCloseInputStream(shellCommand).use { inputStream -> + inputStream.bufferedReader(Charsets.UTF_8).lines().forEach { line -> + val cols = line.split("\\s+".toRegex()) + val name = cols.last() + val user = cols.first() + val pid = cols[1] + // Grep doesn't seem to work with executeShellCommand, so manually pulling out items of interest. + if (name.isNotBlank() && name.contains("org.mozilla.geckoview.test")) { + result[name] = Process(user, pid) + } + } + } + return result + } + @Test fun isolatedProcessSetting() { val settingsEnabled = GeckoRuntimeSettings.Builder().isolatedProcessEnabled(true).build() @@ -51,4 +88,117 @@ class IsolatedProcessTest : BaseSessionTest() { sessionRule.runtime.settings.isolatedProcessEnabled, ) } + + @Test + fun appZygoteSetting() { + val settingsEnabled = GeckoRuntimeSettings.Builder().appZygoteProcessEnabled(true).build() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + assertTrue( + "App Zygote preloading should be enabled by settings", + settingsEnabled.appZygoteProcessEnabled, + ) + } else { + assertFalse( + "App Zygote preloading should not be enabled by settings on Android 9 or below.", + settingsEnabled.appZygoteProcessEnabled, + ) + } + + val settingsDisabled = GeckoRuntimeSettings.Builder().appZygoteProcessEnabled(false).build() + assertFalse( + "App Zygote preloading process should be disabled by settings", + settingsDisabled.appZygoteProcessEnabled, + ) + } + + @Test + fun appZygoteEnabled() { + assumeThat(sessionRule.env.isAppZygoteProcess, equalTo(true)) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + assertTrue( + "App Zygote preloading process is enabled on runtime settings", + sessionRule.runtime.settings.appZygoteProcessEnabled, + ) + } else { + assertFalse( + "App Zygote preloading should not be enabled by settings on Android 9 or below.", + sessionRule.runtime.settings.appZygoteProcessEnabled, + ) + } + } + + @Test + fun appZygoteDisabled() { + assumeThat(sessionRule.env.isAppZygoteProcess, equalTo(false)) + + assertFalse( + "App Zygote preloading is disabled on runtime settings", + sessionRule.runtime.settings.appZygoteProcessEnabled, + ) + } + + @Test + fun conventionalProcessBehavior() { + assumeThat("Isolated process is not enabled", sessionRule.env.isIsolatedProcess, equalTo(false)) + assumeThat("App Zygote preloading is not enabled", sessionRule.env.isAppZygoteProcess, equalTo(false)) + + val processes = getTestRunnerProcesses() + assertFalse("App Zygote process is not present.", processes.containsKey("org.mozilla.geckoview.test_zygote")) + + var contentProc: Process? = null + for (process in processes) { + if (process.key.contains("tab", ignoreCase = false)) { + assertTrue("User ID does not indicate process isolation.", process.value.user.contains("u0_a")) + contentProc = process.value + break + } + } + assertNotNull("Content process successfully identified.", contentProc) + } + + @Test + fun isolatedProcessBehavior() { + assumeThat("Isolated process is enabled", sessionRule.env.isIsolatedProcess, equalTo(true)) + assumeThat("App Zygote preloading is not enabled", sessionRule.env.isAppZygoteProcess, equalTo(false)) + + val processes = getTestRunnerProcesses() + assertFalse("App Zygote process is not present.", processes.containsKey("org.mozilla.geckoview.test_zygote")) + + var contentProc: Process? = null + for (process in processes) { + if (process.key.contains("isolatedTab")) { + assertTrue("User ID indicates process isolation.", process.value.user.contains("u0_i")) + contentProc = process.value + break + } + } + assertNotNull("Content process successfully identified.", contentProc) + } + + @Test + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q) + fun appZygoteProcessBehavior() { + assumeThat("Isolated process is not enabled", sessionRule.env.isIsolatedProcess, equalTo(false)) + assumeThat("App Zygote preloading is enabled", sessionRule.env.isAppZygoteProcess, equalTo(true)) + + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.waitForPageStop() + + // Check for Zygote process + val processes = getTestRunnerProcesses() + assertTrue("App Zygote process is present.", processes.containsKey("org.mozilla.geckoview.test_zygote")) + + // Check for isolated process + Zygote naming + var contentProc: Process? = null + for (process in processes) { + if (process.key.contains("isolatedTabWithZygote")) { + assertTrue("User ID indicates process isolation.", process.value.user.contains("u0_i")) + contentProc = process.value + break + } + } + assertNotNull("Content process successfully identified.", contentProc) + } } diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Environment.java b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Environment.java @@ -70,6 +70,10 @@ public class Environment { return BuildConfig.MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS; } + public boolean isAppZygoteProcess() { + return getEnvVar("MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_WITH_ZYGOTE").equals("1"); + } + public long getScaledTimeoutMillis() { if (isX86()) { return isEmulator() ? DEFAULT_X86_EMULATOR_TIMEOUT_MILLIS : DEFAULT_X86_DEVICE_TIMEOUT_MILLIS; diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java @@ -207,6 +207,7 @@ public class RuntimeCreator { .crashHandler(TestCrashHandler.class) .experimentDelegate(sRuntimeExperimentDelegateProxy) .isolatedProcessEnabled(BuildConfig.MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS) + .appZygoteProcessEnabled(env.isAppZygoteProcess()) .build(); sRuntime = diff --git a/mobile/android/geckoview/src/main/AndroidManifest_overlay.jinja b/mobile/android/geckoview/src/main/AndroidManifest_overlay.jinja @@ -3,9 +3,11 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" +<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="org.mozilla.geckoview"> - <application> + <application + android:zygotePreloadName="org.mozilla.gecko.process.ZygotePreload" + tools:targetApi="q"> {% for id in range(0, MOZ_ANDROID_CONTENT_SERVICE_COUNT | int) %} <service android:name="org.mozilla.gecko.process.GeckoChildProcessServices$tab{{ id }}" @@ -21,6 +23,16 @@ android:isolatedProcess="true" android:process=":isolatedTab{{ id }}"> </service> + <service + android:name="org.mozilla.gecko.process.GeckoChildProcessServices$isolatedTabWithZygote{{ id }}" + android:enabled="true" + android:exported="false" + android:isolatedProcess="true" + {% if (id == 0) %} + android:useAppZygote="true" + {% endif %} + android:process=":isolatedTabWithZygote{{ id }}"> + </service> {% endfor %} </application> </manifest> diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java @@ -148,6 +148,8 @@ public class GeckoThread extends Thread { 1 << 3; // Disable low-memory detection and notifications. public static final int FLAG_CHILD = 1 << 4; // This is a child process. public static final int FLAG_CONTENT_ISOLATED = 1 << 5; // Content service is isolated process. + public static final int FLAG_CONTENT_ISOLATED_HAS_ZYGOTE = + 1 << 6; // Content service has app Zygote enabled. /* package */ static final String EXTRA_ARGS = "args"; @@ -499,6 +501,10 @@ public class GeckoThread extends Thread { GeckoProcessManager.getInstance().setIsolatedProcessEnabled(true); } + if ((mInitInfo.flags & FLAG_CONTENT_ISOLATED_HAS_ZYGOTE) != 0) { + GeckoProcessManager.getInstance().setAppZygoteEnabled(true); + } + if ((mInitInfo.flags & FLAG_DEBUGGING) != 0) { try { Thread.sleep(5 * 1000 /* 5 seconds */); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoChildProcessServices.jinja b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoChildProcessServices.jinja @@ -21,5 +21,6 @@ public class GeckoChildProcessServices { {% for id in range(0, MOZ_ANDROID_CONTENT_SERVICE_COUNT | int) %} public static final class tab{{ id }} extends GeckoServiceChildProcess {} public static final class isolatedTab{{ id }} extends GeckoServiceChildProcess {} + public static final class isolatedTabWithZygote{{ id }} extends GeckoServiceChildProcess {} {% endfor %} } diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java @@ -42,6 +42,7 @@ public final class GeckoProcessManager extends IProcessManager.Stub { private final String mInstanceId; private boolean mIsolatedProcess = false; + private boolean mAppZygote = false; public static GeckoProcessManager getInstance() { return INSTANCE; @@ -128,9 +129,8 @@ public final class GeckoProcessManager extends IProcessManager.Stub { throw new RuntimeException("Invalid PID"); } - if (type == GeckoProcessType.CONTENT - && GeckoProcessManager.getInstance().isIsolatedProcessEnabled()) { - mType = GeckoProcessType.CONTENT_ISOLATED; + if (type == GeckoProcessType.CONTENT) { + mType = GeckoProcessType.determineContentProcessType(); } else { mType = type; } @@ -309,8 +309,11 @@ public final class GeckoProcessManager extends IProcessManager.Stub { } } - private static boolean isContent(final GeckoProcessType type) { - return type == GeckoProcessType.CONTENT || type == GeckoProcessType.CONTENT_ISOLATED; + /** package */ + static boolean isContent(final GeckoProcessType type) { + return type == GeckoProcessType.CONTENT + || type == GeckoProcessType.CONTENT_ISOLATED + || type == GeckoProcessType.CONTENT_ISOLATED_WITH_ZYGOTE; } private static class NonContentConnection extends ChildConnection { @@ -426,12 +429,7 @@ public final class GeckoProcessManager extends IProcessManager.Stub { public ContentConnection( @NonNull final ServiceAllocator allocator, @NonNull final PriorityLevel initialPriority) { - super( - allocator, - GeckoProcessManager.getInstance().isIsolatedProcessEnabled() - ? GeckoProcessType.CONTENT_ISOLATED - : GeckoProcessType.CONTENT, - initialPriority); + super(allocator, GeckoProcessType.determineContentProcessType(), initialPriority); } @Override @@ -715,11 +713,21 @@ public final class GeckoProcessManager extends IProcessManager.Stub { mIsolatedProcess = enabled; } + /** Sets whether the content service runs on isolated process with app Zygote preloading. */ + public void setAppZygoteEnabled(final boolean enabled) { + mAppZygote = enabled; + } + /** true if the content service runs on isolated process. */ public boolean isIsolatedProcessEnabled() { return mIsolatedProcess; } + /** true if app Zygote preloading is enabled. */ + public boolean isAppZygoteEnabled() { + return mAppZygote; + } + public void crashChild(@NonNull final Selector selector) { XPCOMEventTarget.launcherThread() .execute( diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessType.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessType.java @@ -4,6 +4,7 @@ package org.mozilla.gecko.process; +import android.os.Build; import org.mozilla.gecko.annotation.WrapForJNI; @WrapForJNI @@ -21,7 +22,8 @@ public enum GeckoProcessType { OBSOLETE2("sandboxbroker"), FORKSERVER("forkserver"), UTILITY("utility"), - CONTENT_ISOLATED("isolatedTab"); + CONTENT_ISOLATED("isolatedTab"), + CONTENT_ISOLATED_WITH_ZYGOTE("isolatedTabWithZygote"); private final String mGeckoName; @@ -38,4 +40,24 @@ public enum GeckoProcessType { private static GeckoProcessType fromInt(final int type) { return values()[type]; } + + /** + * Convenience method to determine what the process type should be based on runtime settings. + * + * <p>Note: App Zygote isolated process enabled will take precedence over plain isolated process + * enabled. Must be SDK 29 or up. + * + * @return The corresponding process type. + */ + /** package */ + static GeckoProcessType determineContentProcessType() { + final GeckoProcessManager pm = GeckoProcessManager.getInstance(); + if (pm.isAppZygoteEnabled() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return GeckoProcessType.CONTENT_ISOLATED_WITH_ZYGOTE; + } else if (pm.isIsolatedProcessEnabled()) { + return GeckoProcessType.CONTENT_ISOLATED; + } else { + return GeckoProcessType.CONTENT; + } + } } diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java @@ -5,9 +5,11 @@ package org.mozilla.gecko.process; +import android.app.Application; import android.app.Service; import android.content.Intent; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -53,7 +55,12 @@ public class GeckoServiceChildProcess extends Service { @Override public void onCreate() { super.onCreate(); - Log.i(LOGTAG, "onCreate"); + + final StringBuilder sb = new StringBuilder("onCreate"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + sb.append(", process name: ").append(Application.getProcessName()); + } + Log.i(LOGTAG, sb.toString()); if (sState != ProcessState.NEW) { // We don't support reusing processes, and this could get us in a really weird state, diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ServiceAllocator.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ServiceAllocator.java @@ -214,7 +214,9 @@ import org.mozilla.gecko.util.XPCOMEventTarget; } public boolean isContent() { - return mType == GeckoProcessType.CONTENT || mType == GeckoProcessType.CONTENT_ISOLATED; + return mType == GeckoProcessType.CONTENT + || mType == GeckoProcessType.CONTENT_ISOLATED + || mType == GeckoProcessType.CONTENT_ISOLATED_WITH_ZYGOTE; } public GeckoProcessType getType() { @@ -530,7 +532,7 @@ import org.mozilla.gecko.util.XPCOMEventTarget; @SuppressLint("NewApi") // Linter cannot follow our hasQApis() checks private String allocate(@NonNull final GeckoProcessType type) { XPCOMEventTarget.assertOnLauncherThread(); - if (type != GeckoProcessType.CONTENT && type != GeckoProcessType.CONTENT_ISOLATED) { + if (!GeckoProcessManager.isContent(type)) { // No unique id necessary return null; } @@ -538,7 +540,9 @@ import org.mozilla.gecko.util.XPCOMEventTarget; // Lazy initialization of mContentAllocPolicy to ensure that it is constructed on the // launcher thread. if (mContentAllocPolicy == null) { - if (hasQApis() && type == GeckoProcessType.CONTENT_ISOLATED) { + if (hasQApis() + && (type == GeckoProcessType.CONTENT_ISOLATED + || type == GeckoProcessType.CONTENT_ISOLATED_WITH_ZYGOTE)) { mContentAllocPolicy = new IsolatedContentPolicy(); } else { mContentAllocPolicy = new DefaultContentPolicy(); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ServiceUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ServiceUtils.java @@ -47,11 +47,11 @@ import androidx.annotation.NonNull; * * <p>Content services are defined in the manifest as "tab0" through "tabN" for some value of N. * For the purposes of binding to an isolated content service, we simply need to repeatedly re-use - * the definition of "tab0" or "isolatedTab0", the "0" being stored as the - * DEFAULT_ISOLATED_CONTENT_SERVICE_NAME_SUFFIX constant. + * the definition of "tab0" or "isolatedTab0" or "isolatedTabWithZygote0", the "0" being stored as + * the DEFAULT_ISOLATED_CONTENT_SERVICE_NAME_SUFFIX constant. */ public static String buildIsolatedSvcName(@NonNull final GeckoProcessType type) { - if (type == GeckoProcessType.CONTENT || type == GeckoProcessType.CONTENT_ISOLATED) { + if (GeckoProcessManager.isContent(type)) { return buildSvcName(type, DEFAULT_ISOLATED_CONTENT_SERVICE_NAME_SUFFIX); } diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ZygotePreload.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ZygotePreload.java @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.process; + +import android.content.pm.ApplicationInfo; +import android.os.Build; +import android.util.Log; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +@Keep +@RequiresApi(api = Build.VERSION_CODES.Q) +public class ZygotePreload implements android.app.ZygotePreload { + private static final String LOGTAG = "GeckoViewZygotePreload"; + + @Override + public void doPreload(@NonNull final ApplicationInfo applicationInfo) { + Log.i(LOGTAG, "doPreload"); + try { + System.loadLibrary("mozglue"); + System.loadLibrary("xul"); + System.loadLibrary("nss3"); + } catch (final Exception e) { + Log.e(LOGTAG, "An exception occurred when using app Zygote preloading!: ", e); + } + } +} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java @@ -471,6 +471,10 @@ public final class GeckoRuntime implements Parcelable { flags |= GeckoThread.FLAG_CONTENT_ISOLATED; } + if (settings.getAppZygoteProcessEnabled()) { + flags |= GeckoThread.FLAG_CONTENT_ISOLATED_HAS_ZYGOTE; + } + final Class<?> crashHandler = settings.getCrashHandler(); if (crashHandler != null) { try { diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java @@ -10,6 +10,7 @@ import static android.os.Build.VERSION; import android.app.Service; import android.graphics.Rect; +import android.os.Build; import android.os.Bundle; import android.os.LocaleList; import android.os.Parcel; @@ -681,6 +682,27 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { getSettings().mIsolatedProcess = enabled; return this; } + + /** + * Set whether App Zygote preloading should be enabled or not. This must be set before startup. + * + * <p>Will take precedence over{@link #isolatedProcessEnabled(boolean) } if both are enabled. + * + * <p>Only settable on SDK 29 or higher. + * + * @param enabled A flag determining whether or not to enable App Zygote preloading. + * @return The builder instance. + */ + public @NonNull Builder appZygoteProcessEnabled(final boolean enabled) { + if (enabled && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + Log.w(LOGTAG, "Cannot set app Zygote preloading to true below SDK 29 (Android 10)!"); + getSettings().mAppZygoteProcess = false; + return this; + } + getSettings().mAppZygoteProcess = enabled; + + return this; + } } private GeckoRuntime mRuntime; @@ -815,6 +837,7 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { /* package */ boolean mUseMaxScreenDepth; /* package */ boolean mLowMemoryDetection = true; /* package */ boolean mIsolatedProcess = false; + /* package */ boolean mAppZygoteProcess = false; /* package */ float mDisplayDensityOverride = -1.0f; /* package */ int mDisplayDpiOverride; /* package */ int mScreenWidthOverride; @@ -867,6 +890,7 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { mUseMaxScreenDepth = settings.mUseMaxScreenDepth; mLowMemoryDetection = settings.mLowMemoryDetection; mIsolatedProcess = settings.mIsolatedProcess; + mAppZygoteProcess = settings.mAppZygoteProcess; mDisplayDensityOverride = settings.mDisplayDensityOverride; mDisplayDpiOverride = settings.mDisplayDpiOverride; mScreenWidthOverride = settings.mScreenWidthOverride; @@ -2381,6 +2405,15 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { return mIsolatedProcess; } + /** + * Gets whether the App Zygote process is enabled or not for preloading. + * + * @return True if App Zygote preloading is enabled. + */ + public boolean getAppZygoteProcessEnabled() { + return mAppZygoteProcess; + } + @Override // Parcelable public void writeToParcel(final Parcel out, final int flags) { super.writeToParcel(out, flags); @@ -2392,6 +2425,7 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { ParcelableUtils.writeBoolean(out, mUseMaxScreenDepth); ParcelableUtils.writeBoolean(out, mLowMemoryDetection); ParcelableUtils.writeBoolean(out, mIsolatedProcess); + ParcelableUtils.writeBoolean(out, mAppZygoteProcess); out.writeFloat(mDisplayDensityOverride); out.writeInt(mDisplayDpiOverride); out.writeInt(mScreenWidthOverride); @@ -2412,6 +2446,7 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { mUseMaxScreenDepth = ParcelableUtils.readBoolean(source); mLowMemoryDetection = ParcelableUtils.readBoolean(source); mIsolatedProcess = ParcelableUtils.readBoolean(source); + mAppZygoteProcess = ParcelableUtils.readBoolean(source); mDisplayDensityOverride = source.readFloat(); mDisplayDpiOverride = source.readInt(); mScreenWidthOverride = source.readInt(); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md @@ -16,8 +16,12 @@ exclude: true ## v146 - Added `getSafeBrowsingV5Enabled` and `setSafeBrowsingV5Enabled` to [`ContentBlocking.Settings`][146.1] to control whether to use the SafeBrowsing V5 protocol to access the Google SafeBrowsing service. - Added [`Autocomplete.AddressStructure`][146.2] API used to retrieve the structure of an address for a given country. +- Added [`GeckoRuntimeSettings.getAppZygoteProcessEnabled`][146.3] and [`GeckoRuntimeSettings.Builder.appZygoteProcessEnabled`][146.4] to control whether content service runs using App Zygote preloading or not. [146.1]: {{javadoc_uri}}/ContentBlocking.html +[146.2]: {{javadoc_uri}}/Autocomplete.AddressStructure.html +[146.3]: {{javadoc_uri}}/GeckoRuntimeSettings.html#getAppZygoteProcessEnabled +[146.4]: {{javadoc_uri}}/GeckoRuntimeSettings.Builder.html#appZygoteProcessEnabled(boolean) ## v145 - Added [`WebNotification.show`][145.1]. Implementations of `WebNotificationDelegate.onShowNotification` should now call either `show` when the notification is successfully opened, or `dismiss` if it failed. @@ -33,7 +37,6 @@ exclude: true [145.5]: {{javadoc_uri}}/GeckoRuntimeSettings.html#setCertificateTransparencyMode ## v144 -- Added [`GeckoSession.flushSessionState()`][144.1] to immediately notify the registered [`GeckoSession.ProgressDelegate`][144.2] and [`GeckoSession.HistoryDelegate`][144.3] of the current session state. - Added [`GeckoRuntimeSettings.getIsolatedProcessEnabled`][144.4] and [`GeckoRuntimeSettings.Builder.isolatedProcessEnabled`][144.5] to control whether content service runs on isolated process or not. - Added [`ContentBlocking.GOOGLE_SAFE_BROWSING_V5_PROVIDER`][144.6] for the configuration of the SafeBrowsing V5 provider - ⚠️ Removed deprecated `onOptionalPrompt` function signature. ([bug 1972510]({{bugzilla}}1972510)) @@ -1852,4 +1855,4 @@ to allow adding gecko profiler markers. [65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport(android.content.Context,android.os.Bundle,java.lang.String) [65.25]: {{javadoc_uri}}/GeckoResult.html -[api-version]: dcffdbd2bcd2bf198da5645acf5fb0908beac9f7 +[api-version]: bfecdf1c171f62dba8161757bdb91d3345b9283c diff --git a/mobile/android/moz.configure b/mobile/android/moz.configure @@ -40,6 +40,25 @@ set_config("MOZ_ANDROID_CONTENT_SERVICE_COUNT", num_content_services) set_define("MOZ_ANDROID_CONTENT_SERVICE_COUNT", num_content_services) option( + "--enable-isolated-zygote-process", + env="MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_WITH_ZYGOTE", + help="Enable generating content process services with app Zygote preloading and isolated processes", + default=False, +) + +isIsolatedAppZygoteProcessEnabled = depends_if( + "MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_WITH_ZYGOTE" +)(lambda _: True) +set_config( + "MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_WITH_ZYGOTE", + isIsolatedAppZygoteProcessEnabled, +) +set_define( + "MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_WITH_ZYGOTE", + isIsolatedAppZygoteProcessEnabled, +) + +option( "--enable-isolated-process", env="MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS", help="Enable generating content process services with isolatedProcess=true", diff --git a/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java b/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java @@ -22,6 +22,7 @@ import java.util.ArrayDeque; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import org.json.JSONException; import org.json.JSONObject; import org.mozilla.geckoview.AllowOrDeny; @@ -472,7 +473,10 @@ public class TestRunnerActivity extends Activity { .safeBrowsingProviders(google, googleLegacy, google5) .build()) .lowMemoryDetection(false) // Avoid unpredictability in tests - .isolatedProcessEnabled(BuildConfig.MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS); + .isolatedProcessEnabled(BuildConfig.MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS) + .appZygoteProcessEnabled( + Objects.equals( + System.getenv("MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_WITH_ZYGOTE"), "1")); sRuntime = GeckoRuntime.create(this, runtimeSettingsBuilder.build()); diff --git a/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/XpcshellTestRunnerService.java b/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/XpcshellTestRunnerService.java @@ -10,6 +10,7 @@ import android.os.IBinder; import android.util.Log; import androidx.annotation.Nullable; import java.util.HashMap; +import java.util.Objects; import org.mozilla.geckoview.BuildConfig; import org.mozilla.geckoview.ContentBlocking; import org.mozilla.geckoview.GeckoResult; @@ -84,6 +85,9 @@ public class XpcshellTestRunnerService extends Service { .build()) .lowMemoryDetection(false) // Avoid unpredictability in tests .isolatedProcessEnabled(BuildConfig.MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS) + .appZygoteProcessEnabled( + Objects.equals( + System.getenv("MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_WITH_ZYGOTE"), "1")) .build(); sRuntime = GeckoRuntime.create(this, runtimeSettings);