blob: 2e8b93bd8341707676665f4c6e5de29a3885965c [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/tabs/tab_list_bridge.h"
#include "base/notimplemented.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/tabs/tab_enums.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
#include "components/tabs/public/tab_interface.h"
namespace {
// Returns the browser with the corresponding `target_session_id` if and only
// if the profile also matches `restrict_to_profile`.
BrowserWindowInterface* GetBrowserWithSessionId(
const SessionID& target_session_id,
const Profile* restrict_to_profile) {
std::vector<BrowserWindowInterface*> all_browsers =
GetAllBrowserWindowInterfaces();
for (auto* browser : all_browsers) {
if (browser->GetProfile() == restrict_to_profile &&
browser->GetSessionID() == target_session_id) {
return browser;
}
}
return nullptr;
}
} // namespace
DEFINE_USER_DATA(TabListBridge);
TabListBridge::TabListBridge(TabStripModel& tab_strip_model,
ui::UnownedUserDataHost& unowned_user_data_host)
: tab_strip_(tab_strip_model),
scoped_data_holder_(unowned_user_data_host, *this) {
tab_strip_->AddObserver(this);
}
// Note: TabStripObserver already implements RemoveObserver() calls; no need to
// remove this object as an observer here.
TabListBridge::~TabListBridge() = default;
void TabListBridge::AddTabListInterfaceObserver(
TabListInterfaceObserver* observer) {
observers_.AddObserver(observer);
}
void TabListBridge::RemoveTabListInterfaceObserver(
TabListInterfaceObserver* observer) {
observers_.RemoveObserver(observer);
}
int TabListBridge::GetTabCount() const {
return tab_strip_->count();
}
int TabListBridge::GetActiveIndex() const {
return tab_strip_->active_index();
}
tabs::TabInterface* TabListBridge::GetActiveTab() {
return tab_strip_->GetActiveTab();
}
tabs::TabInterface* TabListBridge::OpenTab(const GURL& url, int index) {
NOTIMPLEMENTED();
return nullptr;
}
void TabListBridge::DiscardTab(tabs::TabHandle tab) {}
tabs::TabInterface* TabListBridge::DuplicateTab(tabs::TabHandle tab) {
// TODO(dpenning): It's a bit of a code smell to reach in and grab the
// delegate from TabStripModel, but it avoids introducing new dependencies
// here.
TabStripModelDelegate* delegate = tab_strip_->delegate();
const int index = GetIndexOfTab(tab);
CHECK_NE(index, TabStripModel::kNoTab);
if (!delegate->CanDuplicateContentsAt(index)) {
return nullptr;
}
content::WebContents* new_contents = delegate->DuplicateContentsAt(index);
if (!new_contents) {
return nullptr;
}
return tabs::TabInterface::MaybeGetFromContents(new_contents);
}
tabs::TabInterface* TabListBridge::GetTab(int index) {
return tab_strip_->GetTabAtIndex(index);
}
int TabListBridge::GetIndexOfTab(tabs::TabHandle tab) {
return tab_strip_->GetIndexOfTab(tab.Get());
}
void TabListBridge::HighlightTabs(tabs::TabHandle tab_to_activate,
const std::set<tabs::TabHandle>& tabs) {
CHECK(tabs.contains(tab_to_activate))
<< "Tab to activate is not included in tabs to highlight.";
ui::ListSelectionModel selected_tabs = tab_strip_->selection_model();
for (const auto& tab_handle : tabs) {
auto index = tab_strip_->GetIndexOfTab(tab_handle.Get());
CHECK_NE(index, TabStripModel::kNoTab)
<< "Trying to highlight a non-existent tab.";
selected_tabs.AddIndexToSelection(index);
}
selected_tabs.set_active(tab_strip_->GetIndexOfTab(tab_to_activate.Get()));
tab_strip_->SetSelectionFromModel(std::move(selected_tabs));
}
void TabListBridge::MoveTab(tabs::TabHandle tab, int index) {
int current_index = GetIndexOfTab(tab);
CHECK_NE(index, TabStripModel::kNoTab)
<< "Trying to move a non-existent tab.";
tab_strip_->MoveWebContentsAt(current_index, index,
/*select_after_move=*/false);
}
void TabListBridge::CloseTab(tabs::TabHandle tab) {
const int index = GetIndexOfTab(tab);
CHECK_NE(index, TabStripModel::kNoTab)
<< "Trying to close a tab that doesn't exist in this tab list.";
tab_strip_->CloseWebContentsAt(index, TabCloseTypes::CLOSE_NONE);
}
std::vector<tabs::TabInterface*> TabListBridge::GetAllTabs() {
std::vector<tabs::TabInterface*> all_tabs;
size_t tab_count = tab_strip_->count();
all_tabs.reserve(tab_count);
for (tabs::TabInterface* tab : *tab_strip_) {
all_tabs.push_back(tab);
}
return all_tabs;
}
void TabListBridge::PinTab(tabs::TabHandle tab) {
int index = GetIndexOfTab(tab);
CHECK_NE(index, TabStripModel::kNoTab)
<< "Trying to pin a tab that doesn't exist in this tab list.";
tab_strip_->SetTabPinned(index, true);
}
void TabListBridge::UnpinTab(tabs::TabHandle tab) {
int index = GetIndexOfTab(tab);
CHECK_NE(index, TabStripModel::kNoTab)
<< "Trying to unpin a tab that doesn't exist in this tab list.";
tab_strip_->SetTabPinned(index, false);
}
std::optional<tab_groups::TabGroupId> TabListBridge::AddTabsToGroup(
std::optional<tab_groups::TabGroupId> group_id,
const std::set<tabs::TabHandle>& tabs) {
std::vector<int> tab_indices;
tab_indices.reserve(tabs.size());
for (const auto& tab_handle : tabs) {
auto index = tab_strip_->GetIndexOfTab(tab_handle.Get());
CHECK_NE(index, TabStripModel::kNoTab)
<< "Trying to add a non-existent tab to a group.";
tab_indices.push_back(index);
}
std::sort(tab_indices.begin(), tab_indices.end());
if (group_id.has_value()) {
tab_strip_->AddToExistingGroup(std::move(tab_indices), *group_id);
return group_id;
}
return tab_strip_->AddToNewGroup(std::move(tab_indices));
}
void TabListBridge::Ungroup(const std::set<tabs::TabHandle>& tabs) {
std::vector<int> tab_indices;
tab_indices.reserve(tabs.size());
for (const auto& tab_handle : tabs) {
auto index = tab_strip_->GetIndexOfTab(tab_handle.Get());
CHECK_NE(index, TabStripModel::kNoTab)
<< "Trying to remove a non-existent tab from a group.";
tab_indices.push_back(index);
}
std::sort(tab_indices.begin(), tab_indices.end());
tab_strip_->RemoveFromGroup(tab_indices);
}
void TabListBridge::MoveGroupTo(tab_groups::TabGroupId group_id, int index) {}
void TabListBridge::MoveTabToWindow(tabs::TabHandle tab,
SessionID destination_window_id,
int destination_index) {
int source_index = GetIndexOfTab(tab);
CHECK_NE(source_index, TabStripModel::kNoTab);
BrowserWindowInterface* target_window =
GetBrowserWithSessionId(destination_window_id, tab_strip_->profile());
CHECK(target_window);
TabListInterface* target_list_interface =
TabListInterface::From(target_window);
CHECK(target_list_interface);
// This is the only implementation on these platforms, so this cast is safe.
TabListBridge* target_bridge =
static_cast<TabListBridge*>(target_list_interface);
std::unique_ptr<tabs::TabModel> detached_tab =
tab_strip_->DetachTabAtForInsertion(source_index);
if (!detached_tab) {
return;
}
target_bridge->tab_strip_->InsertDetachedTabAt(
destination_index, std::move(detached_tab), AddTabTypes::ADD_NONE);
}
void TabListBridge::MoveTabGroupToWindow(tab_groups::TabGroupId group_id,
SessionID destination_window_id,
int destination_index) {}
void TabListBridge::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
switch (change.type()) {
case TabStripModelChange::kInserted: {
// See comment on TabStripModelChange::Insert for notes about the format
// of `contents`.
// NOTE: This might be *unsafe* if callers mutate the tab strip model
// synchronously from this event.
for (const auto& web_contents_and_index : change.GetInsert()->contents) {
// This will (correctly) crash if `tab` is not found. Since we just
// inserted the tab, we know it should exist.
tabs::TabInterface* tab = web_contents_and_index.tab.get();
for (auto& observer : observers_) {
observer.OnTabAdded(tab, web_contents_and_index.index);
}
}
break;
}
case TabStripModelChange::kRemoved:
case TabStripModelChange::kMoved:
case TabStripModelChange::kReplaced:
case TabStripModelChange::kSelectionOnly:
break;
}
if (selection.active_tab_changed()) {
tabs::TabInterface* tab = tab_strip_->GetActiveTab();
if (tab) {
for (auto& observer : observers_) {
observer.OnActiveTabChanged(tab);
}
}
}
}
// static
// From //chrome/browser/ui/tabs/tab_list_interface.h
TabListInterface* TabListInterface::From(
BrowserWindowInterface* browser_window_interface) {
return ui::ScopedUnownedUserData<TabListBridge>::Get(
browser_window_interface->GetUnownedUserDataHost());
}