Files
brave-core/browser/ui/browser_commands.cc
Alex 429c51348f [VPN 2.0] Add GN buildflags for VPN Architectures 1.0 and 2.0 (#36787)
To add a new BraveVpnService implementation based on Architecture 2.0,
which must co-exist with Architecture 1.0 for quite a while, we need
to split service's interface and implementation. All the external
components will keep accessing VPN service via the BraveVpnService
interface, but the implementation mostly goes into BraveVpnServiceImpl.

This change revisits the approach how Brave VPN is enabled in GN.
New flags are introduced: enable_brave_vpn_v1 and enable_brave_vpn_v2.
Currently, only V1 architecture is implemented, hence, the GN flag
enable_brave_vpn_v1 is enabled by default, and enable_brave_vpn_v2
is disabled by default. enable_brave_vpn becomes a dependent flag:
it is enabled when either enable_brave_vpn_v1, or enable_brave_vpn_v2,
or both are enabled.

Implements part of https://github.com/brave/brave-browser/issues/54597
2026-05-28 21:56:45 +01:00

1297 lines
40 KiB
C++

/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */
#include "brave/browser/ui/browser_commands.h"
#include <algorithm>
#include <memory>
#include <numeric>
#include <stack>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/file_util_icu.h"
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "brave/app/brave_command_ids.h"
#include "brave/browser/brave_shields/brave_shields_tab_helper.h"
#include "brave/browser/debounce/debounce_service_factory.h"
#include "brave/browser/ui/bookmark/brave_bookmark_prefs.h"
#include "brave/browser/ui/brave_browser.h"
#include "brave/browser/ui/focus_mode/focus_mode_controller.h"
#include "brave/browser/ui/sidebar/sidebar_service_factory.h"
#include "brave/browser/ui/tabs/brave_tab_prefs.h"
#include "brave/browser/ui/tabs/brave_tab_strip_model.h"
#include "brave/browser/ui/views/frame/vertical_tabs/vertical_tab_strip_region_view.h"
#include "brave/browser/ui/views/frame/vertical_tabs/vertical_tab_strip_widget_delegate_view.h"
#include "brave/browser/ui/views/tabs/vertical_tab_utils.h"
#include "brave/browser/url_sanitizer/url_sanitizer_service_factory.h"
#include "brave/components/brave_vpn/common/buildflags/buildflags.h"
#include "brave/components/constants/pref_names.h"
#include "brave/components/containers/buildflags/buildflags.h"
#include "brave/components/debounce/core/browser/debounce_service.h"
#include "brave/components/query_filter/browser/utils.h"
#include "brave/components/sidebar/browser/sidebar_service.h"
#include "brave/components/speedreader/common/buildflags/buildflags.h"
#include "brave/components/tor/buildflags/buildflags.h"
#include "brave/components/url_sanitizer/core/browser/url_sanitizer_service.h"
#include "chrome/browser/bookmarks/bookmark_html_writer.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_metrics.h"
#include "chrome/browser/profiles/profile_window.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/navigator/browser_navigator.h"
#include "chrome/browser/ui/navigator/browser_navigator_params.h"
#include "chrome/browser/ui/profiles/profile_picker.h"
#include "chrome/browser/ui/tabs/features.h"
#include "chrome/browser/ui/tabs/tab_enums.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/tabs/tab_model.h"
#include "chrome/browser/ui/tabs/tab_muted_utils.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_utils.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/split_tabs/split_tab_visual_data.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "components/tabs/public/tab_group.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/window_open_disposition.h"
#include "ui/shell_dialogs/select_file_dialog.h"
#include "ui/shell_dialogs/select_file_policy.h"
#include "ui/shell_dialogs/selected_file_info.h"
#include "url/origin.h"
#if defined(TOOLKIT_VIEWS)
#include "brave/browser/ui/views/frame/brave_browser_view.h"
#include "chrome/browser/ui/side_panel/side_panel_entry.h"
#include "chrome/browser/ui/side_panel/side_panel_entry_id.h"
#include "chrome/browser/ui/side_panel/side_panel_enums.h"
#include "chrome/browser/ui/side_panel/side_panel_ui.h"
#endif
#if BUILDFLAG(ENABLE_SPEEDREADER)
#include "brave/browser/speedreader/speedreader_service_factory.h"
#include "brave/browser/ui/speedreader/speedreader_tab_helper.h"
#include "brave/components/speedreader/speedreader_service.h"
#endif
#if BUILDFLAG(ENABLE_TOR)
#include "brave/browser/tor/tor_profile_manager.h"
#include "brave/browser/tor/tor_profile_service_factory.h"
#include "brave/components/tor/tor_profile_service.h"
#endif
#if BUILDFLAG(ENABLE_BRAVE_VPN)
#include "brave/browser/brave_vpn/brave_vpn_service_factory.h"
#include "brave/browser/ui/brave_vpn/brave_vpn_controller.h"
#include "brave/components/brave_vpn/browser/brave_vpn_service.h"
#include "brave/components/brave_vpn/common/brave_vpn_constants.h"
#include "brave/components/brave_vpn/common/brave_vpn_utils.h"
#include "brave/components/brave_vpn/common/pref_names.h"
#endif // BUILDFLAG(ENABLE_BRAVE_VPN)
#if BUILDFLAG(ENABLE_BRAVE_VPN_V1) && BUILDFLAG(IS_WIN)
#include "brave/browser/brave_vpn/win/storage_utils.h"
#include "brave/browser/brave_vpn/win/wireguard_utils_win.h"
#endif // BUILDFLAG(ENABLE_BRAVE_VPN_V1) && BUILDFLAG(IS_WIN)
#if BUILDFLAG(ENABLE_COMMANDER)
#include "brave/browser/ui/commander/commander_service.h"
#include "brave/browser/ui/commander/commander_service_factory.h"
#endif
#if BUILDFLAG(ENABLE_CONTAINERS)
#include "brave/browser/containers/containers_service_factory.h"
#include "brave/browser/ui/tabs/public/brave_tab_features.h"
#include "brave/browser/ui/views/page_action/partitioned_storage_page_action_controller.h"
#include "brave/components/containers/content/browser/storage_partition_utils.h"
#include "brave/components/containers/core/browser/containers_service.h"
#include "components/tabs/public/tab_interface.h"
#if defined(TOOLKIT_VIEWS)
#include "chrome/browser/ui/views/frame/browser_view.h"
#endif
#endif
using content::WebContents;
namespace brave {
namespace {
bool CanTakeTabs(const Browser* from, const Browser* to) {
return from != to && from->type() == Browser::TYPE_NORMAL &&
!from->IsAttemptingToCloseBrowser() && !from->is_delete_scheduled() &&
to->profile() == from->profile();
}
std::vector<int> GetSelectedIndices(Browser* browser) {
auto* model = browser->tab_strip_model();
const auto& selection = model->selection_model().GetListSelectionModel();
auto indices = std::vector<int>(selection.selected_indices().begin(),
selection.selected_indices().end());
CHECK(!indices.empty())
<< "Returning empty indices could case infinite recursion";
return indices;
}
} // namespace
/**
* @note This function creates a default filename like
* `bookmarks_2025_12_15.html`, for example, if the date is December 15, 2025.
*
* @note This function mimics the behavior of a function with the same name in
* the Chromium source code.
*
* @see
* https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc;l=205-222?q=IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME
*/
base::FilePath GetDefaultFilepathForBookmarkExport() {
std::string bookmarks_yyyy_MM_dd = l10n_util::GetStringFUTF8(
IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
base::UTF8ToUTF16(base::UnlocalizedTimeFormatWithPattern(
base::Time::Now(), "yyyy_MM_dd")));
base::FilePath path = base::FilePath::FromUTF8Unsafe(bookmarks_yyyy_MM_dd);
base::FilePath::StringType path_str = path.value();
base::i18n::ReplaceIllegalCharactersInPath(&path_str, '_');
base::FilePath default_path;
base::PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_path);
return default_path.Append(base::FilePath(path_str));
}
/**
* @class BookmarksExportListener
* @brief A listener class for handling bookmark export file selection.
*
* This class is responsible for showing a file dialog to the user for selecting
* the location to save exported bookmarks.
*
* @note The lifetime of this class is tied to the FileSelected dialog. It will
* be automatically deleted when the dialog is closed, a file is selected, or
* the dialog is cancelled.
*/
class BookmarksExportListener : public ui::SelectFileDialog::Listener {
public:
explicit BookmarksExportListener(Profile* profile)
: profile_(profile),
file_selector_(ui::SelectFileDialog::Create(this, nullptr)) {}
void FileSelected(const ui::SelectedFileInfo& file, int index) override {
bookmark_html_writer::WriteBookmarks(profile_, file.file_path,
base::DoNothing());
file_selector_->ListenerDestroyed();
delete this;
}
void FileSelectionCanceled() override {
file_selector_->ListenerDestroyed();
delete this;
}
void ShowFileDialog(Browser* browser) {
ui::SelectFileDialog::FileTypeInfo file_types;
// Only show HTML files in the file dialog.
file_types.extensions.push_back({FILE_PATH_LITERAL("html")});
file_selector_->SelectFile(
ui::SelectFileDialog::SELECT_SAVEAS_FILE,
l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_MENU_EXPORT),
GetDefaultFilepathForBookmarkExport(), &file_types, 1,
FILE_PATH_LITERAL("html"), browser->window()->GetNativeWindow(),
nullptr);
}
private:
raw_ptr<Profile> profile_;
scoped_refptr<ui::SelectFileDialog> file_selector_;
};
#if BUILDFLAG(ENABLE_TOR)
void NewOffTheRecordWindowTor(Browser* browser) {
CHECK(browser);
NewOffTheRecordWindowTor(browser->profile());
}
void NewOffTheRecordWindowTor(Profile* profile) {
CHECK(profile);
if (profile->IsTor()) {
chrome::OpenEmptyWindow(profile);
return;
}
TorProfileManager::SwitchToTorProfile(profile);
}
void NewTorConnectionForSite(BrowserWindowInterface* browser) {
Profile* profile = browser->GetProfile();
DCHECK(profile);
tor::TorProfileService* service =
TorProfileServiceFactory::GetForContext(profile);
DCHECK(service);
WebContents* current_tab =
browser->GetTabStripModel()->GetActiveWebContents();
if (!current_tab) {
return;
}
service->SetNewTorCircuit(current_tab);
}
#endif
void MaybeDistillAndShowSpeedreaderBubble(Browser* browser) {
#if BUILDFLAG(ENABLE_SPEEDREADER)
WebContents* contents = browser->tab_strip_model()->GetActiveWebContents();
if (!contents) {
return;
}
if (auto* tab_helper =
speedreader::SpeedreaderTabHelper::FromWebContents(contents)) {
tab_helper->ProcessIconClick();
}
#endif // BUILDFLAG(ENABLE_SPEEDREADER)
}
void ShowBraveVPNBubble(Browser* browser) {
#if BUILDFLAG(ENABLE_BRAVE_VPN)
browser->GetFeatures().brave_vpn_controller()->ShowBraveVPNBubble();
#endif
}
void ToggleBraveVPNTrayIcon() {
#if BUILDFLAG(ENABLE_BRAVE_VPN_V1) && BUILDFLAG(IS_WIN)
brave_vpn::EnableVPNTrayIcon(!brave_vpn::IsVPNTrayIconEnabled());
if (brave_vpn::IsVPNTrayIconEnabled()) {
brave_vpn::wireguard::ShowBraveVpnStatusTrayIcon();
}
#endif
}
void ToggleBraveVPNButton(Browser* browser) {
#if BUILDFLAG(ENABLE_BRAVE_VPN)
auto* prefs = browser->profile()->GetPrefs();
const bool show = prefs->GetBoolean(brave_vpn::prefs::kBraveVPNShowButton);
prefs->SetBoolean(brave_vpn::prefs::kBraveVPNShowButton, !show);
#endif
}
void OpenBraveVPNUrls(Browser* browser, int command_id) {
#if BUILDFLAG(ENABLE_BRAVE_VPN)
auto* vpn_service =
brave_vpn::BraveVpnServiceFactory::GetForProfile(browser->profile());
CHECK(vpn_service);
std::string target_url;
switch (command_id) {
case IDC_SEND_BRAVE_VPN_FEEDBACK:
target_url = brave_vpn::kFeedbackUrl;
break;
case IDC_ABOUT_BRAVE_VPN:
target_url = brave_vpn::kAboutUrl;
break;
case IDC_MANAGE_BRAVE_VPN_PLAN:
target_url =
brave_vpn::GetManageUrl(vpn_service->GetCurrentEnvironment());
break;
default:
NOTREACHED() << "This should only be called with one of the above VPN "
"commands. (was "
<< command_id << ")";
}
chrome::AddTabAt(browser, GURL(target_url), -1, true);
#endif
}
void ToggleAIChat(Browser* browser) {
#if defined(TOOLKIT_VIEWS)
SidePanelUI* side_panel_ui = browser->GetFeatures().side_panel_ui();
side_panel_ui->Toggle(SidePanelEntry::Key(SidePanelEntryId::kChatUI),
SidePanelOpenTrigger::kToolbarButton);
#endif
}
#if BUILDFLAG(ENABLE_BRAVE_WALLET)
void ShowWalletBubble(Browser* browser) {
#if defined(TOOLKIT_VIEWS)
static_cast<BraveBrowserView*>(browser->window())->CreateWalletBubble();
#endif
}
void CloseWalletBubble(Browser* browser) {
#if defined(TOOLKIT_VIEWS)
static_cast<BraveBrowserView*>(browser->window())->CloseWalletBubble();
#endif
}
#endif
void CopySanitizedURL(BrowserWindowInterface* browser, const GURL& url) {
if (!browser || !browser->GetProfile()) {
return;
}
GURL sanitized_url = brave::URLSanitizerServiceFactory::GetForBrowserContext(
browser->GetProfile())
->SanitizeURL(url);
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteText(base::UTF8ToUTF16(sanitized_url.spec()));
}
// Copies an url cleared through:
// - Debouncer (potentially debouncing many levels)
// - Query filter
// - URLSanitizerService
void CopyLinkWithStrictCleaning(Browser* browser, const GURL& url) {
if (!browser || !browser->profile()) {
return;
}
DCHECK(url.SchemeIsHTTPOrHTTPS());
GURL final_url;
// Apply debounce rules.
auto* debounce_service =
debounce::DebounceServiceFactory::GetForBrowserContext(
browser->profile());
if (debounce_service && !debounce_service->Debounce(url, &final_url)) {
VLOG(1) << "Unable to apply debounce rules";
final_url = url;
}
// Apply query filters.
auto filtered_url = query_filter::ApplyQueryFilter(final_url);
if (filtered_url.has_value()) {
final_url = filtered_url.value();
}
// Sanitize url.
final_url = brave::URLSanitizerServiceFactory::GetForBrowserContext(
browser->profile())
->SanitizeURL(final_url);
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteText(base::UTF8ToUTF16(final_url.spec()));
}
void ToggleWindowTitleVisibilityForVerticalTabs(Browser* browser) {
auto* prefs = browser->profile()->GetOriginalProfile()->GetPrefs();
prefs->SetBoolean(
brave_tabs::kVerticalTabsShowTitleOnWindow,
!prefs->GetBoolean(brave_tabs::kVerticalTabsShowTitleOnWindow));
}
void ToggleVerticalTabStrip(Browser* browser) {
if (!tabs::utils::IsVerticalTabToggleEnabled(browser)) {
return;
}
auto* profile = browser->profile()->GetOriginalProfile();
auto* prefs = profile->GetPrefs();
const bool was_using_vertical_tab_strip =
prefs->GetBoolean(brave_tabs::kVerticalTabsEnabled);
prefs->SetBoolean(brave_tabs::kVerticalTabsEnabled,
!was_using_vertical_tab_strip);
}
void ToggleVerticalTabStripFloatingMode(Browser* browser) {
auto* prefs = browser->profile()->GetOriginalProfile()->GetPrefs();
prefs->SetBoolean(
brave_tabs::kVerticalTabsFloatingEnabled,
!prefs->GetBoolean(brave_tabs::kVerticalTabsFloatingEnabled));
}
void ToggleVerticalTabStripExpanded(Browser* browser) {
auto* prefs = browser->profile()->GetPrefs();
bool expanded_state_per_window =
prefs->GetBoolean(brave_tabs::kVerticalTabsExpandedStatePerWindow);
// Toggle preference if all tabs share the same state (derived from prefs)
if (!expanded_state_per_window) {
prefs->SetBoolean(brave_tabs::kVerticalTabsCollapsed,
!prefs->GetBoolean(brave_tabs::kVerticalTabsCollapsed));
return;
}
// Otherwise, retrieve current vertical tab strip region view
auto* browser_view = static_cast<BraveBrowserView*>(browser->window());
if (!browser_view) {
return;
}
auto* vtsr_view = browser_view->vertical_tab_strip_widget_delegate_view()
->vertical_tab_strip_region_view();
if (!vtsr_view) {
return;
}
// toggle state for only this vtsr view
vtsr_view->ToggleState();
}
void ToggleActiveTabAudioMute(Browser* browser) {
WebContents* contents = browser->tab_strip_model()->GetActiveWebContents();
if (!contents) {
return;
}
bool mute_tab = !contents->IsAudioMuted();
SetTabAudioMuted(contents, mute_tab, TabMutedReason::kAudioIndicator,
std::string());
}
void ToggleSidebarPosition(Browser* browser) {
auto* prefs = browser->profile()->GetPrefs();
prefs->SetBoolean(prefs::kSidePanelHorizontalAlignment,
!prefs->GetBoolean(prefs::kSidePanelHorizontalAlignment));
}
void ToggleSidebar(Browser* browser) {
if (!browser) {
return;
}
if (auto* brave_browser_window =
BraveBrowserWindow::From(browser->window())) {
brave_browser_window->ToggleSidebar();
}
}
bool HasSelectedURL(Browser* browser) {
if (!browser) {
return false;
}
auto* brave_browser_window = BraveBrowserWindow::From(browser->window());
return brave_browser_window && brave_browser_window->HasSelectedURL();
}
void CleanAndCopySelectedURL(Browser* browser) {
if (!browser) {
return;
}
auto* brave_browser_window = BraveBrowserWindow::From(browser->window());
if (brave_browser_window) {
brave_browser_window->CleanAndCopySelectedURL();
}
}
void ToggleFocusMode(BrowserWindowInterface* browser) {
if (auto* controller = browser->GetFeatures().focus_mode_controller()) {
controller->ToggleEnabled();
}
}
void ToggleShieldsEnabled(Browser* browser) {
if (!browser) {
return;
}
auto* contents = browser->tab_strip_model()->GetActiveWebContents();
if (!contents) {
return;
}
auto* shields =
brave_shields::BraveShieldsTabHelper::FromWebContents(contents);
if (!shields) {
return;
}
shields->SetBraveShieldsEnabled(!shields->GetBraveShieldsEnabled());
}
void ToggleJavascriptEnabled(Browser* browser) {
if (!browser) {
return;
}
auto* contents = browser->tab_strip_model()->GetActiveWebContents();
if (!contents) {
return;
}
auto* shields =
brave_shields::BraveShieldsTabHelper::FromWebContents(contents);
if (!shields) {
return;
}
shields->SetIsNoScriptEnabled(!shields->GetNoScriptEnabled());
}
#if BUILDFLAG(ENABLE_COMMANDER)
void ToggleCommander(Browser* browser) {
if (auto* commander_service =
commander::CommanderServiceFactory::GetForBrowserContext(
browser->profile())) {
commander_service->Toggle();
}
}
#endif
#if BUILDFLAG(ENABLE_PLAYLIST_WEBUI)
void ShowPlaylistBubble(Browser* browser) {
BraveBrowserWindow::From(browser->window())->ShowPlaylistBubble();
}
#endif
#if BUILDFLAG(ENABLE_BRAVE_WAYBACK_MACHINE)
void ShowWaybackMachineBubble(Browser* browser) {
BraveBrowserWindow::From(browser->window())->ShowWaybackMachineBubble();
}
#endif
void GroupTabsOnCurrentOrigin(Browser* browser) {
auto url =
browser->tab_strip_model()->GetActiveWebContents()->GetVisibleURL();
auto origin = url::Origin::Create(url);
std::vector<int> group_indices;
for (int index = 0; index < browser->tab_strip_model()->count(); ++index) {
auto* tab = browser->tab_strip_model()->GetWebContentsAt(index);
auto tab_origin = url::Origin::Create(tab->GetVisibleURL());
if (origin.IsSameOriginWith(tab_origin)) {
group_indices.push_back(index);
}
}
auto group_id = browser->tab_strip_model()->AddToNewGroup(group_indices);
auto* group =
browser->tab_strip_model()->group_model()->GetTabGroup(group_id);
auto data = *group->visual_data();
data.SetTitle(base::UTF8ToUTF16(origin.host()));
group->SetVisualData(data);
}
void MoveGroupToNewWindow(Browser* browser) {
auto* tsm = browser->tab_strip_model();
auto current_group_id = tsm->GetTabGroupForTab(tsm->active_index());
if (!current_group_id.has_value()) {
return;
}
tsm->delegate()->MoveGroupToNewWindow(current_group_id.value());
}
bool IsInGroup(Browser* browser) {
if (!browser) {
return false;
}
auto* tsm = browser->tab_strip_model();
auto current_group_id = tsm->GetTabGroupForTab(tsm->active_index());
return current_group_id.has_value();
}
bool HasUngroupedTabs(Browser* browser) {
if (!browser) {
return false;
}
auto* tsm = browser->tab_strip_model();
for (int i = 0; i < tsm->count(); ++i) {
if (!tsm->GetTabGroupForTab(i)) {
return true;
}
}
return false;
}
void GroupUngroupedTabs(Browser* browser) {
if (!browser) {
return;
}
auto* tsm = browser->tab_strip_model();
std::vector<int> group_indices;
for (int i = 0; i < tsm->count(); ++i) {
if (tsm->GetTabGroupForTab(i)) {
continue;
}
group_indices.push_back(i);
}
if (group_indices.empty()) {
return;
}
tsm->AddToNewGroup(group_indices);
}
void UngroupCurrentGroup(Browser* browser) {
if (!browser) {
return;
}
auto* tsm = browser->tab_strip_model();
auto group_id = tsm->GetTabGroupForTab(tsm->active_index());
if (!group_id) {
return;
}
auto* group = tsm->group_model()->GetTabGroup(group_id.value());
std::vector<int> indices(group->tab_count());
tabs::TabInterface* first_tab = group->GetFirstTab();
DCHECK(first_tab);
std::iota(indices.begin(), indices.end(), tsm->GetIndexOfTab(first_tab));
tsm->RemoveFromGroup(indices);
}
void RemoveTabFromGroup(Browser* browser) {
if (!browser) {
return;
}
auto* tsm = browser->tab_strip_model();
tsm->RemoveFromGroup({tsm->active_index()});
}
void NameGroup(Browser* browser) {
if (!browser) {
return;
}
auto* tsm = browser->tab_strip_model();
auto group_id = tsm->GetTabGroupForTab(tsm->active_index());
if (!group_id) {
return;
}
tsm->OpenTabGroupEditor(*group_id);
}
void NewTabInGroup(Browser* browser) {
if (!browser) {
return;
}
auto* tsm = browser->tab_strip_model();
auto group_id = tsm->GetTabGroupForTab(tsm->active_index());
if (!group_id) {
return;
}
const auto tabs = tsm->group_model()->GetTabGroup(*group_id)->ListTabs();
tsm->delegate()->AddTabAt(GURL(), tabs.end(), true, *group_id);
}
bool CanUngroupAllTabs(Browser* browser) {
if (!browser) {
return false;
}
auto* tsm = browser->tab_strip_model();
for (int i = 0; i < tsm->count(); ++i) {
if (tsm->GetTabGroupForTab(i)) {
return true;
}
}
return false;
}
void UngroupAllTabs(Browser* browser) {
if (!browser) {
return;
}
std::vector<int> indices(browser->tab_strip_model()->count());
std::iota(indices.begin(), indices.end(), 0);
browser->tab_strip_model()->RemoveFromGroup(indices);
}
void ToggleGroupExpanded(Browser* browser) {
if (!browser) {
return;
}
auto* tsm = browser->tab_strip_model();
auto group_id = tsm->GetTabGroupForTab(tsm->active_index());
if (!group_id) {
return;
}
auto* group = tsm->group_model()->GetTabGroup(*group_id);
auto* vd = group->visual_data();
tab_groups::TabGroupVisualData vd_update(vd->title(), vd->color(),
!vd->is_collapsed());
group->SetVisualData(vd_update);
}
void CloseUngroupedTabs(Browser* browser) {
if (!browser) {
return;
}
auto* tsm = static_cast<BraveTabStripModel*>(browser->tab_strip_model());
CHECK(tsm);
std::vector<int> indices;
for (int i = tsm->count() - 1; i >= 0; --i) {
if (!tsm->GetTabGroupForTab(i)) {
indices.push_back(i);
}
}
for (const auto& index : indices) {
tsm->CloseWebContentsAt(index,
TabCloseTypes::CLOSE_USER_GESTURE |
TabCloseTypes::CLOSE_CREATE_HISTORICAL_TAB);
}
}
void CloseTabsNotInCurrentGroup(Browser* browser) {
if (!browser) {
return;
}
auto* tsm = static_cast<BraveTabStripModel*>(browser->tab_strip_model());
CHECK(tsm);
auto group_id = tsm->GetTabGroupForTab(tsm->active_index());
if (!group_id) {
return;
}
std::vector<int> indices;
for (int i = tsm->count() - 1; i >= 0; --i) {
if (tsm->GetTabGroupForTab(i) != *group_id) {
indices.push_back(i);
}
}
for (const auto& index : indices) {
tsm->CloseWebContentsAt(index,
TabCloseTypes::CLOSE_USER_GESTURE |
TabCloseTypes::CLOSE_CREATE_HISTORICAL_TAB);
}
}
void CloseGroup(Browser* browser) {
if (!browser) {
return;
}
auto* tsm = browser->tab_strip_model();
auto group_id = tsm->GetTabGroupForTab(tsm->active_index());
if (!group_id) {
return;
}
tsm->CloseAllTabsInGroup(*group_id);
}
bool CanBringAllTabs(Browser* browser) {
if (!browser) {
return false;
}
bool result = false;
GlobalBrowserCollection::GetInstance()->ForEach(
[browser, &result](BrowserWindowInterface* from) {
result = CanTakeTabs(from->GetBrowserForMigrationOnly(), browser);
return !result;
});
return result;
}
void BringAllTabs(Browser* browser) {
if (!browser) {
return;
}
// Find all browsers with the same profile
std::vector<Browser*> browsers;
GlobalBrowserCollection::GetInstance()->ForEach(
[&browsers, browser](BrowserWindowInterface* from) {
auto* from_deprecated = from->GetBrowserForMigrationOnly();
if (CanTakeTabs(from_deprecated, browser)) {
browsers.push_back(from_deprecated);
}
return true;
});
// Detach all tabs from other browsers
std::stack<std::unique_ptr<tabs::TabModel>> detached_pinned_tabs;
std::stack<std::unique_ptr<tabs::TabModel>> detached_unpinned_tabs;
const bool shared_pinned_tab_enabled =
base::FeatureList::IsEnabled(tabs::kBraveSharedPinnedTabs) &&
browser->profile()->GetPrefs()->GetBoolean(brave_tabs::kSharedPinnedTab);
base::flat_set<Browser*> browsers_to_close;
std::ranges::for_each(browsers, [&detached_pinned_tabs,
&detached_unpinned_tabs, &browsers_to_close,
shared_pinned_tab_enabled](auto* other) {
static_cast<BraveBrowser*>(other)
->set_ignore_enable_closing_last_tab_pref();
auto* tab_strip_model = other->tab_strip_model();
const int pinned_tab_count = tab_strip_model->IndexOfFirstNonPinnedTab();
for (int i = tab_strip_model->count() - 1; i >= 0; --i) {
const bool is_pinned = i < pinned_tab_count;
if (is_pinned && shared_pinned_tab_enabled) {
// SharedPinnedTabService is responsible for synchronizing pinned
// tabs, thus we shouldn't manually detach and attach tabs here.
// Meanwhile, the tab strips don't get empty when they have dummy
// contents, we should close the browsers manually.
browsers_to_close.insert(other);
continue;
}
auto tab = tab_strip_model->DetachTabAtForInsertion(i);
if (is_pinned) {
detached_pinned_tabs.push(std::move(tab));
} else {
detached_unpinned_tabs.push(std::move(tab));
}
}
});
// Insert pinned tabs
auto* tab_strip_model = browser->tab_strip_model();
while (!detached_pinned_tabs.empty()) {
tab_strip_model->InsertDetachedTabAt(
tab_strip_model->IndexOfFirstNonPinnedTab(),
std::move(detached_pinned_tabs.top()), AddTabTypes::ADD_PINNED);
detached_pinned_tabs.pop();
}
// Insert unpinned tabs
while (!detached_unpinned_tabs.empty()) {
tab_strip_model->InsertDetachedTabAt(
tab_strip_model->count(), std::move(detached_unpinned_tabs.top()),
AddTabTypes::ADD_NONE);
detached_unpinned_tabs.pop();
}
if (shared_pinned_tab_enabled) {
std::ranges::for_each(browsers_to_close,
[](auto* other) { other->window()->Close(); });
}
}
bool HasDuplicateTabs(Browser* browser) {
if (!browser) {
return false;
}
auto* tsm = browser->tab_strip_model();
auto* active_web_contents = tsm->GetActiveWebContents();
if (!active_web_contents) {
return false;
}
auto url = active_web_contents->GetVisibleURL();
for (int i = 0; i < tsm->count(); ++i) {
// Don't check the active tab.
if (tsm->active_index() == i) {
continue;
}
auto* tab = tsm->GetWebContentsAt(i);
if (tab->GetVisibleURL() == url) {
return true;
}
}
return false;
}
void CloseDuplicateTabs(Browser* browser) {
auto* tsm = browser->tab_strip_model();
auto url = tsm->GetActiveWebContents()->GetVisibleURL();
for (int i = tsm->count() - 1; i >= 0; --i) {
// Don't close the active tab.
if (tsm->active_index() == i) {
continue;
}
auto* tab = tsm->GetWebContentsAt(i);
if (tab->GetVisibleURL() == url) {
tab->Close();
}
}
}
bool CanCloseTabsToLeft(Browser* browser) {
auto* tsm = browser->tab_strip_model();
const auto& selection = tsm->selection_model();
if (selection.empty()) {
return false;
}
int left_selected =
*(selection.GetListSelectionModel().selected_indices().begin());
return left_selected > 0;
}
void CloseTabsToLeft(Browser* browser) {
auto* tsm = browser->tab_strip_model();
const auto& selection = tsm->selection_model();
if (selection.empty()) {
return;
}
int left_selected =
*(selection.GetListSelectionModel().selected_indices().begin());
for (int i = left_selected - 1; i >= 0; --i) {
tsm->CloseWebContentsAt(i, TabCloseTypes::CLOSE_CREATE_HISTORICAL_TAB |
TabCloseTypes::CLOSE_USER_GESTURE);
}
}
bool CanCloseUnpinnedTabs(Browser* browser) {
auto first_unpinned_index =
browser->tab_strip_model()->IndexOfFirstNonPinnedTab();
return first_unpinned_index < browser->tab_strip_model()->count();
}
void CloseUnpinnedTabs(Browser* browser) {
auto* tsm = browser->tab_strip_model();
DCHECK(CanCloseUnpinnedTabs(browser));
for (int i = tsm->count() - 1; i >= tsm->IndexOfFirstNonPinnedTab(); --i) {
tsm->CloseWebContentsAt(i, TabCloseTypes::CLOSE_CREATE_HISTORICAL_TAB |
TabCloseTypes::CLOSE_USER_GESTURE);
}
}
void AddAllTabsToNewGroup(Browser* browser) {
std::vector<int> indices(browser->tab_strip_model()->count());
std::iota(indices.begin(), indices.end(), 0);
browser->tab_strip_model()->AddToNewGroup(indices);
}
bool CanMuteAllTabs(Browser* browser, bool exclude_active) {
auto* tsm = browser->tab_strip_model();
for (int i = 0; i < tsm->count(); ++i) {
if (exclude_active && tsm->active_index() == i) {
continue;
}
auto* contents = tsm->GetWebContentsAt(i);
if (contents->IsCurrentlyAudible()) {
return true;
}
}
return false;
}
void MuteAllTabs(Browser* browser, bool exclude_active) {
auto* tsm = browser->tab_strip_model();
for (int i = 0; i < tsm->count(); ++i) {
if (exclude_active && tsm->active_index() == i) {
continue;
}
auto* contents = tsm->GetWebContentsAt(i);
if (contents->IsCurrentlyAudible()) {
contents->SetAudioMuted(true);
}
}
}
bool CanUnmuteAllTabs(Browser* browser) {
auto* tsm = browser->tab_strip_model();
for (int i = 0; i < tsm->count(); ++i) {
auto* contents = tsm->GetWebContentsAt(i);
if (contents->IsAudioMuted()) {
return true;
}
}
return false;
}
void UnmuteAllTabs(Browser* browser) {
auto* tsm = browser->tab_strip_model();
for (int i = 0; i < tsm->count(); ++i) {
auto* contents = tsm->GetWebContentsAt(i);
if (contents->IsAudioMuted()) {
contents->SetAudioMuted(false);
}
}
}
void ScrollTabToTop(Browser* browser) {
auto* contents = browser->tab_strip_model()->GetActiveWebContents();
contents->ScrollToTopOfDocument();
}
void ScrollTabToBottom(Browser* browser) {
auto* contents = browser->tab_strip_model()->GetActiveWebContents();
contents->ScrollToBottomOfDocument();
}
void ExportAllBookmarks(Browser* browser) {
(new BookmarksExportListener(browser->profile()))->ShowFileDialog(browser);
}
void ToggleAllBookmarksButtonVisibility(Browser* browser) {
auto* prefs = browser->profile()->GetPrefs();
prefs->SetBoolean(
brave::bookmarks::prefs::kShowAllBookmarksButton,
!prefs->GetBoolean(brave::bookmarks::prefs::kShowAllBookmarksButton));
}
bool CanOpenNewSplitTabsWithSideBySide(Browser* browser) {
auto* tab_strip_model = browser->tab_strip_model();
auto active_index = tab_strip_model->active_index();
if (active_index == TabStripModel::kNoTab) {
return false;
}
return !tab_strip_model->GetSplitForTab(active_index).has_value();
}
bool CanSplitTabsWithSideBySide(Browser* browser) {
auto* tab_strip_model = browser->tab_strip_model();
if (tab_strip_model->empty()) {
return false;
}
auto selected_indices = GetSelectedIndices(browser);
if (selected_indices.size() != 2) {
return false;
}
return std::ranges::none_of(selected_indices, [&](auto index) {
return tab_strip_model->GetSplitForTab(index).has_value();
});
}
bool IsSplitTabs(Browser* browser) {
auto* tab_strip_model = browser->tab_strip_model();
if (tab_strip_model->empty()) {
return false;
}
auto selected_indices = GetSelectedIndices(browser);
return std::ranges::any_of(selected_indices, [&](auto index) {
return tab_strip_model->GetSplitForTab(index).has_value();
});
}
void SplitTabsWithSideBySide(Browser* browser,
split_tabs::SplitTabCreatedSource source) {
CHECK(brave::CanSplitTabsWithSideBySide(browser));
auto selected_indices = GetSelectedIndices(browser);
CHECK(selected_indices.size() == 2);
auto* tab_strip_model = browser->tab_strip_model();
const auto active_index = tab_strip_model->active_index();
CHECK_NE(TabStripModel::kNoTab, active_index);
// selected indices must have active tab index.
CHECK(active_index == selected_indices[0] ||
active_index == selected_indices[1]);
const int non_active_index_from_selected_indices =
active_index == selected_indices[0] ? selected_indices[1]
: selected_indices[0];
tab_strip_model->AddToNewSplit(
{non_active_index_from_selected_indices},
split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kVertical),
source);
}
void RemoveSplitWithSideBySide(Browser* browser) {
auto selected_indices = GetSelectedIndices(browser);
auto* tab_strip_model = browser->tab_strip_model();
for (auto index : selected_indices) {
if (auto split_id = tab_strip_model->GetSplitForTab(index)) {
tab_strip_model->RemoveSplit(*split_id);
}
}
}
void SwapTabsInSplitWithSideBySide(Browser* browser) {
auto* tab_strip_model = browser->tab_strip_model();
auto active_index = tab_strip_model->active_index();
CHECK_NE(TabStripModel::kNoTab, active_index);
auto split_id = tab_strip_model->GetSplitForTab(active_index);
CHECK(split_id.has_value());
tab_strip_model->ReverseTabsInSplit(*split_id);
}
void ForcePasteInBrowser(Browser* browser) {
CHECK(browser);
auto* contents = browser->tab_strip_model()->GetActiveWebContents();
if (!contents) {
return;
}
ForcePasteInWebContents(contents);
}
void ForcePasteInWebContents(content::WebContents* web_contents) {
CHECK(web_contents);
// Check the WebContents is focused.
auto* frame = web_contents->GetFocusedFrame();
if (!frame) {
return;
}
std::u16string result;
std::optional<ui::DataTransferEndpoint> data = ui::DataTransferEndpoint(
frame->GetMainFrame()->GetLastCommittedURL(),
ui::DataTransferEndpointOptions{
.notify_if_restricted = true,
.off_the_record = frame->GetBrowserContext()->IsOffTheRecord()});
ui::Clipboard::GetForCurrentThread()->ReadText(
ui::ClipboardBuffer::kCopyPaste, data,
base::BindOnce(
[](base::WeakPtr<content::WebContents> web_contents,
std::u16string result) {
// If there's no text in the clipboard don't do anything.
if (!web_contents || result.empty()) {
return;
}
// Replace works just like Paste, but it doesn't trigger onpaste
// handlers
web_contents->Replace(result);
},
web_contents->GetWeakPtr()));
}
#if BUILDFLAG(ENABLE_CONTAINERS)
void OpenTabUrlsInContainer(BrowserWindowInterface* browser_window,
const std::vector<tabs::TabHandle>& tabs,
const containers::mojom::ContainerPtr& container) {
for (const auto& tab : tabs) {
const auto* tab_ptr = tab.Get();
if (!tab_ptr) {
LOG(ERROR) << "Tab is not valid";
continue;
}
const GURL& url = tab_ptr->GetContents()->GetLastCommittedURL();
OpenUrlInContainer(browser_window, url, container);
}
}
void OpenUrlInContainer(BrowserWindowInterface* browser_window,
const GURL& url,
const containers::mojom::ContainerPtr& container,
bool is_link) {
if (!url.is_valid()) {
LOG(ERROR) << "Url is not valid";
return;
}
CHECK(container);
NavigateParams params(
browser_window, url,
is_link ? ui::PAGE_TRANSITION_LINK : ui::PAGE_TRANSITION_TYPED);
params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
params.storage_partition_config = content::StoragePartitionConfig::Create(
browser_window->GetProfile(),
containers::kContainersStoragePartitionDomain, container->id,
browser_window->GetProfile()->IsOffTheRecord());
Navigate(&params);
}
void OpenTabUrlsWithoutContainer(BrowserWindowInterface* browser_window,
const std::vector<tabs::TabHandle>& tabs) {
for (const auto& tab : tabs) {
const auto* tab_ptr = tab.Get();
if (!tab_ptr) {
LOG(ERROR) << "Tab is not valid";
continue;
}
const GURL& url = tab_ptr->GetContents()->GetLastCommittedURL();
OpenUrlWithoutContainer(browser_window, url);
}
}
void OpenUrlWithoutContainer(BrowserWindowInterface* browser_window,
const GURL& url,
bool is_link) {
if (!url.is_valid()) {
LOG(ERROR) << "Url is not valid";
return;
}
NavigateParams params(
browser_window, url,
is_link ? ui::PAGE_TRANSITION_LINK : ui::PAGE_TRANSITION_TYPED);
params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
Navigate(&params);
}
void CreateTemporaryContainerAndOpenTabUrls(
BrowserWindowInterface* browser_window,
const std::vector<tabs::TabHandle>& tabs) {
auto* containers_service =
ContainersServiceFactory::GetForProfile(browser_window->GetProfile());
CHECK(containers_service);
OpenTabUrlsInContainer(
browser_window, tabs,
containers_service->CreateAndPersistTemporaryContainer());
}
void CreateTemporaryContainerAndOpenUrl(BrowserWindowInterface* browser_window,
const GURL& url,
bool is_link) {
CHECK(browser_window);
if (!url.is_valid()) {
LOG(ERROR) << "Url is not valid";
return;
}
auto* containers_service =
ContainersServiceFactory::GetForProfile(browser_window->GetProfile());
CHECK(containers_service);
OpenUrlInContainer(browser_window, url,
containers_service->CreateAndPersistTemporaryContainer(),
is_link);
}
void OpenContainerMenuOnPageActionView(BrowserWindowInterface* browser_window,
actions::ActionItem* item) {
if (!browser_window) {
DVLOG(1) << "Browser window is not valid";
return;
}
#if !defined(TOOLKIT_VIEWS)
return;
#else
BrowserView* const browser_view =
BrowserView::GetBrowserViewForBrowser(browser_window);
if (!browser_view || !browser_view->toolbar_button_provider()) {
DVLOG(1) << "Browser view or toolbar button provider is not valid";
return;
}
tabs::TabInterface* tab = browser_window->GetActiveTabInterface();
if (!tab) {
DVLOG(1) << "Tab is not valid";
return;
}
tabs::BraveTabFeatures* brave_tab_features =
tabs::BraveTabFeatures::FromTabFeatures(tab->GetTabFeatures());
CHECK(brave_tab_features);
page_actions::PartitionedStoragePageActionController* const controller =
brave_tab_features->partitioned_storage_page_action_controller();
CHECK(controller);
controller->ExecuteAction(browser_view->toolbar_button_provider(), item);
#endif
}
#endif
} // namespace brave