This change allows a couple of more places to be migrated to `BrowserWindowInterface`. Chromium changes: https://chromium.googlesource.com/chromium/src/+/e8d0df41d25900497716dd3a4b070c6436fa1a97 commit e8d0df41d25900497716dd3a4b070c6436fa1a97 Author: Kun Wang <kunwang@microsoft.com> Date: Wed Apr 15 23:35:39 2026 -0700 [bedrock] Migrate FindLastActiveWithProfile Step 1 Bug: 494010890 Change-Id: Ie0385deeda2e2f28baf75a810ada0becf5807c78 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7753884 Reviewed-by: Thomas Lukaszewicz <tluk@chromium.org> Reviewed-by: Qikai Zhong <qikaizhong@microsoft.com> Commit-Queue: Kun Wang <kunwang@microsoft.com> Cr-Commit-Position: refs/heads/main@{#1615659}
321 lines
12 KiB
C++
321 lines
12 KiB
C++
// Copyright (c) 2024 The Brave Authors. All rights reserved.
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
// Based on Chromium code subject to the following license:
|
|
// Copyright 2021 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "brave/browser/ui/commander/tab_command_source.h"
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <numeric>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "base/check.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "brave/app/command_utils.h"
|
|
#include "brave/browser/ui/commander/entity_match.h"
|
|
#include "brave/browser/ui/commander/fuzzy_finder.h"
|
|
#include "chrome/app/chrome_command_ids.h"
|
|
#include "chrome/browser/send_tab_to_self/send_tab_to_self_util.h"
|
|
#include "chrome/browser/ui/accelerator_utils.h"
|
|
#include "chrome/browser/ui/browser.h"
|
|
#include "chrome/browser/ui/browser_commands.h"
|
|
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
|
|
#include "chrome/browser/ui/tabs/tab_enums.h"
|
|
#include "chrome/browser/ui/tabs/tab_group_model.h"
|
|
#include "chrome/browser/ui/tabs/tab_strip_model.h"
|
|
#include "chrome/grit/generated_resources.h"
|
|
#include "components/grit/brave_components_strings.h"
|
|
#include "components/sessions/content/session_tab_helper.h"
|
|
#include "ui/base/l10n/l10n_util.h"
|
|
#include "ui/base/models/list_selection_model.h"
|
|
|
|
namespace commander {
|
|
|
|
namespace {
|
|
|
|
// TODO(lgrey): It *might* make to pull this out later into a CommandSource
|
|
// method or a free function in some common place. Not committing yet.
|
|
std::unique_ptr<CommandItem> ItemForTitle(const std::u16string& title,
|
|
FuzzyFinder& finder,
|
|
std::vector<gfx::Range>& ranges) {
|
|
double score = finder.Find(title, ranges);
|
|
if (score > 0) {
|
|
return std::make_unique<CommandItem>(title, score, ranges);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Returns the tab group that the currently selected tabs can *not* be moved to.
|
|
// In practice, this is the tab group that *all* selected tabs belong to, if
|
|
// any. In the common special case of single selection, this will return that
|
|
// tab's group if it has one.
|
|
std::optional<tab_groups::TabGroupId> IneligibleGroupForSelected(
|
|
TabStripModel* tab_strip_model) {
|
|
std::optional<tab_groups::TabGroupId> excluded_group = std::nullopt;
|
|
for (tabs::TabInterface* tab :
|
|
tab_strip_model->selection_model().selected_tabs()) {
|
|
if (auto group = tab->GetGroup(); group.has_value()) {
|
|
if (!excluded_group.has_value()) {
|
|
excluded_group = group;
|
|
} else if (group != excluded_group) {
|
|
// More than one group in the selection, so don't exclude anything.
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
}
|
|
return excluded_group;
|
|
}
|
|
|
|
// Returns true only if `browser` is alive, and the contents at `index` match
|
|
// `tab_session_id`.
|
|
bool DoesTabAtIndexMatchSessionId(base::WeakPtr<BrowserWindowInterface> browser,
|
|
int index,
|
|
int tab_session_id) {
|
|
if (!browser.get()) {
|
|
return false;
|
|
}
|
|
|
|
auto* tab_strip_model = browser->GetTabStripModel();
|
|
if (tab_strip_model->count() <= index) {
|
|
return false;
|
|
}
|
|
content::WebContents* contents = tab_strip_model->GetWebContentsAt(index);
|
|
DCHECK(contents);
|
|
return sessions::SessionTabHelper::IdForTab(contents).id() == tab_session_id;
|
|
}
|
|
|
|
// Commands:
|
|
|
|
bool HasUnpinnedTabs(const TabStripModel* model) {
|
|
return model->IndexOfFirstNonPinnedTab() < model->count();
|
|
}
|
|
|
|
bool HasPinnedTabs(const TabStripModel* model) {
|
|
return model->IndexOfFirstNonPinnedTab() > 0;
|
|
}
|
|
|
|
bool CanMoveTabsToExistingWindow(
|
|
const BrowserWindowInterface* browser_to_exclude) {
|
|
bool has_found = false;
|
|
GlobalBrowserCollection::GetInstance()->ForEach(
|
|
[browser_to_exclude, &has_found](BrowserWindowInterface* browser) {
|
|
has_found = browser != browser_to_exclude &&
|
|
browser->GetType() == BrowserWindowInterface::TYPE_NORMAL &&
|
|
browser->GetProfile() == browser_to_exclude->GetProfile();
|
|
return !has_found;
|
|
});
|
|
return has_found;
|
|
}
|
|
|
|
void MoveTabsToExistingWindow(base::WeakPtr<BrowserWindowInterface> source,
|
|
base::WeakPtr<BrowserWindowInterface> target) {
|
|
if (!source.get() || !target.get()) {
|
|
return;
|
|
}
|
|
const ui::ListSelectionModel::SelectedIndices& sel =
|
|
source->GetTabStripModel()
|
|
->selection_model()
|
|
.GetListSelectionModel()
|
|
.selected_indices();
|
|
chrome::MoveTabsToExistingWindow(source.get(), target.get(),
|
|
std::vector<int>(sel.begin(), sel.end()));
|
|
}
|
|
|
|
void AddSelectedToNewGroup(BrowserWindowInterface* browser) {
|
|
TabStripModel* model = browser->GetTabStripModel();
|
|
const ui::ListSelectionModel::SelectedIndices& sel =
|
|
model->selection_model().GetListSelectionModel().selected_indices();
|
|
model->AddToNewGroup(std::vector<int>(sel.begin(), sel.end()));
|
|
}
|
|
|
|
// Multiphase commands:
|
|
|
|
void TogglePinTab(base::WeakPtr<BrowserWindowInterface> browser,
|
|
int tab_index,
|
|
int tab_session_id,
|
|
bool pin) {
|
|
if (!DoesTabAtIndexMatchSessionId(browser, tab_index, tab_session_id)) {
|
|
return;
|
|
}
|
|
browser->GetTabStripModel()->SetTabPinned(tab_index, pin);
|
|
}
|
|
|
|
std::unique_ptr<CommandItem> CreatePinTabItem(const TabMatch& match,
|
|
BrowserWindowInterface* browser,
|
|
bool pin) {
|
|
auto item = match.ToCommandItem();
|
|
item->command = base::BindOnce(&TogglePinTab, browser->GetWeakPtr(),
|
|
match.index, match.session_id, pin);
|
|
return item;
|
|
}
|
|
|
|
CommandSource::CommandResults TogglePinTabCommandsForTabsMatching(
|
|
BrowserWindowInterface* browser,
|
|
bool pin,
|
|
const std::u16string& input) {
|
|
CommandSource::CommandResults results;
|
|
TabSearchOptions options;
|
|
if (pin) {
|
|
options.only_unpinned = true;
|
|
} else {
|
|
options.only_pinned = true;
|
|
}
|
|
for (auto& match : TabsMatchingInput(browser, input, options)) {
|
|
results.push_back(CreatePinTabItem(match, browser, pin));
|
|
}
|
|
return results;
|
|
}
|
|
|
|
std::unique_ptr<CommandItem> CreateMoveTabsToWindowItem(
|
|
BrowserWindowInterface* source,
|
|
const WindowMatch& match) {
|
|
auto item = match.ToCommandItem();
|
|
item->command =
|
|
base::BindOnce(&MoveTabsToExistingWindow, source->GetWeakPtr(),
|
|
match.browser->GetWeakPtr());
|
|
return item;
|
|
}
|
|
|
|
CommandSource::CommandResults MoveTabsToWindowCommandsForWindowsMatching(
|
|
BrowserWindowInterface* source,
|
|
const std::u16string& input) {
|
|
CommandSource::CommandResults results;
|
|
// Add "New Window", if appropriate. It should score highest with no input.
|
|
std::u16string new_window_title =
|
|
base::UTF8ToUTF16(commands::GetCommandName(IDC_NEW_WINDOW));
|
|
std::unique_ptr<CommandItem> item;
|
|
if (input.empty()) {
|
|
item = std::make_unique<CommandItem>(new_window_title, .99,
|
|
std::vector<gfx::Range>());
|
|
} else {
|
|
FuzzyFinder finder(input);
|
|
std::vector<gfx::Range> ranges;
|
|
item = ItemForTitle(new_window_title, finder, ranges);
|
|
}
|
|
if (item) {
|
|
item->entity_type = CommandItem::Entity::kWindow;
|
|
item->command = base::BindOnce(&chrome::MoveActiveTabToNewWindow,
|
|
base::Unretained(source));
|
|
results.push_back(std::move(item));
|
|
}
|
|
for (auto& match : WindowsMatchingInput(source, input)) {
|
|
results.push_back(CreateMoveTabsToWindowItem(source, match));
|
|
}
|
|
return results;
|
|
}
|
|
|
|
void AddTabsToGroup(base::WeakPtr<BrowserWindowInterface> browser,
|
|
tab_groups::TabGroupId group) {
|
|
if (!browser.get()) {
|
|
return;
|
|
}
|
|
const ui::ListSelectionModel::SelectedIndices& sel =
|
|
browser->GetTabStripModel()
|
|
->selection_model()
|
|
.GetListSelectionModel()
|
|
.selected_indices();
|
|
browser->GetTabStripModel()->AddToExistingGroup(
|
|
std::vector<int>(sel.begin(), sel.end()), group);
|
|
}
|
|
|
|
CommandSource::CommandResults AddTabsToGroupCommandsForGroupsMatching(
|
|
BrowserWindowInterface* browser,
|
|
const std::u16string& input) {
|
|
CommandSource::CommandResults results;
|
|
TabStripModel* tab_strip_model = browser->GetTabStripModel();
|
|
// Add "New Group", if appropriate. It should score highest with no input.
|
|
std::u16string new_group_title =
|
|
l10n_util::GetStringUTF16(IDS_TAB_CXMENU_SUBMENU_NEW_GROUP);
|
|
std::unique_ptr<CommandItem> item;
|
|
if (input.empty()) {
|
|
item = std::make_unique<CommandItem>(new_group_title, .99,
|
|
std::vector<gfx::Range>());
|
|
} else {
|
|
FuzzyFinder finder(input);
|
|
std::vector<gfx::Range> ranges;
|
|
item = ItemForTitle(new_group_title, finder, ranges);
|
|
}
|
|
if (item) {
|
|
item->entity_type = CommandItem::Entity::kGroup;
|
|
item->command =
|
|
base::BindOnce(&AddSelectedToNewGroup, base::Unretained(browser));
|
|
results.push_back(std::move(item));
|
|
}
|
|
for (auto& match : GroupsMatchingInput(
|
|
browser, input, IneligibleGroupForSelected(tab_strip_model))) {
|
|
auto command_item = match.ToCommandItem();
|
|
command_item->command =
|
|
base::BindOnce(&AddTabsToGroup, browser->GetWeakPtr(), match.group);
|
|
results.push_back(std::move(command_item));
|
|
}
|
|
return results;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TabCommandSource::TabCommandSource() = default;
|
|
TabCommandSource::~TabCommandSource() = default;
|
|
|
|
CommandSource::CommandResults TabCommandSource::GetCommands(
|
|
const std::u16string& input,
|
|
BrowserWindowInterface* browser) const {
|
|
CommandSource::CommandResults results;
|
|
FuzzyFinder finder(input);
|
|
std::vector<gfx::Range> ranges;
|
|
|
|
TabStripModel* tab_strip_model = browser->GetTabStripModel();
|
|
|
|
if (CanMoveTabsToExistingWindow(browser)) {
|
|
auto text = l10n_util::GetStringUTF16(IDS_COMMANDER_MOVE_TABS_TO_WINDOW);
|
|
if (auto item = ItemForTitle(text, finder, ranges)) {
|
|
item->command = std::make_pair(
|
|
text, base::BindRepeating(&MoveTabsToWindowCommandsForWindowsMatching,
|
|
base::Unretained(browser)));
|
|
results.push_back(std::move(item));
|
|
}
|
|
}
|
|
|
|
auto add_tab_to_existing_group =
|
|
l10n_util::GetStringUTF16(IDS_COMMANDER_ADD_TABS_TO_EXISTING_GROUP);
|
|
if (auto item = ItemForTitle(add_tab_to_existing_group, finder, ranges)) {
|
|
item->command = std::make_pair(
|
|
add_tab_to_existing_group,
|
|
base::BindRepeating(&AddTabsToGroupCommandsForGroupsMatching,
|
|
base::Unretained(browser)));
|
|
results.push_back(std::move(item));
|
|
}
|
|
|
|
if (HasUnpinnedTabs(tab_strip_model)) {
|
|
auto text = l10n_util::GetStringUTF16(IDS_COMMANDER_PIN_TAB);
|
|
if (auto item = ItemForTitle(text, finder, ranges)) {
|
|
item->command = std::make_pair(
|
|
text, base::BindRepeating(&TogglePinTabCommandsForTabsMatching,
|
|
base::Unretained(browser), true));
|
|
results.push_back((std::move(item)));
|
|
}
|
|
}
|
|
|
|
if (HasPinnedTabs(tab_strip_model)) {
|
|
auto text = l10n_util::GetStringUTF16(IDS_COMMANDER_UNPIN_TAB);
|
|
if (auto item = ItemForTitle(text, finder, ranges)) {
|
|
item->command = std::make_pair(
|
|
text, base::BindRepeating(&TogglePinTabCommandsForTabsMatching,
|
|
base::Unretained(browser), false));
|
|
results.push_back((std::move(item)));
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
} // namespace commander
|