ExtensionSyncService: listen for relevant changes instead of being explicitly called...
[chromium-blink-merge.git] / chrome / browser / sessions / tab_restore_service_helper.cc
blob766335bb73e05c6ec7b885d7969a19795be6a657
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/sessions/tab_restore_service_helper.h"
7 #include <algorithm>
8 #include <iterator>
10 #include "base/logging.h"
11 #include "base/metrics/histogram.h"
12 #include "base/stl_util.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/sessions/tab_restore_service_delegate.h"
15 #include "chrome/browser/sessions/tab_restore_service_observer.h"
16 #include "chrome/common/url_constants.h"
17 #include "components/sessions/content/content_serialized_navigation_builder.h"
18 #include "components/sessions/session_types.h"
19 #include "content/public/browser/navigation_controller.h"
20 #include "content/public/browser/navigation_entry.h"
21 #include "content/public/browser/session_storage_namespace.h"
22 #include "content/public/browser/web_contents.h"
24 #if defined(ENABLE_EXTENSIONS)
25 #include "chrome/browser/extensions/tab_helper.h"
26 #include "chrome/common/extensions/extension_constants.h"
27 #include "chrome/common/extensions/extension_metrics.h"
28 #include "extensions/browser/extension_registry.h"
29 #include "extensions/common/extension.h"
30 #include "extensions/common/extension_set.h"
31 #endif
33 using content::NavigationController;
34 using content::NavigationEntry;
35 using content::WebContents;
37 namespace {
39 void RecordAppLaunch(Profile* profile, const TabRestoreService::Tab& tab) {
40 #if defined(ENABLE_EXTENSIONS)
41 GURL url = tab.navigations.at(tab.current_navigation_index).virtual_url();
42 const extensions::Extension* extension =
43 extensions::ExtensionRegistry::Get(profile)
44 ->enabled_extensions().GetAppByURL(url);
45 if (!extension)
46 return;
48 extensions::RecordAppLaunchType(
49 extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED,
50 extension->GetType());
51 #endif // defined(ENABLE_EXTENSIONS)
54 } // namespace
56 // TabRestoreServiceHelper::Observer -------------------------------------------
58 TabRestoreServiceHelper::Observer::~Observer() {}
60 void TabRestoreServiceHelper::Observer::OnClearEntries() {}
62 void TabRestoreServiceHelper::Observer::OnRestoreEntryById(
63 SessionID::id_type id,
64 Entries::const_iterator entry_iterator) {
67 void TabRestoreServiceHelper::Observer::OnAddEntry() {}
69 // TabRestoreServiceHelper -----------------------------------------------------
71 TabRestoreServiceHelper::TabRestoreServiceHelper(
72 TabRestoreService* tab_restore_service,
73 Observer* observer,
74 Profile* profile,
75 TabRestoreService::TimeFactory* time_factory)
76 : tab_restore_service_(tab_restore_service),
77 observer_(observer),
78 profile_(profile),
79 restoring_(false),
80 time_factory_(time_factory) {
81 DCHECK(tab_restore_service_);
84 TabRestoreServiceHelper::~TabRestoreServiceHelper() {
85 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
86 TabRestoreServiceDestroyed(tab_restore_service_));
87 STLDeleteElements(&entries_);
90 void TabRestoreServiceHelper::AddObserver(
91 TabRestoreServiceObserver* observer) {
92 observer_list_.AddObserver(observer);
95 void TabRestoreServiceHelper::RemoveObserver(
96 TabRestoreServiceObserver* observer) {
97 observer_list_.RemoveObserver(observer);
100 void TabRestoreServiceHelper::CreateHistoricalTab(
101 content::WebContents* contents,
102 int index) {
103 if (restoring_)
104 return;
106 TabRestoreServiceDelegate* delegate =
107 TabRestoreServiceDelegate::FindDelegateForWebContents(contents);
108 if (closing_delegates_.find(delegate) != closing_delegates_.end())
109 return;
111 scoped_ptr<Tab> local_tab(new Tab());
112 PopulateTab(local_tab.get(), index, delegate, &contents->GetController());
113 if (local_tab->navigations.empty())
114 return;
116 AddEntry(local_tab.release(), true, true);
119 void TabRestoreServiceHelper::BrowserClosing(
120 TabRestoreServiceDelegate* delegate) {
121 closing_delegates_.insert(delegate);
123 scoped_ptr<Window> window(new Window());
124 window->selected_tab_index = delegate->GetSelectedIndex();
125 window->timestamp = TimeNow();
126 window->app_name = delegate->GetAppName();
128 // Don't use std::vector::resize() because it will push copies of an empty tab
129 // into the vector, which will give all tabs in a window the same ID.
130 for (int i = 0; i < delegate->GetTabCount(); ++i) {
131 window->tabs.push_back(Tab());
133 size_t entry_index = 0;
134 for (int tab_index = 0; tab_index < delegate->GetTabCount(); ++tab_index) {
135 PopulateTab(&(window->tabs[entry_index]),
136 tab_index,
137 delegate,
138 &delegate->GetWebContentsAt(tab_index)->GetController());
139 if (window->tabs[entry_index].navigations.empty()) {
140 window->tabs.erase(window->tabs.begin() + entry_index);
141 } else {
142 window->tabs[entry_index].browser_id = delegate->GetSessionID().id();
143 entry_index++;
146 if (window->tabs.size() == 1 && window->app_name.empty()) {
147 // Short-circuit creating a Window if only 1 tab was present. This fixes
148 // http://crbug.com/56744. Copy the Tab because it's owned by an object on
149 // the stack.
150 AddEntry(new Tab(window->tabs[0]), true, true);
151 } else if (!window->tabs.empty()) {
152 window->selected_tab_index =
153 std::min(static_cast<int>(window->tabs.size() - 1),
154 window->selected_tab_index);
155 AddEntry(window.release(), true, true);
159 void TabRestoreServiceHelper::BrowserClosed(
160 TabRestoreServiceDelegate* delegate) {
161 closing_delegates_.erase(delegate);
164 void TabRestoreServiceHelper::ClearEntries() {
165 if (observer_)
166 observer_->OnClearEntries();
167 STLDeleteElements(&entries_);
168 NotifyTabsChanged();
171 const TabRestoreService::Entries& TabRestoreServiceHelper::entries() const {
172 return entries_;
175 std::vector<content::WebContents*>
176 TabRestoreServiceHelper::RestoreMostRecentEntry(
177 TabRestoreServiceDelegate* delegate,
178 chrome::HostDesktopType host_desktop_type) {
179 if (entries_.empty())
180 return std::vector<WebContents*>();
182 return RestoreEntryById(delegate, entries_.front()->id, host_desktop_type,
183 UNKNOWN);
186 TabRestoreService::Tab* TabRestoreServiceHelper::RemoveTabEntryById(
187 SessionID::id_type id) {
188 Entries::iterator i = GetEntryIteratorById(id);
189 if (i == entries_.end())
190 return NULL;
192 Entry* entry = *i;
193 if (entry->type != TabRestoreService::TAB)
194 return NULL;
196 Tab* tab = static_cast<Tab*>(entry);
197 entries_.erase(i);
198 return tab;
201 std::vector<content::WebContents*> TabRestoreServiceHelper::RestoreEntryById(
202 TabRestoreServiceDelegate* delegate,
203 SessionID::id_type id,
204 chrome::HostDesktopType host_desktop_type,
205 WindowOpenDisposition disposition) {
206 Entries::iterator entry_iterator = GetEntryIteratorById(id);
207 if (entry_iterator == entries_.end())
208 // Don't hoark here, we allow an invalid id.
209 return std::vector<WebContents*>();
211 if (observer_)
212 observer_->OnRestoreEntryById(id, entry_iterator);
213 restoring_ = true;
214 Entry* entry = *entry_iterator;
216 // If the entry's ID does not match the ID that is being restored, then the
217 // entry is a window from which a single tab will be restored.
218 bool restoring_tab_in_window = entry->id != id;
220 if (!restoring_tab_in_window) {
221 entries_.erase(entry_iterator);
222 entry_iterator = entries_.end();
225 // |delegate| will be NULL in cases where one isn't already available (eg,
226 // when invoked on Mac OS X with no windows open). In this case, create a
227 // new browser into which we restore the tabs.
228 std::vector<WebContents*> web_contents;
229 if (entry->type == TabRestoreService::TAB) {
230 Tab* tab = static_cast<Tab*>(entry);
231 WebContents* restored_tab = NULL;
232 delegate = RestoreTab(*tab, delegate, host_desktop_type, disposition,
233 &restored_tab);
234 web_contents.push_back(restored_tab);
235 delegate->ShowBrowserWindow();
236 } else if (entry->type == TabRestoreService::WINDOW) {
237 TabRestoreServiceDelegate* current_delegate = delegate;
238 Window* window = static_cast<Window*>(entry);
240 // When restoring a window, either the entire window can be restored, or a
241 // single tab within it. If the entry's ID matches the one to restore, then
242 // the entire window will be restored.
243 if (!restoring_tab_in_window) {
244 delegate = TabRestoreServiceDelegate::Create(profile_, host_desktop_type,
245 window->app_name);
246 for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) {
247 const Tab& tab = window->tabs[tab_i];
248 WebContents* restored_tab = delegate->AddRestoredTab(
249 tab.navigations,
250 delegate->GetTabCount(),
251 tab.current_navigation_index,
252 tab.extension_app_id,
253 static_cast<int>(tab_i) == window->selected_tab_index,
254 tab.pinned,
255 tab.from_last_session,
256 tab.session_storage_namespace.get(),
257 tab.user_agent_override);
258 if (restored_tab) {
259 restored_tab->GetController().LoadIfNecessary();
260 RecordAppLaunch(profile_, tab);
261 web_contents.push_back(restored_tab);
264 // All the window's tabs had the same former browser_id.
265 if (window->tabs[0].has_browser()) {
266 UpdateTabBrowserIDs(window->tabs[0].browser_id,
267 delegate->GetSessionID().id());
269 } else {
270 // Restore a single tab from the window. Find the tab that matches the ID
271 // in the window and restore it.
272 for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
273 tab_i != window->tabs.end(); ++tab_i) {
274 const Tab& tab = *tab_i;
275 if (tab.id == id) {
276 WebContents* restored_tab = NULL;
277 delegate = RestoreTab(tab, delegate, host_desktop_type, disposition,
278 &restored_tab);
279 web_contents.push_back(restored_tab);
280 window->tabs.erase(tab_i);
281 // If restoring the tab leaves the window with nothing else, delete it
282 // as well.
283 if (!window->tabs.size()) {
284 entries_.erase(entry_iterator);
285 delete entry;
286 } else {
287 // Update the browser ID of the rest of the tabs in the window so if
288 // any one is restored, it goes into the same window as the tab
289 // being restored now.
290 UpdateTabBrowserIDs(tab.browser_id,
291 delegate->GetSessionID().id());
292 for (std::vector<Tab>::iterator tab_j = window->tabs.begin();
293 tab_j != window->tabs.end(); ++tab_j) {
294 (*tab_j).browser_id = delegate->GetSessionID().id();
297 break;
301 delegate->ShowBrowserWindow();
303 if (disposition == CURRENT_TAB && current_delegate &&
304 current_delegate->GetActiveWebContents()) {
305 current_delegate->CloseTab();
307 } else {
308 NOTREACHED();
311 if (!restoring_tab_in_window) {
312 delete entry;
315 restoring_ = false;
316 NotifyTabsChanged();
317 return web_contents;
320 void TabRestoreServiceHelper::NotifyTabsChanged() {
321 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
322 TabRestoreServiceChanged(tab_restore_service_));
325 void TabRestoreServiceHelper::NotifyLoaded() {
326 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
327 TabRestoreServiceLoaded(tab_restore_service_));
330 void TabRestoreServiceHelper::AddEntry(Entry* entry,
331 bool notify,
332 bool to_front) {
333 if (!FilterEntry(entry) || (entries_.size() >= kMaxEntries && !to_front)) {
334 delete entry;
335 return;
338 if (to_front)
339 entries_.push_front(entry);
340 else
341 entries_.push_back(entry);
343 PruneEntries();
345 if (notify)
346 NotifyTabsChanged();
348 if (observer_)
349 observer_->OnAddEntry();
352 void TabRestoreServiceHelper::PruneEntries() {
353 Entries new_entries;
355 for (TabRestoreService::Entries::const_iterator iter = entries_.begin();
356 iter != entries_.end(); ++iter) {
357 TabRestoreService::Entry* entry = *iter;
359 if (FilterEntry(entry) &&
360 new_entries.size() < kMaxEntries) {
361 new_entries.push_back(entry);
362 } else {
363 delete entry;
367 entries_ = new_entries;
370 TabRestoreService::Entries::iterator
371 TabRestoreServiceHelper::GetEntryIteratorById(SessionID::id_type id) {
372 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
373 if ((*i)->id == id)
374 return i;
376 // For Window entries, see if the ID matches a tab. If so, report the window
377 // as the Entry.
378 if ((*i)->type == TabRestoreService::WINDOW) {
379 std::vector<Tab>& tabs = static_cast<Window*>(*i)->tabs;
380 for (std::vector<Tab>::iterator j = tabs.begin();
381 j != tabs.end(); ++j) {
382 if ((*j).id == id) {
383 return i;
388 return entries_.end();
391 // static
392 bool TabRestoreServiceHelper::ValidateEntry(Entry* entry) {
393 if (entry->type == TabRestoreService::TAB)
394 return ValidateTab(static_cast<Tab*>(entry));
396 if (entry->type == TabRestoreService::WINDOW)
397 return ValidateWindow(static_cast<Window*>(entry));
399 NOTREACHED();
400 return false;
403 void TabRestoreServiceHelper::PopulateTab(
404 Tab* tab,
405 int index,
406 TabRestoreServiceDelegate* delegate,
407 NavigationController* controller) {
408 const int pending_index = controller->GetPendingEntryIndex();
409 int entry_count = controller->GetEntryCount();
410 if (entry_count == 0 && pending_index == 0)
411 entry_count++;
412 tab->navigations.resize(static_cast<int>(entry_count));
413 for (int i = 0; i < entry_count; ++i) {
414 NavigationEntry* entry = (i == pending_index) ?
415 controller->GetPendingEntry() : controller->GetEntryAtIndex(i);
416 tab->navigations[i] =
417 sessions::ContentSerializedNavigationBuilder::FromNavigationEntry(
418 i, *entry);
420 tab->timestamp = TimeNow();
421 tab->current_navigation_index = controller->GetCurrentEntryIndex();
422 if (tab->current_navigation_index == -1 && entry_count > 0)
423 tab->current_navigation_index = 0;
424 tab->tabstrip_index = index;
426 #if defined(ENABLE_EXTENSIONS)
427 extensions::TabHelper* extensions_tab_helper =
428 extensions::TabHelper::FromWebContents(controller->GetWebContents());
429 // extensions_tab_helper is NULL in some browser tests.
430 if (extensions_tab_helper) {
431 const extensions::Extension* extension =
432 extensions_tab_helper->extension_app();
433 if (extension)
434 tab->extension_app_id = extension->id();
436 #endif
438 tab->user_agent_override =
439 controller->GetWebContents()->GetUserAgentOverride();
441 // TODO(ajwong): This does not correctly handle storage for isolated apps.
442 tab->session_storage_namespace =
443 controller->GetDefaultSessionStorageNamespace();
445 // Delegate may be NULL during unit tests.
446 if (delegate) {
447 tab->browser_id = delegate->GetSessionID().id();
448 tab->pinned = delegate->IsTabPinned(tab->tabstrip_index);
452 TabRestoreServiceDelegate* TabRestoreServiceHelper::RestoreTab(
453 const Tab& tab,
454 TabRestoreServiceDelegate* delegate,
455 chrome::HostDesktopType host_desktop_type,
456 WindowOpenDisposition disposition,
457 WebContents** contents) {
458 WebContents* web_contents;
459 if (disposition == CURRENT_TAB && delegate) {
460 web_contents = delegate->ReplaceRestoredTab(
461 tab.navigations,
462 tab.current_navigation_index,
463 tab.from_last_session,
464 tab.extension_app_id,
465 tab.session_storage_namespace.get(),
466 tab.user_agent_override);
467 } else {
468 // We only respsect the tab's original browser if there's no disposition.
469 if (disposition == UNKNOWN && tab.has_browser()) {
470 delegate = TabRestoreServiceDelegate::FindDelegateWithID(
471 tab.browser_id, host_desktop_type);
474 int tab_index = -1;
476 // |delegate| will be NULL in cases where one isn't already available (eg,
477 // when invoked on Mac OS X with no windows open). In this case, create a
478 // new browser into which we restore the tabs.
479 if (delegate && disposition != NEW_WINDOW) {
480 tab_index = tab.tabstrip_index;
481 } else {
482 delegate = TabRestoreServiceDelegate::Create(profile_, host_desktop_type,
483 std::string());
484 if (tab.has_browser())
485 UpdateTabBrowserIDs(tab.browser_id, delegate->GetSessionID().id());
488 // Place the tab at the end if the tab index is no longer valid or
489 // we were passed a specific disposition.
490 if (tab_index < 0 || tab_index > delegate->GetTabCount() ||
491 disposition != UNKNOWN) {
492 tab_index = delegate->GetTabCount();
495 web_contents = delegate->AddRestoredTab(tab.navigations,
496 tab_index,
497 tab.current_navigation_index,
498 tab.extension_app_id,
499 disposition != NEW_BACKGROUND_TAB,
500 tab.pinned,
501 tab.from_last_session,
502 tab.session_storage_namespace.get(),
503 tab.user_agent_override);
504 web_contents->GetController().LoadIfNecessary();
506 RecordAppLaunch(profile_, tab);
507 if (contents)
508 *contents = web_contents;
510 return delegate;
514 bool TabRestoreServiceHelper::ValidateTab(Tab* tab) {
515 if (tab->navigations.empty())
516 return false;
518 tab->current_navigation_index =
519 std::max(0, std::min(tab->current_navigation_index,
520 static_cast<int>(tab->navigations.size()) - 1));
522 return true;
525 bool TabRestoreServiceHelper::ValidateWindow(Window* window) {
526 window->selected_tab_index =
527 std::max(0, std::min(window->selected_tab_index,
528 static_cast<int>(window->tabs.size() - 1)));
530 int i = 0;
531 for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
532 tab_i != window->tabs.end();) {
533 if (!ValidateTab(&(*tab_i))) {
534 tab_i = window->tabs.erase(tab_i);
535 if (i < window->selected_tab_index)
536 window->selected_tab_index--;
537 else if (i == window->selected_tab_index)
538 window->selected_tab_index = 0;
539 } else {
540 ++tab_i;
541 ++i;
545 if (window->tabs.empty())
546 return false;
548 return true;
551 bool TabRestoreServiceHelper::IsTabInteresting(const Tab* tab) {
552 if (tab->navigations.empty())
553 return false;
555 if (tab->navigations.size() > 1)
556 return true;
558 return tab->pinned ||
559 tab->navigations.at(0).virtual_url() !=
560 GURL(chrome::kChromeUINewTabURL);
563 bool TabRestoreServiceHelper::IsWindowInteresting(const Window* window) {
564 if (window->tabs.empty())
565 return false;
567 if (window->tabs.size() > 1)
568 return true;
570 return IsTabInteresting(&window->tabs[0]);
573 bool TabRestoreServiceHelper::FilterEntry(Entry* entry) {
574 if (!ValidateEntry(entry))
575 return false;
577 if (entry->type == TabRestoreService::TAB)
578 return IsTabInteresting(static_cast<Tab*>(entry));
579 else if (entry->type == TabRestoreService::WINDOW)
580 return IsWindowInteresting(static_cast<Window*>(entry));
582 NOTREACHED();
583 return false;
586 void TabRestoreServiceHelper::UpdateTabBrowserIDs(SessionID::id_type old_id,
587 SessionID::id_type new_id) {
588 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
589 Entry* entry = *i;
590 if (entry->type == TabRestoreService::TAB) {
591 Tab* tab = static_cast<Tab*>(entry);
592 if (tab->browser_id == old_id)
593 tab->browser_id = new_id;
598 base::Time TabRestoreServiceHelper::TimeNow() const {
599 return time_factory_ ? time_factory_->TimeNow() : base::Time::Now();