chromium/chrome/android/java/src/org/chromium/chrome/browser/tab/RedirectHandlerTabHelper.java

// Copyright 2020 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.tab;

import android.content.Intent;

import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsIntent;

import org.chromium.base.IntentUtils;
import org.chromium.base.UserData;
import org.chromium.base.UserDataHost;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.LaunchIntentDispatcher;
import org.chromium.components.external_intents.RedirectHandler;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.ui.base.WindowAndroid;

/** This class glues RedirectHandler instances to Tabs. */
public class RedirectHandlerTabHelper extends EmptyTabObserver implements UserData {
    private static final Class<RedirectHandlerTabHelper> USER_DATA_KEY =
            RedirectHandlerTabHelper.class;

    private Tab mTab;
    private RedirectHandler mRedirectHandler;

    /**
     * Returns {@link RedirectHandler} that hangs on to a given {@link Tab}.
     * If not present, creates a new instance and associate it with the {@link UserDataHost}
     * that the {@link Tab} manages.
     * @param tab Tab instance that the RedirectHandler hangs on to.
     * @return RedirectHandler for a given Tab.
     */
    public static RedirectHandler getOrCreateHandlerFor(Tab tab) {
        UserDataHost host = tab.getUserDataHost();
        RedirectHandlerTabHelper helper = host.getUserData(USER_DATA_KEY);
        if (helper == null) {
            helper = new RedirectHandlerTabHelper(tab);
            host.setUserData(USER_DATA_KEY, helper);
            tab.addObserver(helper);
        }
        return helper.mRedirectHandler;
    }

    /**
     * @return {@link RedirectHandler} hanging to the given {@link Tab},
     *     or {@code null} if there is no instance available.
     */
    public static @Nullable RedirectHandler getHandlerFor(Tab tab) {
        RedirectHandlerTabHelper helper = tab.getUserDataHost().getUserData(USER_DATA_KEY);
        if (helper == null) return null;
        return helper.mRedirectHandler;
    }

    /**
     * Replace {@link RedirectHandler} instance for the Tab with the new one.
     * @return Old {@link RedirectHandler} associated with the Tab. Could be {@code null}.
     */
    public static RedirectHandler swapHandlerFor(Tab tab, RedirectHandler newHandler) {
        assert newHandler != null;
        RedirectHandlerTabHelper helper = tab.getUserDataHost().getUserData(USER_DATA_KEY);
        if (helper == null) {
            getOrCreateHandlerFor(tab);
            helper = tab.getUserDataHost().getUserData(USER_DATA_KEY);
        }
        RedirectHandler oldHandler = helper.mRedirectHandler;
        helper.mRedirectHandler = newHandler;
        return oldHandler;
    }

    private RedirectHandlerTabHelper(Tab tab) {
        mTab = tab;
        mRedirectHandler = RedirectHandler.create();
    }

    private RedirectHandlerTabHelper(Tab tab, RedirectHandler handler) {
        mTab = tab;
        mRedirectHandler = handler;
    }

    @Override
    public void destroy() {
        mTab.removeObserver(this);
        mTab = null;
    }

    @Override
    public void onHidden(Tab tab, @TabHidingType int type) {
        mRedirectHandler.clear();
    }

    @Override
    public void onDidFinishNavigationInPrimaryMainFrame(Tab tab, NavigationHandle navigation) {
        if (navigation.isPageActivation()) {
            // Page Activations (e.g. for back/forward cache or Prerender) don't trigger
            // NavigationThrottles, so the RedirectHandler doesn't have insight into these
            // navigations, and we don't want to consider navigations after a Page Activation to be
            // part of the previous navigation chain.
            mRedirectHandler.clear();
        }
    }

    @Override
    public void onActivityAttachmentChanged(Tab tab, @Nullable WindowAndroid window) {
        // Intentionally do nothing to prevent automatic observer removal on detachment.
    }

    /** Wrapper around RedirectHandler#updateIntent() that supplies //chrome-level params. */
    public static void updateIntentInTab(Tab tab, @Nullable Intent intent) {
        boolean isCustomTab = false;
        boolean sendToExternalHandler = false;
        boolean startedTabbedChromeTask = false;
        if (intent != null) {
            isCustomTab = LaunchIntentDispatcher.isCustomTabIntent(intent);
            sendToExternalHandler = CustomTabsIntent.isSendToExternalDefaultHandlerEnabled(intent);
            startedTabbedChromeTask =
                    IntentUtils.safeGetBooleanExtra(
                            intent, IntentHandler.EXTRA_STARTED_TABBED_CHROME_TASK, false);
        }
        RedirectHandlerTabHelper.getOrCreateHandlerFor(tab)
                .updateIntent(intent, isCustomTab, sendToExternalHandler, startedTabbedChromeTask);
    }
}