Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(39)

Side by Side Diff: chrome/browser/sessions/tab_restore_service.cc

Issue 10989027: Split TabRestoreService into InMemoryTRS and PersistentTRS (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address Scott's comments Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/sessions/tab_restore_service.h" 5 #include "chrome/browser/sessions/tab_restore_service.h"
6 6
7 #include <algorithm> 7 #include "content/public/browser/session_storage_namespace.h"
8 #include <iterator>
9 #include <map>
10
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/callback.h"
14 #include "base/memory/scoped_vector.h"
15 #include "base/metrics/histogram.h"
16 #include "base/stl_util.h"
17 #include "chrome/browser/extensions/extension_service.h"
18 #include "chrome/browser/extensions/tab_helper.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/sessions/session_command.h"
21 #include "chrome/browser/sessions/session_service.h"
22 #include "chrome/browser/sessions/session_service_factory.h"
23 #include "chrome/browser/sessions/session_types.h"
24 #include "chrome/browser/sessions/tab_restore_service_delegate.h"
25 #include "chrome/browser/sessions/tab_restore_service_observer.h"
26 #include "chrome/browser/ui/tab_contents/tab_contents.h"
27 #include "chrome/browser/ui/webui/ntp/app_launcher_handler.h"
28 #include "chrome/common/extensions/extension.h"
29 #include "chrome/common/extensions/extension_constants.h"
30 #include "chrome/common/url_constants.h"
31 #include "content/public/browser/navigation_controller.h"
32 #include "content/public/browser/navigation_entry.h"
33 #include "content/public/browser/web_contents.h"
34
35 using base::Time;
36 using content::NavigationController;
37 using content::NavigationEntry;
38 using content::WebContents;
39 8
40 // TimeFactory----------------------------------------------------------------- 9 // TimeFactory-----------------------------------------------------------------
41 10
42 TabRestoreService::TimeFactory::~TimeFactory() {} 11 TabRestoreService::TimeFactory::~TimeFactory() {}
43 12
44 // Entry ---------------------------------------------------------------------- 13 // Entry ----------------------------------------------------------------------
45 14
46 // ID of the next Entry. 15 // ID of the next Entry.
47 static SessionID::id_type next_entry_id = 1; 16 static SessionID::id_type next_entry_id = 1;
48 17
49 TabRestoreService::Entry::Entry() 18 TabRestoreService::Entry::Entry()
50 : id(next_entry_id++), 19 : id(next_entry_id++),
51 type(TAB), 20 type(TAB),
52 from_last_session(false) {} 21 from_last_session(false) {}
53 22
54 TabRestoreService::Entry::Entry(Type type) 23 TabRestoreService::Entry::Entry(Type type)
55 : id(next_entry_id++), 24 : id(next_entry_id++),
56 type(type), 25 type(type),
57 from_last_session(false) {} 26 from_last_session(false) {}
58 27
59 TabRestoreService::Entry::~Entry() {} 28 TabRestoreService::Entry::~Entry() {}
60 29
61 // TabRestoreService ---------------------------------------------------------- 30 // Tab ------------------------------------------------------------------------
62
63 // static
64 const size_t TabRestoreService::kMaxEntries = 25;
65
66 // Identifier for commands written to file.
67 // The ordering in the file is as follows:
68 // . When the user closes a tab a command of type
69 // kCommandSelectedNavigationInTab is written identifying the tab and
70 // the selected index, then a kCommandPinnedState command if the tab was
71 // pinned and kCommandSetExtensionAppID if the tab has an app id and
72 // the user agent override if it was using one. This is
73 // followed by any number of kCommandUpdateTabNavigation commands (1 per
74 // navigation entry).
75 // . When the user closes a window a kCommandSelectedNavigationInTab command
76 // is written out and followed by n tab closed sequences (as previoulsy
77 // described).
78 // . When the user restores an entry a command of type kCommandRestoredEntry
79 // is written.
80 static const SessionCommand::id_type kCommandUpdateTabNavigation = 1;
81 static const SessionCommand::id_type kCommandRestoredEntry = 2;
82 static const SessionCommand::id_type kCommandWindow = 3;
83 static const SessionCommand::id_type kCommandSelectedNavigationInTab = 4;
84 static const SessionCommand::id_type kCommandPinnedState = 5;
85 static const SessionCommand::id_type kCommandSetExtensionAppID = 6;
86 static const SessionCommand::id_type kCommandSetWindowAppName = 7;
87 static const SessionCommand::id_type kCommandSetTabUserAgentOverride = 8;
88
89 // Number of entries (not commands) before we clobber the file and write
90 // everything.
91 static const int kEntriesPerReset = 40;
92
93 namespace {
94
95 // Payload structures.
96
97 typedef int32 RestoredEntryPayload;
98
99 // Payload used for the start of a window close. This is the old struct that is
100 // used for backwards compat when it comes to reading the session files. This
101 // struct must be POD, because we memset the contents.
102 struct WindowPayload {
103 SessionID::id_type window_id;
104 int32 selected_tab_index;
105 int32 num_tabs;
106 };
107
108 // Payload used for the start of a tab close. This is the old struct that is
109 // used for backwards compat when it comes to reading the session files.
110 struct SelectedNavigationInTabPayload {
111 SessionID::id_type id;
112 int32 index;
113 };
114
115 // Payload used for the start of a window close. This struct must be POD,
116 // because we memset the contents.
117 struct WindowPayload2 : WindowPayload {
118 int64 timestamp;
119 };
120
121 // Payload used for the start of a tab close.
122 struct SelectedNavigationInTabPayload2 : SelectedNavigationInTabPayload {
123 int64 timestamp;
124 };
125
126 // Only written if the tab is pinned.
127 typedef bool PinnedStatePayload;
128
129 typedef std::map<SessionID::id_type, TabRestoreService::Entry*> IDToEntry;
130
131 // If |id_to_entry| contains an entry for |id| the corresponding entry is
132 // deleted and removed from both |id_to_entry| and |entries|. This is used
133 // when creating entries from the backend file.
134 void RemoveEntryByID(SessionID::id_type id,
135 IDToEntry* id_to_entry,
136 std::vector<TabRestoreService::Entry*>* entries) {
137 // Look for the entry in the map. If it is present, erase it from both
138 // collections and return.
139 IDToEntry::iterator i = id_to_entry->find(id);
140 if (i != id_to_entry->end()) {
141 entries->erase(std::find(entries->begin(), entries->end(), i->second));
142 delete i->second;
143 id_to_entry->erase(i);
144 return;
145 }
146
147 // Otherwise, loop over all items in the map and see if any of the Windows
148 // have Tabs with the |id|.
149 for (IDToEntry::iterator i = id_to_entry->begin(); i != id_to_entry->end();
150 ++i) {
151 if (i->second->type == TabRestoreService::WINDOW) {
152 TabRestoreService::Window* window =
153 static_cast<TabRestoreService::Window*>(i->second);
154 std::vector<TabRestoreService::Tab>::iterator j = window->tabs.begin();
155 for ( ; j != window->tabs.end(); ++j) {
156 // If the ID matches one of this window's tabs, remove it from the list.
157 if ((*j).id == id) {
158 window->tabs.erase(j);
159 return;
160 }
161 }
162 }
163 }
164 }
165
166 void RecordAppLaunch(Profile* profile, const TabRestoreService::Tab& tab) {
167 GURL url = tab.navigations.at(tab.current_navigation_index).virtual_url();
168 DCHECK(profile->GetExtensionService());
169 if (!profile->GetExtensionService()->IsInstalledApp(url))
170 return;
171
172 AppLauncherHandler::RecordAppLaunchType(
173 extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED);
174 }
175
176 } // namespace
177 31
178 TabRestoreService::Tab::Tab() 32 TabRestoreService::Tab::Tab()
179 : Entry(TAB), 33 : Entry(TAB),
180 current_navigation_index(-1), 34 current_navigation_index(-1),
181 browser_id(0), 35 browser_id(0),
182 tabstrip_index(-1), 36 tabstrip_index(-1),
183 pinned(false) { 37 pinned(false) {
184 } 38 }
185 39
186 TabRestoreService::Tab::~Tab() { 40 TabRestoreService::Tab::~Tab() {
187 } 41 }
188 42
43 // Window ---------------------------------------------------------------------
44
189 TabRestoreService::Window::Window() : Entry(WINDOW), selected_tab_index(-1) { 45 TabRestoreService::Window::Window() : Entry(WINDOW), selected_tab_index(-1) {
190 } 46 }
191 47
192 TabRestoreService::Window::~Window() { 48 TabRestoreService::Window::~Window() {
193 } 49 }
194 50
195 TabRestoreService::TabRestoreService(Profile* profile, 51 // TabRestoreService ----------------------------------------------------------
196 TabRestoreService::TimeFactory* time_factory)
197 : BaseSessionService(BaseSessionService::TAB_RESTORE, profile,
198 FilePath()),
199 load_state_(NOT_LOADED),
200 restoring_(false),
201 entries_to_write_(0),
202 entries_written_(0),
203 time_factory_(time_factory) {
204 }
205 52
206 TabRestoreService::~TabRestoreService() { 53 TabRestoreService::~TabRestoreService() {
207 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
208 TabRestoreServiceDestroyed(this));
209 STLDeleteElements(&entries_);
210 STLDeleteElements(&staging_entries_);
211 time_factory_ = NULL;
212 } 54 }
213
214 void TabRestoreService::AddObserver(TabRestoreServiceObserver* observer) {
215 observer_list_.AddObserver(observer);
216 }
217
218 void TabRestoreService::RemoveObserver(TabRestoreServiceObserver* observer) {
219 observer_list_.RemoveObserver(observer);
220 }
221
222 void TabRestoreService::CreateHistoricalTab(content::WebContents* contents,
223 int index) {
224 if (restoring_)
225 return;
226
227 TabRestoreServiceDelegate* delegate =
228 TabRestoreServiceDelegate::FindDelegateForWebContents(contents);
229 if (closing_delegates_.find(delegate) != closing_delegates_.end())
230 return;
231
232 scoped_ptr<Tab> local_tab(new Tab());
233 PopulateTab(local_tab.get(), index, delegate, &contents->GetController());
234 if (local_tab->navigations.empty())
235 return;
236
237 AddEntry(local_tab.release(), true, true);
238 }
239
240 void TabRestoreService::BrowserClosing(TabRestoreServiceDelegate* delegate) {
241 closing_delegates_.insert(delegate);
242
243 scoped_ptr<Window> window(new Window());
244 window->selected_tab_index = delegate->GetSelectedIndex();
245 window->timestamp = TimeNow();
246 window->app_name = delegate->GetAppName();
247
248 // Don't use std::vector::resize() because it will push copies of an empty tab
249 // into the vector, which will give all tabs in a window the same ID.
250 for (int i = 0; i < delegate->GetTabCount(); ++i) {
251 window->tabs.push_back(Tab());
252 }
253 size_t entry_index = 0;
254 for (int tab_index = 0; tab_index < delegate->GetTabCount(); ++tab_index) {
255 PopulateTab(&(window->tabs[entry_index]),
256 tab_index,
257 delegate,
258 &delegate->GetWebContentsAt(tab_index)->GetController());
259 if (window->tabs[entry_index].navigations.empty()) {
260 window->tabs.erase(window->tabs.begin() + entry_index);
261 } else {
262 window->tabs[entry_index].browser_id = delegate->GetSessionID().id();
263 entry_index++;
264 }
265 }
266 if (window->tabs.size() == 1 && window->app_name.empty()) {
267 // Short-circuit creating a Window if only 1 tab was present. This fixes
268 // http://crbug.com/56744. Copy the Tab because it's owned by an object on
269 // the stack.
270 AddEntry(new Tab(window->tabs[0]), true, true);
271 } else if (!window->tabs.empty()) {
272 window->selected_tab_index =
273 std::min(static_cast<int>(window->tabs.size() - 1),
274 window->selected_tab_index);
275 AddEntry(window.release(), true, true);
276 }
277 }
278
279 void TabRestoreService::BrowserClosed(TabRestoreServiceDelegate* delegate) {
280 closing_delegates_.erase(delegate);
281 }
282
283 void TabRestoreService::ClearEntries() {
284 // Mark all the tabs as closed so that we don't attempt to restore them.
285 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i)
286 ScheduleCommand(CreateRestoredEntryCommand((*i)->id));
287
288 entries_to_write_ = 0;
289
290 // Schedule a pending reset so that we nuke the file on next write.
291 set_pending_reset(true);
292
293 // Schedule a command, otherwise if there are no pending commands Save does
294 // nothing.
295 ScheduleCommand(CreateRestoredEntryCommand(1));
296
297 STLDeleteElements(&entries_);
298 NotifyTabsChanged();
299 }
300
301 const TabRestoreService::Entries& TabRestoreService::entries() const {
302 return entries_;
303 }
304
305 void TabRestoreService::RestoreMostRecentEntry(
306 TabRestoreServiceDelegate* delegate) {
307 if (entries_.empty())
308 return;
309
310 RestoreEntryById(delegate, entries_.front()->id, UNKNOWN);
311 }
312
313 TabRestoreService::Tab* TabRestoreService::RemoveTabEntryById(
314 SessionID::id_type id) {
315 Entries::iterator i = GetEntryIteratorById(id);
316 if (i == entries_.end())
317 return NULL;
318
319 Entry* entry = *i;
320 if (entry->type != TAB)
321 return NULL;
322
323 Tab* tab = static_cast<Tab*>(entry);
324 entries_.erase(i);
325 return tab;
326 }
327
328 void TabRestoreService::RestoreEntryById(TabRestoreServiceDelegate* delegate,
329 SessionID::id_type id,
330 WindowOpenDisposition disposition) {
331 Entries::iterator i = GetEntryIteratorById(id);
332 if (i == entries_.end()) {
333 // Don't hoark here, we allow an invalid id.
334 return;
335 }
336
337 size_t index = 0;
338 for (Entries::iterator j = entries_.begin(); j != i && j != entries_.end();
339 ++j, ++index) {}
340 if (static_cast<int>(index) < entries_to_write_)
341 entries_to_write_--;
342
343 ScheduleCommand(CreateRestoredEntryCommand(id));
344
345 restoring_ = true;
346 Entry* entry = *i;
347
348 // If the entry's ID does not match the ID that is being restored, then the
349 // entry is a window from which a single tab will be restored.
350 bool restoring_tab_in_window = entry->id != id;
351
352 if (!restoring_tab_in_window) {
353 entries_.erase(i);
354 i = entries_.end();
355 }
356
357 // |delegate| will be NULL in cases where one isn't already available (eg,
358 // when invoked on Mac OS X with no windows open). In this case, create a
359 // new browser into which we restore the tabs.
360 if (entry->type == TAB) {
361 Tab* tab = static_cast<Tab*>(entry);
362 delegate = RestoreTab(*tab, delegate, disposition);
363 delegate->ShowBrowserWindow();
364 } else if (entry->type == WINDOW) {
365 TabRestoreServiceDelegate* current_delegate = delegate;
366 Window* window = static_cast<Window*>(entry);
367
368 // When restoring a window, either the entire window can be restored, or a
369 // single tab within it. If the entry's ID matches the one to restore, then
370 // the entire window will be restored.
371 if (!restoring_tab_in_window) {
372 delegate = TabRestoreServiceDelegate::Create(profile(), window->app_name);
373 for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) {
374 const Tab& tab = window->tabs[tab_i];
375 WebContents* restored_tab =
376 delegate->AddRestoredTab(tab.navigations, delegate->GetTabCount(),
377 tab.current_navigation_index,
378 tab.extension_app_id,
379 static_cast<int>(tab_i) ==
380 window->selected_tab_index,
381 tab.pinned, tab.from_last_session,
382 tab.session_storage_namespace,
383 tab.user_agent_override);
384 if (restored_tab) {
385 restored_tab->GetController().LoadIfNecessary();
386 RecordAppLaunch(profile(), tab);
387 }
388 }
389 // All the window's tabs had the same former browser_id.
390 if (window->tabs[0].has_browser()) {
391 UpdateTabBrowserIDs(window->tabs[0].browser_id,
392 delegate->GetSessionID().id());
393 }
394 } else {
395 // Restore a single tab from the window. Find the tab that matches the ID
396 // in the window and restore it.
397 for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
398 tab_i != window->tabs.end(); ++tab_i) {
399 const Tab& tab = *tab_i;
400 if (tab.id == id) {
401 delegate = RestoreTab(tab, delegate, disposition);
402 window->tabs.erase(tab_i);
403 // If restoring the tab leaves the window with nothing else, delete it
404 // as well.
405 if (!window->tabs.size()) {
406 entries_.erase(i);
407 delete entry;
408 } else {
409 // Update the browser ID of the rest of the tabs in the window so if
410 // any one is restored, it goes into the same window as the tab
411 // being restored now.
412 UpdateTabBrowserIDs(tab.browser_id,
413 delegate->GetSessionID().id());
414 for (std::vector<Tab>::iterator tab_j = window->tabs.begin();
415 tab_j != window->tabs.end(); ++tab_j) {
416 (*tab_j).browser_id = delegate->GetSessionID().id();
417 }
418 }
419 break;
420 }
421 }
422 }
423 delegate->ShowBrowserWindow();
424
425 if (disposition == CURRENT_TAB && current_delegate &&
426 current_delegate->GetActiveWebContents()) {
427 current_delegate->CloseTab();
428 }
429 } else {
430 NOTREACHED();
431 }
432
433 if (!restoring_tab_in_window) {
434 delete entry;
435 }
436
437 restoring_ = false;
438 NotifyTabsChanged();
439 }
440
441 void TabRestoreService::LoadTabsFromLastSession() {
442 if (load_state_ != NOT_LOADED || entries_.size() == kMaxEntries)
443 return;
444
445 #if !defined(ENABLE_SESSION_SERVICE)
446 // If sessions are not stored in the SessionService, default to
447 // |LOADED_LAST_SESSION| state.
448 load_state_ = LOADING | LOADED_LAST_SESSION;
449 #else
450 load_state_ = LOADING;
451
452 SessionService* session_service =
453 SessionServiceFactory::GetForProfile(profile());
454 Profile::ExitType exit_type = profile()->GetLastSessionExitType();
455 if (!profile()->restored_last_session() && session_service &&
456 (exit_type == Profile::EXIT_CRASHED ||
457 exit_type == Profile::EXIT_SESSION_ENDED)) {
458 // The previous session crashed and wasn't restored, or was a forced
459 // shutdown. Both of which won't have notified us of the browser close so
460 // that we need to load the windows from session service (which will have
461 // saved them).
462 session_service->GetLastSession(
463 &crash_consumer_,
464 base::Bind(&TabRestoreService::OnGotPreviousSession,
465 base::Unretained(this)));
466 } else {
467 load_state_ |= LOADED_LAST_SESSION;
468 }
469 #endif
470
471 // Request the tabs closed in the last session. If the last session crashed,
472 // this won't contain the tabs/window that were open at the point of the
473 // crash (the call to GetLastSession above requests those).
474 ScheduleGetLastSessionCommands(
475 new InternalGetCommandsRequest(
476 base::Bind(&TabRestoreService::OnGotLastSessionCommands,
477 base::Unretained(this))),
478 &load_consumer_);
479 }
480
481 bool TabRestoreService::IsLoaded() const {
482 return !(load_state_ & (NOT_LOADED | LOADING));
483 }
484
485 void TabRestoreService::Shutdown() {
486 if (backend())
487 Save();
488 }
489
490 void TabRestoreService::Save() {
491 int to_write_count = std::min(entries_to_write_,
492 static_cast<int>(entries_.size()));
493 entries_to_write_ = 0;
494 if (entries_written_ + to_write_count > kEntriesPerReset) {
495 to_write_count = entries_.size();
496 set_pending_reset(true);
497 }
498 if (to_write_count) {
499 // Write the to_write_count most recently added entries out. The most
500 // recently added entry is at the front, so we use a reverse iterator to
501 // write in the order the entries were added.
502 Entries::reverse_iterator i = entries_.rbegin();
503 DCHECK(static_cast<size_t>(to_write_count) <= entries_.size());
504 std::advance(i, entries_.size() - static_cast<int>(to_write_count));
505 for (; i != entries_.rend(); ++i) {
506 Entry* entry = *i;
507 if (entry->type == TAB) {
508 Tab* tab = static_cast<Tab*>(entry);
509 int selected_index = GetSelectedNavigationIndexToPersist(*tab);
510 if (selected_index != -1)
511 ScheduleCommandsForTab(*tab, selected_index);
512 } else {
513 ScheduleCommandsForWindow(*static_cast<Window*>(entry));
514 }
515 entries_written_++;
516 }
517 }
518 if (pending_reset())
519 entries_written_ = 0;
520 BaseSessionService::Save();
521 }
522
523 void TabRestoreService::PopulateTab(Tab* tab,
524 int index,
525 TabRestoreServiceDelegate* delegate,
526 NavigationController* controller) {
527 const int pending_index = controller->GetPendingEntryIndex();
528 int entry_count = controller->GetEntryCount();
529 if (entry_count == 0 && pending_index == 0)
530 entry_count++;
531 tab->navigations.resize(static_cast<int>(entry_count));
532 for (int i = 0; i < entry_count; ++i) {
533 NavigationEntry* entry = (i == pending_index) ?
534 controller->GetPendingEntry() : controller->GetEntryAtIndex(i);
535 tab->navigations[i] =
536 TabNavigation::FromNavigationEntry(i, *entry);
537 }
538 tab->timestamp = TimeNow();
539 tab->current_navigation_index = controller->GetCurrentEntryIndex();
540 if (tab->current_navigation_index == -1 && entry_count > 0)
541 tab->current_navigation_index = 0;
542 tab->tabstrip_index = index;
543
544 TabContents* tab_contents =
545 TabContents::FromWebContents(controller->GetWebContents());
546 // tab_contents is NULL in some browser tests.
547 if (tab_contents) {
548 const extensions::Extension* extension =
549 extensions::TabHelper::FromWebContents(controller->GetWebContents())->
550 extension_app();
551 if (extension)
552 tab->extension_app_id = extension->id();
553 }
554
555 tab->user_agent_override =
556 controller->GetWebContents()->GetUserAgentOverride();
557
558 // TODO(ajwong): This does not correctly handle storage for isolated apps.
559 tab->session_storage_namespace =
560 controller->GetDefaultSessionStorageNamespace();
561
562 // Delegate may be NULL during unit tests.
563 if (delegate) {
564 tab->browser_id = delegate->GetSessionID().id();
565 tab->pinned = delegate->IsTabPinned(tab->tabstrip_index);
566 }
567 }
568
569 void TabRestoreService::NotifyTabsChanged() {
570 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
571 TabRestoreServiceChanged(this));
572 }
573
574 void TabRestoreService::AddEntry(Entry* entry, bool notify, bool to_front) {
575 if (!FilterEntry(entry) || (entries_.size() >= kMaxEntries && !to_front)) {
576 delete entry;
577 return;
578 }
579
580 if (to_front)
581 entries_.push_front(entry);
582 else
583 entries_.push_back(entry);
584
585 PruneEntries();
586
587 if (notify)
588 NotifyTabsChanged();
589
590 // Start the save timer, when it fires we'll generate the commands.
591 StartSaveTimer();
592 entries_to_write_++;
593 }
594
595 void TabRestoreService::PruneEntries() {
596 Entries new_entries;
597
598 for (TabRestoreService::Entries::const_iterator iter = entries_.begin();
599 iter != entries_.end(); ++iter) {
600 TabRestoreService::Entry* entry = *iter;
601
602 if (FilterEntry(entry) &&
603 new_entries.size() < kMaxEntries) {
604 new_entries.push_back(entry);
605 } else {
606 delete entry;
607 }
608 }
609
610 entries_ = new_entries;
611 }
612
613 TabRestoreService::Entries::iterator TabRestoreService::GetEntryIteratorById(
614 SessionID::id_type id) {
615 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
616 if ((*i)->id == id)
617 return i;
618
619 // For Window entries, see if the ID matches a tab. If so, report the window
620 // as the Entry.
621 if ((*i)->type == WINDOW) {
622 std::vector<Tab>& tabs = static_cast<Window*>(*i)->tabs;
623 for (std::vector<Tab>::iterator j = tabs.begin();
624 j != tabs.end(); ++j) {
625 if ((*j).id == id) {
626 return i;
627 }
628 }
629 }
630 }
631 return entries_.end();
632 }
633
634 void TabRestoreService::ScheduleCommandsForWindow(const Window& window) {
635 DCHECK(!window.tabs.empty());
636 int selected_tab = window.selected_tab_index;
637 int valid_tab_count = 0;
638 int real_selected_tab = selected_tab;
639 for (size_t i = 0; i < window.tabs.size(); ++i) {
640 if (GetSelectedNavigationIndexToPersist(window.tabs[i]) != -1) {
641 valid_tab_count++;
642 } else if (static_cast<int>(i) < selected_tab) {
643 real_selected_tab--;
644 }
645 }
646 if (valid_tab_count == 0)
647 return; // No tabs to persist.
648
649 ScheduleCommand(
650 CreateWindowCommand(window.id,
651 std::min(real_selected_tab, valid_tab_count - 1),
652 valid_tab_count,
653 window.timestamp));
654
655 if (!window.app_name.empty()) {
656 ScheduleCommand(
657 CreateSetWindowAppNameCommand(kCommandSetWindowAppName,
658 window.id,
659 window.app_name));
660 }
661
662 for (size_t i = 0; i < window.tabs.size(); ++i) {
663 int selected_index = GetSelectedNavigationIndexToPersist(window.tabs[i]);
664 if (selected_index != -1)
665 ScheduleCommandsForTab(window.tabs[i], selected_index);
666 }
667 }
668
669 void TabRestoreService::ScheduleCommandsForTab(const Tab& tab,
670 int selected_index) {
671 const std::vector<TabNavigation>& navigations = tab.navigations;
672 int max_index = static_cast<int>(navigations.size());
673
674 // Determine the first navigation we'll persist.
675 int valid_count_before_selected = 0;
676 int first_index_to_persist = selected_index;
677 for (int i = selected_index - 1; i >= 0 &&
678 valid_count_before_selected < max_persist_navigation_count; --i) {
679 if (ShouldTrackEntry(navigations[i].virtual_url())) {
680 first_index_to_persist = i;
681 valid_count_before_selected++;
682 }
683 }
684
685 // Write the command that identifies the selected tab.
686 ScheduleCommand(
687 CreateSelectedNavigationInTabCommand(tab.id,
688 valid_count_before_selected,
689 tab.timestamp));
690
691 if (tab.pinned) {
692 PinnedStatePayload payload = true;
693 SessionCommand* command =
694 new SessionCommand(kCommandPinnedState, sizeof(payload));
695 memcpy(command->contents(), &payload, sizeof(payload));
696 ScheduleCommand(command);
697 }
698
699 if (!tab.extension_app_id.empty()) {
700 ScheduleCommand(
701 CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID, tab.id,
702 tab.extension_app_id));
703 }
704
705 if (!tab.user_agent_override.empty()) {
706 ScheduleCommand(
707 CreateSetTabUserAgentOverrideCommand(kCommandSetTabUserAgentOverride,
708 tab.id, tab.user_agent_override));
709 }
710
711 // Then write the navigations.
712 for (int i = first_index_to_persist, wrote_count = 0;
713 i < max_index && wrote_count < 2 * max_persist_navigation_count; ++i) {
714 if (ShouldTrackEntry(navigations[i].virtual_url())) {
715 ScheduleCommand(
716 CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, tab.id,
717 navigations[i]));
718 }
719 }
720 }
721
722 SessionCommand* TabRestoreService::CreateWindowCommand(SessionID::id_type id,
723 int selected_tab_index,
724 int num_tabs,
725 Time timestamp) {
726 WindowPayload2 payload;
727 // |timestamp| is aligned on a 16 byte boundary, leaving 4 bytes of
728 // uninitialized memory in the struct.
729 memset(&payload, 0, sizeof(payload));
730 payload.window_id = id;
731 payload.selected_tab_index = selected_tab_index;
732 payload.num_tabs = num_tabs;
733 payload.timestamp = timestamp.ToInternalValue();
734
735 SessionCommand* command =
736 new SessionCommand(kCommandWindow, sizeof(payload));
737 memcpy(command->contents(), &payload, sizeof(payload));
738 return command;
739 }
740
741 SessionCommand* TabRestoreService::CreateSelectedNavigationInTabCommand(
742 SessionID::id_type tab_id,
743 int32 index,
744 Time timestamp) {
745 SelectedNavigationInTabPayload2 payload;
746 payload.id = tab_id;
747 payload.index = index;
748 payload.timestamp = timestamp.ToInternalValue();
749 SessionCommand* command =
750 new SessionCommand(kCommandSelectedNavigationInTab, sizeof(payload));
751 memcpy(command->contents(), &payload, sizeof(payload));
752 return command;
753 }
754
755 SessionCommand* TabRestoreService::CreateRestoredEntryCommand(
756 SessionID::id_type entry_id) {
757 RestoredEntryPayload payload = entry_id;
758 SessionCommand* command =
759 new SessionCommand(kCommandRestoredEntry, sizeof(payload));
760 memcpy(command->contents(), &payload, sizeof(payload));
761 return command;
762 }
763
764 int TabRestoreService::GetSelectedNavigationIndexToPersist(const Tab& tab) {
765 const std::vector<TabNavigation>& navigations = tab.navigations;
766 int selected_index = tab.current_navigation_index;
767 int max_index = static_cast<int>(navigations.size());
768
769 // Find the first navigation to persist. We won't persist the selected
770 // navigation if ShouldTrackEntry returns false.
771 while (selected_index >= 0 &&
772 !ShouldTrackEntry(navigations[selected_index].virtual_url())) {
773 selected_index--;
774 }
775
776 if (selected_index != -1)
777 return selected_index;
778
779 // Couldn't find a navigation to persist going back, go forward.
780 selected_index = tab.current_navigation_index + 1;
781 while (selected_index < max_index &&
782 !ShouldTrackEntry(navigations[selected_index].virtual_url())) {
783 selected_index++;
784 }
785
786 return (selected_index == max_index) ? -1 : selected_index;
787 }
788
789 void TabRestoreService::OnGotLastSessionCommands(
790 Handle handle,
791 scoped_refptr<InternalGetCommandsRequest> request) {
792 std::vector<Entry*> entries;
793 CreateEntriesFromCommands(request, &entries);
794 // Closed tabs always go to the end.
795 staging_entries_.insert(staging_entries_.end(), entries.begin(),
796 entries.end());
797 load_state_ |= LOADED_LAST_TABS;
798 LoadStateChanged();
799 }
800
801 void TabRestoreService::CreateEntriesFromCommands(
802 scoped_refptr<InternalGetCommandsRequest> request,
803 std::vector<Entry*>* loaded_entries) {
804 if (request->canceled() || entries_.size() == kMaxEntries)
805 return;
806
807 std::vector<SessionCommand*>& commands = request->commands;
808 // Iterate through the commands populating entries and id_to_entry.
809 ScopedVector<Entry> entries;
810 IDToEntry id_to_entry;
811 // If non-null we're processing the navigations of this tab.
812 Tab* current_tab = NULL;
813 // If non-null we're processing the tabs of this window.
814 Window* current_window = NULL;
815 // If > 0, we've gotten a window command but not all the tabs yet.
816 int pending_window_tabs = 0;
817 for (std::vector<SessionCommand*>::const_iterator i = commands.begin();
818 i != commands.end(); ++i) {
819 const SessionCommand& command = *(*i);
820 switch (command.id()) {
821 case kCommandRestoredEntry: {
822 if (pending_window_tabs > 0) {
823 // Should never receive a restored command while waiting for all the
824 // tabs in a window.
825 return;
826 }
827
828 current_tab = NULL;
829 current_window = NULL;
830
831 RestoredEntryPayload payload;
832 if (!command.GetPayload(&payload, sizeof(payload)))
833 return;
834 RemoveEntryByID(payload, &id_to_entry, &(entries.get()));
835 break;
836 }
837
838 case kCommandWindow: {
839 WindowPayload2 payload;
840 if (pending_window_tabs > 0) {
841 // Should never receive a window command while waiting for all the
842 // tabs in a window.
843 return;
844 }
845
846 // Try the new payload first
847 if (!command.GetPayload(&payload, sizeof(payload))) {
848 // then the old payload
849 WindowPayload old_payload;
850 if (!command.GetPayload(&old_payload, sizeof(old_payload)))
851 return;
852
853 // Copy the old payload data to the new payload.
854 payload.window_id = old_payload.window_id;
855 payload.selected_tab_index = old_payload.selected_tab_index;
856 payload.num_tabs = old_payload.num_tabs;
857 // Since we don't have a time use time 0 which is used to mark as an
858 // unknown timestamp.
859 payload.timestamp = 0;
860 }
861
862 pending_window_tabs = payload.num_tabs;
863 if (pending_window_tabs <= 0) {
864 // Should always have at least 1 tab. Likely indicates corruption.
865 return;
866 }
867
868 RemoveEntryByID(payload.window_id, &id_to_entry, &(entries.get()));
869
870 current_window = new Window();
871 current_window->selected_tab_index = payload.selected_tab_index;
872 current_window->timestamp = Time::FromInternalValue(payload.timestamp);
873 entries.push_back(current_window);
874 id_to_entry[payload.window_id] = current_window;
875 break;
876 }
877
878 case kCommandSelectedNavigationInTab: {
879 SelectedNavigationInTabPayload2 payload;
880 if (!command.GetPayload(&payload, sizeof(payload))) {
881 SelectedNavigationInTabPayload old_payload;
882 if (!command.GetPayload(&old_payload, sizeof(old_payload)))
883 return;
884 payload.id = old_payload.id;
885 payload.index = old_payload.index;
886 // Since we don't have a time use time 0 which is used to mark as an
887 // unknown timestamp.
888 payload.timestamp = 0;
889 }
890
891 if (pending_window_tabs > 0) {
892 if (!current_window) {
893 // We should have created a window already.
894 NOTREACHED();
895 return;
896 }
897 current_window->tabs.resize(current_window->tabs.size() + 1);
898 current_tab = &(current_window->tabs.back());
899 if (--pending_window_tabs == 0)
900 current_window = NULL;
901 } else {
902 RemoveEntryByID(payload.id, &id_to_entry, &(entries.get()));
903 current_tab = new Tab();
904 id_to_entry[payload.id] = current_tab;
905 current_tab->timestamp = Time::FromInternalValue(payload.timestamp);
906 entries.push_back(current_tab);
907 }
908 current_tab->current_navigation_index = payload.index;
909 break;
910 }
911
912 case kCommandUpdateTabNavigation: {
913 if (!current_tab) {
914 // Should be in a tab when we get this.
915 return;
916 }
917 current_tab->navigations.resize(current_tab->navigations.size() + 1);
918 SessionID::id_type tab_id;
919 if (!RestoreUpdateTabNavigationCommand(
920 command, &current_tab->navigations.back(), &tab_id)) {
921 return;
922 }
923 break;
924 }
925
926 case kCommandPinnedState: {
927 if (!current_tab) {
928 // Should be in a tab when we get this.
929 return;
930 }
931 // NOTE: payload doesn't matter. kCommandPinnedState is only written if
932 // tab is pinned.
933 current_tab->pinned = true;
934 break;
935 }
936
937 case kCommandSetWindowAppName: {
938 if (!current_window) {
939 // We should have created a window already.
940 NOTREACHED();
941 return;
942 }
943
944 SessionID::id_type window_id;
945 std::string app_name;
946 if (!RestoreSetWindowAppNameCommand(command, &window_id, &app_name))
947 return;
948
949 current_window->app_name.swap(app_name);
950 break;
951 }
952
953 case kCommandSetExtensionAppID: {
954 if (!current_tab) {
955 // Should be in a tab when we get this.
956 return;
957 }
958 SessionID::id_type tab_id;
959 std::string extension_app_id;
960 if (!RestoreSetTabExtensionAppIDCommand(command, &tab_id,
961 &extension_app_id)) {
962 return;
963 }
964 current_tab->extension_app_id.swap(extension_app_id);
965 break;
966 }
967
968 case kCommandSetTabUserAgentOverride: {
969 if (!current_tab) {
970 // Should be in a tab when we get this.
971 return;
972 }
973 SessionID::id_type tab_id;
974 std::string user_agent_override;
975 if (!RestoreSetTabUserAgentOverrideCommand(command, &tab_id,
976 &user_agent_override)) {
977 return;
978 }
979 current_tab->user_agent_override.swap(user_agent_override);
980 break;
981 }
982
983 default:
984 // Unknown type, usually indicates corruption of file. Ignore it.
985 return;
986 }
987 }
988
989 // If there was corruption some of the entries won't be valid.
990 ValidateAndDeleteEmptyEntries(&(entries.get()));
991
992 loaded_entries->swap(entries.get());
993 }
994
995 TabRestoreServiceDelegate* TabRestoreService::RestoreTab(
996 const Tab& tab,
997 TabRestoreServiceDelegate* delegate,
998 WindowOpenDisposition disposition) {
999 if (disposition == CURRENT_TAB && delegate) {
1000 delegate->ReplaceRestoredTab(tab.navigations,
1001 tab.current_navigation_index,
1002 tab.from_last_session,
1003 tab.extension_app_id,
1004 tab.session_storage_namespace,
1005 tab.user_agent_override);
1006 } else {
1007 // We only respsect the tab's original browser if there's no disposition.
1008 if (disposition == UNKNOWN && tab.has_browser())
1009 delegate = TabRestoreServiceDelegate::FindDelegateWithID(tab.browser_id);
1010
1011 int tab_index = -1;
1012
1013 // |delegate| will be NULL in cases where one isn't already available (eg,
1014 // when invoked on Mac OS X with no windows open). In this case, create a
1015 // new browser into which we restore the tabs.
1016 if (delegate && disposition != NEW_WINDOW) {
1017 tab_index = tab.tabstrip_index;
1018 } else {
1019 delegate = TabRestoreServiceDelegate::Create(profile(), std::string());
1020 if (tab.has_browser())
1021 UpdateTabBrowserIDs(tab.browser_id, delegate->GetSessionID().id());
1022 }
1023
1024 // Place the tab at the end if the tab index is no longer valid or
1025 // we were passed a specific disposition.
1026 if (tab_index < 0 || tab_index > delegate->GetTabCount() ||
1027 disposition != UNKNOWN) {
1028 tab_index = delegate->GetTabCount();
1029 }
1030
1031 WebContents* web_contents = delegate->AddRestoredTab(
1032 tab.navigations,
1033 tab_index,
1034 tab.current_navigation_index,
1035 tab.extension_app_id,
1036 disposition != NEW_BACKGROUND_TAB,
1037 tab.pinned,
1038 tab.from_last_session,
1039 tab.session_storage_namespace,
1040 tab.user_agent_override);
1041 web_contents->GetController().LoadIfNecessary();
1042 }
1043 RecordAppLaunch(profile(), tab);
1044 return delegate;
1045 }
1046
1047
1048 bool TabRestoreService::ValidateTab(Tab* tab) {
1049 if (tab->navigations.empty())
1050 return false;
1051
1052 tab->current_navigation_index =
1053 std::max(0, std::min(tab->current_navigation_index,
1054 static_cast<int>(tab->navigations.size()) - 1));
1055
1056 return true;
1057 }
1058
1059 bool TabRestoreService::ValidateWindow(Window* window) {
1060 window->selected_tab_index =
1061 std::max(0, std::min(window->selected_tab_index,
1062 static_cast<int>(window->tabs.size() - 1)));
1063
1064 int i = 0;
1065 for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
1066 tab_i != window->tabs.end();) {
1067 if (!ValidateTab(&(*tab_i))) {
1068 tab_i = window->tabs.erase(tab_i);
1069 if (i < window->selected_tab_index)
1070 window->selected_tab_index--;
1071 else if (i == window->selected_tab_index)
1072 window->selected_tab_index = 0;
1073 } else {
1074 ++tab_i;
1075 ++i;
1076 }
1077 }
1078
1079 if (window->tabs.empty())
1080 return false;
1081
1082 return true;
1083 }
1084
1085 bool TabRestoreService::ValidateEntry(Entry* entry) {
1086 if (entry->type == TAB)
1087 return ValidateTab(static_cast<Tab*>(entry));
1088
1089 if (entry->type == WINDOW)
1090 return ValidateWindow(static_cast<Window*>(entry));
1091
1092 NOTREACHED();
1093 return false;
1094 }
1095
1096 bool TabRestoreService::IsTabInteresting(const Tab* tab) {
1097 if (tab->navigations.empty())
1098 return false;
1099
1100 if (tab->navigations.size() > 1)
1101 return true;
1102
1103 return tab->pinned ||
1104 tab->navigations.at(0).virtual_url() !=
1105 GURL(chrome::kChromeUINewTabURL);
1106 }
1107
1108 bool TabRestoreService::IsWindowInteresting(const Window* window) {
1109 if (window->tabs.empty())
1110 return false;
1111
1112 if (window->tabs.size() > 1)
1113 return true;
1114
1115 return IsTabInteresting(&window->tabs[0]);
1116 }
1117
1118 bool TabRestoreService::FilterEntry(Entry* entry) {
1119 if (!ValidateEntry(entry))
1120 return false;
1121
1122 if (entry->type == TAB)
1123 return IsTabInteresting(static_cast<Tab*>(entry));
1124 else if (entry->type == WINDOW)
1125 return IsWindowInteresting(static_cast<Window*>(entry));
1126
1127 NOTREACHED();
1128 return false;
1129 }
1130
1131 void TabRestoreService::ValidateAndDeleteEmptyEntries(
1132 std::vector<Entry*>* entries) {
1133 std::vector<Entry*> valid_entries;
1134 std::vector<Entry*> invalid_entries;
1135
1136 // Iterate from the back so that we keep the most recently closed entries.
1137 for (std::vector<Entry*>::reverse_iterator i = entries->rbegin();
1138 i != entries->rend(); ++i) {
1139 if (ValidateEntry(*i))
1140 valid_entries.push_back(*i);
1141 else
1142 invalid_entries.push_back(*i);
1143 }
1144 // NOTE: at this point the entries are ordered with newest at the front.
1145 entries->swap(valid_entries);
1146
1147 // Delete the remaining entries.
1148 STLDeleteElements(&invalid_entries);
1149 }
1150
1151 void TabRestoreService::UpdateTabBrowserIDs(SessionID::id_type old_id,
1152 SessionID::id_type new_id) {
1153 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
1154 Entry* entry = *i;
1155 if (entry->type == TAB) {
1156 Tab* tab = static_cast<Tab*>(entry);
1157 if (tab->browser_id == old_id)
1158 tab->browser_id = new_id;
1159 }
1160 }
1161 }
1162
1163 void TabRestoreService::OnGotPreviousSession(
1164 Handle handle,
1165 std::vector<SessionWindow*>* windows,
1166 SessionID::id_type ignored_active_window) {
1167 std::vector<Entry*> entries;
1168 CreateEntriesFromWindows(windows, &entries);
1169 // Previous session tabs go first.
1170 staging_entries_.insert(staging_entries_.begin(), entries.begin(),
1171 entries.end());
1172 load_state_ |= LOADED_LAST_SESSION;
1173 LoadStateChanged();
1174 }
1175
1176 void TabRestoreService::CreateEntriesFromWindows(
1177 std::vector<SessionWindow*>* windows,
1178 std::vector<Entry*>* entries) {
1179 for (size_t i = 0; i < windows->size(); ++i) {
1180 scoped_ptr<Window> window(new Window());
1181 if (ConvertSessionWindowToWindow((*windows)[i], window.get()))
1182 entries->push_back(window.release());
1183 }
1184 }
1185
1186 bool TabRestoreService::ConvertSessionWindowToWindow(
1187 SessionWindow* session_window,
1188 Window* window) {
1189 for (size_t i = 0; i < session_window->tabs.size(); ++i) {
1190 if (!session_window->tabs[i]->navigations.empty()) {
1191 window->tabs.resize(window->tabs.size() + 1);
1192 Tab& tab = window->tabs.back();
1193 tab.pinned = session_window->tabs[i]->pinned;
1194 tab.navigations.swap(session_window->tabs[i]->navigations);
1195 tab.current_navigation_index =
1196 session_window->tabs[i]->current_navigation_index;
1197 tab.extension_app_id = session_window->tabs[i]->extension_app_id;
1198 tab.timestamp = Time();
1199 }
1200 }
1201 if (window->tabs.empty())
1202 return false;
1203
1204 window->selected_tab_index =
1205 std::min(session_window->selected_tab_index,
1206 static_cast<int>(window->tabs.size() - 1));
1207 window->timestamp = Time();
1208 return true;
1209 }
1210
1211 void TabRestoreService::LoadStateChanged() {
1212 if ((load_state_ & (LOADED_LAST_TABS | LOADED_LAST_SESSION)) !=
1213 (LOADED_LAST_TABS | LOADED_LAST_SESSION)) {
1214 // Still waiting on previous session or previous tabs.
1215 return;
1216 }
1217
1218 // We're done loading.
1219 load_state_ ^= LOADING;
1220
1221 if (staging_entries_.empty() || entries_.size() >= kMaxEntries) {
1222 STLDeleteElements(&staging_entries_);
1223 return;
1224 }
1225
1226 if (staging_entries_.size() + entries_.size() > kMaxEntries) {
1227 // If we add all the staged entries we'll end up with more than
1228 // kMaxEntries. Delete entries such that we only end up with
1229 // at most kMaxEntries.
1230 int surplus = kMaxEntries - entries_.size();
1231 CHECK_LE(0, surplus);
1232 CHECK_GE(static_cast<int>(staging_entries_.size()), surplus);
1233 STLDeleteContainerPointers(
1234 staging_entries_.begin() + (kMaxEntries - entries_.size()),
1235 staging_entries_.end());
1236 staging_entries_.erase(
1237 staging_entries_.begin() + (kMaxEntries - entries_.size()),
1238 staging_entries_.end());
1239 }
1240
1241 // And add them.
1242 for (size_t i = 0; i < staging_entries_.size(); ++i) {
1243 staging_entries_[i]->from_last_session = true;
1244 AddEntry(staging_entries_[i], false, false);
1245 }
1246
1247 // AddEntry takes ownership of the entry, need to clear out entries so that
1248 // it doesn't delete them.
1249 staging_entries_.clear();
1250
1251 // Make it so we rewrite all the tabs. We need to do this otherwise we won't
1252 // correctly write out the entries when Save is invoked (Save starts from
1253 // the front, not the end and we just added the entries to the end).
1254 entries_to_write_ = staging_entries_.size();
1255
1256 PruneEntries();
1257 NotifyTabsChanged();
1258 }
1259
1260 Time TabRestoreService::TimeNow() const {
1261 return time_factory_ ? time_factory_->TimeNow() : Time::Now();
1262 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698