Commit fb7bb96f authored by Clark DuVall's avatar Clark DuVall Committed by Chromium LUCI CQ

Reland "Preload chrome split on startup"

This is a reland of 9124dffe

Reason for reland: This was breaking Android N by accessing the base
context too early: crbug.com/1156018c. Changes from original are from
patchset 1 on.

Original change's description:
> Preload chrome split on startup
>
> The chrome split can take a long time to load, so we get significant
> improvements in startup time if this work is offloaded to a background
> thread. Ideally, we would give as much time as possible to allow this
> preload to finish, which means running as much code as possible which
> does not depend on the chrome split during the preload. This required
> refactoring much of attachBaseContext into the base module, and moving
> the pieces which could not be easily moved to the base module into
> onCreate.
>
> The order of startup generally goes:
> 1. Application.attachBaseContext
> 2. Each provider is instantiated in order, and onCreate is called
> 3. Application.onCreate
>
> If a provider lives in a split, it will call createContextForSplit on
> the application. This will end up being the first thing needing the
> chrome split, which will wait for the preload to complete. This is why
> ChromeFileProvider was moved to the base module, so there could be more
> time to preload.
>
> Pinpoint runs:
> Pixel 2: https://pinpoint-dot-chromeperf.appspot.com/job/16925180d20000
> -7% messageloop_start_time
> -3% first_contentful_paint_time
> -3.8% navigation_commit_time
>
> Android Go: https://pinpoint-dot-chromeperf.appspot.com/job/115e53e7520000
> -18.7% messageloop_start_time
> -1.5% first_contentful_paint_time
> -2.1% navigation_commit_time
>
> A couple notes:
> - setUsageAndCrashReportingFromNative was removed from UmaUtils because
>   it is never used, and allowed for much more easily bringing UmaUtils
>   into the base module.
> - I wanted to make sure PureJavaExceptionHandler is still set up as
>   early as possible, so left it in attachBaseContext. This required
>   loading PureJavaExceptionReporter by reflection if an exception
>   happens, since it has many hooks into chrome code.
> - MainDexApplicationImpl is no longer needed since all the common code
>   was moved to SplitCompatApplication
>
> Bug: 1150600
> Change-Id: Idd6c11293e47fa5e8bd6eb30c14535e60c204867
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2570125
> Commit-Queue: Clark DuVall <cduvall@chromium.org>
> Reviewed-by: Andrew Grieve <agrieve@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#833923}

Bug: 1150600
Change-Id: Id9de8d25e74b18199fa10fb12a669384908acdb6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2577546
Commit-Queue: Clark DuVall <cduvall@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/master@{#834317}
parent 13e78ab2
......@@ -2105,6 +2105,7 @@ android_library("base_monochrome_module_java") {
"//base:base_java",
"//chrome/browser/version:java",
"//components/version_info/android:version_constants_java",
"//content/public/android:content_java",
]
if (webview_includes_weblayer) {
deps += [ "//weblayer/browser/java:base_module_java" ]
......@@ -2118,7 +2119,6 @@ android_library("base_module_java") {
"java/src/com/google/ipc/invalidation/ticl/android2/channel/GcmRegistrationTaskService.java",
"java/src/org/chromium/chrome/browser/ChromeBackgroundService.java",
"java/src/org/chromium/chrome/browser/ChromeBackupAgent.java",
"java/src/org/chromium/chrome/browser/base/MainDexApplicationImpl.java",
"java/src/org/chromium/chrome/browser/base/SplitChromeApplication.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatAppComponentFactory.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java",
......@@ -2132,16 +2132,23 @@ android_library("base_module_java") {
"java/src/org/chromium/chrome/browser/base/SplitCompatRemoteViewsService.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatService.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatUtils.java",
"java/src/org/chromium/chrome/browser/base/SplitPreloader.java",
"java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetService.java",
"java/src/org/chromium/chrome/browser/crash/ApplicationStatusTracker.java",
"java/src/org/chromium/chrome/browser/crash/ChromeMinidumpUploadJobService.java",
"java/src/org/chromium/chrome/browser/crash/FirebaseConfig.java",
"java/src/org/chromium/chrome/browser/crash/MinidumpUploadService.java",
"java/src/org/chromium/chrome/browser/crash/PureJavaExceptionHandler.java",
"java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionService.java",
"java/src/org/chromium/chrome/browser/download/DownloadBroadcastManager.java",
"java/src/org/chromium/chrome/browser/download/DownloadForegroundService.java",
"java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationService.java",
"java/src/org/chromium/chrome/browser/invalidation/ChromeBrowserSyncAdapterService.java",
"java/src/org/chromium/chrome/browser/language/AppLocaleUtils.java",
"java/src/org/chromium/chrome/browser/language/GlobalAppLocaleController.java",
"java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationService.java",
"java/src/org/chromium/chrome/browser/media/ui/ChromeMediaNotificationControllerServices.java",
"java/src/org/chromium/chrome/browser/metrics/UmaUtils.java",
"java/src/org/chromium/chrome/browser/notifications/NotificationJobService.java",
"java/src/org/chromium/chrome/browser/notifications/NotificationService.java",
"java/src/org/chromium/chrome/browser/omaha/OmahaClient.java",
......@@ -2156,6 +2163,11 @@ android_library("base_module_java") {
":chrome_base_module_resources",
"$google_play_services_package:google_play_services_gcm_java",
"//base:base_java",
"//base:jni_java",
"//chrome/browser/flags:java",
"//chrome/browser/preferences:java",
"//chrome/browser/util:java",
"//components/crash/android:java",
"//components/embedder_support/android:application_java",
"//components/media_router/browser/android:cast_options_provider_java",
"//components/minidump_uploader:minidump_uploader_java",
......@@ -2163,6 +2175,7 @@ android_library("base_module_java") {
"//third_party/android_deps:androidx_annotation_annotation_java",
"//third_party/android_deps:androidx_collection_collection_java",
"//third_party/android_deps:androidx_fragment_fragment_java",
"//third_party/google_android_play_core:com_google_android_play_core_java",
"//ui/android:ui_no_recycler_view_java",
# Deps needed for child processes.
......@@ -2197,6 +2210,7 @@ android_library("base_module_java") {
jar_excluded_patterns = [ "*/ProductConfig.class" ]
resources_package = "org.chromium.chrome.base"
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
}
# Defines a target that derives from the monochrome public application. This
......
......@@ -410,16 +410,13 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java",
"java/src/org/chromium/chrome/browser/contextualsearch/TapWordEdgeSuppression.java",
"java/src/org/chromium/chrome/browser/contextualsearch/TapWordLengthSuppression.java",
"java/src/org/chromium/chrome/browser/crash/ApplicationStatusTracker.java",
"java/src/org/chromium/chrome/browser/crash/ChromeMinidumpUploadJobServiceImpl.java",
"java/src/org/chromium/chrome/browser/crash/ChromeMinidumpUploaderDelegate.java",
"java/src/org/chromium/chrome/browser/crash/CrashUploadCountStore.java",
"java/src/org/chromium/chrome/browser/crash/FirebaseConfig.java",
"java/src/org/chromium/chrome/browser/crash/LogcatExtractionRunnable.java",
"java/src/org/chromium/chrome/browser/crash/MinidumpLogcatPrepender.java",
"java/src/org/chromium/chrome/browser/crash/MinidumpUploadRetry.java",
"java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java",
"java/src/org/chromium/chrome/browser/crash/PureJavaExceptionHandler.java",
"java/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporter.java",
"java/src/org/chromium/chrome/browser/cryptids/ProbabilisticCryptidRenderer.java",
"java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java",
......@@ -755,8 +752,6 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/invalidation/ResumableDelayedTaskRunner.java",
"java/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManager.java",
"java/src/org/chromium/chrome/browser/javascript/WebContextFetcher.java",
"java/src/org/chromium/chrome/browser/language/AppLocaleUtils.java",
"java/src/org/chromium/chrome/browser/language/GlobalAppLocaleController.java",
"java/src/org/chromium/chrome/browser/language/LanguageAskPrompt.java",
"java/src/org/chromium/chrome/browser/language/settings/AddLanguageFragment.java",
"java/src/org/chromium/chrome/browser/language/settings/AvailableUiLanguages.java",
......@@ -799,7 +794,6 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/metrics/PageLoadMetrics.java",
"java/src/org/chromium/chrome/browser/metrics/UkmRecorder.java",
"java/src/org/chromium/chrome/browser/metrics/UmaSessionStats.java",
"java/src/org/chromium/chrome/browser/metrics/UmaUtils.java",
"java/src/org/chromium/chrome/browser/metrics/VariationsSession.java",
"java/src/org/chromium/chrome/browser/metrics/WebApkSplashscreenMetrics.java",
"java/src/org/chromium/chrome/browser/metrics/WebApkUma.java",
......
......@@ -16,6 +16,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/background_sync/BackgroundSyncGooglePlayServicesCheckerTest.java",
"junit/src/org/chromium/chrome/browser/background_sync/PeriodicBackgroundSyncChromeWakeUpTaskTest.java",
"junit/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTaskTest.java",
"junit/src/org/chromium/chrome/browser/base/SplitPreloaderTest.java",
"junit/src/org/chromium/chrome/browser/bookmarks/ReadingListSectionHeaderTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/ClearDataDialogResultRecorderTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/ClientAppBroadcastReceiverTest.java",
......
......@@ -874,6 +874,7 @@
android:authorities="$PACKAGE.FileProvider"
android:exported="false"
android:grantUriPermissions="true"
android:initOrder="10000"
android:name="org.chromium.chrome.browser.util.ChromeFileProvider">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/>
</provider> # DIFF-ANCHOR: 6e306896
......
......@@ -812,6 +812,7 @@
android:authorities="$PACKAGE.FileProvider"
android:exported="false"
android:grantUriPermissions="true"
android:initOrder="10000"
android:name="org.chromium.chrome.browser.util.ChromeFileProvider">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/>
</provider> # DIFF-ANCHOR: 6e306896
......
......@@ -886,15 +886,6 @@ by a child template that "extends" this file.
android:readPermission="android.permission.GLOBAL_SEARCH" />
</provider>
<!-- Provider for FileProvider. -->
<provider android:name="org.chromium.chrome.browser.util.ChromeFileProvider"
android:authorities="{{ manifest_package }}.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<provider android:name="org.chromium.chrome.browser.download.DownloadFileProvider"
android:authorities="{{ manifest_package }}.DownloadFileProvider"
android:exported="false"
......@@ -1261,6 +1252,18 @@ by a child template that "extends" this file.
"com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="org.chromium.components.media_router.caf.CastOptionsProvider"/>
<!-- Provider for FileProvider. This is declared in the base module and
has the initOrder attribute to ensure it is loaded first and gives
the chrome split preloader more time to work. -->
<provider android:name="org.chromium.chrome.browser.util.ChromeFileProvider"
android:authorities="{{ manifest_package }}.FileProvider"
android:exported="false"
android:initOrder="10000"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
{% block base_application_definitions %}
{% endblock %}
{% block extra_application_definitions_for_test %}
......
......@@ -4,31 +4,16 @@
package org.chromium.chrome.browser;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.os.Bundle;
import androidx.annotation.Nullable;
import org.chromium.base.ApplicationState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.BuildInfo;
import org.chromium.base.CommandLineInitUtil;
import org.chromium.base.ContextUtils;
import org.chromium.base.LocaleUtils;
import org.chromium.base.PathUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.annotations.MainDex;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.memory.MemoryPressureMonitor;
import org.chromium.chrome.browser.background_task_scheduler.ChromeBackgroundTaskFactory;
import org.chromium.chrome.browser.base.MainDexApplicationImpl;
import org.chromium.chrome.browser.base.SplitCompatApplication;
import org.chromium.chrome.browser.crash.ApplicationStatusTracker;
import org.chromium.chrome.browser.crash.FirebaseConfig;
import org.chromium.chrome.browser.crash.PureJavaExceptionHandler;
import org.chromium.chrome.browser.crash.PureJavaExceptionReporter;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.dependency_injection.ChromeAppComponent;
......@@ -37,13 +22,10 @@ import org.chromium.chrome.browser.dependency_injection.DaggerChromeAppComponent
import org.chromium.chrome.browser.dependency_injection.ModuleFactoryOverrides;
import org.chromium.chrome.browser.flags.CachedFeatureFlags;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.language.GlobalAppLocaleController;
import org.chromium.chrome.browser.metrics.UmaUtils;
import org.chromium.chrome.browser.night_mode.SystemNightModeMonitor;
import org.chromium.chrome.browser.vr.OnExitVrRequestListener;
import org.chromium.chrome.browser.vr.VrModuleProvider;
import org.chromium.components.browser_ui.util.GlobalDiscardableReferencePool;
import org.chromium.components.module_installer.util.ModuleUtil;
import org.chromium.components.version_info.Channel;
import org.chromium.components.version_info.VersionConstants;
import org.chromium.url.GURL;
......@@ -56,102 +38,15 @@ import org.chromium.url.GURL;
* called from the superclass. See {@link SplitCompatApplication} for more info.
*/
public class ChromeApplication extends SplitCompatApplication {
private static final String COMMAND_LINE_FILE = "chrome-command-line";
// Public to allow use in ChromeBackupAgent
public static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "chrome";
/** Lock on creation of sComponent. */
private static final Object sLock = new Object();
@Nullable
private static volatile ChromeAppComponent sComponent;
/** Chrome application logic. */
public static class ChromeApplicationImpl extends MainDexApplicationImpl {
public static class ChromeApplicationImpl extends Impl {
public ChromeApplicationImpl() {}
// Called by the framework for ALL processes. Runs before ContentProviders are created.
// Quirk: context.getApplicationContext() returns null during this method.
@Override
public void attachBaseContext(Context context) {
boolean isBrowserProcess = SplitCompatApplication.isBrowserProcess();
if (isBrowserProcess) {
UmaUtils.recordMainEntryPointTime();
// If the app locale override preference is set, create a new override
// context to use as the base context for the application.
// Must be initialized early to override Application level localizations.
if (GlobalAppLocaleController.getInstance().init(context)) {
Configuration config =
GlobalAppLocaleController.getInstance().getOverrideConfig(context);
LocaleUtils.setDefaultLocalesFromConfiguration(config);
context = context.createConfigurationContext(config);
}
}
super.attachBaseContext(context);
if (isBrowserProcess) {
checkAppBeingReplaced();
PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX);
// Renderer and GPU processes have command line passed to them via IPC
// (see ChildProcessService.java).
CommandLineInitUtil.initCommandLine(
COMMAND_LINE_FILE, ChromeApplicationImpl::shouldUseDebugFlags);
// Enable ATrace on debug OS or app builds.
int applicationFlags = context.getApplicationInfo().flags;
boolean isAppDebuggable = (applicationFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
boolean isOsDebuggable = BuildInfo.isDebugAndroid();
// Requires command-line flags.
TraceEvent.maybeEnableEarlyTracing(
(isAppDebuggable || isOsDebuggable) ? TraceEvent.ATRACE_TAG_APP : 0,
/*readCommandLine=*/true);
TraceEvent.begin("ChromeApplication.attachBaseContext");
// Register for activity lifecycle callbacks. Must be done before any activities are
// created and is needed only by processes that use the ApplicationStatus api (which
// for Chrome is just the browser process).
ApplicationStatus.initialize(getApplication());
// Register and initialize application status listener for crashes, this needs to be
// done as early as possible so that this value is set before any crashes are
// reported.
ApplicationStatusTracker tracker = new ApplicationStatusTracker();
tracker.onApplicationStateChange(ApplicationStatus.getStateForApplication());
ApplicationStatus.registerApplicationStateListener(tracker);
// Disable MemoryPressureMonitor polling when Chrome goes to the background.
ApplicationStatus.registerApplicationStateListener(
ChromeApplicationImpl::updateMemoryPressurePolling);
// Initializes the support for dynamic feature modules (browser only).
ModuleUtil.initApplication();
// Set Chrome factory for mapping BackgroundTask classes to TaskIds.
ChromeBackgroundTaskFactory.setAsDefault();
AppHooks.get().getChimeDelegate().initialize();
if (VersionConstants.CHANNEL == Channel.CANARY) {
GURL.setReportDebugThrowableCallback(
PureJavaExceptionReporter::reportJavaException);
}
}
BuildInfo.setFirebaseAppId(FirebaseConfig.getFirebaseAppId());
if (!ContextUtils.isIsolatedProcess()) {
// Incremental install disables process isolation, so things in this block will
// actually be run for incremental apks, but not normal apks.
PureJavaExceptionHandler.installHandler();
}
if (isBrowserProcess) {
TraceEvent.end("ChromeApplication.attachBaseContext");
}
}
@Override
public void onCreate() {
super.onCreate();
......@@ -161,28 +56,16 @@ public class ChromeApplication extends SplitCompatApplication {
// Kick off library loading in a separate thread so it's ready when we need it.
new Thread(() -> LibraryLoader.getInstance().ensureMainDexInitialized()).start();
}
}
private static Boolean shouldUseDebugFlags() {
return CachedFeatureFlags.isEnabled(ChromeFeatureList.COMMAND_LINE_ON_NON_ROOTED);
if (VersionConstants.CHANNEL == Channel.CANARY) {
GURL.setReportDebugThrowableCallback(
PureJavaExceptionReporter::reportJavaException);
}
private static void updateMemoryPressurePolling(@ApplicationState int newState) {
if (newState == ApplicationState.HAS_RUNNING_ACTIVITIES) {
MemoryPressureMonitor.INSTANCE.enablePolling();
} else if (newState == ApplicationState.HAS_STOPPED_ACTIVITIES) {
MemoryPressureMonitor.INSTANCE.disablePolling();
}
}
// Set Chrome factory for mapping BackgroundTask classes to TaskIds.
ChromeBackgroundTaskFactory.setAsDefault();
/** Ensure this application object is not out-of-date. */
private static void checkAppBeingReplaced() {
// During app update the old apk can still be triggered by broadcasts and spin up an
// out-of-date application. Kill old applications in this bad state. See
// http://crbug.com/658130 for more context and http://b.android.com/56296 for the bug.
if (ContextUtils.getApplicationAssets() == null) {
throw new RuntimeException("App out of date, getResources() null, closing app.");
}
AppHooks.get().getChimeDelegate().initialize();
}
@MainDex
......@@ -229,7 +112,7 @@ public class ChromeApplication extends SplitCompatApplication {
}
public ChromeApplication(Impl impl) {
setImpl(impl);
setImplSupplier(() -> impl);
}
public ChromeApplication() {
......
......@@ -9,10 +9,8 @@ import android.content.Context;
import org.chromium.android_webview.nonembedded.WebViewApkApplication;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.library_loader.LibraryProcessType;
import org.chromium.chrome.browser.base.SplitMonochromeApplication;
import org.chromium.chrome.browser.version.ChromeVersionInfo;
import org.chromium.content_public.browser.ChildProcessCreationParams;
/**
* This is Application class for Monochrome.
......@@ -35,22 +33,6 @@ public class MonochromeApplication extends ChromeApplication {
public static class MonochromeApplicationImpl extends ChromeApplicationImpl {
public MonochromeApplicationImpl() {}
@Override
public void attachBaseContext(Context context) {
super.attachBaseContext(context);
SplitMonochromeApplication.initializeMonochromeProcessCommon();
// ChildProcessCreationParams is only needed for browser process, though it is
// created and set in all processes. We must set isExternalService to true for
// Monochrome because Monochrome's renderer services are shared with WebView
// and are external, and will fail to bind otherwise.
boolean bindToCaller = false;
boolean ignoreVisibilityForImportance = false;
ChildProcessCreationParams.set(getApplication().getPackageName(),
null /* privilegedServicesName */, getApplication().getPackageName(),
null /* sandboxedServicesName */, true /* isExternalService */,
LibraryProcessType.PROCESS_CHILD, bindToCaller, ignoreVisibilityForImportance);
}
@Override
public void onCreate() {
super.onCreate();
......@@ -58,7 +40,7 @@ public class MonochromeApplication extends ChromeApplication {
// Performing Monochrome WebView DevTools Launcher icon showing/hiding logic in
// onCreate rather than in attachBaseContext() because it depends on application
// context being initiatied.
if (isWebViewProcess()) {
if (getApplication().isWebViewProcess()) {
// Whenever a monochrome webview process is launched (WebView service or
// developer UI), post a background task to show/hide the DevTools icon.
WebViewApkApplication.postDeveloperUiLauncherIconTask();
......@@ -74,10 +56,16 @@ public class MonochromeApplication extends ChromeApplication {
}
}
}
}
@Override
public void attachBaseContext(Context context) {
super.attachBaseContext(context);
SplitMonochromeApplication.initializeMonochromeProcessCommon(getPackageName());
}
@Override
public boolean isWebViewProcess() {
return WebViewApkApplication.isWebViewProcess();
}
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.base;
import android.content.Context;
import androidx.annotation.CallSuper;
import org.chromium.base.BundleUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.JNIUtils;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.library_loader.LibraryProcessType;
import org.chromium.base.memory.MemoryPressureMonitor;
import org.chromium.base.task.AsyncTask;
import org.chromium.chrome.browser.ProductConfig;
import org.chromium.components.embedder_support.application.FontPreloadingWorkaround;
import org.chromium.components.module_installer.util.ModuleUtil;
import org.chromium.ui.base.ResourceBundle;
/**
* Implementation of {@link SplitCompatApplication.Impl} that will be initialized in all processes.
*/
public class MainDexApplicationImpl extends SplitCompatApplication.Impl {
@Override
@CallSuper
public void onCreate() {
// These can't go in attachBaseContext because Context.getApplicationContext() (which
// they use under-the-hood) does not work until after it returns.
FontPreloadingWorkaround.maybeInstallWorkaround(getApplication());
MemoryPressureMonitor.INSTANCE.registerComponentCallbacks();
}
@Override
@CallSuper
public void attachBaseContext(Context context) {
super.attachBaseContext(context);
// Perform initialization of globals common to all processes.
ContextUtils.initApplicationContext(getApplication());
maybeInitProcessType();
BundleUtils.setIsBundle(ProductConfig.IS_BUNDLE);
// Write installed modules to crash keys. This needs to be done as early as possible so
// that these values are set before any crashes are reported.
ModuleUtil.updateCrashKeys();
AsyncTask.takeOverAndroidThreadPool();
JNIUtils.setClassLoader(getApplication().getClassLoader());
ResourceBundle.setAvailablePakLocales(
ProductConfig.COMPRESSED_LOCALES, ProductConfig.UNCOMPRESSED_LOCALES);
LibraryLoader.getInstance().setLinkerImplementation(
ProductConfig.USE_CHROMIUM_LINKER, ProductConfig.USE_MODERN_LINKER);
LibraryLoader.getInstance().enableJniChecks();
}
public boolean isWebViewProcess() {
return false;
}
private void maybeInitProcessType() {
if (SplitCompatApplication.isBrowserProcess()) {
LibraryLoader.getInstance().setLibraryProcessType(LibraryProcessType.PROCESS_BROWSER);
return;
}
// WebView initialization sets the correct process type.
if (isWebViewProcess()) return;
// Child processes set their own process type when bound.
String processName = ContextUtils.getProcessName();
if (processName.contains("privileged_process")
|| processName.contains("sandboxed_process")) {
return;
}
// We must be in an isolated service process.
LibraryLoader.getInstance().setLibraryProcessType(LibraryProcessType.PROCESS_CHILD);
}
}
......@@ -4,12 +4,16 @@
package org.chromium.chrome.browser.base;
import static org.chromium.chrome.browser.base.SplitCompatUtils.CHROME_SPLIT_NAME;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.JNIUtils;
import java.lang.reflect.Field;
......@@ -21,6 +25,7 @@ import java.lang.reflect.Field;
*/
public class SplitChromeApplication extends SplitCompatApplication {
private String mChromeApplicationClassName;
private SplitPreloader mSplitPreloader;
public SplitChromeApplication() {
this(SplitCompatUtils.getIdentifierName(
......@@ -32,21 +37,62 @@ public class SplitChromeApplication extends SplitCompatApplication {
}
@Override
protected void attachBaseContext(Context context) {
if (isBrowserProcess()) {
context = SplitCompatUtils.createChromeContext(context);
setImpl((Impl) SplitCompatUtils.newInstance(context, mChromeApplicationClassName));
} else {
setImpl(createNonBrowserApplication());
public void onCreate() {
if (mSplitPreloader != null) {
mSplitPreloader.wait(CHROME_SPLIT_NAME);
}
super.onCreate();
}
@Override
protected void attachBaseContext(Context context) {
super.attachBaseContext(context);
if (isBrowserProcess()) {
setImplSupplier(() -> {
Context chromeContext = SplitCompatUtils.createChromeContext(this);
return (Impl) SplitCompatUtils.newInstance(
chromeContext, mChromeApplicationClassName);
});
applyActivityClassLoaderWorkaround();
} else {
setImplSupplier(() -> createNonBrowserApplication());
}
}
protected MainDexApplicationImpl createNonBrowserApplication() {
return new MainDexApplicationImpl();
@Override
public Context createContextForSplit(String name) throws PackageManager.NameNotFoundException {
if (mSplitPreloader != null) {
// Wait for any splits that are preloading so we don't have a race to update the
// class loader cache (b/172602571).
mSplitPreloader.wait(name);
}
return super.createContextForSplit(name);
}
@Override
protected void performBrowserProcessPreloading(Context context) {
// The chrome split has a large amount of code, which can slow down startup. Loading
// this in the background allows us to do this in parallel with startup tasks which do
// not depend on code in the chrome split.
mSplitPreloader = new SplitPreloader(context);
// If the chrome module is not enabled or isolated splits are not supported (e.g. in Android
// N), the onComplete function will run immediately so it must handle the case where the
// base context of the application has not been set yet.
mSplitPreloader.preload(CHROME_SPLIT_NAME, (chromeContext) -> {
// When installed, the vr module is always loaded on startup, so preload here.
mSplitPreloader.preload("vr", null);
// If the chrome module is not enabled or isolated splits are not supported,
// chromeContext will have the same ClassLoader as the base context, so no need to
// replace the ClassLoaders here.
if (!context.getClassLoader().equals(chromeContext.getClassLoader())) {
replaceClassLoader(this, chromeContext.getClassLoader());
JNIUtils.setClassLoader(chromeContext.getClassLoader());
}
});
}
protected Impl createNonBrowserApplication() {
return new Impl();
}
/**
......@@ -75,21 +121,24 @@ public class SplitChromeApplication extends SplitCompatApplication {
return;
}
Context baseContext = activity.getBaseContext();
replaceClassLoader(
activity.getBaseContext(), activity.getClass().getClassLoader());
}
});
}
private static void replaceClassLoader(Context baseContext, ClassLoader classLoader) {
while (baseContext instanceof ContextWrapper) {
baseContext = ((ContextWrapper) baseContext).getBaseContext();
}
try {
// baseContext should now be an instance of ContextImpl.
Field classLoaderField =
baseContext.getClass().getDeclaredField("mClassLoader");
Field classLoaderField = baseContext.getClass().getDeclaredField("mClassLoader");
classLoaderField.setAccessible(true);
classLoaderField.set(baseContext, activity.getClass().getClassLoader());
classLoaderField.set(baseContext, classLoader);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Error fixing Activity ClassLoader.", e);
}
throw new RuntimeException("Error setting ClassLoader.", e);
}
});
}
}
......@@ -18,7 +18,7 @@ import org.chromium.base.annotations.IdentifierNameString;
/** Utils for compatibility with isolated splits. */
public class SplitCompatUtils {
private static final String CHROME_SPLIT_NAME = "chrome";
public static final String CHROME_SPLIT_NAME = "chrome";
private static final ArraySet<ClassLoader> sInflationClassLoaders = new ArraySet<>();
private SplitCompatUtils() {}
......
......@@ -10,34 +10,25 @@ import com.android.webview.chromium.MonochromeLibraryPreloader;
import org.chromium.android_webview.nonembedded.WebViewApkApplication;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.library_loader.LibraryProcessType;
import org.chromium.chrome.browser.version.ChromeVersionInfo;
import org.chromium.content_public.browser.ChildProcessCreationParams;
/**
* Application class to use for Monochrome when //chrome code is in an isolated split. See {@link
* SplitChromeApplication} for more info.
*/
public class SplitMonochromeApplication extends SplitChromeApplication {
private static class NonBrowserMonochromeApplication extends MainDexApplicationImpl {
@Override
public void attachBaseContext(Context context) {
super.attachBaseContext(context);
initializeMonochromeProcessCommon();
}
private static class NonBrowserMonochromeApplication extends Impl {
@Override
public void onCreate() {
super.onCreate();
// TODO(crbug.com/1126301): This matches logic in MonochromeApplication.java.
// Deduplicate if chrome split launches.
if (!ChromeVersionInfo.isStableBuild() && isWebViewProcess()) {
if (!ChromeVersionInfo.isStableBuild() && getApplication().isWebViewProcess()) {
WebViewApkApplication.postDeveloperUiLauncherIconTask();
}
}
@Override
public boolean isWebViewProcess() {
return WebViewApkApplication.isWebViewProcess();
}
}
public SplitMonochromeApplication() {
......@@ -46,14 +37,35 @@ public class SplitMonochromeApplication extends SplitChromeApplication {
}
@Override
protected MainDexApplicationImpl createNonBrowserApplication() {
public void attachBaseContext(Context context) {
super.attachBaseContext(context);
initializeMonochromeProcessCommon(getPackageName());
}
@Override
protected Impl createNonBrowserApplication() {
return new NonBrowserMonochromeApplication();
}
public static void initializeMonochromeProcessCommon() {
public static void initializeMonochromeProcessCommon(String packageName) {
WebViewApkApplication.maybeInitProcessGlobals();
if (!LibraryLoader.getInstance().isLoadedByZygote()) {
LibraryLoader.getInstance().setNativeLibraryPreloader(new MonochromeLibraryPreloader());
}
// ChildProcessCreationParams is only needed for browser process, though it is
// created and set in all processes. We must set isExternalService to true for
// Monochrome because Monochrome's renderer services are shared with WebView
// and are external, and will fail to bind otherwise.
boolean bindToCaller = false;
boolean ignoreVisibilityForImportance = false;
ChildProcessCreationParams.set(packageName, null /* privilegedServicesName */, packageName,
null /* sandboxedServicesName */, true /* isExternalService */,
LibraryProcessType.PROCESS_CHILD, bindToCaller, ignoreVisibilityForImportance);
}
@Override
public boolean isWebViewProcess() {
return WebViewApkApplication.isWebViewProcess();
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.base;
import android.content.Context;
import androidx.collection.SimpleArrayMap;
import org.chromium.base.BundleUtils;
import org.chromium.base.task.AsyncTask;
import org.chromium.base.task.TaskTraits;
/**
* Handles preloading split Contexts on a background thread. Loading a new isolated split
* Context can be expensive since the ClassLoader may need to be created. See crbug.com/1150600 for
* more info.
*/
public class SplitPreloader {
private final SimpleArrayMap<String, PreloadTask> mPreloadTasks = new SimpleArrayMap<>();
private final Context mContext;
/** Interface to run code after preload completion. */
public interface OnComplete {
void run(Context context);
}
private class PreloadTask extends AsyncTask<Void> {
private final String mName;
private OnComplete mOnComplete;
public PreloadTask(String name, OnComplete onComplete) {
mName = name;
mOnComplete = onComplete;
}
@Override
protected Void doInBackground() {
BundleUtils.createIsolatedSplitContext(mContext, mName);
return null;
}
@Override
protected void onPostExecute(Void result) {
finish();
}
/**
* Waits for the preload to finish and calls the onComplete function if needed. onComplete
* is expected to be called before {@link SplitPreloader#wait(String)} returns, so this
* method is called there since onPostExecute does not run before get() returns.
*/
public void finish() {
try {
get();
} catch (Exception e) {
// Ignore exception, not a problem if preload fails.
}
if (mOnComplete != null) {
// Recreate the context here to make sure we have the latest version, in case there
// was a race to update the class loader cache, see b/172602571.
mOnComplete.run(BundleUtils.createIsolatedSplitContext(mContext, mName));
mOnComplete = null;
}
}
}
public SplitPreloader(Context context) {
mContext = context;
}
/** Starts preloading a split context on a background thread. */
public void preload(String name, OnComplete onComplete) {
if (!BundleUtils.isIsolatedSplitInstalled(mContext, name)) {
if (onComplete != null) {
onComplete.run(mContext);
}
return;
}
assert !mPreloadTasks.containsKey(name);
PreloadTask task = new PreloadTask(name, onComplete);
task.executeWithTaskTraits(TaskTraits.USER_BLOCKING_MAY_BLOCK);
mPreloadTasks.put(name, task);
}
/** Waits for the specified split to be finished loading. */
public void wait(String name) {
PreloadTask task = mPreloadTasks.remove(name);
if (task != null) {
// Make sure the task is finished and onComplete has run.
task.finish();
}
}
}
......@@ -4,8 +4,10 @@
package org.chromium.chrome.browser.crash;
import org.chromium.base.ContextUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.MainDex;
import org.chromium.chrome.browser.base.SplitCompatUtils;
import org.chromium.components.crash.CrashKeys;
/**
......@@ -20,6 +22,11 @@ public class PureJavaExceptionHandler implements Thread.UncaughtExceptionHandler
private boolean mHandlingException;
private static boolean sIsDisabled;
/** Interface to allow uploading reports. */
public interface JavaExceptionReporter {
void createAndUploadReport(Throwable e);
}
private PureJavaExceptionHandler(Thread.UncaughtExceptionHandler parent) {
mParent = parent;
}
......@@ -53,6 +60,10 @@ public class PureJavaExceptionHandler implements Thread.UncaughtExceptionHandler
}
private void reportJavaException(Throwable e) {
PureJavaExceptionReporter.reportJavaException(e);
// PureJavaExceptionReporter may be in the chrome module, so load by reflection from there.
JavaExceptionReporter reporter = (JavaExceptionReporter) SplitCompatUtils.newInstance(
SplitCompatUtils.createChromeContext(ContextUtils.getApplicationContext()),
"org.chromium.chrome.browser.crash.PureJavaExceptionReporter");
reporter.createAndUploadReport(e);
}
}
......@@ -19,6 +19,7 @@ import org.chromium.base.ContextUtils;
import org.chromium.base.PiiElider;
import org.chromium.base.StrictModeContext;
import org.chromium.base.annotations.MainDex;
import org.chromium.base.annotations.UsedByReflection;
import org.chromium.chrome.browser.version.ChromeVersionInfo;
import org.chromium.components.crash.CrashKeys;
......@@ -35,7 +36,8 @@ import java.util.concurrent.atomic.AtomicReferenceArray;
* This class is written in pure Java, so it can handle exception happens before native is loaded.
*/
@MainDex
public class PureJavaExceptionReporter {
@UsedByReflection("PureJavaExceptionHandler.java")
public class PureJavaExceptionReporter implements PureJavaExceptionHandler.JavaExceptionReporter {
// report fields, please keep the name sync with MIME blocks in breakpad_linux.cc
public static final String CHANNEL = "channel";
public static final String VERSION = "ver";
......@@ -68,6 +70,9 @@ public class PureJavaExceptionReporter {
private final String mLocalId = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
private final String mBoundary = "------------" + UUID.randomUUID() + RN;
@UsedByReflection("PureJavaExceptionHandler.java")
public PureJavaExceptionReporter() {}
/**
* Report and upload the device info and stack trace as if it was a crash. Runs synchronously
* and results in I/O on the main thread.
......@@ -79,8 +84,8 @@ public class PureJavaExceptionReporter {
reporter.createAndUploadReport(javaException);
}
@VisibleForTesting
void createAndUploadReport(Throwable javaException) {
@Override
public void createAndUploadReport(Throwable javaException) {
// It is OK to do IO in main thread when we know there is a crash happens.
try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) {
createReport(javaException);
......
......@@ -106,11 +106,6 @@ public class UmaUtils {
return sForegroundStartTimeMs;
}
@CalledByNative
private static void setUsageAndCrashReportingFromNative(boolean enabled) {
UmaSessionStats.changeMetricsReportingConsent(enabled);
}
@NativeMethods
interface Natives {
boolean isClientInMetricsReportingSample();
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.base;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.os.Build;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;
import java.util.ArrayList;
import java.util.List;
/**
* Unit tests for {@link SplitPreloader}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = Build.VERSION_CODES.O)
public class SplitPreloaderTest {
private static final String SPLIT_A = "split_a";
private static final String SPLIT_B = "split_b";
private static class SplitContext extends ContextWrapper {
private final String mName;
private final boolean mCreatedOnUiThread;
public SplitContext(Context context, String name) {
super(context);
mName = name;
mCreatedOnUiThread = ThreadUtils.runningOnUiThread();
}
public String getName() {
return mName;
}
public boolean wasCreatedOnUiThread() {
return mCreatedOnUiThread;
}
}
private static class MainContext extends ContextWrapper {
private final List<String> mUiThreadContextNames = new ArrayList<>();
private final List<String> mBackgroundThreadContextNames = new ArrayList<>();
public MainContext(Context context) {
super(context);
}
@Override
public Context createContextForSplit(String name)
throws PackageManager.NameNotFoundException {
if (ThreadUtils.runningOnUiThread()) {
mUiThreadContextNames.add(name);
} else {
mBackgroundThreadContextNames.add(name);
}
return new SplitContext(this, name);
}
public List<String> getUiThreadContextNames() {
return mUiThreadContextNames;
}
public List<String> getBackgroundThreadContextNames() {
return mBackgroundThreadContextNames;
}
}
private MainContext mContext;
private SplitPreloader mPreloader;
@Before
public void setUp() {
mContext = new MainContext(ContextUtils.getApplicationContext());
mPreloader = new SplitPreloader(mContext);
}
@Test
public void testPreload_splitInstalled() {
mContext.getApplicationInfo().splitNames = new String[] {SPLIT_A};
mPreloader.preload(SPLIT_A, null);
mPreloader.wait(SPLIT_A);
assertThat(mContext.getUiThreadContextNames()).isEmpty();
assertThat(mContext.getBackgroundThreadContextNames()).containsExactly(SPLIT_A);
}
@Test
public void testPreload_withOnComplete_splitInstalled() {
mContext.getApplicationInfo().splitNames = new String[] {SPLIT_A};
SplitContext[] contextHolder = new SplitContext[1];
mPreloader.preload(SPLIT_A, (Context context) -> contextHolder[0] = (SplitContext) context);
mPreloader.wait(SPLIT_A);
assertThat(mContext.getUiThreadContextNames()).containsExactly(SPLIT_A);
assertThat(mContext.getBackgroundThreadContextNames()).containsExactly(SPLIT_A);
assertTrue(contextHolder[0].wasCreatedOnUiThread());
assertEquals(contextHolder[0].getName(), SPLIT_A);
}
@Test
public void testPreload_multipleWaitCalls() {
mContext.getApplicationInfo().splitNames = new String[] {SPLIT_A};
SplitContext[] contextHolder = new SplitContext[1];
mPreloader.preload(SPLIT_A, (Context context) -> contextHolder[0] = (SplitContext) context);
mPreloader.wait(SPLIT_A);
mPreloader.wait(SPLIT_A);
mPreloader.wait(SPLIT_A);
assertThat(mContext.getUiThreadContextNames()).containsExactly(SPLIT_A);
assertThat(mContext.getBackgroundThreadContextNames()).containsExactly(SPLIT_A);
assertTrue(contextHolder[0].wasCreatedOnUiThread());
assertEquals(contextHolder[0].getName(), SPLIT_A);
}
@Test
public void testPreload_withOnComplete_multipleSplitsInstalled() {
mContext.getApplicationInfo().splitNames = new String[] {SPLIT_A, SPLIT_B};
SplitContext[] contextHolderA = new SplitContext[1];
mPreloader.preload(
SPLIT_A, (Context context) -> contextHolderA[0] = (SplitContext) context);
SplitContext[] contextHolderB = new SplitContext[1];
mPreloader.preload(
SPLIT_B, (Context context) -> contextHolderB[0] = (SplitContext) context);
mPreloader.wait(SPLIT_A);
mPreloader.wait(SPLIT_B);
assertThat(mContext.getUiThreadContextNames()).containsExactly(SPLIT_A, SPLIT_B);
assertThat(mContext.getBackgroundThreadContextNames()).containsExactly(SPLIT_A, SPLIT_B);
assertTrue(contextHolderA[0].wasCreatedOnUiThread());
assertEquals(contextHolderA[0].getName(), SPLIT_A);
assertTrue(contextHolderB[0].wasCreatedOnUiThread());
assertEquals(contextHolderB[0].getName(), SPLIT_B);
}
@Test
public void testPreload_splitNotInstalled() {
mPreloader.preload(SPLIT_A, null);
mPreloader.wait(SPLIT_A);
assertThat(mContext.getUiThreadContextNames()).isEmpty();
assertThat(mContext.getBackgroundThreadContextNames()).isEmpty();
}
@Test
public void testPreload_withOnComplete_splitNotInstalled() {
Context[] contextHolder = new Context[1];
mPreloader.preload(SPLIT_A, (Context context) -> contextHolder[0] = context);
mPreloader.wait(SPLIT_A);
assertThat(mContext.getUiThreadContextNames()).isEmpty();
assertThat(mContext.getBackgroundThreadContextNames()).isEmpty();
assertEquals(contextHolder[0], mContext);
}
}
......@@ -44,10 +44,5 @@ static void JNI_UmaUtils_RecordMetricsReportingDefaultOptIn(
: metrics::EnableMetricsDefault::OPT_OUT);
}
void SetUsageAndCrashReporting(bool enabled) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_UmaUtils_setUsageAndCrashReportingFromNative(env, enabled);
}
} // namespace android
} // namespace chrome
......@@ -15,10 +15,6 @@ namespace android {
base::TimeTicks GetApplicationStartTime();
base::TimeTicks GetProcessStartTime();
// Sets whether UMA reporting is enabled. This will call to Java to update
// the shared preference that is the source of truth for UMA reporting.
void SetUsageAndCrashReporting(bool enabled);
} // namespace android
} // namespace chrome
......
......@@ -706,6 +706,7 @@ if (is_android) {
deps = [
"//base:base_java",
"//base:base_java_test_support",
"//chrome/android:base_module_java",
"//chrome/android:chrome_all_java",
"//chrome/browser/tabmodel:java",
"//components/crash/android:handler_java",
......
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