blob: 0a1b3212acc20409bb2aa55dceb69a14a70c1ffa [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_SAVED_TAB_GROUPS_INTERNAL_SAVED_TAB_GROUP_MODEL_H_
#define COMPONENTS_SAVED_TAB_GROUPS_INTERNAL_SAVED_TAB_GROUP_MODEL_H_
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "base/uuid.h"
#include "components/saved_tab_groups/public/saved_tab_group.h"
#include "components/saved_tab_groups/public/saved_tab_group_tab.h"
#include "components/saved_tab_groups/public/types.h"
#include "components/sync/base/collaboration_id.h"
#include "components/sync/protocol/saved_tab_group_specifics.pb.h"
#include "components/tab_groups/tab_group_color.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "google_apis/gaia/gaia_id.h"
namespace tab_groups {
class SavedTabGroupModelObserver;
class SavedTabGroup;
// Serves to maintain the current state of all saved tab groups in the current
// session.
class SavedTabGroupModel {
public:
SavedTabGroupModel();
SavedTabGroupModel(const SavedTabGroupModel&) = delete;
SavedTabGroupModel& operator=(const SavedTabGroupModel& other) = delete;
~SavedTabGroupModel();
// Accessor for the underlying storage vector. Prefer the methods below to
// distinguish between the saved and shared tab groups.
const std::vector<SavedTabGroup>& saved_tab_groups() const {
return saved_tab_groups_;
}
// Returns saved tab groups (which are not shared). The returned pointers can
// be invalidated on any model's mutation.
std::vector<const SavedTabGroup*> GetSavedTabGroupsOnly() const;
// Returns shared tab groups. The returned pointers can be invalidated on any
// model's mutation.
std::vector<const SavedTabGroup*> GetSharedTabGroupsOnly() const;
bool is_loaded() { return is_loaded_; }
// Returns the index of the SavedTabGroup if it exists in the vector. Else
// std::nullopt.
std::optional<int> GetIndexOf(const LocalTabGroupID local_group_id) const;
std::optional<int> GetIndexOf(const base::Uuid& id) const;
// Return weather the group is pinned if it exists in the vector. Else
// std::nullopt.
std::optional<bool> IsGroupPinned(const base::Uuid& id) const;
// Get a pointer to the SavedTabGroup from an ID. Returns nullptr if not in
// vector.
const SavedTabGroup* Get(const LocalTabGroupID local_group_id) const;
const SavedTabGroup* Get(const base::Uuid& id) const;
// Methods for checking if a group is in the SavedTabGroupModel.
bool Contains(const LocalTabGroupID& local_group_id) const {
return GetIndexOf(local_group_id).has_value();
}
bool Contains(const base::Uuid& id) const {
return GetIndexOf(id).has_value();
}
// Helper for getting number of SavedTabGroups in the vector.
int Count() const { return saved_tab_groups_.size(); }
// Helper for getting empty state of the SavedTabGroup vector.
bool IsEmpty() const { return Count() <= 0; }
// Add / Remove / Update a single tab group from the model. For local changes
// that should be synced.
void AddedLocally(SavedTabGroup saved_group);
void RemovedLocally(const LocalTabGroupID local_group_id);
void RemovedLocally(const base::Uuid& id);
void UpdateVisualDataLocally(
const LocalTabGroupID local_group_id,
const tab_groups::TabGroupVisualData* visual_data);
// Does not notify observers or create a deep copy. Rather this directly sets
// the collaboration_id on the group. It is up to callers to ensure the
// updated group is retrieved from the service before use.
void MakeTabGroupSharedForTesting(const LocalTabGroupID& local_group_id,
syncer::CollaborationId collaboration_id);
void MakeTabGroupUnsharedForTesting(const LocalTabGroupID& local_group_id);
// Mark whether the tab group identified by `local_group_id` is transitioning
// to a saved group.
void SetIsTransitioningToSaved(const LocalTabGroupID& local_group_id,
bool is_transitioning_to_saved);
// Pin SavedTabGroup if it's unpinned. Unpin SavedTabGroup if it's pinned.
void TogglePinState(base::Uuid id);
// Similar to the Add/Remove/Update but originate from sync. As such, these
// function do not notify sync observers of these changes to avoid looping
// calls.
void AddedFromSync(SavedTabGroup saved_group);
void RemovedFromSync(const LocalTabGroupID local_group_id);
void RemovedFromSync(const base::Uuid& id);
void UpdatedVisualDataFromSync(
const LocalTabGroupID local_group_id,
const tab_groups::TabGroupVisualData* visual_data);
void UpdatedVisualDataFromSync(
const base::Uuid& id,
const tab_groups::TabGroupVisualData* visual_data);
const SavedTabGroup* GetGroupContainingTab(
const base::Uuid& saved_tab_guid) const;
const SavedTabGroup* GetGroupContainingTab(
const LocalTabID& local_tab_id) const;
// Adds a saved tab to `index` in the specified group denoted by `group_id` if
// it exists. Notify local observers if the tab was added locally, and sync
// observers if it was added from sync.
void AddTabToGroupLocally(const base::Uuid& group_id, SavedTabGroupTab tab);
void AddTabToGroupFromSync(const base::Uuid& group_id, SavedTabGroupTab tab);
// Calls the UpdateTab method on a group found by group id in the model.
// Calls the observer function SavedTabGroupUpdatedLocally, if
// `notify_observers` is true.
void UpdateTabInGroup(const base::Uuid& group_id,
SavedTabGroupTab tab,
bool notify_observers);
// Updates `tab` with a new `local_id`. Unlike `UpdateTabInGroup`, this method
// does not notify observers, as this is not a change we want to sync.
void UpdateLocalTabId(const base::Uuid& group_id,
SavedTabGroupTab tab,
std::optional<LocalTabID> local_id);
// Removes saved tab `tab_id` in the specified group denoted by `group_id` if
// it exists. The group is deleted if the last tab is removed from it.
// Notifies observers if the tab was removed locally.
void RemoveTabFromGroupLocally(
const base::Uuid& group_id,
const base::Uuid& tab_id,
std::optional<GaiaId> local_gaia_id = std::nullopt);
// Similar to above but the group with `group_id` must exist. Notifies
// observers that the tab was removed from sync. `removed_by` is the user who
// removed the tab group (may be empty, e.g. if unknown), populated for shared
// tab groups only. If `prevent_group_destruction_for_testing` is set to true,
// then the group will not be removed as a result of calling this method on
// the last tab in the group. This should only be used for testing, since
// there are no cases where the group should live after the tab is deleted,
// except during a race condition in sync.
void RemoveTabFromGroupFromSync(
const base::Uuid& group_id,
const base::Uuid& tab_id,
GaiaId removed_by = GaiaId(),
bool prevent_group_destruction_for_testing = false);
// Moves a saved tab from its current position to `index` in the specified
// group denoted by `group_id` if it exists.
void MoveTabInGroupTo(const base::Uuid& group_id,
const base::Uuid& tab_id,
int index);
// Attempts to merge the remote group metadata or tab with the local object
// that holds the same `guid`.
const SavedTabGroup* MergeRemoteGroupMetadata(
const base::Uuid& guid,
const std::u16string& title,
TabGroupColorId color,
std::optional<size_t> position,
std::optional<std::string> creator_cache_guid,
std::optional<std::string> last_updater_cache_guid,
base::Time update_time,
const GaiaId& updated_by);
const SavedTabGroupTab* MergeRemoteTab(const SavedTabGroupTab& remote_tab);
// Changes the index of a given tab group by id. The new index provided is the
// expected index after the group is removed. Notify local observers if the
// group was reordered locally, and sync observers if the group was reordered
// from sync.
void ReorderGroupLocally(const base::Uuid& id, int new_index);
void ReorderGroupFromSync(const base::Uuid& id, int new_index);
// Update the creator cache guid for all saved groups that have
// `old_cache_guid`, to `new_cache_guid`.
std::pair<std::set<base::Uuid>, std::set<base::Uuid>> UpdateLocalCacheGuid(
std::optional<std::string> old_cache_guid,
std::optional<std::string> new_cache_guid);
// Update the last interaction time with the group.
void UpdateLastUserInteractionTimeLocally(
const LocalTabGroupID& local_group_id);
// Update the last seen time for a tab.
void UpdateTabLastSeenTimeFromLocal(const base::Uuid& group_id,
const base::Uuid& tab_id);
void UpdateTabLastSeenTimeFromSync(const base::Uuid& group_id,
const base::Uuid& tab_id,
base::Time time);
// Update the position for a share group from sync. If the position is
// nullopt, the group will be moved to the end of the list.
void UpdatePositionForSharedGroupFromSync(const base::Uuid& group_id,
std::optional<size_t> position);
// Update the last updater cache guid for a give group and optionally a tab.
void UpdateLastUpdaterCacheGuidForGroup(
const std::optional<std::string>& cache_guid,
const LocalTabGroupID& group_id,
const std::optional<LocalTabID>& tab_id);
// Updates the shared attribution for a given group. This method does not
// notify observers as this method should be called together with other
// changes which would notify observers anyway.
void UpdateSharedAttribution(const LocalTabGroupID& group_id,
const std::optional<LocalTabID>& tab_id,
GaiaId updated_by);
// Loads the model from the storage. `tabs` must have a corresponding group in
// `groups`.
void LoadStoredEntries(std::vector<SavedTabGroup> groups,
std::vector<SavedTabGroupTab> tabs);
// Functions that should be called when a SavedTabGroup's corresponding
// TabGroup is closed or opened.
void OnGroupOpenedInTabStrip(const base::Uuid& id,
const LocalTabGroupID& local_group_id);
void OnGroupClosedInTabStrip(const LocalTabGroupID& local_group_id);
// Add/Remove observers for this model.
void AddObserver(SavedTabGroupModelObserver* observer);
void RemoveObserver(SavedTabGroupModelObserver* observer);
// One time migration of saved tab groups from v1 to v2.
void MigrateTabGroupSavesUIUpdate();
// Start transitioning a shared tab group to a saved group. `shared_group_id`
// is the ID of the shared group.
// TODO(crbug.com/396143520): Rename this method to
// StartTransitioningToShared().
void MarkTransitionedToShared(const base::Uuid& shared_group_id);
// Marks that a tab group is hidden and should not be shown to users.
void SetGroupHidden(const base::Uuid& group_id);
// Restores the hidden state of a tab group.
void RestoreHiddenGroupFromSync(const base::Uuid& group_id);
// Called to notify of the sync bridge state changes, e.g. whether initial
// merge or disable sync are in progress. Invoked only for shared tab group
// bridge.
void OnSyncBridgeUpdateTypeChanged(
SyncBridgeUpdateType sync_bridge_update_type);
// Update the archival status and archival timestamp of the local tab group.
void UpdateArchivalStatus(const base::Uuid& id, bool archivalStatus);
// Update bookmark node id of the local tab group.
void UpdateBookmarkNodeId(const base::Uuid& id,
const std::optional<base::Uuid>& bookmark_node_id);
private:
// Returns mutable group containing tab with ID `saved_tab_guid`, otherwise
// returns null.
SavedTabGroup* MutableGroupContainingTab(const base::Uuid& saved_tab_guid);
SavedTabGroup* GetMutableGroup(const LocalTabGroupID& local_group_id);
SavedTabGroup* GetMutableGroup(const base::Uuid& id);
// Moves the group denoted by `id` to the position `new_index`.
void ReorderGroupImpl(const base::Uuid& id, int new_index);
// Updates all group positions to match the index they are currently stored
// at.
void UpdateGroupPositionsImpl();
// Insert `group` into sorted order based on its position compared to already
// stored groups in `saved_tab_groups_`. It should be noted that
// `saved_tab_groups` must already be in sorted order for this function to
// work as intended. To do this, UpdatePositionsImpl() can be called.
void InsertGroupImpl(SavedTabGroup group);
// Implementations of CRUD operations.
SavedTabGroup RemoveImpl(size_t index);
void UpdateVisualDataImpl(int index,
const tab_groups::TabGroupVisualData* visual_data);
// Pending NTP related operations. Pending NTP is a placeholder NTP
// automatically created when a group from sync reaches zero-tabs state to
// make it easier for UI to handle since UI today doesn't support zero-tab tab
// groups in any platform. Zero-tab state is a valid transient state since
// concurrent tab additions and removals are common in shared tab groups. A
// pending NTP exists locally in the model and the UI, but not synced. Any
// incoming / outgoing navigations or tab additions will commit this tab to
// sync. There can only be one maximum pending NTP in a group and it will be
// the only tab in the group.
void CreatePendingNtp(SavedTabGroup& group);
void StartSyncingPendingNtpIfAny(SavedTabGroup& group);
void MergePendingNtpWithIncomingTabIfAny(SavedTabGroup& group,
const base::Uuid& tab_id);
SavedTabGroupTab* FindPendingNtpInGroup(SavedTabGroup& group);
void HandleTabGroupRemovedFromSync(int index);
// Obsevers of the model.
base::ObserverList<SavedTabGroupModelObserver>::Unchecked observers_;
// True when SavedTabGroupModel::LoadStoredEntries has finished, false
// otherwise.
bool is_loaded_ = false;
// Storage of all saved tab groups in the order they are displayed. The
// position of the groups must maintain sorted order as sync may not propagate
// an entire update completely leaving us with missing groups / gaps between
// the positions.
std::vector<SavedTabGroup> saved_tab_groups_;
};
} // namespace tab_groups
#endif // COMPONENTS_SAVED_TAB_GROUPS_INTERNAL_SAVED_TAB_GROUP_MODEL_H_