Android: Start VIEW_INTENT navigations ASAP
Usually ChromeTabbedActivity.initializeState performs the initial
navigation, however for view intents we know the URL and can start as
soon as the profile is loaded (can be several hundred ms earlier on
low end devices). In theory it might be possible to do this for main
intents too but getting hold of the URL is beyond the scope of this
patch.
This change is guarded behind the kPrioritizeBootstrapTasks feature.
BUG 863341, 835323
Change-Id: If296fde03b381178ed6feeba70bc50b786eac1e0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1663146
Reviewed-by: Alexander Timin <[email protected]>
Reviewed-by: Pavel Shmakov <[email protected]>
Reviewed-by: Yaron Friedman <[email protected]>
Reviewed-by: Ted Choc <[email protected]>
Reviewed-by: Min Qin <[email protected]>
Reviewed-by: Sami Kyöstilä <[email protected]>
Reviewed-by: Mohamed Heikal <[email protected]>
Commit-Queue: Alex Clarke <[email protected]>
Cr-Commit-Position: refs/heads/master@{#703307}
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index b134ec0..4f52fd2e2 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -835,6 +835,7 @@
"java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java",
"java/src/org/chromium/chrome/browser/init/ServiceManagerStartupUtils.java",
"java/src/org/chromium/chrome/browser/init/SingleWindowKeyboardVisibilityDelegate.java",
+ "java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java",
"java/src/org/chromium/chrome/browser/installedapp/InstalledAppProviderFactory.java",
"java/src/org/chromium/chrome/browser/installedapp/InstalledAppProviderImpl.java",
"java/src/org/chromium/chrome/browser/installedapp/PackageHash.java",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index 30e5be6..594d93a 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -203,6 +203,9 @@
"javatests/src/org/chromium/chrome/browser/init/ChainedTasksTest.java",
"javatests/src/org/chromium/chrome/browser/init/ChromeBrowserInitializerTest.java",
"javatests/src/org/chromium/chrome/browser/init/FirstDrawDetectorTest.java",
+ "javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderCustomTabTest.java",
+ "javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderTest.java",
+ "javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderUnitTest.java",
"javatests/src/org/chromium/chrome/browser/input/SelectPopupOtherContentViewTest.java",
"javatests/src/org/chromium/chrome/browser/instantapps/InstantAppsHandlerTest.java",
"javatests/src/org/chromium/chrome/browser/interstitials/LookalikeInterstitialTest.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index d81b4075..94de4ec 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -99,6 +99,7 @@
import org.chromium.chrome.browser.infobar.InfoBarContainer;
import org.chromium.chrome.browser.init.AsyncInitializationActivity;
import org.chromium.chrome.browser.init.ProcessInitializationHandler;
+import org.chromium.chrome.browser.init.StartupTabPreloader;
import org.chromium.chrome.browser.keyboard_accessory.ManualFillingComponent;
import org.chromium.chrome.browser.keyboard_accessory.ManualFillingComponentFactory;
import org.chromium.chrome.browser.locale.LocaleManager;
@@ -337,6 +338,9 @@
*/
private RootUiCoordinator mRootUiCoordinator;
+ @Nullable
+ private StartupTabPreloader mStartupTabPreloader;
+
// TODO(972867): Pull MenuOrKeyboardActionController out of ChromeActivity.
private List<MenuOrKeyboardActionController.MenuOrKeyboardActionHandler> mMenuActionHandlers =
new ArrayList<>();
@@ -501,7 +505,8 @@
@Override
protected void initializeStartupMetrics() {
- mActivityTabStartupMetricsTracker = new ActivityTabStartupMetricsTracker(this);
+ mActivityTabStartupMetricsTracker =
+ new ActivityTabStartupMetricsTracker(mTabModelSelectorSupplier);
}
protected ActivityTabStartupMetricsTracker getActivityTabStartupMetricsTracker() {
@@ -705,6 +710,18 @@
}
/**
+ * @return The {@link StartupTabPreloader} associated with this ChromeActivity. If there isn't
+ * one it creates it.
+ */
+ protected StartupTabPreloader getStartupTabPreloader() {
+ if (mStartupTabPreloader == null) {
+ mStartupTabPreloader = new StartupTabPreloader(
+ this::getIntent, getLifecycleDispatcher(), getWindowAndroid(), this);
+ }
+ return mStartupTabPreloader;
+ }
+
+ /**
* @return The {@link TabModelSelector} owned by this {@link ChromeActivity}.
*/
protected abstract TabModelSelector createTabModelSelector();
@@ -1016,7 +1033,7 @@
}
super.onNewIntentWithNative(intent);
- if (mIntentHandler.shouldIgnoreIntent(intent)) return;
+ if (IntentHandler.shouldIgnoreIntent(intent)) return;
// We send this intent so that we can enter WebVr presentation mode if needed. This
// call doesn't consume the intent because it also has the url that we need to load.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 4e7201f..611407b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -474,7 +474,7 @@
private class TabbedModeTabCreator extends ChromeTabCreator {
public TabbedModeTabCreator(
ChromeTabbedActivity activity, WindowAndroid nativeWindow, boolean incognito) {
- super(activity, nativeWindow, incognito);
+ super(activity, nativeWindow, getStartupTabPreloader(), incognito);
}
@Override
@@ -1131,7 +1131,7 @@
private boolean maybeLaunchNtpOrResetBottomSheetFromMainIntent(Intent intent) {
assert isMainIntentFromLauncher(intent);
- if (!mIntentHandler.isIntentUserVisible()) return false;
+ if (!IntentHandler.isIntentUserVisible()) return false;
if (!mInactivityTracker.inactivityThresholdPassed()) return false;
@@ -1219,7 +1219,7 @@
LAST_BACKGROUNDED_TIME_MS_PREF, this.getLifecycleDispatcher());
mIntentWithEffect = false;
if (getSavedInstanceState() == null && intent != null) {
- if (!mIntentHandler.shouldIgnoreIntent(intent)) {
+ if (!IntentHandler.shouldIgnoreIntent(intent)) {
mIntentWithEffect = mIntentHandler.onNewIntent(intent);
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
index 56e0dd02..18d6479c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
@@ -285,7 +285,7 @@
/**
* Receiver for screen unlock broadcast.
*/
- private DelayedScreenLockIntentHandler mDelayedScreenIntentHandler;
+ private static DelayedScreenLockIntentHandler sDelayedScreenIntentHandler;
@IntDef({TabOpenType.OPEN_NEW_TAB, TabOpenType.REUSE_URL_MATCHING_TAB_ELSE_NEW_TAB,
TabOpenType.REUSE_APP_ID_MATCHING_TAB_ELSE_NEW_TAB, TabOpenType.CLOBBER_CURRENT_TAB,
@@ -435,13 +435,13 @@
}
}
- private void updateDeferredIntent(Intent intent) {
- if (mDelayedScreenIntentHandler == null && intent != null) {
- mDelayedScreenIntentHandler = new DelayedScreenLockIntentHandler();
+ private static void updateDeferredIntent(Intent intent) {
+ if (sDelayedScreenIntentHandler == null && intent != null) {
+ sDelayedScreenIntentHandler = new DelayedScreenLockIntentHandler();
}
- if (mDelayedScreenIntentHandler != null) {
- mDelayedScreenIntentHandler.updateDeferredIntent(intent);
+ if (sDelayedScreenIntentHandler != null) {
+ sDelayedScreenIntentHandler.updateDeferredIntent(intent);
}
}
@@ -864,7 +864,7 @@
* @param intent Intent to check.
* @return true if the intent should be ignored.
*/
- public boolean shouldIgnoreIntent(Intent intent) {
+ public static boolean shouldIgnoreIntent(Intent intent) {
// Although not documented to, many/most methods that retrieve values from an Intent may
// throw. Because we can't control what packages might send to us, we should catch any
// Throwable and then fail closed (safe). This is ugly, but resolves top crashers in the
@@ -942,7 +942,7 @@
}
@VisibleForTesting
- boolean intentHasValidUrl(Intent intent) {
+ static boolean intentHasValidUrl(Intent intent) {
String url = extractUrlFromIntent(intent);
// Check if this is a valid googlechrome:// URL.
@@ -1024,7 +1024,7 @@
}
@VisibleForTesting
- boolean isIntentUserVisible() {
+ static boolean isIntentUserVisible() {
// Only process Intents if the screen is on and the device is unlocked;
// i.e. the user will see what is going on.
Context appContext = ContextUtils.getApplicationContext();
@@ -1078,7 +1078,7 @@
: TabOpenType.REUSE_APP_ID_MATCHING_TAB_ELSE_NEW_TAB;
}
- private boolean isInvalidScheme(String scheme) {
+ private static boolean isInvalidScheme(String scheme) {
return scheme != null
&& (scheme.toLowerCase(Locale.US).equals(UrlConstants.JAVASCRIPT_SCHEME)
|| scheme.toLowerCase(Locale.US).equals(UrlConstants.JAR_SCHEME));
@@ -1120,7 +1120,7 @@
return scheme;
}
- private boolean isJavascriptSchemeOrInvalidUrl(String url) {
+ private static boolean isJavascriptSchemeOrInvalidUrl(String url) {
String urlScheme = getSanitizedUrlScheme(url);
return isInvalidScheme(urlScheme);
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index e3b87ef..1e9589f29 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -4,11 +4,11 @@
package org.chromium.chrome.browser.customtabs;
-import static org.chromium.chrome.browser.customtabs.content.CustomTabActivityNavigationController.FinishReason.USER_NAVIGATION;
-
import static androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_DARK;
import static androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_LIGHT;
+import static org.chromium.chrome.browser.customtabs.content.CustomTabActivityNavigationController.FinishReason.USER_NAVIGATION;
+
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@@ -23,6 +23,11 @@
import android.view.KeyEvent;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.browser.customtabs.CustomTabsIntent;
+import androidx.browser.customtabs.CustomTabsSessionToken;
+
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram;
@@ -68,11 +73,6 @@
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.browser.customtabs.CustomTabsIntent;
-import androidx.browser.customtabs.CustomTabsSessionToken;
-
/**
* The activity for custom tabs. It will be launched on top of a client's task.
*/
@@ -567,11 +567,10 @@
ChromeActivityCommonsModule commonsModule) {
// mIntentHandler comes from the base class.
IntentIgnoringCriterion intentIgnoringCriterion =
- (intent) -> mIntentHandler.shouldIgnoreIntent(intent);
+ (intent) -> IntentHandler.shouldIgnoreIntent(intent);
- CustomTabActivityModule customTabsModule =
- new CustomTabActivityModule(mIntentDataProvider, mNightModeStateController,
- intentIgnoringCriterion);
+ CustomTabActivityModule customTabsModule = new CustomTabActivityModule(mIntentDataProvider,
+ mNightModeStateController, intentIgnoringCriterion, getStartupTabPreloader());
CustomTabActivityComponent component =
ChromeApplication.getComponent().createCustomTabActivityComponent(
commonsModule, customTabsModule);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
index 2b4dc0e..cf245a5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
@@ -34,6 +34,7 @@
import org.chromium.chrome.browser.customtabs.FirstMeaningfulPaintObserver;
import org.chromium.chrome.browser.customtabs.PageLoadMetricsObserver;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
+import org.chromium.chrome.browser.init.StartupTabPreloader;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.InflationObserver;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
@@ -44,13 +45,17 @@
import org.chromium.chrome.browser.tab.TabRedirectHandler;
import org.chromium.chrome.browser.tabmodel.AsyncTabParams;
import org.chromium.chrome.browser.tabmodel.AsyncTabParamsManager;
+import org.chromium.chrome.browser.tabmodel.TabLaunchType;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl;
import org.chromium.chrome.browser.tabmodel.TabReparentingParams;
import org.chromium.chrome.browser.translate.TranslateBridge;
import org.chromium.chrome.browser.util.IntentUtils;
+import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.common.Referrer;
+import org.chromium.network.mojom.ReferrerPolicy;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -91,6 +96,7 @@
private final CustomTabNavigationEventObserver mTabNavigationEventObserver;
private final ActivityTabProvider mActivityTabProvider;
private final CustomTabActivityTabProvider mTabProvider;
+ private final StartupTabPreloader mStartupTabPreloader;
@Nullable
private final CustomTabsSessionToken mSession;
@@ -114,7 +120,7 @@
CustomTabTabPersistencePolicy persistencePolicy, CustomTabActivityTabFactory tabFactory,
Lazy<CustomTabObserver> customTabObserver, WebContentsFactory webContentsFactory,
CustomTabNavigationEventObserver tabNavigationEventObserver,
- CustomTabActivityTabProvider tabProvider) {
+ CustomTabActivityTabProvider tabProvider, StartupTabPreloader startupTabPreloader) {
mCustomTabDelegateFactory = customTabDelegateFactory;
mActivity = activity;
mConnection = connection;
@@ -129,6 +135,7 @@
mTabNavigationEventObserver = tabNavigationEventObserver;
mActivityTabProvider = activityTabProvider;
mTabProvider = tabProvider;
+ mStartupTabPreloader = startupTabPreloader;
mSession = mIntentDataProvider.getSession();
mIntent = mIntentDataProvider.getIntent();
@@ -237,6 +244,32 @@
}
}
+ /**
+ * @return A tab if mStartupTabPreloader contains a tab matching the intent.
+ */
+ private Tab maybeTakeTabFromStartupTabPreloader() {
+ // Don't overwrite any pre-existing tab.
+ if (mTabProvider.getTab() != null) return null;
+
+ LoadUrlParams loadUrlParams = new LoadUrlParams(mIntentDataProvider.getUrlToLoad());
+ String referrer = mConnection.getReferrer(mSession, mIntent);
+ if (referrer != null && !referrer.isEmpty()) {
+ loadUrlParams.setReferrer(new Referrer(referrer, ReferrerPolicy.DEFAULT));
+ }
+
+ Tab tab = mStartupTabPreloader.takeTabIfMatchingOrDestroy(
+ loadUrlParams, TabLaunchType.FROM_EXTERNAL_APP);
+ if (tab == null) return null;
+
+ TabAssociatedApp.from(tab).setAppId(mConnection.getClientPackageNameForSession(mSession));
+ if (mIntentDataProvider.shouldEnableEmbeddedMediaExperience()) {
+ // Configures web preferences for viewing downloaded media.
+ if (tab.getWebContents() != null) tab.getWebContents().notifyRendererPreferenceUpdate();
+ }
+ initializeTab(tab);
+ return tab;
+ }
+
// Creates the tab on native init, if it hasn't been created yet, and does all the additional
// initialization steps necessary at this stage.
private void finalizeCreatingTab(TabModelSelectorImpl tabModelSelector, TabModel tabModel) {
@@ -254,7 +287,15 @@
}
if (tab == null) {
- // No tab was restored or created early, creating a new tab.
+ // No tab was restored or created early, check if we preloaded a tab.
+ tab = maybeTakeTabFromStartupTabPreloader();
+ if (tab != null) mode = TabCreationMode.FROM_STARTUP_TAB_PRELOADER;
+ } else {
+ mStartupTabPreloader.destroy();
+ }
+
+ if (tab == null) {
+ // No tab was restored, preloaded or created early, creating a new tab.
tab = createTab();
mode = TabCreationMode.DEFAULT;
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabFactory.java
index ffdbbdd..5686aba3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabFactory.java
@@ -15,6 +15,7 @@
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.customtabs.CustomTabTabPersistencePolicy;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
+import org.chromium.chrome.browser.init.StartupTabPreloader;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabBuilder;
import org.chromium.chrome.browser.tab.TabDelegateFactory;
@@ -40,6 +41,7 @@
private final Lazy<ActivityWindowAndroid> mActivityWindowAndroid;
private final Lazy<CustomTabDelegateFactory> mCustomTabDelegateFactory;
private final CustomTabIntentDataProvider mIntentDataProvider;
+ private final StartupTabPreloader mStartupTabPreloader;
@Nullable
private TabModelSelectorImpl mTabModelSelector;
@@ -49,12 +51,14 @@
CustomTabTabPersistencePolicy persistencePolicy,
Lazy<ActivityWindowAndroid> activityWindowAndroid,
Lazy<CustomTabDelegateFactory> customTabDelegateFactory,
- CustomTabIntentDataProvider intentDataProvider) {
+ CustomTabIntentDataProvider intentDataProvider,
+ StartupTabPreloader startupTabPreloader) {
mActivity = activity;
mPersistencePolicy = persistencePolicy;
mActivityWindowAndroid = activityWindowAndroid;
mCustomTabDelegateFactory = customTabDelegateFactory;
mIntentDataProvider = intentDataProvider;
+ mStartupTabPreloader = startupTabPreloader;
}
/** Creates a {@link TabModelSelector} for the custom tab. */
@@ -79,7 +83,8 @@
}
private ChromeTabCreator createTabCreator(boolean incognito) {
- return new ChromeTabCreator(mActivity, mActivityWindowAndroid.get(), incognito) {
+ return new ChromeTabCreator(
+ mActivity, mActivityWindowAndroid.get(), mStartupTabPreloader, incognito) {
@Override
public TabDelegateFactory createDefaultTabDelegateFactory() {
return mCustomTabDelegateFactory.get();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/TabCreationMode.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/TabCreationMode.java
index e9017905..bf904fe 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/TabCreationMode.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/TabCreationMode.java
@@ -7,6 +7,7 @@
import androidx.annotation.IntDef;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
+import org.chromium.chrome.browser.init.StartupTabPreloader;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -14,12 +15,11 @@
/**
* Specifies the way the initial Tab in a Custom Tab activity was created.
*/
-@IntDef({TabCreationMode.NONE,
- TabCreationMode.DEFAULT, TabCreationMode.EARLY,
- TabCreationMode.RESTORED, TabCreationMode.HIDDEN})
+@IntDef({TabCreationMode.NONE, TabCreationMode.DEFAULT, TabCreationMode.EARLY,
+ TabCreationMode.RESTORED, TabCreationMode.HIDDEN,
+ TabCreationMode.FROM_STARTUP_TAB_PRELOADER})
@Retention(RetentionPolicy.SOURCE)
public @interface TabCreationMode {
-
/** The tab has not been created yet */
int NONE = 0;
@@ -39,4 +39,7 @@
* A hidden tab that was created preemptively via {@link CustomTabsConnection#mayLaunchUrl}.
*/
int HIDDEN = 4;
-}
\ No newline at end of file
+
+ /** Opened speculatively by the {@link StartupTabPreloader}.. */
+ int FROM_STARTUP_TAB_PRELOADER = 5;
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dependency_injection/CustomTabActivityModule.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dependency_injection/CustomTabActivityModule.java
index 97e7dbe..35754ea 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dependency_injection/CustomTabActivityModule.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dependency_injection/CustomTabActivityModule.java
@@ -11,6 +11,7 @@
import org.chromium.chrome.browser.customtabs.content.CustomTabIntentHandler.IntentIgnoringCriterion;
import org.chromium.chrome.browser.customtabs.content.CustomTabIntentHandlingStrategy;
import org.chromium.chrome.browser.customtabs.content.DefaultCustomTabIntentHandlingStrategy;
+import org.chromium.chrome.browser.init.StartupTabPreloader;
import org.chromium.chrome.browser.webapps.WebApkPostShareTargetNavigator;
import dagger.Lazy;
@@ -26,13 +27,16 @@
private final CustomTabIntentDataProvider mIntentDataProvider;
private final CustomTabNightModeStateController mNightModeController;
private final IntentIgnoringCriterion mIntentIgnoringCriterion;
+ private final StartupTabPreloader mStartupTabPreloader;
public CustomTabActivityModule(CustomTabIntentDataProvider intentDataProvider,
CustomTabNightModeStateController nightModeController,
- IntentIgnoringCriterion intentIgnoringCriterion) {
+ IntentIgnoringCriterion intentIgnoringCriterion,
+ StartupTabPreloader startupTabPreloader) {
mIntentDataProvider = intentDataProvider;
mNightModeController = nightModeController;
mIntentIgnoringCriterion = intentIgnoringCriterion;
+ mStartupTabPreloader = startupTabPreloader;
}
@Provides
@@ -67,4 +71,9 @@
public WebApkPostShareTargetNavigator providePostShareTargetNavigator() {
return new WebApkPostShareTargetNavigator();
}
+
+ @Provides
+ public StartupTabPreloader provideStartupTabPreloader() {
+ return mStartupTabPreloader;
+ }
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
index bcef872..cce7178 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
@@ -48,6 +48,7 @@
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.util.ConversionUtils;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.components.download.DownloadState;
@@ -89,7 +90,7 @@
public class DownloadManagerService
implements DownloadController.DownloadNotificationService,
NetworkChangeNotifierAutoDetect.Observer, DownloadServiceDelegate,
- BackendProvider.DownloadDelegate, BrowserStartupController.StartupCallback {
+ BackendProvider.DownloadDelegate, ProfileManager.Observer {
// Download status.
@IntDef({DownloadStatus.IN_PROGRESS, DownloadStatus.COMPLETE, DownloadStatus.FAILED,
DownloadStatus.CANCELLED, DownloadStatus.INTERRUPTED})
@@ -1216,31 +1217,21 @@
*/
private long getNativeDownloadManagerService() {
if (mNativeDownloadManagerService == 0) {
- boolean startupCompleted =
- BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
- .isFullBrowserStarted();
+ boolean startupCompleted = ProfileManager.isInitialized();
mNativeDownloadManagerService = DownloadManagerServiceJni.get().init(
DownloadManagerService.this, startupCompleted);
- if (!startupCompleted) {
- BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
- .addStartupCompletedObserver(this);
- }
+ if (!startupCompleted) ProfileManager.addObserver(this);
}
return mNativeDownloadManagerService;
}
@Override
- public void onSuccess() {
- if (BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
- .isFullBrowserStarted()) {
- DownloadManagerServiceJni.get().onFullBrowserStarted(
- mNativeDownloadManagerService, DownloadManagerService.this);
- }
+ public void onProfileCreated(Profile profile) {
+ ProfileManager.removeObserver(this);
+ DownloadManagerServiceJni.get().onProfileCreated(
+ mNativeDownloadManagerService, DownloadManagerService.this);
}
- @Override
- public void onFailure() {}
-
@CalledByNative
void onResumptionFailed(String downloadGuid) {
mDownloadNotifier.notifyDownloadFailed(new DownloadInfo.Builder()
@@ -2095,7 +2086,7 @@
interface Natives {
boolean isSupportedMimeType(String mimeType);
int getAutoResumptionLimit();
- long init(DownloadManagerService caller, boolean isFullBrowserStarted);
+ long init(DownloadManagerService caller, boolean isProfileCreated);
void openDownload(long nativeDownloadManagerService, DownloadManagerService caller,
String downloadGuid, boolean isOffTheRecord, int source);
void resumeDownload(long nativeDownloadManagerService, DownloadManagerService caller,
@@ -2117,7 +2108,7 @@
DownloadManagerService caller, boolean isOffTheRecord);
void updateLastAccessTime(long nativeDownloadManagerService, DownloadManagerService caller,
String downloadGuid, boolean isOffTheRecord);
- void onFullBrowserStarted(long nativeDownloadManagerService, DownloadManagerService caller);
+ void onProfileCreated(long nativeDownloadManagerService, DownloadManagerService caller);
void createInterruptedDownloadForTest(long nativeDownloadManagerService,
DownloadManagerService caller, String url, String guid, String targetPath);
void recordFirstBackgroundInterruptReason(long nativeDownloadManagerService,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java b/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java
new file mode 100644
index 0000000..d7405795
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java
@@ -0,0 +1,213 @@
+// Copyright 2019 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.init;
+
+import android.content.Intent;
+import android.text.TextUtils;
+
+import org.chromium.base.Supplier;
+import org.chromium.base.TraceEvent;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.WebContentsFactory;
+import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
+import org.chromium.chrome.browser.lifecycle.Destroyable;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.profiles.ProfileManager;
+import org.chromium.chrome.browser.tab.EmptyTabObserver;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
+import org.chromium.chrome.browser.tabmodel.ChromeTabCreator;
+import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
+import org.chromium.chrome.browser.tabmodel.TabLaunchType;
+import org.chromium.chrome.browser.util.IntentUtils;
+import org.chromium.components.url_formatter.UrlFormatter;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.common.Referrer;
+import org.chromium.network.mojom.ReferrerPolicy;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * This class attempts to preload the tab if the url is known from the intent when the profile
+ * is created. This is done to improve startup latency.
+ */
+public class StartupTabPreloader implements ProfileManager.Observer, Destroyable {
+ private final Supplier<Intent> mIntentSupplier;
+ private final ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
+ private final WindowAndroid mWindowAndroid;
+ private final TabCreatorManager mTabCreatorManager;
+ private LoadUrlParams mLoadUrlParams;
+ private Tab mTab;
+ private StartupTabObserver mObserver;
+
+ public StartupTabPreloader(Supplier<Intent> intentSupplier,
+ ActivityLifecycleDispatcher activityLifecycleDispatcher, WindowAndroid windowAndroid,
+ TabCreatorManager tabCreatorManager) {
+ mIntentSupplier = intentSupplier;
+ mActivityLifecycleDispatcher = activityLifecycleDispatcher;
+ mWindowAndroid = windowAndroid;
+ mTabCreatorManager = tabCreatorManager;
+
+ mActivityLifecycleDispatcher.register(this);
+ ProfileManager.addObserver(this);
+ }
+
+ @Override
+ public void destroy() {
+ if (mTab != null) mTab.destroy();
+ mTab = null;
+
+ ProfileManager.removeObserver(this);
+ mActivityLifecycleDispatcher.unregister(this);
+ }
+
+ /**
+ * Returns the Tab if loadUrlParams and type match, otherwise the Tab is discarded.
+ *
+ * @param loadUrlParams The actual parameters of the url load.
+ * @param type The actual launch type type.
+ * @return The results of maybeNavigate() if they match loadUrlParams and type or null
+ * otherwise.
+ */
+ public Tab takeTabIfMatchingOrDestroy(LoadUrlParams loadUrlParams, @TabLaunchType int type) {
+ if (mTab == null) return null;
+
+ boolean tabMatches = type == mTab.getLaunchType()
+ && doLoadUrlParamsMatchForWarmupManagerNavigation(mLoadUrlParams, loadUrlParams);
+
+ RecordHistogram.recordBooleanHistogram(
+ "Startup.Android.StartupTabPreloader.TabTaken", tabMatches);
+
+ if (!tabMatches) {
+ mTab.destroy();
+ mTab = null;
+ mLoadUrlParams = null;
+ return null;
+ }
+
+ Tab tab = mTab;
+ mTab = null;
+ mLoadUrlParams = null;
+ tab.removeObserver(mObserver);
+ return tab;
+ }
+
+ @VisibleForTesting
+ static boolean doLoadUrlParamsMatchForWarmupManagerNavigation(
+ LoadUrlParams preconnectParams, LoadUrlParams loadParams) {
+ if (!TextUtils.equals(preconnectParams.getUrl(), loadParams.getUrl())) return false;
+
+ String preconnectReferrer = preconnectParams.getReferrer() != null
+ ? preconnectParams.getReferrer().getUrl()
+ : null;
+ String loadParamsReferrer =
+ loadParams.getReferrer() != null ? loadParams.getReferrer().getUrl() : null;
+
+ return TextUtils.equals(preconnectReferrer, loadParamsReferrer);
+ }
+
+ /**
+ * Called by the ProfileManager when a profile has been created. This occurs during startup
+ * and it's the earliest point at which we can create and load a tab. If the url can be
+ * determined from the intent, then a tab will be loaded and potentially adopted by
+ * {@link ChromeTabCreator}.
+ */
+ @Override
+ public void onProfileCreated(Profile profile) {
+ try (TraceEvent e = TraceEvent.scoped("StartupTabPreloader.onProfileCreated")) {
+ // We only care about the first non-incognito profile that's created during startup.
+ if (profile.isOffTheRecord()) return;
+
+ ProfileManager.removeObserver(this);
+ boolean shouldLoad = shouldLoadTab();
+ if (shouldLoad) loadTab();
+ RecordHistogram.recordBooleanHistogram(
+ "Startup.Android.StartupTabPreloader.TabLoaded", shouldLoad);
+ }
+ }
+
+ /**
+ * @returns True if based on the intent we should load the tab, returns false otherwise.
+ */
+ @VisibleForTesting
+ boolean shouldLoadTab() {
+ if (!ChromeFeatureList.isEnabled(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)) {
+ return false;
+ }
+
+ // If mTab isn't null we've been called before and there is nothing to do.
+ if (mTab != null) return false;
+
+ Intent intent = mIntentSupplier.get();
+ if (IntentHandler.shouldIgnoreIntent(intent)) return false;
+ if (getUrlFromIntent(intent) == null) return false;
+
+ // We don't support incognito tabs because only chrome can send new incognito tab
+ // intents and that's not a startup scenario.
+ boolean incognito = IntentUtils.safeGetBooleanExtra(
+ intent, IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, false);
+ if (incognito) return false;
+
+ TabCreatorManager.TabCreator tabCreator = mTabCreatorManager.getTabCreator(incognito);
+
+ // We want to get the TabDelegateFactory but only ChromeTabCreator has one.
+ if (!(tabCreator instanceof ChromeTabCreator)) return false;
+
+ return true;
+ }
+
+ private void loadTab() {
+ Intent intent = mIntentSupplier.get();
+ String url = UrlFormatter.fixupUrl(getUrlFromIntent(intent));
+
+ ChromeTabCreator chromeTabCreator =
+ (ChromeTabCreator) mTabCreatorManager.getTabCreator(false);
+ WebContents webContents = WebContentsFactory.createWebContents(false, false);
+
+ mLoadUrlParams = new LoadUrlParams(url);
+ String referrer = IntentHandler.getReferrerUrlIncludingExtraHeaders(intent);
+ if (referrer != null && !referrer.isEmpty()) {
+ mLoadUrlParams.setReferrer(new Referrer(referrer, ReferrerPolicy.DEFAULT));
+ }
+
+ // Create a detached tab and navigate it.
+ mTab = TabBuilder.createLiveTab(false)
+ .setIncognito(false)
+ .setLaunchType(TabLaunchType.FROM_EXTERNAL_APP)
+ .setWindow(mWindowAndroid)
+ .build();
+
+ mObserver = new StartupTabObserver();
+ mTab.addObserver(mObserver);
+
+ // Create and load the tab, but don't add it to the tab model yet. We'll do that
+ // later if the loadUrlParams etc... match.
+ mTab.initialize(webContents, chromeTabCreator.createDefaultTabDelegateFactory(), false,
+ /* tabState */ null,
+ /* unfreeze */ false);
+ mTab.loadUrl(mLoadUrlParams);
+ }
+
+ private static String getUrlFromIntent(Intent intent) {
+ if (Intent.ACTION_VIEW.equals(intent.getAction())
+ || Intent.ACTION_MAIN.equals(intent.getAction())) {
+ // TODO(alexclarke): For ACTION_MAIN maybe refactor TabPersistentStore so we can
+ // instantiate (a subset of that) here to extract the URL.
+ return IntentHandler.getUrlFromIntent(intent);
+ } else {
+ return null;
+ }
+ }
+
+ private class StartupTabObserver extends EmptyTabObserver {
+ @Override
+ public void onCrash(Tab tab) {
+ destroy();
+ }
+ }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
index 9c39e78..a37fe3a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
@@ -6,13 +6,12 @@
import android.os.SystemClock;
-import org.chromium.base.library_loader.LibraryProcessType;
+import org.chromium.base.ObservableSupplier;
import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
import org.chromium.chrome.browser.util.UrlUtilities;
-import org.chromium.content_public.browser.BrowserStartupController;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.content_public.browser.WebContents;
@@ -22,7 +21,6 @@
*/
public class ActivityTabStartupMetricsTracker {
private final long mActivityStartTimeMs;
- private final ChromeActivity mActivity;
// Event duration recorded from the |mActivityStartTimeMs|.
private long mFirstCommitTimeMs;
@@ -31,29 +29,16 @@
private PageLoadMetrics.Observer mPageLoadMetricsObserver;
private boolean mShouldTrackStartupMetrics;
- public ActivityTabStartupMetricsTracker(ChromeActivity activity) {
+ public ActivityTabStartupMetricsTracker(
+ ObservableSupplier<TabModelSelector> tabModelSelectorSupplier) {
mActivityStartTimeMs = SystemClock.uptimeMillis();
- mActivity = activity;
- BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
- .addStartupCompletedObserver(new BrowserStartupController.StartupCallback() {
- @Override
- public void onSuccess() {
- // The activity's TabModelSelector may not have been initialized yet
- // causing a crash. See https://crbug.com/847580
- if (!mActivity.areTabModelsInitialized()) return;
- registerObservers();
- }
-
- @Override
- public void onFailure() {}
- });
+ tabModelSelectorSupplier.addObserver((selector) -> registerObservers(selector));
}
- private void registerObservers() {
+ private void registerObservers(TabModelSelector tabModelSelector) {
if (!mShouldTrackStartupMetrics) return;
mTabModelSelectorTabObserver =
- new TabModelSelectorTabObserver(mActivity.getTabModelSelector()) {
-
+ new TabModelSelectorTabObserver(tabModelSelector) {
private boolean mIsFirstPageLoadStart = true;
@Override
@@ -78,7 +63,7 @@
}
};
mPageLoadMetricsObserver = new PageLoadMetrics.Observer() {
- private final static long NO_NAVIGATION_ID = -1;
+ private static final long NO_NAVIGATION_ID = -1;
private long mNavigationId = NO_NAVIGATION_ID;
private boolean mShouldRecordHistograms;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
index 8bc1fc61..7b835f7d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
@@ -14,6 +14,7 @@
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.ServiceTabLauncher;
+import org.chromium.chrome.browser.init.StartupTabPreloader;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabAssociatedApp;
@@ -37,15 +38,17 @@
public class ChromeTabCreator extends TabCreatorManager.TabCreator {
private final ChromeActivity mActivity;
+ private final StartupTabPreloader mStartupTabPreloader;
private final boolean mIncognito;
private WindowAndroid mNativeWindow;
private TabModel mTabModel;
private TabModelOrderController mOrderController;
- public ChromeTabCreator(
- ChromeActivity activity, WindowAndroid nativeWindow, boolean incognito) {
+ public ChromeTabCreator(ChromeActivity activity, WindowAndroid nativeWindow,
+ StartupTabPreloader startupTabPreloader, boolean incognito) {
mActivity = activity;
+ mStartupTabPreloader = startupTabPreloader;
mNativeWindow = nativeWindow;
mIncognito = incognito;
}
@@ -155,15 +158,22 @@
.build();
tab.initialize(null, delegateFactory, !openInForeground, null, false);
} else {
- tab = TabBuilder.createLiveTab(!openInForeground)
- .setParent(parent)
- .setIncognito(mIncognito)
- .setWindow(mNativeWindow)
- .setLaunchType(type)
- .build();
+ tab = (mStartupTabPreloader != null)
+ ? mStartupTabPreloader.takeTabIfMatchingOrDestroy(loadUrlParams, type)
+ : null;
- tab.initialize(null, delegateFactory, !openInForeground, null, false);
- tab.loadUrl(loadUrlParams);
+ if (tab == null) {
+ TraceEvent.begin("ChromeTabCreator.loadUrl");
+ tab = TabBuilder.createLiveTab(!openInForeground)
+ .setParent(parent)
+ .setIncognito(mIncognito)
+ .setWindow(mNativeWindow)
+ .setLaunchType(type)
+ .build();
+ tab.initialize(null, delegateFactory, !openInForeground, null, false);
+ tab.loadUrl(loadUrlParams);
+ TraceEvent.end("ChromeTabCreator.loadUrl");
+ }
}
TabRedirectHandler.from(tab).updateIntent(intent);
if (intent != null && intent.hasExtra(ServiceTabLauncher.LAUNCH_REQUEST_ID_EXTRA)) {
@@ -363,7 +373,7 @@
/**
* @return The default tab delegate factory to be used if creating new tabs w/o parents.
*/
- protected TabDelegateFactory createDefaultTabDelegateFactory() {
+ public TabDelegateFactory createDefaultTabDelegateFactory() {
return new TabDelegateFactoryImpl(mActivity);
}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerTest.java
index c4572f2..9daa31b5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerTest.java
@@ -117,7 +117,7 @@
for (String url : urls) {
mIntent.setData(Uri.parse(url));
- if (mIntentHandler.intentHasValidUrl(mIntent) != isValid) {
+ if (IntentHandler.intentHasValidUrl(mIntent) != isValid) {
failedTests.add(url);
}
}
@@ -212,7 +212,7 @@
public void testNullUrlIntent() {
mIntent.setData(null);
Assert.assertTrue(
- "Intent with null data should be valid", mIntentHandler.intentHasValidUrl(mIntent));
+ "Intent with null data should be valid", IntentHandler.intentHasValidUrl(mIntent));
}
@Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderCustomTabTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderCustomTabTest.java
new file mode 100644
index 0000000..e41e536
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderCustomTabTest.java
@@ -0,0 +1,66 @@
+// Copyright 2019 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.init;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.LaunchIntentDispatcher;
+import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.net.test.EmbeddedTestServerRule;
+
+/**
+ * Browser tests for {@link StartupTabPreloader}.
+ */
+@RunWith(ChromeJUnit4ClassRunner.class)
[email protected](ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
+public class StartupTabPreloaderCustomTabTest {
+ private static final String TEST_PAGE = "/chrome/test/data/android/google.html";
+ private static final String TAB_LOADED_HISTOGRAM =
+ "Startup.Android.StartupTabPreloader.TabLoaded";
+ private static final String TAB_TAKEN_HISTOGRAM =
+ "Startup.Android.StartupTabPreloader.TabTaken";
+
+ @Rule
+ public CustomTabActivityTestRule mActivityRule = new CustomTabActivityTestRule();
+
+ @Rule
+ public EmbeddedTestServerRule mServerRule = new EmbeddedTestServerRule();
+
+ @Test
+ @LargeTest
+ @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testStartupTabPreloaderWithCustomTab() throws Exception {
+ Uri uri = Uri.parse(mServerRule.getServer().getURL(TEST_PAGE));
+ Intent customTabActivityIntent = TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return LaunchIntentDispatcher.createCustomTabActivityIntent(
+ InstrumentationRegistry.getTargetContext(), intent);
+ });
+
+ mActivityRule.startCustomTabActivityWithIntent(customTabActivityIntent);
+
+ // The StartupTabPreloader should have loaded a url.
+ Assert.assertEquals(
+ 1, RecordHistogram.getHistogramValueCountForTesting(TAB_LOADED_HISTOGRAM, 1));
+ Assert.assertEquals(
+ 1, RecordHistogram.getHistogramValueCountForTesting(TAB_TAKEN_HISTOGRAM, 1));
+ }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderTest.java
new file mode 100644
index 0000000..fd693d40
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderTest.java
@@ -0,0 +1,151 @@
+// Copyright 2019 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.init;
+
+import android.content.Intent;
+import android.support.test.filters.LargeTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
+import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
+import org.chromium.net.test.EmbeddedTestServerRule;
+
+/**
+ * Browser tests for {@link StartupTabPreloader}.
+ */
+@RunWith(ChromeJUnit4ClassRunner.class)
[email protected](ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
+public class StartupTabPreloaderTest {
+ private static final String TEST_PAGE = "/chrome/test/data/android/google.html";
+ private static final String TEST_PAGE2 = "/chrome/test/data/android/about.html";
+ private static final String TAB_LOADED_HISTOGRAM =
+ "Startup.Android.StartupTabPreloader.TabLoaded";
+ private static final String TAB_TAKEN_HISTOGRAM =
+ "Startup.Android.StartupTabPreloader.TabTaken";
+
+ @Rule
+ public ChromeTabbedActivityTestRule mActivityRule = new ChromeTabbedActivityTestRule();
+
+ @Rule
+ public EmbeddedTestServerRule mServerRule = new EmbeddedTestServerRule();
+
+ @Test
+ @LargeTest
+ @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testStartupTabPreloaderWithViewIntent() throws Exception {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ mActivityRule.startMainActivityFromIntent(
+ intent, mServerRule.getServer().getURL(TEST_PAGE));
+
+ // The StartupTabPreloader should have loaded a url.
+ Assert.assertEquals(
+ 1, RecordHistogram.getHistogramValueCountForTesting(TAB_LOADED_HISTOGRAM, 1));
+ Assert.assertEquals(
+ 1, RecordHistogram.getHistogramValueCountForTesting(TAB_TAKEN_HISTOGRAM, 1));
+ }
+
+ @Test
+ @LargeTest
+ @DisableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testStartupTabPreloaderWithViewIntentFeatureDisabled() throws Exception {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ mActivityRule.startMainActivityFromIntent(
+ intent, mServerRule.getServer().getURL(TEST_PAGE));
+
+ // The StartupTabPreloader should have ignored the intent.
+ Assert.assertEquals(
+ 0, RecordHistogram.getHistogramValueCountForTesting(TAB_LOADED_HISTOGRAM, 1));
+ Assert.assertEquals(
+ 0, RecordHistogram.getHistogramValueCountForTesting(TAB_TAKEN_HISTOGRAM, 1));
+ }
+
+ @Test
+ @LargeTest
+ @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testStartupTabPreloaderWithIncognitoViewIntent() throws Exception {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, true);
+ mActivityRule.startMainActivityFromIntent(
+ intent, mServerRule.getServer().getURL(TEST_PAGE));
+
+ // Incognito requests should be ignored.
+ Assert.assertEquals(
+ 0, RecordHistogram.getHistogramValueCountForTesting(TAB_LOADED_HISTOGRAM, 1));
+ Assert.assertEquals(
+ 0, RecordHistogram.getHistogramValueCountForTesting(TAB_TAKEN_HISTOGRAM, 1));
+ }
+
+ @Test
+ @LargeTest
+ @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testStartupTabPreloaderWithMainIntentWithUrl() throws Exception {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ mActivityRule.startMainActivityFromIntent(
+ intent, mServerRule.getServer().getURL(TEST_PAGE));
+
+ // The StartupTabPreloader should have loaded a url.
+ Assert.assertEquals(
+ 1, RecordHistogram.getHistogramValueCountForTesting(TAB_LOADED_HISTOGRAM, 1));
+ Assert.assertEquals(
+ 1, RecordHistogram.getHistogramValueCountForTesting(TAB_TAKEN_HISTOGRAM, 1));
+ }
+
+ @Test
+ @LargeTest
+ @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testStartupTabPreloaderWithMainIntentWithoutUrl() throws Exception {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_HOME);
+ mActivityRule.startMainActivityFromIntent(intent, null);
+
+ // There is no url so the StartupTabPreloader should ignore it.
+ Assert.assertEquals(
+ 0, RecordHistogram.getHistogramValueCountForTesting(TAB_LOADED_HISTOGRAM, 1));
+ Assert.assertEquals(
+ 0, RecordHistogram.getHistogramValueCountForTesting(TAB_TAKEN_HISTOGRAM, 1));
+ }
+
+ @Test
+ @LargeTest
+ @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testStartupTabPreloaderWithMultipleViewIntents() throws Exception {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ mActivityRule.startMainActivityFromIntent(
+ intent, mServerRule.getServer().getURL(TEST_PAGE));
+
+ // The StartupTabPreloader should have loaded a url.
+ Assert.assertEquals(
+ 1, RecordHistogram.getHistogramValueCountForTesting(TAB_LOADED_HISTOGRAM, 1));
+ Assert.assertEquals(
+ 1, RecordHistogram.getHistogramValueCountForTesting(TAB_TAKEN_HISTOGRAM, 1));
+
+ intent = new Intent(Intent.ACTION_VIEW);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ mActivityRule.startMainActivityFromIntent(
+ intent, mServerRule.getServer().getURL(TEST_PAGE2));
+
+ // The second intent should be ignored and not increment the counters.
+ Assert.assertEquals(
+ 1, RecordHistogram.getHistogramValueCountForTesting(TAB_LOADED_HISTOGRAM, 1));
+ Assert.assertEquals(
+ 1, RecordHistogram.getHistogramValueCountForTesting(TAB_TAKEN_HISTOGRAM, 1));
+ }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderUnitTest.java
new file mode 100644
index 0000000..d58ccce8
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderUnitTest.java
@@ -0,0 +1,173 @@
+// Copyright 2019 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.init;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.Supplier;
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.tabmodel.ChromeTabCreator;
+import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
+import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
+import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.common.Referrer;
+import org.chromium.network.mojom.ReferrerPolicy;
+
+/**
+ * Unit tests for {@link StartupTabPreloader}.
+ */
+@RunWith(ChromeJUnit4ClassRunner.class)
+public class StartupTabPreloaderUnitTest {
+ private static final String SITE_A = "https://a.com";
+ private static final String SITE_B = "https://b.com";
+ private static final String SITE_C = "https://c.com";
+ private static final String INVALID_SCHEME = "javascript:alert()";
+ private static final Intent VIEW_INTENT =
+ new Intent(Intent.ACTION_VIEW).setData(Uri.parse(SITE_A));
+ private static final Intent INCOGNITO_VIEW_INTENT =
+ new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse(SITE_A))
+ .putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, true);
+ private static final Intent VIEW_INTENT_WITH_INVALID_SCHEME =
+ new Intent(Intent.ACTION_VIEW).setData(Uri.parse(INVALID_SCHEME));
+ private static final Intent MAIN_INTENT_WITH_URL =
+ new Intent(Intent.ACTION_MAIN).setData(Uri.parse(SITE_B));
+ private static final Intent MAIN_INTENT_WITHOUT_URL = new Intent(Intent.ACTION_MAIN);
+ private static final TabCreatorManager sChromeTabCreator = new ChromeTabCreatorManager();
+ private static final TabCreatorManager sNonChromeTabCreator = new NonChromeTabCreatorManager();
+
+ @Rule
+ public Features.JUnitProcessor processor = new Features.JUnitProcessor();
+
+ @Test
+ @SmallTest
+ public void testDoLoadUrlParamsMatchForWarmupManagerNavigation() {
+ LoadUrlParams siteAWithSiteBReferrer_1 = new LoadUrlParams(SITE_A);
+ siteAWithSiteBReferrer_1.setReferrer(new Referrer(SITE_B, ReferrerPolicy.DEFAULT));
+ LoadUrlParams siteAWithSiteBReferrer_2 = new LoadUrlParams(SITE_A);
+ siteAWithSiteBReferrer_2.setReferrer(new Referrer(SITE_B, ReferrerPolicy.DEFAULT));
+
+ LoadUrlParams siteAWithSiteBReferrer = new LoadUrlParams(SITE_A);
+ siteAWithSiteBReferrer.setReferrer(new Referrer(SITE_B, ReferrerPolicy.DEFAULT));
+
+ LoadUrlParams siteAWithSiteCReferrer = new LoadUrlParams(SITE_A);
+ siteAWithSiteCReferrer.setReferrer(new Referrer(SITE_C, ReferrerPolicy.DEFAULT));
+
+ LoadUrlParams siteAWithNoReferrer = new LoadUrlParams(SITE_A);
+ LoadUrlParams siteBWithNoReferrer_1 = new LoadUrlParams(SITE_B);
+ LoadUrlParams siteBWithNoReferrer_2 = new LoadUrlParams(SITE_B);
+
+ Assert.assertTrue(StartupTabPreloader.doLoadUrlParamsMatchForWarmupManagerNavigation(
+ siteAWithSiteBReferrer_1, siteAWithSiteBReferrer_2));
+ Assert.assertTrue(StartupTabPreloader.doLoadUrlParamsMatchForWarmupManagerNavigation(
+ siteBWithNoReferrer_2, siteBWithNoReferrer_2));
+
+ Assert.assertFalse(StartupTabPreloader.doLoadUrlParamsMatchForWarmupManagerNavigation(
+ siteAWithSiteBReferrer_1, siteAWithSiteCReferrer));
+ Assert.assertFalse(StartupTabPreloader.doLoadUrlParamsMatchForWarmupManagerNavigation(
+ siteAWithSiteBReferrer_1, siteAWithNoReferrer));
+ Assert.assertFalse(StartupTabPreloader.doLoadUrlParamsMatchForWarmupManagerNavigation(
+ siteAWithSiteBReferrer_1, siteBWithNoReferrer_1));
+ Assert.assertFalse(StartupTabPreloader.doLoadUrlParamsMatchForWarmupManagerNavigation(
+ siteAWithNoReferrer, siteBWithNoReferrer_1));
+ }
+
+ @Test
+ @SmallTest
+ @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testShouldLoadTab_AllowViewIntents() {
+ Assert.assertTrue(
+ createStartupTabPreloader(VIEW_INTENT, sChromeTabCreator).shouldLoadTab());
+ }
+
+ @Test
+ @SmallTest
+ @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testShouldLoadTab_AllowMainIntentsWithUrl() {
+ Assert.assertTrue(
+ createStartupTabPreloader(MAIN_INTENT_WITH_URL, sChromeTabCreator).shouldLoadTab());
+ }
+
+ @Test
+ @SmallTest
+ @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testShouldLoadTab_BlockedMainIntentsWithoutUrl() {
+ Assert.assertFalse(createStartupTabPreloader(MAIN_INTENT_WITHOUT_URL, sChromeTabCreator)
+ .shouldLoadTab());
+ }
+
+ @Test
+ @SmallTest
+ @DisableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testShouldLoadTab_BlockedWhenFeatureDisabled() {
+ Assert.assertFalse(
+ createStartupTabPreloader(VIEW_INTENT, sChromeTabCreator).shouldLoadTab());
+ }
+
+ @Test
+ @SmallTest
+ @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testShouldLoadTab_BlockedInvalidSchemeIntent() {
+ Assert.assertFalse(
+ createStartupTabPreloader(VIEW_INTENT_WITH_INVALID_SCHEME, sChromeTabCreator)
+ .shouldLoadTab());
+ }
+
+ @Test
+ @SmallTest
+ @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testShouldLoadTab_BlockedNonChromeTabCreators() {
+ Assert.assertFalse(
+ createStartupTabPreloader(VIEW_INTENT, sNonChromeTabCreator).shouldLoadTab());
+ }
+
+ @Test
+ @SmallTest
+ @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+ public void testShouldLoadTab_BlockedIncognitoIntents() {
+ Assert.assertFalse(createStartupTabPreloader(INCOGNITO_VIEW_INTENT, sChromeTabCreator)
+ .shouldLoadTab());
+ }
+
+ private StartupTabPreloader createStartupTabPreloader(
+ Intent intent, TabCreatorManager tabCreatorManager) {
+ return new StartupTabPreloader(new Supplier<Intent>() {
+ @Override
+ public Intent get() {
+ return intent;
+ }
+ }, new ActivityLifecycleDispatcherImpl(), null, tabCreatorManager);
+ }
+
+ private static class ChromeTabCreatorManager implements TabCreatorManager {
+ @Override
+ public TabCreatorManager.TabCreator getTabCreator(boolean incognito) {
+ Assert.assertFalse(incognito);
+ return new ChromeTabCreator(null, null, null, false);
+ }
+ }
+
+ private static class NonChromeTabCreatorManager implements TabCreatorManager {
+ @Override
+ public TabCreatorManager.TabCreator getTabCreator(boolean incognito) {
+ Assert.assertFalse(incognito);
+
+ // The important thing is this isn't ChromeTabCreator.
+ return new TabDelegate(false);
+ }
+ }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
index c32bc11..6d985033f 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
@@ -6,6 +6,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
@@ -15,6 +16,8 @@
import android.os.Bundle;
import android.view.View;
+import androidx.browser.customtabs.CustomTabsSessionToken;
+
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.mockito.ArgumentCaptor;
@@ -42,6 +45,7 @@
import org.chromium.chrome.browser.customtabs.shadows.ShadowExternalNavigationDelegateImpl;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
+import org.chromium.chrome.browser.init.StartupTabPreloader;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserverRegistrar;
@@ -54,8 +58,6 @@
import org.chromium.content_public.browser.NavigationController;
import org.chromium.content_public.browser.WebContents;
-import androidx.browser.customtabs.CustomTabsSessionToken;
-
/**
* A TestRule that sets up the mocks and contains helper methods for JUnit/Robolectric tests scoped
* to the content layer of Custom Tabs code.
@@ -88,6 +90,8 @@
@Mock public ToolbarManager toolbarManager;
@Mock public ChromeBrowserInitializer browserInitializer;
@Mock public ChromeFullscreenManager fullscreenManager;
+ @Mock
+ public StartupTabPreloader startupTabPreloader;
public final CustomTabActivityTabProvider tabProvider = new CustomTabActivityTabProvider();
@Captor public ArgumentCaptor<ActivityTabObserver> activityTabObserverCaptor;
@@ -116,6 +120,8 @@
// Default setup is toolbarManager doesn't consume back press event.
when(toolbarManager.back()).thenReturn(null);
+ when(startupTabPreloader.takeTabIfMatchingOrDestroy(any(), anyInt())).thenReturn(null);
+
doNothing().when(activityTabProvider).addObserverAndTrigger(
activityTabObserverCaptor.capture());
doNothing()
@@ -133,10 +139,15 @@
public CustomTabActivityTabController createTabController() {
return new CustomTabActivityTabController(activity,
- () -> customTabDelegateFactory, connection, intentDataProvider, activityTabProvider,
- tabObserverRegistrar, () -> compositorViewHolder, lifecycleDispatcher,
- warmupManager, tabPersistencePolicy, tabFactory, () -> customTabObserver,
- webContentsFactory, navigationEventObserver, tabProvider);
+ ()
+ -> customTabDelegateFactory,
+ connection, intentDataProvider, activityTabProvider, tabObserverRegistrar,
+ ()
+ -> compositorViewHolder,
+ lifecycleDispatcher, warmupManager, tabPersistencePolicy, tabFactory,
+ ()
+ -> customTabObserver,
+ webContentsFactory, navigationEventObserver, tabProvider, startupTabPreloader);
}
public CustomTabActivityNavigationController createNavigationController(
diff --git a/chrome/android/public/profiles/BUILD.gn b/chrome/android/public/profiles/BUILD.gn
index 67f2f7a..3a522a53 100644
--- a/chrome/android/public/profiles/BUILD.gn
+++ b/chrome/android/public/profiles/BUILD.gn
@@ -19,6 +19,7 @@
"java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java",
"java/src/org/chromium/chrome/browser/profiles/Profile.java",
"java/src/org/chromium/chrome/browser/profiles/ProfileKey.java",
+ "java/src/org/chromium/chrome/browser/profiles/ProfileManager.java",
"java/src/org/chromium/chrome/browser/profiles/ProfileManagerUtils.java",
]
}
@@ -28,6 +29,7 @@
"java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java",
"java/src/org/chromium/chrome/browser/profiles/Profile.java",
"java/src/org/chromium/chrome/browser/profiles/ProfileKey.java",
+ "java/src/org/chromium/chrome/browser/profiles/ProfileManager.java",
"java/src/org/chromium/chrome/browser/profiles/ProfileManagerUtils.java",
]
}
diff --git a/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/Profile.java b/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/Profile.java
index dbc510a..b81a9752 100644
--- a/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/Profile.java
+++ b/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/Profile.java
@@ -7,9 +7,7 @@
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
-import org.chromium.base.library_loader.LibraryProcessType;
import org.chromium.chrome.browser.cookies.CookiesFetcher;
-import org.chromium.content_public.browser.BrowserStartupController;
import org.chromium.content_public.browser.WebContents;
/**
@@ -29,8 +27,7 @@
public static Profile getLastUsedProfile() {
// TODO(crbug.com/704025): turn this into an assert once the bug is fixed
- if (!BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
- .isFullBrowserStarted()) {
+ if (!ProfileManager.isInitialized()) {
throw new IllegalStateException("Browser hasn't finished initialization yet!");
}
return (Profile) ProfileJni.get().getLastUsedProfile();
diff --git a/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/ProfileManager.java b/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/ProfileManager.java
new file mode 100644
index 0000000..49c9bfb
--- /dev/null
+++ b/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/ProfileManager.java
@@ -0,0 +1,55 @@
+// Copyright 2019 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.profiles;
+
+import org.chromium.base.ObserverList;
+import org.chromium.base.annotations.CalledByNative;
+
+/**
+ * Java interface to the C++ ProfileManager.
+ */
+public class ProfileManager {
+ private static ObserverList<Observer> sObservers = new ObserverList<>();
+ private static boolean sInitialized;
+
+ /** Observer for Profile creation. */
+ public static interface Observer {
+ /**
+ * Called whenever a profile is created.
+ * @param profile The profile that has just been created.
+ */
+ public void onProfileCreated(Profile profile);
+ }
+
+ /**
+ * Add an observer to be notified when profiles get created.
+ */
+ public static void addObserver(Observer observer) {
+ sObservers.addObserver(observer);
+ }
+
+ /**
+ * Remove an observer of profiles changes.
+ */
+ public static void removeObserver(Observer observer) {
+ sObservers.removeObserver(observer);
+ }
+
+ /**
+ * @return True iff any profile has been created.
+ */
+ public static boolean isInitialized() {
+ return sInitialized;
+ }
+
+ @CalledByNative
+ private static void onProfileAdded(Profile profile) {
+ // If a profile has been added, we know the ProfileManager has been initialized.
+ sInitialized = true;
+ for (Observer observer : sObservers) {
+ observer.onProfileCreated(profile);
+ }
+ }
+}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index c6f80ac..9282f6f 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2883,6 +2883,8 @@
"profiles/profile_android.h",
"profiles/profile_key_android.cc",
"profiles/profile_key_android.h",
+ "profiles/profile_manager_android.cc",
+ "profiles/profile_manager_android.h",
"search/contextual_search_policy_handler_android.cc",
"search/contextual_search_policy_handler_android.h",
"search_engines/template_url_service_factory_android.cc",
@@ -5381,8 +5383,8 @@
if (is_win || is_mac || is_desktop_linux || is_chromeos) {
deps += [
- "//chrome/browser/resources/management:polymer3_elements",
"//chrome/browser/resources/discards:discards_resources_gen",
+ "//chrome/browser/resources/management:polymer3_elements",
"//chrome/browser/ui/webui/discards:mojo_bindings_js",
"//services/resource_coordinator/public/mojom:mojom_js",
]
diff --git a/chrome/browser/android/download/download_manager_service.cc b/chrome/browser/android/download/download_manager_service.cc
index bc1aa10..0939cc06 100644
--- a/chrome/browser/android/download/download_manager_service.cc
+++ b/chrome/browser/android/download/download_manager_service.cc
@@ -202,10 +202,10 @@
void DownloadManagerService::Init(JNIEnv* env,
jobject obj,
- bool is_full_browser_started) {
+ bool is_profile_created) {
java_ref_.Reset(env, obj);
- if (is_full_browser_started) {
- OnFullBrowserStarted(env, obj);
+ if (is_profile_created) {
+ OnProfileCreated(env, obj);
} else {
// In reduced mode, only non-incognito downloads should be loaded.
DownloadStartupUtils::EnsureDownloadSystemInitialized(
@@ -215,7 +215,7 @@
}
}
-void DownloadManagerService::OnFullBrowserStarted(JNIEnv* env, jobject obj) {
+void DownloadManagerService::OnProfileCreated(JNIEnv* env, jobject obj) {
registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
content::NotificationService::AllSources());
// Register coordinator for each available profile.
diff --git a/chrome/browser/android/download/download_manager_service.h b/chrome/browser/android/download/download_manager_service.h
index 8af5bce..48a5db4a 100644
--- a/chrome/browser/android/download/download_manager_service.h
+++ b/chrome/browser/android/download/download_manager_service.h
@@ -49,13 +49,13 @@
DownloadManagerService();
~DownloadManagerService() override;
- // Called to Initialize this object. If |is_full_browser_started| is false,
- // it means only the service manager is launched. OnFullBrowserStarted() will
- // be called later when browser process fully launches.
- void Init(JNIEnv* env, jobject obj, bool is_full_browser_started);
+ // Called to Initialize this object. If |is_profile_created| is false,
+ // it means only the service manager is launched. OnProfileCreated() will
+ // be called later when the profile is created.
+ void Init(JNIEnv* env, jobject obj, bool is_profile_created);
- // Called when full browser process starts.
- void OnFullBrowserStarted(JNIEnv* env, jobject obj);
+ // Called when the prfile is created.
+ void OnProfileCreated(JNIEnv* env, jobject obj);
// Called to handle subsequent steps, after a download was determined as a OMA
// download type.
diff --git a/chrome/browser/chrome_browser_main_android.cc b/chrome/browser/chrome_browser_main_android.cc
index 8773e35..fb266d5 100644
--- a/chrome/browser/chrome_browser_main_android.cc
+++ b/chrome/browser/chrome_browser_main_android.cc
@@ -14,6 +14,7 @@
#include "chrome/browser/android/preferences/clipboard_android.h"
#include "chrome/browser/android/seccomp_support_detector.h"
#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile_manager.h"
#include "components/crash/content/browser/child_exit_observer_android.h"
#include "components/crash/content/browser/child_process_crash_observer_android.h"
#include "components/metrics/stability_metrics_helper.h"
@@ -75,6 +76,12 @@
return ChromeBrowserMainParts::PreEarlyInitialization();
}
+void ChromeBrowserMainPartsAndroid::PostEarlyInitialization() {
+ profile_manager_android_.reset(new ProfileManagerAndroid());
+ g_browser_process->profile_manager()->AddObserver(
+ profile_manager_android_.get());
+}
+
void ChromeBrowserMainPartsAndroid::PostBrowserStart() {
ChromeBrowserMainParts::PostBrowserStart();
diff --git a/chrome/browser/chrome_browser_main_android.h b/chrome/browser/chrome_browser_main_android.h
index 7bc1b4a6..d9f5975 100644
--- a/chrome/browser/chrome_browser_main_android.h
+++ b/chrome/browser/chrome_browser_main_android.h
@@ -8,6 +8,7 @@
#include "base/macros.h"
#include "chrome/browser/android/chrome_backup_watcher.h"
#include "chrome/browser/chrome_browser_main.h"
+#include "chrome/browser/profiles/profile_manager_android.h"
class ChromeBrowserMainPartsAndroid : public ChromeBrowserMainParts {
public:
@@ -19,6 +20,7 @@
int PreCreateThreads() override;
void PostProfileInit() override;
int PreEarlyInitialization() override;
+ void PostEarlyInitialization() override;
// ChromeBrowserMainParts overrides.
void PostBrowserStart() override;
@@ -26,6 +28,7 @@
private:
std::unique_ptr<android::ChromeBackupWatcher> backup_watcher_;
+ std::unique_ptr<ProfileManagerAndroid> profile_manager_android_;
DISALLOW_COPY_AND_ASSIGN(ChromeBrowserMainPartsAndroid);
};
diff --git a/chrome/browser/navigation_predictor/navigation_predictor.cc b/chrome/browser/navigation_predictor/navigation_predictor.cc
index ed06794..f5ec02e 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor.cc
+++ b/chrome/browser/navigation_predictor/navigation_predictor.cc
@@ -765,7 +765,7 @@
metrics[0]->source_url);
MergeMetricsSameTargetUrl(&metrics);
- if (metrics.empty())
+ if (metrics.empty() || viewport_size.IsEmpty())
return;
number_of_anchors_ = metrics.size();
diff --git a/chrome/browser/profiles/profile_manager_android.cc b/chrome/browser/profiles/profile_manager_android.cc
new file mode 100644
index 0000000..a02a575
--- /dev/null
+++ b/chrome/browser/profiles/profile_manager_android.cc
@@ -0,0 +1,21 @@
+// Copyright 2019 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.
+
+#include "chrome/browser/profiles/profile_manager_android.h"
+
+#include "chrome/android/public/profiles/jni_headers/ProfileManager_jni.h"
+#include "chrome/browser/profiles/profile_android.h"
+
+ProfileManagerAndroid::ProfileManagerAndroid() = default;
+
+ProfileManagerAndroid::~ProfileManagerAndroid() = default;
+
+void ProfileManagerAndroid::OnProfileAdded(Profile* profile) {
+ Java_ProfileManager_onProfileAdded(
+ base::android::AttachCurrentThread(),
+ ProfileAndroid::FromProfile(profile)->GetJavaObject());
+}
+
+void ProfileManagerAndroid::OnProfileMarkedForPermanentDeletion(
+ Profile* profile) {}
diff --git a/chrome/browser/profiles/profile_manager_android.h b/chrome/browser/profiles/profile_manager_android.h
new file mode 100644
index 0000000..d777de1
--- /dev/null
+++ b/chrome/browser/profiles/profile_manager_android.h
@@ -0,0 +1,21 @@
+// Copyright 2019 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.
+
+#ifndef CHROME_BROWSER_PROFILES_PROFILE_MANAGER_ANDROID_H_
+#define CHROME_BROWSER_PROFILES_PROFILE_MANAGER_ANDROID_H_
+
+#include <jni.h>
+
+#include "chrome/browser/profiles/profile_manager_observer.h"
+
+class ProfileManagerAndroid : public ProfileManagerObserver {
+ public:
+ ProfileManagerAndroid();
+ ~ProfileManagerAndroid() override;
+
+ void OnProfileAdded(Profile* profile) override;
+ void OnProfileMarkedForPermanentDeletion(Profile* profile) override;
+};
+
+#endif // CHROME_BROWSER_PROFILES_PROFILE_MANAGER_ANDROID_H_
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index d67fa33..ce51b2b 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -138968,6 +138968,31 @@
</summary>
</histogram>
+<histogram name="Startup.Android.StartupTabPreloader.TabLoaded" units="Boolean"
+ expires_after="2020-01-31">
+ <owner>[email protected]</owner>
+ <owner>[email protected]</owner>
+ <summary>
+ Android: Whether or not creation of a profile lead to the
+ StartupTabPreloader speculatively created a tab. Recorded when a profile is
+ created, assuming a StartupTabPreloader has been constructed for intents
+ with a url and either regular Chrome or a Custom Tab will be loaded.
+ </summary>
+</histogram>
+
+<histogram name="Startup.Android.StartupTabPreloader.TabTaken" units="Boolean"
+ expires_after="2020-01-31">
+ <owner>[email protected]</owner>
+ <owner>[email protected]</owner>
+ <summary>
+ Android: Whether or not a tab speculatively created by the
+ StartupTabPreloader was subsequently adopted by ChromeTabCreator. Recorded
+ when a tab is loaded, assuming a StartupTabPreloader has been constructed
+ for intents with a url for either regular Chrome or a Custom Tab will be
+ loaded.
+ </summary>
+</histogram>
+
<histogram name="Startup.AppListFirstPaintColdStart" units="ms"
expires_after="2018-03-20">
<obsolete>