| // Copyright 2018 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/browser_commands.h" |
| |
| #include "base/path_service.h" |
| #include "base/task/current_thread.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "chrome/browser/content_settings/cookie_settings_factory.h" |
| #include "chrome/browser/lifetime/browser_shutdown.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/browser_window/public/browser_window_interface.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_model.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #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" |
| #include "components/commerce/core/mojom/product_specifications.mojom.h" |
| #include "components/commerce/core/pref_names.h" |
| #include "components/content_settings/core/browser/cookie_settings.h" |
| #include "components/content_settings/core/common/pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/tab_groups/tab_group_id.h" |
| #include "components/tabs/public/split_tab_id.h" |
| #include "components/tabs/public/tab_group.h" |
| #include "components/ukm/test_ukm_recorder.h" |
| #include "content/public/common/content_paths.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "ui/base/ui_base_features.h" |
| |
| namespace chrome { |
| |
| class BrowserCommandsTest : public InProcessBrowserTest { |
| public: |
| BrowserCommandsTest() : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| feature_list_.InitWithFeatures( |
| { |
| features::kTabOrganization, |
| features::kTabstripDeclutter, |
| toast_features::kReadingListToast, |
| toast_features::kLinkCopiedToast, |
| features::kSideBySide, |
| }, |
| { |
| features::kReloadSelectionModel, |
| }); |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| net::test_server::EmbeddedTestServer https_server_; |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| base::FilePath path; |
| base::PathService::Get(content::DIR_TEST_DATA, &path); |
| https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); |
| https_server_.ServeFilesFromDirectory(path); |
| https_server_.AddDefaultHandlers(GetChromeTestDataDir()); |
| ASSERT_TRUE(https_server_.Start()); |
| } |
| |
| static constexpr char kUrl[] = "chrome://version/"; |
| |
| void AddTabs(Browser* browser, int num_tabs) { |
| for (int i = 0; i < num_tabs; ++i) { |
| chrome::NewTab(browser); |
| } |
| } |
| |
| void AddTabs(int num_tabs) { AddTabs(browser(), num_tabs); } |
| |
| void AddAndReloadTabs(int tab_count) { |
| AddTabs(tab_count); |
| |
| // Add tabs to the selection (the last one created remains selected) and |
| // trigger a reload command on all of them. |
| for (int i = 0; i < tab_count - 1; ++i) { |
| browser()->tab_strip_model()->SelectTabAt(i + 1); |
| } |
| EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_RELOAD)); |
| browser()->tab_strip_model()->CloseSelectedTabs(); |
| } |
| |
| void SetThirdPartyCookieBlocking(bool enabled) { |
| browser()->profile()->GetPrefs()->SetInteger( |
| prefs::kCookieControlsMode, |
| static_cast<int>( |
| enabled ? content_settings::CookieControlsMode::kBlockThirdParty |
| : content_settings::CookieControlsMode::kOff)); |
| } |
| |
| void CheckReloadBreakageMetrics(ukm::TestAutoSetUkmRecorder& ukm_recorder, |
| size_t size, |
| size_t index, |
| bool blocked, |
| bool settings_blocked) { |
| auto entries = ukm_recorder.GetEntries( |
| "ThirdPartyCookies.BreakageIndicator.UserReload", |
| {"TPCBlocked", "TPCBlockedInSettings"}); |
| EXPECT_EQ(entries.size(), size); |
| EXPECT_EQ(entries.at(index).metrics.at("TPCBlocked"), blocked); |
| EXPECT_EQ(entries.at(index).metrics.at("TPCBlockedInSettings"), |
| settings_blocked); |
| } |
| |
| void CheckBrowserContainsTabGroupWithSize( |
| const BrowserWindowInterface* browser, |
| tab_groups::TabGroupId group_id, |
| int size) { |
| EXPECT_TRUE( |
| browser->GetTabStripModel()->group_model()->ContainsTabGroup(group_id)); |
| EXPECT_EQ(size, browser->GetTabStripModel() |
| ->group_model() |
| ->GetTabGroup(group_id) |
| ->ListTabs() |
| .length()); |
| } |
| |
| class ReloadObserver : public content::WebContentsObserver { |
| public: |
| ~ReloadObserver() override = default; |
| |
| int load_count() const { return load_count_; } |
| void SetWebContents(content::WebContents* web_contents) { |
| Observe(web_contents); |
| } |
| |
| // content::WebContentsObserver |
| void DidStartLoading() override { load_count_++; } |
| |
| private: |
| int load_count_ = 0; |
| }; |
| }; |
| |
| // Verify that calling BookmarkCurrentTab() just after closing all tabs doesn't |
| // cause a crash. https://crbug.com/799668 |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, BookmarkCurrentTabAfterCloseTabs) { |
| browser()->tab_strip_model()->CloseAllTabs(); |
| BookmarkCurrentTab(browser()); |
| } |
| |
| // Verify that all of selected tabs are refreshed after executing a reload |
| // command. https://crbug.com/862102 |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, ReloadSelectedTabs) { |
| constexpr int kTabCount = 3; |
| std::vector<ReloadObserver> watcher_vec(kTabCount); |
| for (int i = 0; i < kTabCount; i++) { |
| ASSERT_TRUE(AddTabAtIndexToBrowser(browser(), i + 1, GURL(kUrl), |
| ui::PAGE_TRANSITION_LINK, false)); |
| content::WebContents* tab = |
| browser()->tab_strip_model()->GetWebContentsAt(i + 1); |
| watcher_vec[i].SetWebContents(tab); |
| } |
| |
| for (ReloadObserver& watcher : watcher_vec) { |
| EXPECT_EQ(0, watcher.load_count()); |
| } |
| |
| // Add two tabs to the selection (the last one created remains selected) and |
| // trigger a reload command on all of them. |
| for (int i = 0; i < kTabCount - 1; i++) { |
| browser()->tab_strip_model()->SelectTabAt(i + 1); |
| } |
| EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_RELOAD)); |
| |
| int load_sum = 0; |
| for (ReloadObserver& watcher : watcher_vec) { |
| load_sum += watcher.load_count(); |
| } |
| EXPECT_EQ(kTabCount, load_sum); |
| } |
| |
| class BrowserCommandsWithReloadSelectionModelTest : public BrowserCommandsTest { |
| public: |
| BrowserCommandsWithReloadSelectionModelTest() { |
| feature_list_.InitWithFeatures( |
| { |
| features::kReloadSelectionModel, |
| }, |
| {}); |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // With kReloadSelectionModel enabled, only the active tab is reloaded. |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsWithReloadSelectionModelTest, |
| ReloadSelectedTabs) { |
| // Add feature |
| constexpr int kTabCount = 3; |
| std::vector<ReloadObserver> watcher_vec(kTabCount); |
| for (int i = 0; i < kTabCount; i++) { |
| ASSERT_TRUE(AddTabAtIndexToBrowser(browser(), i + 1, GURL(kUrl), |
| ui::PAGE_TRANSITION_LINK, false)); |
| content::WebContents* tab = |
| browser()->tab_strip_model()->GetWebContentsAt(i + 1); |
| watcher_vec[i].SetWebContents(tab); |
| } |
| |
| for (ReloadObserver& watcher : watcher_vec) { |
| EXPECT_EQ(0, watcher.load_count()); |
| } |
| |
| // Add two tabs to the selection (the last one created remains selected) and |
| // trigger a reload command on the active tab. |
| for (int i = 0; i < kTabCount - 1; i++) { |
| browser()->tab_strip_model()->SelectTabAt(i + 1); |
| } |
| EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_RELOAD)); |
| |
| int load_sum = 0; |
| for (ReloadObserver& watcher : watcher_vec) { |
| load_sum += watcher.load_count(); |
| } |
| EXPECT_EQ(1, load_sum); |
| } |
| |
| class BrowserCommandsWithCloseHotkeySplitViewTest : public BrowserCommandsTest { |
| public: |
| BrowserCommandsWithCloseHotkeySplitViewTest() { |
| feature_list_.InitWithFeatures( |
| { |
| features::kCloseActiveTabInSplitViewViaHotkey, |
| features::kSideBySide, |
| }, |
| {}); |
| } |
| |
| protected: |
| TabStripModel* GetTabStripModel(Browser* browser) { |
| return browser->tab_strip_model(); |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // With kCloseHotkeySplitView enabled, only the active tab in the split view is |
| // closed. |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsWithCloseHotkeySplitViewTest, |
| OnlyCloseActiveTabInSplitView) { |
| // Add 2 tabs. |
| constexpr int kTabCount = 3; |
| for (int i = 1; i < kTabCount; i++) { |
| ASSERT_TRUE(AddTabAtIndexToBrowser(browser(), i, GURL(kUrl), |
| ui::PAGE_TRANSITION_LINK, false)); |
| } |
| |
| EXPECT_EQ(kTabCount, GetTabStripModel(browser())->GetTabCount()); |
| |
| // Add second last tab to split view with the last tab. |
| GetTabStripModel(browser())->AddToNewSplit( |
| {kTabCount - 2}, |
| split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kVertical, |
| 1.0f), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_TRUE(GetTabStripModel(browser())->IsActiveTabSplit()); |
| |
| EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_CLOSE_TAB)); |
| |
| EXPECT_FALSE(GetTabStripModel(browser())->IsActiveTabSplit()); |
| EXPECT_EQ(2, GetTabStripModel(browser())->GetTabCount()); |
| } |
| |
| // All tabs in the selection model get closed. |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsWithCloseHotkeySplitViewTest, |
| CloseAllTabsInSelectionModel) { |
| // Add 3 tabs. |
| constexpr int kTabCount = 4; |
| for (int i = 1; i < kTabCount; i++) { |
| ASSERT_TRUE(AddTabAtIndexToBrowser(browser(), i, GURL(kUrl), |
| ui::PAGE_TRANSITION_LINK, false)); |
| } |
| |
| EXPECT_EQ(kTabCount, GetTabStripModel(browser())->GetTabCount()); |
| |
| // Add second last tab to split view with the last tab. |
| GetTabStripModel(browser())->AddToNewSplit( |
| {kTabCount - 2}, |
| split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kVertical, |
| 1.0f), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| EXPECT_TRUE(GetTabStripModel(browser())->IsActiveTabSplit()); |
| |
| // Add a non-split tab to the selection model. |
| GetTabStripModel(browser())->SelectTabAt(kTabCount - 3); |
| |
| EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_CLOSE_TAB)); |
| |
| // Only one, non-split tab should remain. |
| EXPECT_FALSE(GetTabStripModel(browser())->IsActiveTabSplit()); |
| EXPECT_EQ(1, GetTabStripModel(browser())->GetTabCount()); |
| } |
| |
| // Check that the ThirdPartyCookieBreakageIndicator UKM is sent on Reload. |
| // Disabled because of crbug.com/1468528 |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, DISABLED_ReloadBreakageUKM) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| content_settings::CookieSettings* settings = |
| CookieSettingsFactory::GetForProfile(browser()->profile()).get(); |
| |
| // Test simple reload measurements without 3PCB. |
| SetThirdPartyCookieBlocking(false); |
| EXPECT_FALSE(settings->ShouldBlockThirdPartyCookies()); |
| |
| AddAndReloadTabs(1); |
| CheckReloadBreakageMetrics(ukm_recorder, 1, 0, false, false); |
| |
| AddAndReloadTabs(1); |
| CheckReloadBreakageMetrics(ukm_recorder, 2, 1, false, false); |
| |
| // Test that enabled 3PCB is correctly reflected in the metrics. |
| SetThirdPartyCookieBlocking(true); |
| EXPECT_TRUE(settings->ShouldBlockThirdPartyCookies()); |
| |
| AddAndReloadTabs(1); |
| CheckReloadBreakageMetrics(ukm_recorder, 3, 2, false, true); |
| |
| // Test that allow-listing is correctly reflected in the metrics. |
| GURL origin(kUrl); |
| settings->SetThirdPartyCookieSetting(origin, |
| ContentSetting::CONTENT_SETTING_ALLOW); |
| EXPECT_TRUE(settings->IsThirdPartyAccessAllowed(origin, nullptr)); |
| |
| AddAndReloadTabs(1); |
| CheckReloadBreakageMetrics(ukm_recorder, 4, 3, false, false); |
| |
| // Reload multiple tabs, all reloads are counted. |
| AddAndReloadTabs(3); |
| CheckReloadBreakageMetrics(ukm_recorder, 7, 4, false, false); |
| CheckReloadBreakageMetrics(ukm_recorder, 7, 5, false, false); |
| CheckReloadBreakageMetrics(ukm_recorder, 7, 6, false, false); |
| |
| // Load a page with an iframe and try to set a cross-site cookie inside of |
| // that iframe. |
| constexpr char host_a[] = "a.test"; |
| constexpr char host_b[] = "b.test"; |
| GURL main_url(https_server_.GetURL(host_a, "/iframe.html")); |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); |
| GURL page = https_server_.GetURL( |
| host_b, "/set-cookie?thirdparty=1;SameSite=None;Secure"); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", page)); |
| |
| // Reload the page with the cross-site iframe. |
| EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_RELOAD)); |
| |
| // We should now observe a 3P cookie *actually* blocked. |
| CheckReloadBreakageMetrics(ukm_recorder, 8, 7, true, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, MoveTabsToNewWindow) { |
| // Single Tab Move to New Window. |
| // 1 (Current) + 1 (Added) = 2 |
| AddTabs(1); |
| std::vector<int> indices = {0}; |
| // 2 (Current) - 1 (Moved) = 1 |
| auto browser_created_observer = |
| std::make_optional<ui_test_utils::BrowserCreatedObserver>(); |
| chrome::MoveTabsToNewWindow(browser(), indices); |
| const BrowserWindowInterface* const second_browser = |
| browser_created_observer->Wait(); |
| ASSERT_TRUE(browser()->GetTabStripModel()->count() == 1); |
| |
| // Multi-Tab Move to New Window. |
| // 1 (Current) + 3 (Added) = 4 |
| AddTabs(3); |
| indices = {0, 1}; |
| // 4 (Current) - 2 (Moved) = 2 |
| browser_created_observer.emplace(); |
| chrome::MoveTabsToNewWindow(browser(), indices); |
| const BrowserWindowInterface* const third_browser = |
| browser_created_observer->Wait(); |
| ASSERT_EQ(2, browser()->GetTabStripModel()->count()); |
| |
| // Check that the two additional windows have been created. |
| BrowserList* active_browser_list = BrowserList::GetInstance(); |
| EXPECT_EQ(3u, active_browser_list->size()); |
| |
| // Check that the tabs made it to other windows. |
| EXPECT_EQ(1, second_browser->GetTabStripModel()->count()); |
| EXPECT_EQ(2, third_browser->GetTabStripModel()->count()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, MoveTabsToNewWindow_WithGroup) { |
| // Three tabs with first two in a group |
| AddTabs(2); |
| std::vector<int> indices = {1, 2}; |
| tab_groups::TabGroupId group_id = |
| browser()->tab_strip_model()->AddToNewGroup(indices); |
| browser()->tab_strip_model()->ChangeTabGroupVisuals( |
| group_id, tab_groups::TabGroupVisualData( |
| u"Test Group", tab_groups::TabGroupColorId::kGrey)); |
| |
| // Move both tabs in the group to a new window. |
| auto browser_created_observer = |
| std::make_optional<ui_test_utils::BrowserCreatedObserver>(); |
| chrome::MoveTabsToNewWindow(browser(), indices); |
| const BrowserWindowInterface* const second_browser = |
| browser_created_observer->Wait(); |
| |
| // Original browser has one tab and no group. |
| ASSERT_EQ(1, browser()->GetTabStripModel()->count()); |
| EXPECT_FALSE( |
| browser()->GetTabStripModel()->group_model()->ContainsTabGroup(group_id)); |
| |
| // New browser has two tabs with the tab group. |
| ASSERT_EQ(2, second_browser->GetTabStripModel()->count()); |
| CheckBrowserContainsTabGroupWithSize(second_browser, group_id, 2u); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, MoveTabsToNewWindow_WithSplitView) { |
| // Three tabs with last two in a group |
| AddTabs(2); |
| browser()->tab_strip_model()->ActivateTabAt(2); |
| const split_tabs::SplitTabId split_id = |
| browser()->tab_strip_model()->AddToNewSplit( |
| {1}, |
| split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kVertical, |
| 1.0f), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| // Move both tabs in the split to a new window. |
| auto browser_created_observer = |
| std::make_optional<ui_test_utils::BrowserCreatedObserver>(); |
| chrome::MoveTabsToNewWindow(browser(), {1, 2}); |
| const BrowserWindowInterface* const second_browser = |
| browser_created_observer->Wait(); |
| |
| // Original browser has one tab and no split view. |
| ASSERT_EQ(1, browser()->GetTabStripModel()->count()); |
| EXPECT_FALSE(browser()->GetTabStripModel()->ContainsSplit(split_id)); |
| |
| // New browser has two tabs with the split view. |
| ASSERT_EQ(2, second_browser->GetTabStripModel()->count()); |
| EXPECT_TRUE(second_browser->GetTabStripModel()->ContainsSplit(split_id)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, |
| MoveTabsToNewWindow_WithTabsAndCollections) { |
| // Seven tabs with the following structure: 0 1gs 2gs 3g 4 5 6 |
| AddTabs(6); |
| browser()->tab_strip_model()->ActivateTabAt(1); |
| const split_tabs::SplitTabId split_id = |
| browser()->tab_strip_model()->AddToNewSplit( |
| {2}, |
| split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kVertical, |
| 1.0f), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| tab_groups::TabGroupId group_id = |
| browser()->tab_strip_model()->AddToNewGroup({1, 2, 3}); |
| |
| // Move tabs 1 through 4 to a new window. |
| auto browser_created_observer = |
| std::make_optional<ui_test_utils::BrowserCreatedObserver>(); |
| chrome::MoveTabsToNewWindow(browser(), {1, 2, 3, 4}); |
| const BrowserWindowInterface* const second_browser = |
| browser_created_observer->Wait(); |
| |
| // Original browser has three tabs and no split view or group. |
| ASSERT_EQ(3, browser()->GetTabStripModel()->count()); |
| EXPECT_FALSE(browser()->GetTabStripModel()->ContainsSplit(split_id)); |
| EXPECT_FALSE( |
| browser()->GetTabStripModel()->group_model()->ContainsTabGroup(group_id)); |
| |
| // New browser has 4 tabs with a split and group. |
| ASSERT_EQ(4, second_browser->GetTabStripModel()->count()); |
| EXPECT_TRUE(second_browser->GetTabStripModel()->ContainsSplit(split_id)); |
| CheckBrowserContainsTabGroupWithSize(second_browser, group_id, 3u); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, MoveGroupToNewWindow) { |
| AddTabs(2); |
| std::vector<int> indices = {1, 2}; |
| tab_groups::TabGroupId group_id = |
| browser()->tab_strip_model()->AddToNewGroup(indices); |
| browser()->tab_strip_model()->ChangeTabGroupVisuals( |
| group_id, tab_groups::TabGroupVisualData( |
| u"Test Group", tab_groups::TabGroupColorId::kGrey)); |
| |
| auto browser_created_observer = |
| std::make_optional<ui_test_utils::BrowserCreatedObserver>(); |
| chrome::MoveGroupToNewWindow(browser(), group_id); |
| ASSERT_EQ(1, browser()->tab_strip_model()->count()); |
| |
| Browser* active_browser = browser_created_observer->Wait(); |
| |
| CheckBrowserContainsTabGroupWithSize(active_browser, group_id, 2u); |
| EXPECT_EQ(tab_groups::TabGroupVisualData(u"Test Group", |
| tab_groups::TabGroupColorId::kGrey), |
| *active_browser->tab_strip_model() |
| ->group_model() |
| ->GetTabGroup(group_id) |
| ->visual_data()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, MoveGroupToExistingWindow) { |
| // Prepare the source browser with a few tabs and a tab group. |
| AddTabs(browser(), 3); |
| std::vector<int> indices = {1, 2}; |
| tab_groups::TabGroupId group_id = |
| browser()->tab_strip_model()->AddToNewGroup(indices); |
| browser()->tab_strip_model()->ChangeTabGroupVisuals( |
| group_id, |
| tab_groups::TabGroupVisualData(u"Test Group ExistingWindow", |
| tab_groups::TabGroupColorId::kBlue)); |
| |
| // Prepare the target browser (existing window). |
| Browser* target_browser = |
| Browser::Create(Browser::CreateParams(browser()->profile(), true)); |
| ASSERT_TRUE(target_browser); |
| AddTabs(target_browser, 1); |
| |
| // Perform the move to the existing window. |
| chrome::MoveGroupToExistingWindow(browser(), target_browser, group_id); |
| |
| // Verify the source window no longer contains the tab group. |
| EXPECT_FALSE( |
| browser()->tab_strip_model()->group_model()->ContainsTabGroup(group_id)); |
| EXPECT_EQ(2u, browser()->tab_strip_model()->count()); |
| |
| // Verify the target window received the tab group with correct properties. |
| CheckBrowserContainsTabGroupWithSize(target_browser, group_id, 2u); |
| EXPECT_EQ(tab_groups::TabGroupVisualData(u"Test Group ExistingWindow", |
| tab_groups::TabGroupColorId::kBlue), |
| *target_browser->tab_strip_model() |
| ->group_model() |
| ->GetTabGroup(group_id) |
| ->visual_data()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, MoveTabsToExistingWindow) { |
| // Create another window, and add tabs. |
| Browser* second_window = |
| ui_test_utils::OpenNewEmptyWindowAndWaitUntilActivated( |
| browser()->profile()); |
| AddTabs(browser(), 2); |
| AddTabs(second_window, 1); |
| ASSERT_EQ(3, browser()->tab_strip_model()->count()); |
| ASSERT_EQ(2, second_window->tab_strip_model()->count()); |
| |
| // Single tab move to an existing window. |
| chrome::MoveTabsToExistingWindow(browser(), second_window, {0}); |
| ASSERT_EQ(2, browser()->tab_strip_model()->count()); |
| ASSERT_EQ(3, second_window->tab_strip_model()->count()); |
| |
| // Multiple tab move to an existing window. |
| chrome::MoveTabsToExistingWindow(second_window, browser(), {0, 2}); |
| ASSERT_EQ(4, browser()->tab_strip_model()->count()); |
| ASSERT_EQ(1, second_window->tab_strip_model()->count()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, |
| MoveTabsToExistingWindow_ActiveTabMoved) { |
| // Source browser: 0(NTP),1,2(active),3 |
| AddTabs(browser(), 3); |
| browser()->tab_strip_model()->ActivateTabAt(2); |
| content::WebContents* src_active_tab = |
| browser()->tab_strip_model()->GetWebContentsAt(2); |
| ASSERT_EQ(4, browser()->tab_strip_model()->count()); |
| |
| // Target browser: 0(active) |
| Browser* target_browser = |
| Browser::Create(Browser::CreateParams(browser()->profile(), true)); |
| AddTabs(target_browser, 1); |
| ASSERT_EQ(1, target_browser->tab_strip_model()->count()); |
| |
| // Move tabs: includes active tab. |
| chrome::MoveTabsToExistingWindow(browser(), target_browser, {1, 2, 3}); |
| |
| // Verify tab counts after move. |
| EXPECT_EQ(1, browser()->tab_strip_model()->count()); |
| EXPECT_EQ(4, target_browser->tab_strip_model()->count()); |
| |
| // Verify active tab in target matches the original active tab. |
| // 0,1,2(active),3 |
| EXPECT_EQ(2, target_browser->tab_strip_model()->active_index()); |
| EXPECT_EQ(src_active_tab, |
| target_browser->tab_strip_model()->GetActiveWebContents()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, |
| MoveTabsToExistingWindow_ActiveTabNotMoved) { |
| // Source browser: 0(NTP,active),1,2,3 |
| AddTabs(browser(), 3); |
| browser()->tab_strip_model()->ActivateTabAt(0); |
| ASSERT_EQ(4, browser()->tab_strip_model()->count()); |
| |
| // Target browser: 0(active) |
| Browser* target_browser = |
| Browser::Create(Browser::CreateParams(browser()->profile(), true)); |
| AddTabs(target_browser, 1); |
| ASSERT_EQ(1, target_browser->tab_strip_model()->count()); |
| |
| // Move tabs: does NOT include active tab. |
| content::WebContents* first_moved_tab = |
| browser()->tab_strip_model()->GetWebContentsAt(1); |
| chrome::MoveTabsToExistingWindow(browser(), target_browser, {1, 2, 3}); |
| |
| // Verify tab counts after move. |
| EXPECT_EQ(1, browser()->tab_strip_model()->count()); |
| EXPECT_EQ(4, target_browser->tab_strip_model()->count()); |
| |
| // Fallback to the first moved tab. |
| // 0,1(active),2,3 |
| EXPECT_EQ(1, target_browser->tab_strip_model()->active_index()); |
| EXPECT_EQ(first_moved_tab, |
| target_browser->tab_strip_model()->GetActiveWebContents()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, |
| MoveTabsToExistingWindow_WithGroup) { |
| // Source browser: three tabs with first two in a group |
| AddTabs(2); |
| std::vector<int> indices = {1, 2}; |
| tab_groups::TabGroupId group_id = |
| browser()->tab_strip_model()->AddToNewGroup(indices); |
| browser()->tab_strip_model()->ChangeTabGroupVisuals( |
| group_id, tab_groups::TabGroupVisualData( |
| u"Test Group", tab_groups::TabGroupColorId::kGrey)); |
| |
| // Target browser: 0(active) |
| Browser* target_browser = |
| Browser::Create(Browser::CreateParams(browser()->profile(), true)); |
| AddTabs(target_browser, 1); |
| ASSERT_EQ(1, target_browser->tab_strip_model()->count()); |
| |
| // Move both tabs in the group to a new window. |
| chrome::MoveTabsToExistingWindow(browser(), target_browser, indices); |
| |
| // Original browser has one tab and no group. |
| ASSERT_EQ(1, browser()->GetTabStripModel()->count()); |
| EXPECT_FALSE( |
| browser()->GetTabStripModel()->group_model()->ContainsTabGroup(group_id)); |
| |
| // New browser has three tabs with the tab group. |
| ASSERT_EQ(3, target_browser->GetTabStripModel()->count()); |
| CheckBrowserContainsTabGroupWithSize(target_browser, group_id, 2u); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, |
| MoveTabsToExistingWindow_WithSplitView) { |
| // Source browser: three tabs with last two in a group |
| AddTabs(2); |
| browser()->tab_strip_model()->ActivateTabAt(2); |
| const split_tabs::SplitTabId split_id = |
| browser()->tab_strip_model()->AddToNewSplit( |
| {1}, |
| split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kVertical, |
| 1.0f), |
| split_tabs::SplitTabCreatedSource::kToolbarButton); |
| |
| // Target browser: 0(active) |
| Browser* target_browser = |
| Browser::Create(Browser::CreateParams(browser()->profile(), true)); |
| AddTabs(target_browser, 1); |
| ASSERT_EQ(1, target_browser->tab_strip_model()->count()); |
| |
| // Move both tabs in the split to a new window. |
| chrome::MoveTabsToExistingWindow(browser(), target_browser, {1, 2}); |
| |
| // Original browser has one tab and no split view. |
| ASSERT_EQ(1, browser()->GetTabStripModel()->count()); |
| EXPECT_FALSE(browser()->GetTabStripModel()->ContainsSplit(split_id)); |
| |
| // New browser has three tabs with the split view. |
| ASSERT_EQ(3, target_browser->GetTabStripModel()->count()); |
| EXPECT_TRUE(target_browser->GetTabStripModel()->ContainsSplit(split_id)); |
| } |
| |
| // Tests IDC_MOVE_TAB_TO_NEW_WINDOW. This is a browser test and not a unit test |
| // since it needs to create a new browser window, which doesn't work with a |
| // TestingProfile. |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, MoveActiveTabToNewWindow) { |
| GURL url1("chrome://version"); |
| GURL url2("chrome://about"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1)); |
| |
| // Should be disabled with 1 tab. |
| EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_MOVE_TAB_TO_NEW_WINDOW)); |
| ASSERT_TRUE(AddTabAtIndex(1, url2, ui::PAGE_TRANSITION_LINK)); |
| // Two tabs is enough for it to be meaningful to pop one out. |
| EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_MOVE_TAB_TO_NEW_WINDOW)); |
| |
| BrowserList* browser_list = BrowserList::GetInstance(); |
| // Pre-command, assert that we have one browser, with two tabs, with the |
| // url2 tab active. |
| EXPECT_EQ(browser_list->size(), 1u); |
| EXPECT_EQ(browser()->tab_strip_model()->count(), 2); |
| EXPECT_EQ(browser()->tab_strip_model()->GetActiveWebContents()->GetURL(), |
| url2); |
| |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| chrome::ExecuteCommand(browser(), IDC_MOVE_TAB_TO_NEW_WINDOW); |
| Browser* active_browser = browser_created_observer.Wait(); |
| ui_test_utils::WaitUntilBrowserBecomeActive(active_browser); |
| |
| // Now we should have: two browsers, each with one tab (url1 in browser(), |
| // and url2 in the new one). |
| EXPECT_EQ(browser_list->size(), 2u); |
| EXPECT_NE(active_browser, browser()); |
| EXPECT_EQ(browser()->tab_strip_model()->count(), 1); |
| EXPECT_EQ(active_browser->tab_strip_model()->count(), 1); |
| EXPECT_EQ(browser()->tab_strip_model()->GetActiveWebContents()->GetURL(), |
| url1); |
| EXPECT_EQ(active_browser->tab_strip_model()->GetActiveWebContents()->GetURL(), |
| url2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, |
| MoveActiveTabToNewWindowMultipleSelection) { |
| GURL url1("chrome://version"); |
| GURL url2("chrome://about"); |
| GURL url3("chrome://terms"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1)); |
| ASSERT_TRUE(AddTabAtIndex(1, url2, ui::PAGE_TRANSITION_LINK)); |
| ASSERT_TRUE(AddTabAtIndex(2, url3, ui::PAGE_TRANSITION_LINK)); |
| // Select the first tab. |
| browser()->tab_strip_model()->SelectTabAt(0); |
| // First and third (since it's active) should be selected |
| EXPECT_TRUE(browser()->tab_strip_model()->IsTabSelected(0)); |
| EXPECT_FALSE(browser()->tab_strip_model()->IsTabSelected(1)); |
| EXPECT_TRUE(browser()->tab_strip_model()->IsTabSelected(2)); |
| |
| ui_test_utils::BrowserCreatedObserver browser_created_observer; |
| chrome::ExecuteCommand(browser(), IDC_MOVE_TAB_TO_NEW_WINDOW); |
| Browser* active_browser = browser_created_observer.Wait(); |
| ui_test_utils::WaitUntilBrowserBecomeActive(active_browser); |
| |
| // Now we should have two browsers: |
| // The original, now with only a single tab: url2 |
| // The new one with the two tabs we moved: url1 and url3. This one should |
| // be active. |
| BrowserList* browser_list = BrowserList::GetInstance(); |
| EXPECT_EQ(browser_list->size(), 2u); |
| EXPECT_NE(active_browser, browser()); |
| ASSERT_EQ(browser()->tab_strip_model()->count(), 1); |
| ASSERT_EQ(active_browser->tab_strip_model()->count(), 2); |
| EXPECT_EQ(browser()->tab_strip_model()->GetActiveWebContents()->GetURL(), |
| url2); |
| EXPECT_EQ(active_browser->tab_strip_model()->GetWebContentsAt(0)->GetURL(), |
| url1); |
| EXPECT_EQ(active_browser->tab_strip_model()->GetWebContentsAt(1)->GetURL(), |
| url3); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, StartsOrganizationRequest) { |
| base::HistogramTester histogram_tester; |
| |
| chrome::ExecuteCommand(browser(), IDC_ORGANIZE_TABS); |
| |
| TabOrganizationService* service = |
| TabOrganizationServiceFactory::GetForProfile(browser()->profile()); |
| const TabOrganizationSession* session = |
| service->GetSessionForBrowser(browser()); |
| |
| EXPECT_EQ(TabOrganizationRequest::State::NOT_STARTED, |
| session->request()->state()); |
| |
| histogram_tester.ExpectUniqueSample("Tab.Organization.AllEntrypoints.Clicked", |
| true, 1); |
| histogram_tester.ExpectUniqueSample("Tab.Organization.ThreeDotMenu.Clicked", |
| 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 |
| // tab, in the case where the popup is the only active Browser object |
| Browser* popup_browser = Browser::Create( |
| Browser::CreateParams(Browser::TYPE_POPUP, browser()->profile(), true)); |
| chrome::AddTabAt(popup_browser, GURL(url::kAboutBlankURL), -1, true); |
| popup_browser->tab_strip_model()->SelectTabAt(0); |
| browser()->tab_strip_model()->CloseAllTabs(); |
| ConvertPopupToTabbedBrowser(popup_browser); |
| EXPECT_EQ(false, browser_shutdown::HasShutdownStarted()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, |
| OpenProductSpecifications_ShowNewTab) { |
| // Mock that the disclosure dialog has shown. |
| browser()->profile()->GetPrefs()->SetInteger( |
| commerce::kProductSpecificationsAcceptedDisclosureVersion, |
| static_cast<int>( |
| commerce::product_specifications::mojom::DisclosureVersion::kV1)); |
| |
| int tab_count = browser()->tab_strip_model()->count(); |
| chrome::OpenCommerceProductSpecificationsTab( |
| browser(), {GURL("foo.com"), GURL("bar.com")}, 0); |
| |
| auto* dialog = commerce::ProductSpecificationsDisclosureDialog:: |
| current_instance_for_testing(); |
| ASSERT_FALSE(dialog); |
| // No new tab is created since the dialog will block creating new product |
| // specifications tab. |
| ASSERT_EQ(tab_count + 1, browser()->tab_strip_model()->count()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, |
| OpenProductSpecifications_ShowDialog) { |
| int tab_count = browser()->tab_strip_model()->count(); |
| chrome::OpenCommerceProductSpecificationsTab( |
| browser(), {GURL("foo.com"), GURL("bar.com")}, 0); |
| |
| auto* dialog = commerce::ProductSpecificationsDisclosureDialog:: |
| current_instance_for_testing(); |
| ASSERT_TRUE(dialog); |
| // No new tab is created. |
| ASSERT_EQ(tab_count, browser()->tab_strip_model()->count()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, AddingToReadingListOpensToast) { |
| GURL main_url(https_server_.GetURL("a.test", "/iframe.html")); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); |
| chrome::ExecuteCommand(browser(), IDC_READING_LIST_MENU_ADD_TAB); |
| EXPECT_TRUE(browser()->GetFeatures().toast_controller()->IsShowingToast()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, |
| AddingToReadingListWithSidePanelShowsNoToast) { |
| GURL main_url(https_server_.GetURL("a.test", "/iframe.html")); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); |
| auto* side_panel_coordinator = |
| browser()->GetFeatures().side_panel_coordinator(); |
| side_panel_coordinator->Show(SidePanelEntryId::kReadingList); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return side_panel_coordinator->IsSidePanelShowing(); })); |
| chrome::ExecuteCommand(browser(), IDC_READING_LIST_MENU_ADD_TAB); |
| EXPECT_FALSE(browser()->GetFeatures().toast_controller()->IsShowingToast()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowserCommandsTest, CopyingUrlOpensToast) { |
| GURL main_url(https_server_.GetURL("a.test", "/iframe.html")); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); |
| chrome::ExecuteCommand(browser(), IDC_COPY_URL); |
| EXPECT_TRUE(browser()->GetFeatures().toast_controller()->IsShowingToast()); |
| } |
| |
| } // namespace chrome |