chromium/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabRegistrationObserver.java

// Copyright 2022 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.tabmodel;

import android.util.SparseArray;

import org.chromium.base.ObserverList;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabCreationState;
import org.chromium.chrome.browser.tab.TabLaunchType;

import java.util.LinkedHashSet;
import java.util.List;

/** Allows observing registration (e.g. add / remove) events of Tabs from a TabModelSelector. */
public final class TabModelSelectorTabRegistrationObserver {
    private final TabModelSelectorTabModelObserver mTabModelObserver;
    private final SparseArray<Tab> mTabsToClose = new SparseArray<>();
    private final ObserverList<Observer> mObservers = new ObserverList<>();
    private final LinkedHashSet<Tab> mRegisteredTabs = new LinkedHashSet<>();

    private boolean mTabModelSelectorRegistrationComplete;

    /** The Observer that will receive updates about a tabs registration to a tab model. */
    public interface Observer {
        /**
         * Called when a tab is registered to a tab model this selector is managing.
         * @param tab The registered Tab.
         */
        void onTabRegistered(Tab tab);

        /**
         * Called when a tab is unregistered from a tab model this selector is managing.
         * @param tab The unregistered Tab.
         */
        void onTabUnregistered(Tab tab);
    }

    /**
     * Constructs an observer that should be notified of tab changes for all tabs owned
     * by a specified {@link TabModelSelector}.  Any Tabs created after this call will be
     * observed as well, and Tabs removed will no longer have their information broadcast.
     *
     * <p>
     * {@link #destroy()} must be called to unregister this observer.
     *
     * @param selector The selector that owns the Tabs that should notify this observer.
     */
    public TabModelSelectorTabRegistrationObserver(TabModelSelector selector) {
        mTabModelObserver =
                new TabModelSelectorTabModelObserver(selector) {
                    @Override
                    public void didAddTab(
                            Tab tab,
                            @TabLaunchType int type,
                            @TabCreationState int creationState,
                            boolean markedForSelection) {
                        // This observer is automatically removed by tab when it is destroyed.
                        onTabRegistered(tab);
                    }

                    @Override
                    public void willCloseTab(Tab tab, boolean didCloseAlone) {
                        mTabsToClose.put(tab.getId(), tab);
                    }

                    @Override
                    public void tabClosureUndone(Tab tab) {
                        mTabsToClose.remove(tab.getId());
                    }

                    @Override
                    public void onFinishingTabClosure(Tab tab) {
                        if (mTabsToClose.get(tab.getId()) != null) {
                            mTabsToClose.remove(tab.getId());
                            onTabUnregistered(tab);
                        }
                    }

                    @Override
                    public void tabRemoved(Tab tab) {
                        onTabUnregistered(tab);
                    }

                    @Override
                    protected void onRegistrationComplete() {
                        assert !mTabModelSelectorRegistrationComplete;
                        mTabModelSelectorRegistrationComplete = true;

                        List<TabModel> tabModels = selector.getModels();
                        for (int i = 0; i < tabModels.size(); i++) {
                            TabModel tabModel = tabModels.get(i);
                            TabList comprehensiveTabList = tabModel.getComprehensiveModel();
                            for (int j = 0; j < comprehensiveTabList.getCount(); j++) {
                                onTabRegistered(comprehensiveTabList.getTabAt(j));
                            }
                        }
                    }
                };
    }

    /**
     * Adds an observer for future tab registration events, and notifies the observer of any tabs
     * that have already been registered (synchronously).
     * @param observer The observer to be added.
     */
    public void addObserverAndNotifyExistingTabRegistration(Observer observer) {
        mObservers.addObserver(observer);
        if (mTabModelSelectorRegistrationComplete) {
            for (Tab tab : mRegisteredTabs) observer.onTabRegistered(tab);
        }
    }

    /**
     * Removes an observer for subsequent tab registration events.
     * @param observer The observer to be removed.
     */
    public void removeObserver(Observer observer) {
        mObservers.removeObserver(observer);
    }

    /**
     * Called when a tab is registered to a tab model this selector is managing.
     * @param tab The registered Tab.
     */
    private void onTabRegistered(Tab tab) {
        if (mRegisteredTabs.add(tab)) {
            for (Observer observer : mObservers) observer.onTabRegistered(tab);
        }
    }

    /**
     * Called when a tab is unregistered from a tab model this selector is managing.
     * @param tab The unregistered Tab.
     */
    private void onTabUnregistered(Tab tab) {
        if (mRegisteredTabs.remove(tab)) {
            for (Observer observer : mObservers) observer.onTabUnregistered(tab);
        }
    }

    /** Destroys the observer and removes itself as a listener for Tab updates. */
    public void destroy() {
        mTabModelObserver.destroy();
        Tab[] tabs = mRegisteredTabs.toArray(new Tab[0]);
        for (Tab tab : tabs) onTabUnregistered(tab);
        assert mRegisteredTabs.isEmpty();
    }
}