blob: abb8885e66f2458538dab98fa5bacef1eb65832a [file] [log] [blame]
// Copyright 2019 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/views/tabs/tab_strip.h"
#include <memory>
#include <string>
#include <vector>
#include "base/byte_count.h"
#include "base/strings/string_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_command_controller.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h"
#include "chrome/browser/ui/tabs/alert/tab_alert.h"
#include "chrome/browser/ui/tabs/features.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/tab_group_model.h"
#include "chrome/browser/ui/tabs/tab_renderer_data.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/data_sharing/public/features.h"
#include "components/saved_tab_groups/public/features.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tabs/public/tab_group.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/browser_test.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/text/bytes_formatting.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event_constants.h"
#include "ui/views/test/ax_event_counter.h"
#include "url/gurl.h"
namespace {
ui::MouseEvent GetDummyEvent() {
return ui::MouseEvent(ui::EventType::kMousePressed, gfx::PointF(),
gfx::PointF(), base::TimeTicks::Now(), 0, 0);
}
} // namespace
// Integration tests for interactions between TabStripModel and TabStrip.
class TabStripBrowsertest : public InProcessBrowserTest {
public:
TabStripBrowsertest() {
// The TabStrip is not used in Vertical Tabs. Ensure this suite is not run
// which would end up testing behavior that is not part of the browser.
feature_list_.InitAndDisableFeature(tabs::kVerticalTabs);
}
TabStripModel* tab_strip_model() { return browser()->tab_strip_model(); }
TabStrip* tab_strip() {
return views::AsViewClass<TabStripRegionView>(
browser()->GetBrowserView().tab_strip_view())
->tab_strip();
}
void AppendTab() { chrome::AddTabAt(browser(), GURL(), -1, true); }
tab_groups::TabGroupId AddTabToNewGroup(int tab_index) {
return tab_strip_model()->AddToNewGroup({tab_index});
}
void AddTabToExistingGroup(int tab_index, tab_groups::TabGroupId group) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
tab_strip_model()->AddToExistingGroup({tab_index}, group);
}
std::vector<content::WebContents*> GetWebContentses() {
std::vector<content::WebContents*> contentses;
for (int i = 0; i < tab_strip()->GetTabCount(); ++i) {
contentses.push_back(tab_strip_model()->GetWebContentsAt(i));
}
return contentses;
}
std::vector<content::WebContents*> GetWebContentsesInOrder(
const std::vector<int>& order) {
std::vector<content::WebContents*> contentses;
for (int i = 0; i < tab_strip()->GetTabCount(); ++i) {
contentses.push_back(tab_strip_model()->GetWebContentsAt(order[i]));
}
return contentses;
}
std::u16string GetCollapsedState(tab_groups::TabGroupId group) {
std::u16string collapsed_state = std::u16string();
#if !BUILDFLAG(IS_WIN)
collapsed_state =
tab_strip()->IsGroupCollapsed(group)
? l10n_util::GetStringUTF16(IDS_GROUP_AX_LABEL_COLLAPSED)
: l10n_util::GetStringUTF16(IDS_GROUP_AX_LABEL_EXPANDED);
#endif
return collapsed_state;
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Regression test for crbug.com/983961.
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabAndDeleteGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(0);
AddTabToNewGroup(2);
Tab* tab0 = tab_strip()->tab_at(0);
Tab* tab1 = tab_strip()->tab_at(1);
Tab* tab2 = tab_strip()->tab_at(2);
tab_strip_model()->AddToExistingGroup({2}, group);
EXPECT_EQ(tab0, tab_strip()->tab_at(0));
EXPECT_EQ(tab2, tab_strip()->tab_at(1));
EXPECT_EQ(tab1, tab_strip()->tab_at(2));
EXPECT_EQ(group, tab_strip_model()->GetTabGroupForTab(1));
std::vector<tab_groups::TabGroupId> groups =
tab_strip_model()->group_model()->ListTabGroups();
EXPECT_EQ(groups.size(), 1U);
EXPECT_EQ(groups[0], group);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, DetachAndReInsertGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = tab_strip_model()->AddToNewGroup({0, 1});
std::unique_ptr<DetachedTabCollection> detached_group =
tab_strip_model()->DetachTabGroupForInsertion(group);
EXPECT_EQ(tab_strip()->GetTabCount(), 1);
EXPECT_EQ(tab_strip()->tab_at(0)->group(), std::nullopt);
tab_strip_model()->InsertDetachedTabGroupAt(std::move(detached_group), 1);
EXPECT_EQ(tab_strip()->GetTabCount(), 3);
EXPECT_EQ(tab_strip()->tab_at(0)->group(), std::nullopt);
EXPECT_EQ(tab_strip()->tab_at(1)->group(), group);
EXPECT_EQ(tab_strip()->tab_at(2)->group(), group);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftTabPrevious_Success) {
AppendTab();
AppendTab();
const auto expected = GetWebContentsesInOrder({1, 0, 2});
tab_strip()->ShiftTabPrevious(tab_strip()->tab_at(1));
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftTabPrevious_AddsToGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
// Instead of moving, the tab should be added to the group.
const auto expected = GetWebContentsesInOrder({0, 1, 2});
tab_strip()->ShiftTabPrevious(tab_strip()->tab_at(2));
EXPECT_EQ(expected, GetWebContentses());
EXPECT_EQ(tab_strip()->tab_at(2)->group().value(), group);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftTabPrevious_PastCollapsedGroup_Success) {
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(0);
AddTabToExistingGroup(1, group);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group));
const auto expected = GetWebContentsesInOrder({2, 0, 1});
tab_strip()->ShiftTabPrevious(tab_strip()->tab_at(2));
EXPECT_EQ(expected, GetWebContentses());
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group));
EXPECT_EQ(tab_strip()->tab_at(0)->group(), std::nullopt);
EXPECT_EQ(tab_strip()->tab_at(1)->group().value(), group);
EXPECT_EQ(tab_strip()->tab_at(2)->group().value(), group);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftTabPrevious_BetweenTwoCollapsedGroups_Success) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AppendTab();
AppendTab();
tab_groups::TabGroupId group1 = AddTabToNewGroup(0);
AddTabToExistingGroup(1, group1);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group1));
tab_strip()->ToggleTabGroupCollapsedState(group1);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group1));
tab_groups::TabGroupId group2 = AddTabToNewGroup(2);
AddTabToExistingGroup(3, group2);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group2));
tab_strip()->ToggleTabGroupCollapsedState(group2);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group2));
const auto expected = GetWebContentsesInOrder({0, 1, 4, 2, 3});
tab_strip()->ShiftTabPrevious(tab_strip()->tab_at(4));
EXPECT_EQ(expected, GetWebContentses());
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group1));
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group2));
EXPECT_EQ(tab_strip()->tab_at(0)->group().value(), group1);
EXPECT_EQ(tab_strip()->tab_at(1)->group().value(), group1);
EXPECT_EQ(tab_strip()->tab_at(2)->group(), std::nullopt);
EXPECT_EQ(tab_strip()->tab_at(3)->group().value(), group2);
EXPECT_EQ(tab_strip()->tab_at(4)->group().value(), group2);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftTabPrevious_RemovesFromGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AddTabToNewGroup(1);
// Instead of moving, the tab should be removed from the group.
const auto expected = GetWebContentsesInOrder({0, 1, 2});
tab_strip()->ShiftTabPrevious(tab_strip()->tab_at(1));
EXPECT_EQ(expected, GetWebContentses());
EXPECT_EQ(tab_strip()->tab_at(1)->group(), std::nullopt);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftTabPrevious_ShiftsBetweenGroups) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(0);
AddTabToNewGroup(1);
// Instead of moving, the tab should be removed from its old group, then added
// to the new group.
const auto expected = GetWebContentsesInOrder({0, 1, 2});
tab_strip()->ShiftTabPrevious(tab_strip()->tab_at(1));
EXPECT_EQ(expected, GetWebContentses());
EXPECT_EQ(tab_strip()->tab_at(1)->group(), std::nullopt);
tab_strip()->ShiftTabPrevious(tab_strip()->tab_at(1));
EXPECT_EQ(expected, GetWebContentses());
EXPECT_EQ(tab_strip()->tab_at(1)->group().value(), group);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftTabPrevious_Failure_EdgeOfTabstrip) {
AppendTab();
AppendTab();
const auto contentses = GetWebContentses();
tab_strip()->ShiftTabPrevious(tab_strip()->tab_at(0));
// No change expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftTabPrevious_Failure_Pinned) {
AppendTab();
AppendTab();
tab_strip_model()->SetTabPinned(0, true);
const auto contentses = GetWebContentses();
tab_strip()->ShiftTabPrevious(tab_strip()->tab_at(1));
// No change expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftTabNext_Success) {
AppendTab();
AppendTab();
const auto expected = GetWebContentsesInOrder({1, 0, 2});
tab_strip()->ShiftTabNext(tab_strip()->tab_at(0));
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftTabNext_AddsToGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
// Instead of moving, the tab should be added to the group.
const auto expected = GetWebContentsesInOrder({0, 1, 2});
tab_strip()->ShiftTabNext(tab_strip()->tab_at(0));
EXPECT_EQ(expected, GetWebContentses());
EXPECT_EQ(tab_strip()->tab_at(0)->group().value(), group);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftTabNext_PastCollapsedGroup_Success) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
AddTabToExistingGroup(2, group);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group));
const auto expected = GetWebContentsesInOrder({1, 2, 0});
tab_strip()->ShiftTabNext(tab_strip()->tab_at(0));
EXPECT_EQ(expected, GetWebContentses());
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group));
EXPECT_EQ(tab_strip()->tab_at(0)->group().value(), group);
EXPECT_EQ(tab_strip()->tab_at(1)->group().value(), group);
EXPECT_EQ(tab_strip()->tab_at(2)->group(), std::nullopt);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftTabNext_BetweenTwoCollapsedGroups_Success) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AppendTab();
AppendTab();
tab_groups::TabGroupId group1 = AddTabToNewGroup(1);
AddTabToExistingGroup(2, group1);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group1));
tab_strip()->ToggleTabGroupCollapsedState(group1);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group1));
tab_groups::TabGroupId group2 = AddTabToNewGroup(3);
AddTabToExistingGroup(4, group2);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group2));
tab_strip()->ToggleTabGroupCollapsedState(group2);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group2));
const auto expected = GetWebContentsesInOrder({1, 2, 0, 3, 4});
tab_strip()->ShiftTabNext(tab_strip()->tab_at(0));
EXPECT_EQ(expected, GetWebContentses());
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group1));
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group2));
EXPECT_EQ(tab_strip()->tab_at(0)->group().value(), group1);
EXPECT_EQ(tab_strip()->tab_at(1)->group().value(), group1);
EXPECT_EQ(tab_strip()->tab_at(2)->group(), std::nullopt);
EXPECT_EQ(tab_strip()->tab_at(3)->group().value(), group2);
EXPECT_EQ(tab_strip()->tab_at(4)->group().value(), group2);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftTabNext_RemovesFromGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AddTabToNewGroup(1);
// Instead of moving, the tab should be removed from the group.
const auto expected = GetWebContentsesInOrder({0, 1, 2});
tab_strip()->ShiftTabNext(tab_strip()->tab_at(1));
EXPECT_EQ(expected, GetWebContentses());
EXPECT_EQ(tab_strip()->tab_at(1)->group(), std::nullopt);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftTabNext_ShiftsBetweenGroups) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AddTabToNewGroup(0);
tab_groups::TabGroupId group = AddTabToNewGroup(1);
// Instead of moving, the tab should be removed from its old group, then added
// to the new group.
const auto expected = GetWebContentsesInOrder({0, 1, 2});
tab_strip()->ShiftTabNext(tab_strip()->tab_at(0));
EXPECT_EQ(expected, GetWebContentses());
EXPECT_EQ(tab_strip()->tab_at(0)->group(), std::nullopt);
tab_strip()->ShiftTabNext(tab_strip()->tab_at(0));
EXPECT_EQ(expected, GetWebContentses());
EXPECT_EQ(tab_strip()->tab_at(0)->group().value(), group);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftTabNext_Failure_EdgeOfTabstrip) {
AppendTab();
AppendTab();
const auto contentses = GetWebContentses();
tab_strip()->ShiftTabNext(tab_strip()->tab_at(2));
// No change expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftTabNext_Failure_Pinned) {
AppendTab();
AppendTab();
tab_strip_model()->SetTabPinned(0, true);
const auto contentses = GetWebContentses();
tab_strip()->ShiftTabNext(tab_strip()->tab_at(0));
// No change expected.
EXPECT_EQ(contentses, GetWebContentses());
}
// Regression test for crbug.com/394381780. When active tab is the tab right
// after the collapsed group and a new foreground tab is added to the end of the
// group, the group should expand.
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
AddForegroundTabToCollapsedGroupExpandsGroup) {
AppendTab();
AppendTab();
ASSERT_EQ(3, tab_strip_model()->count());
tab_groups::TabGroupId group = AddTabToNewGroup(1);
tab_strip_model()->ActivateTabAt(2);
tab_strip()->ToggleTabGroupCollapsedState(group);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group));
// Add a tab to the group.
chrome::AddTabAt(browser(), GURL(), 2, true, group);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabFirst_NoPinnedTabs_Success) {
AppendTab();
AppendTab();
AppendTab();
const auto expected = GetWebContentsesInOrder({2, 0, 1, 3});
tab_strip()->MoveTabFirst(tab_strip()->tab_at(2));
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabFirst_PinnedTabs_Success) {
AppendTab();
AppendTab();
AppendTab();
tab_strip_model()->SetTabPinned(0, true);
const auto expected = GetWebContentsesInOrder({0, 2, 1, 3});
tab_strip()->MoveTabFirst(tab_strip()->tab_at(2));
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabFirst_DoesNotAddToGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AddTabToNewGroup(0);
tab_strip()->MoveTabFirst(tab_strip()->tab_at(1));
EXPECT_EQ(tab_strip()->tab_at(0)->group(), std::nullopt);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabFirst_RemovesFromGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AddTabToNewGroup(0);
AddTabToNewGroup(1);
tab_strip()->MoveTabFirst(tab_strip()->tab_at(0));
EXPECT_EQ(tab_strip()->tab_at(0)->group(), std::nullopt);
tab_strip()->MoveTabFirst(tab_strip()->tab_at(1));
EXPECT_EQ(tab_strip()->tab_at(0)->group(), std::nullopt);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabFirst_NoPinnedTabs_Failure) {
AppendTab();
AppendTab();
AppendTab();
const auto contentses = GetWebContentses();
tab_strip()->MoveTabFirst(tab_strip()->tab_at(0));
// No changes expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabFirst_PinnedTabs_Failure) {
AppendTab();
AppendTab();
AppendTab();
tab_strip_model()->SetTabPinned(0, true);
const auto contentses = GetWebContentses();
tab_strip()->MoveTabFirst(tab_strip()->tab_at(1));
// No changes expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
MoveTabFirst_MovePinnedTab_Success) {
AppendTab();
AppendTab();
AppendTab();
tab_strip_model()->SetTabPinned(0, true);
tab_strip_model()->SetTabPinned(1, true);
tab_strip_model()->SetTabPinned(2, true);
const auto expected = GetWebContentsesInOrder({2, 0, 1, 3});
tab_strip()->MoveTabFirst(tab_strip()->tab_at(2));
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabLast_NoPinnedTabs_Success) {
AppendTab();
AppendTab();
const auto expected = GetWebContentsesInOrder({1, 2, 0});
tab_strip()->MoveTabLast(tab_strip()->tab_at(0));
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabLast_MovePinnedTab_Success) {
AppendTab();
AppendTab();
AppendTab();
tab_strip_model()->SetTabPinned(0, true);
tab_strip_model()->SetTabPinned(1, true);
tab_strip_model()->SetTabPinned(2, true);
const auto expected = GetWebContentsesInOrder({0, 2, 1, 3});
tab_strip()->MoveTabLast(tab_strip()->tab_at(1));
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabLast_AllPinnedTabs_Success) {
AppendTab();
AppendTab();
tab_strip_model()->SetTabPinned(0, true);
tab_strip_model()->SetTabPinned(1, true);
tab_strip_model()->SetTabPinned(2, true);
const auto expected = GetWebContentsesInOrder({0, 2, 1});
tab_strip()->MoveTabLast(tab_strip()->tab_at(1));
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabLast_DoesNotAddToGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AddTabToNewGroup(2);
tab_strip()->MoveTabLast(tab_strip()->tab_at(1));
EXPECT_EQ(tab_strip()->tab_at(2)->group(), std::nullopt);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabLast_RemovesFromGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AddTabToNewGroup(1);
AddTabToNewGroup(2);
tab_strip()->MoveTabLast(tab_strip()->tab_at(2));
EXPECT_EQ(tab_strip()->tab_at(2)->group(), std::nullopt);
tab_strip()->MoveTabLast(tab_strip()->tab_at(1));
EXPECT_EQ(tab_strip()->tab_at(2)->group(), std::nullopt);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabLast_NoPinnedTabs_Failure) {
AppendTab();
AppendTab();
const auto contentses = GetWebContentses();
tab_strip()->MoveTabLast(tab_strip()->tab_at(2));
// No changes expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabLast_PinnedTabs_Failure) {
AppendTab();
AppendTab();
tab_strip_model()->SetTabPinned(0, true);
tab_strip_model()->SetTabPinned(1, true);
const auto contentses = GetWebContentses();
tab_strip()->MoveTabLast(tab_strip()->tab_at(1));
// No changes expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MoveTabLast_AllPinnedTabs_Failure) {
AppendTab();
AppendTab();
tab_strip_model()->SetTabPinned(0, true);
tab_strip_model()->SetTabPinned(1, true);
tab_strip_model()->SetTabPinned(2, true);
const auto contentses = GetWebContentses();
tab_strip()->MoveTabLast(tab_strip()->tab_at(2));
// No changes expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftGroupLeft_Success) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
AddTabToExistingGroup(2, group);
const auto expected = GetWebContentsesInOrder({1, 2, 0});
tab_strip()->ShiftGroupLeft(group);
EXPECT_EQ(expected, GetWebContentses());
}
// TODO(crbug.com/353618704): Re-enable this test
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)
#define MAYBE_ShiftGroupLeft_OtherGroup DISABLED_ShiftGroupLeft_OtherGroup
#else
#define MAYBE_ShiftGroupLeft_OtherGroup ShiftGroupLeft_OtherGroup
#endif
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, MAYBE_ShiftGroupLeft_OtherGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AppendTab();
tab_groups::TabGroupId group1 = AddTabToNewGroup(2);
AddTabToExistingGroup(3, group1);
tab_groups::TabGroupId group2 = AddTabToNewGroup(0);
AddTabToExistingGroup(1, group2);
const auto expected = GetWebContentsesInOrder({2, 3, 0, 1});
tab_strip()->ShiftGroupLeft(group1);
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftGroupLeft_Failure_EdgeOfTabstrip) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(0);
AddTabToExistingGroup(1, group);
const auto contentses = GetWebContentses();
tab_strip()->ShiftGroupLeft(group);
// No change expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftGroupLeft_Failure_Pinned) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_strip_model()->SetTabPinned(0, true);
tab_groups::TabGroupId group = AddTabToNewGroup(1);
AddTabToExistingGroup(2, group);
const auto contentses = GetWebContentses();
tab_strip()->ShiftGroupLeft(group);
// No change expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftGroupRight_Success) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(0);
AddTabToExistingGroup(1, group);
const auto expected = GetWebContentsesInOrder({2, 0, 1});
tab_strip()->ShiftGroupRight(group);
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftGroupRight_OtherGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AppendTab();
tab_groups::TabGroupId group1 = AddTabToNewGroup(0);
AddTabToExistingGroup(1, group1);
tab_groups::TabGroupId group2 = AddTabToNewGroup(2);
AddTabToExistingGroup(3, group2);
const auto expected = GetWebContentsesInOrder({2, 3, 0, 1});
tab_strip()->ShiftGroupRight(group1);
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftGroupRight_Failure_EdgeOfTabstrip) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
AddTabToExistingGroup(2, group);
const auto contentses = GetWebContentses();
tab_strip()->ShiftGroupRight(group);
// No change expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftCollapsedGroupLeft_Success) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
AddTabToExistingGroup(2, group);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group));
const auto expected = GetWebContentsesInOrder({1, 2, 0});
tab_strip()->ShiftGroupLeft(group);
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftCollapsedGroupLeft_OtherCollapsedGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AppendTab();
AppendTab();
tab_groups::TabGroupId group1 = AddTabToNewGroup(2);
AddTabToExistingGroup(3, group1);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group1));
tab_strip()->ToggleTabGroupCollapsedState(group1);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group1));
tab_groups::TabGroupId group2 = AddTabToNewGroup(0);
AddTabToExistingGroup(1, group2);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group2));
tab_strip()->ToggleTabGroupCollapsedState(group2);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group2));
const auto expected = GetWebContentsesInOrder({2, 3, 0, 1, 4});
tab_strip()->ShiftGroupLeft(group1);
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftCollapsedGroupLeft_Failure_EdgeOfTabstrip) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(0);
AddTabToExistingGroup(1, group);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group));
const auto contentses = GetWebContentses();
tab_strip()->ShiftGroupLeft(group);
// No change expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftCollapsedGroupLeft_Failure_Pinned) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_strip_model()->SetTabPinned(0, true);
tab_groups::TabGroupId group = AddTabToNewGroup(1);
AddTabToExistingGroup(2, group);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group));
const auto contentses = GetWebContentses();
tab_strip()->ShiftGroupLeft(group);
// No change expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ShiftCollapsedGroupRight_Success) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(0);
AddTabToExistingGroup(1, group);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group));
const auto expected = GetWebContentsesInOrder({2, 0, 1});
tab_strip()->ShiftGroupRight(group);
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftCollapsedGroupRight_OtherCollapsedGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
AppendTab();
AppendTab();
tab_groups::TabGroupId group1 = AddTabToNewGroup(0);
AddTabToExistingGroup(1, group1);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group1));
tab_strip()->ToggleTabGroupCollapsedState(group1);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group1));
tab_groups::TabGroupId group2 = AddTabToNewGroup(2);
AddTabToExistingGroup(3, group2);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group2));
tab_strip()->ToggleTabGroupCollapsedState(group2);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group2));
const auto expected = GetWebContentsesInOrder({2, 3, 0, 1, 4});
tab_strip()->ShiftGroupRight(group1);
EXPECT_EQ(expected, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ShiftCollapsedGroupRight_Failure_EdgeOfTabstrip) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
AddTabToExistingGroup(2, group);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group));
const auto contentses = GetWebContentses();
tab_strip()->ShiftGroupRight(group);
// No change expected.
EXPECT_EQ(contentses, GetWebContentses());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
CollapseGroup_WithActiveTabInGroup_SelectsNext) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(0);
tab_strip()->SelectTab(tab_strip()->tab_at(0), GetDummyEvent());
ASSERT_EQ(0, tab_strip()->GetActiveIndex());
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group));
EXPECT_EQ(1, tab_strip()->GetActiveIndex());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
CollapseGroup_WhenAddingActiveTab_ExpandsGroup) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(0);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group));
EXPECT_EQ(1, tab_strip()->GetActiveIndex());
tab_strip_model()->AddToExistingGroup({1}, group);
EXPECT_FALSE(tab_strip()->IsGroupCollapsed(group));
EXPECT_EQ(1, tab_strip()->GetActiveIndex());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
CollapseGroup_WhenAddingInactiveTab_StaysCollapsed) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(0);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group));
EXPECT_EQ(2, tab_strip()->GetActiveIndex());
tab_strip_model()->AddToExistingGroup({1}, group);
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group));
EXPECT_EQ(2, tab_strip()->GetActiveIndex());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
CollapseGroup_WithActiveTabInGroup_SelectsPrevious) {
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
tab_strip()->SelectTab(tab_strip()->tab_at(1), GetDummyEvent());
ASSERT_EQ(1, tab_strip()->GetActiveIndex());
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group));
EXPECT_EQ(0, tab_strip()->GetActiveIndex());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, AccessibleName) {
AppendTab();
AppendTab();
ui::AXNodeData data;
tab_strip()->tab_at(1)->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(u"New Tab",
data.GetString16Attribute(ax::mojom::StringAttribute::kName));
// AccessibleName should update when tab group is changed
tab_groups::TabGroupId group = AddTabToNewGroup(1);
std::u16string tab_title = browser()->GetTitleForTab(1);
std::u16string group_title = tab_strip()->GetGroupTitle(group);
std::u16string title =
group_title.empty()
? l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_UNNAMED_GROUP_FORMAT,
tab_title)
: l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_UNNAMED_GROUP_FORMAT,
tab_title, group_title);
data = ui::AXNodeData();
tab_strip()->tab_at(1)->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(title,
data.GetString16Attribute(ax::mojom::StringAttribute::kName));
// AccessibleName should update with crashedstatus
TabRendererData tab_renderer_data = tab_strip()->tab_at(1)->data();
tab_renderer_data.crashed_status =
base::TERMINATION_STATUS_PROCESS_WAS_KILLED;
tab_strip()->tab_at(1)->SetData(tab_renderer_data);
data = ui::AXNodeData();
tab_strip()->tab_at(1)->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_CRASHED_FORMAT, title),
data.GetString16Attribute(ax::mojom::StringAttribute::kName));
// AccessibleName update with pinned status and network status change
int new_index = tab_strip_model()->SetTabPinned(1, true);
title = l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_PINNED_FORMAT, tab_title);
tab_renderer_data = tab_strip()->tab_at(new_index)->data();
tab_renderer_data.network_state = TabNetworkState::kError;
tab_strip()->tab_at(new_index)->SetData(tab_renderer_data);
data = ui::AXNodeData();
tab_strip()->tab_at(new_index)->GetViewAccessibility().GetAccessibleNodeData(
&data);
EXPECT_EQ(
l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_NETWORK_ERROR_FORMAT, title),
data.GetString16Attribute(ax::mojom::StringAttribute::kName));
// AccessibleName update with alert on tab
tab_renderer_data = tab_strip()->tab_at(new_index)->data();
tab_renderer_data.network_state = TabNetworkState::kLoading;
tab_renderer_data.alert_state.push_back(tabs::TabAlert::kAudioPlaying);
tab_strip()->tab_at(new_index)->SetData(tab_renderer_data);
data = ui::AXNodeData();
tab_strip()->tab_at(new_index)->GetViewAccessibility().GetAccessibleNodeData(
&data);
EXPECT_EQ(
l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_AUDIO_PLAYING_FORMAT, title),
data.GetString16Attribute(ax::mojom::StringAttribute::kName));
// AccessibleName update with tab resource usage update
tab_renderer_data = tab_strip()->tab_at(new_index)->data();
auto tab_resource_usage = base::MakeRefCounted<TabResourceUsage>();
tab_resource_usage->SetMemoryUsage(base::ByteCount(100));
tab_renderer_data.tab_resource_usage = std::move(tab_resource_usage);
tab_strip()->tab_at(new_index)->SetData(tab_renderer_data);
data = ui::AXNodeData();
tab_strip()->tab_at(new_index)->GetViewAccessibility().GetAccessibleNodeData(
&data);
EXPECT_EQ(l10n_util::GetStringFUTF16(
IDS_TAB_AX_MEMORY_USAGE,
l10n_util::GetStringFUTF16(
IDS_TAB_AX_LABEL_AUDIO_PLAYING_FORMAT, title),
ui::FormatBytes(base::ByteCount(100))),
data.GetString16Attribute(ax::mojom::StringAttribute::kName));
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
DISABLED_TabGroupHeaderAccessibleProperties) {
browser()->set_update_ui_immediately_for_testing();
AppendTab();
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
tab_strip()->tab_at(1)->SetGroup(group);
tab_strip_model()->ChangeTabGroupVisuals(
group, tab_groups::TabGroupVisualData(
u"Test title", tab_groups::TabGroupColorId::kBlue));
auto* group_header = tab_strip()->group_header(group);
std::u16string group_title = tab_strip_model()
->group_model()
->GetTabGroup(group)
->visual_data()
->title();
EXPECT_FALSE(tab_strip()->IsGroupCollapsed(group));
std::u16string collapsed_state = GetCollapsedState(group);
EXPECT_FALSE(group_title.empty());
ui::AXNodeData data;
group_header->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringFUTF16(IDS_GROUP_AX_LABEL_NAMED_GROUP_FORMAT,
u"Test title", u"\"New Tab\"",
collapsed_state));
// Validating tab_group name update & collapsed state change should update the
// accessible name.
tab_strip_model()->ChangeTabGroupVisuals(
group,
tab_groups::TabGroupVisualData(u"", tab_groups::TabGroupColorId::kBlue));
group_title = tab_strip_model()
->group_model()
->GetTabGroup(group)
->visual_data()
->title();
EXPECT_TRUE(group_title.empty());
tab_strip()->ToggleTabGroupCollapsedState(group);
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group));
collapsed_state = GetCollapsedState(group);
data = ui::AXNodeData();
group_header->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringFUTF16(IDS_GROUP_AX_LABEL_UNNAMED_GROUP_FORMAT,
u"\"New Tab\"", collapsed_state));
tab_strip_model()->ChangeTabGroupVisuals(
group, tab_groups::TabGroupVisualData(
u"New test title", tab_groups::TabGroupColorId::kBlue));
collapsed_state = GetCollapsedState(group);
EXPECT_FALSE(tab_strip()->IsGroupCollapsed(group));
group_title = tab_strip_model()
->group_model()
->GetTabGroup(group)
->visual_data()
->title();
EXPECT_FALSE(group_title.empty());
data = ui::AXNodeData();
group_header->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringFUTF16(IDS_GROUP_AX_LABEL_NAMED_GROUP_FORMAT,
u"New test title", u"\"New Tab\"",
collapsed_state));
// Validating tab's title update in a tab_group should update accessible name.
AppendTab();
AppendTab();
AppendTab();
group = AddTabToNewGroup(3);
AddTabToExistingGroup(4, group);
AddTabToExistingGroup(5, group);
base::RunLoop run_loop;
run_loop.RunUntilIdle();
auto* tab_group = tab_strip_model()->group_model()->GetTabGroup(group);
gfx::Range tabs_in_group = tab_group->ListTabs();
auto* web_contents =
tab_strip_model()->GetWebContentsAt(tabs_in_group.start());
content::NavigationEntry* entry =
web_contents->GetController().GetVisibleEntry();
std::u16string new_title = u"New Tab Title For Test";
web_contents->UpdateTitleForEntry(entry, new_title);
run_loop.RunUntilIdle();
EXPECT_EQ(web_contents->GetTitle(), new_title);
collapsed_state = GetCollapsedState(group);
group_header = tab_strip()->group_header(group);
data = ui::AXNodeData();
group_header->GetViewAccessibility().GetAccessibleNodeData(&data);
std::u16string group_header_contents = base::ReplaceStringPlaceholders(
l10n_util::GetPluralStringFUTF16(IDS_TAB_CXMENU_PLACEHOLDER_GROUP_TITLE,
2),
std::vector<std::u16string>{u"New Tab Title For Test"}, nullptr);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringFUTF16(IDS_GROUP_AX_LABEL_UNNAMED_GROUP_FORMAT,
group_header_contents, collapsed_state));
// Other than first tab in a group, if any tab's title is updated, it should
// not update the accessible name.
web_contents = tab_strip_model()->GetWebContentsAt(tabs_in_group.start() + 1);
entry = web_contents->GetController().GetVisibleEntry();
new_title = u"New Tab Title For Test 2";
web_contents->UpdateTitleForEntry(entry, new_title);
run_loop.RunUntilIdle();
EXPECT_EQ(web_contents->GetTitle(), new_title);
data = ui::AXNodeData();
group_header->GetViewAccessibility().GetAccessibleNodeData(&data);
group_header_contents = base::ReplaceStringPlaceholders(
l10n_util::GetPluralStringFUTF16(IDS_TAB_CXMENU_PLACEHOLDER_GROUP_TITLE,
2),
std::vector<std::u16string>{u"New Tab Title For Test"}, nullptr);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringFUTF16(IDS_GROUP_AX_LABEL_UNNAMED_GROUP_FORMAT,
group_header_contents, collapsed_state));
// Validate accessible name update with tab move.
gfx::Range initial_tabs_in_group(3, 6);
EXPECT_EQ(initial_tabs_in_group, tab_group->ListTabs());
tab_strip()->MoveTabFirst(tab_strip()->tab_at(3));
gfx::Range updated_tabs_in_group(4, 6);
EXPECT_EQ(updated_tabs_in_group, tab_group->ListTabs());
EXPECT_EQ(new_title, tab_strip_model()->GetWebContentsAt(4)->GetTitle());
data = ui::AXNodeData();
group_header->GetViewAccessibility().GetAccessibleNodeData(&data);
group_header_contents = base::ReplaceStringPlaceholders(
l10n_util::GetPluralStringFUTF16(IDS_TAB_CXMENU_PLACEHOLDER_GROUP_TITLE,
1),
std::vector<std::u16string>{u"New Tab Title For Test 2"}, nullptr);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringFUTF16(IDS_GROUP_AX_LABEL_UNNAMED_GROUP_FORMAT,
group_header_contents, collapsed_state));
// Rearrange tabs in a group should update the accessible name.
initial_tabs_in_group = gfx::Range(4, 6);
EXPECT_EQ(initial_tabs_in_group, tab_group->ListTabs());
tabs_in_group = tab_group->ListTabs();
web_contents = tab_strip_model()->GetWebContentsAt(tabs_in_group.start() + 1);
entry = web_contents->GetController().GetVisibleEntry();
new_title = u"Middle Tab Title";
web_contents->UpdateTitleForEntry(entry, new_title);
run_loop.RunUntilIdle();
EXPECT_EQ(web_contents->GetTitle(), new_title);
// tab_strip_model()->MoveTabToIndexImpl(6, 4, group, false, false);
tab_strip()->SelectTab(tab_strip()->tab_at(5), GetDummyEvent());
tab_strip_model()->MoveTabPrevious();
updated_tabs_in_group = gfx::Range(4, 6);
EXPECT_EQ(updated_tabs_in_group, tab_group->ListTabs());
EXPECT_EQ(new_title, tab_strip_model()->GetWebContentsAt(4)->GetTitle());
data = ui::AXNodeData();
group_header->GetViewAccessibility().GetAccessibleNodeData(&data);
group_header_contents = base::ReplaceStringPlaceholders(
l10n_util::GetPluralStringFUTF16(IDS_TAB_CXMENU_PLACEHOLDER_GROUP_TITLE,
1),
std::vector<std::u16string>{new_title}, nullptr);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringFUTF16(IDS_GROUP_AX_LABEL_UNNAMED_GROUP_FORMAT,
group_header_contents, collapsed_state));
// Moving out all tabs in a tab group to another group
tab_strip()->SelectTab(tab_strip()->tab_at(4), GetDummyEvent());
tab_strip()->ExtendSelectionTo(tab_strip()->tab_at(6));
tab_groups::TabGroupId new_group = tab_strip_model()->AddToNewGroup({0});
tab_strip_model()->MoveSelectedTabsTo(0, new_group);
collapsed_state = GetCollapsedState(new_group);
group_header = tab_strip()->group_header(new_group);
data = ui::AXNodeData();
group_header->GetViewAccessibility().GetAccessibleNodeData(&data);
group_header_contents = base::ReplaceStringPlaceholders(
l10n_util::GetPluralStringFUTF16(IDS_TAB_CXMENU_PLACEHOLDER_GROUP_TITLE,
3),
std::vector<std::u16string>{new_title}, nullptr);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringFUTF16(IDS_GROUP_AX_LABEL_UNNAMED_GROUP_FORMAT,
group_header_contents, collapsed_state));
}
IN_PROC_BROWSER_TEST_F(
TabStripBrowsertest,
CollapseGroup_WithActiveTabOutsideGroup_DoesNotChangeActiveTab) {
ASSERT_TRUE(tab_strip_model()->SupportsTabGroups());
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(0);
tab_strip()->SelectTab(tab_strip()->tab_at(1), GetDummyEvent());
ASSERT_EQ(1, tab_strip()->GetActiveIndex());
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group));
EXPECT_EQ(1, tab_strip()->GetActiveIndex());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, TabGroupHeaderTooltipText) {
browser()->set_update_ui_immediately_for_testing();
AppendTab();
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
tab_strip()->tab_at(1)->SetGroup(group);
tab_strip_model()->ChangeTabGroupVisuals(
group, tab_groups::TabGroupVisualData(
u"Non empty title text", tab_groups::TabGroupColorId::kBlue));
auto* group_header = tab_strip()->group_header(group);
std::u16string group_title = tab_strip_model()
->group_model()
->GetTabGroup(group)
->visual_data()
->title();
EXPECT_EQ(group_title, group_header->GetTitleTextForTesting());
EXPECT_EQ(
group_header->GetRenderedTooltipText(gfx::Point()),
l10n_util::GetStringFUTF16(
IDS_TAB_GROUPS_NAMED_GROUP_TOOLTIP,
std::u16string(group_header->GetTitleTextForTesting()),
tab_strip()->GetGroupContentString(group_header->group().value())));
tab_strip_model()->ChangeTabGroupVisuals(
group, tab_groups::TabGroupVisualData(
std::u16string(), tab_groups::TabGroupColorId::kBlue));
EXPECT_EQ(group_header->GetRenderedTooltipText(gfx::Point()),
l10n_util::GetStringFUTF16(IDS_TAB_GROUPS_UNNAMED_GROUP_TOOLTIP,
tab_strip()->GetGroupContentString(
group_header->group().value())));
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
TabGroupHeaderTooltipTextAccessibility) {
browser()->set_update_ui_immediately_for_testing();
AppendTab();
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
tab_strip()->tab_at(1)->SetGroup(group);
tab_strip_model()->ChangeTabGroupVisuals(
group, tab_groups::TabGroupVisualData(
u"Non empty title text", tab_groups::TabGroupColorId::kBlue));
auto* group_header = tab_strip()->group_header(group);
std::u16string group_title = tab_strip_model()
->group_model()
->GetTabGroup(group)
->visual_data()
->title();
EXPECT_EQ(group_title, group_header->GetTitleTextForTesting());
EXPECT_EQ(
group_header->GetRenderedTooltipText(gfx::Point()),
l10n_util::GetStringFUTF16(
IDS_TAB_GROUPS_NAMED_GROUP_TOOLTIP,
std::u16string(group_header->GetTitleTextForTesting()),
tab_strip()->GetGroupContentString(group_header->group().value())));
ui::AXNodeData data;
group_header->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_NE(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
group_header->GetRenderedTooltipText(gfx::Point()));
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kDescription),
group_header->GetRenderedTooltipText(gfx::Point()));
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, CollapseGroup_CreatesNewTab) {
ASSERT_EQ(1, tab_strip_model()->count());
AppendTab();
ASSERT_EQ(2, tab_strip_model()->count());
tab_groups::TabGroupId group = AddTabToNewGroup(0);
tab_strip_model()->AddToExistingGroup({1}, group);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
// Any origin other than kMenuAction will work here. At the time this was
// written, it was impossible to trigger this specific interaction (collapsing
// a group) from a context menu.
tab_strip()->ToggleTabGroupCollapsedState(
group, ToggleTabGroupCollapsedStateOrigin::kMouse);
ASSERT_EQ(3, tab_strip_model()->count());
EXPECT_TRUE(tab_strip()->IsGroupCollapsed(group));
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
ActivateTabInCollapsedGroup_ExpandsCollapsedGroup) {
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(0);
ASSERT_FALSE(tab_strip()->IsGroupCollapsed(group));
tab_strip()->ToggleTabGroupCollapsedState(group);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group));
ASSERT_EQ(1, tab_strip()->GetActiveIndex());
tab_strip()->SelectTab(tab_strip()->tab_at(0), GetDummyEvent());
EXPECT_FALSE(tab_strip()->IsGroupCollapsed(group));
}
// Tests IDC_SELECT_TAB_0, IDC_SELECT_NEXT_TAB, IDC_SELECT_PREVIOUS_TAB and
// IDC_SELECT_LAST_TAB. The tab navigation accelerators should ignore tabs in
// collapsed groups.
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, TabGroupTabNavigationAccelerators) {
ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
// Create five tabs.
for (int i = 0; i < 4; i++) {
AppendTab();
}
ASSERT_EQ(5, tab_strip_model()->count());
// Add the first, second, and last tab into their own collapsed groups.
tab_groups::TabGroupId group1 = tab_strip_model()->AddToNewGroup({0});
tab_groups::TabGroupId group2 = tab_strip_model()->AddToNewGroup({1});
tab_groups::TabGroupId group3 = tab_strip_model()->AddToNewGroup({4});
tab_strip()->ToggleTabGroupCollapsedState(group1);
tab_strip()->ToggleTabGroupCollapsedState(group2);
tab_strip()->ToggleTabGroupCollapsedState(group3);
// Select the fourth tab.
tab_strip_model()->ActivateTabAt(3);
CommandUpdater* updater = browser()->command_controller();
// Navigate to the first tab using an accelerator.
updater->ExecuteCommand(IDC_SELECT_TAB_0);
ASSERT_EQ(2, tab_strip_model()->active_index());
// Navigate back to the first tab using the previous accelerators.
updater->ExecuteCommand(IDC_SELECT_PREVIOUS_TAB);
ASSERT_EQ(3, tab_strip_model()->active_index());
// Navigate to the second tab using the next accelerators.
updater->ExecuteCommand(IDC_SELECT_NEXT_TAB);
ASSERT_EQ(2, tab_strip_model()->active_index());
// Navigate to the last tab using the select last accelerator.
updater->ExecuteCommand(IDC_SELECT_LAST_TAB);
ASSERT_EQ(3, tab_strip_model()->active_index());
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest,
TabHasCorrectAccessibleSelectedState) {
AppendTab();
AppendTab();
Tab* tab0 = tab_strip()->tab_at(0);
Tab* tab1 = tab_strip()->tab_at(1);
ui::AXNodeData ax_node_data_0;
ui::AXNodeData ax_node_data_1;
views::test::AXEventCounter counter(views::AXUpdateNotifier::Get());
tab_strip()->SelectTab(tab_strip()->tab_at(0), GetDummyEvent());
tab0->GetViewAccessibility().GetAccessibleNodeData(&ax_node_data_0);
tab1->GetViewAccessibility().GetAccessibleNodeData(&ax_node_data_1);
EXPECT_TRUE(tab0->IsSelected());
EXPECT_TRUE(
ax_node_data_0.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
EXPECT_FALSE(tab1->IsSelected());
EXPECT_FALSE(
ax_node_data_1.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
EXPECT_EQ(counter.GetCount(ax::mojom::Event::kSelection), 1);
tab_strip()->SelectTab(tab_strip()->tab_at(1), GetDummyEvent());
ax_node_data_0 = ui::AXNodeData();
ax_node_data_1 = ui::AXNodeData();
tab0->GetViewAccessibility().GetAccessibleNodeData(&ax_node_data_0);
tab1->GetViewAccessibility().GetAccessibleNodeData(&ax_node_data_1);
EXPECT_FALSE(tab0->IsSelected());
EXPECT_FALSE(
ax_node_data_0.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
EXPECT_TRUE(tab1->IsSelected());
EXPECT_TRUE(
ax_node_data_1.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
EXPECT_EQ(counter.GetCount(ax::mojom::Event::kSelection), 2);
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, TabGroupHeaderAccessibleState) {
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
auto* group_header = tab_strip()->group_header(group);
ui::AXNodeData data;
group_header->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_TRUE(data.HasState(ax::mojom::State::kEditable));
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ToggleTabSelection) {
AppendTab();
AppendTab();
tab_strip()->SelectTab(tab_strip()->tab_at(0), GetDummyEvent());
const ui::EventFlags modifier =
#if BUILDFLAG(IS_MAC)
ui::EF_COMMAND_DOWN;
#else
ui::EF_CONTROL_DOWN;
#endif
ui::MouseEvent click(ui::EventType::kMousePressed, gfx::Point(0, 0),
gfx::Point(0, 0), ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON | modifier,
ui::EF_LEFT_MOUSE_BUTTON);
tab_strip()->tab_at(1)->OnMousePressed(click);
EXPECT_TRUE(tab_strip()->IsTabSelected(tab_strip()->tab_at(0)));
EXPECT_TRUE(tab_strip()->IsTabSelected(tab_strip()->tab_at(1)));
}
IN_PROC_BROWSER_TEST_F(TabStripBrowsertest, ExtendTabSelection) {
AppendTab();
AppendTab();
AppendTab();
AppendTab();
tab_strip()->SelectTab(tab_strip()->tab_at(1), GetDummyEvent());
ui::MouseEvent click(ui::EventType::kMousePressed, gfx::Point(0, 0),
gfx::Point(0, 0), ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON | ui::EF_SHIFT_DOWN,
ui::EF_LEFT_MOUSE_BUTTON);
tab_strip()->tab_at(3)->OnMousePressed(click);
EXPECT_FALSE(tab_strip()->IsTabSelected(tab_strip()->tab_at(0)));
EXPECT_TRUE(tab_strip()->IsTabSelected(tab_strip()->tab_at(1)));
EXPECT_TRUE(tab_strip()->IsTabSelected(tab_strip()->tab_at(2)));
EXPECT_TRUE(tab_strip()->IsTabSelected(tab_strip()->tab_at(3)));
}
class TabStripSplitViewBrowsertest : public TabStripBrowsertest {
public:
TabStripSplitViewBrowsertest() {
scoped_feature_list_.InitWithFeatures({features::kSideBySide}, {});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(TabStripSplitViewBrowsertest, CreateSplitUKMLogged) {
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_ =
std::make_unique<ukm::TestAutoSetUkmRecorder>();
// Create two tabs with the first two being split.
GURL a_url = GURL("https://a.com");
GURL b_url = GURL("https://b.com");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), a_url));
AppendTab();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), b_url));
tab_strip_model()->ActivateTabAt(0);
tab_strip_model()->AddToNewSplit(
{1}, split_tabs::SplitTabVisualData(),
split_tabs::SplitTabCreatedSource::kTabContextMenu);
// Ensure UKM is recorded.
auto entries = ukm_recorder_->GetEntriesByName(
ukm::builders::SplitView_Created::kEntryName);
EXPECT_EQ(2u, entries.size());
ukm_recorder_->ExpectEntrySourceHasUrl(entries[0], a_url);
ukm_recorder_->ExpectEntrySourceHasUrl(entries[1], b_url);
EXPECT_EQ(
*ukm_recorder_->GetEntryMetric(
entries[0], ukm::builders::SplitView_Created::kSplitEventIdName),
*ukm_recorder_->GetEntryMetric(
entries[1], ukm::builders::SplitView_Created::kSplitEventIdName));
}
IN_PROC_BROWSER_TEST_F(TabStripSplitViewBrowsertest,
SwapTabIntoSplitUKMLogged) {
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_ =
std::make_unique<ukm::TestAutoSetUkmRecorder>();
// Create three tabs with the first two being split.
GURL a_url = GURL("https://a.com");
GURL b_url = GURL("https://b.com");
GURL c_url = GURL("https://c.com");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), a_url));
AppendTab();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), b_url));
AppendTab();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), c_url));
tab_strip_model()->ActivateTabAt(0);
tab_strip_model()->AddToNewSplit(
{1}, split_tabs::SplitTabVisualData(),
split_tabs::SplitTabCreatedSource::kTabContextMenu);
// Swap the first tab with the last.
tab_strip_model()->UpdateTabInSplit(tab_strip_model()->GetTabAtIndex(0), 2,
TabStripModel::SplitUpdateType::kSwap);
// Ensure UKM is recorded.
auto entries = ukm_recorder_->GetEntriesByName(
ukm::builders::SplitView_Updated::kEntryName);
EXPECT_EQ(2u, entries.size());
ukm_recorder_->ExpectEntrySourceHasUrl(entries[0], c_url);
ukm_recorder_->ExpectEntrySourceHasUrl(entries[1], b_url);
EXPECT_EQ(
*ukm_recorder_->GetEntryMetric(
entries[0], ukm::builders::SplitView_Updated::kSplitEventIdName),
*ukm_recorder_->GetEntryMetric(
entries[1], ukm::builders::SplitView_Updated::kSplitEventIdName));
}
IN_PROC_BROWSER_TEST_F(TabStripSplitViewBrowsertest,
NavigateSplitTabUKMLogged) {
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_ =
std::make_unique<ukm::TestAutoSetUkmRecorder>();
// Create three tabs with the first two being split.
GURL a_url = GURL("https://a.com");
GURL b_url = GURL("https://b.com");
GURL c_url = GURL("https://c.com");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), a_url));
AppendTab();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), b_url));
tab_strip_model()->ActivateTabAt(0);
tab_strip_model()->AddToNewSplit(
{1}, split_tabs::SplitTabVisualData(),
split_tabs::SplitTabCreatedSource::kTabContextMenu);
// Navigate the first tab to a new URL.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), c_url));
// Ensure UKM is recorded.
auto entries = ukm_recorder_->GetEntriesByName(
ukm::builders::SplitView_Updated::kEntryName);
EXPECT_EQ(2u, entries.size());
ukm_recorder_->ExpectEntrySourceHasUrl(entries[0], c_url);
ukm_recorder_->ExpectEntrySourceHasUrl(entries[1], b_url);
EXPECT_EQ(
*ukm_recorder_->GetEntryMetric(
entries[0], ukm::builders::SplitView_Updated::kSplitEventIdName),
*ukm_recorder_->GetEntryMetric(
entries[1], ukm::builders::SplitView_Updated::kSplitEventIdName));
}
class TabStripSaveBrowsertest : public TabStripBrowsertest {
public:
TabStripSaveBrowsertest() {
scoped_feature_list_.InitWithFeatures(
{data_sharing::features::kDataSharingFeature}, {});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(TabStripSaveBrowsertest, AttentionIndicatorIsShown) {
AppendTab();
AppendTab();
tab_groups::TabGroupId group = AddTabToNewGroup(1);
tab_strip()->ToggleTabGroupCollapsedState(group);
ASSERT_TRUE(tab_strip()->IsGroupCollapsed(group));
auto* group_header = tab_strip()->group_header(group);
group_header->SetTabGroupNeedsAttention(true);
EXPECT_TRUE(group_header->attention_indicator_->GetVisible());
group_header->SetTabGroupNeedsAttention(false);
EXPECT_FALSE(group_header->attention_indicator_->GetVisible());
}