| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CHROME_BROWSER_UI_TABS_TAB_STRIP_MODEL_H_ |
| #define CHROME_BROWSER_UI_TABS_TAB_STRIP_MODEL_H_ |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <variant> |
| #include <vector> |
| |
| #include "base/containers/span.h" |
| #include "base/gtest_prod_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/observer_list.h" |
| #include "base/scoped_multi_source_observation.h" |
| #include "base/scoped_observation.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/ui/tabs/tab_model.h" |
| #include "chrome/browser/ui/tabs/tab_strip_scrubbing_metrics.h" |
| #include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h" |
| #include "components/sessions/core/session_id.h" |
| #include "components/tab_groups/tab_group_id.h" |
| #include "components/tab_groups/tab_group_visual_data.h" |
| #include "components/tabs/public/tab_collection.h" |
| #include "components/tabs/public/tab_interface.h" |
| #include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h" |
| #include "ui/base/models/list_selection_model.h" |
| #include "ui/base/page_transition_types.h" |
| #include "ui/gfx/range/range.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #error This file should only be included on desktop. |
| #endif |
| |
| class DraggingTabsSession; |
| class Profile; |
| class TabGroupModel; |
| class TabStripModelDelegate; |
| class TabStripModelObserver; |
| class TabStripServiceImpl; |
| |
| namespace content { |
| class WebContents; |
| } |
| |
| namespace split_tabs { |
| class SplitTabData; |
| class SplitTabVisualData; |
| enum class SplitTabLayout; |
| } |
| |
| namespace tabs { |
| class SplitTabCollection; |
| class TabStripCollection; |
| class TabGroupTabCollection; |
| } |
| |
| class TabGroupModelFactory { |
| public: |
| TabGroupModelFactory(); |
| TabGroupModelFactory(const TabGroupModelFactory&) = delete; |
| TabGroupModelFactory& operator=(const TabGroupModelFactory&) = delete; |
| |
| static TabGroupModelFactory* GetInstance(); |
| std::unique_ptr<TabGroupModel> Create(); |
| }; |
| |
| // Have DetachedTabCollection object as a container of the `collection_` so |
| // client does not need to worry or deal with the collection object. |
| struct DetachedTabCollection { |
| DetachedTabCollection( |
| std::variant<std::unique_ptr<tabs::TabGroupTabCollection>, |
| std::unique_ptr<tabs::SplitTabCollection>> collection, |
| std::optional<int> active_index, |
| bool pinned_); |
| DetachedTabCollection(const DetachedTabCollection&) = delete; |
| DetachedTabCollection& operator=(const DetachedTabCollection&) = delete; |
| ~DetachedTabCollection(); |
| DetachedTabCollection(DetachedTabCollection&&); |
| std::variant<std::unique_ptr<tabs::TabGroupTabCollection>, |
| std::unique_ptr<tabs::SplitTabCollection>> |
| collection_; |
| // Store the index of tab that was active in the detached group. |
| std::optional<int> active_index_ = std::nullopt; |
| bool pinned_ = false; |
| }; |
| |
| // Holds state for a tab that has been detached from the tab strip. |
| // Will also handle tab deletion if `remove_reason` is kDeleted. |
| struct DetachedTab { |
| DetachedTab(int index_before_any_removals, |
| int index_at_time_of_removal, |
| std::unique_ptr<tabs::TabModel> tab, |
| TabStripModelChange::RemoveReason remove_reason, |
| tabs::TabInterface::DetachReason tab_detach_reason, |
| std::optional<SessionID> id); |
| DetachedTab(const DetachedTab&) = delete; |
| DetachedTab& operator=(const DetachedTab&) = delete; |
| ~DetachedTab(); |
| DetachedTab(DetachedTab&&); |
| |
| std::unique_ptr<tabs::TabModel> tab; |
| |
| // The index of the tab in the original selection model of the tab |
| // strip [prior to any tabs being removed, if multiple tabs are being |
| // simultaneously removed]. |
| const int index_before_any_removals; |
| |
| // The index of the tab at the time it is being removed. If multiple |
| // tabs are being simultaneously removed, the index reflects previously |
| // removed tabs in this batch. |
| const int index_at_time_of_removal; |
| |
| // Reasons for detaching a tab. These may differ, for e.g. when a |
| // tab is detached for re-insertion into a browser of different type, |
| // in which case the TabInterface is destroyed but the WebContents is |
| // retained. |
| TabStripModelChange::RemoveReason remove_reason; |
| tabs::TabInterface::DetachReason tab_detach_reason; |
| |
| // The |contents| associated optional SessionID, used as key for |
| // ClosedTabCache. We only cache |contents| if |remove_reason| is kCached. |
| // |
| // TODO(crbug.com/377537302): The ClosedTabCache feature is gone, but it's |
| // unclear if the session ID is needed for other things as well. |
| std::optional<SessionID> id; |
| }; |
| |
| // A feature which wants to show tabstrip-modal UI should call |
| // TabStripController::ShowModalUI and keep alive the instance of |
| // ScopedTabStripModalUI for the duration of the tabstrip-modal UI. |
| class ScopedTabStripModalUI { |
| public: |
| ScopedTabStripModalUI() = default; |
| virtual ~ScopedTabStripModalUI() = default; |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // TabStripModel |
| // |
| // A model & low level controller of a Browser Window tabstrip. Holds a vector |
| // of WebContents, and provides an API for adding, removing and |
| // shuffling them, as well as a higher level API for doing specific Browser- |
| // related tasks like adding new Tabs from just a URL, etc. |
| // |
| // Each tab may be pinned. Pinned tabs are locked to the left side of the tab |
| // strip and rendered differently (small tabs with only a favicon). The model |
| // makes sure all pinned tabs are at the beginning of the tab strip. For |
| // example, if a non-pinned tab is added it is forced to be with non-pinned |
| // tabs. Requests to move tabs outside the range of the tab type are ignored. |
| // For example, a request to move a pinned tab after non-pinned tabs is ignored. |
| // |
| // A TabStripModel has one delegate that it relies on to perform certain tasks |
| // like creating new TabStripModels (probably hosted in Browser windows) when |
| // required. See TabStripDelegate above for more information. |
| // |
| // A TabStripModel also has N observers (see TabStripModelObserver above), |
| // which can be registered via Add/RemoveObserver. An Observer is notified of |
| // tab creations, removals, moves, and other interesting events. The |
| // TabStrip implements this interface to know when to create new tabs in |
| // the View, and the Browser object likewise implements to be able to update |
| // its bookkeeping when such events happen. |
| // |
| // This implementation of TabStripModel is not thread-safe and should only be |
| // accessed on the UI thread. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| class TabStripModel { |
| public: |
| using TabIterator = tabs::TabCollection::TabIterator; |
| using CollectionIterator = tabs::TabCollection::Iterator; |
| |
| // TODO(crbug.com/40881446): Remove this, and use std::optional<size_t> (or at |
| // least std::optional<int>) in its place. |
| static constexpr int kNoTab = -1; |
| |
| TabStripModel() = delete; |
| |
| // Construct a TabStripModel with a delegate to help it do certain things |
| // (see the TabStripModelDelegate documentation). |delegate| cannot be NULL. |
| // the TabGroupModelFactory can be replaced with a nullptr to set the |
| // group_model to null in cases where groups are not supported. |
| explicit TabStripModel(TabStripModelDelegate* delegate, |
| Profile* profile, |
| TabGroupModelFactory* group_model_factory = |
| TabGroupModelFactory::GetInstance()); |
| |
| TabStripModel(const TabStripModel&) = delete; |
| TabStripModel& operator=(const TabStripModel&) = delete; |
| |
| ~TabStripModel(); |
| |
| // Retrieves the TabStripModelDelegate associated with this TabStripModel. |
| TabStripModelDelegate* delegate() const { return delegate_; } |
| |
| // Sets the TabStripModelObserver used by the UI showing the tabs. As other |
| // observers may query the UI for state, the UI's observer must be first. |
| void SetTabStripUI(TabStripModelObserver* observer); |
| |
| // Add and remove observers to changes within this TabStripModel. |
| void AddObserver(TabStripModelObserver* observer); |
| void RemoveObserver(TabStripModelObserver* observer); |
| |
| // Retrieve the number of WebContentses/emptiness of the TabStripModel. |
| int count() const; |
| |
| // TODO(crbug.com/417291958) remove this function since its the same as |
| // count(). |
| int GetTabCount() const; |
| |
| bool empty() const; |
| |
| // Retrieve the Profile associated with this TabStripModel. |
| Profile* profile() const { return profile_; } |
| |
| // Retrieve the index of the currently active WebContents. The only time this |
| // is kNoTab is if the tab strip is being initialized or destroyed. Note that |
| // tab strip destruction is an asynchronous process. |
| int active_index() const { |
| return selection_model_.active().has_value() |
| ? static_cast<int>(selection_model_.active().value()) |
| : kNoTab; |
| } |
| |
| // Returns true if the tabstrip is currently closing all open tabs (via a |
| // call to CloseAllTabs). As tabs close, the selection in the tabstrip |
| // changes which notifies observers, which can use this as an optimization to |
| // avoid doing meaningless or unhelpful work. |
| bool closing_all() const { return closing_all_; } |
| |
| // Basic API ///////////////////////////////////////////////////////////////// |
| |
| // Determines if the specified index is contained within the TabStripModel. |
| bool ContainsIndex(int index) const; |
| |
| // Adds the specified WebContents in the default location. Tabs opened |
| // in the foreground inherit the opener of the previously active tab. |
| // Use of the detached tab is preferred over webcontents, so when possible |
| // use AppendTab instead of this method. |
| void AppendWebContents(std::unique_ptr<content::WebContents> contents, |
| bool foreground); |
| |
| // Adds the specified Tab at the end of the Tabstrip. Tabs opened |
| // in the foreground inherit the opener of the previously active tab and |
| // become the active tab. |
| void AppendTab(std::unique_ptr<tabs::TabModel> tab, bool foreground); |
| |
| // Adds the specified WebContents at the specified location. |
| // |add_types| is a bitmask of AddTabTypes; see it for details. |
| // |
| // All append/insert methods end up in this method. |
| // |
| // NOTE: adding a tab using this method does NOT query the order controller, |
| // as such the ADD_FORCE_INDEX AddTabTypes is meaningless here. The only time |
| // the |index| is changed is if using the index would result in breaking the |
| // constraint that all pinned tabs occur before non-pinned tabs. It returns |
| // the index the web contents is actually inserted to. See also |
| // AddWebContents. |
| int InsertWebContentsAt( |
| int index, |
| std::unique_ptr<content::WebContents> contents, |
| int add_types, |
| std::optional<tab_groups::TabGroupId> group = std::nullopt); |
| |
| // Creates a group object so that group_model can link it with once group |
| // collection owns it. |
| // TODO(crbug.com/392952244): Remove this after replacing callers with |
| // detaching and attaching groups. |
| void AddTabGroup(const tab_groups::TabGroupId group_id, |
| tab_groups::TabGroupVisualData visual_data); |
| |
| // Adds a TabModel from another tabstrip at the specified location. See |
| // InsertWebContentsAt. |
| int InsertDetachedTabAt( |
| int index, |
| std::unique_ptr<tabs::TabModel> tab, |
| int add_types, |
| std::optional<tab_groups::TabGroupId> group = std::nullopt); |
| |
| // Removes the group collection from the collection hierarchy and passes it to |
| // the client. The client can re-insert into another tabstrip using |
| // `InsertDetachedGroupAt` without destroying the group. |
| std::unique_ptr<DetachedTabCollection> DetachTabGroupForInsertion( |
| const tab_groups::TabGroupId group_id); |
| |
| // Inserts a detached tab group into the tabstrip starting at `index`. |
| gfx::Range InsertDetachedTabGroupAt( |
| std::unique_ptr<DetachedTabCollection> group, |
| int index); |
| |
| // Removes the split collection from the collection hierarchy and passes it to |
| // the client. The client can re-insert into another tabstrip using |
| // `InsertDetachedSplitTabAt` without destroying the split. |
| std::unique_ptr<DetachedTabCollection> DetachSplitTabForInsertion( |
| const split_tabs::SplitTabId split_id); |
| |
| // Inserts a detached split tab into the tabstrip starting at `index`. |
| // `pinned` and `group` information are used to insert it in the right place |
| // in the collection hierarchy. |
| gfx::Range InsertDetachedSplitTabAt( |
| std::unique_ptr<DetachedTabCollection> split, |
| int index, |
| bool pinned, |
| std::optional<tab_groups::TabGroupId> group_id = std::nullopt); |
| |
| // Closes the WebContents at the specified index. This causes the |
| // WebContents to be destroyed, but it may not happen immediately. |
| // |close_types| is a bitmask of CloseTypes. |
| // TODO(crbug.com/392950857): Currently many call sites of CloseWebContentsAt |
| // convert a tab/webcontents to an index, which gets converted back to a |
| // webcontents within this function. Provide a CloseWebContents function that |
| // directly closes a web contents so that we don't have to convert back and |
| // forth. |
| void CloseWebContentsAt(int index, uint32_t close_types); |
| |
| // Discards the WebContents at |index| and replaces it with |new_contents|. |
| // The WebContents that was at |index| is returned and its ownership returns |
| // to the caller. |
| std::unique_ptr<content::WebContents> DiscardWebContentsAt( |
| int index, |
| std::unique_ptr<content::WebContents> new_contents); |
| |
| // Detaches the tab at the specified index for reinsertion into another tab |
| // strip. Returns the detached tab. |
| std::unique_ptr<tabs::TabModel> DetachTabAtForInsertion(int index); |
| |
| // Detaches the WebContents at the specified index for re-insertion into |
| // another browser of a different type, destroying the owning TabModel in the |
| // process. |
| // |
| // This works as follows: |
| // - the contents is extracted from the source browser and the owning tab is |
| // destroyed (performed by DetachWebContentsAtForInsertion()) |
| // - the contents is added to the new browser, creating a new tab model |
| // |
| // TODO(crbug.com/334281979): This is done to avoid TabFeatures having to deal |
| // with changing browser types during tab moves. This should no longer be |
| // necessary once non-normal browser windows do not use Browser, TabStripModel |
| // or TabModel. |
| std::unique_ptr<content::WebContents> DetachWebContentsAtForInsertion( |
| int index); |
| |
| // Detaches the WebContents at the specified index and immediately deletes it. |
| void DetachAndDeleteWebContentsAt(int index); |
| |
| // Makes the tab at the specified index the active tab. |gesture_detail.type| |
| // contains the gesture type that triggers the tab activation. |
| // |gesture_detail.time_stamp| contains the timestamp of the user gesture, if |
| // any. |
| void ActivateTabAt( |
| int index, |
| TabStripUserGestureDetails gesture_detail = TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kNone)); |
| |
| // Move the WebContents at the specified index to another index. This |
| // method does NOT send Detached/Attached notifications, rather it moves the |
| // WebContents inline and sends a Moved notification instead. |
| // EnsureGroupContiguity() is called after the move, so this will never result |
| // in non-contiguous group (though the moved tab's group may change). |
| // If |select_after_move| is false, whatever tab was selected before the move |
| // will still be selected, but its index may have incremented or decremented |
| // one slot. It returns the index the web contents is actually moved to. |
| int MoveWebContentsAt(int index, int to_position, bool select_after_move); |
| |
| // This is similar to `MoveWebContentsAt` but takes in an additional `group` |
| // parameter that the tab is assigned to with the move. This does not make a |
| // best case effort to ensure group contiguity and would rather CHECK if it |
| // breaks group contiguity. |
| int MoveWebContentsAt(int index, |
| int to_position, |
| bool select_after_move, |
| std::optional<tab_groups::TabGroupId> group); |
| |
| // Moves the selected tabs to |index|. |index| is treated as if the tab strip |
| // did not contain any of the selected tabs. For example, if the tabstrip |
| // contains [A b c D E f] (upper case selected) and this is invoked with 1 the |
| // result is [b A D E c f]. |
| // This method maintains that all pinned tabs occur before non-pinned tabs. |
| // When pinned tabs are selected the move is processed in two chunks: first |
| // pinned tabs are moved, then non-pinned tabs are moved. If the index is |
| // after (pinned-tab-count - selected-pinned-tab-count), then the index the |
| // non-pinned selected tabs are moved to is (index + |
| // selected-pinned-tab-count). For example, if the model consists of |
| // [A b c D E f] (A b c are pinned) and this is invoked with 2, the result is |
| // [b c A D E f]. In this example nothing special happened because the target |
| // index was <= (pinned-tab-count - selected-pinned-tab-count). If the target |
| // index were 3, then the result would be [b c A f D F]. A, being pinned, can |
| // move no further than index 2. The non-pinned tabs are moved to the target |
| // index + selected-pinned tab-count (3 + 1). |
| void MoveSelectedTabsTo(int index, |
| std::optional<tab_groups::TabGroupId> group); |
| |
| // Moves all tabs in `group` to `to_index`. This has no checks to make sure |
| // the position is valid for a group to move to. |
| void MoveGroupTo(const tab_groups::TabGroupId& group, int to_index); |
| |
| // Moves all tabs in split with `split_id` to `to_index` with properties |
| // `pinned` and `group_id`. This has no checks to make sure the position is |
| // valid for a split to move to. |
| void MoveSplitTo(const split_tabs::SplitTabId& split_id, |
| int to_index, |
| bool pinned, |
| std::optional<tab_groups::TabGroupId> group_id); |
| |
| // Returns the currently active WebContents, or NULL if there is none. |
| content::WebContents* GetActiveWebContents() const; |
| |
| // Returns the currently active Tab, or NULL if there is none. |
| tabs::TabInterface* GetActiveTab() const; |
| |
| // Returns the WebContents at the specified index, or NULL if there is |
| // none. |
| content::WebContents* GetWebContentsAt(int index) const; |
| |
| // Returns the index of the specified WebContents, or TabStripModel::kNoTab |
| // if the WebContents is not in this TabStripModel. |
| int GetIndexOfWebContents(const content::WebContents* contents) const; |
| |
| // Notify any observers that the tab has changed in some way. See |
| // TabChangeType for details of |change_type|.' |
| void NotifyTabChanged(const tabs::TabInterface* const tab, |
| TabChangeType change_type); |
| |
| // Notify any observers that the WebContents at the specified index has |
| // changed in some way. See TabChangeType for details of |change_type|. |
| void UpdateWebContentsStateAt(int index, TabChangeType change_type); |
| |
| // Cause a tab to display a UI indication to the user that it needs their |
| // attention. |
| void SetTabNeedsAttentionAt(int index, bool attention); |
| |
| // Close all tabs at once. Code can use closing_all() above to defer |
| // operations that might otherwise by invoked by the flurry of detach/select |
| // notifications this method causes. |
| void CloseAllTabs(); |
| |
| // Close all tabs in the given |group| at once. |
| void CloseAllTabsInGroup(const tab_groups::TabGroupId& group); |
| |
| // Returns true if there are any WebContentses that are currently loading |
| // and should be shown on the UI. |
| bool TabsNeedLoadingUI() const; |
| |
| // Returns the WebContents that opened the WebContents at |index|, or NULL if |
| // there is no opener on record. |
| tabs::TabInterface* GetOpenerOfTabAt(const int index) const; |
| |
| // Changes the |opener| of the WebContents at |index|. |
| // Note: |opener| must be in this tab strip. Also a tab must not be its own |
| // opener. |
| void SetOpenerOfWebContentsAt(int index, content::WebContents* opener); |
| |
| // Returns the index of the last WebContents in the model opened by the |
| // specified opener, starting at |start_index|. |
| int GetIndexOfLastWebContentsOpenedBy(const content::WebContents* opener, |
| int start_index) const; |
| |
| // To be called when a navigation is about to occur in the specified |
| // WebContents. Depending on the tab, and the transition type of the |
| // navigation, the TabStripModel may adjust its selection behavior and opener |
| // inheritance. |
| void TabNavigating(content::WebContents* contents, |
| ui::PageTransition transition); |
| |
| // Changes the blocked state of the tab at |index|. |
| void SetTabBlocked(int index, bool blocked); |
| |
| // Changes the pinned state of the tab at `index`. See description above |
| // class for details on this. Returns the index the tab is now at (it may have |
| // been moved to maintain contiguity of pinned tabs at the beginning of the |
| // tabstrip).) |
| int SetTabPinned(int index, bool pinned); |
| |
| // Returns true if the tab at |index| is pinned. |
| // See description above class for details on pinned tabs. |
| bool IsTabPinned(int index) const; |
| |
| bool IsTabCollapsed(int index) const; |
| |
| bool IsGroupCollapsed(const tab_groups::TabGroupId& group) const; |
| |
| // Returns true if the tab at |index| is blocked by a tab modal dialog. |
| bool IsTabBlocked(int index) const; |
| |
| // Returns true if the tab at |index| is allowed to be closed. |
| bool IsTabClosable(int index) const; |
| |
| // Returns true if the tab corresponding to |contents| is allowed to be |
| // closed. |
| bool IsTabClosable(const content::WebContents* contents) const; |
| |
| split_tabs::SplitTabData* GetSplitData(split_tabs::SplitTabId split_id) const; |
| |
| bool ContainsSplit(split_tabs::SplitTabId split_id) const; |
| |
| std::optional<split_tabs::SplitTabId> GetSplitForTab(int index) const; |
| |
| // Returns the group that contains the tab at |index|, or nullopt if the tab |
| // index is invalid or not grouped. |
| std::optional<tab_groups::TabGroupId> GetTabGroupForTab(int index) const; |
| |
| // If a tab inserted at |index| would be within a tab group, return that |
| // group's ID. Otherwise, return nullopt. If |index| points to the first tab |
| // in a group, it will return nullopt since a new tab would be either between |
| // two different groups or just after a non-grouped tab. |
| std::optional<tab_groups::TabGroupId> GetSurroundingTabGroup(int index) const; |
| |
| // Returns the index of the first tab that is not a pinned tab. This returns |
| // |count()| if all of the tabs are pinned tabs, and 0 if none of the tabs are |
| // pinned tabs. |
| int IndexOfFirstNonPinnedTab() const; |
| |
| // Extends the selection from the anchor to |index|. |
| void ExtendSelectionTo(int index); |
| |
| // This can fail if the tabstrip is not editable. |
| void SelectTabAt(int index); |
| |
| // This can fail if the tabstrip is not editable. |
| void DeselectTabAt(int index); |
| |
| // Makes sure the tabs from the anchor to |index| are selected. This adds to |
| // the selection if there is an anchor and resets the selection to |index| if |
| // there is not an anchor. |
| void AddSelectionFromAnchorTo(int index); |
| |
| // Returns true if the tab at |index| is selected. |
| bool IsTabSelected(int index) const; |
| |
| // Sets the selection to match that of |source|. |
| void SetSelectionFromModel(ui::ListSelectionModel source); |
| |
| const ui::ListSelectionModel& selection_model() const; |
| |
| // Features that want to show tabstrip-modal UI are mutually exclusive. |
| // Before showing a modal UI first check `CanShowModalUI`. Then call |
| // ShowModalUI() and keep `ScopedTabStripModal` alive to prevent other |
| // features from showing tabstrip-modal UI. |
| bool CanShowModalUI() const; |
| std::unique_ptr<ScopedTabStripModalUI> ShowModalUI(); |
| void ForceShowingModalUIForTesting(bool showing); |
| |
| // Command level API ///////////////////////////////////////////////////////// |
| |
| // Adds a WebContents at the best position in the TabStripModel given |
| // the specified insertion index, transition, etc. |add_types| is a bitmask of |
| // AddTabTypes; see it for details. This method ends up calling into |
| // InsertWebContentsAt to do the actual insertion. Pass kNoTab for |index| to |
| // append the contents to the end of the tab strip. |
| void AddWebContents( |
| std::unique_ptr<content::WebContents> contents, |
| int index, |
| ui::PageTransition transition, |
| int add_types, |
| std::optional<tab_groups::TabGroupId> group = std::nullopt); |
| void AddTab(std::unique_ptr<tabs::TabModel> tab, |
| int index, |
| ui::PageTransition transition, |
| int add_types, |
| std::optional<tab_groups::TabGroupId> group = std::nullopt); |
| |
| // Closes the selected tabs. |
| void CloseSelectedTabs(); |
| |
| // Select adjacent tabs |
| void SelectNextTab( |
| TabStripUserGestureDetails detail = TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| void SelectPreviousTab( |
| TabStripUserGestureDetails detail = TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| // Selects the last tab in the tab strip. |
| void SelectLastTab( |
| TabStripUserGestureDetails detail = TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| |
| // Moves the active in the specified direction. Respects group boundaries. |
| void MoveTabNext(); |
| void MoveTabPrevious(); |
| |
| // This is used by the `tab_drag_controller` to help precompute the group the |
| // selected tabs would be a part of if they moved to a destination index. It |
| // returns the index of the tabs in the current model that would end up being |
| // the adjacent tabs of the selected unpinned tabs post move operation. |
| std::pair<std::optional<int>, std::optional<int>> |
| GetAdjacentTabsAfterSelectedMove(base::PassKey<DraggingTabsSession>, |
| int destination_index); |
| |
| // Updates the layout for the tabs with `split_id` and notifies observers. |
| void UpdateSplitLayout(split_tabs::SplitTabId split_id, |
| split_tabs::SplitTabLayout tab_layout); |
| |
| // Updates the ratio for the tabs with `split_id` and notifies observers. |
| void UpdateSplitRatio(split_tabs::SplitTabId split_id, |
| double start_content_ratio); |
| |
| // Updates the active tab within `split_id` with the tab at `update_index`. |
| enum class SplitUpdateType { kReplace, kSwap }; |
| void UpdateActiveTabInSplit(split_tabs::SplitTabId split_id, |
| int update_index, |
| SplitUpdateType update_type); |
| |
| // Reverses the order of tabs with `split_id`. |
| void ReverseTabsInSplit(split_tabs::SplitTabId split_id); |
| |
| // Create a new split view with the active tab and add the set of tabs pointed |
| // to by |indices| to it. Reorders the tabs so they are contiguous. |indices| |
| // must be sorted in ascending order. |
| split_tabs::SplitTabId AddToNewSplit( |
| const std::vector<int> indices, |
| split_tabs::SplitTabVisualData visual_data); |
| |
| // Create a new tab group and add the set of tabs pointed to be |indices| to |
| // it. Pins all of the tabs if any of them were pinned, and reorders the tabs |
| // so they are contiguous and do not split an existing group in half. Returns |
| // the new group. |indices| must be sorted in ascending order. |
| tab_groups::TabGroupId AddToNewGroup(const std::vector<int> indices); |
| |
| // Add the set of tabs pointed to by |indices| to the given tab group |group|. |
| // The tabs take on the pinnedness of the tabs already in the group. Tabs |
| // before the group will move to the start, while tabs after the group will |
| // move to the end. If |add_to_end| is true, all tabs will instead move to |
| // the end. |indices| must be sorted in ascending order. |
| void AddToExistingGroup(const std::vector<int> indices, |
| const tab_groups::TabGroupId group, |
| const bool add_to_end = false); |
| |
| // Similar to AddToExistingGroup(), but creates a group with id |group| if it |
| // doesn't exist. This is only intended to be called from session restore |
| // code. |
| void AddToGroupForRestore(const std::vector<int>& indices, |
| const tab_groups::TabGroupId& group); |
| |
| // Removes the set of tabs pointed to by |indices| from the the groups they |
| // are in, if any. The tabs are moved out of the group if necessary. |indices| |
| // must be sorted in ascending order. |
| void RemoveFromGroup(const std::vector<int>& indices); |
| |
| // Unsplits all the tabs that are part of the split with `split_id`. The tabs |
| // maintain their group and pin properties. |
| void RemoveSplit(split_tabs::SplitTabId split_id); |
| |
| TabGroupModel* group_model() const { return group_model_.get(); } |
| |
| bool SupportsTabGroups() const { return group_model_.get() != nullptr; } |
| |
| // Returns true if one or more of the tabs pointed to by |indices| are |
| // supported by read later. |
| bool IsReadLaterSupportedForAny(const std::vector<int>& indices); |
| |
| // Saves tabs with url supported by Read Later. |
| void AddToReadLater(const std::vector<int>& indices); |
| |
| // Notifies all group observers that the TabGroupEditor is opening. This is |
| // used by Views that want to force the editor to open without having to find |
| // the group's header view in the Tab Strip. |
| void OpenTabGroupEditor(const tab_groups::TabGroupId& group); |
| |
| // Updates the group visuals and notifies observers. |
| void ChangeTabGroupVisuals(const tab_groups::TabGroupId& group, |
| tab_groups::TabGroupVisualData visual_data, |
| bool is_customized = false); |
| |
| // Returns iterators for traversing through all the tabs in the tabstrip. |
| TabIterator begin() const; |
| TabIterator end() const; |
| |
| CollectionIterator collection_begin( |
| base::PassKey<TabStripServiceImpl> key) const; |
| CollectionIterator collection_end( |
| base::PassKey<TabStripServiceImpl> key) const; |
| |
| // View API ////////////////////////////////////////////////////////////////// |
| |
| // Context menu functions. Tab groups uses command ids following CommandLast |
| // for entries in the 'Add to existing group' submenu. |
| enum ContextMenuCommand { |
| CommandFirst, |
| CommandNewTabToRight, |
| CommandReload, |
| CommandDuplicate, |
| CommandCloseTab, |
| CommandCloseOtherTabs, |
| CommandCloseTabsToRight, |
| CommandTogglePinned, |
| CommandToggleGrouped, |
| CommandToggleSiteMuted, |
| CommandSendTabToSelf, |
| CommandAddNote, |
| CommandAddToReadLater, |
| CommandAddToNewGroup, |
| CommandAddToExistingGroup, |
| CommandAddToNewGroupFromMenuItem, |
| CommandAddToNewComparisonTable, |
| CommandAddToExistingComparisonTable, |
| CommandAddToSplit, |
| CommandSwapWithActiveSplit, |
| CommandArrangeSplit, |
| CommandRemoveFromGroup, |
| CommandMoveToExistingWindow, |
| CommandMoveTabsToNewWindow, |
| CommandOrganizeTabs, |
| CommandCopyURL, |
| CommandGoBack, |
| CommandCloseAllTabs, |
| CommandCommerceProductSpecifications, |
| CommandLast |
| }; |
| |
| // Returns true if the specified command is enabled. If |context_index| is |
| // selected the response applies to all selected tabs. |
| bool IsContextMenuCommandEnabled(int context_index, |
| ContextMenuCommand command_id) const; |
| |
| // Performs the action associated with the specified command for the given |
| // TabStripModel index |context_index|. If |context_index| is selected the |
| // command applies to all selected tabs. |
| void ExecuteContextMenuCommand(int context_index, |
| ContextMenuCommand command_id); |
| |
| // Returns a list of the group ids that are going to be deleted if a given |
| // list of tab indexes are removed from the group. used by context menu |
| // commands to decide whether to confirm group deletion. |
| std::vector<tab_groups::TabGroupId> GetGroupsDestroyedFromRemovingIndices( |
| const std::vector<int>& indices) const; |
| |
| // This should be called after GetGroupsDestroyedFromRemovingIndices(). Marks |
| // all groups in `group_ids` as closing. This is useful in the event you need |
| // to know if a group is currently closing or not such as when a grouped tab |
| // is closed which has an unload handler. |
| void MarkTabGroupsForClosing( |
| const std::vector<tab_groups::TabGroupId> group_ids); |
| |
| // There are multiple commands that close by indices. They all must check the |
| // Group affiliation of the indices, confirm that they can delete groups, and |
| // then perform the close of the indices. When true `delete_groups` also |
| // deletes any saved groups that are closing. When false, groups will close |
| // normally but continue to be saved. |
| void ExecuteCloseTabsByIndicesCommand( |
| base::RepeatingCallback<std::vector<int>()> get_indices_to_close, |
| bool delete_groups); |
| |
| // Adds the tab at |context_index| to the given tab group |group|. If |
| // |context_index| is selected the command applies to all selected tabs. |
| void ExecuteAddToExistingGroupCommand(int context_index, |
| const tab_groups::TabGroupId& group); |
| |
| // Adds the tab at |context_index| to the browser window at |browser_index|. |
| // If |context_index| is selected the command applies to all selected tabs. |
| void ExecuteAddToExistingWindowCommand(int context_index, int browser_index); |
| |
| // Returns true if 'CommandToggleSiteMuted' will mute. |index| is the |
| // index supplied to |ExecuteContextMenuCommand|. |
| bool WillContextMenuMuteSites(int index); |
| |
| // Returns true if 'CommandTogglePinned' will pin. |index| is the index |
| // supplied to |ExecuteContextMenuCommand|. |
| bool WillContextMenuPin(int index); |
| |
| // Returns true if 'CommandToggleGrouped' will group. |index| is the index |
| // supplied to |ExecuteContextMenuCommand|. |
| bool WillContextMenuGroup(int index); |
| |
| // Convert a ContextMenuCommand into a browser command. Returns true if a |
| // corresponding browser command exists, false otherwise. |
| static bool ContextMenuCommandToBrowserCommand(int cmd_id, int* browser_cmd); |
| |
| // Returns the index of the next tab spawned by the specified tabs in |
| // `block_tab_range`. |
| int GetIndexOfNextWebContentsOpenedBy( |
| const gfx::Range& block_tab_range) const; |
| |
| // Returns the index of the next tab spawned by the opener of the specified |
| // tabs in `block_tab_range`. |
| int GetIndexOfNextWebContentsOpenedByOpenerOf( |
| const gfx::Range& block_tab_range) const; |
| |
| // Finds the next available tab to switch to as the active tab starting at |
| // a block of tabs. The methods will check the indices to |
| // the right of the block before checking the indices to the left of the |
| // block. Index within the block cannot be returned. Returns std::nullopt if |
| // there are no valid tabs. |
| std::optional<int> GetNextExpandedActiveTab( |
| const gfx::Range& block_tab_range) const; |
| std::optional<int> GetNextExpandedActiveTab( |
| tab_groups::TabGroupId collapsing_group) const; |
| |
| // Forget all opener relationships, to reduce unpredictable tab switching |
| // behavior in complex session states. |
| void ForgetAllOpeners(); |
| |
| // Forgets the opener relationship of the specified WebContents. |
| void ForgetOpener(content::WebContents* contents); |
| |
| // Determine where to place a newly opened tab by using the supplied |
| // transition and foreground flag to figure out how it was opened. |
| int DetermineInsertionIndex(ui::PageTransition transition, bool foreground); |
| |
| // If a tab is in a group and the tab failed to close, this method will be |
| // called from the unload_controller. Ungroup the group to maintain |
| // consistency with the user's intended action (to get rid of the group). |
| void GroupCloseStopped(const tab_groups::TabGroupId& group); |
| |
| // Serialise this object into a trace. |
| void WriteIntoTrace(perfetto::TracedValue context) const; |
| |
| // Convert between tabs and indices. |
| int GetIndexOfTab(const tabs::TabInterface* tab) const; |
| tabs::TabInterface* GetTabAtIndex(int index) const; |
| |
| // TODO(349161508) remove this method once tabs dont need to be converted |
| // into webcontents. |
| tabs::TabInterface* GetTabForWebContents( |
| const content::WebContents* contents) const; |
| |
| // Returns [start, end) where the leftmost tab in the split has index start |
| // and the rightmost tab in the split has index end - 1. |
| gfx::Range GetIndexRangeOfSplit(split_tabs::SplitTabId split_id) const; |
| |
| private: |
| FRIEND_TEST_ALL_PREFIXES(TabStripModelTest, GetIndicesClosedByCommand); |
| |
| struct DetachNotifications; |
| struct MoveNotification { |
| int initial_index; |
| std::optional<tab_groups::TabGroupId> intial_group; |
| bool initial_pinned; |
| raw_ptr<const tabs::TabInterface> tab; |
| TabStripSelectionChange selection_change; |
| }; |
| |
| // Tracks whether a tabstrip-modal UI is showing. |
| class ScopedTabStripModalUIImpl : public ScopedTabStripModalUI { |
| public: |
| explicit ScopedTabStripModalUIImpl(TabStripModel* model); |
| ~ScopedTabStripModalUIImpl() override; |
| |
| private: |
| // Owns this. |
| raw_ptr<TabStripModel> model_; |
| }; |
| |
| tabs::TabModel* GetTabModelAtIndex(int index) const; |
| |
| // Perform tasks associated with changes to the model. Change the Active Index |
| // and notify observers. |
| void OnChange(const TabStripModelChange& change, |
| const TabStripSelectionChange& selection); |
| |
| // Notify observers that a `group` was created. |
| void NotifyTabGroupVisualsChanged(const tab_groups::TabGroupId& group_id, |
| TabGroupChange::VisualsChange visuals); |
| |
| // Notify observers that a `group` was created. |
| void NotifyTabGroupCreated(const tab_groups::TabGroupId& group); |
| |
| // Notify observers that a `group` was closed. |
| void NotifyTabGroupClosed(const tab_groups::TabGroupId& group); |
| |
| // Notify observers that `group` is moved. |
| void NotifyTabGroupMoved(const tab_groups::TabGroupId& group); |
| |
| // Notify observers that `group` is detached from the model. This also sends |
| // split related observations within the group. |
| void NotifyTabGroupDetached( |
| tabs::TabGroupTabCollection* group_collection, |
| std::map<split_tabs::SplitTabId, |
| std::vector<std::pair<tabs::TabInterface*, int>>> |
| splits_in_group); |
| |
| // Notify observers that `group` is attached to the model. This also sends |
| // split related observations within the group. |
| void NotifyTabGroupAttached(tabs::TabGroupTabCollection* group_collection); |
| |
| // Notify observers that split with `split_id` has been created. |
| void NotifySplitTabCreated( |
| split_tabs::SplitTabId split_id, |
| const std::vector<std::pair<tabs::TabInterface*, int>>& tabs_with_indices, |
| SplitTabChange::SplitTabAddReason reason, |
| const split_tabs::SplitTabVisualData& visual_data); |
| |
| // Notify observers that visual data for a split has changed. |
| void NotifySplitTabVisualsChanged( |
| split_tabs::SplitTabId split_id, |
| const split_tabs::SplitTabVisualData& old_visual_data, |
| const split_tabs::SplitTabVisualData& new_visual_data); |
| |
| // Notify observers that contents of a split has been reordered. |
| void NotifySplitTabContentsUpdated( |
| split_tabs::SplitTabId split_id, |
| const std::vector<std::pair<tabs::TabInterface*, int>>& prev_tabs, |
| const std::vector<std::pair<tabs::TabInterface*, int>>& new_tabs); |
| |
| // Notify observers that split with `split_id` has been removed. |
| void NotifySplitTabRemoved( |
| split_tabs::SplitTabId split_id, |
| const std::vector<std::pair<tabs::TabInterface*, int>>& tabs_with_indices, |
| SplitTabChange::SplitTabRemoveReason reason); |
| |
| // Notify observers that a split was detached from this tabstrip model. |
| // This also sends any group related notification. |
| void NotifySplitTabDetached( |
| tabs::SplitTabCollection* split_collection, |
| std::vector<std::pair<tabs::TabInterface*, int>> tabs_in_split, |
| std::optional<tab_groups::TabGroupId> previous_group_state); |
| |
| // Notify observers that a split was attached to this tabstrip model. |
| // This also sends any group related notification. |
| void NotifySplitTabAttached(tabs::SplitTabCollection* split_collection); |
| |
| // Detaches the tab at the specified `index` from this strip. |
| // `web_contents_remove_reason` is used to indicate to observers what is going |
| // to happen to the WebContents (i.e. deleted or reinserted into another tab |
| // strip). `tab_detach_reason` is used to indicate to observers what is going |
| // to happen to the TabModel owning the WebContents. These reasons may not |
| // always match (a WebContents may be retained for re-insertion while its |
| // owning TabModel may be destroyed). |
| std::unique_ptr<DetachedTab> DetachTabWithReasonAt( |
| int index, |
| TabStripModelChange::RemoveReason web_contents_remove_reason, |
| tabs::TabInterface::DetachReason tab_detach_reason); |
| |
| // Performs all the work to detach a TabModel instance but avoids sending |
| // most notifications. TabClosingAt() and TabDetachedAt() are sent because |
| // observers are reliant on the selection model being accurate at the time |
| // that TabDetachedAt() is called. |
| std::unique_ptr<DetachedTab> DetachTabImpl( |
| int index_before_any_removals, |
| int index_at_time_of_removal, |
| bool create_historical_tab, |
| TabStripModelChange::RemoveReason web_contents_remove_reason, |
| tabs::TabInterface::DetachReason tab_detach_reason); |
| |
| // Removes a tab collection from `contents_data_` using |
| // `execute_detach_collection_operation`. Also sends collection specific |
| // observation using `execute_tabs_notify_observer_operation` like group and |
| // split related observation calls. `TabStripModelChange` and |
| // `TabStripSelectionChange` observation calls are handled as common code. |
| std::unique_ptr<tabs::TabCollection> DetachTabCollectionImpl( |
| tabs::TabCollection* collection, |
| base::OnceCallback<std::unique_ptr<tabs::TabCollection>()> |
| execute_detach_collection_operation, |
| base::OnceClosure execute_tabs_notify_observer_operation); |
| |
| // Helper method performing tasks like notification, fixing opener and |
| // returning back a Remove struct before actually detaching the set of |
| // tab_indices. |
| TabStripModelChange::Remove ProcessTabsForDetach(gfx::Range tab_indices); |
| |
| // Helper method for updating the selection model after detaching a collection |
| // from `contents_data_`. |
| void UpdateSelectionModelForDetach(gfx::Range tab_indices, |
| std::optional<int> next_selected_index); |
| |
| // Attaches a tab collection to `contents_data_` using |
| // `execute_insert_detached_tabs_operation`. Also sends collection specific |
| // observation using `execute_tabs_notify_observer_operation` like group and |
| // split related observation calls. `TabStripModelChange` and |
| // `TabStripSelectionChange` observation calls are handled as common code. |
| gfx::Range InsertDetachedCollectionImpl( |
| tabs::TabCollection* collection, |
| std::optional<int> active_index, |
| base::OnceClosure execute_insert_detached_tabs_operation, |
| base::OnceClosure execute_tabs_notify_observer_operation); |
| |
| // This is the callback used as `execute_insert_detached_tabs_operation` in |
| // `InsertDetachedCollectionImpl` when a group is inserted into a tabstrip. It |
| // updates the `group_model_` and inserts the `group_collection` into |
| // `contents_data_`. |
| void InsertDetachedTabGroupImpl( |
| std::unique_ptr<tabs::TabGroupTabCollection> group_collection, |
| int index); |
| |
| // We batch send notifications. This has two benefits: |
| // 1) This allows us to send the minimal number of necessary notifications. |
| // This is important because some notifications cause the main thread to |
| // synchronously communicate with the GPU process and cause jank. |
| // https://crbug.com/826287. |
| // 2) This allows us to avoid some problems caused by re-entrancy [e.g. |
| // using destroyed WebContents instances]. Ideally, this second check |
| // wouldn't be necessary because we would enforce that there is no |
| // re-entrancy in the TabStripModel, but that condition is currently |
| // violated in tests [and possibly in the wild as well]. |
| void SendDetachWebContentsNotifications(DetachNotifications* notifications); |
| |
| bool RunUnloadListenerBeforeClosing(content::WebContents* contents); |
| bool ShouldRunUnloadListenerBeforeClosing(content::WebContents* contents); |
| |
| int ConstrainInsertionIndex(int index, bool pinned_tab) const; |
| |
| int ConstrainMoveIndex(int index, bool pinned_tab) const; |
| |
| // If |index| is selected all the selected indices are returned, otherwise a |
| // vector with |index| is returned. This is used when executing commands to |
| // determine which indices the command applies to. Indices are sorted in |
| // increasing order. |
| std::vector<int> GetIndicesForCommand(int index) const; |
| |
| // Returns a vector of indices of the tabs that will close when executing the |
| // command |id| for the tab at |index|. The returned indices are sorted in |
| // descending order. |
| std::vector<int> GetIndicesClosedByCommand(int index, |
| ContextMenuCommand id) const; |
| |
| // Returns true if the specified WebContents is a New Tab at the end of |
| // the tabstrip. We check for this because opener relationships are _not_ |
| // forgotten for the New Tab page opened as a result of a New Tab gesture |
| // (e.g. Ctrl+T, etc) since the user may open a tab transiently to look up |
| // something related to their current activity. |
| bool IsNewTabAtEndOfTabStrip(content::WebContents* contents) const; |
| |
| // Adds the specified TabModel at the specified location. |
| // |add_types| is a bitmask of AddTabTypes; see it for details. |
| // |
| // All append/insert methods end up in this method. |
| // |
| // NOTE: adding a tab using this method does NOT query the order controller, |
| // as such the ADD_FORCE_INDEX AddTabTypes is meaningless here. The only time |
| // the |index| is changed is if using the index would result in breaking the |
| // constraint that all pinned tabs occur before non-pinned tabs. It returns |
| // the index the tab is actually inserted to. See also AddWebContents. |
| int InsertTabAtImpl(int index, |
| std::unique_ptr<tabs::TabModel> tab, |
| int add_types, |
| std::optional<tab_groups::TabGroupId> group); |
| |
| // Closes the WebContentses at the specified indices. This causes the |
| // WebContentses to be destroyed, but it may not happen immediately. If |
| // the page in question has an unload event the WebContents will not be |
| // destroyed until after the event has completed, which will then call back |
| // into this method. |
| void CloseTabs(base::span<content::WebContents* const> items, |
| uint32_t close_types); |
| |
| // Executes a call to CloseTabs on the web contentses contained in tabs |
| // returned from |get_indices_to_close|. This is a helper method |
| // bound by ExecuteCloseTabsByIndicesCommand in order to properly |
| // protect the stack from reentrancy. |
| void ExecuteCloseTabsByIndices( |
| base::RepeatingCallback<std::vector<int>()> get_indices_to_close, |
| uint32_t close_types); |
| |
| // |close_types| is a bitmask of the types in CloseTypes. |
| // Returns true if all the tabs have been deleted. A return value of false |
| // means some portion (potentially none) of the WebContents were deleted. |
| // WebContents not deleted by this function are processing unload handlers |
| // which may eventually be deleted based on the results of the unload handler. |
| // Additionally processing the unload handlers may result in needing to show |
| // UI for the WebContents. See UnloadController for details on how unload |
| // handlers are processed. |
| bool CloseWebContentses(base::span<content::WebContents* const> items, |
| uint32_t close_types, |
| DetachNotifications* notifications); |
| |
| // Returns the WebContentses at the specified indices. This does no checking |
| // of the indices, it is assumed they are valid. |
| std::vector<content::WebContents*> GetWebContentsesByIndices( |
| const std::vector<int>& indices) const; |
| |
| // Sets the selection to |new_model| and notifies any observers. |
| // Note: This function might end up sending 0 to 3 notifications in the |
| // following order: TabDeactivated, ActiveTabChanged, TabSelectionChanged. |
| // |selection| will be filled with information corresponding to 3 notification |
| // above. When it's |triggered_by_other_operation|, This won't notify |
| // observers that selection was changed. Callers should notify it by |
| // themselves. |
| TabStripSelectionChange SetSelection( |
| ui::ListSelectionModel new_model, |
| TabStripModelObserver::ChangeReason reason, |
| bool triggered_by_other_operation); |
| |
| // Direction of relative tab movements or selections. kNext indicates moving |
| // forward (positive increment) in the tab strip. kPrevious indicates |
| // backward (negative increment). |
| enum class TabRelativeDirection { |
| kNext, |
| kPrevious, |
| }; |
| |
| // Selects either the next tab (kNext), or the previous tab (kPrevious). |
| void SelectRelativeTab(TabRelativeDirection direction, |
| TabStripUserGestureDetails detail); |
| |
| // Moves the active tabs into the next slot (kNext), or the |
| // previous slot (kPrevious). Respects group boundaries and creates |
| // movement slots into and out of groups. |
| void MoveTabRelative(TabRelativeDirection direction); |
| |
| // Implementation of MoveSelectedTabsTo. Moves |length| of the selected tabs |
| // starting at |start| to |index|. See MoveSelectedTabsTo for more details. |
| void MoveSelectedTabsToImpl(int index, size_t start, size_t length); |
| |
| std::vector<int> GetSelectedPinnedTabs(); |
| std::vector<int> GetSelectedUnpinnedTabs(); |
| |
| split_tabs::SplitTabId AddToSplitImpl( |
| split_tabs::SplitTabId split_id, |
| std::vector<int> indices, |
| split_tabs::SplitTabVisualData visual_data, |
| SplitTabChange::SplitTabAddReason reasons); |
| |
| void RemoveSplitImpl(split_tabs::SplitTabId split_id, |
| SplitTabChange::SplitTabRemoveReason reason); |
| |
| // Adds tabs to newly-allocated group id |new_group|. This group must be new |
| // and have no tabs in it. |
| void AddToNewGroupImpl( |
| const std::vector<int>& indices, |
| const tab_groups::TabGroupId& new_group, |
| std::optional<tab_groups::TabGroupVisualData> visual_data = std::nullopt); |
| |
| void MoveGroupToImpl(const tab_groups::TabGroupId& group, int to_index); |
| |
| // Adds tabs to existing group |group|. This group must have been initialized |
| // by a previous call to |AddToNewGroupImpl()|. |
| void AddToExistingGroupImpl(const std::vector<int>& indices, |
| const tab_groups::TabGroupId& group, |
| const bool add_to_end = false); |
| |
| // Adds all selected indices provided by `context_index` into a new tab group. |
| void AddToNewGroupFromContextIndex(int context_index); |
| |
| // Implementation of MoveTabsAndSetPropertiesImpl. Moves the set of tabs in |
| // |indices| to the |destination_index| and updates the tabs to the |
| // appropriate |group| and |pinned| properties. |
| // Note: |destination_index| refers to a place in the tabstrip prior to the |
| // move operation. |
| void MoveTabsAndSetPropertiesImpl(const std::vector<int>& indices, |
| int destination_index, |
| std::optional<tab_groups::TabGroupId> group, |
| bool pinned); |
| |
| void AddToReadLaterImpl(const std::vector<int>& indices); |
| |
| // Updates the `contents_data_` and sends out observer notifications for |
| // inserting a new tab in the tabstrip. |
| void InsertTabAtIndexImpl(std::unique_ptr<tabs::TabModel> tab_model, |
| int index, |
| std::optional<tab_groups::TabGroupId> group, |
| bool pin, |
| bool active); |
| |
| // Updates the `contents_data_` and sends out observer notifications for |
| // removing an existing tab in the tabstrip. |
| std::unique_ptr<tabs::TabModel> RemoveTabFromIndexImpl( |
| int index, |
| tabs::TabInterface::DetachReason tab_detach_reason); |
| |
| // Updates the `contents_data_` and sends out observer notifications for |
| // updating the index, pinned state or group property. |
| void MoveTabToIndexImpl(int initial_index, |
| int final_index, |
| const std::optional<tab_groups::TabGroupId> group, |
| bool pin, |
| bool select_after_move); |
| |
| // Similar to `MoveTabToIndexImpl` but this is used for multiple tabs either |
| // being moved or having their group updated. `tab_indices` should be sorted. |
| // Tabs are inserted at `destination_index` after they are removed. |
| void MoveTabsToIndexImpl(const std::vector<int>& tab_indices, |
| int destination_index, |
| const std::optional<tab_groups::TabGroupId> group); |
| |
| // Sends group notifications for a tab at `index` based on its initial_group |
| // and `final_group` and updates the `group_model_`. |
| void TabGroupStateChanged( |
| int index, |
| tabs::TabInterface* tab, |
| const std::optional<tab_groups::TabGroupId> initial_group, |
| const std::optional<tab_groups::TabGroupId> new_group); |
| |
| // Updates the `group_model` by decrementing the tab count of `group`. |
| void RemoveTabFromGroupModel(const tab_groups::TabGroupId& group); |
| |
| // Updates the `group_model` by incrementing the tab count of `group`. |
| void AddTabToGroupModel(const tab_groups::TabGroupId& group); |
| |
| // Checks if the `contents_data_` is in a valid order. This checks for |
| // pinned tabs placement, group contiguity and selected tabs validity. |
| void ValidateTabStripModel(); |
| |
| void SendMoveNotificationForTab( |
| int index, |
| int to_position, |
| tabs::TabInterface* tab, |
| const TabStripSelectionChange& selection_change); |
| |
| void UpdateSelectionModelForMove(int initial_index, |
| int final_index, |
| bool select_after_move); |
| |
| void UpdateSelectionModelForMoves(const std::vector<int>& tab_indices, |
| int destination_index); |
| |
| // Clears any previous selection and sets the selected index. This takes into |
| // account split tabs so both will be selected if `index` is a split tab. |
| void SetSelectedIndex(ui::ListSelectionModel* selection, int index); |
| |
| // Returns the range of indices between the anchor and a provided index, that |
| // takes into account split tabs. If the anchor or the tab at index is part of |
| // a split, the range will include that split. The start and end indices are |
| // inclusive. |
| std::pair<int, int> GetSelectionRangeFromAnchorToIndex(int index); |
| |
| // Generates the MoveNotifications for `MoveTabsToIndexImpl` and updates the |
| // selection model and openers. |
| std::vector<TabStripModel::MoveNotification> PrepareTabsToMoveToIndex( |
| const std::vector<int>& tab_indices, |
| int destination_index); |
| |
| // Generates a sequence of initial and destination index for tabs in |
| // `tab_indices` when the tabs need to move to `destination_index`. |
| std::vector<std::pair<int, int>> CalculateIncrementalTabMoves( |
| const std::vector<int>& tab_indices, |
| int destination_index) const; |
| |
| // Changes the pinned state of all tabs at `indices`, moving them in the |
| // process if necessary. If indices contains all tabs in a split, the whole |
| // split is pinned/unpinned. Otherwise, the tabs will be individually |
| // processed, resulting in the split being unsplit. |
| void SetTabsPinned(const std::vector<int> indices, bool pinned); |
| |
| // Implementation for setting the pinned state of the tab at `index`. |
| int SetTabPinnedImpl(int indices, bool pinned); |
| |
| // Changes the pinned state of a split collection, moving it in the process if |
| // necessary. |
| void SetSplitPinnedImpl(tabs::SplitTabCollection* split, bool pinned); |
| |
| // Wrapper for bulk move operations to make them send out the appropriate |
| // change notifications. |
| void MoveTabsWithNotifications(std::vector<int> tab_indices, |
| int destination_index, |
| base::OnceClosure execute_tabs_move_operation); |
| |
| // Sets the sound content setting for each site at the |indices|. |
| void SetSitesMuted(const std::vector<int>& indices, bool mute) const; |
| |
| // Sets the opener of any tabs that reference the tab at |index| to that tab's |
| // opener or null if there's a cycle. |
| void FixOpeners(int index); |
| |
| // Returns a group when the index of a tab is updated from `index` to |
| // `to_position` that would not break group contiguity. The group returned |
| // keeps the original group if it is valid at the `to_position` and otherwise |
| // returns a valid group. |
| std::optional<tab_groups::TabGroupId> GetGroupToAssign(int index, |
| int to_position); |
| |
| // Returns a valid index to be selected after the tabs in `block_tabs` are |
| // closed. If index is after the block, index is adjusted to reflect the fact |
| // that the block is going away. |
| int GetTabIndexAfterClosing(int index, const gfx::Range& block_tabs) const; |
| |
| // Takes the |selection| change and decides whether to forget the openers. |
| void OnActiveTabChanged(const TabStripSelectionChange& selection); |
| |
| // Checks if policy allows a tab to be closed. |
| bool PolicyAllowsTabClosing(content::WebContents* contents) const; |
| |
| // Determine where to shift selection after a tab or collection is closed. |
| std::optional<int> DetermineNewSelectedIndex( |
| std::variant<tabs::TabInterface*, tabs::TabCollection*> tab_or_collection) |
| const; |
| |
| std::vector<std::pair<tabs::TabInterface*, int>> GetTabsAndIndicesInSplit( |
| split_tabs::SplitTabId split_id); |
| |
| // If inserting at `index` breaks a split, returns its id, otherwise nullopt. |
| std::optional<split_tabs::SplitTabId> InsertionBreaksSplitContiguity( |
| int index); |
| |
| // Helper to determine if moving a block of tabs from `start_index` with block |
| // size `length` to `final_index` breaks contiguity. |
| std::optional<split_tabs::SplitTabId> |
| MoveBreaksSplitContiguity(int start_index, int length, int final_index); |
| |
| void MaybeRemoveSplitsForMove( |
| int initial_index, |
| int final_index, |
| const std::optional<tab_groups::TabGroupId> group, |
| bool pin); |
| |
| // The WebContents data currently hosted within this TabStripModel. This must |
| // be kept in sync with |selection_model_|. |
| std::unique_ptr<tabs::TabStripCollection> contents_data_; |
| |
| // The model for tab groups hosted within this TabStripModel. |
| std::unique_ptr<TabGroupModel> group_model_; |
| |
| raw_ptr<TabStripModelDelegate> delegate_; |
| |
| bool tab_strip_ui_was_set_ = false; |
| |
| base::ObserverList<TabStripModelObserver>::UncheckedAndDanglingUntriaged |
| observers_; |
| |
| // A profile associated with this TabStripModel. |
| raw_ptr<Profile, AcrossTasksDanglingUntriaged> profile_; |
| |
| // True if all tabs are currently being closed via CloseAllTabs. |
| bool closing_all_ = false; |
| |
| // This must be kept in sync with |contents_data_|. |
| ui::ListSelectionModel selection_model_; |
| |
| // TabStripModel is not re-entrancy safe. This member is used to guard public |
| // methods that mutate state of |selection_model_| or |contents_data_|. |
| bool reentrancy_guard_ = false; |
| |
| TabStripScrubbingMetrics scrubbing_metrics_; |
| |
| // Tracks whether a modal UI is showing. |
| bool showing_modal_ui_ = false; |
| |
| base::WeakPtrFactory<TabStripModel> weak_factory_{this}; |
| }; |
| |
| // Forbid construction of ScopedObservation and ScopedMultiSourceObservation |
| // with TabStripModel: TabStripModelObserver already implements their |
| // functionality natively. |
| namespace base { |
| |
| template <> |
| class ScopedObservation<TabStripModel, TabStripModelObserver> { |
| public: |
| // Deleting the constructor gives a clear error message traceable back to |
| // here. |
| explicit ScopedObservation(TabStripModelObserver* observer) = delete; |
| }; |
| |
| template <> |
| class ScopedMultiSourceObservation<TabStripModel, TabStripModelObserver> { |
| public: |
| // Deleting the constructor gives a clear error message traceable back to |
| // here. |
| explicit ScopedMultiSourceObservation(TabStripModelObserver* observer) = |
| delete; |
| }; |
| |
| } // namespace base |
| |
| #endif // CHROME_BROWSER_UI_TABS_TAB_STRIP_MODEL_H_ |