chromium/ios/chrome/browser/tabs/model/tab_helper_delegate_installer.h

// 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.

#ifndef IOS_CHROME_BROWSER_TABS_MODEL_TAB_HELPER_DELEGATE_INSTALLER_H_
#define IOS_CHROME_BROWSER_TABS_MODEL_TAB_HELPER_DELEGATE_INSTALLER_H_

#include "base/check.h"
#import "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/browser/browser_observer.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list_observer.h"

// TabHelperDelegateInstaller is used to install a single delegate instance as
// the delegate for the tab helper associated with every WebState in a Browser's
// WebStateList.  When a TabHelperDelegateInstaller or its associated Browser is
// destroyed, all tab helpers' delegates are reset to nullptr.  Useful when a
// Browser-scoped object acts as the delegate for every tab helper.
//
// Usage example for a BrowserUserData that owns the tab helper delegates for
// FooTabHelper.  Two installers are used, one for the main delegate that is set
// via FooTabHelper::SetDelegate(), and one for a UI delegate that is set via
// FooTabHelper::SetUIDelegate():
//
//   class FooBrowserAgent : public BrowserUserData<FooBrowserAgent> {
//    public:
//     ...
//    private:
//     explicit FooBrowserAgent(Browser* browser)
//       : delegate_installer_(&tab_helper_delegate_, browser),
//         ui_delegate_installer_(&tab_helper_ui_delegate_, browser) {}
//
//     FooTabHelperDelegate tab_helper_delegate_;
//     TabHelperDelegateInstaller<FooTabHelper,
//                                FooTabHelperDelegate> delegate_installer_;
//
//     FooTabHelperUIDelegate tab_helper_ui_delegate_;
//     TabHelperDelegateInstaller<FooTabHelper,
//                                FooTabHelperUIDelegate,
//                                &FooTabHelper::SetUIDelegate>
//       ui_delegate_installer_;
//   };
template <class Helper,
          class Delegate,
          void (Helper::*SetDelFn)(Delegate*) = &Helper::SetDelegate>
class TabHelperDelegateInstaller {
 public:
  TabHelperDelegateInstaller(Delegate* delegate, Browser* browser)
      : installer_(delegate, browser->GetWebStateList()),
        shutdown_helper_(&installer_, browser) {}
  ~TabHelperDelegateInstaller() = default;

  TabHelperDelegateInstaller(const TabHelperDelegateInstaller&) = delete;
  TabHelperDelegateInstaller& operator=(const TabHelperDelegateInstaller&) =
      delete;

 private:
  // Helper object that sets up the delegates for all Helpers in a Browser's
  // WebStateList.
  class Installer : public WebStateListObserver {
   public:
    Installer(Delegate* delegate, WebStateList* web_state_list)
        : delegate_(delegate), web_state_list_(web_state_list) {
      DCHECK(delegate_);
      DCHECK(web_state_list_);
      scoped_observation_.Observe(web_state_list_.get());
      for (int i = 0; i < web_state_list_->count(); ++i) {
        SetTabHelperDelegate(web_state_list_->GetWebStateAt(i),
                             delegate_.get());
      }
    }
    ~Installer() override { Disconnect(); }

    // Uninstalls the delegate and stops observing the WebStateList.
    void Disconnect() {
      if (!web_state_list_)
        return;
      for (int i = 0; i < web_state_list_->count(); ++i) {
        SetTabHelperDelegate(web_state_list_->GetWebStateAt(i), nullptr);
      }
      DCHECK(scoped_observation_.IsObservingSource(web_state_list_.get()));
      scoped_observation_.Reset();
      web_state_list_ = nullptr;
    }

   private:
    // WebStateListObserver:
    void WebStateListWillChange(WebStateList* web_state_list,
                                const WebStateListChangeDetach& detach_change,
                                const WebStateListStatus& status) override {
      SetTabHelperDelegate(detach_change.detached_web_state(), nullptr);
    }

    void WebStateListDidChange(WebStateList* web_state_list,
                               const WebStateListChange& change,
                               const WebStateListStatus& status) override {
      switch (change.type()) {
        case WebStateListChange::Type::kStatusOnly:
          // Do nothing when a WebState is selected and its status is updated.
          break;
        case WebStateListChange::Type::kDetach:
          // Do nothing when a WebState is detached.
          break;
        case WebStateListChange::Type::kMove:
          // Do nothing when a WebState is moved.
          break;
        case WebStateListChange::Type::kReplace: {
          const WebStateListChangeReplace& replace_change =
              change.As<WebStateListChangeReplace>();
          SetTabHelperDelegate(replace_change.replaced_web_state(), nullptr);
          SetTabHelperDelegate(replace_change.inserted_web_state(),
                               delegate_.get());
          break;
        }
        case WebStateListChange::Type::kInsert: {
          const WebStateListChangeInsert& insert_change =
              change.As<WebStateListChangeInsert>();
          SetTabHelperDelegate(insert_change.inserted_web_state(),
                               delegate_.get());
          break;
        }
        case WebStateListChange::Type::kGroupCreate:
          // Do nothing when a group is created.
          break;
        case WebStateListChange::Type::kGroupVisualDataUpdate:
          // Do nothing when a tab group's visual data are updated.
          break;
        case WebStateListChange::Type::kGroupMove:
          // Do nothing when a tab group is moved.
          break;
        case WebStateListChange::Type::kGroupDelete:
          // Do nothing when a group is deleted.
          break;
      }
    }

    // Sets the delegate for `web_state`'s Helper to `delegate`.
    void SetTabHelperDelegate(web::WebState* web_state, Delegate* delegate) {
      (Helper::FromWebState(web_state)->*SetDelFn)(delegate);
    }

    // The delegate that is installed for each WebState in the WebStateList.
    raw_ptr<Delegate> delegate_ = nullptr;
    // The WebStateList whose Helpers's delegates are being installed.
    raw_ptr<WebStateList> web_state_list_ = nullptr;
    // Scoped observer for `web_state_list_`.
    base::ScopedObservation<WebStateList, WebStateListObserver>
        scoped_observation_{this};
  };

  // Helper object that sets up the delegate installer and tears it down
  // when the Browser is destroyed.
  class BrowserShutdownHelper : public BrowserObserver {
   public:
    BrowserShutdownHelper(Installer* installer, Browser* browser)
        : installer_(installer) {
      DCHECK(installer_);
      DCHECK(browser);
      scoped_observation_.Observe(browser);
    }
    ~BrowserShutdownHelper() override = default;

   private:
    // BrowserObserver:
    void BrowserDestroyed(Browser* browser) override {
      installer_->Disconnect();
      DCHECK(scoped_observation_.IsObservingSource(browser));
      scoped_observation_.Reset();
    }

    // The installer used to set up the Delegates.
    raw_ptr<Installer> installer_ = nullptr;
    // Scoped observer for the Browser.
    base::ScopedObservation<Browser, BrowserObserver> scoped_observation_{this};
  };

  // Helper object that installs delegates for all the Browser's tab helpers.
  Installer installer_;
  // Helper object that sets up and tears down the Installer for a Browser.
  BrowserShutdownHelper shutdown_helper_;
};

#endif  // IOS_CHROME_BROWSER_TABS_MODEL_TAB_HELPER_DELEGATE_INSTALLER_H_