chromium/chrome/browser/ui/android/preloading/java/src/org/chromium/chrome/browser/preloading/AndroidPrerenderManager.java

// Copyright 2024 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.preloading;

import org.jni_zero.JniType;
import org.jni_zero.NativeMethods;

import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.content_public.browser.WebContents;
import org.chromium.url.GURL;

/**
 * Java bridge to handle conditional prerendering using as the user interacts with UI.
 *
 * <p>AndroidPrerenderManager provides interface to start or stop prerendering to the (native).
 */
public class AndroidPrerenderManager {
    private long mNativeAndroidPrerenderManager;
    private WebContents mWebContents;
    private static AndroidPrerenderManager sAndroidPrerenderManager;

    private final TabObserver mTabObserver =
            new EmptyTabObserver() {

                @Override
                public void onContentChanged(Tab tab) {
                    // Opening or navigating back to a new tab page will trigger onContentChanged as
                    // well, resetting variables in this scenario should be avoided.
                    if (tab.getUrl().getSpec().equals(UrlConstants.NTP_URL)
                            || tab.getUrl().getSpec().equals("")) {
                        return;
                    }
                    sAndroidPrerenderManager = null;
                    mWebContents = null;
                }

                @Override
                public void onDestroyed(Tab tab) {
                    sAndroidPrerenderManager = null;
                    mWebContents = null;
                }
            };

    public static AndroidPrerenderManager getAndroidPrerenderManager() {
        assert ThreadUtils.runningOnUiThread()
                : "AndroidPrerenderManager should only be called on the UIThread";
        if (sAndroidPrerenderManager == null) {
            sAndroidPrerenderManager = new AndroidPrerenderManager();
        }
        return sAndroidPrerenderManager;
    }

    public static void setAndroidPrerenderManagerForTesting(
            AndroidPrerenderManager androidPrerenderManager) {
        sAndroidPrerenderManager = androidPrerenderManager;
    }

    public static void clearAndroidPrerenderManagerForTesting() {
        sAndroidPrerenderManager = null;
    }

    private AndroidPrerenderManager() {
        mNativeAndroidPrerenderManager = AndroidPrerenderManagerJni.get().init(this);
    }

    /**
     * Link the webContents from the tab with the AndroidPrerenderManager, and observe the tab.
     *
     * @param tab The tab to be linked with the AndroidPrerenderManager.
     */
    public void initializeWithTab(Tab tab) {
        mWebContents = tab.getWebContents();
        tab.addObserver(mTabObserver);
    }

    /**
     * Start prerendering with the given URL. This should only be called when
     * AndroidPrerenderManager is properly initialized.
     *
     * @param prerenderUrl The url to be prerendered.
     * @return Whether prerendering has been started successfully.
     */
    public boolean startPrerendering(GURL prerenderUrl) {
        if (mNativeAndroidPrerenderManager == 0 || mWebContents == null) return false;
        return AndroidPrerenderManagerJni.get()
                .startPrerendering(mNativeAndroidPrerenderManager, prerenderUrl, mWebContents);
    }

    /**
     * Stop the started prerendering in the AndroidPrerenderManager instance. This is used to cancel
     * the on-going but stale prerendering.
     */
    public void stopPrerendering() {
        if (mNativeAndroidPrerenderManager == 0 || mWebContents == null) return;
        AndroidPrerenderManagerJni.get()
                .stopPrerendering(mNativeAndroidPrerenderManager, mWebContents);
    }

    @NativeMethods
    public interface Natives {
        long init(AndroidPrerenderManager caller);

        boolean startPrerendering(
                long nativeAndroidPrerenderManager,
                @JniType("GURL") GURL prerenderUrl,
                WebContents webContents);

        void stopPrerendering(long nativeAndroidPrerenderManager, WebContents webContents);
    }
}