1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/tabs/existing_window_sub_menu_model.h"
6 
7 #include "base/strings/string_util.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "build/build_config.h"
10 #include "chrome/browser/ui/browser_commands.h"
11 #include "chrome/browser/ui/browser_list.h"
12 #include "chrome/test/base/browser_with_test_window_test.h"
13 #include "chrome/test/base/test_browser_window.h"
14 #include "ui/gfx/text_elider.h"
15 
16 namespace {
17 
18 class ExistingWindowSubMenuModelTest : public BrowserWithTestWindowTest {
19  public:
ExistingWindowSubMenuModelTest()20   ExistingWindowSubMenuModelTest() : BrowserWithTestWindowTest() {}
21 
22  protected:
23   std::unique_ptr<Browser> CreateTestBrowser(bool incognito, bool popup);
24   void AddTabWithTitle(Browser* browser, std::string title);
25 
26   static void CheckBrowserTitle(const base::string16& title,
27                                 const std::string& expected_title,
28                                 const int expected_num_tabs);
29 };
30 
CreateTestBrowser(bool incognito,bool popup)31 std::unique_ptr<Browser> ExistingWindowSubMenuModelTest::CreateTestBrowser(
32     bool incognito,
33     bool popup) {
34   TestBrowserWindow* window = new TestBrowserWindow;
35   new TestBrowserWindowOwner(window);
36   Profile* profile = incognito ? browser()->profile()->GetPrimaryOTRProfile()
37                                : browser()->profile();
38   Browser::Type type = popup ? Browser::TYPE_POPUP : Browser::TYPE_NORMAL;
39 
40   std::unique_ptr<Browser> browser =
41       CreateBrowser(profile, type, false, window);
42   BrowserList::SetLastActive(browser.get());
43   return browser;
44 }
45 
AddTabWithTitle(Browser * browser,std::string title)46 void ExistingWindowSubMenuModelTest::AddTabWithTitle(Browser* browser,
47                                                      std::string title) {
48   AddTab(browser, GURL("about:blank"));
49   NavigateAndCommitActiveTabWithTitle(browser, GURL("about:blank"),
50                                       base::ASCIIToUTF16(title));
51 }
52 
53 // static
CheckBrowserTitle(const base::string16 & title,const std::string & expected_title,const int expected_num_tabs)54 void ExistingWindowSubMenuModelTest::CheckBrowserTitle(
55     const base::string16& title,
56     const std::string& expected_title,
57     const int expected_num_tabs) {
58   const base::string16 expected_title16 = base::ASCIIToUTF16(expected_title);
59 
60   // Check the suffix, which should always show if there are multiple tabs.
61   if (expected_num_tabs > 1) {
62     std::ostringstream oss;
63     oss << " and " << expected_num_tabs - 1;
64     oss << ((expected_num_tabs == 2) ? " other tab" : " other tabs");
65     const base::string16 expected_suffix16 = base::ASCIIToUTF16(oss.str());
66 
67     // Not case sensitive, since MacOS uses title case.
68     EXPECT_TRUE(base::EndsWith(title, expected_suffix16,
69                                base::CompareCase::INSENSITIVE_ASCII));
70   }
71 
72   // Either the title contains the whole tab title, or it was elided.
73   if (!base::StartsWith(title, expected_title16,
74                         base::CompareCase::SENSITIVE)) {
75     // Check that the title before being elided matches the tab title.
76     std::vector<base::string16> tokens =
77         SplitString(title, gfx::kEllipsisUTF16, base::KEEP_WHITESPACE,
78                     base::SPLIT_WANT_NONEMPTY);
79     EXPECT_TRUE(base::StartsWith(expected_title16, tokens[0],
80                                  base::CompareCase::SENSITIVE));
81 
82     // Title should always have at least a few characters.
83     EXPECT_GE(tokens[0].size(), 3ull);
84   }
85 }
86 
87 // Ensure that the move to existing window menu only appears when another window
88 // of the current profile exists.
TEST_F(ExistingWindowSubMenuModelTest,ShouldShowSubmenu)89 TEST_F(ExistingWindowSubMenuModelTest, ShouldShowSubmenu) {
90   // Shouldn't show menu for one window.
91   ASSERT_FALSE(ExistingWindowSubMenuModel::ShouldShowSubmenu(profile()));
92 
93   // Add another browser, and make sure we do show the menu now.
94   std::unique_ptr<Browser> browser_2(CreateTestBrowser(false, false));
95   ASSERT_TRUE(ExistingWindowSubMenuModel::ShouldShowSubmenu(profile()));
96 
97   // Close the window, so the menu does not show anymore.
98   BrowserList::RemoveBrowser(browser_2.get());
99   ASSERT_FALSE(ExistingWindowSubMenuModel::ShouldShowSubmenu(profile()));
100 }
101 
102 // Ensure we only show the menu on incognito when at least one other incognito
103 // window exists.
TEST_F(ExistingWindowSubMenuModelTest,ShouldShowSubmenuIncognito)104 TEST_F(ExistingWindowSubMenuModelTest, ShouldShowSubmenuIncognito) {
105   // Shouldn't show menu for one window.
106   ASSERT_FALSE(ExistingWindowSubMenuModel::ShouldShowSubmenu(profile()));
107   ASSERT_FALSE(ExistingWindowSubMenuModel::ShouldShowSubmenu(
108       profile()->GetPrimaryOTRProfile()));
109 
110   // Create an incognito browser. We shouldn't show the menu, because we only
111   // move tabs between windows of the same profile.
112   std::unique_ptr<Browser> incognito_browser_1(CreateTestBrowser(true, false));
113   ASSERT_FALSE(ExistingWindowSubMenuModel::ShouldShowSubmenu(profile()));
114   ASSERT_FALSE(ExistingWindowSubMenuModel::ShouldShowSubmenu(
115       profile()->GetPrimaryOTRProfile()));
116 
117   // Add another incognito browser, and make sure we do show the menu now.
118   std::unique_ptr<Browser> incognito_browser_2(CreateTestBrowser(true, false));
119   ASSERT_FALSE(ExistingWindowSubMenuModel::ShouldShowSubmenu(profile()));
120   ASSERT_TRUE(ExistingWindowSubMenuModel::ShouldShowSubmenu(
121       profile()->GetPrimaryOTRProfile()));
122 }
123 
124 // Ensure we don't show the menu on a popup window.
TEST_F(ExistingWindowSubMenuModelTest,ShouldShowSubmenuPopup)125 TEST_F(ExistingWindowSubMenuModelTest, ShouldShowSubmenuPopup) {
126   // Popup windows aren't counted when determining whether to show the menu.
127   std::unique_ptr<Browser> browser_2(CreateTestBrowser(false, true));
128   ASSERT_FALSE(ExistingWindowSubMenuModel::ShouldShowSubmenu(profile()));
129 
130   // Add another tabbed window, make sure the menu shows.
131   std::unique_ptr<Browser> browser_3(CreateTestBrowser(false, false));
132   ASSERT_TRUE(ExistingWindowSubMenuModel::ShouldShowSubmenu(profile()));
133 }
134 
135 // Validate that windows appear in MRU order and with the expected labels.
TEST_F(ExistingWindowSubMenuModelTest,BuildSubmenuOrder)136 TEST_F(ExistingWindowSubMenuModelTest, BuildSubmenuOrder) {
137   // Add some browsers.
138   BrowserList::SetLastActive(browser());
139   std::unique_ptr<Browser> browser_2(CreateTestBrowser(false, false));
140   std::unique_ptr<Browser> browser_3(CreateTestBrowser(false, false));
141   std::unique_ptr<Browser> browser_4(CreateTestBrowser(false, false));
142 
143   // Add tabs.
144   constexpr char kLongTabTitleExample[] =
145       "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas "
146       "porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies,"
147       "purus lectus malesuada libero, sit amet commodo magna eros quis urna.";
148 
149   AddTabWithTitle(browser(), "Browser 1");
150   AddTabWithTitle(browser_2.get(), kLongTabTitleExample);
151   AddTabWithTitle(browser_3.get(), "Browser 3 Tab 1");
152   AddTabWithTitle(browser_3.get(), "Browser 3 Tab 2");
153   AddTabWithTitle(browser_4.get(), "Browser 4 Tab 1");
154   AddTabWithTitle(browser_4.get(), "Browser 4 Tab 2");
155   AddTabWithTitle(browser_4.get(), kLongTabTitleExample);
156 
157   // Create menu from browser 1.
158   ExistingWindowSubMenuModel menu1(nullptr, browser()->tab_strip_model(), 0);
159   ASSERT_EQ(5, menu1.GetItemCount());
160   CheckBrowserTitle(menu1.GetLabelAt(2), kLongTabTitleExample, 3);
161   CheckBrowserTitle(menu1.GetLabelAt(3), "Browser 3 Tab 2", 2);
162   CheckBrowserTitle(menu1.GetLabelAt(4), kLongTabTitleExample, 1);
163 
164   // Create menu from browser 2.
165   ExistingWindowSubMenuModel menu2(nullptr, browser_2->tab_strip_model(), 0);
166   ASSERT_EQ(5, menu2.GetItemCount());
167   CheckBrowserTitle(menu2.GetLabelAt(2), kLongTabTitleExample, 3);
168   CheckBrowserTitle(menu2.GetLabelAt(3), "Browser 3 Tab 2", 2);
169   CheckBrowserTitle(menu2.GetLabelAt(4), "Browser 1", 1);
170 
171   // Rearrange the MRU and re-test.
172   BrowserList::SetLastActive(browser());
173   BrowserList::SetLastActive(browser_2.get());
174 
175   ExistingWindowSubMenuModel menu3(nullptr, browser_3->tab_strip_model(), 0);
176   ASSERT_EQ(5, menu3.GetItemCount());
177   CheckBrowserTitle(menu3.GetLabelAt(2), kLongTabTitleExample, 1);
178   CheckBrowserTitle(menu3.GetLabelAt(3), "Browser 1", 1);
179   CheckBrowserTitle(menu3.GetLabelAt(4), kLongTabTitleExample, 3);
180 
181   // Clean up.
182   chrome::CloseTab(browser_2.get());
183   chrome::CloseTab(browser_3.get());
184   chrome::CloseTab(browser_3.get());
185   chrome::CloseTab(browser_4.get());
186   chrome::CloseTab(browser_4.get());
187   chrome::CloseTab(browser_4.get());
188 }
189 
190 // Ensure that normal browsers and incognito browsers have their own lists.
TEST_F(ExistingWindowSubMenuModelTest,BuildSubmenuIncognito)191 TEST_F(ExistingWindowSubMenuModelTest, BuildSubmenuIncognito) {
192   // Add some browsers.
193   BrowserList::SetLastActive(browser());
194   std::unique_ptr<Browser> browser_2(CreateTestBrowser(false, false));
195   std::unique_ptr<Browser> browser_3(CreateTestBrowser(false, false));
196   std::unique_ptr<Browser> incognito_browser_1(CreateTestBrowser(true, false));
197   std::unique_ptr<Browser> incognito_browser_2(CreateTestBrowser(true, false));
198 
199   AddTabWithTitle(browser(), "Browser 1");
200   AddTabWithTitle(browser_2.get(), "Browser 2");
201   AddTabWithTitle(browser_3.get(), "Browser 3");
202   AddTabWithTitle(incognito_browser_1.get(), "Incognito Browser 1");
203   AddTabWithTitle(incognito_browser_2.get(), "Incognito Browser 2");
204 
205   const base::string16 kBrowser2ExpectedTitle = base::WideToUTF16(L"Browser 2");
206   const base::string16 kBrowser3ExpectedTitle = base::WideToUTF16(L"Browser 3");
207   const base::string16 kIncognitoBrowser2ExpectedTitle =
208       base::WideToUTF16(L"Incognito Browser 2");
209 
210   // Test that a non-incognito browser only shows non-incognito windows.
211   ExistingWindowSubMenuModel menu(nullptr, browser()->tab_strip_model(), 0);
212   ASSERT_EQ(4, menu.GetItemCount());
213   ASSERT_EQ(kBrowser3ExpectedTitle, menu.GetLabelAt(2));
214   ASSERT_EQ(kBrowser2ExpectedTitle, menu.GetLabelAt(3));
215 
216   // Test that a incognito browser only shows incognito windows.
217   ExistingWindowSubMenuModel menu_incognito(
218       nullptr, incognito_browser_1->tab_strip_model(), 0);
219   ASSERT_EQ(3, menu_incognito.GetItemCount());
220   ASSERT_EQ(kIncognitoBrowser2ExpectedTitle, menu_incognito.GetLabelAt(2));
221 
222   // Clean up.
223   chrome::CloseTab(browser_2.get());
224   chrome::CloseTab(browser_3.get());
225   chrome::CloseTab(incognito_browser_1.get());
226   chrome::CloseTab(incognito_browser_2.get());
227 }
228 
229 // Ensure that popups don't appear in the list of existing windows.
TEST_F(ExistingWindowSubMenuModelTest,BuildSubmenuPopups)230 TEST_F(ExistingWindowSubMenuModelTest, BuildSubmenuPopups) {
231   // Add some browsers.
232   BrowserList::SetLastActive(browser());
233   std::unique_ptr<Browser> browser_2(CreateTestBrowser(false, false));
234   std::unique_ptr<Browser> browser_3(CreateTestBrowser(false, false));
235   std::unique_ptr<Browser> popup_browser_1(CreateTestBrowser(false, true));
236   std::unique_ptr<Browser> popup_browser_2(CreateTestBrowser(false, true));
237 
238   AddTabWithTitle(browser(), "Browser 1");
239   AddTabWithTitle(browser_2.get(), "Browser 2");
240   AddTabWithTitle(browser_3.get(), "Browser 3");
241 
242   const base::string16 kBrowser2ExpectedTitle = base::WideToUTF16(L"Browser 2");
243   const base::string16 kBrowser3ExpectedTitle = base::WideToUTF16(L"Browser 3");
244 
245   // Test that popups do not show.
246   ExistingWindowSubMenuModel menu(nullptr, browser()->tab_strip_model(), 0);
247   ASSERT_EQ(4, menu.GetItemCount());
248   ASSERT_EQ(kBrowser3ExpectedTitle, menu.GetLabelAt(2));
249   ASSERT_EQ(kBrowser2ExpectedTitle, menu.GetLabelAt(3));
250 
251   // Clean up.
252   chrome::CloseTab(browser_2.get());
253   chrome::CloseTab(browser_3.get());
254   chrome::CloseTab(popup_browser_1.get());
255   chrome::CloseTab(popup_browser_2.get());
256 }
257 
258 }  // namespace
259