// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_SHARED_MODEL_WEB_STATE_LIST_WEB_STATE_LIST_H_
#define IOS_CHROME_BROWSER_SHARED_MODEL_WEB_STATE_LIST_WEB_STATE_LIST_H_
#include <map>
#include <memory>
#include <set>
#include <vector>
#include "base/auto_reset.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "ios/chrome/browser/shared/model/web_state_list/web_state_opener.h"
#include "url/gurl.h"
// TODO(crbug.com/328831758): Remove this once all use cases for
// MoveWebStateWrapperAt have landed and covered by tests.
#include "base/gtest_prod_util.h"
class RemovingIndexes;
class TabGroup;
class WebStateListDelegate;
class WebStateListObserver;
// TODO(crbug.com/328831758): Remove this once all use cases for
// MoveWebStateWrapperAt have landed and covered by tests.
class WebStateListTest;
namespace tab_groups {
class TabGroupId;
class TabGroupVisualData;
} // namespace tab_groups
namespace web {
class WebState;
} // namespace web
// Manages a list of WebStates.
//
// This class supports mutating the list and to observe the mutations via the
// WebStateListObserver interface. However, the class is not re-entrant, thus
// it is an error to mutate the list from an observer (either directly by the
// observer, or indirectly via code invoked from the observer).
//
// The WebStateList takes ownership of the WebStates that it manages.
//
// WebStates can be pinned, grouped, or ungrouped, which are mutually exclusive
// states. Pinned tabs are always at the beginning of the list.
class WebStateList {
public:
// Parameters used when inserting WebStates.
struct InsertionParams {
// Lets the WebStateList decide where to insert the WebState.
static InsertionParams Automatic() { return {}; }
// Provides the WebStateList with a desired index where to insert the
// WebState.
static InsertionParams AtIndex(int desired_index) {
InsertionParams params;
params.desired_index = desired_index;
return params;
}
InsertionParams(const InsertionParams&);
InsertionParams& operator=(const InsertionParams&);
InsertionParams(InsertionParams&&);
InsertionParams& operator=(InsertionParams&&);
WebStateOpener opener;
raw_ptr<const TabGroup> in_group = nullptr;
int desired_index = kInvalidIndex;
bool inherit_opener = false;
bool activate = false;
bool pinned = false;
// Used to check that Pinned() or InGroup() have not been
// called on the same object.
bool pinned_called = false;
bool in_group_called = false;
// Sets the potential opener.
InsertionParams& WithOpener(WebStateOpener an_opener) {
this->opener = an_opener;
return *this;
}
// Whether the WebState inherits its opener. If set, the WebState opener is
// set to the active WebState, otherwise it must be explicitly passed via
// `WithOpener`.
InsertionParams& InheritOpener(bool inherits_opener = true) {
this->inherit_opener = inherits_opener;
return *this;
}
// Whether the WebState becomes the active WebState on insertion.
InsertionParams& Activate(bool activates = true) {
this->activate = activates;
return *this;
}
// Whether the WebState is added to the pinned WebStates. This is ignored if
// an opener is set or inherited which belongs to a group. The WebState will
// then be inserted in that group.
// Cannot be called after `InGroup(...)`.
InsertionParams& Pinned(bool pin = true) {
CHECK(!in_group_called);
pinned_called = true;
pinned = pin;
return *this;
}
// Sets the group the new WebState belongs to.
// Cannot be called after `Pinned(...)`.
InsertionParams& InGroup(const TabGroup* group) {
CHECK(!pinned_called);
in_group_called = true;
in_group = group;
return *this;
}
private:
// Client code should use `Automatic()` to make intention explicit.
InsertionParams();
};
// Constants used when closing WebStates.
enum ClosingFlags {
// Used to indicate that nothing special should happen to the closed
// WebState.
CLOSE_NO_FLAGS = 0,
// Used to indicate that the WebState was closed due to user action.
CLOSE_USER_ACTION = 1 << 0,
};
// Scoped type representing a batch operation in progress.
//
// This is returned by `StartBatchOperation(...)`. The batch operation will
// be considered as over when the returned instance is destroyed. To perform
// a batch operation, the pattern is the following:
//
// void DoBatchChangeOnWebStateList(WebStateList* web_state_list) {
// WebStateList::ScopedBatchOperation lock =
// web_state_list->StartBatchOperation();
// ... // modify the WebStateList ...
// }
//
// If the caller wants to perform a batch operation in a larger method, use
// a scope to limit the lifetime of the ScopedBatchOperation object.
//
// In all case, the ScopedBatchOperation must have a smaller lifetime than
// the WebStateList that returned it.
//
// Important note: the public API of WebStateList never performs an operation
// as part of a batch operation. It is the responsibility of the calling code
// the call StartBatchOperation() and to properly scope the returned object.
class [[maybe_unused, nodiscard]] ScopedBatchOperation {
public:
ScopedBatchOperation(ScopedBatchOperation&& other)
: web_state_list_(std::exchange(other.web_state_list_, nullptr)) {}
ScopedBatchOperation& operator=(ScopedBatchOperation&& other) {
web_state_list_ = std::exchange(other.web_state_list_, nullptr);
return *this;
}
~ScopedBatchOperation();
private:
friend class WebStateList;
ScopedBatchOperation(WebStateList* web_state_list);
// The WebStateList on which the batch operation is in progress.
raw_ptr<WebStateList> web_state_list_ = nullptr;
};
explicit WebStateList(WebStateListDelegate* delegate);
WebStateList(const WebStateList&) = delete;
WebStateList& operator=(const WebStateList&) = delete;
~WebStateList();
// Returns a weak pointer to the WebStateList.
base::WeakPtr<WebStateList> AsWeakPtr();
// Returns whether the model is empty or not.
bool empty() const { return web_state_wrappers_.empty(); }
// Returns the number of WebStates in the model.
int count() const { return static_cast<int>(web_state_wrappers_.size()); }
// Returns the number of pinned tabs. Since pinned tabs are always at the
// beginning of the WebStateList, any tabs whose index is smaller than is
// pinned, and any tabs whose index is greater or equal is not pinned.
int pinned_tabs_count() const { return pinned_tabs_count_; }
// Returns the number of regular tabs (i.e. the number of tabs that are
// not pinned).
int regular_tabs_count() const { return count() - pinned_tabs_count(); }
// Returns the index of the currently active WebState, or kInvalidIndex if
// there are no active WebState.
int active_index() const { return active_index_; }
// Returns true if the specified index is contained by the model.
bool ContainsIndex(int index) const;
// Returns true if the list is currently mutating.
bool IsMutating() const;
// Returns true if a batch operation is in progress.
bool IsBatchInProgress() const;
// Returns the currently active WebState or null if there is none.
web::WebState* GetActiveWebState() const;
// Returns the WebState at the specified index. It is invalid to call this
// with an index such that `ContainsIndex(index)` returns false.
web::WebState* GetWebStateAt(int index) const;
// Returns the index of the specified WebState or kInvalidIndex if the
// WebState is not in the model.
int GetIndexOfWebState(const web::WebState* web_state) const;
// Returns the index of the first WebState in the model whose visible URL is
// `url` or kInvalidIndex if no WebState with that URL exists.
int GetIndexOfWebStateWithURL(const GURL& url) const;
// Returns the index of the first WebState, ignoring the currently active
// WebState, in the model whose visible URL is `url` or kInvalidIndex if no
// non-active WebState with that URL exists.
int GetIndexOfInactiveWebStateWithURL(const GURL& url) const;
// Returns information about the opener of the WebState at the specified
// index. The structure `opener` will be null if there is no opener.
WebStateOpener GetOpenerOfWebStateAt(int index) const;
// Stores information about the opener of the WebState at the specified
// index. The WebStateOpener `opener` must be non-null and the WebState
// must be in WebStateList.
void SetOpenerOfWebStateAt(int index, WebStateOpener opener);
// Changes the pinned state of the WebState at `index`. Returns the index the
// WebState is now at (it may have been moved to maintain contiguity of pinned
// WebStates at the beginning of the list).
int SetWebStatePinnedAt(int index, bool pinned);
// Returns true if the WebState at `index` is pinned.
bool IsWebStatePinnedAt(int index) const;
// Inserts the specified WebState at the best position in the WebStateList
// given `params`.
// Returns the effective insertion index.
int InsertWebState(std::unique_ptr<web::WebState> web_state,
InsertionParams params = InsertionParams::Automatic());
// Moves the WebState at the specified index to another index.
void MoveWebStateAt(int from_index, int to_index);
// Replaces the WebState at the specified index with new WebState. Returns
// the old WebState at that index to the caller (abandon ownership of the
// returned WebState).
std::unique_ptr<web::WebState> ReplaceWebStateAt(
int index,
std::unique_ptr<web::WebState> web_state);
// Detaches the WebState at the specified index. Returns the detached WebState
// to the caller (abandon ownership of the returned WebState).
std::unique_ptr<web::WebState> DetachWebStateAt(int index);
// Closes and destroys the WebState at the specified index. The `close_flags`
// is a bitwise combination of ClosingFlags values.
void CloseWebStateAt(int index, int close_flags);
// Makes the WebState at the specified index the active WebState.
void ActivateWebStateAt(int index);
// Closes and destroys all WebStates at `removing_indexes`. The `close_flags`
// is a bitwise combination of ClosingFlags values.
void CloseWebStatesAtIndices(int close_flags,
RemovingIndexes removing_indexes);
// Returns the tab group the WebState belongs to, if any. Otherwise, returns
// `nullptr`.
//
// The returned TabGroup is valid as long as the WebStateList is not mutated.
// To get its exact lifecycle, Listen to the group deletion notification,
// after-which the pointer should not be used.
const TabGroup* GetGroupOfWebStateAt(int index) const;
// Returns the list of all groups. The order is not particularly the order in
// which they appear in this WebStateList.
std::set<const TabGroup*> GetGroups() const;
// Creates a new tab group and moves the set of WebStates at `indices` to
// it.
// - This unpins pinned WebState.
// - This ungroups grouped WebState.
// - This reorders the WebStates so they are contiguous and do not split an
// existing group in half.
// Returns the new group.
//
// The returned TabGroup is valid as long as the WebStateList is not mutated.
// To get its exact lifecycle, Listen to the group deletion notification,
// after-which the pointer should not be used.
const TabGroup* CreateGroup(const std::set<int>& indices,
const tab_groups::TabGroupVisualData& visual_data,
tab_groups::TabGroupId tab_group_id);
// Returns true if the specified group is contained by the model.
bool ContainsGroup(const TabGroup* group) const;
// Updates the visual data for the given group.
void UpdateGroupVisualData(const TabGroup* group,
const tab_groups::TabGroupVisualData& visual_data);
// Moves the set of WebStates at `indices` at the end of the given tab group.
void MoveToGroup(const std::set<int>& indices, const TabGroup* group);
// Removes the set of WebStates at `indices` from the groups they are in,
// if any. The WebStates are reordered out of the groups if necessary.
void RemoveFromGroups(const std::set<int>& indices);
// Moves the WebStates of `group` to be before the WebState at `before_index`.
// To move the group at the end, pass `before_index` greater or equal to
// `count`.
void MoveGroup(const TabGroup* group, int before_index);
// Removes all WebStates from the group. The WebStates stay where they are.
// The group is destroyed.
void DeleteGroup(const TabGroup* group);
// Adds an observer to the model.
void AddObserver(WebStateListObserver* observer);
// Removes an observer from the model.
void RemoveObserver(WebStateListObserver* observer);
// Starts a batch operation and returns a ScopedBatchOperation. The batch
// will be considered complete when the ScopedBatchOperation is destroyed.
ScopedBatchOperation StartBatchOperation();
// Invalid index.
static constexpr int kInvalidIndex = -1;
private:
friend class ScopedBatchOperation;
struct DetachParams;
class WebStateWrapper;
// 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 undefined behavior).
base::AutoReset<bool> LockForMutation();
// Inserts the specified WebState at the best position in the WebStateList
// given `params`.
// Returns the effective insertion index.
//
// Assumes that the WebStateList is locked.
int InsertWebStateImpl(std::unique_ptr<web::WebState> web_state,
InsertionParams params);
// Moves the WebState at the specified index to another index.
//
// Assumes that the WebStateList is locked.
void MoveWebStateAtImpl(int from_index, int to_index);
// Replaces the WebState at the specified index with new WebState. Returns
// the old WebState at that index to the caller (abandon ownership of the
// returned WebState).
//
// Assumes that the WebStateList is locked.
std::unique_ptr<web::WebState> ReplaceWebStateAtImpl(
int index,
std::unique_ptr<web::WebState> web_state);
// Detaches the WebState at the specified index. Returns the detached WebState
// to the caller (abandon ownership of the returned WebState).
//
// Assumes that the WebStateList is locked.
std::unique_ptr<web::WebState> DetachWebStateAtImpl(int index,
int new_active_index,
DetachParams params);
// Detaches all WebStates at `removing_indexes`. Returns a vector with all the
// detached WebStates to the caller (abandoning ownership).
//
// Assumes that the WebStateList is locked.
std::vector<std::unique_ptr<web::WebState>> DetachWebStatesAtIndicesImpl(
RemovingIndexes removing_indexes,
DetachParams detach_params);
// Makes the WebState at the specified index the active WebState.
//
// Assumes that the WebStateList is locked.
void ActivateWebStateAtImpl(int index);
// Changes the pinned state of the WebState at `index`. Returns the index the
// WebState is now at (it may have been moved to maintain contiguity of pinned
// WebStates at the beginning of the list).
//
// Assumes that the WebStateList is locked.
int SetWebStatePinnedAtImpl(int index, bool pinned);
// Creates a new tab group and moves the set of WebStates at `indices` to
// it.
// - This unpins pinned WebState.
// - This ungroups grouped WebState.
// - This reorders the WebStates so they are contiguous and do not split an
// existing group in half.
// Returns the new group.
//
// The returned TabGroup is valid as long as the WebStateList is not mutated.
// To get its exact lifecycle, Listen to the group deletion notification,
// after-which the pointer should not be used.
//
// Assumes that the WebStateList is locked.
const TabGroup* CreateGroupImpl(
const std::set<int>& indices,
const tab_groups::TabGroupVisualData& visual_data,
tab_groups::TabGroupId tab_group_id);
// Moves the set of WebStates at `indices` at the end of the given tab group.
//
// Assumes that the WebStateList is locked.
void MoveToGroupImpl(const std::set<int>& indices, const TabGroup* group);
// Updates the visual data for the given group.
//
// Assumes that the WebStateList is locked.
void UpdateGroupVisualDataImpl(
const TabGroup* group,
const tab_groups::TabGroupVisualData& visual_data);
// Removes the set of WebStates at `indices` from the groups they are in,
// if any. The WebStates are reordered out of the groups if necessary.
//
// Assumes that the WebStateList is locked.
void RemoveFromGroupsImpl(const std::set<int>& indices);
// Moves the WebStates of `group` to be before the WebState at `to_index`. To
// move the group at the end, pass `to_index` greater or equal to `count`.
//
// Assumes that the WebStateList is locked.
void MoveGroupImpl(const TabGroup* group, int to_index);
// Removes all WebStates from the group. The WebStates stay where they are.
// The group is destroyed.
//
// Assumes that the WebStateList is locked.
void DeleteGroupImpl(const TabGroup* group);
// Sets the opener of any WebState that reference the WebState at the
// specified index to null.
void ClearOpenersReferencing(int index);
// Verifies that WebState's insertion `index` is within the proper index
// range. `pinned` WebStates `index` should be within the pinned WebStates
// range. Regular WebState `index` should be outside of the pinned WebStates
// range. Returns an updated insertion `index` of the WebState.
int ConstrainInsertionIndex(int index, bool pinned) const;
// Verifies that WebState's move `index` is within the proper index range.
// `pinned` WebStates `index` should be within the pinned WebStates range.
// Regular WebState `index` should be outside of the pinned WebStates range.
// Returns an updated move `index` of the WebState.
int ConstrainMoveIndex(int index, bool pinned) const;
// Returns the wrapper of the currently active WebState or null if there
// is none.
WebStateWrapper* GetActiveWebStateWrapper() const;
// Returns the wrapper of the WebState at the specified index. It is invalid
// to call this with an index such that `ContainsIndex(index)` returns false.
WebStateWrapper* GetWebStateWrapperAt(int index) const;
// Moves the wrapper of the WebState at `from_index` to `to_index`. This
// performs the move, updates the relevant WebStateList state (number of
// pinned tabs, index of the active WebState, groups ranges), and notifies
// observers. The indices and state changes must be valid, i.e. there is no
// fallback.
//
// Assumes that the WebStateList is locked.
void MoveWebStateWrapperAt(int from_index,
int to_index,
bool pinned,
const TabGroup* group);
// Removes `group` from `groups_` if `group` is empty.
//
// Assumes that the WebStateList is locked.
void DeleteGroupIfEmpty(const TabGroup* group);
// Updates the active index, updates the WebState opener for the old active
// WebState if exists and brings the new active WebState to the "realized"
// state.
void SetActiveIndex(int active_index);
// Takes action when the active WebState changes. Does nothing it
// there is no active WebState.
void OnActiveWebStateChanged();
SEQUENCE_CHECKER(sequence_checker_);
// The WebStateList delegate.
raw_ptr<WebStateListDelegate> delegate_ = nullptr;
// Wrappers to the WebStates hosted by the WebStateList.
std::vector<std::unique_ptr<WebStateWrapper>> web_state_wrappers_;
// The current set of groups.
std::set<std::unique_ptr<TabGroup>, base::UniquePtrComparator> groups_;
// List of observers notified of changes to the model.
base::ObserverList<WebStateListObserver, true> observers_;
// Index of the currently active WebState, kInvalidIndex if no such WebState.
int active_index_ = kInvalidIndex;
// Number of pinned tabs. Always in range from 0 to count() inclusive.
int pinned_tabs_count_ = 0;
// Lock to prevent observers from mutating or deleting the list while it is
// mutating. The lock is managed by LockForMutation() method (and released
// by the returned base::AutoReset<bool>).
bool locked_ = false;
// Lock to prevent nesting batched operations.
bool batch_operation_in_progress_ = false;
// Weak pointer factory.
base::WeakPtrFactory<WebStateList> weak_factory_{this};
// TODO(crbug.com/328831758): Remove this once all use cases for
// MoveWebStateWrapperAt have landed and covered by tests.
FRIEND_TEST_ALL_PREFIXES(WebStateListTest, MoveToGroup_NoMove_GoToRightGroup);
FRIEND_TEST_ALL_PREFIXES(WebStateListTest,
MoveToGroup_NoMove_GoToRightGroup_OldGroupEmpty);
FRIEND_TEST_ALL_PREFIXES(WebStateListTest,
MoveToGroup_NoMove_GoToRightGroup_OldGroupNonEmpty);
FRIEND_TEST_ALL_PREFIXES(WebStateListTest, MoveToGroup_NoMove_PinnedToGroup);
};
// Helper function that closes all WebStates in `web_state_list`. The operation
// is performed as a batch operation and thus cannot be called from another
// batch operation. The `close_flags` is a bitwise combination of ClosingFlags
// values.
void CloseAllWebStates(WebStateList& web_state_list, int close_flags);
// Helper function that closes all regular WebStates in `web_state_list`. The
// operation is performed as a batch operation and thus cannot be called from
// another batch operation. The `close_flags` is a bitwise combination of
// ClosingFlags values.
void CloseAllNonPinnedWebStates(WebStateList& web_state_list, int close_flags);
// Helper function that closes all WebStates from `group` in `web_state_list`.
// The operation is performed as a batch operation and thus cannot be called
// from another batch operation. The `close_flags` is a bitwise combination of
// ClosingFlags values.
void CloseAllWebStatesInGroup(WebStateList& web_state_list,
const TabGroup* group,
int close_flags);
// Helper function that closes all WebStates in `web_state_list` that are not at
// `index_to_keep`. The operation is performed as a batch operation and thus
// cannot be called from another batch operation. The `close_flags` is a bitwise
// combination of ClosingFlags values.
void CloseOtherWebStates(WebStateList& web_state_list,
int index_to_keep,
int close_flags);
#endif // IOS_CHROME_BROWSER_SHARED_MODEL_WEB_STATE_LIST_WEB_STATE_LIST_H_