CallbackList improvements, part 4: OnceCallback support

This is implemented as OnceCallbackList vs. RepeatingCallbackList.  It
might in theory be nice to simply have a single CallbackList that
supports simultaneously containing both Once and Repeating callbacks.
However, I don't personally need it, and while I suspect it's possible,
I didn't want to try and figure it out.

Bug: none
Change-Id: Ib04d39adedca5b15e002b1b3d5df8957b40f2254
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2174162
Reviewed-by: Daniel Cheng <[email protected]>
Reviewed-by: Jan Wilken Dörrie <[email protected]>
Commit-Queue: Peter Kasting <[email protected]>
Cr-Commit-Position: refs/heads/master@{#766187}
diff --git a/base/callback_list_unittest.cc b/base/callback_list_unittest.cc
index a706eb4c..2f15a8a 100644
--- a/base/callback_list_unittest.cc
+++ b/base/callback_list_unittest.cc
@@ -33,6 +33,7 @@
   int scaler_ = 1;
 };
 
+template <typename T>
 class Remover {
  public:
   Remover() = default;
@@ -45,8 +46,7 @@
     removal_subscription_.reset();
   }
 
-  void SetSubscriptionToRemove(
-      std::unique_ptr<CallbackList<void(void)>::Subscription> sub) {
+  void SetSubscriptionToRemove(std::unique_ptr<typename T::Subscription> sub) {
     removal_subscription_ = std::move(sub);
   }
 
@@ -54,12 +54,12 @@
 
  private:
   int total_ = 0;
-  std::unique_ptr<CallbackList<void(void)>::Subscription> removal_subscription_;
+  std::unique_ptr<typename T::Subscription> removal_subscription_;
 };
 
 class Adder {
  public:
-  explicit Adder(CallbackList<void(void)>* cb_reg) : cb_reg_(cb_reg) {}
+  explicit Adder(RepeatingClosureList* cb_reg) : cb_reg_(cb_reg) {}
   Adder(const Adder&) = delete;
   Adder& operator=(const Adder&) = delete;
   ~Adder() = default;
@@ -80,8 +80,8 @@
  private:
   bool added_ = false;
   int total_ = 0;
-  CallbackList<void(void)>* cb_reg_;
-  std::unique_ptr<CallbackList<void(void)>::Subscription> subscription_;
+  RepeatingClosureList* cb_reg_;
+  std::unique_ptr<RepeatingClosureList::Subscription> subscription_;
 };
 
 class Summer {
@@ -127,47 +127,50 @@
 TEST(CallbackListTest, ArityTest) {
   Summer s;
 
-  CallbackList<void(int)> c1;
-  std::unique_ptr<CallbackList<void(int)>::Subscription> subscription1 =
-      c1.Add(BindRepeating(&Summer::AddOneParam, Unretained(&s)));
+  RepeatingCallbackList<void(int)> c1;
+  std::unique_ptr<RepeatingCallbackList<void(int)>::Subscription>
+      subscription1 =
+          c1.Add(BindRepeating(&Summer::AddOneParam, Unretained(&s)));
 
   c1.Notify(1);
   EXPECT_EQ(1, s.value());
 
-  CallbackList<void(int, int)> c2;
-  std::unique_ptr<CallbackList<void(int, int)>::Subscription> subscription2 =
-      c2.Add(BindRepeating(&Summer::AddTwoParam, Unretained(&s)));
+  RepeatingCallbackList<void(int, int)> c2;
+  std::unique_ptr<RepeatingCallbackList<void(int, int)>::Subscription>
+      subscription2 =
+          c2.Add(BindRepeating(&Summer::AddTwoParam, Unretained(&s)));
 
   c2.Notify(1, 2);
   EXPECT_EQ(3, s.value());
 
-  CallbackList<void(int, int, int)> c3;
-  std::unique_ptr<CallbackList<void(int, int, int)>::Subscription>
+  RepeatingCallbackList<void(int, int, int)> c3;
+  std::unique_ptr<RepeatingCallbackList<void(int, int, int)>::Subscription>
       subscription3 =
           c3.Add(BindRepeating(&Summer::AddThreeParam, Unretained(&s)));
 
   c3.Notify(1, 2, 3);
   EXPECT_EQ(6, s.value());
 
-  CallbackList<void(int, int, int, int)> c4;
-  std::unique_ptr<CallbackList<void(int, int, int, int)>::Subscription>
+  RepeatingCallbackList<void(int, int, int, int)> c4;
+  std::unique_ptr<RepeatingCallbackList<void(int, int, int, int)>::Subscription>
       subscription4 =
           c4.Add(BindRepeating(&Summer::AddFourParam, Unretained(&s)));
 
   c4.Notify(1, 2, 3, 4);
   EXPECT_EQ(10, s.value());
 
-  CallbackList<void(int, int, int, int, int)> c5;
-  std::unique_ptr<CallbackList<void(int, int, int, int, int)>::Subscription>
+  RepeatingCallbackList<void(int, int, int, int, int)> c5;
+  std::unique_ptr<
+      RepeatingCallbackList<void(int, int, int, int, int)>::Subscription>
       subscription5 =
           c5.Add(BindRepeating(&Summer::AddFiveParam, Unretained(&s)));
 
   c5.Notify(1, 2, 3, 4, 5);
   EXPECT_EQ(15, s.value());
 
-  CallbackList<void(int, int, int, int, int, int)> c6;
+  RepeatingCallbackList<void(int, int, int, int, int, int)> c6;
   std::unique_ptr<
-      CallbackList<void(int, int, int, int, int, int)>::Subscription>
+      RepeatingCallbackList<void(int, int, int, int, int, int)>::Subscription>
       subscription6 =
           c6.Add(BindRepeating(&Summer::AddSixParam, Unretained(&s)));
 
@@ -178,12 +181,12 @@
 // Sanity check that closures added to the list will be run, and those removed
 // from the list will not be run.
 TEST(CallbackListTest, BasicTest) {
-  CallbackList<void(void)> cb_reg;
+  RepeatingClosureList cb_reg;
   Listener a, b, c;
 
-  std::unique_ptr<CallbackList<void(void)>::Subscription> a_subscription =
+  std::unique_ptr<RepeatingClosureList::Subscription> a_subscription =
       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&a)));
-  std::unique_ptr<CallbackList<void(void)>::Subscription> b_subscription =
+  std::unique_ptr<RepeatingClosureList::Subscription> b_subscription =
       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b)));
 
   EXPECT_TRUE(a_subscription.get());
@@ -196,7 +199,7 @@
 
   b_subscription.reset();
 
-  std::unique_ptr<CallbackList<void(void)>::Subscription> c_subscription =
+  std::unique_ptr<RepeatingClosureList::Subscription> c_subscription =
       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&c)));
 
   cb_reg.Notify();
@@ -206,10 +209,45 @@
   EXPECT_EQ(1, c.total());
 }
 
+// Similar to BasicTest but with OnceCallbacks instead of Repeating.
+TEST(CallbackListTest, OnceCallbacks) {
+  OnceClosureList cb_reg;
+  Listener a, b, c;
+
+  std::unique_ptr<OnceClosureList::Subscription> a_subscription =
+      cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&a)));
+  std::unique_ptr<OnceClosureList::Subscription> b_subscription =
+      cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&b)));
+
+  EXPECT_TRUE(a_subscription.get());
+  EXPECT_TRUE(b_subscription.get());
+
+  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.reset();
+
+  std::unique_ptr<OnceClosureList::Subscription> 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 = CallbackList<void(int)>;
+  using CallbackListType = RepeatingCallbackList<void(int)>;
   CallbackListType cb_reg;
   Listener a(1), b(-1), c(1);
 
@@ -241,19 +279,21 @@
 // Test the a callback can remove itself or a different callback from the list
 // during iteration without invalidating the iterator.
 TEST(CallbackListTest, RemoveCallbacksDuringIteration) {
-  CallbackList<void(void)> cb_reg;
+  RepeatingClosureList cb_reg;
   Listener a, b;
-  Remover remover_1, remover_2;
+  Remover<RepeatingClosureList> remover_1, remover_2;
 
-  std::unique_ptr<CallbackList<void(void)>::Subscription> remover_1_sub =
-      cb_reg.Add(BindRepeating(&Remover::IncrementTotalAndRemove,
-                               Unretained(&remover_1)));
-  std::unique_ptr<CallbackList<void(void)>::Subscription> remover_2_sub =
-      cb_reg.Add(BindRepeating(&Remover::IncrementTotalAndRemove,
-                               Unretained(&remover_2)));
-  std::unique_ptr<CallbackList<void(void)>::Subscription> a_subscription =
+  std::unique_ptr<RepeatingClosureList::Subscription> remover_1_sub =
+      cb_reg.Add(
+          BindRepeating(&Remover<RepeatingClosureList>::IncrementTotalAndRemove,
+                        Unretained(&remover_1)));
+  std::unique_ptr<RepeatingClosureList::Subscription> remover_2_sub =
+      cb_reg.Add(
+          BindRepeating(&Remover<RepeatingClosureList>::IncrementTotalAndRemove,
+                        Unretained(&remover_2)));
+  std::unique_ptr<RepeatingClosureList::Subscription> a_subscription =
       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&a)));
-  std::unique_ptr<CallbackList<void(void)>::Subscription> b_subscription =
+  std::unique_ptr<RepeatingClosureList::Subscription> b_subscription =
       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b)));
 
   // |remover_1| will remove itself.
@@ -279,16 +319,57 @@
   EXPECT_EQ(2, b.total());
 }
 
+// Similar to RemoveCallbacksDuringIteration but with OnceCallbacks instead of
+// Repeating.
+TEST(CallbackListTest, RemoveOnceCallbacksDuringIteration) {
+  OnceClosureList cb_reg;
+  Listener a, b;
+  Remover<OnceClosureList> remover_1, remover_2;
+
+  std::unique_ptr<OnceClosureList::Subscription> remover_1_sub =
+      cb_reg.Add(BindOnce(&Remover<OnceClosureList>::IncrementTotalAndRemove,
+                          Unretained(&remover_1)));
+  std::unique_ptr<OnceClosureList::Subscription> remover_2_sub =
+      cb_reg.Add(BindOnce(&Remover<OnceClosureList>::IncrementTotalAndRemove,
+                          Unretained(&remover_2)));
+  std::unique_ptr<OnceClosureList::Subscription> a_subscription =
+      cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&a)));
+  std::unique_ptr<OnceClosureList::Subscription> 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) {
-  CallbackList<void(void)> cb_reg;
+  RepeatingClosureList cb_reg;
   Adder a(&cb_reg);
   Listener b;
-  std::unique_ptr<CallbackList<void(void)>::Subscription> a_subscription =
+  std::unique_ptr<RepeatingClosureList::Subscription> a_subscription =
       cb_reg.Add(BindRepeating(&Adder::AddCallback, Unretained(&a)));
-  std::unique_ptr<CallbackList<void(void)>::Subscription> b_subscription =
+  std::unique_ptr<RepeatingClosureList::Subscription> b_subscription =
       cb_reg.Add(BindRepeating(&Listener::IncrementTotal, Unretained(&b)));
 
   cb_reg.Notify();
@@ -305,18 +386,18 @@
 
 // Sanity check: notifying an empty list is a no-op.
 TEST(CallbackListTest, EmptyList) {
-  CallbackList<void(void)> cb_reg;
+  RepeatingClosureList cb_reg;
 
   cb_reg.Notify();
 }
 
 TEST(CallbackListTest, RemovalCallback) {
   Counter remove_count;
-  CallbackList<void(void)> cb_reg;
+  RepeatingClosureList cb_reg;
   cb_reg.set_removal_callback(
       BindRepeating(&Counter::Increment, Unretained(&remove_count)));
 
-  std::unique_ptr<CallbackList<void(void)>::Subscription> subscription =
+  std::unique_ptr<RepeatingClosureList::Subscription> subscription =
       cb_reg.Add(DoNothing());
 
   // Removing a subscription outside of iteration signals the callback.
@@ -325,13 +406,15 @@
   EXPECT_EQ(1, remove_count.value());
 
   // Configure two subscriptions to remove themselves.
-  Remover remover_1, remover_2;
-  std::unique_ptr<CallbackList<void(void)>::Subscription> remover_1_sub =
-      cb_reg.Add(BindRepeating(&Remover::IncrementTotalAndRemove,
-                               Unretained(&remover_1)));
-  std::unique_ptr<CallbackList<void(void)>::Subscription> remover_2_sub =
-      cb_reg.Add(BindRepeating(&Remover::IncrementTotalAndRemove,
-                               Unretained(&remover_2)));
+  Remover<RepeatingClosureList> remover_1, remover_2;
+  std::unique_ptr<RepeatingClosureList::Subscription> remover_1_sub =
+      cb_reg.Add(
+          BindRepeating(&Remover<RepeatingClosureList>::IncrementTotalAndRemove,
+                        Unretained(&remover_1)));
+  std::unique_ptr<RepeatingClosureList::Subscription> remover_2_sub =
+      cb_reg.Add(
+          BindRepeating(&Remover<RepeatingClosureList>::IncrementTotalAndRemove,
+                        Unretained(&remover_2)));
   remover_1.SetSubscriptionToRemove(std::move(remover_1_sub));
   remover_2.SetSubscriptionToRemove(std::move(remover_2_sub));
 
@@ -344,9 +427,9 @@
 
 TEST(CallbackListTest, AbandonSubscriptions) {
   Listener listener;
-  std::unique_ptr<CallbackList<void(void)>::Subscription> subscription;
+  std::unique_ptr<RepeatingClosureList::Subscription> subscription;
   {
-    CallbackList<void(void)> cb_reg;
+    RepeatingClosureList cb_reg;
     subscription = cb_reg.Add(
         BindRepeating(&Listener::IncrementTotal, Unretained(&listener)));
     // Make sure the callback is signaled while cb_reg is in scope.
@@ -359,5 +442,22 @@
   subscription.reset();
 }
 
+TEST(CallbackListTest, CancelBeforeRunning) {
+  OnceClosureList cb_reg;
+  Listener a;
+
+  std::unique_ptr<OnceClosureList::Subscription> a_subscription =
+      cb_reg.Add(BindOnce(&Listener::IncrementTotal, Unretained(&a)));
+
+  EXPECT_TRUE(a_subscription.get());
+
+  // Canceling a OnceCallback before running it should not cause problems.
+  a_subscription.reset();
+  cb_reg.Notify();
+
+  // |a| should not have received any callbacks.
+  EXPECT_EQ(0, a.total());
+}
+
 }  // namespace
 }  // namespace base