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"
10 #include "base/command_line.h"
11 #include "base/run_loop.h"
12 #include "chrome/app/chrome_command_ids.h"
13 #include "chrome/browser/sessions/persistent_tab_restore_service.h"
14 #include "chrome/browser/sessions/session_service.h"
15 #include "chrome/browser/sessions/session_service_factory.h"
16 #include "chrome/browser/sessions/session_types.h"
17 #include "chrome/browser/sessions/tab_restore_service_factory.h"
18 #include "chrome/browser/sync/glue/synced_session.h"
19 #include "chrome/browser/sync/profile_sync_service_mock.h"
20 #include "chrome/browser/sync/sessions/sessions_sync_manager.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/browser_tabstrip.h"
23 #include "chrome/browser/ui/tabs/tab_strip_model.h"
24 #include "chrome/browser/ui/toolbar/recent_tabs_builder_test_helper.h"
25 #include "chrome/common/chrome_switches.h"
26 #include "chrome/test/base/browser_with_test_window_test.h"
27 #include "chrome/test/base/menu_model_test.h"
28 #include "chrome/test/base/testing_profile.h"
29 #include "components/sessions/serialized_navigation_entry_test_helper.h"
30 #include "components/sync_driver/local_device_info_provider_mock.h"
31 #include "content/public/browser/browser_thread.h"
32 #include "content/public/test/test_utils.h"
33 #include "grit/generated_resources.h"
34 #include "sync/api/fake_sync_change_processor.h"
35 #include "sync/api/sync_error_factory_mock.h"
36 #include "testing/gmock/include/gmock/gmock.h"
37 #include "testing/gtest/include/gtest/gtest.h"
41 // This copies parts of MenuModelTest::Delegate and combines them with the
42 // RecentTabsSubMenuModel since RecentTabsSubMenuModel is a
43 // SimpleMenuModel::Delegate and not just derived from SimpleMenuModel.
44 class TestRecentTabsSubMenuModel : public RecentTabsSubMenuModel {
46 TestRecentTabsSubMenuModel(ui::AcceleratorProvider* provider,
48 browser_sync::OpenTabsUIDelegate* delegate)
49 : RecentTabsSubMenuModel(provider, browser, delegate),
54 // Testing overrides to ui::SimpleMenuModel::Delegate:
55 bool IsCommandIdEnabled(int command_id) const override {
56 bool val = RecentTabsSubMenuModel::IsCommandIdEnabled(command_id);
62 void ExecuteCommand(int command_id, int event_flags) override {
66 int execute_count() const { return execute_count_; }
67 int enable_count() const { return enable_count_; }
71 int mutable enable_count_; // Mutable because IsCommandIdEnabledAt is const.
73 DISALLOW_COPY_AND_ASSIGN(TestRecentTabsSubMenuModel);
76 class TestRecentTabsMenuModelDelegate : public ui::MenuModelDelegate {
78 explicit TestRecentTabsMenuModelDelegate(ui::MenuModel* model)
81 model_->SetMenuModelDelegate(this);
84 ~TestRecentTabsMenuModelDelegate() override {
85 model_->SetMenuModelDelegate(NULL);
88 // ui::MenuModelDelegate implementation:
90 void OnIconChanged(int index) override {}
92 void OnMenuStructureChanged() override { got_changes_ = true; }
94 bool got_changes() const { return got_changes_; }
97 ui::MenuModel* model_;
100 DISALLOW_COPY_AND_ASSIGN(TestRecentTabsMenuModelDelegate);
103 class DummyRouter : public browser_sync::LocalSessionEventRouter {
105 ~DummyRouter() override {}
107 browser_sync::LocalSessionEventHandler* handler) override {}
108 void Stop() override {}
113 class RecentTabsSubMenuModelTest
114 : public BrowserWithTestWindowTest {
116 RecentTabsSubMenuModelTest()
117 : sync_service_(&testing_profile_),
118 local_device_(new sync_driver::LocalDeviceInfoProviderMock(
119 "RecentTabsSubMenuModelTest",
123 sync_pb::SyncEnums_DeviceType_TYPE_LINUX,
125 manager_.reset(new browser_sync::SessionsSyncManager(
128 scoped_ptr<browser_sync::LocalSessionEventRouter>(
129 new DummyRouter())));
130 manager_->MergeDataAndStartSyncing(
132 syncer::SyncDataList(),
133 scoped_ptr<syncer::SyncChangeProcessor>(
134 new syncer::FakeSyncChangeProcessor),
135 scoped_ptr<syncer::SyncErrorFactory>(
136 new syncer::SyncErrorFactoryMock));
139 void WaitForLoadFromLastSession() {
140 content::RunAllBlockingPoolTasksUntilIdle();
143 static KeyedService* GetTabRestoreService(
144 content::BrowserContext* browser_context) {
145 // Ownership is tranfered to the profile.
146 return new PersistentTabRestoreService(
147 Profile::FromBrowserContext(browser_context), NULL);
150 browser_sync::OpenTabsUIDelegate* GetOpenTabsDelegate() {
151 return manager_.get();
154 void RegisterRecentTabs(RecentTabsBuilderTestHelper* helper) {
155 helper->ExportToSessionsSyncManager(manager_.get());
159 TestingProfile testing_profile_;
160 testing::NiceMock<ProfileSyncServiceMock> sync_service_;
162 scoped_ptr<browser_sync::SessionsSyncManager> manager_;
163 scoped_ptr<sync_driver::LocalDeviceInfoProviderMock> local_device_;
166 // Test disabled "Recently closed" header with no foreign tabs.
167 TEST_F(RecentTabsSubMenuModelTest, NoTabs) {
168 TestRecentTabsSubMenuModel model(NULL, browser(), NULL);
171 // Menu index Menu items
172 // ---------------------------------------------
173 // 0 Recently closed header (disabled)
175 // 2 No tabs from other Devices
177 int num_items = model.GetItemCount();
178 EXPECT_EQ(3, num_items);
179 EXPECT_FALSE(model.IsEnabledAt(0));
180 EXPECT_FALSE(model.IsEnabledAt(2));
181 EXPECT_EQ(0, model.enable_count());
183 EXPECT_EQ(NULL, model.GetLabelFontListAt(0));
184 EXPECT_EQ(NULL, model.GetLabelFontListAt(1));
185 EXPECT_EQ(NULL, model.GetLabelFontListAt(2));
188 base::string16 title;
189 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(0, &url, &title));
190 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(1, &url, &title));
191 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(2, &url, &title));
194 // Test enabled "Recently closed" header with no foreign tabs.
195 TEST_F(RecentTabsSubMenuModelTest, RecentlyClosedTabsFromCurrentSession) {
196 TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
197 profile(), RecentTabsSubMenuModelTest::GetTabRestoreService);
199 // Add 2 tabs and close them.
200 AddTab(browser(), GURL("http://foo/1"));
201 AddTab(browser(), GURL("http://foo/2"));
202 browser()->tab_strip_model()->CloseAllTabs();
204 TestRecentTabsSubMenuModel model(NULL, browser(), NULL);
206 // Menu index Menu items
207 // --------------------------------------
208 // 0 Recently closed header
209 // 1 <tab for http://foo/2>
210 // 2 <tab for http://foo/1>
212 // 4 No tabs from other Devices
213 int num_items = model.GetItemCount();
214 EXPECT_EQ(5, num_items);
215 EXPECT_FALSE(model.IsEnabledAt(0));
216 EXPECT_TRUE(model.IsEnabledAt(1));
217 EXPECT_TRUE(model.IsEnabledAt(2));
218 model.ActivatedAt(1);
219 model.ActivatedAt(2);
220 EXPECT_FALSE(model.IsEnabledAt(4));
221 EXPECT_EQ(2, model.enable_count());
222 EXPECT_EQ(2, model.execute_count());
224 EXPECT_TRUE(model.GetLabelFontListAt(0) != NULL);
225 EXPECT_EQ(NULL, model.GetLabelFontListAt(1));
226 EXPECT_EQ(NULL, model.GetLabelFontListAt(2));
227 EXPECT_EQ(NULL, model.GetLabelFontListAt(3));
228 EXPECT_EQ(NULL, model.GetLabelFontListAt(4));
231 base::string16 title;
232 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(0, &url, &title));
233 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(1, &url, &title));
234 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(2, &url, &title));
235 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(3, &url, &title));
236 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(4, &url, &title));
239 // TODO(sail): enable this test when dynamic model is enabled in
240 // RecentTabsSubMenuModel.
241 #if defined(OS_MACOSX)
242 #define MAYBE_RecentlyClosedTabsAndWindowsFromLastSession \
243 DISABLED_RecentlyClosedTabsAndWindowsFromLastSession
245 #define MAYBE_RecentlyClosedTabsAndWindowsFromLastSession \
246 RecentlyClosedTabsAndWindowsFromLastSession
248 TEST_F(RecentTabsSubMenuModelTest,
249 MAYBE_RecentlyClosedTabsAndWindowsFromLastSession) {
250 TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
251 profile(), RecentTabsSubMenuModelTest::GetTabRestoreService);
253 // Add 2 tabs and close them.
254 AddTab(browser(), GURL("http://wnd/tab0"));
255 AddTab(browser(), GURL("http://wnd/tab1"));
256 browser()->tab_strip_model()->CloseAllTabs();
258 // Create a SessionService for the profile (profile owns the service) and add
259 // a window with a tab to this session.
260 SessionService* session_service = new SessionService(profile());
261 SessionServiceFactory::SetForTestProfile(profile(), session_service);
264 session_service->SetWindowType(window_id,
265 Browser::TYPE_TABBED,
266 SessionService::TYPE_NORMAL);
267 session_service->SetTabWindow(window_id, tab_id);
268 session_service->SetTabIndexInWindow(window_id, tab_id, 0);
269 session_service->SetSelectedTabInWindow(window_id, 0);
270 session_service->UpdateTabNavigation(
272 sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
273 "http://wnd1/tab0", "title"));
274 // Set this, otherwise previous session won't be loaded.
275 profile()->set_last_session_exited_cleanly(false);
276 // Move this session to the last so that TabRestoreService will load it as the
278 SessionServiceFactory::GetForProfile(profile())->
279 MoveCurrentSessionToLastSession();
281 // Create a new TabRestoreService so that it'll load the recently closed tabs
282 // and windows afresh.
283 TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
284 profile(), RecentTabsSubMenuModelTest::GetTabRestoreService);
285 // Let the shutdown of previous TabRestoreService run.
286 content::RunAllBlockingPoolTasksUntilIdle();
288 TestRecentTabsSubMenuModel model(NULL, browser(), NULL);
289 TestRecentTabsMenuModelDelegate delegate(&model);
290 EXPECT_FALSE(delegate.got_changes());
292 // Expected menu before tabs/windows from last session are loaded:
293 // Menu index Menu items
294 // ----------------------------------------------------------------
295 // 0 Recently closed header
297 // 2 No tabs from other Devices
299 int num_items = model.GetItemCount();
300 EXPECT_EQ(3, num_items);
301 EXPECT_FALSE(model.IsEnabledAt(0));
302 EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model.GetTypeAt(1));
303 EXPECT_FALSE(model.IsEnabledAt(2));
304 EXPECT_EQ(0, model.enable_count());
306 // Wait for tabs from last session to be loaded.
307 WaitForLoadFromLastSession();
309 // Expected menu after tabs/windows from last session are loaded:
310 // Menu index Menu items
311 // --------------------------------------------------------------
312 // 0 Recently closed header
313 // 1 <window for the tab http://wnd1/tab0>
314 // 2 <tab for http://wnd0/tab1>
315 // 3 <tab for http://wnd0/tab0>
317 // 5 No tabs from other Devices
319 EXPECT_TRUE(delegate.got_changes());
321 num_items = model.GetItemCount();
322 EXPECT_EQ(6, num_items);
323 EXPECT_FALSE(model.IsEnabledAt(0));
324 EXPECT_TRUE(model.IsEnabledAt(1));
325 EXPECT_TRUE(model.IsEnabledAt(2));
326 EXPECT_TRUE(model.IsEnabledAt(3));
327 model.ActivatedAt(1);
328 model.ActivatedAt(2);
329 model.ActivatedAt(3);
330 EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model.GetTypeAt(4));
331 EXPECT_FALSE(model.IsEnabledAt(5));
332 EXPECT_EQ(3, model.enable_count());
333 EXPECT_EQ(3, model.execute_count());
335 EXPECT_TRUE(model.GetLabelFontListAt(0) != NULL);
336 EXPECT_EQ(NULL, model.GetLabelFontListAt(1));
337 EXPECT_EQ(NULL, model.GetLabelFontListAt(2));
338 EXPECT_EQ(NULL, model.GetLabelFontListAt(3));
339 EXPECT_EQ(NULL, model.GetLabelFontListAt(4));
340 EXPECT_EQ(NULL, model.GetLabelFontListAt(5));
343 base::string16 title;
344 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(0, &url, &title));
345 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(1, &url, &title));
346 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(2, &url, &title));
347 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(3, &url, &title));
348 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(4, &url, &title));
349 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(5, &url, &title));
352 // Test disabled "Recently closed" header with multiple sessions, multiple
353 // windows, and multiple enabled tabs from other devices.
354 TEST_F(RecentTabsSubMenuModelTest, OtherDevices) {
355 // Tabs are populated in decreasing timestamp.
356 base::Time timestamp = base::Time::Now();
357 const base::TimeDelta time_delta = base::TimeDelta::FromMinutes(10);
359 RecentTabsBuilderTestHelper recent_tabs_builder;
361 // Create 1st session : 1 window, 3 tabs
362 recent_tabs_builder.AddSession();
363 recent_tabs_builder.AddWindow(0);
364 for (int i = 0; i < 3; ++i) {
365 timestamp -= time_delta;
366 recent_tabs_builder.AddTabWithInfo(0, 0, timestamp, base::string16());
369 // Create 2nd session : 2 windows, 1 tab in 1st window, 2 tabs in 2nd window
370 recent_tabs_builder.AddSession();
371 recent_tabs_builder.AddWindow(1);
372 recent_tabs_builder.AddWindow(1);
373 timestamp -= time_delta;
374 recent_tabs_builder.AddTabWithInfo(1, 0, timestamp, base::string16());
375 timestamp -= time_delta;
376 recent_tabs_builder.AddTabWithInfo(1, 1, timestamp, base::string16());
377 timestamp -= time_delta;
378 recent_tabs_builder.AddTabWithInfo(1, 1, timestamp, base::string16());
380 RegisterRecentTabs(&recent_tabs_builder);
382 // Verify that data is populated correctly in RecentTabsSubMenuModel.
384 // - first inserted tab is most recent and hence is top
385 // Menu index Menu items
386 // -----------------------------------------------------
387 // 0 Recently closed header (disabled)
389 // 2 <section header for 1st session>
390 // 3-5 <3 tabs of the only window of session 0>
392 // 7 <section header for 2nd session>
393 // 8 <the only tab of window 0 of session 1>
394 // 9-10 <2 tabs of window 1 of session 2>
398 TestRecentTabsSubMenuModel model(NULL, browser(), GetOpenTabsDelegate());
399 int num_items = model.GetItemCount();
400 EXPECT_EQ(13, num_items);
401 model.ActivatedAt(0);
402 EXPECT_FALSE(model.IsEnabledAt(0));
403 model.ActivatedAt(3);
404 EXPECT_TRUE(model.IsEnabledAt(3));
405 model.ActivatedAt(4);
406 EXPECT_TRUE(model.IsEnabledAt(4));
407 model.ActivatedAt(5);
408 EXPECT_TRUE(model.IsEnabledAt(5));
409 model.ActivatedAt(8);
410 EXPECT_TRUE(model.IsEnabledAt(8));
411 model.ActivatedAt(9);
412 EXPECT_TRUE(model.IsEnabledAt(9));
413 model.ActivatedAt(10);
414 EXPECT_TRUE(model.IsEnabledAt(10));
415 EXPECT_TRUE(model.IsEnabledAt(12));
416 EXPECT_EQ(7, model.enable_count());
417 EXPECT_EQ(7, model.execute_count());
419 EXPECT_EQ(NULL, model.GetLabelFontListAt(0));
420 EXPECT_EQ(NULL, model.GetLabelFontListAt(1));
421 EXPECT_TRUE(model.GetLabelFontListAt(2) != NULL);
422 EXPECT_EQ(NULL, model.GetLabelFontListAt(3));
423 EXPECT_EQ(NULL, model.GetLabelFontListAt(4));
424 EXPECT_EQ(NULL, model.GetLabelFontListAt(5));
425 EXPECT_EQ(NULL, model.GetLabelFontListAt(6));
426 EXPECT_TRUE(model.GetLabelFontListAt(7) != NULL);
427 EXPECT_EQ(NULL, model.GetLabelFontListAt(8));
428 EXPECT_EQ(NULL, model.GetLabelFontListAt(9));
429 EXPECT_EQ(NULL, model.GetLabelFontListAt(10));
430 EXPECT_EQ(NULL, model.GetLabelFontListAt(11));
431 EXPECT_EQ(NULL, model.GetLabelFontListAt(12));
434 base::string16 title;
435 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(0, &url, &title));
436 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(1, &url, &title));
437 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(2, &url, &title));
438 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(3, &url, &title));
439 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(4, &url, &title));
440 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(5, &url, &title));
441 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(6, &url, &title));
442 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(7, &url, &title));
443 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(8, &url, &title));
444 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(9, &url, &title));
445 EXPECT_TRUE(model.GetURLAndTitleForItemAtIndex(10, &url, &title));
446 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(11, &url, &title));
447 EXPECT_FALSE(model.GetURLAndTitleForItemAtIndex(12, &url, &title));
450 TEST_F(RecentTabsSubMenuModelTest, MaxSessionsAndRecency) {
451 // Create 4 sessions : each session has 1 window with 1 tab each.
452 RecentTabsBuilderTestHelper recent_tabs_builder;
453 for (int s = 0; s < 4; ++s) {
454 recent_tabs_builder.AddSession();
455 recent_tabs_builder.AddWindow(s);
456 recent_tabs_builder.AddTab(s, 0);
458 RegisterRecentTabs(&recent_tabs_builder);
460 // Verify that data is populated correctly in RecentTabsSubMenuModel.
462 // - max sessions is 3, so only 3 most-recent sessions will show.
463 // Menu index Menu items
464 // ----------------------------------------------------------
465 // 0 Recently closed header (disabled)
467 // 2 <section header for 1st session>
468 // 3 <the only tab of the only window of session 3>
470 // 5 <section header for 2nd session>
471 // 6 <the only tab of the only window of session 2>
473 // 8 <section header for 3rd session>
474 // 9 <the only tab of the only window of session 1>
478 TestRecentTabsSubMenuModel model(NULL, browser(), GetOpenTabsDelegate());
479 int num_items = model.GetItemCount();
480 EXPECT_EQ(12, num_items);
482 std::vector<base::string16> tab_titles =
483 recent_tabs_builder.GetTabTitlesSortedByRecency();
484 EXPECT_EQ(tab_titles[0], model.GetLabelAt(3));
485 EXPECT_EQ(tab_titles[1], model.GetLabelAt(6));
486 EXPECT_EQ(tab_titles[2], model.GetLabelAt(9));
489 TEST_F(RecentTabsSubMenuModelTest, MaxTabsPerSessionAndRecency) {
490 // Create a session: 2 windows with 5 tabs each.
491 RecentTabsBuilderTestHelper recent_tabs_builder;
492 recent_tabs_builder.AddSession();
493 for (int w = 0; w < 2; ++w) {
494 recent_tabs_builder.AddWindow(0);
495 for (int t = 0; t < 5; ++t)
496 recent_tabs_builder.AddTab(0, w);
498 RegisterRecentTabs(&recent_tabs_builder);
500 // Verify that data is populated correctly in RecentTabsSubMenuModel.
502 // - max tabs per session is 4, so only 4 most-recent tabs will show,
503 // independent of which window they came from.
504 // Menu index Menu items
505 // ---------------------------------------------
506 // 0 Recently closed header (disabled)
508 // 2 <section header for session>
509 // 3-6 <4 most-recent tabs of session>
513 TestRecentTabsSubMenuModel model(NULL, browser(), GetOpenTabsDelegate());
514 int num_items = model.GetItemCount();
515 EXPECT_EQ(9, num_items);
517 std::vector<base::string16> tab_titles =
518 recent_tabs_builder.GetTabTitlesSortedByRecency();
519 for (int i = 0; i < 4; ++i)
520 EXPECT_EQ(tab_titles[i], model.GetLabelAt(i + 3));
523 TEST_F(RecentTabsSubMenuModelTest, MaxWidth) {
524 // Create 1 session with 1 window and 1 tab.
525 RecentTabsBuilderTestHelper recent_tabs_builder;
526 recent_tabs_builder.AddSession();
527 recent_tabs_builder.AddWindow(0);
528 recent_tabs_builder.AddTab(0, 0);
529 RegisterRecentTabs(&recent_tabs_builder);
531 // Menu index Menu items
532 // ----------------------------------------------------------
533 // 0 Recently closed header (disabled)
535 // 2 <section header for 1st session>
536 // 3 <the only tab of the only window of session 1>
540 TestRecentTabsSubMenuModel model(NULL, browser(), GetOpenTabsDelegate());
541 EXPECT_EQ(6, model.GetItemCount());
542 EXPECT_EQ(-1, model.GetMaxWidthForItemAtIndex(0));
543 EXPECT_NE(-1, model.GetMaxWidthForItemAtIndex(1));
544 EXPECT_NE(-1, model.GetMaxWidthForItemAtIndex(2));
545 EXPECT_NE(-1, model.GetMaxWidthForItemAtIndex(3));
548 TEST_F(RecentTabsSubMenuModelTest, MaxWidthNoDevices) {
550 // Menu index Menu items
551 // --------------------------------------------
552 // 0 Recently closed heaer (disabled)
554 // 2 No tabs from other Devices
556 TestRecentTabsSubMenuModel model(NULL, browser(), NULL);
557 EXPECT_EQ(3, model.GetItemCount());
558 EXPECT_EQ(-1, model.GetMaxWidthForItemAtIndex(0));
559 EXPECT_NE(-1, model.GetMaxWidthForItemAtIndex(1));
560 EXPECT_EQ(-1, model.GetMaxWidthForItemAtIndex(2));