blob: 60a7e3cd313c898c5fbd28f11368161e6ed3f1f3 [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/callback_list.h"
#include "base/feature_list.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/commerce/product_specifications/product_specifications_service_factory.h"
#include "chrome/browser/commerce/shopping_service_factory.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/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.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/saved_tab_groups/saved_tab_group_utils.h"
#include "chrome/browser/ui/tabs/split_tab_metrics.h"
#include "chrome/browser/ui/tabs/split_tab_swap_menu_model.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/in_process_browser_test.h"
#include "chrome/test/base/menu_model_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/commerce/core/feature_utils.h"
#include "components/commerce/core/mock_account_checker.h"
#include "components/commerce/core/mock_shopping_service.h"
#include "components/commerce/core/product_specifications/mock_product_specifications_service.h"
#include "components/commerce/core/shopping_service.h"
#include "components/commerce/core/test_utils.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/optimization_guide/core/model_execution/model_execution_features.h"
#include "components/optimization_guide/core/optimization_guide_features.h"
#include "components/prefs/testing_pref_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/models/menu_model.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#include "ui/base/ui_base_features.h"
#include "ui/menus/simple_menu_model.h"
#if BUILDFLAG(ENABLE_GLIC)
#include "chrome/browser/glic/host/glic_features.mojom.h"
#include "chrome/browser/glic/public/glic_keyed_service.h"
#include "chrome/browser/glic/public/glic_keyed_service_factory.h"
#include "chrome/browser/glic/test_support/glic_test_environment.h"
#endif
class TabMenuModelBrowserTest : public MenuModelTest,
public InProcessBrowserTest {
public:
TabMenuModelBrowserTest() {
// Enable tab organization before KeyedServices are instantiated, otherwise
// TabOrganizationServiceFactory::GetForProfile() will return nullptr.
feature_list_.InitWithFeatures({features::kTabOrganization}, {});
TabOrganizationUtils::GetInstance()->SetIgnoreOptGuideForTesting(true);
}
Profile* profile() { return browser()->profile(); }
void ActivateSwapWithSplitSubmenuCommand(
int tab_index,
SplitTabSwapMenuModel::CommandId command_id) {
TabMenuModel menu(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
browser()->tab_strip_model(), tab_index);
size_t submenu_index =
menu.GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
.value();
ui::SimpleMenuModel* submenu = static_cast<ui::SimpleMenuModel*>(
menu.GetSubmenuModelAt(submenu_index));
submenu->ActivatedAt(static_cast<size_t>(
submenu->GetIndexOfCommandId(static_cast<int>(command_id)).value()));
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, Basics) {
chrome::NewTab(browser());
TabMenuModel model(&delegate_,
browser()->GetFeatures().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_);
}
IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, OrganizeTabs) {
chrome::NewTab(browser());
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
// Verify that CommandOrganizeTabs is in the menu.
EXPECT_TRUE(model.GetIndexOfCommandId(TabStripModel::CommandOrganizeTabs)
.has_value());
}
IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, MoveToNewWindow) {
chrome::NewTab(browser());
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
// Verify that CommandMoveTabsToNewWindow is in the menu.
EXPECT_TRUE(
model.GetIndexOfCommandId(TabStripModel::CommandMoveTabsToNewWindow)
.has_value());
}
IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, 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()->GetFeatures().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);
}
IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest,
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()->GetFeatures().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
IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest,
AddToExistingGroupAfterGroupDestroyed) {
chrome::NewTab(browser());
chrome::NewTab(browser());
TabStripModel* tab_strip_model = browser()->tab_strip_model();
tab_strip_model->AddToNewGroup({0});
TabMenuModel menu(&delegate_,
browser()->GetFeatures().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 TabMenuModelSplitTabsTest : public TabMenuModelBrowserTest {
public:
TabMenuModelSplitTabsTest() {
feature_list_.InitAndEnableFeature(features::kSideBySide);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(TabMenuModelSplitTabsTest, ActiveTabNotSplit) {
chrome::NewTab(browser());
chrome::NewTab(browser());
chrome::NewTab(browser());
TabStripModel* tab_strip_model = browser()->tab_strip_model();
EXPECT_EQ(tab_strip_model->count(), 4);
EXPECT_EQ(tab_strip_model->active_index(), 3);
tab_strip_model->ExecuteContextMenuCommand(2,
TabStripModel::CommandAddToSplit);
tab_strip_model->ActivateTabAt(0);
// Active tab is not split, context menu index is active tab
{
TabMenuModel menu_model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip_model, 0);
EXPECT_TRUE(menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit)
.has_value());
EXPECT_FALSE(
menu_model
.GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
.has_value());
EXPECT_FALSE(
menu_model.GetIndexOfCommandId(TabStripModel::CommandArrangeSplit)
.has_value());
}
// Active tab is not split, context menu index is on inactive tab
{
TabMenuModel menu_model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip_model, 1);
EXPECT_TRUE(menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit)
.has_value());
EXPECT_FALSE(
menu_model
.GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
.has_value());
EXPECT_FALSE(
menu_model.GetIndexOfCommandId(TabStripModel::CommandArrangeSplit)
.has_value());
}
// Active tab is not split, context menu index is on inactive split tab
{
TabMenuModel menu_model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip_model, 2);
EXPECT_FALSE(
menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit)
.has_value());
EXPECT_FALSE(
menu_model
.GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
.has_value());
EXPECT_TRUE(
menu_model.GetIndexOfCommandId(TabStripModel::CommandArrangeSplit)
.has_value());
}
}
IN_PROC_BROWSER_TEST_F(TabMenuModelSplitTabsTest, SplitActiveTab) {
chrome::NewTab(browser());
chrome::NewTab(browser());
chrome::NewTab(browser());
TabStripModel* tab_strip_model = browser()->tab_strip_model();
EXPECT_EQ(tab_strip_model->count(), 4);
EXPECT_EQ(tab_strip_model->active_index(), 3);
tab_strip_model->ExecuteContextMenuCommand(2,
TabStripModel::CommandAddToSplit);
// Active tab is split, context menu index is active tab
{
TabMenuModel menu_model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip_model, 3);
EXPECT_FALSE(
menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit)
.has_value());
EXPECT_FALSE(
menu_model
.GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
.has_value());
EXPECT_TRUE(
menu_model.GetIndexOfCommandId(TabStripModel::CommandArrangeSplit)
.has_value());
}
// Active tab is split, context menu index is on inactive tab
{
TabMenuModel menu_model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip_model, 1);
EXPECT_FALSE(
menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit)
.has_value());
EXPECT_TRUE(
menu_model
.GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
.has_value());
EXPECT_FALSE(
menu_model.GetIndexOfCommandId(TabStripModel::CommandArrangeSplit)
.has_value());
}
}
IN_PROC_BROWSER_TEST_F(TabMenuModelSplitTabsTest, MultiSelectTabs) {
chrome::NewTab(browser());
chrome::NewTab(browser());
chrome::NewTab(browser());
TabStripModel* tab_strip_model = browser()->tab_strip_model();
EXPECT_EQ(tab_strip_model->count(), 4);
EXPECT_EQ(tab_strip_model->active_index(), 3);
tab_strip_model->ActivateTabAt(1);
tab_strip_model->AddSelectionFromAnchorTo(2);
{
TabMenuModel menu_model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip_model, 2);
auto index =
menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit);
EXPECT_TRUE(index.has_value());
EXPECT_TRUE(menu_model.IsEnabledAt(index.value()));
}
tab_strip_model->ActivateTabAt(0);
tab_strip_model->AddSelectionFromAnchorTo(2);
{
TabMenuModel menu_model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip_model, 2);
auto index =
menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit);
EXPECT_TRUE(index.has_value());
EXPECT_FALSE(menu_model.IsEnabledAt(index.value()));
}
}
IN_PROC_BROWSER_TEST_F(TabMenuModelSplitTabsTest, SwapWithActiveTab) {
// Add 3 tabs to the browser.
chrome::NewTab(browser());
chrome::NewTab(browser());
TabStripModel* tab_strip_model = browser()->tab_strip_model();
EXPECT_EQ(tab_strip_model->count(), 3);
// Create a split. Assert that the last two tabs are split with the rightmost
// tab active.
tab_strip_model->ActivateTabAt(0);
tab_strip_model->AddToNewSplit(
{1},
split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kVertical),
split_tabs::SplitTabCreatedSource::kToolbarButton);
EXPECT_TRUE(tab_strip_model->GetSplitForTab(0).has_value());
EXPECT_TRUE(tab_strip_model->GetSplitForTab(1).has_value());
EXPECT_FALSE(tab_strip_model->GetSplitForTab(2).has_value());
EXPECT_EQ(tab_strip_model->active_index(), 0);
// Trigger the swap start tab command.
ActivateSwapWithSplitSubmenuCommand(
2, SplitTabSwapMenuModel::CommandId::kSwapStartTab);
// Check that now the left two tabs are in a split and the left (swapped) tab
// in the split is active.
EXPECT_TRUE(tab_strip_model->GetSplitForTab(0).has_value());
EXPECT_TRUE(tab_strip_model->GetSplitForTab(1).has_value());
EXPECT_FALSE(tab_strip_model->GetSplitForTab(2).has_value());
EXPECT_EQ(tab_strip_model->active_index(), 0);
}
IN_PROC_BROWSER_TEST_F(TabMenuModelSplitTabsTest, SwapWithInactiveTab) {
// Add 3 tabs to the browser.
chrome::NewTab(browser());
chrome::NewTab(browser());
TabStripModel* tab_strip_model = browser()->tab_strip_model();
EXPECT_EQ(tab_strip_model->count(), 3);
// Create a split. Assert that the last two tabs are split with the rightmost
// tab active.
tab_strip_model->AddToNewSplit(
{1},
split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kVertical),
split_tabs::SplitTabCreatedSource::kToolbarButton);
EXPECT_FALSE(tab_strip_model->GetSplitForTab(0).has_value());
EXPECT_TRUE(tab_strip_model->GetSplitForTab(1).has_value());
EXPECT_TRUE(tab_strip_model->GetSplitForTab(2).has_value());
EXPECT_EQ(tab_strip_model->active_index(), 2);
// Trigger the swap start tab command.
ActivateSwapWithSplitSubmenuCommand(
0, SplitTabSwapMenuModel::CommandId::kSwapStartTab);
// Check that now the right two tabs are in a split and the right (swapped)
// tab in the split is active.
EXPECT_FALSE(tab_strip_model->GetSplitForTab(0).has_value());
EXPECT_TRUE(tab_strip_model->GetSplitForTab(1).has_value());
EXPECT_TRUE(tab_strip_model->GetSplitForTab(2).has_value());
EXPECT_EQ(tab_strip_model->active_index(), 2);
}
class TabMenuModelCommerceProductSpecsTest : public TabMenuModelBrowserTest {
public:
TabMenuModelCommerceProductSpecsTest()
: account_checker_(std::make_unique<commerce::MockAccountChecker>()),
prefs_(std::make_unique<TestingPrefServiceSimple>()) {
feature_list_.InitAndEnableFeature(commerce::kProductSpecifications);
dependency_manager_subscription_ =
BrowserContextDependencyManager::GetInstance()
->RegisterCreateServicesCallbackForTesting(base::BindRepeating(
&TabMenuModelCommerceProductSpecsTest::SetTestingFactory,
base::Unretained(this)));
}
void SetUpOnMainThread() override {
TabMenuModelBrowserTest::SetUpOnMainThread();
commerce::MockAccountChecker::RegisterCommercePrefs(prefs_->registry());
account_checker_->SetPrefs(prefs_.get());
auto* shopping_service = static_cast<commerce::MockShoppingService*>(
commerce::ShoppingServiceFactory::GetForBrowserContext(profile()));
shopping_service->SetAccountChecker(account_checker_.get());
// By default, the account checker and prefs are set up to enable product
// specifications.
commerce::EnableProductSpecificationsDataFetch(account_checker_.get(),
prefs_.get());
}
void SetTestingFactory(content::BrowserContext* context) {
commerce::ShoppingServiceFactory::GetInstance()->SetTestingFactory(
context, base::BindRepeating([](content::BrowserContext* context)
-> std::unique_ptr<KeyedService> {
return commerce::MockShoppingService::Build();
}));
}
protected:
std::unique_ptr<commerce::MockAccountChecker> account_checker_;
private:
base::CallbackListSubscription dependency_manager_subscription_;
std::unique_ptr<TestingPrefServiceSimple> prefs_;
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(TabMenuModelCommerceProductSpecsTest,
MenuShowForNormalWindow) {
ASSERT_TRUE(
commerce::CanFetchProductSpecificationsData(account_checker_.get()));
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("https://example.com"),
WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("https://example2.com"),
WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
// Close about:blank tab since we don't need it.
browser()->tab_strip_model()->CloseWebContentsAt(0,
TabCloseTypes::CLOSE_NONE);
TabStripModel* tab_strip = browser()->tab_strip_model();
tab_strip->ActivateTabAt(
0, TabStripUserGestureDetails(
TabStripUserGestureDetails::GestureType::kOther));
tab_strip->AddSelectionFromAnchorTo(1);
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
EXPECT_TRUE(model
.GetIndexOfCommandId(
TabStripModel::CommandCommerceProductSpecifications)
.has_value());
}
IN_PROC_BROWSER_TEST_F(TabMenuModelCommerceProductSpecsTest,
MenuNotShowForIncognitoWindow) {
ASSERT_TRUE(
commerce::CanFetchProductSpecificationsData(account_checker_.get()));
Browser* incognito_browser = CreateIncognitoBrowser(profile());
ui_test_utils::NavigateToURLWithDisposition(
incognito_browser, GURL("https://example.com"),
WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
ui_test_utils::NavigateToURLWithDisposition(
incognito_browser, GURL("https://example2.com"),
WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
// Close about:blank tab since we don't need it.
incognito_browser->tab_strip_model()->CloseWebContentsAt(
0, TabCloseTypes::CLOSE_NONE);
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->GetFeatures().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();
}
IN_PROC_BROWSER_TEST_F(TabMenuModelCommerceProductSpecsTest,
MenuNotShowForInvalidScheme) {
ASSERT_TRUE(
commerce::CanFetchProductSpecificationsData(account_checker_.get()));
TabStripModel* tab_strip = browser()->tab_strip_model();
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("chrome://bookmarks"),
WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("chrome://history"),
WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
// Close about:blank tab since we don't need it.
browser()->tab_strip_model()->CloseWebContentsAt(0,
TabCloseTypes::CLOSE_NONE);
tab_strip->ActivateTabAt(
0, TabStripUserGestureDetails(
TabStripUserGestureDetails::GestureType::kOther));
tab_strip->AddSelectionFromAnchorTo(1);
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandCommerceProductSpecifications)
.has_value());
}
IN_PROC_BROWSER_TEST_F(TabMenuModelCommerceProductSpecsTest, MenuShowForHttp) {
ASSERT_TRUE(
commerce::CanFetchProductSpecificationsData(account_checker_.get()));
TabStripModel* tab_strip = browser()->tab_strip_model();
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("http://example.com"),
WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("http://example2.com"),
WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
// Close about:blank tab since we don't need it.
browser()->tab_strip_model()->CloseWebContentsAt(0,
TabCloseTypes::CLOSE_NONE);
tab_strip->ActivateTabAt(
0, TabStripUserGestureDetails(
TabStripUserGestureDetails::GestureType::kOther));
tab_strip->AddSelectionFromAnchorTo(1);
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
EXPECT_TRUE(model
.GetIndexOfCommandId(
TabStripModel::CommandCommerceProductSpecifications)
.has_value());
}
class TabMenuModelCommerceProductSpecsDisabledTest
: public TabMenuModelCommerceProductSpecsTest {
public:
TabMenuModelCommerceProductSpecsDisabledTest() {
feature_list_.InitAndDisableFeature(commerce::kProductSpecifications);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(TabMenuModelCommerceProductSpecsDisabledTest,
MenuNotShowForFeatureDisable) {
ASSERT_FALSE(
commerce::CanFetchProductSpecificationsData(account_checker_.get()));
TabStripModel* tab_strip = browser()->tab_strip_model();
chrome::NewTab(browser());
tab_strip->AddSelectionFromAnchorTo(1);
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandCommerceProductSpecifications)
.has_value());
}
IN_PROC_BROWSER_TEST_F(TabMenuModelCommerceProductSpecsTest,
MenuNotShowForFetchDisable) {
// Update account checker to disable product specifications data fetch.
account_checker_->SetIsSubjectToParentalControls(true);
ASSERT_FALSE(
commerce::CanFetchProductSpecificationsData(account_checker_.get()));
TabStripModel* tab_strip = browser()->tab_strip_model();
chrome::NewTab(browser());
chrome::NewTab(browser());
// Close about:blank tab since we don't need it.
browser()->tab_strip_model()->CloseWebContentsAt(0,
TabCloseTypes::CLOSE_NONE);
tab_strip->AddSelectionFromAnchorTo(1);
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandCommerceProductSpecifications)
.has_value());
}
IN_PROC_BROWSER_TEST_F(TabMenuModelCommerceProductSpecsTest,
MenuNotShowForInsuffcientSelection) {
ASSERT_TRUE(
commerce::CanFetchProductSpecificationsData(account_checker_.get()));
chrome::NewTab(browser());
chrome::NewTab(browser());
// Close about:blank tab since we don't need it.
browser()->tab_strip_model()->CloseWebContentsAt(0,
TabCloseTypes::CLOSE_NONE);
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
browser()->tab_strip_model(), 0);
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandCommerceProductSpecifications)
.has_value());
}
class TabMenuModelComparisonTableTest : public TabMenuModelBrowserTest {
public:
TabMenuModelComparisonTableTest() {
dependency_manager_subscription_ =
BrowserContextDependencyManager::GetInstance()
->RegisterCreateServicesCallbackForTesting(base::BindRepeating(
&TabMenuModelComparisonTableTest::SetTestingFactory,
base::Unretained(this)));
feature_list_.InitAndEnableFeature(commerce::kProductSpecifications);
}
void SetTestingFactory(content::BrowserContext* context) {
commerce::ProductSpecificationsServiceFactory::GetInstance()
->SetTestingFactory(
context, base::BindRepeating([](content::BrowserContext* context)
-> std::unique_ptr<KeyedService> {
return commerce::MockProductSpecificationsService::Build();
}));
}
protected:
TabStripModel* tab_strip() { return browser()->tab_strip_model(); }
void AddAndSelectTab(Browser* browser, const GURL& url) {
// ASSERT_TRUE(
// AddTabAtIndex(0, url, ui::PageTransition::PAGE_TRANSITION_TYPED));
ui_test_utils::NavigateToURLWithDisposition(
browser, url, WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
browser->tab_strip_model()->SelectTabAt(
browser->tab_strip_model()->count() - 1);
}
void SelectAllTabs() {
tab_strip()->AddSelectionFromAnchorTo(tab_strip()->count() - 1);
}
void SetProductSpecs(
const std::vector<commerce::ProductSpecificationsSet>& sets) {
auto* product_specs_service =
static_cast<commerce::MockProductSpecificationsService*>(
commerce::ProductSpecificationsServiceFactory::GetForBrowserContext(
browser()->profile()));
ON_CALL(*product_specs_service, GetAllProductSpecifications())
.WillByDefault(testing::Return(sets));
}
base::CallbackListSubscription dependency_manager_subscription_;
base::test::ScopedFeatureList feature_list_;
};
class TabMenuModelComparisonTableDisabledTest
: public TabMenuModelComparisonTableTest {
public:
TabMenuModelComparisonTableDisabledTest() {
feature_list_.InitAndDisableFeature(commerce::kProductSpecifications);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(TabMenuModelComparisonTableDisabledTest,
MenuNotShownWhenFeatureDisabled) {
AddAndSelectTab(browser(), GURL("https://example.com"));
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip(), 0);
EXPECT_FALSE(
model.GetIndexOfCommandId(TabStripModel::CommandAddToNewComparisonTable)
.has_value());
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandAddToExistingComparisonTable)
.has_value());
}
IN_PROC_BROWSER_TEST_F(TabMenuModelComparisonTableTest,
MenuShownForNormalWindow) {
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip(), 0);
// No existing tables, so only the option for adding to a new table should be
// visible.
auto index =
model.GetIndexOfCommandId(TabStripModel::CommandAddToNewComparisonTable);
ASSERT_TRUE(index.has_value());
EXPECT_TRUE(model.IsEnabledAt(index.value()));
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandAddToExistingComparisonTable)
.has_value());
}
IN_PROC_BROWSER_TEST_F(TabMenuModelComparisonTableTest,
MenuNotShownForIncognitoWindow) {
Browser* incognito_browser = CreateIncognitoBrowser(profile());
AddAndSelectTab(incognito_browser, GURL("https://example.com"));
TabMenuModel model(&delegate_,
incognito_browser->GetFeatures().tab_menu_model_delegate(),
incognito_browser->tab_strip_model(), 0);
EXPECT_FALSE(
model.GetIndexOfCommandId(TabStripModel::CommandAddToNewComparisonTable)
.has_value());
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandAddToExistingComparisonTable)
.has_value());
// All tabs must be closed before the object is destroyed.
incognito_browser->tab_strip_model()->CloseAllTabs();
}
IN_PROC_BROWSER_TEST_F(TabMenuModelComparisonTableTest,
MenuNotShownWhenMultipleTabsSelected) {
AddAndSelectTab(browser(), GURL("https://example.com"));
AddAndSelectTab(browser(), GURL("https://sample.com"));
// Close about:blank tab since we don't need it.
browser()->tab_strip_model()->CloseWebContentsAt(0,
TabCloseTypes::CLOSE_NONE);
SelectAllTabs();
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip(), 0);
EXPECT_FALSE(
model.GetIndexOfCommandId(TabStripModel::CommandAddToNewComparisonTable)
.has_value());
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandAddToExistingComparisonTable)
.has_value());
}
IN_PROC_BROWSER_TEST_F(TabMenuModelComparisonTableTest,
MenuShownForExistingTables_SetsDoNotContainUrl) {
const std::vector<commerce::ProductSpecificationsSet> sets = {
commerce::ProductSpecificationsSet(
base::Uuid::GenerateRandomV4().AsLowercaseString(), 0, 0,
{
GURL("https://example1.com"),
},
"Set 1"),
commerce::ProductSpecificationsSet(
base::Uuid::GenerateRandomV4().AsLowercaseString(), 0, 0,
{
GURL("https://example2.com"),
},
"Set 2")};
SetProductSpecs(sets);
AddAndSelectTab(browser(), GURL("https://example.com"));
// Close about:blank tab since we don't need it.
browser()->tab_strip_model()->CloseWebContentsAt(0,
TabCloseTypes::CLOSE_NONE);
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip(), 0);
// There are existing tables, so the submenu for adding to an existing table
// should be visible.
EXPECT_FALSE(
model.GetIndexOfCommandId(TabStripModel::CommandAddToNewComparisonTable)
.has_value());
auto index = model.GetIndexOfCommandId(
TabStripModel::CommandAddToExistingComparisonTable);
EXPECT_TRUE(index.has_value());
EXPECT_TRUE(model.IsEnabledAt(index.value()));
}
IN_PROC_BROWSER_TEST_F(TabMenuModelComparisonTableTest,
MenuShownForExistingTables_SetsContainUrl) {
const std::vector<commerce::ProductSpecificationsSet> sets = {
commerce::ProductSpecificationsSet(
base::Uuid::GenerateRandomV4().AsLowercaseString(), 0, 0,
{
GURL("https://example.com"),
},
"Set 1"),
commerce::ProductSpecificationsSet(
base::Uuid::GenerateRandomV4().AsLowercaseString(), 0, 0,
{
GURL("https://example.com"),
},
"Set 2")};
SetProductSpecs(sets);
AddAndSelectTab(browser(), GURL("https://example.com"));
// Close about:blank tab since we don't need it.
browser()->tab_strip_model()->CloseWebContentsAt(0,
TabCloseTypes::CLOSE_NONE);
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip(), 0);
// All existing tables contain the URL, so only the option for adding to a new
// table should be visible.
auto index =
model.GetIndexOfCommandId(TabStripModel::CommandAddToNewComparisonTable);
EXPECT_TRUE(index.has_value());
EXPECT_TRUE(model.IsEnabledAt(index.value()));
EXPECT_FALSE(model
.GetIndexOfCommandId(
TabStripModel::CommandAddToExistingComparisonTable)
.has_value());
}
// This is a regression test. See crbug.com/406013445 for more details.
IN_PROC_BROWSER_TEST_F(
TabMenuModelComparisonTableTest,
MenuShownForExistingTables_SetsDoNotContainUrl_TabNotActive) {
const std::vector<commerce::ProductSpecificationsSet> sets = {
commerce::ProductSpecificationsSet(
base::Uuid::GenerateRandomV4().AsLowercaseString(), 0, 0,
{
GURL("https://example1.com"),
},
"Set 1"),
};
SetProductSpecs(sets);
AddAndSelectTab(browser(), GURL("https://example2.com"));
// Open a foreground tab with a URL in all sets.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("https://example1.com"),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
// Close about:blank tab since we don't need it.
browser()->tab_strip_model()->CloseWebContentsAt(0,
TabCloseTypes::CLOSE_NONE);
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip(), 0);
// The existing tables do not contain the selected tab's URL, so the submenu
// for adding to an existing table should be visible.
EXPECT_FALSE(
model.GetIndexOfCommandId(TabStripModel::CommandAddToNewComparisonTable)
.has_value());
auto index = model.GetIndexOfCommandId(
TabStripModel::CommandAddToExistingComparisonTable);
EXPECT_TRUE(index.has_value());
EXPECT_TRUE(model.IsEnabledAt(index.value()));
}
#if BUILDFLAG(ENABLE_GLIC)
class TabMenuModelGlicMultiTabTest : public TabMenuModelBrowserTest {
public:
TabMenuModelGlicMultiTabTest() {
scoped_feature_list_.InitAndEnableFeature(
glic::mojom::features::kGlicMultiTab);
}
protected:
TabStripModel* tab_strip() { return browser()->tab_strip_model(); }
glic::GlicSharingManager& sharing_manager() {
return glic::GlicKeyedServiceFactory::GetGlicKeyedService(profile())
->sharing_manager();
}
tabs::TabHandle TabHandleAtIndex(int index) {
return tab_strip()->GetTabAtIndex(index)->GetHandle();
}
glic::GlicTestEnvironment glic_test_environment_;
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(TabMenuModelGlicMultiTabTest, NotShared) {
chrome::NewTab(browser());
chrome::NewTab(browser());
chrome::NewTab(browser());
TabStripModel* tab_strip_model = browser()->tab_strip_model();
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip_model, 1);
EXPECT_TRUE(model.GetIndexOfCommandId(TabStripModel::CommandGlicStartShare)
.has_value());
EXPECT_FALSE(model.GetIndexOfCommandId(TabStripModel::CommandGlicStopShare)
.has_value());
EXPECT_FALSE(model.GetIndexOfCommandId(TabStripModel::CommandGlicShareLimit)
.has_value());
}
IN_PROC_BROWSER_TEST_F(TabMenuModelGlicMultiTabTest, SomeShared) {
chrome::NewTab(browser());
chrome::NewTab(browser());
chrome::NewTab(browser());
sharing_manager().PinTabs({TabHandleAtIndex(0)});
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip(), 1);
EXPECT_TRUE(model.GetIndexOfCommandId(TabStripModel::CommandGlicStartShare)
.has_value());
EXPECT_FALSE(model.GetIndexOfCommandId(TabStripModel::CommandGlicStopShare)
.has_value());
EXPECT_FALSE(model.GetIndexOfCommandId(TabStripModel::CommandGlicShareLimit)
.has_value());
}
IN_PROC_BROWSER_TEST_F(TabMenuModelGlicMultiTabTest, AllShared) {
for (int i = 0; i < 3; ++i) {
chrome::NewTab(browser());
sharing_manager().PinTabs({TabHandleAtIndex(i)});
}
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip(), 1);
EXPECT_FALSE(model.GetIndexOfCommandId(TabStripModel::CommandGlicStartShare)
.has_value());
EXPECT_TRUE(model.GetIndexOfCommandId(TabStripModel::CommandGlicStopShare)
.has_value());
EXPECT_FALSE(model.GetIndexOfCommandId(TabStripModel::CommandGlicShareLimit)
.has_value());
}
IN_PROC_BROWSER_TEST_F(TabMenuModelGlicMultiTabTest, TooManyShared) {
const int limit = sharing_manager().GetMaxPinnedTabs();
for (int i = 0; i < limit; ++i) {
chrome::NewTab(browser());
sharing_manager().PinTabs({TabHandleAtIndex(i)});
}
chrome::NewTab(browser());
tab_strip()->SelectTabAt(limit);
EXPECT_FALSE(sharing_manager().IsTabPinned(TabHandleAtIndex(limit)));
TabMenuModel model(&delegate_,
browser()->GetFeatures().tab_menu_model_delegate(),
tab_strip(), limit);
EXPECT_FALSE(model.GetIndexOfCommandId(TabStripModel::CommandGlicStartShare)
.has_value());
EXPECT_FALSE(model.GetIndexOfCommandId(TabStripModel::CommandGlicStopShare)
.has_value());
EXPECT_TRUE(model.GetIndexOfCommandId(TabStripModel::CommandGlicShareLimit)
.has_value());
}
#endif // BUILDFLAG(ENABLE_GLIC)