TabStripModelObserver: clean up on destruction

This change has TabStripModelObserver remember which TabStripModels it
is observing, and unregister from those if necessary on destruction. It
also removes instances of now-unnecessary cleanup code in subclass
destructors.

This change also introduces a new observable event on
TabStripModelObserver: OnTabStripModelDestroyed. This event
notifies observers that the model is being destroyed so
they can do any necessary cleanup. In the future most uses
of TabStripModelObserver::TabStripEmpty() should migrate to
OnTabStripModelDestroyed().


Bug: 991308
Change-Id: Ifed459ec4d88136db4a00e4386dc6c2095048b46
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1739552
Commit-Queue: Elly Fong-Jones <[email protected]>
Auto-Submit: Elly Fong-Jones <[email protected]>
Reviewed-by: Nico Weber <[email protected]>
Cr-Commit-Position: refs/heads/master@{#686075}
diff --git a/chrome/browser/apps/platform_apps/app_browsertest.cc b/chrome/browser/apps/platform_apps/app_browsertest.cc
index d96e2642..d3e0a73 100644
--- a/chrome/browser/apps/platform_apps/app_browsertest.cc
+++ b/chrome/browser/apps/platform_apps/app_browsertest.cc
@@ -101,7 +101,7 @@
  public:
   TabsAddedNotificationObserver(Browser* browser, size_t observations)
       : observations_(observations) {
-    tab_strip_observer_.Add(browser->tab_strip_model());
+    browser->tab_strip_model()->AddObserver(this);
   }
 
   ~TabsAddedNotificationObserver() override = default;
@@ -129,8 +129,6 @@
   base::RunLoop run_loop_;
   size_t observations_;
   std::vector<content::WebContents*> observed_tabs_;
-  ScopedObserver<TabStripModel, TabStripModelObserver> tab_strip_observer_{
-      this};
 
   DISALLOW_COPY_AND_ASSIGN(TabsAddedNotificationObserver);
 };
diff --git a/chrome/browser/captive_portal/captive_portal_browsertest.cc b/chrome/browser/captive_portal/captive_portal_browsertest.cc
index 86715b2..1e6e520d 100644
--- a/chrome/browser/captive_portal/captive_portal_browsertest.cc
+++ b/chrome/browser/captive_portal/captive_portal_browsertest.cc
@@ -515,8 +515,8 @@
 class TabActivationWaiter : public TabStripModelObserver {
  public:
   explicit TabActivationWaiter(TabStripModel* tab_strip_model)
-      : number_of_unconsumed_active_tab_changes_(0), scoped_observer_(this) {
-    scoped_observer_.Add(tab_strip_model);
+      : number_of_unconsumed_active_tab_changes_(0) {
+    tab_strip_model->AddObserver(this);
   }
 
   void WaitForActiveTabChange() {
@@ -548,7 +548,6 @@
  private:
   scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
   int number_of_unconsumed_active_tab_changes_;
-  ScopedObserver<TabStripModel, TabActivationWaiter> scoped_observer_;
 
   DISALLOW_COPY_AND_ASSIGN(TabActivationWaiter);
 };
diff --git a/chrome/browser/download/download_frame_policy_browsertest.cc b/chrome/browser/download/download_frame_policy_browsertest.cc
index 33ef238..064d6ed 100644
--- a/chrome/browser/download/download_frame_policy_browsertest.cc
+++ b/chrome/browser/download/download_frame_policy_browsertest.cc
@@ -99,8 +99,8 @@
   PopupPageLoadMetricsWaiterInitializer(
       TabStripModel* tab_strip_model,
       std::unique_ptr<page_load_metrics::PageLoadMetricsTestWaiter>* waiter)
-      : waiter_(waiter), scoped_observer_(this) {
-    scoped_observer_.Add(tab_strip_model);
+      : waiter_(waiter) {
+    tab_strip_model->AddObserver(this);
   }
 
   void OnTabStripModelChanged(
@@ -117,8 +117,6 @@
 
  private:
   std::unique_ptr<page_load_metrics::PageLoadMetricsTestWaiter>* waiter_;
-  ScopedObserver<TabStripModel, PopupPageLoadMetricsWaiterInitializer>
-      scoped_observer_;
 
   DISALLOW_COPY_AND_ASSIGN(PopupPageLoadMetricsWaiterInitializer);
 };
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
index 2e1bab0..73e03b3 100644
--- a/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
@@ -431,10 +431,6 @@
       browser_->tab_strip_model()->AddObserver(this);
     }
 
-    ~IndicatorChangeObserver() override {
-      browser_->tab_strip_model()->RemoveObserver(this);
-    }
-
     TabAlertState last_alert_state() const { return last_alert_state_; }
 
     void TabChangedAt(content::WebContents* contents,
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc b/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
index ac34f48..2110bfc 100644
--- a/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
+++ b/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
@@ -76,7 +76,7 @@
         delay_url_(delay_url),
         until_url_suffix_(until_url_suffix),
         script_(script) {
-    tab_strip_observer_.Add(browser->tab_strip_model());
+    browser->tab_strip_model()->AddObserver(this);
   }
 
   ~DelayLoadStartAndExecuteJavascript() override {}
@@ -91,7 +91,7 @@
 
     content::WebContentsObserver::Observe(
         change.GetInsert()->contents[0].contents);
-    tab_strip_observer_.RemoveAll();
+    tab_strip_model->RemoveObserver(this);
   }
 
   // WebContentsObserver:
@@ -165,8 +165,6 @@
 
   base::WeakPtr<WillStartRequestObserverThrottle> throttle_;
 
-  ScopedObserver<TabStripModel, TabStripModelObserver> tab_strip_observer_{
-      this};
   GURL delay_url_;
   std::string until_url_suffix_;
   std::string script_;
diff --git a/chrome/browser/extensions/extension_unload_browsertest.cc b/chrome/browser/extensions/extension_unload_browsertest.cc
index 0de3b152..8fa6d34c 100644
--- a/chrome/browser/extensions/extension_unload_browsertest.cc
+++ b/chrome/browser/extensions/extension_unload_browsertest.cc
@@ -37,8 +37,8 @@
 class TestTabStripModelObserver : public TabStripModelObserver {
  public:
   explicit TestTabStripModelObserver(TabStripModel* model)
-      : model_(model), desired_count_(0), scoped_observer_(this) {
-    scoped_observer_.Add(model);
+      : model_(model), desired_count_(0) {
+    model->AddObserver(this);
   }
   ~TestTabStripModelObserver() override = default;
 
@@ -62,7 +62,6 @@
   TabStripModel* model_;
   int desired_count_;
   base::RunLoop run_loop_;
-  ScopedObserver<TabStripModel, TabStripModelObserver> scoped_observer_;
 
   DISALLOW_COPY_AND_ASSIGN(TestTabStripModelObserver);
 };
diff --git a/chrome/browser/external_protocol/external_protocol_handler_browsertest.cc b/chrome/browser/external_protocol/external_protocol_handler_browsertest.cc
index 5a6c854..91af2d5c 100644
--- a/chrome/browser/external_protocol/external_protocol_handler_browsertest.cc
+++ b/chrome/browser/external_protocol/external_protocol_handler_browsertest.cc
@@ -18,9 +18,8 @@
 // Observe that the tab is created then automatically closed.
 class TabAddedRemovedObserver : public TabStripModelObserver {
  public:
-  explicit TabAddedRemovedObserver(TabStripModel* tab_strip_model)
-      : scoped_observer_(this) {
-    scoped_observer_.Add(tab_strip_model);
+  explicit TabAddedRemovedObserver(TabStripModel* tab_strip_model) {
+    tab_strip_model->AddObserver(this);
   }
 
   void OnTabStripModelChanged(
@@ -50,7 +49,6 @@
   bool inserted_ = false;
   bool removed_ = false;
   base::RunLoop loop_;
-  ScopedObserver<TabStripModel, TabAddedRemovedObserver> scoped_observer_;
 };
 
 IN_PROC_BROWSER_TEST_F(ExternalProtocolHandlerBrowserTest,
diff --git a/chrome/browser/media/cast_mirroring_service_host_browsertest.cc b/chrome/browser/media/cast_mirroring_service_host_browsertest.cc
index 0f90d53..b8cb1b2 100644
--- a/chrome/browser/media/cast_mirroring_service_host_browsertest.cc
+++ b/chrome/browser/media/cast_mirroring_service_host_browsertest.cc
@@ -272,10 +272,6 @@
       browser_->tab_strip_model()->AddObserver(this);
     }
 
-    ~IndicatorChangeObserver() override {
-      browser_->tab_strip_model()->RemoveObserver(this);
-    }
-
     TabAlertState last_alert_state() const { return last_alert_state_; }
 
     void TabChangedAt(content::WebContents* contents,
diff --git a/chrome/browser/metrics/tab_stats_tracker.cc b/chrome/browser/metrics/tab_stats_tracker.cc
index a4424f6..aa006fa4 100644
--- a/chrome/browser/metrics/tab_stats_tracker.cc
+++ b/chrome/browser/metrics/tab_stats_tracker.cc
@@ -213,11 +213,7 @@
 
 TabStatsTracker::~TabStatsTracker() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  BrowserList* browser_list = BrowserList::GetInstance();
-  for (Browser* browser : *browser_list)
-    browser->tab_strip_model()->RemoveObserver(this);
-
-  browser_list->RemoveObserver(this);
+  BrowserList::GetInstance()->RemoveObserver(this);
 
   base::PowerMonitor::RemoveObserver(this);
 }
diff --git a/chrome/browser/prerender/prerender_browsertest.cc b/chrome/browser/prerender/prerender_browsertest.cc
index 8824c094..4fc5122 100644
--- a/chrome/browser/prerender/prerender_browsertest.cc
+++ b/chrome/browser/prerender/prerender_browsertest.cc
@@ -451,7 +451,7 @@
   NewTabNavigationOrSwapObserver() {
     BrowserList::AddObserver(this);
     for (const Browser* browser : *BrowserList::GetInstance())
-      tab_strip_observer_.Add(browser->tab_strip_model());
+      browser->tab_strip_model()->AddObserver(this);
   }
 
   ~NewTabNavigationOrSwapObserver() override {
@@ -481,12 +481,10 @@
 
   // BrowserListObserver:
   void OnBrowserAdded(Browser* browser) override {
-    tab_strip_observer_.Add(browser->tab_strip_model());
+    browser->tab_strip_model()->AddObserver(this);
   }
 
  private:
-  ScopedObserver<TabStripModel, TabStripModelObserver> tab_strip_observer_{
-      this};
   base::RunLoop new_tab_run_loop_;
   std::unique_ptr<NavigationOrSwapObserver> swap_observer_;
 
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
index a59019e..6e82c9c 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
@@ -150,7 +150,7 @@
       public TabStripModelObserver {
  public:
   explicit TestNavigationObserverManager(Browser* browser) {
-    observer_.Add(browser->tab_strip_model());
+    browser->tab_strip_model()->AddObserver(this);
   }
 
   // TabStripModelObserver:
@@ -175,7 +175,6 @@
   ~TestNavigationObserverManager() override = default;
 
  private:
-  ScopedObserver<TabStripModel, TabStripModelObserver> observer_{this};
   std::vector<std::unique_ptr<SafeBrowsingNavigationObserver>> observer_list_;
 
   DISALLOW_COPY_AND_ASSIGN(TestNavigationObserverManager);
diff --git a/chrome/browser/supervised_user/supervised_user_url_filter_browsertest.cc b/chrome/browser/supervised_user/supervised_user_url_filter_browsertest.cc
index 1da0835..f878398 100644
--- a/chrome/browser/supervised_user/supervised_user_url_filter_browsertest.cc
+++ b/chrome/browser/supervised_user/supervised_user_url_filter_browsertest.cc
@@ -164,8 +164,6 @@
     tab_strip_->AddObserver(this);
   }
 
-  ~TabClosingObserver() override { tab_strip_->RemoveObserver(this); }
-
   void WaitForContentsClosing() {
     if (!contents_)
       return;
diff --git a/chrome/browser/sync/sessions/browser_list_router_helper.cc b/chrome/browser/sync/sessions/browser_list_router_helper.cc
index 19083cc..6ce0521 100644
--- a/chrome/browser/sync/sessions/browser_list_router_helper.cc
+++ b/chrome/browser/sync/sessions/browser_list_router_helper.cc
@@ -26,13 +26,6 @@
 }
 
 BrowserListRouterHelper::~BrowserListRouterHelper() {
-  BrowserList* browser_list = BrowserList::GetInstance();
-  for (Browser* browser : *browser_list) {
-    if (browser->profile() == profile_) {
-      browser->tab_strip_model()->RemoveObserver(this);
-    }
-  }
-
   BrowserList::GetInstance()->RemoveObserver(this);
 }
 
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index 750609e..fb259dc9 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -526,7 +526,6 @@
 
   // The tab strip should not have any tabs at this point.
   DCHECK(tab_strip_model_->empty());
-  tab_strip_model_->RemoveObserver(this);
   bubble_manager_.reset();
 
   // Destroy the BrowserCommandController before removing the browser, so that
diff --git a/chrome/browser/ui/search/instant_controller.cc b/chrome/browser/ui/search/instant_controller.cc
index e146612..b8fc888 100644
--- a/chrome/browser/ui/search/instant_controller.cc
+++ b/chrome/browser/ui/search/instant_controller.cc
@@ -41,8 +41,8 @@
 
 InstantController::InstantController(Profile* profile,
                                      TabStripModel* tab_strip_model)
-    : profile_(profile), tab_strip_observer_(this) {
-  tab_strip_observer_.Add(tab_strip_model);
+    : profile_(profile) {
+  tab_strip_model->AddObserver(this);
 }
 
 InstantController::~InstantController() = default;
diff --git a/chrome/browser/ui/search/instant_controller.h b/chrome/browser/ui/search/instant_controller.h
index f6e5c786..aabfb16b 100644
--- a/chrome/browser/ui/search/instant_controller.h
+++ b/chrome/browser/ui/search/instant_controller.h
@@ -9,7 +9,6 @@
 
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
-#include "base/scoped_observer.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 
@@ -54,7 +53,6 @@
   void UpdateInfoForInstantTab();
 
   Profile* const profile_;
-  ScopedObserver<TabStripModel, TabStripModelObserver> tab_strip_observer_;
 
   // Observes the currently active tab, and calls us back if it becomes an NTP.
   std::unique_ptr<TabObserver> tab_observer_;
diff --git a/chrome/browser/ui/tabs/pinned_tab_service.cc b/chrome/browser/ui/tabs/pinned_tab_service.cc
index 29e862bb..cf103eb2 100644
--- a/chrome/browser/ui/tabs/pinned_tab_service.cc
+++ b/chrome/browser/ui/tabs/pinned_tab_service.cc
@@ -41,7 +41,7 @@
   //     over-writing the correct state.
   // Saving is re-enabled if a new tab or window is opened.
   DCHECK_EQ(type, chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST);
-  if (observed_tab_strips_.IsObservingSources())
+  if (TabStripModelObserver::IsObservingAny(this))
     WritePinnedTabsIfNecessary();
 }
 
@@ -50,14 +50,14 @@
     return;
 
   need_to_write_pinned_tabs_ = true;
-  observed_tab_strips_.Add(browser->tab_strip_model());
+  browser->tab_strip_model()->AddObserver(this);
 }
 
 void PinnedTabService::OnBrowserClosing(Browser* browser) {
   if (browser->profile() != profile_ || !browser->is_type_tabbed())
     return;
 
-  if (observed_tab_strips_.GetSourcesCount() == 1U)
+  if (TabStripModelObserver::CountObservedModels(this) == 1)
     WritePinnedTabsIfNecessary();
 }
 
@@ -65,14 +65,14 @@
   if (browser->profile() != profile_ || !browser->is_type_tabbed())
     return;
 
-  observed_tab_strips_.Remove(browser->tab_strip_model());
+  browser->tab_strip_model()->RemoveObserver(this);
 
   // This happens when user closes each tabs manually via the close button on
   // them. In this case OnBrowserClosing() above is not called. This causes
   // pinned tabs to repopen on the next startup. So we should call
   // WritePinnedTab() to clear the data.
   // http://crbug.com/71939
-  if (!observed_tab_strips_.IsObservingSources())
+  if (!TabStripModelObserver::IsObservingAny(this))
     WritePinnedTabsIfNecessary();
 }
 
diff --git a/chrome/browser/ui/tabs/pinned_tab_service.h b/chrome/browser/ui/tabs/pinned_tab_service.h
index 2d624c0..11c482f 100644
--- a/chrome/browser/ui/tabs/pinned_tab_service.h
+++ b/chrome/browser/ui/tabs/pinned_tab_service.h
@@ -61,10 +61,6 @@
 
   ScopedObserver<BrowserList, BrowserListObserver> browser_list_observer_{this};
 
-  // |this| observes all tabbed browsers that match |profile_|.
-  ScopedObserver<TabStripModel, TabStripModelObserver> observed_tab_strips_{
-      this};
-
   DISALLOW_COPY_AND_ASSIGN(PinnedTabService);
 };
 
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index 00bb862..63eba3b 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -327,6 +327,10 @@
 }
 
 TabStripModel::~TabStripModel() {
+  std::vector<TabStripModelObserver*> observers;
+  for (auto& observer : observers_)
+    observer.ModelDestroyed(TabStripModelObserver::ModelPasskey(), this);
+
   contents_data_.clear();
   order_controller_.reset();
 }
@@ -343,14 +347,17 @@
   for (auto* new_observer : new_observers)
     observers_.AddObserver(new_observer);
 
+  observer->StartedObserving(TabStripModelObserver::ModelPasskey(), this);
   tab_strip_ui_was_set_ = true;
 }
 
 void TabStripModel::AddObserver(TabStripModelObserver* observer) {
   observers_.AddObserver(observer);
+  observer->StartedObserving(TabStripModelObserver::ModelPasskey(), this);
 }
 
 void TabStripModel::RemoveObserver(TabStripModelObserver* observer) {
+  observer->StoppedObserving(TabStripModelObserver::ModelPasskey(), this);
   observers_.RemoveObserver(observer);
 }
 
diff --git a/chrome/browser/ui/tabs/tab_strip_model_observer.cc b/chrome/browser/ui/tabs/tab_strip_model_observer.cc
index 3f59037..3a71af9 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_observer.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model_observer.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/logging.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
 
 using content::WebContents;
 
@@ -114,7 +115,14 @@
 ////////////////////////////////////////////////////////////////////////////////
 // TabStripModelObserver
 //
-TabStripModelObserver::TabStripModelObserver() {
+TabStripModelObserver::TabStripModelObserver() {}
+
+TabStripModelObserver::~TabStripModelObserver() {
+  std::set<TabStripModel*> models(observed_models_.begin(),
+                                  observed_models_.end());
+  for (auto* model : models) {
+    model->RemoveObserver(this);
+  }
 }
 
 void TabStripModelObserver::OnTabStripModelChanged(
@@ -150,3 +158,48 @@
 void TabStripModelObserver::CloseAllTabsStopped(TabStripModel* tab_strip_model,
                                                 CloseAllStoppedReason reason) {}
 void TabStripModelObserver::SetTabNeedsAttentionAt(int index, bool attention) {}
+void TabStripModelObserver::OnTabStripModelDestroyed(TabStripModel* model) {}
+
+// static
+void TabStripModelObserver::StopObservingAll(TabStripModelObserver* observer) {
+  while (!observer->observed_models_.empty()) {
+    (*observer->observed_models_.begin())->RemoveObserver(observer);
+  }
+}
+
+// static
+bool TabStripModelObserver::IsObservingAny(TabStripModelObserver* observer) {
+  return !observer->observed_models_.empty();
+}
+
+// static
+int TabStripModelObserver::CountObservedModels(
+    TabStripModelObserver* observer) {
+  return observer->observed_models_.size();
+}
+
+void TabStripModelObserver::StartedObserving(
+    TabStripModelObserver::ModelPasskey,
+    TabStripModel* model) {
+  // TODO(https://crbug.com/991308): Add this DCHECK here. This DCHECK enforces
+  // that a given TabStripModelObserver only observes a given TabStripModel
+  // once.
+  // DCHECK_EQ(observed_models_.count(model), 0U);
+  observed_models_.insert(model);
+}
+
+void TabStripModelObserver::StoppedObserving(
+    TabStripModelObserver::ModelPasskey,
+    TabStripModel* model) {
+  // TODO(https://crbug.com/991308): Add this DCHECK here. This DCHECK enforces
+  // that a given TabStripModelObserver is only removed from a given
+  // TabStripModel once.
+  // DCHECK_EQ(observed_models_.count(model), 1U);
+  observed_models_.erase(model);
+}
+
+void TabStripModelObserver::ModelDestroyed(TabStripModelObserver::ModelPasskey,
+                                           TabStripModel* model) {
+  model->RemoveObserver(this);
+  OnTabStripModelDestroyed(model);
+}
diff --git a/chrome/browser/ui/tabs/tab_strip_model_observer.h b/chrome/browser/ui/tabs/tab_strip_model_observer.h
index e583769..bb9eac0 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_observer.h
+++ b/chrome/browser/ui/tabs/tab_strip_model_observer.h
@@ -10,6 +10,7 @@
 
 #include "base/macros.h"
 #include "base/optional.h"
+#include "base/scoped_observer.h"
 #include "chrome/browser/ui/tabs/tab_change_type.h"
 #include "chrome/browser/ui/tabs/tab_group_id.h"
 #include "ui/base/models/list_selection_model.h"
@@ -215,6 +216,12 @@
   int reason = 0;
 };
 
+// Forbid construction of ScopedObserver with TabStripModel:
+// TabStripModelObserver already implements ScopedObserver's functionality
+// natively.
+template <class U>
+class ScopedObserver<TabStripModel, U> {};
+
 ////////////////////////////////////////////////////////////////////////////////
 //
 // TabStripModelObserver
@@ -310,11 +317,39 @@
   // |attention| is true.
   virtual void SetTabNeedsAttentionAt(int index, bool attention);
 
+  // Called when an observed TabStripModel is beginning destruction.
+  virtual void OnTabStripModelDestroyed(TabStripModel* tab_strip_model);
+
+  static void StopObservingAll(TabStripModelObserver* observer);
+  static bool IsObservingAny(TabStripModelObserver* observer);
+  static int CountObservedModels(TabStripModelObserver* observer);
+
+  // A passkey for TabStripModel to access some methods on this class - see
+  // </docs/patterns/passkey.md>.
+  class ModelPasskey {
+   private:
+    friend class TabStripModel;
+    ModelPasskey() = default;
+    ~ModelPasskey() = default;
+  };
+
+  // These methods are used by TabStripModel to notify this class of lifecycle
+  // events on the TabStripModelObserver or the TabStripModel itself. The first
+  // two are used to allow TabStripModelObserver to track which models it is
+  // observing. The third is used to allow TabStripModelObserver to clean up
+  // when an observed TabStripModel is destroyed, and to send the
+  // OnTabStripModelDestroyed notification above.
+  void StartedObserving(ModelPasskey, TabStripModel* model);
+  void StoppedObserving(ModelPasskey, TabStripModel* model);
+  void ModelDestroyed(ModelPasskey, TabStripModel* model);
+
  protected:
   TabStripModelObserver();
-  virtual ~TabStripModelObserver() {}
+  virtual ~TabStripModelObserver();
 
  private:
+  std::set<TabStripModel*> observed_models_;
+
   DISALLOW_COPY_AND_ASSIGN(TabStripModelObserver);
 };
 
diff --git a/chrome/browser/ui/tabs/tab_strip_model_order_controller.cc b/chrome/browser/ui/tabs/tab_strip_model_order_controller.cc
index 1347f88..509bde9 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_order_controller.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model_order_controller.cc
@@ -16,9 +16,7 @@
   tabstrip_->AddObserver(this);
 }
 
-TabStripModelOrderController::~TabStripModelOrderController() {
-  tabstrip_->RemoveObserver(this);
-}
+TabStripModelOrderController::~TabStripModelOrderController() {}
 
 int TabStripModelOrderController::DetermineInsertionIndex(
     ui::PageTransition transition,
diff --git a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
index 8f347bba..7243f064 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
@@ -3439,3 +3439,13 @@
 
   strip.CloseAllTabs();
 }
+
+TEST_F(TabStripModelTest, ObserverCanBeDestroyedEarly) {
+  TestTabStripModelDelegate delegate;
+  TabStripModel strip(&delegate, profile());
+
+  {
+    MockTabStripModelObserver observer;
+    strip.AddObserver(&observer);
+  }
+}
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar.cc b/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
index ed2241b..cd1329b4 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
@@ -109,15 +109,14 @@
       should_check_extension_bubble_(!main_bar),
       popped_out_action_(nullptr),
       is_popped_out_sticky_(false),
-      is_showing_bubble_(false),
-      tab_strip_observer_(this) {
+      is_showing_bubble_(false) {
   if (model_)  // |model_| can be null in unittests.
     model_observer_.Add(model_);
 
   if (base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu))
     DCHECK(!in_overflow_mode());
 
-  tab_strip_observer_.Add(browser_->tab_strip_model());
+  browser_->tab_strip_model()->AddObserver(this);
 }
 
 ToolbarActionsBar::~ToolbarActionsBar() {
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar.h b/chrome/browser/ui/toolbar/toolbar_actions_bar.h
index 0ab6ac5..22c593b 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar.h
@@ -361,8 +361,6 @@
   // no drag is in progress.
   base::Optional<size_t> index_of_dragged_item_;
 
-  ScopedObserver<TabStripModel, TabStripModelObserver> tab_strip_observer_;
-
   base::ObserverList<ToolbarActionsBarObserver>::Unchecked observers_;
 
   base::WeakPtrFactory<ToolbarActionsBar> weak_ptr_factory_{this};
diff --git a/chrome/browser/ui/views/close_bubble_on_tab_activation_helper.cc b/chrome/browser/ui/views/close_bubble_on_tab_activation_helper.cc
index 1ecb6834..54295623 100644
--- a/chrome/browser/ui/views/close_bubble_on_tab_activation_helper.cc
+++ b/chrome/browser/ui/views/close_bubble_on_tab_activation_helper.cc
@@ -11,9 +11,9 @@
 CloseBubbleOnTabActivationHelper::CloseBubbleOnTabActivationHelper(
     views::BubbleDialogDelegateView* owner_bubble,
     Browser* browser)
-    : owner_bubble_(owner_bubble), tab_strip_observer_(this) {
+    : owner_bubble_(owner_bubble) {
   DCHECK(owner_bubble_);
-  tab_strip_observer_.Add(browser->tab_strip_model());
+  browser->tab_strip_model()->AddObserver(this);
 }
 
 CloseBubbleOnTabActivationHelper::~CloseBubbleOnTabActivationHelper() = default;
diff --git a/chrome/browser/ui/views/close_bubble_on_tab_activation_helper.h b/chrome/browser/ui/views/close_bubble_on_tab_activation_helper.h
index a7f4b7d..9e91de5 100644
--- a/chrome/browser/ui/views/close_bubble_on_tab_activation_helper.h
+++ b/chrome/browser/ui/views/close_bubble_on_tab_activation_helper.h
@@ -5,7 +5,6 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_CLOSE_BUBBLE_ON_TAB_ACTIVATION_HELPER_H_
 #define CHROME_BROWSER_UI_VIEWS_CLOSE_BUBBLE_ON_TAB_ACTIVATION_HELPER_H_
 
-#include "base/scoped_observer.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 
 class Browser;
@@ -34,8 +33,6 @@
 
  private:
   views::BubbleDialogDelegateView* owner_bubble_;  // weak, owns me.
-  ScopedObserver<TabStripModel, CloseBubbleOnTabActivationHelper>
-      tab_strip_observer_;
 
   DISALLOW_COPY_AND_ASSIGN(CloseBubbleOnTabActivationHelper);
 };
diff --git a/chrome/browser/ui/views/extensions/extension_popup.cc b/chrome/browser/ui/views/extensions/extension_popup.cc
index e4c23013..12b77ca 100644
--- a/chrome/browser/ui/views/extensions/extension_popup.cc
+++ b/chrome/browser/ui/views/extensions/extension_popup.cc
@@ -191,7 +191,7 @@
       this, extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
       content::Source<content::BrowserContext>(host_->browser_context()));
   content::DevToolsAgentHost::AddObserver(this);
-  observer_.Add(GetExtensionView()->GetBrowser()->tab_strip_model());
+  GetExtensionView()->GetBrowser()->tab_strip_model()->AddObserver(this);
 
   // If the host had somehow finished loading, then we'd miss the notification
   // and not show.  This seems to happen in single-process mode.
diff --git a/chrome/browser/ui/views/extensions/extension_popup.h b/chrome/browser/ui/views/extensions/extension_popup.h
index 4a3ea77..597cbfa 100644
--- a/chrome/browser/ui/views/extensions/extension_popup.h
+++ b/chrome/browser/ui/views/extensions/extension_popup.h
@@ -8,7 +8,6 @@
 #include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/macros.h"
-#include "base/scoped_observer.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/views/extensions/extension_view_views.h"
@@ -127,7 +126,6 @@
   ShowAction show_action_;
 
   content::NotificationRegistrar registrar_;
-  ScopedObserver<TabStripModel, TabStripModelObserver> observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionPopup);
 };
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 9f7c7d0..37e8033 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -465,8 +465,6 @@
   // Immersive mode may need to reparent views before they are removed/deleted.
   immersive_mode_controller_.reset();
 
-  browser_->tab_strip_model()->RemoveObserver(this);
-
   extensions::ExtensionCommandsGlobalRegistry* global_registry =
       extensions::ExtensionCommandsGlobalRegistry::Get(browser_->profile());
   if (global_registry->registry_for_active_window() ==
diff --git a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc
index 84ea02c..21ae6243 100644
--- a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc
@@ -182,7 +182,6 @@
                                    LocationBarView::Delegate* delegate)
     : TabStripModelObserver(),
       delegate_(delegate),
-      tab_strip_model_observer_(this),
       browser_(browser_view->browser()) {
   set_context_menu_controller(this);
   base::Optional<SkColor> optional_theme_color =
@@ -225,7 +224,7 @@
       .SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
       .SetInteriorMargin(GetLayoutInsets(LayoutInset::TOOLBAR_INTERIOR_MARGIN));
 
-  tab_strip_model_observer_.Add(browser_->tab_strip_model());
+  browser_->tab_strip_model()->AddObserver(this);
 }
 
 CustomTabBarView::~CustomTabBarView() {}
diff --git a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h
index 16fe0b1..0a23ebc6 100644
--- a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h
+++ b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h
@@ -7,7 +7,6 @@
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "base/scoped_observer.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
 #include "chrome/browser/ui/views/location_bar/location_icon_view.h"
@@ -113,7 +112,6 @@
   LocationBarView::Delegate* delegate_ = nullptr;
   LocationIconView* location_icon_view_ = nullptr;
   CustomTabBarTitleOriginView* title_origin_view_ = nullptr;
-  ScopedObserver<TabStripModel, CustomTabBarView> tab_strip_model_observer_;
   std::unique_ptr<ui::SimpleMenuModel> context_menu_model_;
   std::unique_ptr<views::MenuRunner> context_menu_runner_;
   Browser* browser_ = nullptr;
diff --git a/chrome/browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc b/chrome/browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc
index 29451f4..6a02447b 100644
--- a/chrome/browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc
+++ b/chrome/browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/scoped_observer.h"
 #include "build/build_config.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h"
@@ -37,11 +36,9 @@
   // Create a new TitleObserver for the browser of |contents|, waiting for
   // |target_title|.
   TestTitleObserver(content::WebContents* contents, base::string16 target_title)
-      : contents_(contents),
-        target_title_(target_title),
-        tab_strip_model_observer_(this) {
+      : contents_(contents), target_title_(target_title) {
     browser_ = chrome::FindBrowserWithWebContents(contents_);
-    tab_strip_model_observer_.Add(browser_->tab_strip_model());
+    browser_->tab_strip_model()->AddObserver(this);
   }
 
   // Run a loop, blocking until a tab has the title |target_title|.
@@ -74,7 +71,6 @@
   Browser* browser_;
   base::string16 target_title_;
   base::RunLoop awaiter_;
-  ScopedObserver<TabStripModel, TestTitleObserver> tab_strip_model_observer_;
 };
 
 // Opens a new popup window from |web_contents| on |target_url| and returns
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc
index 73a59fb7..8cecf26 100644
--- a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc
@@ -166,7 +166,7 @@
 
 void TabSharingUIViews::OnBrowserAdded(Browser* browser) {
   if (browser->profile()->GetOriginalProfile() == profile_)
-    tab_strip_models_observer_.Add(browser->tab_strip_model());
+    browser->tab_strip_model()->AddObserver(this);
 }
 
 void TabSharingUIViews::OnBrowserRemoved(Browser* browser) {
@@ -174,9 +174,7 @@
   if (browser_list->empty())
     browser_list->RemoveObserver(this);
 
-  TabStripModel* tab_strip_model = browser->tab_strip_model();
-  if (tab_strip_models_observer_.IsObserving(tab_strip_model))
-    tab_strip_models_observer_.Remove(tab_strip_model);
+  browser->tab_strip_model()->RemoveObserver(this);
 }
 
 void TabSharingUIViews::OnTabStripModelChanged(
@@ -233,7 +231,7 @@
 
 void TabSharingUIViews::RemoveInfobarsForAllTabs() {
   BrowserList::GetInstance()->RemoveObserver(this);
-  tab_strip_models_observer_.RemoveAll();
+  TabStripModelObserver::StopObservingAll(this);
 
   for (const auto& infobars_entry : infobars_)
     infobars_entry.second->RemoveSelf();
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.h b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.h
index bf846250..68210f2 100644
--- a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.h
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.h
@@ -10,7 +10,6 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
-#include "base/scoped_observer.h"
 #include "base/strings/string16.h"
 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
 #include "chrome/browser/ui/browser_list_observer.h"
@@ -69,8 +68,6 @@
   void RemoveInfobarsForAllTabs();
 
   std::map<content::WebContents*, infobars::InfoBar*> infobars_;
-  ScopedObserver<TabStripModel, TabStripModelObserver>
-      tab_strip_models_observer_{this};
   const base::string16 app_name_;
   content::WebContents* shared_tab_;
   base::string16 shared_tab_name_;
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
index b3663a6..e2e7b93 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
@@ -242,27 +242,26 @@
  public:
   explicit SourceTabStripEmptinessTracker(TabStripModel* tabstrip,
                                           TabDragController* parent)
-      : tab_strip_(tabstrip), parent_(parent), observer_(this) {
-    observer_.Add(tab_strip_);
+      : tab_strip_(tabstrip), parent_(parent) {
+    tab_strip_->AddObserver(this);
   }
 
  private:
   void TabStripEmpty() override {
-    observer_.Remove(tab_strip_);
+    tab_strip_->RemoveObserver(this);
     parent_->OnSourceTabStripEmpty();
   }
 
   TabStripModel* const tab_strip_;
   TabDragController* const parent_;
-  ScopedObserver<TabStripModel, TabStripModelObserver> observer_;
 };
 
 class TabDragController::DraggedTabsClosedTracker
     : public TabStripModelObserver {
  public:
   DraggedTabsClosedTracker(TabStripModel* tabstrip, TabDragController* parent)
-      : parent_(parent), observer_(this) {
-    observer_.Add(tabstrip);
+      : parent_(parent) {
+    tabstrip->AddObserver(this);
   }
 
   void OnTabStripModelChanged(
@@ -277,7 +276,6 @@
 
  private:
   TabDragController* const parent_;
-  ScopedObserver<TabStripModel, TabStripModelObserver> observer_;
 };
 
 TabDragController::TabDragData::TabDragData()
diff --git a/chrome/test/base/ui_test_utils.cc b/chrome/test/base/ui_test_utils.cc
index 8934e079e..78fcb8fe1 100644
--- a/chrome/test/base/ui_test_utils.cc
+++ b/chrome/test/base/ui_test_utils.cc
@@ -620,11 +620,9 @@
 }
 
 TabAddedWaiter::TabAddedWaiter(Browser* browser) {
-  scoped_observer_.Add(browser->tab_strip_model());
+  browser->tab_strip_model()->AddObserver(this);
 }
 
-TabAddedWaiter::~TabAddedWaiter() = default;
-
 void TabAddedWaiter::Wait() {
   run_loop_.Run();
 }
@@ -640,7 +638,7 @@
 AllBrowserTabAddedWaiter::AllBrowserTabAddedWaiter() {
   BrowserList::AddObserver(this);
   for (const Browser* browser : *BrowserList::GetInstance())
-    tab_strip_observer_.Add(browser->tab_strip_model());
+    browser->tab_strip_model()->AddObserver(this);
 }
 
 AllBrowserTabAddedWaiter::~AllBrowserTabAddedWaiter() {
@@ -664,7 +662,7 @@
 }
 
 void AllBrowserTabAddedWaiter::OnBrowserAdded(Browser* browser) {
-  tab_strip_observer_.Add(browser->tab_strip_model());
+  browser->tab_strip_model()->AddObserver(this);
 }
 
 }  // namespace ui_test_utils
diff --git a/chrome/test/base/ui_test_utils.h b/chrome/test/base/ui_test_utils.h
index 79c9d9e..c7407ba 100644
--- a/chrome/test/base/ui_test_utils.h
+++ b/chrome/test/base/ui_test_utils.h
@@ -281,7 +281,7 @@
 class TabAddedWaiter : public TabStripModelObserver {
  public:
   explicit TabAddedWaiter(Browser* browser);
-  ~TabAddedWaiter() override;
+  ~TabAddedWaiter() override = default;
 
   void Wait();
 
@@ -293,7 +293,6 @@
 
  private:
   base::RunLoop run_loop_;
-  ScopedObserver<TabStripModel, TabStripModelObserver> scoped_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(TabAddedWaiter);
 };
@@ -318,8 +317,6 @@
   void OnBrowserAdded(Browser* browser) override;
 
  private:
-  ScopedObserver<TabStripModel, TabStripModelObserver> tab_strip_observer_{
-      this};
   base::RunLoop run_loop_;
 
   // The last tab that was added.
diff --git a/docs/patterns/passkey.md b/docs/patterns/passkey.md
new file mode 100644
index 0000000..b45c76e
--- /dev/null
+++ b/docs/patterns/passkey.md
@@ -0,0 +1,47 @@
+# The Passkey Pattern
+
+The Passkey pattern is used when you need to expose a subset of a class's
+methods to another class in a more granular way than simply friending the other
+class. In essence, it involves creating a "passkey" class that can only be
+constructed by specific other classes, and requiring an instance of that passkey
+class to be passed in when calling methods you wish to restrict the use of. It
+is used like this:
+
+```
+class Foo {
+ public:
+  Foo();
+  ~Foo();
+
+  void NormalPublicMethod();
+  bool AnotherNormalPublicMethod(int a, int b);
+
+  class BarPasskey {
+   private:
+    friend class Bar;
+    BarPasskey() = default;
+    ~BarPasskey() = default;
+  };
+
+  void HelpBarOut(BarPasskey, ...);
+};
+
+...
+
+void Bar::DoStuff() {
+  foo->HelpBarOut(Foo::BarPasskey(), ...);
+}
+```
+
+The private constructor on `Foo::BarPasskey` prevents any class other than `Bar`
+from constructing a `Foo::BarPasskey`, which means that:
+
+* Only `Bar` can call those methods
+* `Bar` can delegate the ability to call those methods to other
+  classes/functions by passing them a `Foo::BarPasskey` instance
+
+This method is effectively free at runtime - a few extra bytes of argument space
+are used to pass in the Passkey object.
+
+It is encouraged to leave the `BarPasskey` parameter unnamed to reinforce that it
+carries no semantic information and is not actually used for anything.