blob: 564577c9c225da8f424519458d85db23fa82fb51 [file] [log] [blame]
// Copyright 2012 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_menu_model.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/feed/web_feed_tab_helper.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/tabs/existing_base_sub_menu_model.h"
#include "chrome/browser/ui/tabs/organization/tab_organization_utils.h"
#include "chrome/browser/ui/tabs/tab_menu_model_delegate.h"
#include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/menu_model_test.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/optimization_guide/core/model_execution/model_execution_features.h"
#include "components/optimization_guide/core/optimization_guide_features.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/models/menu_model.h"
#include "ui/base/ui_base_features.h"
class TabMenuModelTest : public MenuModelTest,
public BrowserWithTestWindowTest {
};
TEST_F(TabMenuModelTest, Basics) {
chrome::NewTab(browser());
TabMenuModel model(&delegate_, browser()->tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
// Verify it has items. The number varies by platform, so we don't check
// the exact number.
EXPECT_GT(model.GetItemCount(), 5u);
int item_count = 0;
CountEnabledExecutable(&model, &item_count);
EXPECT_GT(item_count, 0);
EXPECT_EQ(item_count, delegate_.execute_count_);
EXPECT_EQ(item_count, delegate_.enable_count_);
}
TEST_F(TabMenuModelTest, OrganizeTabs) {
TabOrganizationUtils::GetInstance()->SetIgnoreOptGuideForTesting(true);
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures(
{features::kTabOrganization, features::kChromeRefresh2023,
features::kChromeWebuiRefresh2023},
{});
chrome::NewTab(browser());
TabMenuModel model(&delegate_, browser()->tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
// Verify that CommandOrganizeTabs is in the menu.
EXPECT_TRUE(model.GetIndexOfCommandId(TabStripModel::CommandOrganizeTabs)
.has_value());
}
TEST_F(TabMenuModelTest, CommerceProductSpecs) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({commerce::kProductSpecifications}, {});
TabStripModel* tab_strip = browser()->tab_strip_model();
std::unique_ptr<content::WebContents> https_web_content =
content::WebContentsTester::CreateTestWebContents(profile(), nullptr);
content::WebContentsTester::For(https_web_content.get())
->NavigateAndCommit(GURL("https://www.example.com"));
tab_strip->AppendWebContents(std::move(https_web_content), true);
chrome::NewTab(browser());
tab_strip->ActivateTabAt(
0, TabStripUserGestureDetails(
TabStripUserGestureDetails::GestureType::kOther));
tab_strip->AddSelectionFromAnchorTo(1);
TabMenuModel model(&delegate_, browser()->tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
EXPECT_TRUE(model
.GetIndexOfCommandId(
TabStripModel::CommandCommerceProductSpecifications)
.has_value());
}
TEST_F(TabMenuModelTest, CommerceProductSpecsIncognito) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({commerce::kProductSpecifications}, {});
TestingProfile::Builder incognito_profile_builder;
auto* incognito_profile = incognito_profile_builder.BuildIncognito(profile());
Browser::CreateParams native_params(incognito_profile, true);
native_params.initial_show_state = ui::SHOW_STATE_DEFAULT;
std::unique_ptr<Browser> browser =
CreateBrowserWithTestWindowForParams(native_params);
Browser* incognito_browser = browser.get();
AddTab(incognito_browser, GURL("https://example.com"));
AddTab(incognito_browser, GURL("https://example2.com"));
TabStripModel* tab_strip = incognito_browser->tab_strip_model();
tab_strip->ActivateTabAt(
0, TabStripUserGestureDetails(
TabStripUserGestureDetails::GestureType::kOther));
tab_strip->AddSelectionFromAnchorTo(1);
TabMenuModel model(&delegate_, incognito_browser->tab_menu_model_delegate(),
incognito_browser->tab_strip_model(), 0);
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandCommerceProductSpecifications)
.has_value());
// All tabs must be closed before the object is destroyed.
incognito_browser->tab_strip_model()->CloseAllTabs();
}
TEST_F(TabMenuModelTest, CommerceProductSpecsInvalidScheme) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({commerce::kProductSpecifications}, {});
TabStripModel* tab_strip = browser()->tab_strip_model();
std::unique_ptr<content::WebContents> chrome_web_contents =
content::WebContentsTester::CreateTestWebContents(profile(), nullptr);
content::WebContentsTester::For(chrome_web_contents.get())
->NavigateAndCommit(GURL("chrome://bookmarks"));
tab_strip->AppendWebContents(std::move(chrome_web_contents), true);
chrome::NewTab(browser());
tab_strip->ActivateTabAt(
0, TabStripUserGestureDetails(
TabStripUserGestureDetails::GestureType::kOther));
tab_strip->AddSelectionFromAnchorTo(1);
TabMenuModel model(&delegate_, browser()->tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandCommerceProductSpecifications)
.has_value());
}
TEST_F(TabMenuModelTest, CommerceProductSpecsHttp) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({commerce::kProductSpecifications}, {});
TabStripModel* tab_strip = browser()->tab_strip_model();
std::unique_ptr<content::WebContents> http_web_contents =
content::WebContentsTester::CreateTestWebContents(profile(), nullptr);
content::WebContentsTester::For(http_web_contents.get())
->NavigateAndCommit(GURL("http://example.com"));
tab_strip->AppendWebContents(std::move(http_web_contents), true);
chrome::NewTab(browser());
tab_strip->ActivateTabAt(
0, TabStripUserGestureDetails(
TabStripUserGestureDetails::GestureType::kOther));
tab_strip->AddSelectionFromAnchorTo(1);
TabMenuModel model(&delegate_, browser()->tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
EXPECT_TRUE(model
.GetIndexOfCommandId(
TabStripModel::CommandCommerceProductSpecifications)
.has_value());
}
TEST_F(TabMenuModelTest, CommerceProductSpecsFeatureCheck) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({}, {commerce::kProductSpecifications});
TabStripModel* tab_strip = browser()->tab_strip_model();
chrome::NewTab(browser());
chrome::NewTab(browser());
tab_strip->AddSelectionFromAnchorTo(1);
TabMenuModel model(&delegate_, browser()->tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandCommerceProductSpecifications)
.has_value());
}
TEST_F(TabMenuModelTest, CommerceProductSpecsInsuffcientSelection) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({commerce::kProductSpecifications}, {});
chrome::NewTab(browser());
chrome::NewTab(browser());
TabMenuModel model(&delegate_, browser()->tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandCommerceProductSpecifications)
.has_value());
}
TEST_F(TabMenuModelTest, MoveToNewWindow) {
chrome::NewTab(browser());
TabMenuModel model(&delegate_, browser()->tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
// Verify that CommandMoveTabsToNewWindow is in the menu.
EXPECT_TRUE(
model.GetIndexOfCommandId(TabStripModel::CommandMoveTabsToNewWindow)
.has_value());
}
TEST_F(TabMenuModelTest, AddToExistingGroupSubmenu) {
chrome::NewTab(browser());
chrome::NewTab(browser());
chrome::NewTab(browser());
chrome::NewTab(browser());
TabStripModel* tab_strip_model = browser()->tab_strip_model();
tab_strip_model->AddToNewGroup({0});
tab_strip_model->AddToNewGroup({1});
tab_strip_model->AddToNewGroup({2});
TabMenuModel menu(&delegate_, browser()->tab_menu_model_delegate(),
tab_strip_model, 3);
size_t submenu_index =
menu.GetIndexOfCommandId(TabStripModel::CommandAddToExistingGroup)
.value();
ui::MenuModel* submenu = menu.GetSubmenuModelAt(submenu_index);
EXPECT_EQ(submenu->GetItemCount(), 5u);
EXPECT_EQ(submenu->GetCommandIdAt(0),
ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId);
EXPECT_EQ(submenu->GetTypeAt(1), ui::MenuModel::TYPE_SEPARATOR);
EXPECT_EQ(submenu->GetCommandIdAt(2),
ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId + 1);
EXPECT_FALSE(submenu->GetIconAt(2).IsEmpty());
EXPECT_EQ(submenu->GetCommandIdAt(3),
ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId + 2);
EXPECT_EQ(submenu->GetCommandIdAt(4),
ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId + 3);
}
TEST_F(TabMenuModelTest, AddToExistingGroupSubmenu_DoesNotIncludeCurrentGroup) {
chrome::NewTab(browser());
chrome::NewTab(browser());
chrome::NewTab(browser());
chrome::NewTab(browser());
TabStripModel* tab_strip_model = browser()->tab_strip_model();
tab_strip_model->AddToNewGroup({0});
tab_strip_model->AddToNewGroup({1});
tab_strip_model->AddToNewGroup({2});
TabMenuModel menu(&delegate_, browser()->tab_menu_model_delegate(),
tab_strip_model, 1);
size_t submenu_index =
menu.GetIndexOfCommandId(TabStripModel::CommandAddToExistingGroup)
.value();
ui::MenuModel* submenu = menu.GetSubmenuModelAt(submenu_index);
EXPECT_EQ(submenu->GetItemCount(), 4u);
EXPECT_EQ(submenu->GetCommandIdAt(0),
ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId);
EXPECT_EQ(submenu->GetTypeAt(1), ui::MenuModel::TYPE_SEPARATOR);
EXPECT_EQ(submenu->GetCommandIdAt(2),
ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId + 1);
EXPECT_FALSE(submenu->GetIconAt(2).IsEmpty());
EXPECT_EQ(submenu->GetCommandIdAt(3),
ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId + 2);
}
// In some cases, groups may change after the menu is created. For example an
// extension may modify groups while the menu is open. If a group referenced in
// the menu goes away, ensure we handle this gracefully.
//
// Regression test for crbug.com/1197875
TEST_F(TabMenuModelTest, AddToExistingGroupAfterGroupDestroyed) {
chrome::NewTab(browser());
chrome::NewTab(browser());
TabStripModel* tab_strip_model = browser()->tab_strip_model();
tab_strip_model->AddToNewGroup({0});
TabMenuModel menu(&delegate_, browser()->tab_menu_model_delegate(),
tab_strip_model, 1);
size_t submenu_index =
menu.GetIndexOfCommandId(TabStripModel::CommandAddToExistingGroup)
.value();
ui::MenuModel* submenu = menu.GetSubmenuModelAt(submenu_index);
EXPECT_EQ(submenu->GetItemCount(), 3u);
// Ungroup the tab at 0 to make the group in the menu dangle.
tab_strip_model->RemoveFromGroup({0});
// Try adding to the group from the menu.
submenu->ActivatedAt(2);
EXPECT_FALSE(tab_strip_model->GetTabGroupForTab(0).has_value());
EXPECT_FALSE(tab_strip_model->GetTabGroupForTab(1).has_value());
}
class TabMenuModelTestTabStripModelDelegate : public TestTabStripModelDelegate {
public:
bool IsForWebApp() override { return true; }
bool SupportsReadLater() override { return false; }
};
TEST_F(TabMenuModelTest, TabbedWebApp) {
// Create a tabbed web app window without home tab
TabMenuModelTestTabStripModelDelegate delegate;
TabStripModel strip(&delegate, profile());
strip.AppendWebContents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr),
true);
TabMenuModel model(&delegate_, browser()->tab_menu_model_delegate(), &strip,
0);
// When adding/removing a menu item, either update this count and add it to
// the list below or disable it for tabbed web apps.
EXPECT_EQ(model.GetItemCount(), 7u);
EXPECT_TRUE(
model.GetIndexOfCommandId(TabStripModel::CommandCopyURL).has_value());
EXPECT_TRUE(
model.GetIndexOfCommandId(TabStripModel::CommandReload).has_value());
EXPECT_TRUE(
model.GetIndexOfCommandId(TabStripModel::CommandGoBack).has_value());
EXPECT_TRUE(
model.GetIndexOfCommandId(TabStripModel::CommandMoveTabsToNewWindow)
.has_value());
EXPECT_EQ(model.GetTypeAt(4), ui::MenuModel::TYPE_SEPARATOR);
EXPECT_TRUE(
model.GetIndexOfCommandId(TabStripModel::CommandCloseTab).has_value());
EXPECT_TRUE(model.GetIndexOfCommandId(TabStripModel::CommandCloseOtherTabs)
.has_value());
}
TEST_F(TabMenuModelTest, TabbedWebAppHomeTab) {
TabMenuModelTestTabStripModelDelegate delegate;
TabStripModel strip(&delegate, profile());
strip.AppendWebContents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr),
true);
// Pin the first tab so we get the pinned home tab menu.
strip.SetTabPinned(0, true);
TabMenuModel home_tab_model(&delegate_, browser()->tab_menu_model_delegate(),
&strip, 0);
// When adding/removing a menu item, either update this count and add it to
// the list below or disable it for tabbed web apps.
EXPECT_EQ(home_tab_model.GetItemCount(), 5u);
EXPECT_TRUE(home_tab_model.GetIndexOfCommandId(TabStripModel::CommandCopyURL)
.has_value());
EXPECT_TRUE(home_tab_model.GetIndexOfCommandId(TabStripModel::CommandReload)
.has_value());
EXPECT_TRUE(home_tab_model.GetIndexOfCommandId(TabStripModel::CommandGoBack)
.has_value());
EXPECT_EQ(home_tab_model.GetTypeAt(3), ui::MenuModel::TYPE_SEPARATOR);
EXPECT_TRUE(
home_tab_model.GetIndexOfCommandId(TabStripModel::CommandCloseAllTabs)
.has_value());
strip.AppendWebContents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr),
true);
EXPECT_EQ(strip.count(), 2);
EXPECT_FALSE(strip.IsTabSelected(0));
EXPECT_TRUE(strip.IsTabSelected(1));
TabMenuModel regular_tab_model(
&delegate_, browser()->tab_menu_model_delegate(), &strip, 1);
// When adding/removing a menu item, either update this count and add it to
// the list below or disable it for tabbed web apps.
EXPECT_EQ(regular_tab_model.GetItemCount(), 8u);
EXPECT_TRUE(
regular_tab_model.GetIndexOfCommandId(TabStripModel::CommandCopyURL)
.has_value());
EXPECT_TRUE(
regular_tab_model.GetIndexOfCommandId(TabStripModel::CommandReload)
.has_value());
EXPECT_TRUE(
regular_tab_model.GetIndexOfCommandId(TabStripModel::CommandGoBack)
.has_value());
EXPECT_TRUE(
regular_tab_model
.GetIndexOfCommandId(TabStripModel::CommandMoveTabsToNewWindow)
.has_value());
EXPECT_EQ(regular_tab_model.GetTypeAt(4), ui::MenuModel::TYPE_SEPARATOR);
EXPECT_TRUE(
regular_tab_model.GetIndexOfCommandId(TabStripModel::CommandCloseTab)
.has_value());
EXPECT_TRUE(regular_tab_model
.GetIndexOfCommandId(TabStripModel::CommandCloseOtherTabs)
.has_value());
EXPECT_TRUE(
regular_tab_model.GetIndexOfCommandId(TabStripModel::CommandCloseAllTabs)
.has_value());
}