| // Copyright 2013 The Chromium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "base/callback_list.h" | 
 |  | 
 | #include <memory> | 
 | #include <utility> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/callback_helpers.h" | 
 | #include "base/memory/raw_ptr.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | namespace base { | 
 | namespace { | 
 |  | 
 | class Listener { | 
 |  public: | 
 |   Listener() = default; | 
 |   explicit Listener(int scaler) : scaler_(scaler) {} | 
 |   Listener(const Listener&) = delete; | 
 |   Listener& operator=(const Listener&) = delete; | 
 |   ~Listener() = default; | 
 |  | 
 |   void IncrementTotal() { ++total_; } | 
 |  | 
 |   void IncrementByMultipleOfScaler(int x) { total_ += x * scaler_; } | 
 |  | 
 |   int total() const { return total_; } | 
 |  | 
 |  private: | 
 |   int total_ = 0; | 
 |   int scaler_ = 1; | 
 | }; | 
 |  | 
 | class Remover { | 
 |  public: | 
 |   Remover() = default; | 
 |   Remover(const Remover&) = delete; | 
 |   Remover& operator=(const Remover&) = delete; | 
 |   ~Remover() = default; | 
 |  | 
 |   void IncrementTotalAndRemove() { | 
 |     ++total_; | 
 |     removal_subscription_ = {}; | 
 |   } | 
 |  | 
 |   void SetSubscriptionToRemove(CallbackListSubscription subscription) { | 
 |     removal_subscription_ = std::move(subscription); | 
 |   } | 
 |  | 
 |   int total() const { return total_; } | 
 |  | 
 |  private: | 
 |   int total_ = 0; | 
 |   CallbackListSubscription removal_subscription_; | 
 | }; | 
 |  | 
 | class Adder { | 
 |  public: | 
 |   explicit Adder(RepeatingClosureList* cb_reg) : cb_reg_(cb_reg) {} | 
 |   Adder(const Adder&) = delete; | 
 |   Adder& operator=(const Adder&) = delete; | 
 |   ~Adder() = default; | 
 |  | 
 |   void AddCallback() { | 
 |     if (!added_) { | 
 |       added_ = true; | 
 |       subscription_ = | 
 |           cb_reg_->Add(BindRepeating(&Adder::IncrementTotal, Unretained(this))); | 
 |     } | 
 |   } | 
 |  | 
 |   void IncrementTotal() { ++total_; } | 
 |  | 
 |   bool added() const { return added_; } | 
 |   int total() const { return total_; } | 
 |  | 
 |  private: | 
 |   bool added_ = false; | 
 |   int total_ = 0; | 
 |   raw_ptr<RepeatingClosureList> cb_reg_; | 
 |   CallbackListSubscription subscription_; | 
 | }; | 
 |  | 
 | class Summer { | 
 |  public: | 
 |   Summer() = default; | 
 |   Summer(const Summer&) = delete; | 
 |   Summer& operator=(const Summer&) = delete; | 
 |   ~Summer() = default; | 
 |  | 
 |   void AddOneParam(int a) { value_ = a; } | 
 |   void AddTwoParam(int a, int b) { value_ = a + b; } | 
 |   void AddThreeParam(int a, int b, int c) { value_ = a + b + c; } | 
 |   void AddFourParam(int a, int b, int c, int d) { value_ = a + b + c + d; } | 
 |   void AddFiveParam(int a, int b, int c, int d, int e) { | 
 |     value_ = a + b + c + d + e; | 
 |   } | 
 |   void AddSixParam(int a, int b, int c, int d, int e , int f) { | 
 |     value_ = a + b + c + d + e + f; | 
 |   } | 
 |  | 
 |   int value() const { return value_; } | 
 |  | 
 |  private: | 
 |   int value_ = 0; | 
 | }; | 
 |  | 
 | class Counter { | 
 |  public: | 
 |   Counter() = default; | 
 |   Counter(const Counter&) = delete; | 
 |   Counter& operator=(const Counter&) = delete; | 
 |   ~Counter() = default; | 
 |  | 
 |   void Increment() { ++value_; } | 
 |  | 
 |   int value() const { return value_; } | 
 |  | 
 |  private: | 
 |   int value_ = 0; | 
 | }; | 
 |  | 
 | // Sanity check that we can instantiate a CallbackList for each arity. | 
 | TEST(CallbackListTest, ArityTest) { | 
 |   Summer s; | 
 |  | 
 |   RepeatingCallbackList<void(int)> c1; | 
 |   CallbackListSubscription subscription1 = | 
 |       c1.Add(BindRepeating(&Summer::AddOneParam, Unretained(&s))); | 
 |  | 
 |   c1.Notify(1); | 
 |   EXPECT_EQ(1, s.value()); | 
 |  | 
 |   RepeatingCallbackList<void(int, int)> c2; | 
 |   CallbackListSubscription subscription2 = | 
 |       c2.Add(BindRepeating(&Summer::AddTwoParam, Unretained(&s))); | 
 |  | 
 |   c2.Notify(1, 2); | 
 |   EXPECT_EQ(3, s.value()); | 
 |  | 
 |   RepeatingCallbackList<void(int, int, int)> c3; | 
 |   CallbackListSubscription subscription3 = | 
 |       c3.Add(BindRepeating(&Summer::AddThreeParam, Unretained(&s))); | 
 |  | 
 |   c3.Notify(1, 2, 3); | 
 |   EXPECT_EQ(6, s.value()); | 
 |  | 
 |   RepeatingCallbackList<void(int, int, int, int)> c4; | 
 |   CallbackListSubscription subscription4 = | 
 |       c4.Add(BindRepeating(&Summer::AddFourParam, Unretained(&s))); | 
 |  | 
 |   c4.Notify(1, 2, 3, 4); | 
 |   EXPECT_EQ(10, s.value()); | 
 |  | 
 |   RepeatingCallbackList<void(int, int, int, int, int)> c5; | 
 |   CallbackListSubscription subscription5 = | 
 |       c5.Add(BindRepeating(&Summer::AddFiveParam, Unretained(&s))); | 
 |  | 
 |   c5.Notify(1, 2, 3, 4, 5); | 
 |   EXPECT_EQ(15, s.value()); | 
 |  | 
 |   RepeatingCallbackList<void(int, int, int, int, int, int)> c6; | 
 |   CallbackListSubscription subscription6 = | 
 |       c6.Add(BindRepeating(&Summer::AddSixParam, Unretained(&s))); | 
 |  | 
 |   c6.Notify(1, 2, 3, 4, 5, 6); | 
 |   EXPECT_EQ(21, s.value()); | 
 | } | 
 |  | 
 | // Sanity check that closures added to the list will be run, and those removed | 
 | // from the list will not be run. | 
 | TEST(CallbackListTest, BasicTest) { | 
 |   Listener a, b, c; | 
 |   RepeatingClosureList cb_reg; | 
 |  | 
 |   CallbackListSubscription a_subscription = | 
 |       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&a))); | 
 |   CallbackListSubscription b_subscription = | 
 |       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b))); | 
 |   cb_reg.AddUnsafe(BindRepeating(&Listener::IncrementTotal, Unretained(&c))); | 
 |  | 
 |   EXPECT_TRUE(a_subscription); | 
 |   EXPECT_TRUE(b_subscription); | 
 |  | 
 |   cb_reg.Notify(); | 
 |  | 
 |   EXPECT_EQ(1, a.total()); | 
 |   EXPECT_EQ(1, b.total()); | 
 |   EXPECT_EQ(1, c.total()); | 
 |  | 
 |   b_subscription = {}; | 
 |  | 
 |   CallbackListSubscription c_subscription = | 
 |       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&c))); | 
 |  | 
 |   cb_reg.Notify(); | 
 |  | 
 |   EXPECT_EQ(2, a.total()); | 
 |   EXPECT_EQ(1, b.total()); | 
 |   EXPECT_EQ(3, c.total()); | 
 | } | 
 |  | 
 | // Similar to BasicTest but with OnceCallbacks instead of Repeating. | 
 | TEST(CallbackListTest, OnceCallbacks) { | 
 |   OnceClosureList cb_reg; | 
 |   Listener a, b, c; | 
 |  | 
 |   CallbackListSubscription a_subscription = | 
 |       cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&a))); | 
 |   CallbackListSubscription b_subscription = | 
 |       cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&b))); | 
 |  | 
 |   EXPECT_TRUE(a_subscription); | 
 |   EXPECT_TRUE(b_subscription); | 
 |  | 
 |   cb_reg.Notify(); | 
 |  | 
 |   EXPECT_EQ(1, a.total()); | 
 |   EXPECT_EQ(1, b.total()); | 
 |  | 
 |   // OnceCallbacks should auto-remove themselves after calling Notify(). | 
 |   EXPECT_TRUE(cb_reg.empty()); | 
 |  | 
 |   // Destroying a subscription after the callback is canceled should not cause | 
 |   // any problems. | 
 |   b_subscription = {}; | 
 |  | 
 |   CallbackListSubscription c_subscription = | 
 |       cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&c))); | 
 |  | 
 |   cb_reg.Notify(); | 
 |  | 
 |   EXPECT_EQ(1, a.total()); | 
 |   EXPECT_EQ(1, b.total()); | 
 |   EXPECT_EQ(1, c.total()); | 
 | } | 
 |  | 
 | // Sanity check that callbacks with details added to the list will be run, with | 
 | // the correct details, and those removed from the list will not be run. | 
 | TEST(CallbackListTest, BasicTestWithParams) { | 
 |   using CallbackListType = RepeatingCallbackList<void(int)>; | 
 |   CallbackListType cb_reg; | 
 |   Listener a(1), b(-1), c(1); | 
 |  | 
 |   CallbackListSubscription a_subscription = cb_reg.Add( | 
 |       BindRepeating(&Listener::IncrementByMultipleOfScaler, Unretained(&a))); | 
 |   CallbackListSubscription b_subscription = cb_reg.Add( | 
 |       BindRepeating(&Listener::IncrementByMultipleOfScaler, Unretained(&b))); | 
 |  | 
 |   EXPECT_TRUE(a_subscription); | 
 |   EXPECT_TRUE(b_subscription); | 
 |  | 
 |   cb_reg.Notify(10); | 
 |  | 
 |   EXPECT_EQ(10, a.total()); | 
 |   EXPECT_EQ(-10, b.total()); | 
 |  | 
 |   b_subscription = {}; | 
 |  | 
 |   CallbackListSubscription c_subscription = cb_reg.Add( | 
 |       BindRepeating(&Listener::IncrementByMultipleOfScaler, Unretained(&c))); | 
 |  | 
 |   cb_reg.Notify(10); | 
 |  | 
 |   EXPECT_EQ(20, a.total()); | 
 |   EXPECT_EQ(-10, b.total()); | 
 |   EXPECT_EQ(10, c.total()); | 
 | } | 
 |  | 
 | // Test the a callback can remove itself or a different callback from the list | 
 | // during iteration without invalidating the iterator. | 
 | TEST(CallbackListTest, RemoveCallbacksDuringIteration) { | 
 |   RepeatingClosureList cb_reg; | 
 |   Listener a, b; | 
 |   Remover remover_1, remover_2; | 
 |  | 
 |   CallbackListSubscription remover_1_sub = cb_reg.Add( | 
 |       BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover_1))); | 
 |   CallbackListSubscription remover_2_sub = cb_reg.Add( | 
 |       BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover_2))); | 
 |   CallbackListSubscription a_subscription = | 
 |       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&a))); | 
 |   CallbackListSubscription b_subscription = | 
 |       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b))); | 
 |  | 
 |   // |remover_1| will remove itself. | 
 |   remover_1.SetSubscriptionToRemove(std::move(remover_1_sub)); | 
 |   // |remover_2| will remove a. | 
 |   remover_2.SetSubscriptionToRemove(std::move(a_subscription)); | 
 |  | 
 |   cb_reg.Notify(); | 
 |  | 
 |   // |remover_1| runs once (and removes itself), |remover_2| runs once (and | 
 |   // removes a), |a| never runs, and |b| runs once. | 
 |   EXPECT_EQ(1, remover_1.total()); | 
 |   EXPECT_EQ(1, remover_2.total()); | 
 |   EXPECT_EQ(0, a.total()); | 
 |   EXPECT_EQ(1, b.total()); | 
 |  | 
 |   cb_reg.Notify(); | 
 |  | 
 |   // Only |remover_2| and |b| run this time. | 
 |   EXPECT_EQ(1, remover_1.total()); | 
 |   EXPECT_EQ(2, remover_2.total()); | 
 |   EXPECT_EQ(0, a.total()); | 
 |   EXPECT_EQ(2, b.total()); | 
 | } | 
 |  | 
 | // Similar to RemoveCallbacksDuringIteration but with OnceCallbacks instead of | 
 | // Repeating. | 
 | TEST(CallbackListTest, RemoveOnceCallbacksDuringIteration) { | 
 |   OnceClosureList cb_reg; | 
 |   Listener a, b; | 
 |   Remover remover_1, remover_2; | 
 |  | 
 |   CallbackListSubscription remover_1_sub = cb_reg.Add( | 
 |       BindOnce(&Remover::IncrementTotalAndRemove, Unretained(&remover_1))); | 
 |   CallbackListSubscription remover_2_sub = cb_reg.Add( | 
 |       BindOnce(&Remover::IncrementTotalAndRemove, Unretained(&remover_2))); | 
 |   CallbackListSubscription a_subscription = | 
 |       cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&a))); | 
 |   CallbackListSubscription b_subscription = | 
 |       cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&b))); | 
 |  | 
 |   // |remover_1| will remove itself. | 
 |   remover_1.SetSubscriptionToRemove(std::move(remover_1_sub)); | 
 |   // |remover_2| will remove a. | 
 |   remover_2.SetSubscriptionToRemove(std::move(a_subscription)); | 
 |  | 
 |   cb_reg.Notify(); | 
 |  | 
 |   // |remover_1| runs once (and removes itself), |remover_2| runs once (and | 
 |   // removes a), |a| never runs, and |b| runs once. | 
 |   EXPECT_EQ(1, remover_1.total()); | 
 |   EXPECT_EQ(1, remover_2.total()); | 
 |   EXPECT_EQ(0, a.total()); | 
 |   EXPECT_EQ(1, b.total()); | 
 |  | 
 |   cb_reg.Notify(); | 
 |  | 
 |   // Nothing runs this time. | 
 |   EXPECT_EQ(1, remover_1.total()); | 
 |   EXPECT_EQ(1, remover_2.total()); | 
 |   EXPECT_EQ(0, a.total()); | 
 |   EXPECT_EQ(1, b.total()); | 
 | } | 
 |  | 
 | // Test that a callback can add another callback to the list durning iteration | 
 | // without invalidating the iterator. The newly added callback should be run on | 
 | // the current iteration as will all other callbacks in the list. | 
 | TEST(CallbackListTest, AddCallbacksDuringIteration) { | 
 |   RepeatingClosureList cb_reg; | 
 |   Adder a(&cb_reg); | 
 |   Listener b; | 
 |   CallbackListSubscription a_subscription = | 
 |       cb_reg.Add(BindRepeating(&Adder::AddCallback, Unretained(&a))); | 
 |   CallbackListSubscription b_subscription = | 
 |       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b))); | 
 |  | 
 |   cb_reg.Notify(); | 
 |  | 
 |   EXPECT_EQ(1, a.total()); | 
 |   EXPECT_EQ(1, b.total()); | 
 |   EXPECT_TRUE(a.added()); | 
 |  | 
 |   cb_reg.Notify(); | 
 |  | 
 |   EXPECT_EQ(2, a.total()); | 
 |   EXPECT_EQ(2, b.total()); | 
 | } | 
 |  | 
 | // Sanity check: notifying an empty list is a no-op. | 
 | TEST(CallbackListTest, EmptyList) { | 
 |   RepeatingClosureList cb_reg; | 
 |  | 
 |   cb_reg.Notify(); | 
 | } | 
 |  | 
 | // empty() should be callable during iteration, and return false if not all the | 
 | // remaining callbacks in the list are null. | 
 | TEST(CallbackListTest, NonEmptyListDuringIteration) { | 
 |   // Declare items such that |cb_reg| is torn down before the subscriptions. | 
 |   // This ensures the removal callback's invariant that the callback list is | 
 |   // nonempty will always hold. | 
 |   Remover remover; | 
 |   Listener listener; | 
 |   CallbackListSubscription remover_sub, listener_sub; | 
 |   RepeatingClosureList cb_reg; | 
 |   cb_reg.set_removal_callback(base::BindRepeating( | 
 |       [](const RepeatingClosureList* callbacks) { | 
 |         EXPECT_FALSE(callbacks->empty()); | 
 |       }, | 
 |       Unretained(&cb_reg))); | 
 |  | 
 |   remover_sub = cb_reg.Add( | 
 |       BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover))); | 
 |   listener_sub = cb_reg.Add( | 
 |       BindRepeating(&Listener::IncrementTotal, Unretained(&listener))); | 
 |  | 
 |   // |remover| will remove |listener|. | 
 |   remover.SetSubscriptionToRemove(std::move(listener_sub)); | 
 |  | 
 |   cb_reg.Notify(); | 
 |  | 
 |   EXPECT_EQ(1, remover.total()); | 
 |   EXPECT_EQ(0, listener.total()); | 
 | } | 
 |  | 
 | // empty() should be callable during iteration, and return true if all the | 
 | // remaining callbacks in the list are null. | 
 | TEST(CallbackListTest, EmptyListDuringIteration) { | 
 |   OnceClosureList cb_reg; | 
 |   cb_reg.set_removal_callback(base::BindRepeating( | 
 |       [](const OnceClosureList* callbacks) { EXPECT_TRUE(callbacks->empty()); }, | 
 |       Unretained(&cb_reg))); | 
 |  | 
 |   Remover remover; | 
 |   Listener listener; | 
 |   CallbackListSubscription remover_sub = cb_reg.Add( | 
 |       BindOnce(&Remover::IncrementTotalAndRemove, Unretained(&remover))); | 
 |   CallbackListSubscription listener_sub = | 
 |       cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&listener))); | 
 |  | 
 |   // |remover| will remove |listener|. | 
 |   remover.SetSubscriptionToRemove(std::move(listener_sub)); | 
 |  | 
 |   cb_reg.Notify(); | 
 |  | 
 |   EXPECT_EQ(1, remover.total()); | 
 |   EXPECT_EQ(0, listener.total()); | 
 | } | 
 |  | 
 | TEST(CallbackListTest, RemovalCallback) { | 
 |   Counter remove_count; | 
 |   RepeatingClosureList cb_reg; | 
 |   cb_reg.set_removal_callback( | 
 |       BindRepeating(&Counter::Increment, Unretained(&remove_count))); | 
 |  | 
 |   CallbackListSubscription subscription = cb_reg.Add(DoNothing()); | 
 |  | 
 |   // Removing a subscription outside of iteration signals the callback. | 
 |   EXPECT_EQ(0, remove_count.value()); | 
 |   subscription = {}; | 
 |   EXPECT_EQ(1, remove_count.value()); | 
 |  | 
 |   // Configure two subscriptions to remove themselves. | 
 |   Remover remover_1, remover_2; | 
 |   CallbackListSubscription remover_1_sub = cb_reg.Add( | 
 |       BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover_1))); | 
 |   CallbackListSubscription remover_2_sub = cb_reg.Add( | 
 |       BindRepeating(&Remover::IncrementTotalAndRemove, Unretained(&remover_2))); | 
 |   remover_1.SetSubscriptionToRemove(std::move(remover_1_sub)); | 
 |   remover_2.SetSubscriptionToRemove(std::move(remover_2_sub)); | 
 |  | 
 |   // The callback should be signaled exactly once. | 
 |   EXPECT_EQ(1, remove_count.value()); | 
 |   cb_reg.Notify(); | 
 |   EXPECT_EQ(2, remove_count.value()); | 
 |   EXPECT_TRUE(cb_reg.empty()); | 
 | } | 
 |  | 
 | TEST(CallbackListTest, AbandonSubscriptions) { | 
 |   Listener listener; | 
 |   CallbackListSubscription subscription; | 
 |   { | 
 |     RepeatingClosureList cb_reg; | 
 |     subscription = cb_reg.Add( | 
 |         BindRepeating(&Listener::IncrementTotal, Unretained(&listener))); | 
 |     // Make sure the callback is signaled while cb_reg is in scope. | 
 |     cb_reg.Notify(); | 
 |     // Exiting this scope and running the cb_reg destructor shouldn't fail. | 
 |   } | 
 |   EXPECT_EQ(1, listener.total()); | 
 |  | 
 |   // Destroying the subscription after the list should not cause any problems. | 
 |   subscription = {}; | 
 | } | 
 |  | 
 | // Subscriptions should be movable. | 
 | TEST(CallbackListTest, MoveSubscription) { | 
 |   RepeatingClosureList cb_reg; | 
 |   Listener listener; | 
 |   CallbackListSubscription subscription1 = cb_reg.Add( | 
 |       BindRepeating(&Listener::IncrementTotal, Unretained(&listener))); | 
 |   cb_reg.Notify(); | 
 |   EXPECT_EQ(1, listener.total()); | 
 |  | 
 |   auto subscription2 = std::move(subscription1); | 
 |   cb_reg.Notify(); | 
 |   EXPECT_EQ(2, listener.total()); | 
 |  | 
 |   subscription2 = {}; | 
 |   cb_reg.Notify(); | 
 |   EXPECT_EQ(2, listener.total()); | 
 | } | 
 |  | 
 | TEST(CallbackListTest, CancelBeforeRunning) { | 
 |   OnceClosureList cb_reg; | 
 |   Listener a; | 
 |  | 
 |   CallbackListSubscription a_subscription = | 
 |       cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&a))); | 
 |  | 
 |   EXPECT_TRUE(a_subscription); | 
 |  | 
 |   // Canceling a OnceCallback before running it should not cause problems. | 
 |   a_subscription = {}; | 
 |   cb_reg.Notify(); | 
 |  | 
 |   // |a| should not have received any callbacks. | 
 |   EXPECT_EQ(0, a.total()); | 
 | } | 
 |  | 
 | // Verifies Notify() can be called reentrantly and what its expected effects | 
 | // are. | 
 | TEST(CallbackListTest, ReentrantNotify) { | 
 |   RepeatingClosureList cb_reg; | 
 |   Listener a, b, c, d; | 
 |   CallbackListSubscription a_subscription, c_subscription; | 
 |  | 
 |   // A callback to run for |a|. | 
 |   const auto a_callback = [](RepeatingClosureList* callbacks, Listener* a, | 
 |                              CallbackListSubscription* a_subscription, | 
 |                              const Listener* b, Listener* c, | 
 |                              CallbackListSubscription* c_subscription, | 
 |                              Listener* d) { | 
 |     // This should be the first callback. | 
 |     EXPECT_EQ(0, a->total()); | 
 |     EXPECT_EQ(0, b->total()); | 
 |     EXPECT_EQ(0, c->total()); | 
 |     EXPECT_EQ(0, d->total()); | 
 |  | 
 |     // Increment |a| once. | 
 |     a->IncrementTotal(); | 
 |  | 
 |     // Prevent |a| from being incremented again during the reentrant Notify(). | 
 |     // Since this is the first callback, this also verifies the inner Notify() | 
 |     // doesn't assume the first callback (or all callbacks) are valid. | 
 |     *a_subscription = {}; | 
 |  | 
 |     // Add |c| and |d| to be incremented by the reentrant Notify(). | 
 |     *c_subscription = | 
 |         callbacks->Add(BindRepeating(&Listener::IncrementTotal, Unretained(c))); | 
 |     CallbackListSubscription d_subscription = | 
 |         callbacks->Add(BindRepeating(&Listener::IncrementTotal, Unretained(d))); | 
 |  | 
 |     // Notify reentrantly.  This should not increment |a|, but all the others | 
 |     // should be incremented. | 
 |     callbacks->Notify(); | 
 |     EXPECT_EQ(1, b->total()); | 
 |     EXPECT_EQ(1, c->total()); | 
 |     EXPECT_EQ(1, d->total()); | 
 |  | 
 |     // Since |d_subscription| is locally scoped, it should be canceled before | 
 |     // the outer Notify() increments |d|.  |c_subscription| already exists and | 
 |     // thus |c| should get incremented again by the outer Notify() even though | 
 |     // it wasn't scoped when that was called. | 
 |   }; | 
 |  | 
 |   // Add |a| and |b| to the list to be notified, and notify. | 
 |   a_subscription = cb_reg.Add( | 
 |       BindRepeating(a_callback, Unretained(&cb_reg), Unretained(&a), | 
 |                     Unretained(&a_subscription), Unretained(&b), Unretained(&c), | 
 |                     Unretained(&c_subscription), Unretained(&d))); | 
 |   CallbackListSubscription b_subscription = | 
 |       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b))); | 
 |  | 
 |   // Execute both notifications and check the cumulative effect. | 
 |   cb_reg.Notify(); | 
 |   EXPECT_EQ(1, a.total()); | 
 |   EXPECT_EQ(2, b.total()); | 
 |   EXPECT_EQ(2, c.total()); | 
 |   EXPECT_EQ(1, d.total()); | 
 | } | 
 |  | 
 | }  // namespace | 
 | }  // namespace base |