| // Copyright 2023 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_strip_model.h" |
| |
| #include "ash/constants/web_app_id_constants.h" |
| #include "base/json/json_reader.h" |
| #include "base/scoped_observation.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/values.h" |
| #include "chrome/browser/policy/policy_test_utils.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_features.h" |
| #include "chrome/browser/ui/tabs/organization/tab_organization_service.h" |
| #include "chrome/browser/ui/tabs/organization/tab_organization_service_factory.h" |
| #include "chrome/browser/ui/tabs/organization/tab_organization_session.h" |
| #include "chrome/browser/ui/tabs/split_tab_metrics.h" |
| #include "chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h" |
| #include "chrome/browser/ui/tabs/tab_group_model.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model_observer.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model_test_utils.h" |
| #include "chrome/browser/ui/ui_features.h" |
| #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h" |
| #include "chrome/browser/web_applications/test/os_integration_test_override_impl.h" |
| #include "chrome/browser/web_applications/test/prevent_close_test_base.h" |
| #include "chrome/browser/web_applications/test/web_app_install_test_utils.h" |
| #include "chrome/browser/web_applications/web_app_install_info.h" |
| #include "chrome/common/chrome_features.h" |
| #include "components/policy/core/browser/browser_policy_connector.h" |
| #include "components/policy/core/browser/browser_policy_connector_base.h" |
| #include "components/policy/core/common/mock_configuration_policy_provider.h" |
| #include "components/policy/core/common/policy_map.h" |
| #include "components/policy/policy_constants.h" |
| #include "components/saved_tab_groups/public/features.h" |
| #include "components/tab_groups/tab_group_id.h" |
| #include "components/tabs/public/split_tab_visual_data.h" |
| #include "components/tabs/public/tab_interface.h" |
| #include "components/webapps/common/web_app_id.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/base/window_open_disposition.h" |
| #include "url/gurl.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 |
| |
| using testing::_; |
| |
| namespace { |
| constexpr char kCalculatorAppUrl[] = "https://calculator.apps.chrome/"; |
| |
| constexpr char kPreventCloseEnabledForCalculator[] = R"([ |
| { |
| "manifest_id": "https://calculator.apps.chrome/", |
| "run_on_os_login": "run_windowed", |
| "prevent_close_after_run_on_os_login": true |
| } |
| ])"; |
| |
| constexpr char kCalculatorForceInstalled[] = R"([ |
| { |
| "url": "https://calculator.apps.chrome/", |
| "default_launch_container": "window" |
| } |
| ])"; |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| constexpr bool kShouldPreventClose = true; |
| #else |
| constexpr bool kShouldPreventClose = false; |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| } // namespace |
| |
| class TabStripModelPreventCloseTest : public PreventCloseTestBase, |
| public BrowserListObserver, |
| public TabStripModelObserver { |
| public: |
| TabStripModelPreventCloseTest() { BrowserList::AddObserver(this); } |
| |
| explicit TabStripModelPreventCloseTest(const PreventCloseTestBase&) = delete; |
| TabStripModelPreventCloseTest& operator=( |
| const TabStripModelPreventCloseTest&) = delete; |
| |
| ~TabStripModelPreventCloseTest() override { |
| BrowserList::RemoveObserver(this); |
| } |
| |
| // BrowserListObserver: |
| void OnBrowserRemoved(Browser* browser) override { observer_.Reset(); } |
| |
| // TabStripModelObserver: |
| MOCK_METHOD(void, |
| TabCloseCancelled, |
| (const content::WebContents* contents), |
| (override)); |
| |
| protected: |
| web_app::OsIntegrationTestOverrideBlockingRegistration faked_os_integration_; |
| base::ScopedObservation<TabStripModel, TabStripModelPreventCloseTest> |
| observer_{this}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(TabStripModelPreventCloseTest, |
| PreventCloseEnforedByPolicy) { |
| InstallPWA(GURL(kCalculatorAppUrl), ash::kCalculatorAppId); |
| SetPoliciesAndWaitUntilInstalled(ash::kCalculatorAppId, |
| kPreventCloseEnabledForCalculator, |
| kCalculatorForceInstalled); |
| Browser* const browser = |
| LaunchPWA(ash::kCalculatorAppId, /*launch_in_window=*/true); |
| ASSERT_TRUE(browser); |
| |
| observer_.Observe(browser->tab_strip_model()); |
| |
| TabStripModel* const tab_strip_model = browser->tab_strip_model(); |
| EXPECT_EQ(1, tab_strip_model->count()); |
| EXPECT_EQ(!kShouldPreventClose, tab_strip_model->IsTabClosable( |
| tab_strip_model->GetActiveWebContents())); |
| |
| EXPECT_CALL(*this, TabCloseCancelled(_)).Times(kShouldPreventClose ? 1 : 0); |
| |
| tab_strip_model->CloseAllTabs(); |
| EXPECT_EQ(kShouldPreventClose ? 1 : 0, tab_strip_model->count()); |
| |
| if (kShouldPreventClose) { |
| ClearWebAppSettings(); |
| EXPECT_TRUE(tab_strip_model->IsTabClosable( |
| tab_strip_model->GetActiveWebContents())); |
| |
| tab_strip_model->CloseAllTabs(); |
| EXPECT_EQ(0, tab_strip_model->count()); |
| } |
| } |
| |
| // TODO(b/321593065): enable this flaky test. |
| #if BUILDFLAG(IS_CHROMEOS) |
| #define MAYBE_PreventCloseEnforcedByPolicyTabbedAppShallBeClosable \ |
| DISABLED_PreventCloseEnforcedByPolicyTabbedAppShallBeClosable |
| #else |
| #define MAYBE_PreventCloseEnforcedByPolicyTabbedAppShallBeClosable \ |
| PreventCloseEnforcedByPolicyTabbedAppShallBeClosable |
| #endif |
| IN_PROC_BROWSER_TEST_F( |
| TabStripModelPreventCloseTest, |
| MAYBE_PreventCloseEnforcedByPolicyTabbedAppShallBeClosable) { |
| InstallPWA(GURL(kCalculatorAppUrl), ash::kCalculatorAppId); |
| SetPoliciesAndWaitUntilInstalled(ash::kCalculatorAppId, |
| kPreventCloseEnabledForCalculator, |
| kCalculatorForceInstalled); |
| Browser* const browser = |
| LaunchPWA(ash::kCalculatorAppId, /*launch_in_window=*/false); |
| ASSERT_TRUE(browser); |
| |
| observer_.Observe(browser->tab_strip_model()); |
| |
| TabStripModel* const tab_strip_model = browser->tab_strip_model(); |
| EXPECT_NE(0, tab_strip_model->count()); |
| EXPECT_TRUE( |
| tab_strip_model->IsTabClosable(tab_strip_model->GetActiveWebContents())); |
| |
| EXPECT_CALL(*this, TabCloseCancelled(_)).Times(0); |
| |
| tab_strip_model->CloseAllTabs(); |
| EXPECT_EQ(0, tab_strip_model->count()); |
| } |
| |
| class TabStripModelBrowserTest : public InProcessBrowserTest, |
| public TabStripModelObserver { |
| public: |
| TabStripModelBrowserTest() { |
| feature_list_.InitWithFeatures( |
| {features::kTabOrganization, features::kSideBySide}, {}); |
| } |
| |
| void TearDownOnMainThread() override { observer_.Reset(); } |
| |
| MOCK_METHOD(void, |
| OnTabGroupAdded, |
| (const tab_groups::TabGroupId& group_id), |
| (override)); |
| MOCK_METHOD(void, |
| OnTabGroupWillBeRemoved, |
| (const tab_groups::TabGroupId& group_id), |
| (override)); |
| |
| void AddTabs(int num_tabs) { |
| for (int i = 0; i < num_tabs; i++) { |
| chrome::AddTabAt(browser(), GURL(url::kAboutBlankURL), 1, false); |
| } |
| } |
| |
| void PrepareTabs(int num_tabs) { |
| for (int i = 0; i < num_tabs; i++) { |
| if (!browser()->tab_strip_model()->ContainsIndex(i)) { |
| chrome::AddTabAt(browser(), GURL(url::kAboutBlankURL), i, false); |
| } |
| SetID(browser()->tab_strip_model()->GetWebContentsAt(i), i); |
| } |
| EXPECT_EQ(num_tabs, browser()->tab_strip_model()->count()); |
| } |
| |
| void PrepareTabstripForSelectionTest(TabStripModel* model, |
| int tab_count, |
| int pinned_count, |
| const std::vector<int> selected_tabs) { |
| ::PrepareTabstripForSelectionTest( |
| base::BindOnce(&TabStripModelBrowserTest::PrepareTabs, |
| base::Unretained(this)), |
| model, tab_count, pinned_count, selected_tabs); |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| base::ScopedObservation<TabStripModel, TabStripModelBrowserTest> observer_{ |
| this}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(TabStripModelBrowserTest, OnTabGroupAdded) { |
| EXPECT_EQ(1, browser()->tab_strip_model()->count()); |
| |
| // We should already have a tab. Add it to a group and see if |
| // TabStripModelObserver::OnTabGroupAdded is called. |
| EXPECT_CALL(*this, OnTabGroupAdded(_)).Times(1); |
| |
| observer_.Observe(browser()->tab_strip_model()); |
| browser()->tab_strip_model()->AddToNewGroup({0}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabStripModelBrowserTest, OnTabGroupWillBeRemoved) { |
| EXPECT_EQ(1, browser()->tab_strip_model()->count()); |
| tab_groups::TabGroupId group_id = |
| browser()->tab_strip_model()->AddToNewGroup({0}); |
| |
| // Close the group and see if TabStripModelObserver::OnTabGroupWillBeRemoved |
| // is called. |
| EXPECT_CALL(*this, OnTabGroupWillBeRemoved(group_id)).Times(1); |
| |
| observer_.Observe(browser()->tab_strip_model()); |
| browser()->tab_strip_model()->CloseAllTabsInGroup(group_id); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabStripModelBrowserTest, CommandOrganizeTabs) { |
| base::HistogramTester histogram_tester; |
| |
| TabStripModel* const tab_strip_model = browser()->tab_strip_model(); |
| EXPECT_EQ(1, tab_strip_model->count()); |
| |
| EXPECT_TRUE(tab_strip_model->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandOrganizeTabs)); |
| |
| // Execute CommandOrganizeTabs once. Expect a request to have been started. |
| tab_strip_model->ExecuteContextMenuCommand( |
| 0, TabStripModel::CommandOrganizeTabs); |
| |
| TabOrganizationService* const service = |
| TabOrganizationServiceFactory::GetForProfile(browser()->profile()); |
| const TabOrganizationSession* const session = |
| service->GetSessionForBrowser(browser()); |
| EXPECT_NE(session, nullptr); |
| EXPECT_EQ(session->request()->state(), |
| TabOrganizationRequest::State::NOT_STARTED); |
| |
| histogram_tester.ExpectUniqueSample("Tab.Organization.AllEntrypoints.Clicked", |
| true, 1); |
| histogram_tester.ExpectUniqueSample("Tab.Organization.TabContextMenu.Clicked", |
| true, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabStripModelBrowserTest, |
| DetachWebContentsAtForInsertion) { |
| class WebContentsRemovedObserver : public TabStripModelObserver { |
| public: |
| WebContentsRemovedObserver() = default; |
| WebContentsRemovedObserver(const WebContentsRemovedObserver&) = delete; |
| WebContentsRemovedObserver& operator=(const WebContentsRemovedObserver&) = |
| delete; |
| ~WebContentsRemovedObserver() override = default; |
| |
| // TabStripModelObserver: |
| void OnTabStripModelChanged( |
| TabStripModel* tab_strip_model, |
| const TabStripModelChange& change, |
| const TabStripSelectionChange& selection) override { |
| if (change.type() == TabStripModelChange::kRemoved) { |
| const TabStripModelChange::RemovedTab& removed_tab = |
| change.GetRemove()->contents[0]; |
| remove_reason_ = removed_tab.remove_reason; |
| tab_detach_reason_ = removed_tab.tab_detach_reason; |
| } |
| } |
| |
| std::optional<TabStripModelChange::RemoveReason> remove_reason() const { |
| return remove_reason_; |
| } |
| std::optional<tabs::TabInterface::DetachReason> tab_detach_reason() const { |
| return tab_detach_reason_; |
| } |
| |
| private: |
| std::optional<TabStripModelChange::RemoveReason> remove_reason_; |
| std::optional<tabs::TabInterface::DetachReason> tab_detach_reason_; |
| }; |
| |
| // Start with a browser window with 2 tabs. |
| TabStripModel* tab_strip_model = browser()->tab_strip_model(); |
| chrome::AddTabAt(browser(), GURL(url::kAboutBlankURL), 1, true); |
| tabs::TabInterface* const initial_tab = tab_strip_model->GetTabAtIndex(1); |
| EXPECT_EQ(2, tab_strip_model->count()); |
| |
| base::MockCallback<tabs::TabInterface::WillDetach> tab_detached_callback; |
| |
| base::CallbackListSubscription tab_subscription = |
| initial_tab->RegisterWillDetach(tab_detached_callback.Get()); |
| WebContentsRemovedObserver removed_observer; |
| tab_strip_model->AddObserver(&removed_observer); |
| |
| // Extract the new WebContents for re-insertion. |
| EXPECT_CALL(tab_detached_callback, |
| Run(tab_strip_model->GetTabAtIndex(1), |
| tabs::TabInterface::DetachReason::kDelete)); |
| std::unique_ptr<content::WebContents> extracted_contents = |
| tab_strip_model->DetachWebContentsAtForInsertion(1); |
| EXPECT_EQ(TabStripModelChange::RemoveReason::kInsertedIntoOtherTabStrip, |
| removed_observer.remove_reason()); |
| EXPECT_EQ(tabs::TabInterface::DetachReason::kDelete, |
| removed_observer.tab_detach_reason()); |
| tab_strip_model->AppendWebContents(std::move(extracted_contents), true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabStripModelBrowserTest, |
| ReplaceActiveTabWhenDeletesGroupShowsDeletionDialog) { |
| TabStripModel* const tab_strip_model = browser()->tab_strip_model(); |
| AddTabs(4); |
| EXPECT_EQ(tab_strip_model->count(), 5); |
| |
| // Enter a zero state split. This adds a new tab. |
| tab_strip_model->ActivateTabAt(0); |
| tab_strip_model->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandAddToSplit); |
| |
| // Add tab at index 4 to a group. |
| tab_groups::TabGroupId group_id = tab_strip_model->AddToNewGroup({4}); |
| tab_strip_model->ActivateTabAt(1); |
| |
| tab_strip_model->UpdateTabInSplit(tab_strip_model->GetTabAtIndex(1), 4, |
| TabStripModel::SplitUpdateType::kReplace); |
| |
| // Make sure the dialog is shown, and fake clicking the button. |
| tab_groups::DeletionDialogController* deletion_dialog_controller = |
| browser()->GetFeatures().tab_group_deletion_dialog_controller(); |
| EXPECT_TRUE(deletion_dialog_controller->IsShowingDialog()); |
| |
| // Pull the dialog state and call the OnDialogOk method. |
| deletion_dialog_controller->SimulateOkButtonForTesting(); |
| |
| EXPECT_FALSE(tab_strip_model->group_model()->ContainsTabGroup(group_id)); |
| EXPECT_TRUE(tab_strip_model->GetTabAtIndex(0)->GetSplit().has_value()); |
| EXPECT_TRUE(tab_strip_model->GetTabAtIndex(1)->GetSplit().has_value()); |
| } |
| |
| // Tests IsContextMenuCommandEnabled and ExecuteContextMenuCommand with |
| // CommandTogglePinned. |
| IN_PROC_BROWSER_TEST_F(TabStripModelBrowserTest, CommandAddToSplit) { |
| TabStripModel* const tab_strip_model = browser()->tab_strip_model(); |
| AddTabs(4); |
| EXPECT_EQ(tab_strip_model->count(), 5); |
| tab_strip_model->SetTabPinned(0, true); |
| tab_strip_model->SetTabPinned(1, true); |
| |
| // Add tab at index 4 to a group. |
| tab_strip_model->AddToNewGroup({4}); |
| |
| tab_strip_model->ActivateTabAt(3); |
| EXPECT_TRUE(tab_strip_model->IsContextMenuCommandEnabled( |
| 0, TabStripModel::CommandAddToSplit)); |
| EXPECT_TRUE(tab_strip_model->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandAddToSplit)); |
| EXPECT_TRUE(tab_strip_model->IsContextMenuCommandEnabled( |
| 2, TabStripModel::CommandAddToSplit)); |
| EXPECT_TRUE(tab_strip_model->IsContextMenuCommandEnabled( |
| 3, TabStripModel::CommandAddToSplit)); |
| EXPECT_TRUE(tab_strip_model->IsContextMenuCommandEnabled( |
| 4, TabStripModel::CommandAddToSplit)); |
| |
| tab_strip_model->ExecuteContextMenuCommand(0, |
| TabStripModel::CommandAddToSplit); |
| |
| // The first tab should become unpinned and adjacent to the active tab. |
| EXPECT_TRUE(tab_strip_model->GetTabAtIndex(0)->IsPinned()); |
| EXPECT_FALSE(tab_strip_model->GetTabAtIndex(1)->IsPinned()); |
| EXPECT_FALSE(tab_strip_model->GetTabAtIndex(1)->IsSplit()); |
| EXPECT_TRUE(tab_strip_model->GetTabAtIndex(2)->IsSplit()); |
| EXPECT_TRUE(tab_strip_model->GetTabAtIndex(3)->IsSplit()); |
| EXPECT_TRUE(tab_strip_model->GetTabAtIndex(4)->GetGroup().has_value()); |
| EXPECT_FALSE(tab_strip_model->GetTabAtIndex(4)->IsSplit()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabStripModelBrowserTest, |
| CommandAddToSplitWhenDeletesGroupShowsDeletionDialog) { |
| TabStripModel* const tab_strip_model = browser()->tab_strip_model(); |
| AddTabs(4); |
| EXPECT_EQ(tab_strip_model->count(), 5); |
| |
| // Add tab at index 4 to a group. |
| tab_groups::TabGroupId group_id = tab_strip_model->AddToNewGroup({4}); |
| |
| tab_strip_model->ActivateTabAt(3); |
| EXPECT_TRUE(tab_strip_model->IsContextMenuCommandEnabled( |
| 4, TabStripModel::CommandAddToSplit)); |
| |
| tab_strip_model->ExecuteContextMenuCommand(4, |
| TabStripModel::CommandAddToSplit); |
| |
| // Make sure the dialog is shown, and fake clicking the button. |
| tab_groups::DeletionDialogController* deletion_dialog_controller = |
| browser()->GetFeatures().tab_group_deletion_dialog_controller(); |
| EXPECT_TRUE(deletion_dialog_controller->IsShowingDialog()); |
| |
| // Pull the dialog state and call the OnDialogOk method. |
| deletion_dialog_controller->SimulateOkButtonForTesting(); |
| |
| EXPECT_FALSE(tab_strip_model->group_model()->ContainsTabGroup(group_id)); |
| EXPECT_TRUE(tab_strip_model->GetTabAtIndex(3)->GetSplit().has_value()); |
| EXPECT_TRUE(tab_strip_model->GetTabAtIndex(4)->GetSplit().has_value()); |
| } |
| |
| // Calling duplicate on a tab that isn't selected doesn't affect selected tabs. |
| IN_PROC_BROWSER_TEST_F(TabStripModelBrowserTest, CommandDuplicate) { |
| TabStripModel* const tab_strip_model = browser()->tab_strip_model(); |
| |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tab_strip_model, 3, 1, {0, 1})); |
| ASSERT_EQ("0p 1 2", GetTabStripStateString(tab_strip_model)); |
| |
| EXPECT_TRUE(tab_strip_model->IsContextMenuCommandEnabled( |
| 2, TabStripModel::CommandDuplicate)); |
| tab_strip_model->ExecuteContextMenuCommand(2, |
| TabStripModel::CommandDuplicate); |
| // Should have duplicated tab 2. |
| EXPECT_EQ("0p 1 2 -1", GetTabStripStateString(tab_strip_model)); |
| } |
| |
| // Calling duplicate on a split that isn't selected doesn't affect selected |
| // tabs. |
| IN_PROC_BROWSER_TEST_F(TabStripModelBrowserTest, CommandDuplicateSplit) { |
| TabStripModel* const tab_strip_model = browser()->tab_strip_model(); |
| |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tab_strip_model, 4, 1, {2})); |
| tab_strip_model->AddToNewSplit( |
| {3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| ASSERT_EQ("0p 1 2s 3s", GetTabStripStateString(tab_strip_model)); |
| tab_strip_model->ActivateTabAt(1); |
| |
| EXPECT_TRUE(tab_strip_model->IsContextMenuCommandEnabled( |
| 2, TabStripModel::CommandDuplicate)); |
| tab_strip_model->ExecuteContextMenuCommand(2, |
| TabStripModel::CommandDuplicate); |
| // Should have duplicated split with tabs 2 and 3. |
| EXPECT_EQ("0p 1 2s 3s -1s -1s", GetTabStripStateString(tab_strip_model)); |
| } |
| |
| // Calling duplicate on a tab that is selected affects all the selected tabs. |
| IN_PROC_BROWSER_TEST_F(TabStripModelBrowserTest, CommandDuplicateSelected) { |
| TabStripModel* const tab_strip_model = browser()->tab_strip_model(); |
| |
| ASSERT_NO_FATAL_FAILURE( |
| PrepareTabstripForSelectionTest(tab_strip_model, 12, 6, {2})); |
| tab_strip_model->AddToNewSplit( |
| {3}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tab_strip_model->ActivateTabAt(4); |
| tab_strip_model->AddToNewSplit( |
| {5}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tab_strip_model->ActivateTabAt(8); |
| tab_strip_model->AddToNewSplit( |
| {9}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tab_strip_model->ActivateTabAt(10); |
| tab_strip_model->AddToNewSplit( |
| {11}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| ASSERT_EQ("0p 1p 2ps 3ps 4ps 5ps 6 7 8s 9s 10s 11s", |
| GetTabStripStateString(tab_strip_model)); |
| tab_strip_model->ActivateTabAt(1); |
| tab_strip_model->SelectTabAt(4); |
| tab_strip_model->SelectTabAt(5); |
| tab_strip_model->SelectTabAt(7); |
| tab_strip_model->SelectTabAt(10); |
| tab_strip_model->SelectTabAt(11); |
| |
| EXPECT_TRUE(tab_strip_model->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandDuplicate)); |
| tab_strip_model->ExecuteContextMenuCommand(1, |
| TabStripModel::CommandDuplicate); |
| // Should have duplicated tabs 1, 4, 5, 7, 10, and 11. |
| EXPECT_EQ("0p 1p -1p 2ps 3ps 4ps 5ps -1ps -1ps 6 7 -1 8s 9s 10s 11s -1s -1s", |
| GetTabStripStateString(tab_strip_model)); |
| } |
| |
| #if BUILDFLAG(ENABLE_GLIC) |
| class TabStripModelGlicMultiTabBrowserTest : public TabStripModelBrowserTest { |
| public: |
| TabStripModelGlicMultiTabBrowserTest() { |
| scoped_feature_list_.InitAndEnableFeature( |
| glic::mojom::features::kGlicMultiTab); |
| } |
| |
| protected: |
| TabStripModel* tab_strip() { return browser()->tab_strip_model(); } |
| |
| glic::GlicKeyedService* service() { |
| return glic::GlicKeyedServiceFactory::GetGlicKeyedService( |
| browser()->profile()); |
| } |
| |
| glic::GlicSharingManager& sharing_manager() { |
| return service()->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(TabStripModelGlicMultiTabBrowserTest, StartSharing) { |
| AddTabs(3); |
| tab_strip()->ActivateTabAt(0); |
| tab_strip()->SelectTabAt(1); |
| tab_strip()->SelectTabAt(2); |
| EXPECT_TRUE(tab_strip()->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandGlicStartShare)); |
| tab_strip()->ExecuteContextMenuCommand(1, |
| TabStripModel::CommandGlicStartShare); |
| for (int i = 0; i < 3; ++i) { |
| EXPECT_TRUE(sharing_manager().IsTabPinned(TabHandleAtIndex(i))); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabStripModelGlicMultiTabBrowserTest, |
| StartSharingSubset) { |
| AddTabs(3); |
| tab_strip()->ActivateTabAt(0); |
| tab_strip()->SelectTabAt(1); |
| tab_strip()->SelectTabAt(2); |
| sharing_manager().PinTabs({TabHandleAtIndex(1)}); |
| EXPECT_TRUE(tab_strip()->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandGlicStartShare)); |
| tab_strip()->ExecuteContextMenuCommand(1, |
| TabStripModel::CommandGlicStartShare); |
| for (int i = 0; i < 3; ++i) { |
| EXPECT_TRUE(sharing_manager().IsTabPinned(TabHandleAtIndex(i))) |
| << "with tab index: " << i; |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabStripModelGlicMultiTabBrowserTest, StopSharing) { |
| AddTabs(2); |
| tab_strip()->ActivateTabAt(0); |
| tab_strip()->SelectTabAt(1); |
| sharing_manager().PinTabs({TabHandleAtIndex(0), TabHandleAtIndex(1)}); |
| EXPECT_TRUE(tab_strip()->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandGlicStopShare)); |
| tab_strip()->ExecuteContextMenuCommand(1, |
| TabStripModel::CommandGlicStopShare); |
| for (int i = 0; i < 2; ++i) { |
| EXPECT_FALSE(sharing_manager().IsTabPinned(TabHandleAtIndex(i))) |
| << "with tab index: " << i; |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabStripModelGlicMultiTabBrowserTest, ShareLimit) { |
| const int limit = sharing_manager().GetMaxPinnedTabs(); |
| AddTabs(limit); |
| for (int i = 0; i < limit; ++i) { |
| sharing_manager().PinTabs({TabHandleAtIndex(i)}); |
| } |
| EXPECT_FALSE(tab_strip()->IsContextMenuCommandEnabled( |
| 1, TabStripModel::CommandGlicShareLimit)); |
| |
| tab_strip()->SelectTabAt(0); |
| tab_strip()->CloseSelectedTabs(); |
| EXPECT_EQ(limit - 1, sharing_manager().GetNumPinnedTabs()); |
| |
| sharing_manager().UnpinAllTabs(); |
| EXPECT_EQ(0, sharing_manager().GetNumPinnedTabs()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabStripModelGlicMultiTabBrowserTest, |
| StartSharingShouldOpenGlicWindow) { |
| AddTabs(1); |
| tab_strip()->ActivateTabAt(0); |
| EXPECT_FALSE(service()->IsWindowOrFreShowing()); |
| |
| tab_strip()->ExecuteContextMenuCommand(1, |
| TabStripModel::CommandGlicStartShare); |
| |
| EXPECT_TRUE(service()->IsWindowOrFreShowing()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabStripModelGlicMultiTabBrowserTest, |
| StartSharingShouldNotCloseGlicWindow) { |
| AddTabs(1); |
| tab_strip()->ActivateTabAt(0); |
| service()->ToggleUI(/*bwi=*/nullptr, /*prevent_close=*/true, |
| glic::mojom::InvocationSource::kOsButton); |
| EXPECT_TRUE(service()->IsWindowOrFreShowing()); |
| |
| tab_strip()->ExecuteContextMenuCommand(1, |
| TabStripModel::CommandGlicStartShare); |
| |
| EXPECT_TRUE(service()->IsWindowOrFreShowing()); |
| } |
| |
| #endif // BUILDFLAG(ENABLE_GLIC) |