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

// Copyright 2014 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 org.chromium.base.ThreadUtils;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;

import java.util.ArrayList;
import java.util.List;

/** Observer of tab changes for all tabs owned by a {@link TabModelSelector}. */
public class TabModelSelectorTabObserver extends EmptyTabObserver {
    private final TabModelSelectorTabRegistrationObserver mTabRegistrationObserver;
    private boolean mShouldDeferTabRegisterNotifications;
    private List<Tab> mDeferredTabs = new ArrayList<>();
    private boolean mIsDestroyed;
    private boolean mIsDeferredInitializationFinished;

    /**
     * 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 TabModelSelectorTabObserver(TabModelSelector selector) {
        mTabRegistrationObserver = new TabModelSelectorTabRegistrationObserver(selector);
        mShouldDeferTabRegisterNotifications = true;
        mTabRegistrationObserver.addObserverAndNotifyExistingTabRegistration(
                createRegistrationObserver());
        mShouldDeferTabRegisterNotifications = false;
        // Run |onTabRegistered| asynchronously so it is done after the tasks in the
        // constructor of the inherited classes are completed and the relevant local
        // variables are ready.
        // TODO(jinsukkim): Consider making this class final, and introducing an inner
        //     class that extends EmptyTabObserver + provides onTab[Un]Registered instead.
        ThreadUtils.getUiThreadHandler()
                .postAtFrontOfQueue(
                        () -> {
                            assert !mShouldDeferTabRegisterNotifications;
                            for (Tab tab : mDeferredTabs) {
                                if (tab.isDestroyed()) continue;
                                onTabRegistered(tab);
                            }
                            mDeferredTabs.clear();
                            mIsDeferredInitializationFinished = true;
                        });
    }

    private TabModelSelectorTabRegistrationObserver.Observer createRegistrationObserver() {
        return new TabModelSelectorTabRegistrationObserver.Observer() {
            @Override
            public void onTabRegistered(Tab tab) {
                if (tab.isDestroyed()) return;
                tab.addObserver(TabModelSelectorTabObserver.this);
                if (mShouldDeferTabRegisterNotifications) {
                    mDeferredTabs.add(tab);
                } else {
                    TabModelSelectorTabObserver.this.onTabRegistered(tab);
                }
            }

            @Override
            public void onTabUnregistered(Tab tab) {
                if (mShouldDeferTabRegisterNotifications) {
                    boolean didExist = mDeferredTabs.remove(tab);
                    assert didExist
                            : "Attempting to remove a tab during deferred registration that "
                                    + "never was added";
                    return;
                }

                if (mIsDestroyed) {
                    performUnregister(tab);
                } else {
                    // Post the removal of the observer so that other tab events are
                    // notified before removing the tab observer (e.g. detach tab from
                    // activity).
                    PostTask.postTask(TaskTraits.UI_DEFAULT, () -> performUnregister(tab));
                }
                TabModelSelectorTabObserver.this.onTabUnregistered(tab);
            }

            private void performUnregister(Tab tab) {
                // If the tab as been destroyed we cannot access PersistedTabData.
                if (tab.isDestroyed()) return;
                tab.removeObserver(TabModelSelectorTabObserver.this);
            }
        };
    }

    /**
     * Called when a tab is registered to a tab model this selector is managing.
     * @param tab The registered Tab.
     */
    protected void onTabRegistered(Tab tab) {}

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

    /** Destroys the observer and removes itself as a listener for Tab updates. */
    public void destroy() {
        mIsDestroyed = true;
        mTabRegistrationObserver.destroy();
    }

    boolean isDeferredInitializationFinishedForTesting() {
        return mIsDeferredInitializationFinished;
    }
}