Declutter: Add app menu entrypoint
Also ensures the tab organization selector doesn't open declutter if it
is currently disabled.
Bug: 369612291
Change-Id: I9ac885c500bc3102b6e5f6533aa8404913e798de
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5892285
Commit-Queue: Emily Shack <emshack@chromium.org>
Reviewed-by: Shibalik Mohapatra <shibalik@chromium.org>
Reviewed-by: Greg Thompson <grt@chromium.org>
Reviewed-by: Dana Fried <dfried@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1363342}
diff --git a/chrome/app/chrome_command_ids.h b/chrome/app/chrome_command_ids.h
index 32e36fe9..5475c86c 100644
--- a/chrome/app/chrome_command_ids.h
+++ b/chrome/app/chrome_command_ids.h
@@ -135,6 +135,7 @@
#define IDC_SHOW_ADDRESSES 35043
#define IDC_ORGANIZE_TABS 35044
#define IDC_CREATE_NEW_TAB_GROUP 35045
+#define IDC_DECLUTTER_TABS 35046
// Page-manipulation commands that target a specified tab, which may not be the
// active one.
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index eceb4c2..4135fa6 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -9699,7 +9699,18 @@
=1 {Visited 1 day ago}
other {Visited # days ago}}
</message>
-
+ <if expr="use_titlecase">
+ <then>
+ <message name="IDS_DECLUTTER_MENU" desc="In Title Case: The text label for the declutter app menu item.">
+ Close Unused Tabs
+ </message>
+ </then>
+ <else>
+ <message name="IDS_DECLUTTER_MENU" desc="The text label for the declutter app menu item.">
+ Close unused tabs
+ </message>
+ </else>
+ </if>
<!-- Strings for tab organization -->
<message name="IDS_TOOLTIP_TAB_ORGANIZE" desc="The tooltip for the Tab Organization button.">
Organize tabs?
diff --git a/chrome/app/generated_resources_grd/IDS_DECLUTTER_MENU.png.sha1 b/chrome/app/generated_resources_grd/IDS_DECLUTTER_MENU.png.sha1
new file mode 100644
index 0000000..e64771c
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_DECLUTTER_MENU.png.sha1
@@ -0,0 +1 @@
+894ae32e7be942fed9b2be7567c66e78344148e0
\ No newline at end of file
diff --git a/chrome/browser/resources/tab_search/tab_organization_selector.ts b/chrome/browser/resources/tab_search/tab_organization_selector.ts
index 51dcdd8..78c2f02 100644
--- a/chrome/browser/resources/tab_search/tab_organization_selector.ts
+++ b/chrome/browser/resources/tab_search/tab_organization_selector.ts
@@ -92,7 +92,13 @@
}
private updateSelectedFeature_(feature: TabOrganizationFeature) {
- if (feature !== TabOrganizationFeature.kNone) {
+ if (feature === TabOrganizationFeature.kNone) {
+ return;
+ }
+ if (feature === TabOrganizationFeature.kDeclutter &&
+ this.disableDeclutter_) {
+ this.selectedState_ = TabOrganizationFeature.kSelector;
+ } else {
this.selectedState_ = feature;
}
}
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index 86af7456..884e6dd 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -685,6 +685,9 @@
case IDC_ORGANIZE_TABS:
StartTabOrganizationRequest(browser_);
break;
+ case IDC_DECLUTTER_TABS:
+ ShowTabDeclutter(browser_);
+ break;
case IDC_SHOW_TRANSLATE:
ShowTranslateBubble(browser_);
break;
@@ -1244,6 +1247,7 @@
command_updater_.UpdateCommandEnabled(IDC_ORGANIZE_TABS, true);
command_updater_.UpdateCommandEnabled(IDC_CREATE_NEW_TAB_GROUP, true);
+ command_updater_.UpdateCommandEnabled(IDC_DECLUTTER_TABS, true);
#if BUILDFLAG(IS_CHROMEOS)
command_updater_.UpdateCommandEnabled(IDC_TOGGLE_MULTITASK_MENU, true);
#endif
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 2c902af..8bbb6ac 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -1882,6 +1882,13 @@
browser->window()->CloseTabSearchBubble();
}
+void ShowTabDeclutter(Browser* browser) {
+ const int tab_organization_tab_index = 1;
+ browser->window()->CreateTabSearchBubble(
+ tab_organization_tab_index,
+ tab_search::mojom::TabOrganizationFeature::kDeclutter);
+}
+
bool CanCloseFind(Browser* browser) {
WebContents* current_tab = browser->tab_strip_model()->GetActiveWebContents();
if (!current_tab) {
diff --git a/chrome/browser/ui/browser_commands.h b/chrome/browser/ui/browser_commands.h
index 71f4ae23..afdf17c 100644
--- a/chrome/browser/ui/browser_commands.h
+++ b/chrome/browser/ui/browser_commands.h
@@ -206,6 +206,7 @@
void FindInPage(Browser* browser, bool find_next, bool forward_direction);
void ShowTabSearch(Browser* browser);
void CloseTabSearch(Browser* browser);
+void ShowTabDeclutter(Browser* browser);
bool CanCloseFind(Browser* browser);
void CloseFind(Browser* browser);
void Zoom(Browser* browser, content::PageZoom zoom);
diff --git a/chrome/browser/ui/browser_commands_browsertest.cc b/chrome/browser/ui/browser_commands_browsertest.cc
index e846908..5e1e1b8 100644
--- a/chrome/browser/ui/browser_commands_browsertest.cc
+++ b/chrome/browser/ui/browser_commands_browsertest.cc
@@ -23,8 +23,10 @@
#include "chrome/browser/ui/toasts/toast_controller.h"
#include "chrome/browser/ui/toasts/toast_features.h"
#include "chrome/browser/ui/ui_features.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
#include "chrome/browser/ui/views/side_panel/side_panel_entry_id.h"
+#include "chrome/browser/ui/views/tab_search_bubble_host.h"
#include "chrome/browser/ui/webui/commerce/product_specifications_disclosure_dialog.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
@@ -48,6 +50,7 @@
feature_list_.InitWithFeatures(
{
features::kTabOrganization,
+ features::kTabstripDeclutter,
toast_features::kToastFramework,
toast_features::kReadingListToast,
toast_features::kLinkCopiedToast,
@@ -375,6 +378,17 @@
true, 1);
}
+IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, ShowsDeclutter) {
+ TabSearchBubbleHost* tab_search_bubble_host =
+ BrowserView::GetBrowserViewForBrowser(browser())
+ ->GetTabSearchBubbleHost();
+ EXPECT_FALSE(tab_search_bubble_host->bubble_created_time_for_testing());
+
+ chrome::ExecuteCommand(browser(), IDC_DECLUTTER_TABS);
+
+ EXPECT_TRUE(tab_search_bubble_host->bubble_created_time_for_testing());
+}
+
IN_PROC_BROWSER_TEST_F(BrowserCommandsTest,
ConvertPopupToTabbedBrowserShutdownRace) {
// Confirm we do not incorrectly start shutdown when converting a popup into a
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index 2527858f..c909731 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -881,6 +881,15 @@
}
}
+ if (base::FeatureList::IsEnabled(features::kTabstripDeclutter)) {
+ // TODO(crbug.com/369638354): Use the correct icon once finalized.
+ AddItemWithStringIdAndVectorIcon(this, IDC_DECLUTTER_TABS,
+ IDS_DECLUTTER_MENU, kAutoTabGroupsIcon);
+ SetIsNewFeatureAt(
+ GetIndexOfCommandId(IDC_DECLUTTER_TABS).value(),
+ browser->window()->MaybeShowNewBadgeFor(features::kTabstripDeclutter));
+ }
+
AddItemWithStringIdAndVectorIcon(this, IDC_NAME_WINDOW, IDS_NAME_WINDOW,
kNameWindowIcon);
diff --git a/chrome/browser/ui/toolbar/app_menu_model_unittest.cc b/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
index 1363254..3d7bceb 100644
--- a/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
@@ -143,7 +143,8 @@
class TestAppMenuModelCR2023 : public AppMenuModelTest {
public:
TestAppMenuModelCR2023() {
- feature_list_.InitWithFeatures({features::kTabOrganization}, {});
+ feature_list_.InitWithFeatures(
+ {features::kTabOrganization, features::kTabstripDeclutter}, {});
}
TestAppMenuModelCR2023(const TestAppMenuModelCR2023&) = delete;
@@ -378,6 +379,16 @@
EXPECT_TRUE(toolModel.IsEnabledAt(organize_tabs_index));
}
+TEST_F(TestAppMenuModelCR2023, DeclutterTabsItem) {
+ TabOrganizationUtils::GetInstance()->SetIgnoreOptGuideForTesting(true);
+ AppMenuModel model(this, browser());
+ model.Init();
+ ToolsMenuModel toolModel(&model, browser());
+ size_t declutter_tabs_index =
+ toolModel.GetIndexOfCommandId(IDC_DECLUTTER_TABS).value();
+ EXPECT_TRUE(toolModel.IsEnabledAt(declutter_tabs_index));
+}
+
TEST_F(TestAppMenuModelCR2023, ModelHasIcons) {
// Skip the items that are either not supposed to have an icon, or are not
// ready to be tested. Remove items once they're ready for testing.
diff --git a/chrome/browser/ui/views/user_education/browser_user_education_service.cc b/chrome/browser/ui/views/user_education/browser_user_education_service.cc
index d76393e..a69151ab 100644
--- a/chrome/browser/ui/views/user_education/browser_user_education_service.cc
+++ b/chrome/browser/ui/views/user_education/browser_user_education_service.cc
@@ -1461,6 +1461,12 @@
user_education::Metadata(
128, "theocristea@google.com",
"For passwords manual fallback; shown in the context menu.")));
+
+ registry.RegisterFeature(user_education::NewBadgeSpecification(
+ features::kTabstripDeclutter,
+ user_education::Metadata(
+ 132, "emshack@chromium.org",
+ "Shown in app menu when Tab Declutter menu item is enabled.")));
}
std::unique_ptr<BrowserFeaturePromoController> CreateUserEducationResources(
diff --git a/tools/metrics/histograms/metadata/user_education/histograms.xml b/tools/metrics/histograms/metadata/user_education/histograms.xml
index 6b1d726b..f44e5a1a 100644
--- a/tools/metrics/histograms/metadata/user_education/histograms.xml
+++ b/tools/metrics/histograms/metadata/user_education/histograms.xml
@@ -45,6 +45,8 @@
summary="Plus address manual fallback entry point in the context menu."/>
<variant name="TabOrganization"
summary="Promotion for Organize Tabs in the app menu."/>
+ <variant name="TabstripDeclutter"
+ summary="Promotion for Close Unused Tabs in the app menu."/>
</variants>
<variants name="TutorialID">