TaskScheduler: Avoid Sequence refcount bump in GetWork()

vmpstr@ discovered an implicit copy constructor resulting
in a refcount bump in https://codereview.chromium.org/1895903003/
and made it explicit in https://codereview.chromium.org/1901223003/
this CL removes the need for it via a couple changes:

1) Make SequenceAndSortKey move-only
 - Allows it to be in std::priority_queue without std::unique_ptr's
  - Less pointer chasing == yay?!
 - Removing need for custom SequenceAndSortKeyComparator.
2) Add a take_sequence() method to it:
 - Allows to take its Sequence without refbump on Pop()
3) Make it a private implementation detail and make Transaction's API:
 - Push(sequence,sort_key), PeekSortKey(), PopSequence()
4) Do fdoray's TODO on adding IsEmpty() to Transaction's API:
 - Removes need for null SequenceAndSortKey (required change to allow for PeekSortKey).
5) Make SequenceSortKey copyable.
 - Required for SequenceAndSortKey's move constructor
 - Means it turns into a class with operator== for tests instead of custom
   test member verifiers.

Might have been able to split these up but they all line up fairly nicely
and don't amount to a crazy amount of code so here it is :-).

BUG=553459

Review-Url: https://codereview.chromium.org/1903133003
Cr-Commit-Position: refs/heads/master@{#390451}
diff --git a/base/task_scheduler/priority_queue.cc b/base/task_scheduler/priority_queue.cc
index 05a90b22..8fec497 100644
--- a/base/task_scheduler/priority_queue.cc
+++ b/base/task_scheduler/priority_queue.cc
@@ -12,15 +12,50 @@
 namespace base {
 namespace internal {
 
-PriorityQueue::SequenceAndSortKey::SequenceAndSortKey()
-    : sort_key(TaskPriority::LOWEST, TimeTicks()) {}
+// A class combining a Sequence and the SequenceSortKey that determines its
+// position in a PriorityQueue. Instances are only mutable via take_sequence()
+// which can only be called once and renders its instance invalid after the
+// call.
+class PriorityQueue::SequenceAndSortKey {
+ public:
+  SequenceAndSortKey(scoped_refptr<Sequence>&& sequence,
+                     const SequenceSortKey& sort_key)
+      : sequence_(sequence), sort_key_(sort_key) {
+    DCHECK(sequence_);
+  }
 
-PriorityQueue::SequenceAndSortKey::SequenceAndSortKey(
-    scoped_refptr<Sequence> sequence,
-    const SequenceSortKey& sort_key)
-    : sequence(std::move(sequence)), sort_key(sort_key) {}
+  // Note: while |sequence_| should always be non-null post-move (i.e. we
+  // shouldn't be moving an invalid SequenceAndSortKey around), there can't be a
+  // DCHECK(sequence_) on moves as the Windows STL moves elements on pop instead
+  // of overwriting them: resulting in the move of a SequenceAndSortKey with a
+  // null |sequence_| in Transaction::Pop()'s implementation.
+  SequenceAndSortKey(SequenceAndSortKey&& other) = default;
+  SequenceAndSortKey& operator=(SequenceAndSortKey&& other) = default;
 
-PriorityQueue::SequenceAndSortKey::~SequenceAndSortKey() = default;
+  // Extracts |sequence_| from this object. This object is invalid after this
+  // call.
+  scoped_refptr<Sequence> take_sequence() {
+    DCHECK(sequence_);
+    return std::move(sequence_);
+  }
+
+  // Compares this SequenceAndSortKey to |other| based on their respective
+  // |sort_key_|.
+  bool operator<(const SequenceAndSortKey& other) const {
+    return sort_key_ < other.sort_key_;
+  }
+  bool operator>(const SequenceAndSortKey& other) const {
+    return other < *this;
+  }
+
+  const SequenceSortKey& sort_key() const { return sort_key_; }
+
+ private:
+  scoped_refptr<Sequence> sequence_;
+  SequenceSortKey sort_key_;
+
+  DISALLOW_COPY_AND_ASSIGN(SequenceAndSortKey);
+};
 
 PriorityQueue::Transaction::Transaction(PriorityQueue* outer_queue)
     : auto_lock_(outer_queue->container_lock_), outer_queue_(outer_queue) {
@@ -32,29 +67,36 @@
 }
 
 void PriorityQueue::Transaction::Push(
-    std::unique_ptr<SequenceAndSortKey> sequence_and_sort_key) {
+    scoped_refptr<Sequence> sequence,
+    const SequenceSortKey& sequence_sort_key) {
   DCHECK(CalledOnValidThread());
-  DCHECK(!sequence_and_sort_key->is_null());
-
-  outer_queue_->container_.push(std::move(sequence_and_sort_key));
+  outer_queue_->container_.emplace(std::move(sequence), sequence_sort_key);
 }
 
-const PriorityQueue::SequenceAndSortKey& PriorityQueue::Transaction::Peek()
-    const {
+const SequenceSortKey& PriorityQueue::Transaction::PeekSortKey() const {
   DCHECK(CalledOnValidThread());
-
-  // TODO(fdoray): Add an IsEmpty() method to Transaction and require Peek() to
-  // be called on a non-empty PriorityQueue only.
-  if (outer_queue_->container_.empty())
-    return outer_queue_->empty_sequence_and_sort_key_;
-
-  return *outer_queue_->container_.top();
+  DCHECK(!IsEmpty());
+  return outer_queue_->container_.top().sort_key();
 }
 
-void PriorityQueue::Transaction::Pop() {
+scoped_refptr<Sequence> PriorityQueue::Transaction::PopSequence() {
   DCHECK(CalledOnValidThread());
-  DCHECK(!outer_queue_->container_.empty());
+  DCHECK(!IsEmpty());
+
+  // The const_cast on top() is okay since the SequenceAndSortKey is
+  // transactionally being popped from |container_| right after and taking its
+  // Sequence does not alter its sort order (a requirement for the Windows STL's
+  // consistency debug-checks for std::priority_queue::top()).
+  scoped_refptr<Sequence> sequence =
+      const_cast<PriorityQueue::SequenceAndSortKey&>(
+          outer_queue_->container_.top())
+          .take_sequence();
   outer_queue_->container_.pop();
+  return sequence;
+}
+
+bool PriorityQueue::Transaction::IsEmpty() const {
+  return outer_queue_->container_.empty();
 }
 
 PriorityQueue::PriorityQueue() = default;
diff --git a/base/task_scheduler/priority_queue.h b/base/task_scheduler/priority_queue.h
index 8e0a532..ac1dbde 100644
--- a/base/task_scheduler/priority_queue.h
+++ b/base/task_scheduler/priority_queue.h
@@ -23,27 +23,6 @@
 // A PriorityQueue holds Sequences of Tasks. This class is thread-safe.
 class BASE_EXPORT PriorityQueue {
  public:
-  // An immutable struct combining a Sequence and the sort key that determines
-  // its position in a PriorityQueue.
-  struct BASE_EXPORT SequenceAndSortKey {
-    // Constructs a null SequenceAndSortKey.
-    SequenceAndSortKey();
-
-    // Constructs a SequenceAndSortKey with the given |sequence| and |sort_key|.
-    SequenceAndSortKey(scoped_refptr<Sequence> sequence,
-                       const SequenceSortKey& sort_key);
-    ~SequenceAndSortKey();
-
-    // Returns true if this is a null SequenceAndSortKey.
-    bool is_null() const { return !sequence; }
-
-    const scoped_refptr<Sequence> sequence;
-    const SequenceSortKey sort_key;
-
-   private:
-    DISALLOW_COPY_AND_ASSIGN(SequenceAndSortKey);
-  };
-
   // A Transaction can perform multiple operations atomically on a
   // PriorityQueue. While a Transaction is alive, it is guaranteed that nothing
   // else will access the PriorityQueue.
@@ -57,17 +36,25 @@
    public:
     ~Transaction();
 
-    // Inserts |sequence_and_sort_key| in the PriorityQueue.
-    void Push(std::unique_ptr<SequenceAndSortKey> sequence_and_sort_key);
+    // Inserts |sequence| in the PriorityQueue with |sequence_sort_key|.
+    // Note: |sequence_sort_key| is required as a parameter instead of being
+    // extracted from |sequence| in Push() to avoid this Transaction having a
+    // lock interdependency with |sequence|.
+    void Push(scoped_refptr<Sequence> sequence,
+              const SequenceSortKey& sequence_sort_key);
 
-    // Returns the SequenceAndSortKey with the highest priority or a null
-    // SequenceAndSortKey if the PriorityQueue is empty. The reference becomes
-    // invalid the next time that a Sequence is popped from the PriorityQueue.
-    const SequenceAndSortKey& Peek() const;
+    // Returns a reference to the SequenceSortKey representing the priority of
+    // the highest pending task in this PriorityQueue. The reference becomes
+    // invalid the next time that this PriorityQueue is modified.
+    // Cannot be called on an empty PriorityQueue.
+    const SequenceSortKey& PeekSortKey() const;
 
-    // Removes the SequenceAndSortKey with the highest priority from the
-    // PriorityQueue. Cannot be called on an empty PriorityQueue.
-    void Pop();
+    // Removes and returns the highest priority Sequence in this PriorityQueue.
+    // Cannot be called on an empty PriorityQueue.
+    scoped_refptr<Sequence> PopSequence();
+
+    // Returns true if the PriorityQueue is empty.
+    bool IsEmpty() const;
 
    private:
     friend class PriorityQueue;
@@ -100,26 +87,17 @@
   const SchedulerLock* container_lock() const { return &container_lock_; }
 
  private:
-  struct SequenceAndSortKeyComparator {
-    bool operator()(const std::unique_ptr<SequenceAndSortKey>& left,
-                    const std::unique_ptr<SequenceAndSortKey>& right) const {
-      return left->sort_key < right->sort_key;
-    }
-  };
-  using ContainerType =
-      std::priority_queue<std::unique_ptr<SequenceAndSortKey>,
-                          std::vector<std::unique_ptr<SequenceAndSortKey>>,
-                          SequenceAndSortKeyComparator>;
+  // A class combining a Sequence and the SequenceSortKey that determines its
+  // position in a PriorityQueue.
+  class SequenceAndSortKey;
+
+  using ContainerType = std::priority_queue<SequenceAndSortKey>;
 
   // Synchronizes access to |container_|.
   SchedulerLock container_lock_;
 
   ContainerType container_;
 
-  // A null SequenceAndSortKey returned by Peek() when the PriorityQueue is
-  // empty.
-  const SequenceAndSortKey empty_sequence_and_sort_key_;
-
   DISALLOW_COPY_AND_ASSIGN(PriorityQueue);
 };
 
diff --git a/base/task_scheduler/priority_queue_unittest.cc b/base/task_scheduler/priority_queue_unittest.cc
index 84195cd..cf8d099 100644
--- a/base/task_scheduler/priority_queue_unittest.cc
+++ b/base/task_scheduler/priority_queue_unittest.cc
@@ -52,21 +52,6 @@
   DISALLOW_COPY_AND_ASSIGN(ThreadBeginningTransaction);
 };
 
-void ExpectSequenceAndSortKeyEq(
-    const PriorityQueue::SequenceAndSortKey& expected,
-    const PriorityQueue::SequenceAndSortKey& actual) {
-  EXPECT_EQ(expected.sequence, actual.sequence);
-  EXPECT_EQ(expected.sort_key.priority, actual.sort_key.priority);
-  EXPECT_EQ(expected.sort_key.next_task_sequenced_time,
-            actual.sort_key.next_task_sequenced_time);
-}
-
-#define EXPECT_SEQUENCE_AND_SORT_KEY_EQ(expected, actual) \
-  do {                                                    \
-    SCOPED_TRACE("");                                     \
-    ExpectSequenceAndSortKeyEq(expected, actual);         \
-  } while (false)
-
 }  // namespace
 
 TEST(TaskSchedulerPriorityQueueTest, PushPopPeek) {
@@ -98,66 +83,46 @@
   // Create a PriorityQueue and a Transaction.
   PriorityQueue pq;
   auto transaction(pq.BeginTransaction());
-  EXPECT_SEQUENCE_AND_SORT_KEY_EQ(PriorityQueue::SequenceAndSortKey(),
-                                  transaction->Peek());
+  EXPECT_TRUE(transaction->IsEmpty());
 
   // Push |sequence_a| in the PriorityQueue. It becomes the sequence with the
   // highest priority.
-  transaction->Push(WrapUnique(
-      new PriorityQueue::SequenceAndSortKey(sequence_a, sort_key_a)));
-  EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
-      PriorityQueue::SequenceAndSortKey(sequence_a, sort_key_a),
-      transaction->Peek());
+  transaction->Push(sequence_a, sort_key_a);
+  EXPECT_EQ(sort_key_a, transaction->PeekSortKey());
 
   // Push |sequence_b| in the PriorityQueue. It becomes the sequence with the
   // highest priority.
-  transaction->Push(WrapUnique(
-      new PriorityQueue::SequenceAndSortKey(sequence_b, sort_key_b)));
-  EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
-      PriorityQueue::SequenceAndSortKey(sequence_b, sort_key_b),
-      transaction->Peek());
+  transaction->Push(sequence_b, sort_key_b);
+  EXPECT_EQ(sort_key_b, transaction->PeekSortKey());
 
   // Push |sequence_c| in the PriorityQueue. |sequence_b| is still the sequence
   // with the highest priority.
-  transaction->Push(WrapUnique(
-      new PriorityQueue::SequenceAndSortKey(sequence_c, sort_key_c)));
-  EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
-      PriorityQueue::SequenceAndSortKey(sequence_b, sort_key_b),
-      transaction->Peek());
+  transaction->Push(sequence_c, sort_key_c);
+  EXPECT_EQ(sort_key_b, transaction->PeekSortKey());
 
   // Push |sequence_d| in the PriorityQueue. |sequence_b| is still the sequence
   // with the highest priority.
-  transaction->Push(WrapUnique(
-      new PriorityQueue::SequenceAndSortKey(sequence_d, sort_key_d)));
-  EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
-      PriorityQueue::SequenceAndSortKey(sequence_b, sort_key_b),
-      transaction->Peek());
+  transaction->Push(sequence_d, sort_key_d);
+  EXPECT_EQ(sort_key_b, transaction->PeekSortKey());
 
   // Pop |sequence_b| from the PriorityQueue. |sequence_c| becomes the sequence
   // with the highest priority.
-  transaction->Pop();
-  EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
-      PriorityQueue::SequenceAndSortKey(sequence_c, sort_key_c),
-      transaction->Peek());
+  EXPECT_EQ(sequence_b, transaction->PopSequence());
+  EXPECT_EQ(sort_key_c, transaction->PeekSortKey());
 
   // Pop |sequence_c| from the PriorityQueue. |sequence_a| becomes the sequence
   // with the highest priority.
-  transaction->Pop();
-  EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
-      PriorityQueue::SequenceAndSortKey(sequence_a, sort_key_a),
-      transaction->Peek());
+  EXPECT_EQ(sequence_c, transaction->PopSequence());
+  EXPECT_EQ(sort_key_a, transaction->PeekSortKey());
 
   // Pop |sequence_a| from the PriorityQueue. |sequence_d| becomes the sequence
   // with the highest priority.
-  transaction->Pop();
-  EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
-      PriorityQueue::SequenceAndSortKey(sequence_d, sort_key_d),
-      transaction->Peek());
+  EXPECT_EQ(sequence_a, transaction->PopSequence());
+  EXPECT_EQ(sort_key_d, transaction->PeekSortKey());
 
   // Pop |sequence_d| from the PriorityQueue. It is now empty.
-  transaction->Pop();
-  EXPECT_SEQUENCE_AND_SORT_KEY_EQ(PriorityQueue::SequenceAndSortKey(),
-                                  transaction->Peek());
+  EXPECT_EQ(sequence_d, transaction->PopSequence());
+  EXPECT_TRUE(transaction->IsEmpty());
 }
 
 // Check that creating Transactions on the same thread for 2 unrelated
@@ -215,14 +180,5 @@
   thread_beginning_transaction.Join();
 }
 
-TEST(TaskSchedulerPriorityQueueTest, SequenceAndSortKeyIsNull) {
-  EXPECT_TRUE(PriorityQueue::SequenceAndSortKey().is_null());
-
-  const PriorityQueue::SequenceAndSortKey non_null_sequence_and_sort_key(
-      make_scoped_refptr(new Sequence),
-      SequenceSortKey(TaskPriority::USER_VISIBLE, TimeTicks()));
-  EXPECT_FALSE(non_null_sequence_and_sort_key.is_null());
-}
-
 }  // namespace internal
 }  // namespace base
diff --git a/base/task_scheduler/scheduler_thread_pool.h b/base/task_scheduler/scheduler_thread_pool.h
index 9793162..928ed3e 100644
--- a/base/task_scheduler/scheduler_thread_pool.h
+++ b/base/task_scheduler/scheduler_thread_pool.h
@@ -18,7 +18,7 @@
 namespace internal {
 
 class SchedulerWorkerThread;
-struct SequenceSortKey;
+class SequenceSortKey;
 
 // Interface for a thread pool.
 class BASE_EXPORT SchedulerThreadPool {
diff --git a/base/task_scheduler/scheduler_thread_pool_impl.cc b/base/task_scheduler/scheduler_thread_pool_impl.cc
index b64405d..1fc854b9 100644
--- a/base/task_scheduler/scheduler_thread_pool_impl.cc
+++ b/base/task_scheduler/scheduler_thread_pool_impl.cc
@@ -276,9 +276,8 @@
 void SchedulerThreadPoolImpl::ReEnqueueSequence(
     scoped_refptr<Sequence> sequence,
     const SequenceSortKey& sequence_sort_key) {
-  shared_priority_queue_.BeginTransaction()->Push(
-      WrapUnique(new PriorityQueue::SequenceAndSortKey(std::move(sequence),
-                                                       sequence_sort_key)));
+  shared_priority_queue_.BeginTransaction()->Push(std::move(sequence),
+                                                  sequence_sort_key);
 
   // The thread calling this method just ran a Task from |sequence| and will
   // soon try to get another Sequence from which to run a Task. If the thread
@@ -347,9 +346,8 @@
     // - A worker thread is running a Task from |sequence|. It will insert
     //   |sequence| in a PriorityQueue once it's done running the Task.
     const auto sequence_sort_key = sequence->GetSortKey();
-    priority_queue->BeginTransaction()->Push(
-        WrapUnique(new PriorityQueue::SequenceAndSortKey(std::move(sequence),
-                                                         sequence_sort_key)));
+    priority_queue->BeginTransaction()->Push(std::move(sequence),
+                                             sequence_sort_key);
 
     // Wake up a worker thread to process |sequence|.
     if (worker_thread)
@@ -395,15 +393,11 @@
   {
     std::unique_ptr<PriorityQueue::Transaction> shared_transaction(
         outer_->shared_priority_queue_.BeginTransaction());
-    const auto& shared_sequence_and_sort_key = shared_transaction->Peek();
-
     std::unique_ptr<PriorityQueue::Transaction> single_threaded_transaction(
         single_threaded_priority_queue_.BeginTransaction());
-    const auto& single_threaded_sequence_and_sort_key =
-        single_threaded_transaction->Peek();
 
-    if (shared_sequence_and_sort_key.is_null() &&
-        single_threaded_sequence_and_sort_key.is_null()) {
+    if (shared_transaction->IsEmpty() &&
+        single_threaded_transaction->IsEmpty()) {
       single_threaded_transaction.reset();
 
       // |shared_transaction| is kept alive while |worker_thread| is added to
@@ -425,20 +419,18 @@
     // True if both PriorityQueues have Sequences and the Sequence at the top of
     // the shared PriorityQueue is more important.
     const bool shared_sequence_is_more_important =
-        !shared_sequence_and_sort_key.is_null() &&
-        !single_threaded_sequence_and_sort_key.is_null() &&
-        shared_sequence_and_sort_key.sort_key >
-            single_threaded_sequence_and_sort_key.sort_key;
+        !shared_transaction->IsEmpty() &&
+        !single_threaded_transaction->IsEmpty() &&
+        shared_transaction->PeekSortKey() >
+            single_threaded_transaction->PeekSortKey();
 
-    if (single_threaded_sequence_and_sort_key.is_null() ||
+    if (single_threaded_transaction->IsEmpty() ||
         shared_sequence_is_more_important) {
-      sequence = shared_sequence_and_sort_key.sequence;
-      shared_transaction->Pop();
+      sequence = shared_transaction->PopSequence();
       last_sequence_is_single_threaded_ = false;
     } else {
-      DCHECK(!single_threaded_sequence_and_sort_key.is_null());
-      sequence = single_threaded_sequence_and_sort_key.sequence;
-      single_threaded_transaction->Pop();
+      DCHECK(!single_threaded_transaction->IsEmpty());
+      sequence = single_threaded_transaction->PopSequence();
       last_sequence_is_single_threaded_ = true;
     }
   }
@@ -455,8 +447,7 @@
     // PriorityQueue from which it was extracted.
     const SequenceSortKey sequence_sort_key = sequence->GetSortKey();
     single_threaded_priority_queue_.BeginTransaction()->Push(
-        WrapUnique(new PriorityQueue::SequenceAndSortKey(std::move(sequence),
-                                                         sequence_sort_key)));
+        std::move(sequence), sequence_sort_key);
   } else {
     // |re_enqueue_sequence_callback_| will determine in which PriorityQueue
     // |sequence| must be enqueued.
diff --git a/base/task_scheduler/scheduler_thread_pool_impl.h b/base/task_scheduler/scheduler_thread_pool_impl.h
index 13fba7e5..bb77967 100644
--- a/base/task_scheduler/scheduler_thread_pool_impl.h
+++ b/base/task_scheduler/scheduler_thread_pool_impl.h
@@ -31,7 +31,6 @@
 namespace internal {
 
 class DelayedTaskManager;
-struct SequenceSortKey;
 class TaskTracker;
 
 // A pool of threads that run Tasks. This class is thread-safe.
diff --git a/base/task_scheduler/sequence_sort_key.cc b/base/task_scheduler/sequence_sort_key.cc
index 758a411..e356c8b 100644
--- a/base/task_scheduler/sequence_sort_key.cc
+++ b/base/task_scheduler/sequence_sort_key.cc
@@ -9,19 +9,20 @@
 
 SequenceSortKey::SequenceSortKey(TaskPriority priority,
                                  TimeTicks next_task_sequenced_time)
-    : priority(priority), next_task_sequenced_time(next_task_sequenced_time) {}
+    : priority_(priority),
+      next_task_sequenced_time_(next_task_sequenced_time) {}
 
 bool SequenceSortKey::operator<(const SequenceSortKey& other) const {
   // This SequenceSortKey is considered less important than |other| if it has a
   // lower priority or if it has the same priority but its next task was posted
   // later than |other|'s.
   const int priority_diff =
-      static_cast<int>(priority) - static_cast<int>(other.priority);
+      static_cast<int>(priority_) - static_cast<int>(other.priority_);
   if (priority_diff < 0)
     return true;
   if (priority_diff > 0)
     return false;
-  return next_task_sequenced_time > other.next_task_sequenced_time;
+  return next_task_sequenced_time_ > other.next_task_sequenced_time_;
 }
 
 }  // namespace internal
diff --git a/base/task_scheduler/sequence_sort_key.h b/base/task_scheduler/sequence_sort_key.h
index f2dd561c..0a03ed1 100644
--- a/base/task_scheduler/sequence_sort_key.h
+++ b/base/task_scheduler/sequence_sort_key.h
@@ -12,20 +12,33 @@
 namespace base {
 namespace internal {
 
-// An immutable representation of the priority of a Sequence.
-struct BASE_EXPORT SequenceSortKey final {
+// An immutable but assignable representation of the priority of a Sequence.
+class BASE_EXPORT SequenceSortKey final {
+ public:
   SequenceSortKey(TaskPriority priority, TimeTicks next_task_sequenced_time);
 
   bool operator<(const SequenceSortKey& other) const;
   bool operator>(const SequenceSortKey& other) const { return other < *this; }
 
+  bool operator==(const SequenceSortKey& other) const {
+    return priority_ == other.priority_ &&
+           next_task_sequenced_time_ == other.next_task_sequenced_time_;
+  }
+  bool operator!=(const SequenceSortKey& other) const {
+    return !(other == *this);
+  };
+
+ private:
+  // The private section allows this class to keep its immutable property while
+  // being copy-assignable (i.e. instead of making its members const).
+
   // Highest task priority in the sequence at the time this sort key was
   // created.
-  const TaskPriority priority;
+  TaskPriority priority_;
 
   // Sequenced time of the next task to run in the sequence at the time this
   // sort key was created.
-  const TimeTicks next_task_sequenced_time;
+  TimeTicks next_task_sequenced_time_;
 };
 
 }  // namespace internal
diff --git a/base/task_scheduler/sequence_sort_key_unittest.cc b/base/task_scheduler/sequence_sort_key_unittest.cc
index 5c6c917..2c1d80d 100644
--- a/base/task_scheduler/sequence_sort_key_unittest.cc
+++ b/base/task_scheduler/sequence_sort_key_unittest.cc
@@ -125,5 +125,119 @@
   EXPECT_FALSE(key_f > key_f);
 }
 
+TEST(TaskSchedulerSequenceSortKeyTest, OperatorEqual) {
+  SequenceSortKey key_a(TaskPriority::USER_BLOCKING,
+                        TimeTicks::FromInternalValue(1000));
+  SequenceSortKey key_b(TaskPriority::USER_BLOCKING,
+                        TimeTicks::FromInternalValue(2000));
+  SequenceSortKey key_c(TaskPriority::USER_VISIBLE,
+                        TimeTicks::FromInternalValue(1000));
+  SequenceSortKey key_d(TaskPriority::USER_VISIBLE,
+                        TimeTicks::FromInternalValue(2000));
+  SequenceSortKey key_e(TaskPriority::BACKGROUND,
+                        TimeTicks::FromInternalValue(1000));
+  SequenceSortKey key_f(TaskPriority::BACKGROUND,
+                        TimeTicks::FromInternalValue(2000));
+
+  EXPECT_EQ(key_a, key_a);
+  EXPECT_FALSE(key_b == key_a);
+  EXPECT_FALSE(key_c == key_a);
+  EXPECT_FALSE(key_d == key_a);
+  EXPECT_FALSE(key_e == key_a);
+  EXPECT_FALSE(key_f == key_a);
+
+  EXPECT_FALSE(key_a == key_b);
+  EXPECT_EQ(key_b, key_b);
+  EXPECT_FALSE(key_c == key_b);
+  EXPECT_FALSE(key_d == key_b);
+  EXPECT_FALSE(key_e == key_b);
+  EXPECT_FALSE(key_f == key_b);
+
+  EXPECT_FALSE(key_a == key_c);
+  EXPECT_FALSE(key_b == key_c);
+  EXPECT_EQ(key_c, key_c);
+  EXPECT_FALSE(key_d == key_c);
+  EXPECT_FALSE(key_e == key_c);
+  EXPECT_FALSE(key_f == key_c);
+
+  EXPECT_FALSE(key_a == key_d);
+  EXPECT_FALSE(key_b == key_d);
+  EXPECT_FALSE(key_c == key_d);
+  EXPECT_EQ(key_d, key_d);
+  EXPECT_FALSE(key_e == key_d);
+  EXPECT_FALSE(key_f == key_d);
+
+  EXPECT_FALSE(key_a == key_e);
+  EXPECT_FALSE(key_b == key_e);
+  EXPECT_FALSE(key_c == key_e);
+  EXPECT_FALSE(key_d == key_e);
+  EXPECT_EQ(key_e, key_e);
+  EXPECT_FALSE(key_f == key_e);
+
+  EXPECT_FALSE(key_a == key_f);
+  EXPECT_FALSE(key_b == key_f);
+  EXPECT_FALSE(key_c == key_f);
+  EXPECT_FALSE(key_d == key_f);
+  EXPECT_FALSE(key_e == key_f);
+  EXPECT_EQ(key_f, key_f);
+}
+
+TEST(TaskSchedulerSequenceSortKeyTest, OperatorNotEqual) {
+  SequenceSortKey key_a(TaskPriority::USER_BLOCKING,
+                        TimeTicks::FromInternalValue(1000));
+  SequenceSortKey key_b(TaskPriority::USER_BLOCKING,
+                        TimeTicks::FromInternalValue(2000));
+  SequenceSortKey key_c(TaskPriority::USER_VISIBLE,
+                        TimeTicks::FromInternalValue(1000));
+  SequenceSortKey key_d(TaskPriority::USER_VISIBLE,
+                        TimeTicks::FromInternalValue(2000));
+  SequenceSortKey key_e(TaskPriority::BACKGROUND,
+                        TimeTicks::FromInternalValue(1000));
+  SequenceSortKey key_f(TaskPriority::BACKGROUND,
+                        TimeTicks::FromInternalValue(2000));
+
+  EXPECT_FALSE(key_a != key_a);
+  EXPECT_NE(key_b, key_a);
+  EXPECT_NE(key_c, key_a);
+  EXPECT_NE(key_d, key_a);
+  EXPECT_NE(key_e, key_a);
+  EXPECT_NE(key_f, key_a);
+
+  EXPECT_NE(key_a, key_b);
+  EXPECT_FALSE(key_b != key_b);
+  EXPECT_NE(key_c, key_b);
+  EXPECT_NE(key_d, key_b);
+  EXPECT_NE(key_e, key_b);
+  EXPECT_NE(key_f, key_b);
+
+  EXPECT_NE(key_a, key_c);
+  EXPECT_NE(key_b, key_c);
+  EXPECT_FALSE(key_c != key_c);
+  EXPECT_NE(key_d, key_c);
+  EXPECT_NE(key_e, key_c);
+  EXPECT_NE(key_f, key_c);
+
+  EXPECT_NE(key_a, key_d);
+  EXPECT_NE(key_b, key_d);
+  EXPECT_NE(key_c, key_d);
+  EXPECT_FALSE(key_d != key_d);
+  EXPECT_NE(key_e, key_d);
+  EXPECT_NE(key_f, key_d);
+
+  EXPECT_NE(key_a, key_e);
+  EXPECT_NE(key_b, key_e);
+  EXPECT_NE(key_c, key_e);
+  EXPECT_NE(key_d, key_e);
+  EXPECT_FALSE(key_e != key_e);
+  EXPECT_NE(key_f, key_e);
+
+  EXPECT_NE(key_a, key_f);
+  EXPECT_NE(key_b, key_f);
+  EXPECT_NE(key_c, key_f);
+  EXPECT_NE(key_d, key_f);
+  EXPECT_NE(key_e, key_f);
+  EXPECT_FALSE(key_f != key_f);
+}
+
 }  // namespace internal
 }  // namespace base
diff --git a/base/task_scheduler/sequence_unittest.cc b/base/task_scheduler/sequence_unittest.cc
index 25baea4..6a15299 100644
--- a/base/task_scheduler/sequence_unittest.cc
+++ b/base/task_scheduler/sequence_unittest.cc
@@ -68,13 +68,6 @@
   DISALLOW_COPY_AND_ASSIGN(TaskSchedulerSequenceTest);
 };
 
-void ExpectSortKey(TaskPriority expected_priority,
-                   TimeTicks expected_sequenced_time,
-                   const SequenceSortKey& actual_sort_key) {
-  EXPECT_EQ(expected_priority, actual_sort_key.priority);
-  EXPECT_EQ(expected_sequenced_time, actual_sort_key.next_task_sequenced_time);
-}
-
 }  // namespace
 
 TEST_F(TaskSchedulerSequenceTest, PushPopPeek) {
@@ -133,56 +126,63 @@
   // Push task A in the sequence. The highest priority is from task A
   // (BACKGROUND). Task A is in front of the sequence.
   sequence->PushTask(std::move(task_a_owned_));
-  ExpectSortKey(TaskPriority::BACKGROUND, task_a_->sequenced_time,
-                sequence->GetSortKey());
+  EXPECT_EQ(SequenceSortKey(TaskPriority::BACKGROUND, task_a_->sequenced_time),
+            sequence->GetSortKey());
 
   // Push task B in the sequence. The highest priority is from task B
   // (USER_VISIBLE). Task A is still in front of the sequence.
   sequence->PushTask(std::move(task_b_owned_));
-  ExpectSortKey(TaskPriority::USER_VISIBLE, task_a_->sequenced_time,
-                sequence->GetSortKey());
+  EXPECT_EQ(
+      SequenceSortKey(TaskPriority::USER_VISIBLE, task_a_->sequenced_time),
+      sequence->GetSortKey());
 
   // Push task C in the sequence. The highest priority is from task C
   // (USER_BLOCKING). Task A is still in front of the sequence.
   sequence->PushTask(std::move(task_c_owned_));
-  ExpectSortKey(TaskPriority::USER_BLOCKING, task_a_->sequenced_time,
-                sequence->GetSortKey());
+  EXPECT_EQ(
+      SequenceSortKey(TaskPriority::USER_BLOCKING, task_a_->sequenced_time),
+      sequence->GetSortKey());
 
   // Push task D in the sequence. The highest priority is from tasks C/D
   // (USER_BLOCKING). Task A is still in front of the sequence.
   sequence->PushTask(std::move(task_d_owned_));
-  ExpectSortKey(TaskPriority::USER_BLOCKING, task_a_->sequenced_time,
-                sequence->GetSortKey());
+  EXPECT_EQ(
+      SequenceSortKey(TaskPriority::USER_BLOCKING, task_a_->sequenced_time),
+      sequence->GetSortKey());
 
   // Pop task A. The highest priority is still USER_BLOCKING. The task in front
   // of the sequence is now task B.
   sequence->PopTask();
-  ExpectSortKey(TaskPriority::USER_BLOCKING, task_b_->sequenced_time,
-                sequence->GetSortKey());
+  EXPECT_EQ(
+      SequenceSortKey(TaskPriority::USER_BLOCKING, task_b_->sequenced_time),
+      sequence->GetSortKey());
 
   // Pop task B. The highest priority is still USER_BLOCKING. The task in front
   // of the sequence is now task C.
   sequence->PopTask();
-  ExpectSortKey(TaskPriority::USER_BLOCKING, task_c_->sequenced_time,
-                sequence->GetSortKey());
+  EXPECT_EQ(
+      SequenceSortKey(TaskPriority::USER_BLOCKING, task_c_->sequenced_time),
+      sequence->GetSortKey());
 
   // Pop task C. The highest priority is still USER_BLOCKING. The task in front
   // of the sequence is now task D.
   sequence->PopTask();
-  ExpectSortKey(TaskPriority::USER_BLOCKING, task_d_->sequenced_time,
-                sequence->GetSortKey());
+  EXPECT_EQ(
+      SequenceSortKey(TaskPriority::USER_BLOCKING, task_d_->sequenced_time),
+      sequence->GetSortKey());
 
   // Push task E in the sequence. The highest priority is still USER_BLOCKING.
   // The task in front of the sequence is still task D.
   sequence->PushTask(std::move(task_e_owned_));
-  ExpectSortKey(TaskPriority::USER_BLOCKING, task_d_->sequenced_time,
-                sequence->GetSortKey());
+  EXPECT_EQ(
+      SequenceSortKey(TaskPriority::USER_BLOCKING, task_d_->sequenced_time),
+      sequence->GetSortKey());
 
   // Pop task D. The highest priority is now from task E (BACKGROUND). The
   // task in front of the sequence is now task E.
   sequence->PopTask();
-  ExpectSortKey(TaskPriority::BACKGROUND, task_e_->sequenced_time,
-                sequence->GetSortKey());
+  EXPECT_EQ(SequenceSortKey(TaskPriority::BACKGROUND, task_e_->sequenced_time),
+            sequence->GetSortKey());
 }
 
 }  // namespace internal