1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h"
7 #include "base/run_loop.h"
8 #include "chrome/app/chrome_command_ids.h"
9 #include "chrome/browser/sessions/session_service.h"
10 #include "chrome/browser/sessions/session_service_factory.h"
11 #include "chrome/browser/sessions/session_types.h"
12 #include "chrome/browser/sessions/persistent_tab_restore_service.h"
13 #include "chrome/browser/sessions/tab_restore_service_factory.h"
14 #include "chrome/browser/sync/glue/session_model_associator.h"
15 #include "chrome/browser/sync/glue/synced_session.h"
16 #include "chrome/browser/sync/profile_sync_service_mock.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_tabstrip.h"
19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
20 #include "chrome/browser/ui/toolbar/recent_tabs_builder_test_helper.h"
21 #include "chrome/test/base/browser_with_test_window_test.h"
22 #include "chrome/test/base/menu_model_test.h"
23 #include "chrome/test/base/testing_profile.h"
24 #include "components/sessions/serialized_navigation_entry_test_helper.h"
25 #include "grit/generated_resources.h"
26 #include "testing/gmock/include/gmock/gmock.h"
27 #include "testing/gtest/include/gtest/gtest.h"
31 // This copies parts of MenuModelTest::Delegate and combines them with the
32 // RecentTabsSubMenuModel since RecentTabsSubMenuModel is a
33 // SimpleMenuModel::Delegate and not just derived from SimpleMenuModel.
34 class TestRecentTabsSubMenuModel : public RecentTabsSubMenuModel {
36 TestRecentTabsSubMenuModel(ui::AcceleratorProvider* provider,
38 browser_sync::SessionModelAssociator* associator)
39 : RecentTabsSubMenuModel(provider, browser, associator),
44 // Testing overrides to ui::SimpleMenuModel::Delegate:
45 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
46 bool val = RecentTabsSubMenuModel::IsCommandIdEnabled(command_id);
52 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
56 int execute_count() const { return execute_count_; }
57 int enable_count() const { return enable_count_; }
61 int mutable enable_count_; // Mutable because IsCommandIdEnabledAt is const.
63 DISALLOW_COPY_AND_ASSIGN(TestRecentTabsSubMenuModel);
66 class TestRecentTabsMenuModelDelegate : public ui::MenuModelDelegate {
68 explicit TestRecentTabsMenuModelDelegate(ui::MenuModel* model)
71 model_->SetMenuModelDelegate(this);
74 virtual ~TestRecentTabsMenuModelDelegate() {
75 model_->SetMenuModelDelegate(NULL);
78 // ui::MenuModelDelegate implementation:
80 virtual void OnIconChanged(int index) OVERRIDE {
83 virtual void OnMenuStructureChanged() OVERRIDE {
87 bool got_changes() const { return got_changes_; }
90 ui::MenuModel* model_;
93 DISALLOW_COPY_AND_ASSIGN(TestRecentTabsMenuModelDelegate);
98 class RecentTabsSubMenuModelTest : public BrowserWithTestWindowTest {
100 RecentTabsSubMenuModelTest()
101 : sync_service_(&testing_profile_),
102 associator_(&sync_service_, true) {
103 associator_.SetCurrentMachineTagForTesting("RecentTabsSubMenuModelTest");
106 void WaitForLoadFromLastSession() {
107 content::BrowserThread::GetBlockingPool()->FlushForTesting();
108 base::RunLoop().RunUntilIdle();
109 content::BrowserThread::GetBlockingPool()->FlushForTesting();
112 static BrowserContextKeyedService* GetTabRestoreService(
113 content::BrowserContext* browser_context) {
114 // Ownership is tranfered to the profile.
115 return new PersistentTabRestoreService(
116 Profile::FromBrowserContext(browser_context), NULL);;
120 TestingProfile testing_profile_;
121 testing::NiceMock<ProfileSyncServiceMock> sync_service_;
124 browser_sync::SessionModelAssociator associator_;
127 // Test disabled "Recently closed" header with no foreign tabs.
128 TEST_F(RecentTabsSubMenuModelTest, NoTabs) {
129 TestRecentTabsSubMenuModel model(NULL, browser(), NULL);
132 // Menu index Menu items
133 // ---------------------------------------------
134 // 0 Recently closed header (disabled)
136 // 2 No tabs from other Devices
138 int num_items = model.GetItemCount();
139 EXPECT_EQ(3, num_items);
140 EXPECT_FALSE(model.IsEnabledAt(0));
141 EXPECT_FALSE(model.IsEnabledAt(2));
142 EXPECT_EQ(0, model.enable_count());
144 EXPECT_EQ(NULL, model.GetLabelFontAt(0));
145 EXPECT_EQ(NULL, model.GetLabelFontAt(1));
146 EXPECT_EQ(NULL, model.GetLabelFontAt(2));
150 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(0, &url, &title));
151 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(1, &url, &title));
152 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(2, &url, &title));
155 // Test enabled "Recently closed" header with no foreign tabs.
156 TEST_F(RecentTabsSubMenuModelTest, RecentlyClosedTabsFromCurrentSession) {
157 TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
158 profile(), RecentTabsSubMenuModelTest::GetTabRestoreService);
160 // Add 2 tabs and close them.
161 AddTab(browser(), GURL("http://foo/1"));
162 AddTab(browser(), GURL("http://foo/2"));
163 browser()->tab_strip_model()->CloseAllTabs();
165 TestRecentTabsSubMenuModel model(NULL, browser(), NULL);
167 // Menu index Menu items
168 // --------------------------------------
169 // 0 Recently closed header
170 // 1 <tab for http://foo/2>
171 // 2 <tab for http://foo/1>
173 // 4 No tabs from other Devices
174 int num_items = model.GetItemCount();
175 EXPECT_EQ(5, num_items);
176 EXPECT_FALSE(model.IsEnabledAt(0));
177 EXPECT_TRUE(model.IsEnabledAt(1));
178 EXPECT_TRUE(model.IsEnabledAt(2));
179 model.ActivatedAt(1);
180 model.ActivatedAt(2);
181 EXPECT_FALSE(model.IsEnabledAt(4));
182 EXPECT_EQ(2, model.enable_count());
183 EXPECT_EQ(2, model.execute_count());
185 EXPECT_TRUE(model.GetLabelFontAt(0) != NULL);
186 EXPECT_EQ(NULL, model.GetLabelFontAt(1));
187 EXPECT_EQ(NULL, model.GetLabelFontAt(2));
188 EXPECT_EQ(NULL, model.GetLabelFontAt(3));
189 EXPECT_EQ(NULL, model.GetLabelFontAt(4));
193 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(0, &url, &title));
194 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(1, &url, &title));
195 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(2, &url, &title));
196 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(3, &url, &title));
197 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(4, &url, &title));
200 // TODO(sail): enable this test when dynamic model is enabled in
201 // RecentTabsSubMenuModel.
202 #if defined(OS_MACOSX)
203 #define MAYBE_RecentlyClosedTabsAndWindowsFromLastSession \
204 DISABLED_RecentlyClosedTabsAndWindowsFromLastSession
206 #define MAYBE_RecentlyClosedTabsAndWindowsFromLastSession \
207 RecentlyClosedTabsAndWindowsFromLastSession
209 TEST_F(RecentTabsSubMenuModelTest,
210 MAYBE_RecentlyClosedTabsAndWindowsFromLastSession) {
211 TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
212 profile(), RecentTabsSubMenuModelTest::GetTabRestoreService);
214 // Add 2 tabs and close them.
215 AddTab(browser(), GURL("http://wnd/tab0"));
216 AddTab(browser(), GURL("http://wnd/tab1"));
217 browser()->tab_strip_model()->CloseAllTabs();
219 // Create a SessionService for the profile (profile owns the service) and add
220 // a window with a tab to this session.
221 SessionService* session_service = new SessionService(profile());
222 SessionServiceFactory::SetForTestProfile(profile(), session_service);
225 session_service->SetWindowType(
226 window_id, Browser::TYPE_TABBED, SessionService::TYPE_NORMAL);
227 session_service->SetTabWindow(window_id, tab_id);
228 session_service->SetTabIndexInWindow(window_id, tab_id, 0);
229 session_service->SetSelectedTabInWindow(window_id, 0);
230 session_service->UpdateTabNavigation(
232 sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
233 "http://wnd1/tab0", "title"));
234 // Set this, otherwise previous session won't be loaded.
235 profile()->set_last_session_exited_cleanly(false);
236 // Move this session to the last so that TabRestoreService will load it as the
238 SessionServiceFactory::GetForProfile(profile())->
239 MoveCurrentSessionToLastSession();
241 // Create a new TabRestoreService so that it'll load the recently closed tabs
242 // and windows afresh.
243 TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
244 profile(), RecentTabsSubMenuModelTest::GetTabRestoreService);
245 // Let the shutdown of previous TabRestoreService run.
246 content::BrowserThread::GetBlockingPool()->FlushForTesting();
248 TestRecentTabsSubMenuModel model(NULL, browser(), NULL);
249 TestRecentTabsMenuModelDelegate delegate(&model);
250 EXPECT_FALSE(delegate.got_changes());
252 // Expected menu before tabs/windows from last session are loaded:
253 // Menu index Menu items
254 // ----------------------------------------------------------------
255 // 0 Recently closed header
257 // 2 No tabs from other Devices
259 int num_items = model.GetItemCount();
260 EXPECT_EQ(3, num_items);
261 EXPECT_FALSE(model.IsEnabledAt(0));
262 EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model.GetTypeAt(1));
263 EXPECT_FALSE(model.IsEnabledAt(2));
264 EXPECT_EQ(0, model.enable_count());
266 // Wait for tabs from last session to be loaded.
267 WaitForLoadFromLastSession();
269 // Expected menu after tabs/windows from last session are loaded:
270 // Menu index Menu items
271 // --------------------------------------------------------------
272 // 0 Recently closed header
273 // 1 <window for the tab http://wnd1/tab0>
274 // 2 <tab for http://wnd0/tab1>
275 // 3 <tab for http://wnd0/tab0>
277 // 5 No tabs from other Devices
279 EXPECT_TRUE(delegate.got_changes());
281 num_items = model.GetItemCount();
282 EXPECT_EQ(6, num_items);
283 EXPECT_FALSE(model.IsEnabledAt(0));
284 EXPECT_TRUE(model.IsEnabledAt(1));
285 EXPECT_TRUE(model.IsEnabledAt(2));
286 EXPECT_TRUE(model.IsEnabledAt(3));
287 model.ActivatedAt(1);
288 model.ActivatedAt(2);
289 model.ActivatedAt(3);
290 EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model.GetTypeAt(4));
291 EXPECT_FALSE(model.IsEnabledAt(5));
292 EXPECT_EQ(3, model.enable_count());
293 EXPECT_EQ(3, model.execute_count());
295 EXPECT_TRUE(model.GetLabelFontAt(0) != NULL);
296 EXPECT_EQ(NULL, model.GetLabelFontAt(1));
297 EXPECT_EQ(NULL, model.GetLabelFontAt(2));
298 EXPECT_EQ(NULL, model.GetLabelFontAt(3));
299 EXPECT_EQ(NULL, model.GetLabelFontAt(4));
300 EXPECT_EQ(NULL, model.GetLabelFontAt(5));
304 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(0, &url, &title));
305 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(1, &url, &title));
306 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(2, &url, &title));
307 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(3, &url, &title));
308 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(4, &url, &title));
309 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(5, &url, &title));
312 // Test disabled "Recently closed" header with multiple sessions, multiple
313 // windows, and multiple enabled tabs from other devices.
314 TEST_F(RecentTabsSubMenuModelTest, OtherDevices) {
315 // Tabs are populated in decreasing timestamp.
316 base::Time timestamp = base::Time::Now();
317 const base::TimeDelta time_delta = base::TimeDelta::FromMinutes(10);
319 RecentTabsBuilderTestHelper recent_tabs_builder;
321 // Create 1st session : 1 window, 3 tabs
322 recent_tabs_builder.AddSession();
323 recent_tabs_builder.AddWindow(0);
324 for (int i = 0; i < 3; ++i) {
325 timestamp -= time_delta;
326 recent_tabs_builder.AddTabWithInfo(0, 0, timestamp, string16());
329 // Create 2nd session : 2 windows, 1 tab in 1st window, 2 tabs in 2nd window
330 recent_tabs_builder.AddSession();
331 recent_tabs_builder.AddWindow(1);
332 recent_tabs_builder.AddWindow(1);
333 timestamp -= time_delta;
334 recent_tabs_builder.AddTabWithInfo(1, 0, timestamp, string16());
335 timestamp -= time_delta;
336 recent_tabs_builder.AddTabWithInfo(1, 1, timestamp, string16());
337 timestamp -= time_delta;
338 recent_tabs_builder.AddTabWithInfo(1, 1, timestamp, string16());
340 recent_tabs_builder.RegisterRecentTabs(&associator_);
342 // Verify that data is populated correctly in RecentTabsSubMenuModel.
344 // - first inserted tab is most recent and hence is top
345 // Menu index Menu items
346 // -----------------------------------------------------
347 // 0 Recently closed header (disabled)
349 // 2 <section header for 1st session>
350 // 3-5 <3 tabs of the only window of session 0>
352 // 7 <section header for 2nd session>
353 // 8 <the only tab of window 0 of session 1>
354 // 9-10 <2 tabs of window 1 of session 2>
358 TestRecentTabsSubMenuModel model(NULL, browser(), &associator_);
359 int num_items = model.GetItemCount();
360 EXPECT_EQ(13, num_items);
361 model.ActivatedAt(0);
362 EXPECT_FALSE(model.IsEnabledAt(0));
363 model.ActivatedAt(3);
364 EXPECT_TRUE(model.IsEnabledAt(3));
365 model.ActivatedAt(4);
366 EXPECT_TRUE(model.IsEnabledAt(4));
367 model.ActivatedAt(5);
368 EXPECT_TRUE(model.IsEnabledAt(5));
369 model.ActivatedAt(8);
370 EXPECT_TRUE(model.IsEnabledAt(8));
371 model.ActivatedAt(9);
372 EXPECT_TRUE(model.IsEnabledAt(9));
373 model.ActivatedAt(10);
374 EXPECT_TRUE(model.IsEnabledAt(10));
375 EXPECT_TRUE(model.IsEnabledAt(12));
376 EXPECT_EQ(7, model.enable_count());
377 EXPECT_EQ(7, model.execute_count());
379 EXPECT_EQ(NULL, model.GetLabelFontAt(0));
380 EXPECT_EQ(NULL, model.GetLabelFontAt(1));
381 EXPECT_TRUE(model.GetLabelFontAt(2) != NULL);
382 EXPECT_EQ(NULL, model.GetLabelFontAt(3));
383 EXPECT_EQ(NULL, model.GetLabelFontAt(4));
384 EXPECT_EQ(NULL, model.GetLabelFontAt(5));
385 EXPECT_EQ(NULL, model.GetLabelFontAt(6));
386 EXPECT_TRUE(model.GetLabelFontAt(7) != NULL);
387 EXPECT_EQ(NULL, model.GetLabelFontAt(8));
388 EXPECT_EQ(NULL, model.GetLabelFontAt(9));
389 EXPECT_EQ(NULL, model.GetLabelFontAt(10));
390 EXPECT_EQ(NULL, model.GetLabelFontAt(11));
391 EXPECT_EQ(NULL, model.GetLabelFontAt(12));
395 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(0, &url, &title));
396 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(1, &url, &title));
397 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(2, &url, &title));
398 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(3, &url, &title));
399 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(4, &url, &title));
400 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(5, &url, &title));
401 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(6, &url, &title));
402 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(7, &url, &title));
403 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(8, &url, &title));
404 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(9, &url, &title));
405 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(10, &url, &title));
406 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(11, &url, &title));
407 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(12, &url, &title));
410 TEST_F(RecentTabsSubMenuModelTest, MaxSessionsAndRecency) {
411 // Create 4 sessions : each session has 1 window with 1 tab each.
412 RecentTabsBuilderTestHelper recent_tabs_builder;
413 for (int s = 0; s < 4; ++s) {
414 recent_tabs_builder.AddSession();
415 recent_tabs_builder.AddWindow(s);
416 recent_tabs_builder.AddTab(s, 0);
418 recent_tabs_builder.RegisterRecentTabs(&associator_);
420 // Verify that data is populated correctly in RecentTabsSubMenuModel.
422 // - max sessions is 3, so only 3 most-recent sessions will show.
423 // Menu index Menu items
424 // ----------------------------------------------------------
425 // 0 Recently closed header (disabled)
427 // 2 <section header for 1st session>
428 // 3 <the only tab of the only window of session 3>
430 // 5 <section header for 2nd session>
431 // 6 <the only tab of the only window of session 2>
433 // 8 <section header for 3rd session>
434 // 9 <the only tab of the only window of session 1>
438 TestRecentTabsSubMenuModel model(NULL, browser(), &associator_);
439 int num_items = model.GetItemCount();
440 EXPECT_EQ(12, num_items);
442 std::vector<string16> tab_titles =
443 recent_tabs_builder.GetTabTitlesSortedByRecency();
444 EXPECT_EQ(tab_titles[0], model.GetLabelAt(3));
445 EXPECT_EQ(tab_titles[1], model.GetLabelAt(6));
446 EXPECT_EQ(tab_titles[2], model.GetLabelAt(9));
449 TEST_F(RecentTabsSubMenuModelTest, MaxTabsPerSessionAndRecency) {
450 // Create a session: 2 windows with 5 tabs each.
451 RecentTabsBuilderTestHelper recent_tabs_builder;
452 recent_tabs_builder.AddSession();
453 for (int w = 0; w < 2; ++w) {
454 recent_tabs_builder.AddWindow(0);
455 for (int t = 0; t < 5; ++t)
456 recent_tabs_builder.AddTab(0, w);
458 recent_tabs_builder.RegisterRecentTabs(&associator_);
460 // Verify that data is populated correctly in RecentTabsSubMenuModel.
462 // - max tabs per session is 4, so only 4 most-recent tabs will show,
463 // independent of which window they came from.
464 // Menu index Menu items
465 // ---------------------------------------------
466 // 0 Recently closed header (disabled)
468 // 2 <section header for session>
469 // 3-6 <4 most-recent tabs of session>
473 TestRecentTabsSubMenuModel model(NULL, browser(), &associator_);
474 int num_items = model.GetItemCount();
475 EXPECT_EQ(9, num_items);
477 std::vector<string16> tab_titles =
478 recent_tabs_builder.GetTabTitlesSortedByRecency();
479 for (int i = 0; i < 4; ++i)
480 EXPECT_EQ(tab_titles[i], model.GetLabelAt(i + 3));
483 TEST_F(RecentTabsSubMenuModelTest, MaxWidth) {
484 // Create 1 session with 1 window and 1 tab.
485 RecentTabsBuilderTestHelper recent_tabs_builder;
486 recent_tabs_builder.AddSession();
487 recent_tabs_builder.AddWindow(0);
488 recent_tabs_builder.AddTab(0, 0);
489 recent_tabs_builder.RegisterRecentTabs(&associator_);
491 // Menu index Menu items
492 // ----------------------------------------------------------
493 // 0 Recently closed header (disabled)
495 // 2 <section header for 1st session>
496 // 3 <the only tab of the only window of session 1>
500 TestRecentTabsSubMenuModel model(NULL, browser(), &associator_);
501 EXPECT_EQ(6, model.GetItemCount());
502 EXPECT_EQ(-1, model.GetMaxWidthForItemAtIndex(0));
503 EXPECT_NE(-1, model.GetMaxWidthForItemAtIndex(1));
504 EXPECT_NE(-1, model.GetMaxWidthForItemAtIndex(2));
505 EXPECT_NE(-1, model.GetMaxWidthForItemAtIndex(3));
508 TEST_F(RecentTabsSubMenuModelTest, MaxWidthNoDevices) {
510 // Menu index Menu items
511 // --------------------------------------------
512 // 0 Recently closed heaer (disabled)
514 // 2 No tabs from other Devices
516 TestRecentTabsSubMenuModel model(NULL, browser(), NULL);
517 EXPECT_EQ(3, model.GetItemCount());
518 EXPECT_EQ(-1, model.GetMaxWidthForItemAtIndex(0));
519 EXPECT_NE(-1, model.GetMaxWidthForItemAtIndex(1));
520 EXPECT_EQ(-1, model.GetMaxWidthForItemAtIndex(2));