blob: 46f18597661051d66c73904d66b17d3ad952f923 [file] [log] [blame]
[email protected]169627b2008-12-06 19:30:191// Copyright (c) 2006-2008 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
[email protected]807204142009-05-05 03:31:445#include "chrome/browser/sessions/tab_restore_service.h"
6
[email protected]169627b2008-12-06 19:30:197#include <algorithm>
8#include <iterator>
[email protected]3ca7e002009-09-16 19:52:049#include <map>
[email protected]169627b2008-12-06 19:30:1910
[email protected]2041cf342010-02-19 03:15:5911#include "base/callback.h"
[email protected]807204142009-05-05 03:31:4412#include "base/scoped_vector.h"
13#include "base/stl_util-inl.h"
[email protected]169627b2008-12-06 19:30:1914#include "chrome/browser/browser_list.h"
[email protected]ce560f82009-06-03 09:39:4415#include "chrome/browser/browser_window.h"
[email protected]169627b2008-12-06 19:30:1916#include "chrome/browser/profile.h"
[email protected]ea6f76572008-12-18 00:09:5517#include "chrome/browser/sessions/session_service.h"
[email protected]bd580a252009-02-12 01:16:3018#include "chrome/browser/sessions/session_command.h"
[email protected]9c92d192009-12-02 08:03:1619#include "chrome/browser/sessions/session_types.h"
[email protected]f3ec7742009-01-15 00:59:1620#include "chrome/browser/tab_contents/navigation_controller.h"
21#include "chrome/browser/tab_contents/navigation_entry.h"
[email protected]5c238752009-06-13 10:29:0722#include "chrome/browser/tab_contents/tab_contents.h"
[email protected]fca656c2010-02-10 20:30:1023#include "chrome/common/extensions/extension.h"
[email protected]169627b2008-12-06 19:30:1924
25using base::Time;
26
27// Entry ----------------------------------------------------------------------
28
29// ID of the next Entry.
30static SessionID::id_type next_entry_id = 1;
31
[email protected]5e369672009-11-03 23:48:3032TabRestoreService::Entry::Entry()
33 : id(next_entry_id++),
34 type(TAB),
35 from_last_session(false) {}
[email protected]169627b2008-12-06 19:30:1936
[email protected]5e369672009-11-03 23:48:3037TabRestoreService::Entry::Entry(Type type)
38 : id(next_entry_id++),
39 type(type),
40 from_last_session(false) {}
[email protected]169627b2008-12-06 19:30:1941
42// TabRestoreService ----------------------------------------------------------
43
[email protected]ea6f76572008-12-18 00:09:5544// static
45const size_t TabRestoreService::kMaxEntries = 10;
[email protected]169627b2008-12-06 19:30:1946
47// Identifier for commands written to file.
48// The ordering in the file is as follows:
[email protected]f0a51fb52009-03-05 12:46:3849// . When the user closes a tab a command of type
[email protected]169627b2008-12-06 19:30:1950// kCommandSelectedNavigationInTab is written identifying the tab and
[email protected]5c0e6482009-07-14 20:20:0951// the selected index, then a kCommandPinnedState command if the tab was
[email protected]98aa0b52010-05-06 17:03:0852// pinned and kCommandSetExtensionAppID if the tab has an app id. This is
[email protected]fca656c2010-02-10 20:30:1053// followed by any number of kCommandUpdateTabNavigation commands (1 per
54// navigation entry).
[email protected]169627b2008-12-06 19:30:1955// . When the user closes a window a kCommandSelectedNavigationInTab command
56// is written out and followed by n tab closed sequences (as previoulsy
57// described).
58// . When the user restores an entry a command of type kCommandRestoredEntry
59// is written.
60static const SessionCommand::id_type kCommandUpdateTabNavigation = 1;
61static const SessionCommand::id_type kCommandRestoredEntry = 2;
62static const SessionCommand::id_type kCommandWindow = 3;
63static const SessionCommand::id_type kCommandSelectedNavigationInTab = 4;
[email protected]5c0e6482009-07-14 20:20:0964static const SessionCommand::id_type kCommandPinnedState = 5;
[email protected]98aa0b52010-05-06 17:03:0865static const SessionCommand::id_type kCommandSetExtensionAppID = 6;
[email protected]169627b2008-12-06 19:30:1966
67// Number of entries (not commands) before we clobber the file and write
68// everything.
69static const int kEntriesPerReset = 40;
70
71namespace {
72
73// Payload structures.
74
75typedef int32 RestoredEntryPayload;
76
[email protected]f4d5b1f2009-07-20 20:28:3777// Payload used for the start of a window close. This is the old struct that is
[email protected]3ca7e002009-09-16 19:52:0478// used for backwards compat when it comes to reading the session files. This
79// struct must be POD, because we memset the contents.
[email protected]169627b2008-12-06 19:30:1980struct WindowPayload {
81 SessionID::id_type window_id;
82 int32 selected_tab_index;
83 int32 num_tabs;
84};
85
[email protected]f4d5b1f2009-07-20 20:28:3786// Payload used for the start of a tab close. This is the old struct that is
87// used for backwards compat when it comes to reading the session files.
[email protected]169627b2008-12-06 19:30:1988struct SelectedNavigationInTabPayload {
89 SessionID::id_type id;
90 int32 index;
91};
92
[email protected]3ca7e002009-09-16 19:52:0493// Payload used for the start of a window close. This struct must be POD,
94// because we memset the contents.
[email protected]f4d5b1f2009-07-20 20:28:3795struct WindowPayload2 : WindowPayload {
96 int64 timestamp;
97};
98
99// Payload used for the start of a tab close.
100struct SelectedNavigationInTabPayload2 : SelectedNavigationInTabPayload {
101 int64 timestamp;
102};
103
[email protected]5c0e6482009-07-14 20:20:09104// Only written if the tab is pinned.
105typedef bool PinnedStatePayload;
106
[email protected]169627b2008-12-06 19:30:19107typedef std::map<SessionID::id_type, TabRestoreService::Entry*> IDToEntry;
108
109// If |id_to_entry| contains an entry for |id| the corresponding entry is
110// deleted and removed from both |id_to_entry| and |entries|. This is used
111// when creating entries from the backend file.
112void RemoveEntryByID(SessionID::id_type id,
113 IDToEntry* id_to_entry,
114 std::vector<TabRestoreService::Entry*>* entries) {
[email protected]f74d4452010-06-18 17:12:27115 // Look for the entry in the map. If it is present, erase it from both
116 // collections and return.
[email protected]169627b2008-12-06 19:30:19117 IDToEntry::iterator i = id_to_entry->find(id);
[email protected]f74d4452010-06-18 17:12:27118 if (i != id_to_entry->end()) {
119 entries->erase(std::find(entries->begin(), entries->end(), i->second));
120 delete i->second;
121 id_to_entry->erase(i);
[email protected]169627b2008-12-06 19:30:19122 return;
[email protected]f74d4452010-06-18 17:12:27123 }
[email protected]169627b2008-12-06 19:30:19124
[email protected]f74d4452010-06-18 17:12:27125 // Otherwise, loop over all items in the map and see if any of the Windows
126 // have Tabs with the |id|.
127 for (IDToEntry::iterator i = id_to_entry->begin(); i != id_to_entry->end();
128 ++i) {
129 if (i->second->type == TabRestoreService::WINDOW) {
130 TabRestoreService::Window* window =
131 static_cast<TabRestoreService::Window*>(i->second);
132 std::vector<TabRestoreService::Tab>::iterator j = window->tabs.begin();
133 for ( ; j != window->tabs.end(); ++j) {
134 // If the ID matches one of this window's tabs, remove it from the list.
135 if ((*j).id == id) {
136 window->tabs.erase(j);
137 return;
138 }
139 }
140 }
141 }
[email protected]169627b2008-12-06 19:30:19142}
143
144} // namespace
145
[email protected]9c92d192009-12-02 08:03:16146TabRestoreService::Tab::Tab()
147 : Entry(TAB),
148 current_navigation_index(-1),
149 browser_id(0),
150 tabstrip_index(-1),
151 pinned(false) {
152}
153
154TabRestoreService::Window::Window() : Entry(WINDOW), selected_tab_index(-1) {
155}
156
[email protected]f4d5b1f2009-07-20 20:28:37157TabRestoreService::TabRestoreService(Profile* profile,
158 TabRestoreService::TimeFactory* time_factory)
[email protected]169627b2008-12-06 19:30:19159 : BaseSessionService(BaseSessionService::TAB_RESTORE, profile,
[email protected]5a82010a2009-02-14 01:33:02160 FilePath()),
[email protected]ea6f76572008-12-18 00:09:55161 load_state_(NOT_LOADED),
[email protected]169627b2008-12-06 19:30:19162 restoring_(false),
163 reached_max_(false),
164 entries_to_write_(0),
[email protected]f4d5b1f2009-07-20 20:28:37165 entries_written_(0),
166 time_factory_(time_factory) {
[email protected]169627b2008-12-06 19:30:19167}
168
169TabRestoreService::~TabRestoreService() {
170 if (backend())
171 Save();
172
173 FOR_EACH_OBSERVER(Observer, observer_list_, TabRestoreServiceDestroyed(this));
174 STLDeleteElements(&entries_);
[email protected]ea6f76572008-12-18 00:09:55175 STLDeleteElements(&staging_entries_);
[email protected]f4d5b1f2009-07-20 20:28:37176 time_factory_ = NULL;
[email protected]169627b2008-12-06 19:30:19177}
178
179void TabRestoreService::AddObserver(Observer* observer) {
180 observer_list_.AddObserver(observer);
181}
182
183void TabRestoreService::RemoveObserver(Observer* observer) {
184 observer_list_.RemoveObserver(observer);
185}
186
187void TabRestoreService::CreateHistoricalTab(NavigationController* tab) {
188 if (restoring_)
189 return;
190
191 Browser* browser = Browser::GetBrowserForController(tab, NULL);
192 if (closing_browsers_.find(browser) != closing_browsers_.end())
193 return;
194
195 scoped_ptr<Tab> local_tab(new Tab());
[email protected]c714a162009-04-22 20:03:08196 PopulateTab(local_tab.get(), browser, tab);
[email protected]169627b2008-12-06 19:30:19197 if (local_tab->navigations.empty())
198 return;
199
200 AddEntry(local_tab.release(), true, true);
201}
202
203void TabRestoreService::BrowserClosing(Browser* browser) {
204 if (browser->type() != Browser::TYPE_NORMAL ||
205 browser->tab_count() == 0)
206 return;
207
208 closing_browsers_.insert(browser);
209
210 Window* window = new Window();
211 window->selected_tab_index = browser->selected_index();
[email protected]f4d5b1f2009-07-20 20:28:37212 window->timestamp = TimeNow();
[email protected]40058d42010-07-08 14:35:27213 // Don't use std::vector::resize() because it will push copies of an empty tab
214 // into the vector, which will give all tabs in a window the same ID.
215 for (int i = 0; i < browser->tab_count(); ++i) {
216 window->tabs.push_back(Tab());
217 }
[email protected]169627b2008-12-06 19:30:19218 size_t entry_index = 0;
219 for (int tab_index = 0; tab_index < browser->tab_count(); ++tab_index) {
[email protected]c714a162009-04-22 20:03:08220 PopulateTab(&(window->tabs[entry_index]),
221 browser,
222 &browser->GetTabContentsAt(tab_index)->controller());
223 if (window->tabs[entry_index].navigations.empty()) {
[email protected]169627b2008-12-06 19:30:19224 window->tabs.erase(window->tabs.begin() + entry_index);
[email protected]c714a162009-04-22 20:03:08225 } else {
226 window->tabs[entry_index].browser_id = browser->session_id().id();
[email protected]169627b2008-12-06 19:30:19227 entry_index++;
[email protected]c714a162009-04-22 20:03:08228 }
[email protected]169627b2008-12-06 19:30:19229 }
230 if (window->tabs.empty()) {
231 delete window;
232 window = NULL;
233 } else {
[email protected]7dc39842009-01-26 18:32:54234 window->selected_tab_index =
235 std::min(static_cast<int>(window->tabs.size() - 1),
236 window->selected_tab_index);
[email protected]169627b2008-12-06 19:30:19237 AddEntry(window, true, true);
238 }
239}
240
241void TabRestoreService::BrowserClosed(Browser* browser) {
242 closing_browsers_.erase(browser);
243}
244
245void TabRestoreService::ClearEntries() {
246 // Mark all the tabs as closed so that we don't attempt to restore them.
247 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i)
248 ScheduleCommand(CreateRestoredEntryCommand((*i)->id));
249
250 entries_to_write_ = 0;
251
252 // Schedule a pending reset so that we nuke the file on next write.
253 set_pending_reset(true);
254
255 // Schedule a command, otherwise if there are no pending commands Save does
256 // nothing.
257 ScheduleCommand(CreateRestoredEntryCommand(1));
258
259 STLDeleteElements(&entries_);
260 NotifyTabsChanged();
261}
262
263void TabRestoreService::RestoreMostRecentEntry(Browser* browser) {
264 if (entries_.empty())
265 return;
266
267 RestoreEntryById(browser, entries_.front()->id, false);
268}
269
270void TabRestoreService::RestoreEntryById(Browser* browser,
271 SessionID::id_type id,
272 bool replace_existing_tab) {
273 Entries::iterator i = GetEntryIteratorById(id);
274 if (i == entries_.end()) {
275 // Don't hoark here, we allow an invalid id.
276 return;
277 }
278
279 size_t index = 0;
280 for (Entries::iterator j = entries_.begin(); j != i && j != entries_.end();
[email protected]f07467d2010-06-16 14:28:30281 ++j, ++index) {}
[email protected]169627b2008-12-06 19:30:19282 if (static_cast<int>(index) < entries_to_write_)
283 entries_to_write_--;
284
285 ScheduleCommand(CreateRestoredEntryCommand(id));
286
287 restoring_ = true;
288 Entry* entry = *i;
[email protected]f74d4452010-06-18 17:12:27289
290 // If the entry's ID does not match the ID that is being restored, then the
291 // entry is a window from which a single tab will be restored.
292 bool restoring_tab_in_window = entry->id != id;
293
294 if (!restoring_tab_in_window) {
295 entries_.erase(i);
296 i = entries_.end();
297 }
[email protected]c714a162009-04-22 20:03:08298
[email protected]fbc947b2009-06-19 13:28:24299 // |browser| will be NULL in cases where one isn't already available (eg,
300 // when invoked on Mac OS X with no windows open). In this case, create a
301 // new browser into which we restore the tabs.
302 if (entry->type == TAB) {
303 Tab* tab = static_cast<Tab*>(entry);
[email protected]f74d4452010-06-18 17:12:27304 browser = RestoreTab(*tab, browser, replace_existing_tab);
[email protected]fbc947b2009-06-19 13:28:24305 } else if (entry->type == WINDOW) {
[email protected]e1f8c2f2009-11-05 22:31:23306 Browser* current_browser = browser;
[email protected]f74d4452010-06-18 17:12:27307 Window* window = static_cast<Window*>(entry);
308
309 // When restoring a window, either the entire window can be restored, or a
310 // single tab within it. If the entry's ID matches the one to restore, then
311 // the entire window will be restored.
312 if (!restoring_tab_in_window) {
313 browser = Browser::Create(profile());
314 for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) {
315 const Tab& tab = window->tabs[tab_i];
316 TabContents* restored_tab =
317 browser->AddRestoredTab(tab.navigations, browser->tab_count(),
318 tab.current_navigation_index,
319 tab.extension_app_id,
320 (static_cast<int>(tab_i) ==
321 window->selected_tab_index),
322 tab.pinned, tab.from_last_session);
323 if (restored_tab)
324 restored_tab->controller().LoadIfNecessary();
325 }
326 // All the window's tabs had the same former browser_id.
327 if (window->tabs[0].has_browser()) {
328 UpdateTabBrowserIDs(window->tabs[0].browser_id,
329 browser->session_id().id());
330 }
331 } else {
332 // Restore a single tab from the window. Find the tab that matches the ID
333 // in the window and restore it.
334 for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
335 tab_i != window->tabs.end(); ++tab_i) {
336 const Tab& tab = *tab_i;
337 if (tab.id == id) {
338 browser = RestoreTab(tab, browser, replace_existing_tab);
339 window->tabs.erase(tab_i);
340 // If restoring the tab leaves the window with nothing else, delete it
341 // as well.
342 if (!window->tabs.size()) {
343 entries_.erase(i);
344 delete entry;
345 } else {
346 // Update the browser ID of the rest of the tabs in the window so if
347 // any one is restored, it goes into the same window as the tab
348 // being restored now.
[email protected]40058d42010-07-08 14:35:27349 UpdateTabBrowserIDs(tab.browser_id,
350 browser->session_id().id());
[email protected]f74d4452010-06-18 17:12:27351 for (std::vector<Tab>::iterator tab_j = window->tabs.begin();
352 tab_j != window->tabs.end(); ++tab_j) {
[email protected]40058d42010-07-08 14:35:27353 (*tab_j).browser_id = browser->session_id().id();
[email protected]f74d4452010-06-18 17:12:27354 }
355 }
356 break;
357 }
358 }
[email protected]fbc947b2009-06-19 13:28:24359 }
360 browser->window()->Show();
[email protected]e1f8c2f2009-11-05 22:31:23361
362 if (replace_existing_tab && current_browser &&
363 current_browser->GetSelectedTabContents()) {
364 current_browser->CloseTab();
365 }
[email protected]fbc947b2009-06-19 13:28:24366 } else {
367 NOTREACHED();
[email protected]169627b2008-12-06 19:30:19368 }
[email protected]fbc947b2009-06-19 13:28:24369
[email protected]f74d4452010-06-18 17:12:27370 if (!restoring_tab_in_window) {
371 delete entry;
372 }
373
[email protected]169627b2008-12-06 19:30:19374 restoring_ = false;
375 NotifyTabsChanged();
376}
377
378void TabRestoreService::LoadTabsFromLastSession() {
[email protected]ea6f76572008-12-18 00:09:55379 if (load_state_ != NOT_LOADED || reached_max_)
[email protected]169627b2008-12-06 19:30:19380 return;
381
[email protected]ea6f76572008-12-18 00:09:55382 load_state_ = LOADING;
[email protected]169627b2008-12-06 19:30:19383
[email protected]ea6f76572008-12-18 00:09:55384 if (!profile()->restored_last_session() &&
385 !profile()->DidLastSessionExitCleanly() &&
386 profile()->GetSessionService()) {
387 // The previous session crashed and wasn't restored. Load the tabs/windows
388 // that were open at the point of crash from the session service.
389 profile()->GetSessionService()->GetLastSession(
390 &load_consumer_,
391 NewCallback(this, &TabRestoreService::OnGotPreviousSession));
392 } else {
393 load_state_ |= LOADED_LAST_SESSION;
394 }
395
396 // Request the tabs closed in the last session. If the last session crashed,
397 // this won't contain the tabs/window that were open at the point of the
398 // crash (the call to GetLastSession above requests those).
[email protected]169627b2008-12-06 19:30:19399 ScheduleGetLastSessionCommands(
400 new InternalGetCommandsRequest(
401 NewCallback(this, &TabRestoreService::OnGotLastSessionCommands)),
[email protected]ea6f76572008-12-18 00:09:55402 &load_consumer_);
[email protected]169627b2008-12-06 19:30:19403}
404
405void TabRestoreService::Save() {
406 int to_write_count = std::min(entries_to_write_,
407 static_cast<int>(entries_.size()));
408 entries_to_write_ = 0;
409 if (entries_written_ + to_write_count > kEntriesPerReset) {
410 to_write_count = entries_.size();
411 set_pending_reset(true);
412 }
413 if (to_write_count) {
414 // Write the to_write_count most recently added entries out. The most
415 // recently added entry is at the front, so we use a reverse iterator to
416 // write in the order the entries were added.
417 Entries::reverse_iterator i = entries_.rbegin();
418 DCHECK(static_cast<size_t>(to_write_count) <= entries_.size());
419 std::advance(i, entries_.size() - static_cast<int>(to_write_count));
420 for (; i != entries_.rend(); ++i) {
421 Entry* entry = *i;
422 if (entry->type == TAB) {
423 Tab* tab = static_cast<Tab*>(entry);
424 int selected_index = GetSelectedNavigationIndexToPersist(*tab);
425 if (selected_index != -1)
426 ScheduleCommandsForTab(*tab, selected_index);
427 } else {
428 ScheduleCommandsForWindow(*static_cast<Window*>(entry));
429 }
430 entries_written_++;
431 }
432 }
433 if (pending_reset())
434 entries_written_ = 0;
435 BaseSessionService::Save();
436}
437
[email protected]c714a162009-04-22 20:03:08438void TabRestoreService::PopulateTab(Tab* tab,
439 Browser* browser,
440 NavigationController* controller) {
[email protected]7f0005a2009-04-15 03:25:11441 const int pending_index = controller->pending_entry_index();
442 int entry_count = controller->entry_count();
[email protected]169627b2008-12-06 19:30:19443 if (entry_count == 0 && pending_index == 0)
444 entry_count++;
445 tab->navigations.resize(static_cast<int>(entry_count));
446 for (int i = 0; i < entry_count; ++i) {
447 NavigationEntry* entry = (i == pending_index) ?
[email protected]7f0005a2009-04-15 03:25:11448 controller->pending_entry() : controller->GetEntryAtIndex(i);
[email protected]169627b2008-12-06 19:30:19449 tab->navigations[i].SetFromNavigationEntry(*entry);
450 }
[email protected]f4d5b1f2009-07-20 20:28:37451 tab->timestamp = TimeNow();
[email protected]169627b2008-12-06 19:30:19452 tab->current_navigation_index = controller->GetCurrentEntryIndex();
453 if (tab->current_navigation_index == -1 && entry_count > 0)
454 tab->current_navigation_index = 0;
[email protected]c714a162009-04-22 20:03:08455
[email protected]98aa0b52010-05-06 17:03:08456 Extension* extension = controller->tab_contents()->extension_app();
[email protected]fca656c2010-02-10 20:30:10457 if (extension)
[email protected]98aa0b52010-05-06 17:03:08458 tab->extension_app_id = extension->id();
[email protected]fca656c2010-02-10 20:30:10459
[email protected]c714a162009-04-22 20:03:08460 // Browser may be NULL during unit tests.
461 if (browser) {
462 tab->browser_id = browser->session_id().id();
[email protected]902cdf772009-05-06 15:08:12463 tab->tabstrip_index =
464 browser->tabstrip_model()->GetIndexOfController(controller);
[email protected]5c0e6482009-07-14 20:20:09465 tab->pinned = browser->tabstrip_model()->IsTabPinned(tab->tabstrip_index);
[email protected]c714a162009-04-22 20:03:08466 }
[email protected]169627b2008-12-06 19:30:19467}
468
469void TabRestoreService::NotifyTabsChanged() {
470 FOR_EACH_OBSERVER(Observer, observer_list_, TabRestoreServiceChanged(this));
471}
472
473void TabRestoreService::AddEntry(Entry* entry, bool notify, bool to_front) {
474 if (to_front)
475 entries_.push_front(entry);
476 else
477 entries_.push_back(entry);
478 if (notify)
479 PruneAndNotify();
480 // Start the save timer, when it fires we'll generate the commands.
481 StartSaveTimer();
482 entries_to_write_++;
483}
484
485void TabRestoreService::PruneAndNotify() {
486 while (entries_.size() > kMaxEntries) {
487 delete entries_.back();
488 entries_.pop_back();
489 reached_max_ = true;
490 }
491
492 NotifyTabsChanged();
493}
494
495TabRestoreService::Entries::iterator TabRestoreService::GetEntryIteratorById(
496 SessionID::id_type id) {
497 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
498 if ((*i)->id == id)
499 return i;
[email protected]f74d4452010-06-18 17:12:27500
501 // For Window entries, see if the ID matches a tab. If so, report the window
502 // as the Entry.
503 if ((*i)->type == WINDOW) {
504 std::vector<Tab>& tabs = static_cast<Window*>(*i)->tabs;
505 for (std::vector<Tab>::iterator j = tabs.begin();
506 j != tabs.end(); ++j) {
507 if ((*j).id == id) {
508 return i;
509 }
510 }
511 }
[email protected]169627b2008-12-06 19:30:19512 }
513 return entries_.end();
514}
515
516void TabRestoreService::ScheduleCommandsForWindow(const Window& window) {
517 DCHECK(!window.tabs.empty());
518 int selected_tab = window.selected_tab_index;
519 int valid_tab_count = 0;
520 int real_selected_tab = selected_tab;
521 for (size_t i = 0; i < window.tabs.size(); ++i) {
522 if (GetSelectedNavigationIndexToPersist(window.tabs[i]) != -1) {
523 valid_tab_count++;
524 } else if (static_cast<int>(i) < selected_tab) {
525 real_selected_tab--;
526 }
527 }
528 if (valid_tab_count == 0)
529 return; // No tabs to persist.
530
531 ScheduleCommand(
532 CreateWindowCommand(window.id,
533 std::min(real_selected_tab, valid_tab_count - 1),
[email protected]f4d5b1f2009-07-20 20:28:37534 valid_tab_count,
535 window.timestamp));
[email protected]169627b2008-12-06 19:30:19536
537 for (size_t i = 0; i < window.tabs.size(); ++i) {
538 int selected_index = GetSelectedNavigationIndexToPersist(window.tabs[i]);
539 if (selected_index != -1)
540 ScheduleCommandsForTab(window.tabs[i], selected_index);
541 }
542}
543
544void TabRestoreService::ScheduleCommandsForTab(const Tab& tab,
545 int selected_index) {
546 const std::vector<TabNavigation>& navigations = tab.navigations;
547 int max_index = static_cast<int>(navigations.size());
548
549 // Determine the first navigation we'll persist.
550 int valid_count_before_selected = 0;
551 int first_index_to_persist = selected_index;
552 for (int i = selected_index - 1; i >= 0 &&
553 valid_count_before_selected < max_persist_navigation_count; --i) {
554 if (ShouldTrackEntry(navigations[i])) {
555 first_index_to_persist = i;
556 valid_count_before_selected++;
557 }
558 }
559
560 // Write the command that identifies the selected tab.
561 ScheduleCommand(
562 CreateSelectedNavigationInTabCommand(tab.id,
[email protected]f4d5b1f2009-07-20 20:28:37563 valid_count_before_selected,
564 tab.timestamp));
[email protected]169627b2008-12-06 19:30:19565
[email protected]5c0e6482009-07-14 20:20:09566 if (tab.pinned) {
567 PinnedStatePayload payload = true;
568 SessionCommand* command =
569 new SessionCommand(kCommandPinnedState, sizeof(payload));
570 memcpy(command->contents(), &payload, sizeof(payload));
571 ScheduleCommand(command);
572 }
573
[email protected]98aa0b52010-05-06 17:03:08574 if (!tab.extension_app_id.empty()) {
[email protected]fca656c2010-02-10 20:30:10575 ScheduleCommand(
[email protected]98aa0b52010-05-06 17:03:08576 CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID, tab.id,
577 tab.extension_app_id));
[email protected]fca656c2010-02-10 20:30:10578 }
579
[email protected]169627b2008-12-06 19:30:19580 // Then write the navigations.
581 for (int i = first_index_to_persist, wrote_count = 0;
582 i < max_index && wrote_count < 2 * max_persist_navigation_count; ++i) {
583 if (ShouldTrackEntry(navigations[i])) {
584 // Creating a NavigationEntry isn't the most efficient way to go about
585 // this, but it simplifies the code and makes it less error prone as we
586 // add new data to NavigationEntry.
587 scoped_ptr<NavigationEntry> entry(
[email protected]b6ea7412010-05-04 23:26:47588 navigations[i].ToNavigationEntry(wrote_count, profile()));
[email protected]169627b2008-12-06 19:30:19589 ScheduleCommand(
590 CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, tab.id,
591 wrote_count++, *entry));
592 }
593 }
594}
595
596SessionCommand* TabRestoreService::CreateWindowCommand(SessionID::id_type id,
597 int selected_tab_index,
[email protected]f4d5b1f2009-07-20 20:28:37598 int num_tabs,
599 Time timestamp) {
600 WindowPayload2 payload;
[email protected]3ca7e002009-09-16 19:52:04601 // |timestamp| is aligned on a 16 byte boundary, leaving 4 bytes of
602 // uninitialized memory in the struct.
603 memset(&payload, 0, sizeof(payload));
[email protected]169627b2008-12-06 19:30:19604 payload.window_id = id;
605 payload.selected_tab_index = selected_tab_index;
606 payload.num_tabs = num_tabs;
[email protected]f4d5b1f2009-07-20 20:28:37607 payload.timestamp = timestamp.ToInternalValue();
[email protected]169627b2008-12-06 19:30:19608
609 SessionCommand* command =
610 new SessionCommand(kCommandWindow, sizeof(payload));
611 memcpy(command->contents(), &payload, sizeof(payload));
612 return command;
613}
614
615SessionCommand* TabRestoreService::CreateSelectedNavigationInTabCommand(
616 SessionID::id_type tab_id,
[email protected]f4d5b1f2009-07-20 20:28:37617 int32 index,
618 Time timestamp) {
619 SelectedNavigationInTabPayload2 payload;
[email protected]169627b2008-12-06 19:30:19620 payload.id = tab_id;
621 payload.index = index;
[email protected]f4d5b1f2009-07-20 20:28:37622 payload.timestamp = timestamp.ToInternalValue();
[email protected]169627b2008-12-06 19:30:19623 SessionCommand* command =
624 new SessionCommand(kCommandSelectedNavigationInTab, sizeof(payload));
625 memcpy(command->contents(), &payload, sizeof(payload));
626 return command;
627}
628
629SessionCommand* TabRestoreService::CreateRestoredEntryCommand(
630 SessionID::id_type entry_id) {
631 RestoredEntryPayload payload = entry_id;
632 SessionCommand* command =
633 new SessionCommand(kCommandRestoredEntry, sizeof(payload));
634 memcpy(command->contents(), &payload, sizeof(payload));
635 return command;
636}
637
638int TabRestoreService::GetSelectedNavigationIndexToPersist(const Tab& tab) {
639 const std::vector<TabNavigation>& navigations = tab.navigations;
640 int selected_index = tab.current_navigation_index;
641 int max_index = static_cast<int>(navigations.size());
642
643 // Find the first navigation to persist. We won't persist the selected
644 // navigation if ShouldTrackEntry returns false.
645 while (selected_index >= 0 &&
646 !ShouldTrackEntry(navigations[selected_index])) {
647 selected_index--;
648 }
649
650 if (selected_index != -1)
651 return selected_index;
652
653 // Couldn't find a navigation to persist going back, go forward.
654 selected_index = tab.current_navigation_index + 1;
655 while (selected_index < max_index &&
656 !ShouldTrackEntry(navigations[selected_index])) {
657 selected_index++;
658 }
659
660 return (selected_index == max_index) ? -1 : selected_index;
661}
662
663void TabRestoreService::OnGotLastSessionCommands(
664 Handle handle,
665 scoped_refptr<InternalGetCommandsRequest> request) {
[email protected]ea6f76572008-12-18 00:09:55666 std::vector<Entry*> entries;
667 CreateEntriesFromCommands(request, &entries);
668 // Closed tabs always go to the end.
669 staging_entries_.insert(staging_entries_.end(), entries.begin(),
670 entries.end());
671 load_state_ |= LOADED_LAST_TABS;
672 LoadStateChanged();
673}
674
675void TabRestoreService::CreateEntriesFromCommands(
676 scoped_refptr<InternalGetCommandsRequest> request,
677 std::vector<Entry*>* loaded_entries) {
[email protected]169627b2008-12-06 19:30:19678 if (request->canceled() || entries_.size() == kMaxEntries)
679 return;
680
681 std::vector<SessionCommand*>& commands = request->commands;
682 // Iterate through the commands populating entries and id_to_entry.
683 ScopedVector<Entry> entries;
684 IDToEntry id_to_entry;
685 // If non-null we're processing the navigations of this tab.
686 Tab* current_tab = NULL;
687 // If non-null we're processing the tabs of this window.
688 Window* current_window = NULL;
689 // If > 0, we've gotten a window command but not all the tabs yet.
690 int pending_window_tabs = 0;
691 for (std::vector<SessionCommand*>::const_iterator i = commands.begin();
692 i != commands.end(); ++i) {
693 const SessionCommand& command = *(*i);
694 switch (command.id()) {
695 case kCommandRestoredEntry: {
696 if (pending_window_tabs > 0) {
697 // Should never receive a restored command while waiting for all the
698 // tabs in a window.
699 return;
700 }
701
702 current_tab = NULL;
703 current_window = NULL;
704
705 RestoredEntryPayload payload;
706 if (!command.GetPayload(&payload, sizeof(payload)))
707 return;
708 RemoveEntryByID(payload, &id_to_entry, &(entries.get()));
709 break;
710 }
711
712 case kCommandWindow: {
[email protected]f4d5b1f2009-07-20 20:28:37713 WindowPayload2 payload;
[email protected]169627b2008-12-06 19:30:19714 if (pending_window_tabs > 0) {
715 // Should never receive a window command while waiting for all the
716 // tabs in a window.
717 return;
718 }
719
[email protected]f4d5b1f2009-07-20 20:28:37720 // Try the new payload first
721 if (!command.GetPayload(&payload, sizeof(payload))) {
722 // then the old payload
723 WindowPayload old_payload;
724 if (!command.GetPayload(&old_payload, sizeof(old_payload)))
725 return;
726
727 // Copy the old payload data to the new payload.
728 payload.window_id = old_payload.window_id;
729 payload.selected_tab_index = old_payload.selected_tab_index;
730 payload.num_tabs = old_payload.num_tabs;
731 // Since we don't have a time use time 0 which is used to mark as an
732 // unknown timestamp.
733 payload.timestamp = 0;
734 }
[email protected]169627b2008-12-06 19:30:19735
736 pending_window_tabs = payload.num_tabs;
737 if (pending_window_tabs <= 0) {
738 // Should always have at least 1 tab. Likely indicates corruption.
739 return;
740 }
741
742 RemoveEntryByID(payload.window_id, &id_to_entry, &(entries.get()));
743
744 current_window = new Window();
745 current_window->selected_tab_index = payload.selected_tab_index;
[email protected]f4d5b1f2009-07-20 20:28:37746 current_window->timestamp = Time::FromInternalValue(payload.timestamp);
[email protected]169627b2008-12-06 19:30:19747 entries->push_back(current_window);
748 id_to_entry[payload.window_id] = current_window;
749 break;
750 }
751
752 case kCommandSelectedNavigationInTab: {
[email protected]f4d5b1f2009-07-20 20:28:37753 SelectedNavigationInTabPayload2 payload;
754 if (!command.GetPayload(&payload, sizeof(payload))) {
755 SelectedNavigationInTabPayload old_payload;
756 if (!command.GetPayload(&old_payload, sizeof(old_payload)))
757 return;
758 payload.id = old_payload.id;
759 payload.index = old_payload.index;
760 // Since we don't have a time use time 0 which is used to mark as an
761 // unknown timestamp.
762 payload.timestamp = 0;
763 }
[email protected]169627b2008-12-06 19:30:19764
765 if (pending_window_tabs > 0) {
766 if (!current_window) {
767 // We should have created a window already.
768 NOTREACHED();
769 return;
770 }
771 current_window->tabs.resize(current_window->tabs.size() + 1);
772 current_tab = &(current_window->tabs.back());
773 if (--pending_window_tabs == 0)
774 current_window = NULL;
775 } else {
776 RemoveEntryByID(payload.id, &id_to_entry, &(entries.get()));
777 current_tab = new Tab();
778 id_to_entry[payload.id] = current_tab;
[email protected]f4d5b1f2009-07-20 20:28:37779 current_tab->timestamp = Time::FromInternalValue(payload.timestamp);
[email protected]169627b2008-12-06 19:30:19780 entries->push_back(current_tab);
781 }
782 current_tab->current_navigation_index = payload.index;
783 break;
784 }
785
786 case kCommandUpdateTabNavigation: {
787 if (!current_tab) {
788 // Should be in a tab when we get this.
789 return;
790 }
791 current_tab->navigations.resize(current_tab->navigations.size() + 1);
792 SessionID::id_type tab_id;
793 if (!RestoreUpdateTabNavigationCommand(
794 command, &current_tab->navigations.back(), &tab_id)) {
795 return;
796 }
797 break;
798 }
799
[email protected]5c0e6482009-07-14 20:20:09800 case kCommandPinnedState: {
801 if (!current_tab) {
802 // Should be in a tab when we get this.
803 return;
804 }
805 // NOTE: payload doesn't matter. kCommandPinnedState is only written if
806 // tab is pinned.
807 current_tab->pinned = true;
808 break;
809 }
810
[email protected]98aa0b52010-05-06 17:03:08811 case kCommandSetExtensionAppID: {
[email protected]fca656c2010-02-10 20:30:10812 if (!current_tab) {
813 // Should be in a tab when we get this.
814 return;
815 }
816 SessionID::id_type tab_id;
[email protected]98aa0b52010-05-06 17:03:08817 std::string extension_app_id;
818 if (!RestoreSetTabExtensionAppIDCommand(command, &tab_id,
819 &extension_app_id)) {
[email protected]fca656c2010-02-10 20:30:10820 return;
821 }
[email protected]98aa0b52010-05-06 17:03:08822 current_tab->extension_app_id.swap(extension_app_id);
[email protected]fca656c2010-02-10 20:30:10823 break;
824 }
825
[email protected]169627b2008-12-06 19:30:19826 default:
827 // Unknown type, usually indicates corruption of file. Ignore it.
828 return;
829 }
830 }
831
832 // If there was corruption some of the entries won't be valid. Prune any
833 // entries with no navigations.
834 ValidateAndDeleteEmptyEntries(&(entries.get()));
835
[email protected]ea6f76572008-12-18 00:09:55836 loaded_entries->swap(entries.get());
[email protected]169627b2008-12-06 19:30:19837}
838
[email protected]f74d4452010-06-18 17:12:27839Browser* TabRestoreService::RestoreTab(const Tab& tab,
840 Browser* browser,
841 bool replace_existing_tab) {
842 // |browser| will be NULL in cases where one isn't already available (eg,
843 // when invoked on Mac OS X with no windows open). In this case, create a
844 // new browser into which we restore the tabs.
845 if (replace_existing_tab && browser) {
846 browser->ReplaceRestoredTab(tab.navigations,
847 tab.current_navigation_index,
848 tab.from_last_session,
849 tab.extension_app_id);
850 } else {
[email protected]91f6e882010-07-07 19:26:23851 if (tab.has_browser())
852 browser = BrowserList::FindBrowserWithID(tab.browser_id);
[email protected]f74d4452010-06-18 17:12:27853
854 int tab_index = -1;
855 if (browser) {
856 tab_index = tab.tabstrip_index;
857 } else {
858 browser = Browser::Create(profile());
859 if (tab.has_browser()) {
860 UpdateTabBrowserIDs(tab.browser_id, browser->session_id().id());
861 }
862 }
863
864 if (tab_index < 0 || tab_index > browser->tab_count()) {
865 tab_index = browser->tab_count();
866 }
867
868 browser->AddRestoredTab(tab.navigations,
869 tab_index,
870 tab.current_navigation_index,
871 tab.extension_app_id,
872 true, tab.pinned, tab.from_last_session);
873 }
874 return browser;
875}
876
877
[email protected]169627b2008-12-06 19:30:19878bool TabRestoreService::ValidateTab(Tab* tab) {
879 if (tab->navigations.empty())
880 return false;
881
882 tab->current_navigation_index =
883 std::max(0, std::min(tab->current_navigation_index,
884 static_cast<int>(tab->navigations.size()) - 1));
885 return true;
886}
887
888void TabRestoreService::ValidateAndDeleteEmptyEntries(
889 std::vector<Entry*>* entries) {
890 std::vector<Entry*> valid_entries;
891 std::vector<Entry*> invalid_entries;
892
893 size_t max_valid = kMaxEntries - entries_.size();
894 // Iterate from the back so that we keep the most recently closed entries.
895 for (std::vector<Entry*>::reverse_iterator i = entries->rbegin();
896 i != entries->rend(); ++i) {
897 bool valid_entry = false;
898 if (valid_entries.size() != max_valid) {
899 if ((*i)->type == TAB) {
900 Tab* tab = static_cast<Tab*>(*i);
901 if (ValidateTab(tab))
902 valid_entry = true;
903 } else {
904 Window* window = static_cast<Window*>(*i);
905 for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
906 tab_i != window->tabs.end();) {
907 if (!ValidateTab(&(*tab_i)))
908 tab_i = window->tabs.erase(tab_i);
909 else
910 ++tab_i;
911 }
912 if (!window->tabs.empty()) {
913 window->selected_tab_index =
914 std::max(0, std::min(window->selected_tab_index,
915 static_cast<int>(window->tabs.size() - 1)));
916 valid_entry = true;
917 }
918 }
919 }
920 if (valid_entry)
921 valid_entries.push_back(*i);
922 else
923 invalid_entries.push_back(*i);
924 }
925 // NOTE: at this point the entries are ordered with newest at the front.
926 entries->swap(valid_entries);
927
928 // Delete the remaining entries.
929 STLDeleteElements(&invalid_entries);
930}
[email protected]ea6f76572008-12-18 00:09:55931
[email protected]c714a162009-04-22 20:03:08932void TabRestoreService::UpdateTabBrowserIDs(SessionID::id_type old_id,
933 SessionID::id_type new_id) {
934 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
935 Entry* entry = *i;
936 if (entry->type == TAB) {
937 Tab* tab = static_cast<Tab*>(entry);
938 if (tab->browser_id == old_id)
939 tab->browser_id = new_id;
940 }
941 }
942}
943
[email protected]ea6f76572008-12-18 00:09:55944void TabRestoreService::OnGotPreviousSession(
945 Handle handle,
946 std::vector<SessionWindow*>* windows) {
947 std::vector<Entry*> entries;
948 CreateEntriesFromWindows(windows, &entries);
949 // Previous session tabs go first.
950 staging_entries_.insert(staging_entries_.begin(), entries.begin(),
951 entries.end());
952 load_state_ |= LOADED_LAST_SESSION;
953 LoadStateChanged();
954}
955
956void TabRestoreService::CreateEntriesFromWindows(
957 std::vector<SessionWindow*>* windows,
958 std::vector<Entry*>* entries) {
959 for (size_t i = 0; i < windows->size(); ++i) {
960 scoped_ptr<Window> window(new Window());
961 if (ConvertSessionWindowToWindow((*windows)[i], window.get()))
962 entries->push_back(window.release());
963 }
964}
965
966bool TabRestoreService::ConvertSessionWindowToWindow(
967 SessionWindow* session_window,
968 Window* window) {
969 for (size_t i = 0; i < session_window->tabs.size(); ++i) {
970 if (!session_window->tabs[i]->navigations.empty()) {
971 window->tabs.resize(window->tabs.size() + 1);
972 Tab& tab = window->tabs.back();
[email protected]5c0e6482009-07-14 20:20:09973 tab.pinned = session_window->tabs[i]->pinned;
[email protected]ea6f76572008-12-18 00:09:55974 tab.navigations.swap(session_window->tabs[i]->navigations);
975 tab.current_navigation_index =
976 session_window->tabs[i]->current_navigation_index;
[email protected]98aa0b52010-05-06 17:03:08977 tab.extension_app_id = session_window->tabs[i]->extension_app_id;
[email protected]f4d5b1f2009-07-20 20:28:37978 tab.timestamp = Time();
[email protected]ea6f76572008-12-18 00:09:55979 }
980 }
981 if (window->tabs.empty())
982 return false;
983
984 window->selected_tab_index =
985 std::min(session_window->selected_tab_index,
986 static_cast<int>(window->tabs.size() - 1));
[email protected]f4d5b1f2009-07-20 20:28:37987 window->timestamp = Time();
[email protected]ea6f76572008-12-18 00:09:55988 return true;
989}
990
991void TabRestoreService::LoadStateChanged() {
992 if ((load_state_ & (LOADED_LAST_TABS | LOADED_LAST_SESSION)) !=
993 (LOADED_LAST_TABS | LOADED_LAST_SESSION)) {
994 // Still waiting on previous session or previous tabs.
995 return;
996 }
997
998 // We're done loading.
999 load_state_ ^= LOADING;
1000
1001 if (staging_entries_.empty() || reached_max_) {
1002 STLDeleteElements(&staging_entries_);
1003 return;
1004 }
1005
1006 if (staging_entries_.size() + entries_.size() > kMaxEntries) {
1007 // If we add all the staged entries we'll end up with more than
1008 // kMaxEntries. Delete entries such that we only end up with
1009 // at most kMaxEntries.
1010 DCHECK(entries_.size() < kMaxEntries);
1011 STLDeleteContainerPointers(
1012 staging_entries_.begin() + (kMaxEntries - entries_.size()),
1013 staging_entries_.end());
1014 staging_entries_.erase(
1015 staging_entries_.begin() + (kMaxEntries - entries_.size()),
1016 staging_entries_.end());
1017 }
1018
1019 // And add them.
[email protected]5e369672009-11-03 23:48:301020 for (size_t i = 0; i < staging_entries_.size(); ++i) {
1021 staging_entries_[i]->from_last_session = true;
[email protected]ea6f76572008-12-18 00:09:551022 AddEntry(staging_entries_[i], false, false);
[email protected]5e369672009-11-03 23:48:301023 }
[email protected]ea6f76572008-12-18 00:09:551024
1025 // AddEntry takes ownership of the entry, need to clear out entries so that
1026 // it doesn't delete them.
1027 staging_entries_.clear();
1028
1029 // Make it so we rewrite all the tabs. We need to do this otherwise we won't
1030 // correctly write out the entries when Save is invoked (Save starts from
1031 // the front, not the end and we just added the entries to the end).
1032 entries_to_write_ = staging_entries_.size();
1033
1034 PruneAndNotify();
1035}
[email protected]f4d5b1f2009-07-20 20:28:371036
1037Time TabRestoreService::TimeNow() const {
1038 return time_factory_ ? time_factory_->TimeNow() : Time::Now();
1039}