blob: d740579598b6b54d5866cefc47bff0d053cdc38a [file] [log] [blame]
// 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();
}
}
}