| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/tabs/public/tab_strip_collection.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <set> |
| |
| #include "base/containers/adapters.h" |
| #include "base/memory/ptr_util.h" |
| #include "components/tabs/public/pinned_tab_collection.h" |
| #include "components/tabs/public/split_tab_collection.h" |
| #include "components/tabs/public/split_tab_id.h" |
| #include "components/tabs/public/split_tab_visual_data.h" |
| #include "components/tabs/public/tab_collection.h" |
| #include "components/tabs/public/tab_collection_observer.h" |
| #include "components/tabs/public/tab_collection_storage.h" |
| #include "components/tabs/public/tab_group_tab_collection.h" |
| #include "components/tabs/public/tab_interface.h" |
| #include "components/tabs/public/unpinned_tab_collection.h" |
| |
| namespace { |
| tabs::TabCollection* GetCommonAncestor(tabs::TabCollection* collection_a, |
| tabs::TabCollection* collection_b) { |
| if (collection_a == collection_b) { |
| return collection_a; |
| } |
| |
| // Use a set to store all ancestors of the first collection. |
| std::set<tabs::TabCollection*> ancestors; |
| |
| // 1. Trace the path from collection_a to the root and store it. |
| tabs::TabCollection* current_a = collection_a; |
| while (current_a) { |
| ancestors.insert(current_a); |
| current_a = current_a->GetParentCollection(); |
| } |
| |
| // 2. Trace the path from collection_b up and check for a match. |
| tabs::TabCollection* current_b = collection_b; |
| while (current_b) { |
| if (ancestors.contains(current_b)) { |
| return current_b; |
| } |
| current_b = current_b->GetParentCollection(); |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| namespace tabs { |
| |
| TabStripCollection::TabStripCollection() |
| : TabCollection( |
| TabCollection::Type::TABSTRIP, |
| {TabCollection::Type::PINNED, TabCollection::Type::UNPINNED}, |
| /*supports_tabs=*/false) { |
| pinned_collection_ = |
| AddCollection(std::make_unique<PinnedTabCollection>(), 0); |
| unpinned_collection_ = |
| AddCollection(std::make_unique<UnpinnedTabCollection>(), 1); |
| } |
| |
| TabStripCollection::~TabStripCollection() = default; |
| |
| size_t TabStripCollection::IndexOfFirstNonPinnedTab() const { |
| return pinned_collection_->TabCountRecursive(); |
| } |
| |
| void TabStripCollection::AddTabRecursive( |
| std::unique_ptr<TabInterface> tab, |
| size_t index, |
| std::optional<tab_groups::TabGroupId> new_group_id, |
| bool new_pinned_state) { |
| CHECK(tab); |
| // `index` can be equal to the tab count as at this point the tab has not yet |
| // been added. |
| CHECK(index >= 0 && index <= TabCountRecursive()); |
| |
| if (new_group_id.has_value()) { |
| CHECK(GetTabGroupCollection(new_group_id.value())); |
| } |
| |
| TabCollection::Position insertion_details = |
| GetInsertionDetails(index, new_pinned_state, new_group_id); |
| |
| AddTabImpl(std::move(tab), insertion_details); |
| } |
| |
| void TabStripCollection::AddTabRecursiveImpl( |
| std::unique_ptr<TabInterface> tab, |
| size_t index, |
| std::optional<tab_groups::TabGroupId> new_group_id, |
| bool new_pinned_state) { |
| CHECK(tab); |
| // `index` can be equal to the tab count as at this point the tab has not yet |
| // been added. |
| CHECK(index >= 0 && index <= TabCountRecursive()); |
| |
| // First tab needs to be added to the group. In this case we need to create a |
| // group collection. |
| if (new_group_id.has_value() && |
| !GetTabGroupCollection(new_group_id.value())) { |
| TabCollection::Position group_position = |
| GetInsertionDetails(index, false, std::nullopt); |
| |
| // Attempt to create a new group for the tab. |
| AddTabCollectionImpl(PopDetachedGroupCollection(new_group_id.value()), |
| group_position); |
| |
| TabGroupTabCollection* group_collection = |
| GetTabGroupCollection(new_group_id.value()); |
| |
| // New empty group was attached, append tab to the group. |
| CHECK(group_collection->ChildCount() == 0); |
| TabInterface* inserted_tab = group_collection->AddTab(std::move(tab), 0); |
| CHECK(GetIndexOfTabRecursive(inserted_tab) == index); |
| return; |
| } |
| |
| TabCollection::Position insertion_details = |
| GetInsertionDetails(index, new_pinned_state, new_group_id); |
| insertion_details.parent_handle.Get()->AddTab(std::move(tab), |
| insertion_details.index); |
| } |
| |
| void TabStripCollection::MoveTabRecursive( |
| size_t initial_index, |
| size_t final_index, |
| std::optional<tab_groups::TabGroupId> new_group_id, |
| bool new_pinned_state) { |
| CHECK(initial_index >= 0 && final_index >= 0); |
| |
| TabInterface* tab = GetTabAtIndexRecursive(initial_index); |
| const std::optional<tab_groups::TabGroupId> old_group = tab->GetGroup(); |
| TabGroupTabCollection* old_group_collection = |
| old_group.has_value() ? GetTabGroupCollection(old_group.value()) |
| : nullptr; |
| const bool is_only_tab_in_group = |
| old_group.has_value() && old_group_collection->TabCountRecursive() == 1; |
| |
| if (old_group == new_group_id && new_group_id.has_value() && |
| is_only_tab_in_group) { |
| unpinned_collection_->MoveGroupToRecursive( |
| final_index - pinned_collection_->TabCountRecursive(), |
| old_group_collection); |
| } else { |
| std::unique_ptr<TabInterface> moved_data = |
| RemoveTabRecursiveImpl(tab, old_group != new_group_id); |
| AddTabRecursiveImpl(std::move(moved_data), final_index, new_group_id, |
| new_pinned_state); |
| } |
| } |
| |
| void TabStripCollection::MoveTabsRecursive( |
| const std::vector<int>& tab_indices, |
| size_t destination_index, |
| std::optional<tab_groups::TabGroupId> new_group_id, |
| bool new_pinned_state, |
| const std::set<TabCollection::Type>& retain_collection_types) { |
| CHECK(destination_index >= 0); |
| ChildrenVector moved_datas; |
| |
| // Removes the tabs and collections needed to be moved. |
| for (auto& tab_or_collection : |
| GetTabsAndCollectionsForMove(tab_indices, retain_collection_types)) { |
| TabCollection* parent_collection; |
| if (std::holds_alternative<TabInterface*>(tab_or_collection)) { |
| TabInterface* tab_to_remove = std::get<TabInterface*>(tab_or_collection); |
| parent_collection = tab_to_remove->GetParentCollection(GetPassKey()); |
| moved_datas.push_back(parent_collection->MaybeRemoveTab(tab_to_remove)); |
| } else { |
| TabCollection* collection_to_remove = |
| std::get<TabCollection*>(tab_or_collection); |
| parent_collection = collection_to_remove->GetParentCollection(); |
| moved_datas.push_back( |
| parent_collection->MaybeRemoveCollection(collection_to_remove)); |
| } |
| |
| if (parent_collection->type() == TabCollection::Type::GROUP && |
| parent_collection->TabCountRecursive() == 0) { |
| RemoveTabCollectionImpl(parent_collection); |
| } |
| } |
| |
| if (new_group_id.has_value() && |
| !GetTabGroupCollection(new_group_id.value())) { |
| TabCollection::Position group_position = |
| GetInsertionDetails(destination_index, false, std::nullopt); |
| AddTabCollectionImpl(PopDetachedGroupCollection(new_group_id.value()), |
| group_position); |
| } |
| |
| // `tab_collection_ptr` is the final collection to insert `moved_datas` |
| // starting at `insert_index`. |
| TabCollection::Position position = |
| GetInsertionDetails(destination_index, new_pinned_state, new_group_id); |
| TabCollection* tab_collection_ptr = position.parent_handle.Get(); |
| int insert_index = position.index; |
| CHECK(tab_collection_ptr); |
| |
| // Insert tabs and collections left to right so destination index can be used |
| // in an incremental manner from the direct destination index computed from |
| // `insertion_details`. |
| for (size_t i = 0; i < moved_datas.size(); i++) { |
| if (std::holds_alternative<std::unique_ptr<TabInterface>>(moved_datas[i])) { |
| tab_collection_ptr->AddTab( |
| std::move(std::get<std::unique_ptr<TabInterface>>(moved_datas[i])), |
| insert_index + i); |
| } else { |
| tab_collection_ptr->AddCollection( |
| std::move(std::get<std::unique_ptr<TabCollection>>(moved_datas[i])), |
| insert_index + i); |
| } |
| } |
| } |
| |
| ChildrenPtrs TabStripCollection::GetTabsAndCollectionsForMove( |
| const std::vector<int>& tab_indices, |
| const std::set<TabCollection::Type>& retain_collection_types) { |
| std::set<const TabInterface*> selected_tabs; |
| for (int index : tab_indices) { |
| selected_tabs.insert(GetTabAtIndexRecursive(index)); |
| } |
| |
| // Contains set of all the collections fully covered by `tab_indices`. This |
| // does not include `pinned_collection_` or `unpinned_collection_` as they |
| // cannot be moved. |
| std::set<const TabCollection*> selected_collections; |
| |
| if (retain_collection_types.contains(TabCollection::Type::GROUP)) { |
| for (const auto& [group_id_, group_collection] : group_mapping_) { |
| bool fully_selected = true; |
| for (const TabInterface* tab : *group_collection) { |
| if (!selected_tabs.contains(tab)) { |
| fully_selected = false; |
| break; |
| } |
| } |
| |
| if (fully_selected) { |
| selected_collections.insert(group_collection); |
| } |
| } |
| } |
| |
| if (retain_collection_types.contains(TabCollection::Type::SPLIT)) { |
| for (const auto& [split_id, split_collection] : split_mapping_) { |
| bool fully_selected = true; |
| for (const TabInterface* tab : *split_collection) { |
| if (!selected_tabs.contains(tab)) { |
| fully_selected = false; |
| break; |
| } |
| } |
| |
| if (fully_selected) { |
| selected_collections.insert(split_collection); |
| } |
| } |
| } |
| |
| ChildrenPtrs move_datas; |
| |
| // Iterates through `tab_indices`. If the tab is a direct child of |
| // `pinned_collection_` or `unpinned_collection_` return it directly as it |
| // needs to be removed directly. Otherwise potentially return the biggest |
| // subcollection that contains the tab to be removed. |
| int array_index = 0; |
| while (array_index < static_cast<int>(tab_indices.size())) { |
| TabInterface* tab = GetTabAtIndexRecursive(tab_indices[array_index]); |
| TabCollection* collection = tab->GetParentCollection(GetPassKey()); |
| if (collection == pinned_collection_ || |
| collection == unpinned_collection_) { |
| TabInterface* move_data = |
| GetTabAtIndexRecursive(tab_indices[array_index]); |
| move_datas.push_back(move_data); |
| array_index++; |
| continue; |
| } else { |
| TabCollection* candidate_subtree_collection = collection; |
| TabCollection* subtree_to_remove = nullptr; |
| |
| // Finds the largest subcollection containing `tab`. `subtree_to_remove` |
| // is the current valid subcollection to remove while |
| // `candidate_subtree_collection` is the next potential subcollection to |
| // remove. |
| while ((candidate_subtree_collection != pinned_collection_ && |
| candidate_subtree_collection != unpinned_collection_) && |
| selected_collections.contains(candidate_subtree_collection)) { |
| subtree_to_remove = candidate_subtree_collection; |
| candidate_subtree_collection = |
| candidate_subtree_collection->GetParentCollection(); |
| } |
| |
| if (subtree_to_remove) { |
| move_datas.push_back(subtree_to_remove); |
| array_index += subtree_to_remove->TabCountRecursive(); |
| } else { |
| TabInterface* move_data = |
| GetTabAtIndexRecursive(tab_indices[array_index]); |
| move_datas.push_back(move_data); |
| array_index++; |
| } |
| } |
| } |
| |
| return move_datas; |
| } |
| |
| std::unique_ptr<TabInterface> TabStripCollection::RemoveTabAtIndexRecursive( |
| size_t index) { |
| TabInterface* tab_to_be_removed = GetTabAtIndexRecursive(index); |
| TabCollection* parent_collection = |
| tab_to_be_removed->GetParentCollection(GetPassKey()); |
| CHECK(parent_collection); |
| |
| // If it is the only tab in the group remove the collection. |
| if (parent_collection != unpinned_collection_ && |
| parent_collection != pinned_collection_ && |
| parent_collection->TabCountRecursive() == 1) { |
| RemoveTabCollectionImpl(parent_collection); |
| return parent_collection->MaybeRemoveTab(tab_to_be_removed); |
| } else { |
| return RemoveTabImpl(tab_to_be_removed); |
| } |
| } |
| |
| std::unique_ptr<TabInterface> TabStripCollection::MaybeRemoveTab( |
| TabInterface* tab) { |
| CHECK(tab); |
| return nullptr; |
| } |
| |
| std::unique_ptr<TabCollection> TabStripCollection::MaybeRemoveCollection( |
| TabCollection* collection) { |
| CHECK(collection); |
| return nullptr; |
| } |
| |
| void TabStripCollection::InsertTabCollectionAt( |
| std::unique_ptr<TabCollection> collection, |
| int index, |
| int pinned, |
| std::optional<tab_groups::TabGroupId> parent_group) { |
| TabCollection::Position insertion_details = |
| GetInsertionDetails(index, pinned, parent_group); |
| |
| CHECK(insertion_details.parent_handle.Get()); |
| AddTabCollectionImpl(std::move(collection), insertion_details); |
| } |
| |
| std::unique_ptr<TabCollection> TabStripCollection::RemoveTabCollection( |
| TabCollection* collection) { |
| // Removed collection can be nullptr in the case of group. Return from |
| // detached collection instead in that case. |
| std::unique_ptr<TabCollection> removed_collection = |
| RemoveTabCollectionImpl(collection); |
| |
| if (collection->type() == TabCollection::Type::GROUP) { |
| return PopDetachedGroupCollection( |
| static_cast<TabGroupTabCollection*>(collection)->GetTabGroupId()); |
| } else { |
| CHECK(removed_collection); |
| return removed_collection; |
| } |
| } |
| |
| void TabStripCollection::CreateTabGroup( |
| std::unique_ptr<tabs::TabGroupTabCollection> tab_group_collection) { |
| CHECK(tab_group_collection); |
| detached_group_collections_.push_back(std::move(tab_group_collection)); |
| } |
| |
| TabGroupTabCollection* TabStripCollection::GetTabGroupCollection( |
| tab_groups::TabGroupId group_id) { |
| if (auto it = group_mapping_.find(group_id); it != group_mapping_.end()) { |
| return it->second; |
| } |
| return nullptr; |
| } |
| |
| std::vector<tab_groups::TabGroupId> TabStripCollection::GetAllTabGroupIds() |
| const { |
| std::vector<tab_groups::TabGroupId> group_ids; |
| group_ids.reserve(group_mapping_.size()); |
| for (const auto& pair : group_mapping_) { |
| group_ids.push_back(pair.first); |
| } |
| |
| return group_ids; |
| } |
| |
| void TabStripCollection::MoveTabGroupTo(const tab_groups::TabGroupId& group, |
| int to_index) { |
| tabs::TabGroupTabCollection* group_collection = GetTabGroupCollection(group); |
| |
| CHECK(to_index >= static_cast<int>(pinned_collection_->TabCountRecursive())); |
| |
| unpinned_collection_->MoveGroupToRecursive( |
| to_index - pinned_collection_->TabCountRecursive(), group_collection); |
| } |
| |
| void TabStripCollection::CloseDetachedTabGroup( |
| const tab_groups::TabGroupId& group_id) { |
| PopDetachedGroupCollection(group_id).reset(); |
| } |
| |
| TabGroupTabCollection* TabStripCollection::GetDetachedTabGroup( |
| const tab_groups::TabGroupId& group_id) { |
| auto it = std::find_if( |
| detached_group_collections_.begin(), detached_group_collections_.end(), |
| [group_id](const std::unique_ptr<TabGroupTabCollection>& collection) { |
| return collection->GetTabGroupId() == group_id; |
| }); |
| if (it == detached_group_collections_.end()) { |
| return nullptr; |
| } |
| return it->get(); |
| } |
| |
| SplitTabCollection* TabStripCollection::GetSplitTabCollection( |
| split_tabs::SplitTabId split_id) { |
| if (auto it = split_mapping_.find(split_id); it != split_mapping_.end()) { |
| return it->second; |
| } |
| return nullptr; |
| } |
| |
| void TabStripCollection::CreateSplit( |
| split_tabs::SplitTabId split_id, |
| const std::vector<TabInterface*>& tabs, |
| split_tabs::SplitTabVisualData visual_data) { |
| CHECK(tabs.size() >= 2); |
| TabCollection* parent_collection = tabs[0]->GetParentCollection(GetPassKey()); |
| CHECK(std::all_of( |
| tabs.begin(), tabs.end(), [this, parent_collection](auto tab) { |
| return parent_collection == tab->GetParentCollection(GetPassKey()); |
| })); |
| |
| size_t dst_index = parent_collection->GetIndexOfTab(tabs[0]).value(); |
| |
| // Create a new split. |
| std::unique_ptr<SplitTabCollection> split = |
| std::make_unique<SplitTabCollection>(split_id, visual_data); |
| SplitTabCollection* split_ptr = split.get(); |
| TabCollection::Position insertion_details = {parent_collection->GetHandle(), |
| dst_index}; |
| AddTabCollectionImpl(std::move(split), insertion_details); |
| |
| // Move tabs to the split. |
| size_t insertion_index = 0; |
| for (TabInterface* tab : tabs) { |
| TabCollection::Position tab_move_details = {split_ptr->GetHandle(), |
| insertion_index}; |
| MoveTabImpl(tab, tab_move_details); |
| insertion_index += 1; |
| } |
| } |
| |
| void TabStripCollection::Unsplit(split_tabs::SplitTabId split_id) { |
| SplitTabCollection* split = GetSplitTabCollection(split_id); |
| if (!split) { |
| return; |
| } |
| |
| CHECK(split_mapping_.contains(split_id)); |
| TabCollection* parent_collection = split->GetParentCollection(); |
| size_t dst_index = parent_collection->GetIndexOfCollection(split).value(); |
| std::vector<TabInterface*> tabs = split->GetTabsRecursive(); |
| |
| // Move tabs to the parent collection. |
| size_t move_index = dst_index; |
| for (TabInterface* tab : tabs) { |
| TabCollection::Position tab_move_details = {parent_collection->GetHandle(), |
| move_index}; |
| MoveTabImpl(tab, tab_move_details); |
| move_index += 1; |
| } |
| |
| RemoveTabCollectionImpl(split); |
| } |
| |
| std::set<split_tabs::SplitTabId> TabStripCollection::ListSplits() const { |
| std::set<split_tabs::SplitTabId> splits; |
| for (const auto& [split_id, _] : split_mapping_) { |
| splits.insert(split_id); |
| } |
| return splits; |
| } |
| |
| void TabStripCollection::ValidateData() const { |
| for (const auto& [_, group] : group_mapping_) { |
| CHECK(group->ChildCount() > 0); |
| } |
| for (const auto& [_, split] : split_mapping_) { |
| CHECK(split->ChildCount() >= 2); |
| for (auto child : split->GetTabsRecursive()) { |
| CHECK(split->GetSplitTabId() == child->GetSplit().value()); |
| } |
| } |
| } |
| |
| std::optional<const tab_groups::TabGroupId> TabStripCollection::FindGroupIdFor( |
| const tabs::TabCollection::Handle& collection_handle, |
| base::PassKey<TabStripModel>) const { |
| for (auto& pair : group_mapping_) { |
| if (pair.second->GetHandle() == collection_handle) { |
| return pair.first; |
| } |
| } |
| |
| return std::nullopt; |
| } |
| |
| std::unique_ptr<TabInterface> TabStripCollection::RemoveTabRecursiveImpl( |
| TabInterface* tab, |
| bool close_empty_group_collection) { |
| CHECK(tab); |
| |
| TabCollection* parent_collection = tab->GetParentCollection(GetPassKey()); |
| const std::optional<tab_groups::TabGroupId> group = tab->GetGroup(); |
| |
| std::unique_ptr<TabInterface> removed_tab = |
| parent_collection->MaybeRemoveTab(tab); |
| |
| CHECK(removed_tab); |
| |
| if (group.has_value() && close_empty_group_collection && |
| GetTabGroupCollection(group.value())->TabCountRecursive() == 0) { |
| RemoveTabCollectionImpl(GetTabGroupCollection(group.value())); |
| } |
| |
| return removed_tab; |
| } |
| |
| std::unique_ptr<tabs::TabGroupTabCollection> |
| TabStripCollection::PopDetachedGroupCollection( |
| const tab_groups::TabGroupId& group_id) { |
| auto it = std::find_if( |
| detached_group_collections_.begin(), detached_group_collections_.end(), |
| [group_id]( |
| const std::unique_ptr<tabs::TabGroupTabCollection>& collection) { |
| return collection->GetTabGroupId() == group_id; |
| }); |
| CHECK(it != detached_group_collections_.end()); |
| |
| std::unique_ptr<tabs::TabGroupTabCollection> group_collection = |
| std::move(*it); |
| detached_group_collections_.erase(it); |
| |
| return group_collection; |
| } |
| |
| void TabStripCollection::AddCollectionMapping(TabCollection* root_collection) { |
| if (root_collection->type() == TabCollection::Type::GROUP) { |
| TabGroupTabCollection* group_collection = |
| static_cast<TabGroupTabCollection*>(root_collection); |
| group_mapping_.insert( |
| {group_collection->GetTabGroupId(), group_collection}); |
| |
| for (const tabs::TabInterface* tab : *group_collection) { |
| if (tab->IsSplit()) { |
| const split_tabs::SplitTabId split_id = tab->GetSplit().value(); |
| if (!split_mapping_.contains(split_id)) { |
| split_mapping_.insert( |
| {split_id, static_cast<SplitTabCollection*>( |
| tab->GetParentCollection(GetPassKey()))}); |
| } |
| } |
| } |
| } else if (root_collection->type() == TabCollection::Type::SPLIT) { |
| SplitTabCollection* split_collection = |
| static_cast<SplitTabCollection*>(root_collection); |
| split_mapping_.insert( |
| {split_collection->GetSplitTabId(), split_collection}); |
| } |
| } |
| |
| void TabStripCollection::RemoveCollectionMapping( |
| TabCollection* root_collection) { |
| if (root_collection->type() == TabCollection::Type::GROUP) { |
| TabGroupTabCollection* group_collection = |
| static_cast<TabGroupTabCollection*>(root_collection); |
| CHECK(group_mapping_.erase(group_collection->GetTabGroupId())); |
| |
| for (const tabs::TabInterface* tab : *group_collection) { |
| if (tab->IsSplit()) { |
| split_mapping_.erase(tab->GetSplit().value()); |
| } |
| } |
| |
| } else if (root_collection->type() == TabCollection::Type::SPLIT) { |
| SplitTabCollection* split_collection = |
| static_cast<SplitTabCollection*>(root_collection); |
| CHECK(split_mapping_.erase(split_collection->GetSplitTabId())); |
| } |
| } |
| |
| void TabStripCollection::AddTabImpl(std::unique_ptr<TabInterface> tab, |
| const TabCollection::Position& position) { |
| auto [tab_collection_handle, insert_index] = position; |
| TabCollection* tab_collection_ptr = tab_collection_handle.Get(); |
| |
| TabInterface* tab_ptr = |
| tab_collection_ptr->AddTab(std::move(tab), insert_index); |
| |
| TabCollectionNodes handles_added; |
| handles_added.push_back(tab_ptr->GetHandle()); |
| |
| tab_collection_ptr->NotifyOnChildrenAdded(GetPassKey(), handles_added, |
| position, this); |
| } |
| |
| void TabStripCollection::AddTabCollectionImpl( |
| std::unique_ptr<TabCollection> collection, |
| const TabCollection::Position& position) { |
| TabCollection* collection_ptr = collection.get(); |
| AddCollectionMapping(collection_ptr); |
| |
| auto [tab_collection_handle, insert_index] = position; |
| TabCollection* tab_collection_ptr = tab_collection_handle.Get(); |
| |
| tab_collection_ptr->AddCollection(std::move(collection), insert_index); |
| |
| TabCollectionNodes handles_added; |
| handles_added.push_back(collection_ptr->GetHandle()); |
| |
| tab_collection_ptr->NotifyOnChildrenAdded(GetPassKey(), handles_added, |
| position, this); |
| } |
| |
| std::unique_ptr<TabInterface> TabStripCollection::RemoveTabImpl( |
| TabInterface* tab) { |
| CHECK(tab); |
| |
| TabCollection* parent_collection = tab->GetParentCollection(GetPassKey()); |
| |
| std::unique_ptr<TabInterface> removed_tab = |
| parent_collection->MaybeRemoveTab(tab); |
| |
| CHECK(removed_tab); |
| |
| parent_collection->NotifyOnChildrenRemoved( |
| GetPassKey(), |
| std::vector{std::variant<tabs::TabCollectionHandle, tabs::TabHandle>{ |
| removed_tab->GetHandle()}}, |
| this); |
| |
| return removed_tab; |
| } |
| |
| std::unique_ptr<TabCollection> TabStripCollection::RemoveTabCollectionImpl( |
| TabCollection* collection) { |
| TabCollectionHandle collection_handle = collection->GetHandle(); |
| TabCollection* parent_collection = collection->GetParentCollection(); |
| |
| RemoveCollectionMapping(collection); |
| std::unique_ptr<TabCollection> removed_collection = |
| parent_collection->MaybeRemoveCollection(collection); |
| |
| // In the case of group return null and store it in |
| // `detached_group_collections_` instead. |
| if (removed_collection->type() == TabCollection::Type::GROUP) { |
| detached_group_collections_.push_back(base::WrapUnique( |
| static_cast<TabGroupTabCollection*>(removed_collection.release()))); |
| } |
| |
| parent_collection->NotifyOnChildrenRemoved( |
| GetPassKey(), NodeHandles{collection_handle}, this); |
| return removed_collection; |
| } |
| |
| void TabStripCollection::MoveTabImpl(TabInterface* tab_ptr, |
| const TabCollection::Position& position) { |
| TabCollection* src_parent_collection = |
| tab_ptr->GetParentCollection(GetPassKey()); |
| TabCollection::Position src_details{ |
| src_parent_collection->GetHandle(), |
| src_parent_collection->GetIndexOfTab(tab_ptr).value()}; |
| |
| TabCollectionNodes handles; |
| handles.push_back(tab_ptr->GetHandle()); |
| |
| std::unique_ptr<TabInterface> removed_tab = |
| src_parent_collection->MaybeRemoveTab(tab_ptr); |
| |
| TabCollection* dst_parent_collection = position.parent_handle.Get(); |
| dst_parent_collection->AddTab(std::move(removed_tab), position.index); |
| |
| // Notify removes,add and moves based on the common ancestor. |
| TabCollection* common_ancestor = |
| GetCommonAncestor(src_parent_collection, dst_parent_collection); |
| |
| if (src_parent_collection != common_ancestor) { |
| src_parent_collection->NotifyOnChildrenRemoved(GetPassKey(), handles, |
| common_ancestor); |
| } |
| |
| if (dst_parent_collection != common_ancestor) { |
| dst_parent_collection->NotifyOnChildrenAdded(GetPassKey(), handles, |
| position, common_ancestor); |
| } |
| |
| common_ancestor->NotifyOnChildMoved(GetPassKey(), handles[0], src_details, |
| position, this); |
| } |
| |
| void TabStripCollection::MoveCollectionImpl( |
| TabCollection* collection_ptr, |
| const TabCollection::Position& position) { |
| TabCollection* src_parent_collection = collection_ptr->GetParentCollection(); |
| TabCollection::Position src_details{ |
| src_parent_collection->GetHandle(), |
| src_parent_collection->GetIndexOfCollection(collection_ptr).value()}; |
| |
| TabCollectionNodes handles; |
| handles.push_back(collection_ptr->GetHandle()); |
| |
| std::unique_ptr<TabCollection> removed_collection = |
| src_parent_collection->MaybeRemoveCollection(collection_ptr); |
| |
| TabCollection* dst_parent_collection = position.parent_handle.Get(); |
| dst_parent_collection->AddCollection(std::move(removed_collection), |
| position.index); |
| |
| // Notify removes,add and moves based on the common ancestor. |
| TabCollection* common_ancestor = |
| GetCommonAncestor(src_parent_collection, dst_parent_collection); |
| |
| if (src_parent_collection != common_ancestor) { |
| src_parent_collection->NotifyOnChildrenRemoved(GetPassKey(), handles, |
| common_ancestor); |
| } |
| |
| if (dst_parent_collection != common_ancestor) { |
| dst_parent_collection->NotifyOnChildrenAdded(GetPassKey(), handles, |
| position, common_ancestor); |
| } |
| |
| common_ancestor->NotifyOnChildMoved(GetPassKey(), handles[0], src_details, |
| position, this); |
| } |
| |
| TabCollection::Position TabStripCollection::GetInsertionDetails( |
| int index, |
| int pinned, |
| std::optional<tab_groups::TabGroupId> group) { |
| size_t direct_dst_index; |
| TabCollection* insert_collection = nullptr; |
| |
| if (pinned) { |
| direct_dst_index = pinned_collection_->ToDirectIndex(index); |
| insert_collection = pinned_collection_; |
| } else if (group.has_value()) { |
| TabGroupTabCollection* group_collection = |
| GetTabGroupCollection(group.value()); |
| CHECK(group_collection); |
| |
| if (group_collection->TabCountRecursive() == 0) { |
| // Group has been created but not yet populated. |
| direct_dst_index = 0; |
| } else { |
| size_t offset = |
| GetIndexOfTabRecursive(group_collection->GetTabAtIndexRecursive(0)) |
| .value(); |
| |
| direct_dst_index = group_collection->ToDirectIndex(index - offset); |
| } |
| insert_collection = group_collection; |
| } else { |
| size_t offset = pinned_collection_->TabCountRecursive(); |
| direct_dst_index = unpinned_collection_->ToDirectIndex(index - offset); |
| insert_collection = unpinned_collection_; |
| } |
| |
| return TabCollection::Position(insert_collection->GetHandle(), |
| direct_dst_index); |
| } |
| |
| } // namespace tabs |