Add a Range to WebStateList

This is going to be helpful when introducing TabGroups.

Bug: 1523987
Change-Id: I2e7ca2e95ea624b85dc086d2de5921b7058fd75c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5255542
Reviewed-by: Quentin Pubert <qpubert@google.com>
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Auto-Submit: Louis Romero <lpromero@google.com>
Commit-Queue: Louis Romero <lpromero@google.com>
Cr-Commit-Position: refs/heads/main@{#1256251}
diff --git a/ios/chrome/browser/shared/model/web_state_list/web_state_list.h b/ios/chrome/browser/shared/model/web_state_list/web_state_list.h
index 035f18b..e56f6a4 100644
--- a/ios/chrome/browser/shared/model/web_state_list/web_state_list.h
+++ b/ios/chrome/browser/shared/model/web_state_list/web_state_list.h
@@ -146,6 +146,41 @@
     raw_ptr<WebStateList> web_state_list_ = nullptr;
   };
 
+  // Represents a range in the WebStateList. Typically used for locating tab
+  // groups.
+  class Range {
+   public:
+    // Initializes the range with a start and count.
+    constexpr Range(int start, int count) : start_(start), count_(count) {
+      DCHECK_GE(count_, 0);
+    }
+
+    // Returns a range that is invalid, which is {kInvalidIndex, 0}.
+    static constexpr Range InvalidRange() { return Range(kInvalidIndex, 0); }
+
+    // Checks if the range is valid through comparison to InvalidRange(). If
+    // this is not valid, you must not call functions on this object.
+    constexpr bool IsValid() const { return *this != InvalidRange(); }
+
+    // Getters.
+    constexpr int start() const { return start_; }
+    constexpr int count() const { return count_; }
+
+    // End is the first index not in the range.
+    constexpr int end() const { return start_ + count_; }
+    // Whether the index is inside the range.
+    constexpr bool contains(int index) const {
+      return start_ <= index && index < start_ + count_;
+    }
+
+    constexpr bool operator==(const Range& other) const = default;
+    constexpr bool operator!=(const Range& other) const = default;
+
+   private:
+    const int start_;
+    const int count_;
+  };
+
   explicit WebStateList(WebStateListDelegate* delegate);
 
   WebStateList(const WebStateList&) = delete;
@@ -283,7 +318,7 @@
 
   // Locks the WebStateList for mutation. This methods checks that the list is
   // not currently mutated (as the class is not re-entrant it would lead to
-  // corruption of the internal state and ultimately to indefined behaviour).
+  // corruption of the internal state and ultimately to undefined behavior).
   base::AutoReset<bool> LockForMutation();
 
   // Inserts the specified WebState at the best position in the WebStateList
diff --git a/ios/chrome/browser/shared/model/web_state_list/web_state_list_unittest.mm b/ios/chrome/browser/shared/model/web_state_list/web_state_list_unittest.mm
index cb8fbbb..96cbf02 100644
--- a/ios/chrome/browser/shared/model/web_state_list/web_state_list_unittest.mm
+++ b/ios/chrome/browser/shared/model/web_state_list/web_state_list_unittest.mm
@@ -1276,7 +1276,7 @@
   EXPECT_EQ(web_state_list_.GetWebStateAt(2)->GetVisibleURL().spec(), kURL2);
   EXPECT_EQ(web_state_list_.GetWebStateAt(3)->GetVisibleURL().spec(), kURL3);
 
-  // Try to move first pinned WebState inside of the pinned WebStates range.
+  // Try to move first pinned WebState contains of the pinned WebStates range.
   web_state_list_.MoveWebStateAt(0, 2);
 
   // Try to move first pinned WebState outside of the pinned WebStates range.
@@ -1344,3 +1344,52 @@
   web_state_list.reset();
   EXPECT_FALSE(weak_web_state_list);
 }
+
+using WebStateListRangeTest = PlatformTest;
+
+TEST_F(WebStateListRangeTest, InvalidRange) {
+  WebStateList::Range range = WebStateList::Range::InvalidRange();
+
+  EXPECT_FALSE(range.IsValid());
+}
+
+TEST_F(WebStateListRangeTest, ZeroRange) {
+  WebStateList::Range range(0, 0);
+
+  EXPECT_TRUE(range.IsValid());
+  EXPECT_EQ(0, range.start());
+  EXPECT_EQ(0, range.count());
+  EXPECT_EQ(0, range.end());
+
+  EXPECT_FALSE(range.contains(-1));
+  EXPECT_FALSE(range.contains(0));
+  EXPECT_FALSE(range.contains(1));
+
+  EXPECT_EQ(WebStateList::Range(0, 0), range);
+  EXPECT_NE(WebStateList::Range(0, 1), range);
+  EXPECT_NE(WebStateList::Range(1, 0), range);
+  EXPECT_NE(WebStateList::Range(1, 1), range);
+  EXPECT_NE(WebStateList::Range::InvalidRange(), range);
+}
+
+TEST_F(WebStateListRangeTest, SomeRange) {
+  WebStateList::Range range(1, 2);
+
+  EXPECT_TRUE(range.IsValid());
+  EXPECT_EQ(1, range.start());
+  EXPECT_EQ(2, range.count());
+  EXPECT_EQ(3, range.end());
+
+  EXPECT_FALSE(range.contains(-1));
+  EXPECT_FALSE(range.contains(0));
+  EXPECT_TRUE(range.contains(1));
+  EXPECT_TRUE(range.contains(2));
+  EXPECT_FALSE(range.contains(3));
+
+  EXPECT_NE(WebStateList::Range(0, 0), range);
+  EXPECT_NE(WebStateList::Range(0, 1), range);
+  EXPECT_NE(WebStateList::Range(1, 0), range);
+  EXPECT_NE(WebStateList::Range(1, 1), range);
+  EXPECT_EQ(WebStateList::Range(1, 2), range);
+  EXPECT_NE(WebStateList::Range::InvalidRange(), range);
+}