1// Copyright (c) 2010 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/sessions/session_types.h"
6#include "chrome/browser/sessions/session_service.h"
7#include "chrome/browser/sessions/tab_restore_service.h"
8#include "chrome/test/render_view_test.h"
9#include "chrome/test/testing_profile.h"
10#include "content/browser/renderer_host/test_render_view_host.h"
11#include "content/browser/tab_contents/navigation_controller.h"
12#include "content/browser/tab_contents/navigation_entry.h"
13#include "content/browser/tab_contents/test_tab_contents.h"
14#include "testing/gtest/include/gtest/gtest.h"
15#include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h"
16
17// Create subclass that overrides TimeNow so that we can control the time used
18// for closed tabs and windows.
19class TabRestoreTimeFactory : public TabRestoreService::TimeFactory {
20 public:
21  TabRestoreTimeFactory() : time_(base::Time::Now()) {}
22
23  virtual ~TabRestoreTimeFactory() {}
24
25  virtual base::Time TimeNow() {
26    return time_;
27  }
28
29 private:
30  base::Time time_;
31};
32
33class TabRestoreServiceTest : public RenderViewHostTestHarness {
34 public:
35  TabRestoreServiceTest() {
36    url1_ = GURL("http://1");
37    url2_ = GURL("http://2");
38    url3_ = GURL("http://3");
39  }
40
41  ~TabRestoreServiceTest() {
42  }
43
44 protected:
45  // testing::Test overrides
46  virtual void SetUp() {
47    RenderViewHostTestHarness::SetUp();
48    time_factory_ = new TabRestoreTimeFactory();
49    service_ = new TabRestoreService(profile(), time_factory_);
50    WebKit::initialize(&webkitclient_);
51  }
52
53  virtual void TearDown() {
54    service_ = NULL;
55    delete time_factory_;
56    RenderViewHostTestHarness::TearDown();
57    WebKit::shutdown();
58  }
59
60  void AddThreeNavigations() {
61    // Navigate to three URLs.
62    NavigateAndCommit(url1_);
63    NavigateAndCommit(url2_);
64    NavigateAndCommit(url3_);
65  }
66
67  void NavigateToIndex(int index) {
68    // Navigate back. We have to do this song and dance as NavigationController
69    // isn't happy if you navigate immediately while going back.
70    controller().GoToIndex(index);
71    contents()->CommitPendingNavigation();
72  }
73
74  void RecreateService() {
75    // Must set service to null first so that it is destroyed before the new
76    // one is created.
77    service_ = NULL;
78    service_ = new TabRestoreService(profile(), time_factory_);
79    service_->LoadTabsFromLastSession();
80  }
81
82  // Adds a window with one tab and url to the profile's session service.
83  // If |pinned| is true, the tab is marked as pinned in the session service.
84  void AddWindowWithOneTabToSessionService(bool pinned) {
85    SessionService* session_service = profile()->GetSessionService();
86    SessionID tab_id;
87    SessionID window_id;
88    session_service->SetWindowType(window_id, Browser::TYPE_NORMAL);
89    session_service->SetTabWindow(window_id, tab_id);
90    session_service->SetTabIndexInWindow(window_id, tab_id, 0);
91    session_service->SetSelectedTabInWindow(window_id, 0);
92    if (pinned)
93      session_service->SetPinnedState(window_id, tab_id, true);
94    NavigationEntry entry;
95    entry.set_url(url1_);
96    session_service->UpdateTabNavigation(window_id, tab_id, 0, entry);
97  }
98
99  // Creates a SessionService and assigns it to the Profile. The SessionService
100  // is configured with a single window with a single tab pointing at url1_ by
101  // way of AddWindowWithOneTabToSessionService. If |pinned| is true, the
102  // tab is marked as pinned in the session service.
103  void CreateSessionServiceWithOneWindow(bool pinned) {
104    // The profile takes ownership of this.
105    SessionService* session_service = new SessionService(profile());
106    profile()->set_session_service(session_service);
107
108    AddWindowWithOneTabToSessionService(pinned);
109
110    // Set this, otherwise previous session won't be loaded.
111    profile()->set_last_session_exited_cleanly(false);
112  }
113
114  GURL url1_;
115  GURL url2_;
116  GURL url3_;
117  scoped_refptr<TabRestoreService> service_;
118  TabRestoreTimeFactory* time_factory_;
119  RenderViewTest::RendererWebKitClientImplNoSandbox webkitclient_;
120};
121
122TEST_F(TabRestoreServiceTest, Basic) {
123  AddThreeNavigations();
124
125  // Have the service record the tab.
126  service_->CreateHistoricalTab(&controller(), -1);
127
128  // Make sure an entry was created.
129  ASSERT_EQ(1U, service_->entries().size());
130
131  // Make sure the entry matches.
132  TabRestoreService::Entry* entry = service_->entries().front();
133  ASSERT_EQ(TabRestoreService::TAB, entry->type);
134  TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
135  EXPECT_FALSE(tab->pinned);
136  EXPECT_TRUE(tab->extension_app_id.empty());
137  ASSERT_EQ(3U, tab->navigations.size());
138  EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
139  EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
140  EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
141  EXPECT_EQ(2, tab->current_navigation_index);
142  EXPECT_EQ(time_factory_->TimeNow().ToInternalValue(),
143            tab->timestamp.ToInternalValue());
144
145  NavigateToIndex(1);
146
147  // And check again.
148  service_->CreateHistoricalTab(&controller(), -1);
149
150  // There should be two entries now.
151  ASSERT_EQ(2U, service_->entries().size());
152
153  // Make sure the entry matches
154  entry = service_->entries().front();
155  ASSERT_EQ(TabRestoreService::TAB, entry->type);
156  tab = static_cast<TabRestoreService::Tab*>(entry);
157  EXPECT_FALSE(tab->pinned);
158  ASSERT_EQ(3U, tab->navigations.size());
159  EXPECT_EQ(url1_, tab->navigations[0].virtual_url());
160  EXPECT_EQ(url2_, tab->navigations[1].virtual_url());
161  EXPECT_EQ(url3_, tab->navigations[2].virtual_url());
162  EXPECT_EQ(1, tab->current_navigation_index);
163  EXPECT_EQ(time_factory_->TimeNow().ToInternalValue(),
164            tab->timestamp.ToInternalValue());
165}
166
167// Make sure TabRestoreService doesn't create an entry for a tab with no
168// navigations.
169TEST_F(TabRestoreServiceTest, DontCreateEmptyTab) {
170  service_->CreateHistoricalTab(&controller(), -1);
171  EXPECT_TRUE(service_->entries().empty());
172}
173
174// Tests restoring a single tab.
175TEST_F(TabRestoreServiceTest, Restore) {
176  AddThreeNavigations();
177
178  // Have the service record the tab.
179  service_->CreateHistoricalTab(&controller(), -1);
180
181  // Recreate the service and have it load the tabs.
182  RecreateService();
183
184  // One entry should be created.
185  ASSERT_EQ(1U, service_->entries().size());
186
187  // And verify the entry.
188  TabRestoreService::Entry* entry = service_->entries().front();
189  ASSERT_EQ(TabRestoreService::TAB, entry->type);
190  TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
191  EXPECT_FALSE(tab->pinned);
192  ASSERT_EQ(3U, tab->navigations.size());
193  EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
194  EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
195  EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
196  EXPECT_EQ(2, tab->current_navigation_index);
197  EXPECT_EQ(time_factory_->TimeNow().ToInternalValue(),
198            tab->timestamp.ToInternalValue());
199}
200
201// Tests restoring a single pinned tab.
202TEST_F(TabRestoreServiceTest, RestorePinnedAndApp) {
203  AddThreeNavigations();
204
205  // Have the service record the tab.
206  service_->CreateHistoricalTab(&controller(), -1);
207
208  // One entry should be created.
209  ASSERT_EQ(1U, service_->entries().size());
210
211  // We have to explicitly mark the tab as pinned as there is no browser for
212  // these tests.
213  TabRestoreService::Entry* entry = service_->entries().front();
214  ASSERT_EQ(TabRestoreService::TAB, entry->type);
215  TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
216  tab->pinned = true;
217  const std::string extension_app_id("test");
218  tab->extension_app_id = extension_app_id;
219
220  // Recreate the service and have it load the tabs.
221  RecreateService();
222
223  // One entry should be created.
224  ASSERT_EQ(1U, service_->entries().size());
225
226  // And verify the entry.
227  entry = service_->entries().front();
228  ASSERT_EQ(TabRestoreService::TAB, entry->type);
229  tab = static_cast<TabRestoreService::Tab*>(entry);
230  EXPECT_TRUE(tab->pinned);
231  ASSERT_EQ(3U, tab->navigations.size());
232  EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
233  EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
234  EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
235  EXPECT_EQ(2, tab->current_navigation_index);
236  EXPECT_TRUE(extension_app_id == tab->extension_app_id);
237}
238
239// Make sure we persist entries to disk that have post data.
240TEST_F(TabRestoreServiceTest, DontPersistPostData) {
241  AddThreeNavigations();
242  controller().GetEntryAtIndex(0)->set_has_post_data(true);
243  controller().GetEntryAtIndex(1)->set_has_post_data(true);
244  controller().GetEntryAtIndex(2)->set_has_post_data(true);
245
246  // Have the service record the tab.
247  service_->CreateHistoricalTab(&controller(), -1);
248  ASSERT_EQ(1U, service_->entries().size());
249
250  // Recreate the service and have it load the tabs.
251  RecreateService();
252
253  // One entry should be created.
254  ASSERT_EQ(1U, service_->entries().size());
255
256  const TabRestoreService::Entry* restored_entry = service_->entries().front();
257  ASSERT_EQ(TabRestoreService::TAB, restored_entry->type);
258
259  const TabRestoreService::Tab* restored_tab =
260      static_cast<const TabRestoreService::Tab*>(restored_entry);
261  // There should be 3 navs.
262  ASSERT_EQ(3U, restored_tab->navigations.size());
263  EXPECT_EQ(time_factory_->TimeNow().ToInternalValue(),
264            restored_tab->timestamp.ToInternalValue());
265}
266
267// Make sure we don't persist entries to disk that have post data. This
268// differs from DontPersistPostData1 in that all the navigations have post
269// data, so that nothing should be persisted.
270TEST_F(TabRestoreServiceTest, DontLoadTwice) {
271  AddThreeNavigations();
272
273  // Have the service record the tab.
274  service_->CreateHistoricalTab(&controller(), -1);
275  ASSERT_EQ(1U, service_->entries().size());
276
277  // Recreate the service and have it load the tabs.
278  RecreateService();
279
280  service_->LoadTabsFromLastSession();
281
282  // There should only be one entry.
283  ASSERT_EQ(1U, service_->entries().size());
284}
285
286// Makes sure we load the previous session as necessary.
287TEST_F(TabRestoreServiceTest, LoadPreviousSession) {
288  CreateSessionServiceWithOneWindow(false);
289
290  profile()->GetSessionService()->MoveCurrentSessionToLastSession();
291
292  service_->LoadTabsFromLastSession();
293
294  // Make sure we get back one entry with one tab whose url is url1.
295  ASSERT_EQ(1U, service_->entries().size());
296  TabRestoreService::Entry* entry2 = service_->entries().front();
297  ASSERT_EQ(TabRestoreService::WINDOW, entry2->type);
298  TabRestoreService::Window* window =
299      static_cast<TabRestoreService::Window*>(entry2);
300  ASSERT_EQ(1U, window->tabs.size());
301  EXPECT_EQ(0, window->timestamp.ToInternalValue());
302  EXPECT_EQ(0, window->selected_tab_index);
303  ASSERT_EQ(1U, window->tabs[0].navigations.size());
304  EXPECT_EQ(0, window->tabs[0].current_navigation_index);
305  EXPECT_EQ(0, window->tabs[0].timestamp.ToInternalValue());
306  EXPECT_TRUE(url1_ == window->tabs[0].navigations[0].virtual_url());
307}
308
309// Makes sure we don't attempt to load previous sessions after a restore.
310TEST_F(TabRestoreServiceTest, DontLoadAfterRestore) {
311  CreateSessionServiceWithOneWindow(false);
312
313  profile()->GetSessionService()->MoveCurrentSessionToLastSession();
314
315  profile()->set_restored_last_session(true);
316
317  service_->LoadTabsFromLastSession();
318
319  // Because we restored a session TabRestoreService shouldn't load the tabs.
320  ASSERT_EQ(0U, service_->entries().size());
321}
322
323// Makes sure we don't attempt to load previous sessions after a clean exit.
324TEST_F(TabRestoreServiceTest, DontLoadAfterCleanExit) {
325  CreateSessionServiceWithOneWindow(false);
326
327  profile()->GetSessionService()->MoveCurrentSessionToLastSession();
328
329  profile()->set_last_session_exited_cleanly(true);
330
331  service_->LoadTabsFromLastSession();
332
333  ASSERT_EQ(0U, service_->entries().size());
334}
335
336TEST_F(TabRestoreServiceTest, LoadPreviousSessionAndTabs) {
337  CreateSessionServiceWithOneWindow(false);
338
339  profile()->GetSessionService()->MoveCurrentSessionToLastSession();
340
341  AddThreeNavigations();
342
343  service_->CreateHistoricalTab(&controller(), -1);
344
345  RecreateService();
346
347  // We should get back two entries, one from the previous session and one from
348  // the tab restore service. The previous session entry should be first.
349  ASSERT_EQ(2U, service_->entries().size());
350  // The first entry should come from the session service.
351  TabRestoreService::Entry* entry = service_->entries().front();
352  ASSERT_EQ(TabRestoreService::WINDOW, entry->type);
353  TabRestoreService::Window* window =
354      static_cast<TabRestoreService::Window*>(entry);
355  ASSERT_EQ(1U, window->tabs.size());
356  EXPECT_EQ(0, window->selected_tab_index);
357  EXPECT_EQ(0, window->timestamp.ToInternalValue());
358  ASSERT_EQ(1U, window->tabs[0].navigations.size());
359  EXPECT_EQ(0, window->tabs[0].current_navigation_index);
360  EXPECT_EQ(0, window->tabs[0].timestamp.ToInternalValue());
361  EXPECT_TRUE(url1_ == window->tabs[0].navigations[0].virtual_url());
362
363  // Then the closed tab.
364  entry = *(++service_->entries().begin());
365  ASSERT_EQ(TabRestoreService::TAB, entry->type);
366  TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
367  ASSERT_FALSE(tab->pinned);
368  ASSERT_EQ(3U, tab->navigations.size());
369  EXPECT_EQ(2, tab->current_navigation_index);
370  EXPECT_EQ(time_factory_->TimeNow().ToInternalValue(),
371            tab->timestamp.ToInternalValue());
372  EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
373  EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
374  EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
375}
376
377// Make sure pinned state is correctly loaded from session service.
378TEST_F(TabRestoreServiceTest, LoadPreviousSessionAndTabsPinned) {
379  CreateSessionServiceWithOneWindow(true);
380
381  profile()->GetSessionService()->MoveCurrentSessionToLastSession();
382
383  AddThreeNavigations();
384
385  service_->CreateHistoricalTab(&controller(), -1);
386
387  RecreateService();
388
389  // We should get back two entries, one from the previous session and one from
390  // the tab restore service. The previous session entry should be first.
391  ASSERT_EQ(2U, service_->entries().size());
392  // The first entry should come from the session service.
393  TabRestoreService::Entry* entry = service_->entries().front();
394  ASSERT_EQ(TabRestoreService::WINDOW, entry->type);
395  TabRestoreService::Window* window =
396      static_cast<TabRestoreService::Window*>(entry);
397  ASSERT_EQ(1U, window->tabs.size());
398  EXPECT_EQ(0, window->selected_tab_index);
399  EXPECT_TRUE(window->tabs[0].pinned);
400  ASSERT_EQ(1U, window->tabs[0].navigations.size());
401  EXPECT_EQ(0, window->tabs[0].current_navigation_index);
402  EXPECT_TRUE(url1_ == window->tabs[0].navigations[0].virtual_url());
403
404  // Then the closed tab.
405  entry = *(++service_->entries().begin());
406  ASSERT_EQ(TabRestoreService::TAB, entry->type);
407  TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
408  ASSERT_FALSE(tab->pinned);
409  ASSERT_EQ(3U, tab->navigations.size());
410  EXPECT_EQ(2, tab->current_navigation_index);
411  EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
412  EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
413  EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
414}
415
416// Creates TabRestoreService::kMaxEntries + 1 windows in the session service
417// and makes sure we only get back TabRestoreService::kMaxEntries on restore.
418TEST_F(TabRestoreServiceTest, ManyWindowsInSessionService) {
419  CreateSessionServiceWithOneWindow(false);
420
421  for (size_t i = 0; i < TabRestoreService::kMaxEntries; ++i)
422    AddWindowWithOneTabToSessionService(false);
423
424  profile()->GetSessionService()->MoveCurrentSessionToLastSession();
425
426  AddThreeNavigations();
427
428  service_->CreateHistoricalTab(&controller(), -1);
429
430  RecreateService();
431
432  // We should get back kMaxEntries entries. We added more, but
433  // TabRestoreService only allows up to kMaxEntries.
434  ASSERT_EQ(TabRestoreService::kMaxEntries, service_->entries().size());
435
436  // The first entry should come from the session service.
437  TabRestoreService::Entry* entry = service_->entries().front();
438  ASSERT_EQ(TabRestoreService::WINDOW, entry->type);
439  TabRestoreService::Window* window =
440      static_cast<TabRestoreService::Window*>(entry);
441  ASSERT_EQ(1U, window->tabs.size());
442  EXPECT_EQ(0, window->selected_tab_index);
443  EXPECT_EQ(0, window->timestamp.ToInternalValue());
444  ASSERT_EQ(1U, window->tabs[0].navigations.size());
445  EXPECT_EQ(0, window->tabs[0].current_navigation_index);
446  EXPECT_EQ(0, window->tabs[0].timestamp.ToInternalValue());
447  EXPECT_TRUE(url1_ == window->tabs[0].navigations[0].virtual_url());
448}
449
450// Makes sure we restore the time stamp correctly.
451TEST_F(TabRestoreServiceTest, TimestampSurvivesRestore) {
452  base::Time tab_timestamp(base::Time::FromInternalValue(123456789));
453
454  AddThreeNavigations();
455
456  // Have the service record the tab.
457  service_->CreateHistoricalTab(&controller(), -1);
458
459  // Make sure an entry was created.
460  ASSERT_EQ(1U, service_->entries().size());
461
462  // Make sure the entry matches.
463  TabRestoreService::Entry* entry = service_->entries().front();
464  ASSERT_EQ(TabRestoreService::TAB, entry->type);
465  TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
466  tab->timestamp = tab_timestamp;
467
468  // Set this, otherwise previous session won't be loaded.
469  profile()->set_last_session_exited_cleanly(false);
470
471  RecreateService();
472
473  // One entry should be created.
474  ASSERT_EQ(1U, service_->entries().size());
475
476  // And verify the entry.
477  TabRestoreService::Entry* restored_entry = service_->entries().front();
478  ASSERT_EQ(TabRestoreService::TAB, restored_entry->type);
479  TabRestoreService::Tab* restored_tab =
480      static_cast<TabRestoreService::Tab*>(restored_entry);
481  EXPECT_EQ(tab_timestamp.ToInternalValue(),
482            restored_tab->timestamp.ToInternalValue());
483}
484