chromium/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/DefaultCustomTabIntentHandlingStrategy.java

// Copyright 2019 The Chromium Authors
// 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.customtabs.content;

import android.text.TextUtils;

import androidx.annotation.VisibleForTesting;

import dagger.Lazy;

import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.customtabs.CustomTabAuthUrlHeuristics;
import org.chromium.chrome.browser.customtabs.CustomTabNavigationEventObserver;
import org.chromium.chrome.browser.customtabs.CustomTabObserver;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.url.GURL;

import javax.inject.Inject;

/**
 * Default implementation of {@link CustomTabIntentHandlingStrategy}. Navigates the Custom Tab to
 * urls provided in intents.
 */
@ActivityScope
public class DefaultCustomTabIntentHandlingStrategy implements CustomTabIntentHandlingStrategy {
    private final CustomTabActivityTabProvider mTabProvider;
    private final CustomTabActivityNavigationController mNavigationController;
    private final CustomTabNavigationEventObserver mNavigationEventObserver;
    private final Lazy<CustomTabObserver> mCustomTabObserver;

    @Inject
    public DefaultCustomTabIntentHandlingStrategy(
            CustomTabActivityTabProvider tabProvider,
            CustomTabActivityNavigationController navigationController,
            CustomTabNavigationEventObserver navigationEventObserver,
            Lazy<CustomTabObserver> customTabObserver) {
        mTabProvider = tabProvider;
        mNavigationController = navigationController;
        mNavigationEventObserver = navigationEventObserver;
        mCustomTabObserver = customTabObserver;
    }

    @Override
    public void handleInitialIntent(BrowserServicesIntentDataProvider intentDataProvider) {
        @TabCreationMode int initialTabCreationMode = mTabProvider.getInitialTabCreationMode();
        if (initialTabCreationMode == TabCreationMode.HIDDEN) {
            handleInitialLoadForHiddenTab(initialTabCreationMode, intentDataProvider);
        } else {
            LoadUrlParams params = new LoadUrlParams(intentDataProvider.getUrlToLoad());
            mNavigationController.navigate(params, intentDataProvider.getIntent());
        }

        CustomTabAuthUrlHeuristics.recordUrlParamsHistogram(intentDataProvider.getUrlToLoad());
    }

    // TODO(yfriedman): Remove & inline once CustomTabs junit tests can be created from a provided
    // GURL. This depends on switching *IntentDataProvider over to GURL.
    // https://crbug.com/783819
    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    public GURL getGurlForUrl(String url) {
        return new GURL(url);
    }

    // The hidden tab case needs a bit of special treatment.
    private void handleInitialLoadForHiddenTab(
            @TabCreationMode int initialTabCreationMode,
            BrowserServicesIntentDataProvider intentDataProvider) {
        Tab tab = mTabProvider.getTab();
        if (tab == null) {
            throw new IllegalStateException("handleInitialIntent called before Tab created");
        }
        String url = intentDataProvider.getUrlToLoad();
        GURL gurl = getGurlForUrl(url);

        // Manually generating metrics in case the hidden tab has completely finished loading.
        if (!tab.isLoading() && !tab.isShowingErrorPage()) {
            mCustomTabObserver.get().onPageLoadStarted(tab, gurl);
            mCustomTabObserver.get().onPageLoadFinished(tab, gurl);
            mNavigationEventObserver.onPageLoadStarted(tab, gurl);
            mNavigationEventObserver.onPageLoadFinished(tab, gurl);
        }

        // No actual load to do if the hidden tab already has the exact correct url.
        String speculatedUrl = mTabProvider.getSpeculatedUrl();

        boolean useSpeculation = TextUtils.equals(speculatedUrl, url);
        boolean hasCommitted = !tab.getWebContents().getLastCommittedUrl().isEmpty();
        mCustomTabObserver
                .get()
                .trackNextPageLoadForHiddenTab(
                        useSpeculation, hasCommitted, intentDataProvider.getIntent());
        if (useSpeculation) {
            if (tab.isLoading()) {
                // CustomTabObserver and CustomTabActivityNavigationObserver are attached
                // as observers in CustomTabActivityTabController, not when the navigation is
                // initiated in HiddenTabHolder.
                mCustomTabObserver.get().onPageLoadStarted(tab, gurl);
                mNavigationEventObserver.onPageLoadStarted(tab, gurl);
            }

            return;
        }

        LoadUrlParams params = new LoadUrlParams(url);

        // The following block is a hack that deals with urls preloaded with
        // the wrong fragment. Does an extra pageload and replaces history.
        if (speculatedUrl != null && UrlUtilities.urlsFragmentsDiffer(speculatedUrl, url)) {
            params.setShouldReplaceCurrentEntry(true);
        }

        mNavigationController.navigate(params, intentDataProvider.getIntent());
    }

    @Override
    public void handleNewIntent(BrowserServicesIntentDataProvider intentDataProvider) {
        String url = intentDataProvider.getUrlToLoad();
        if (TextUtils.isEmpty(url)) return;
        LoadUrlParams params = new LoadUrlParams(url);

        if (intentDataProvider.isWebApkActivity()) {
            // The back stack should be cleared when a WebAPK receives a deep link intent. This is
            // unnecessary for Trusted Web Activities and new-style WebAPKs because Trusted Web
            // Activities and new-style WebAPKs are restarted when they receive an intent from a
            // deep link.
            params.setShouldClearHistoryList(true);
        }

        mNavigationController.navigate(params, intentDataProvider.getIntent());
    }
}