Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / sessions / tab_restore_service_helper.cc
blobe33a298d98073d86aaf7e1d7d7890d567af171c4
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/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/extensions/extension_service.h"
14 #include "chrome/browser/extensions/tab_helper.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/sessions/session_types.h"
17 #include "chrome/browser/sessions/tab_restore_service_delegate.h"
18 #include "chrome/browser/sessions/tab_restore_service_observer.h"
19 #include "chrome/common/extensions/extension_constants.h"
20 #include "chrome/common/url_constants.h"
21 #include "content/public/browser/navigation_controller.h"
22 #include "content/public/browser/navigation_entry.h"
23 #include "content/public/browser/session_storage_namespace.h"
24 #include "content/public/browser/web_contents.h"
25 #include "extensions/common/extension.h"
27 #if !defined(OS_ANDROID)
28 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
29 #endif
31 using content::NavigationController;
32 using content::NavigationEntry;
33 using content::WebContents;
35 namespace {
37 void RecordAppLaunch(Profile* profile, const TabRestoreService::Tab& tab) {
38 #if !defined(OS_ANDROID)
39 GURL url = tab.navigations.at(tab.current_navigation_index).virtual_url();
40 DCHECK(profile->GetExtensionService());
41 const extensions::Extension* extension =
42 profile->GetExtensionService()->GetInstalledApp(url);
43 if (!extension)
44 return;
46 CoreAppLauncherHandler::RecordAppLaunchType(
47 extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED,
48 extension->GetType());
49 #endif // !defined(OS_ANDROID)
52 } // namespace
54 // TabRestoreServiceHelper::Observer -------------------------------------------
56 TabRestoreServiceHelper::Observer::~Observer() {}
58 void TabRestoreServiceHelper::Observer::OnClearEntries() {}
60 void TabRestoreServiceHelper::Observer::OnRestoreEntryById(
61 SessionID::id_type id,
62 Entries::const_iterator entry_iterator) {
65 void TabRestoreServiceHelper::Observer::OnAddEntry() {}
67 // TabRestoreServiceHelper -----------------------------------------------------
69 TabRestoreServiceHelper::TabRestoreServiceHelper(
70 TabRestoreService* tab_restore_service,
71 Observer* observer,
72 Profile* profile,
73 TabRestoreService::TimeFactory* time_factory)
74 : tab_restore_service_(tab_restore_service),
75 observer_(observer),
76 profile_(profile),
77 restoring_(false),
78 time_factory_(time_factory) {
79 DCHECK(tab_restore_service_);
82 TabRestoreServiceHelper::~TabRestoreServiceHelper() {
83 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
84 TabRestoreServiceDestroyed(tab_restore_service_));
85 STLDeleteElements(&entries_);
88 void TabRestoreServiceHelper::AddObserver(
89 TabRestoreServiceObserver* observer) {
90 observer_list_.AddObserver(observer);
93 void TabRestoreServiceHelper::RemoveObserver(
94 TabRestoreServiceObserver* observer) {
95 observer_list_.RemoveObserver(observer);
98 void TabRestoreServiceHelper::CreateHistoricalTab(
99 content::WebContents* contents,
100 int index) {
101 if (restoring_)
102 return;
104 TabRestoreServiceDelegate* delegate =
105 TabRestoreServiceDelegate::FindDelegateForWebContents(contents);
106 if (closing_delegates_.find(delegate) != closing_delegates_.end())
107 return;
109 scoped_ptr<Tab> local_tab(new Tab());
110 PopulateTab(local_tab.get(), index, delegate, &contents->GetController());
111 if (local_tab->navigations.empty())
112 return;
114 AddEntry(local_tab.release(), true, true);
117 void TabRestoreServiceHelper::BrowserClosing(
118 TabRestoreServiceDelegate* delegate) {
119 closing_delegates_.insert(delegate);
121 scoped_ptr<Window> window(new Window());
122 window->selected_tab_index = delegate->GetSelectedIndex();
123 window->timestamp = TimeNow();
124 window->app_name = delegate->GetAppName();
126 // Don't use std::vector::resize() because it will push copies of an empty tab
127 // into the vector, which will give all tabs in a window the same ID.
128 for (int i = 0; i < delegate->GetTabCount(); ++i) {
129 window->tabs.push_back(Tab());
131 size_t entry_index = 0;
132 for (int tab_index = 0; tab_index < delegate->GetTabCount(); ++tab_index) {
133 PopulateTab(&(window->tabs[entry_index]),
134 tab_index,
135 delegate,
136 &delegate->GetWebContentsAt(tab_index)->GetController());
137 if (window->tabs[entry_index].navigations.empty()) {
138 window->tabs.erase(window->tabs.begin() + entry_index);
139 } else {
140 window->tabs[entry_index].browser_id = delegate->GetSessionID().id();
141 entry_index++;
144 if (window->tabs.size() == 1 && window->app_name.empty()) {
145 // Short-circuit creating a Window if only 1 tab was present. This fixes
146 // http://crbug.com/56744. Copy the Tab because it's owned by an object on
147 // the stack.
148 AddEntry(new Tab(window->tabs[0]), true, true);
149 } else if (!window->tabs.empty()) {
150 window->selected_tab_index =
151 std::min(static_cast<int>(window->tabs.size() - 1),
152 window->selected_tab_index);
153 AddEntry(window.release(), true, true);
157 void TabRestoreServiceHelper::BrowserClosed(
158 TabRestoreServiceDelegate* delegate) {
159 closing_delegates_.erase(delegate);
162 void TabRestoreServiceHelper::ClearEntries() {
163 if (observer_)
164 observer_->OnClearEntries();
165 STLDeleteElements(&entries_);
166 NotifyTabsChanged();
169 const TabRestoreService::Entries& TabRestoreServiceHelper::entries() const {
170 return entries_;
173 std::vector<content::WebContents*>
174 TabRestoreServiceHelper::RestoreMostRecentEntry(
175 TabRestoreServiceDelegate* delegate,
176 chrome::HostDesktopType host_desktop_type) {
177 if (entries_.empty())
178 return std::vector<WebContents*>();
180 return RestoreEntryById(delegate, entries_.front()->id, host_desktop_type,
181 UNKNOWN);
184 TabRestoreService::Tab* TabRestoreServiceHelper::RemoveTabEntryById(
185 SessionID::id_type id) {
186 Entries::iterator i = GetEntryIteratorById(id);
187 if (i == entries_.end())
188 return NULL;
190 Entry* entry = *i;
191 if (entry->type != TabRestoreService::TAB)
192 return NULL;
194 Tab* tab = static_cast<Tab*>(entry);
195 entries_.erase(i);
196 return tab;
199 std::vector<content::WebContents*> TabRestoreServiceHelper::RestoreEntryById(
200 TabRestoreServiceDelegate* delegate,
201 SessionID::id_type id,
202 chrome::HostDesktopType host_desktop_type,
203 WindowOpenDisposition disposition) {
204 Entries::iterator entry_iterator = GetEntryIteratorById(id);
205 if (entry_iterator == entries_.end())
206 // Don't hoark here, we allow an invalid id.
207 return std::vector<WebContents*>();
209 if (observer_)
210 observer_->OnRestoreEntryById(id, entry_iterator);
211 restoring_ = true;
212 Entry* entry = *entry_iterator;
214 // If the entry's ID does not match the ID that is being restored, then the
215 // entry is a window from which a single tab will be restored.
216 bool restoring_tab_in_window = entry->id != id;
218 if (!restoring_tab_in_window) {
219 entries_.erase(entry_iterator);
220 entry_iterator = entries_.end();
223 // |delegate| will be NULL in cases where one isn't already available (eg,
224 // when invoked on Mac OS X with no windows open). In this case, create a
225 // new browser into which we restore the tabs.
226 std::vector<WebContents*> web_contents;
227 if (entry->type == TabRestoreService::TAB) {
228 Tab* tab = static_cast<Tab*>(entry);
229 WebContents* restored_tab = NULL;
230 delegate = RestoreTab(*tab, delegate, host_desktop_type, disposition,
231 &restored_tab);
232 web_contents.push_back(restored_tab);
233 delegate->ShowBrowserWindow();
234 } else if (entry->type == TabRestoreService::WINDOW) {
235 TabRestoreServiceDelegate* current_delegate = delegate;
236 Window* window = static_cast<Window*>(entry);
238 // When restoring a window, either the entire window can be restored, or a
239 // single tab within it. If the entry's ID matches the one to restore, then
240 // the entire window will be restored.
241 if (!restoring_tab_in_window) {
242 delegate = TabRestoreServiceDelegate::Create(profile_, host_desktop_type,
243 window->app_name);
244 for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) {
245 const Tab& tab = window->tabs[tab_i];
246 WebContents* restored_tab = delegate->AddRestoredTab(
247 tab.navigations,
248 delegate->GetTabCount(),
249 tab.current_navigation_index,
250 tab.extension_app_id,
251 static_cast<int>(tab_i) == window->selected_tab_index,
252 tab.pinned,
253 tab.from_last_session,
254 tab.session_storage_namespace.get(),
255 tab.user_agent_override);
256 if (restored_tab) {
257 restored_tab->GetController().LoadIfNecessary();
258 RecordAppLaunch(profile_, tab);
259 web_contents.push_back(restored_tab);
262 // All the window's tabs had the same former browser_id.
263 if (window->tabs[0].has_browser()) {
264 UpdateTabBrowserIDs(window->tabs[0].browser_id,
265 delegate->GetSessionID().id());
267 } else {
268 // Restore a single tab from the window. Find the tab that matches the ID
269 // in the window and restore it.
270 for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
271 tab_i != window->tabs.end(); ++tab_i) {
272 const Tab& tab = *tab_i;
273 if (tab.id == id) {
274 WebContents* restored_tab = NULL;
275 delegate = RestoreTab(tab, delegate, host_desktop_type, disposition,
276 &restored_tab);
277 web_contents.push_back(restored_tab);
278 window->tabs.erase(tab_i);
279 // If restoring the tab leaves the window with nothing else, delete it
280 // as well.
281 if (!window->tabs.size()) {
282 entries_.erase(entry_iterator);
283 delete entry;
284 } else {
285 // Update the browser ID of the rest of the tabs in the window so if
286 // any one is restored, it goes into the same window as the tab
287 // being restored now.
288 UpdateTabBrowserIDs(tab.browser_id,
289 delegate->GetSessionID().id());
290 for (std::vector<Tab>::iterator tab_j = window->tabs.begin();
291 tab_j != window->tabs.end(); ++tab_j) {
292 (*tab_j).browser_id = delegate->GetSessionID().id();
295 break;
299 delegate->ShowBrowserWindow();
301 if (disposition == CURRENT_TAB && current_delegate &&
302 current_delegate->GetActiveWebContents()) {
303 current_delegate->CloseTab();
305 } else {
306 NOTREACHED();
309 if (!restoring_tab_in_window) {
310 delete entry;
313 restoring_ = false;
314 NotifyTabsChanged();
315 return web_contents;
318 void TabRestoreServiceHelper::NotifyTabsChanged() {
319 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
320 TabRestoreServiceChanged(tab_restore_service_));
323 void TabRestoreServiceHelper::NotifyLoaded() {
324 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
325 TabRestoreServiceLoaded(tab_restore_service_));
328 void TabRestoreServiceHelper::AddEntry(Entry* entry,
329 bool notify,
330 bool to_front) {
331 if (!FilterEntry(entry) || (entries_.size() >= kMaxEntries && !to_front)) {
332 delete entry;
333 return;
336 if (to_front)
337 entries_.push_front(entry);
338 else
339 entries_.push_back(entry);
341 PruneEntries();
343 if (notify)
344 NotifyTabsChanged();
346 if (observer_)
347 observer_->OnAddEntry();
350 void TabRestoreServiceHelper::PruneEntries() {
351 Entries new_entries;
353 for (TabRestoreService::Entries::const_iterator iter = entries_.begin();
354 iter != entries_.end(); ++iter) {
355 TabRestoreService::Entry* entry = *iter;
357 if (FilterEntry(entry) &&
358 new_entries.size() < kMaxEntries) {
359 new_entries.push_back(entry);
360 } else {
361 delete entry;
365 entries_ = new_entries;
368 TabRestoreService::Entries::iterator
369 TabRestoreServiceHelper::GetEntryIteratorById(SessionID::id_type id) {
370 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
371 if ((*i)->id == id)
372 return i;
374 // For Window entries, see if the ID matches a tab. If so, report the window
375 // as the Entry.
376 if ((*i)->type == TabRestoreService::WINDOW) {
377 std::vector<Tab>& tabs = static_cast<Window*>(*i)->tabs;
378 for (std::vector<Tab>::iterator j = tabs.begin();
379 j != tabs.end(); ++j) {
380 if ((*j).id == id) {
381 return i;
386 return entries_.end();
389 // static
390 bool TabRestoreServiceHelper::ValidateEntry(Entry* entry) {
391 if (entry->type == TabRestoreService::TAB)
392 return ValidateTab(static_cast<Tab*>(entry));
394 if (entry->type == TabRestoreService::WINDOW)
395 return ValidateWindow(static_cast<Window*>(entry));
397 NOTREACHED();
398 return false;
401 void TabRestoreServiceHelper::PopulateTab(
402 Tab* tab,
403 int index,
404 TabRestoreServiceDelegate* delegate,
405 NavigationController* controller) {
406 const int pending_index = controller->GetPendingEntryIndex();
407 int entry_count = controller->GetEntryCount();
408 if (entry_count == 0 && pending_index == 0)
409 entry_count++;
410 tab->navigations.resize(static_cast<int>(entry_count));
411 for (int i = 0; i < entry_count; ++i) {
412 NavigationEntry* entry = (i == pending_index) ?
413 controller->GetPendingEntry() : controller->GetEntryAtIndex(i);
414 tab->navigations[i] =
415 sessions::SerializedNavigationEntry::FromNavigationEntry(i, *entry);
417 tab->timestamp = TimeNow();
418 tab->current_navigation_index = controller->GetCurrentEntryIndex();
419 if (tab->current_navigation_index == -1 && entry_count > 0)
420 tab->current_navigation_index = 0;
421 tab->tabstrip_index = index;
423 extensions::TabHelper* extensions_tab_helper =
424 extensions::TabHelper::FromWebContents(controller->GetWebContents());
425 // extensions_tab_helper is NULL in some browser tests.
426 if (extensions_tab_helper) {
427 const extensions::Extension* extension =
428 extensions_tab_helper->extension_app();
429 if (extension)
430 tab->extension_app_id = extension->id();
433 tab->user_agent_override =
434 controller->GetWebContents()->GetUserAgentOverride();
436 // TODO(ajwong): This does not correctly handle storage for isolated apps.
437 tab->session_storage_namespace =
438 controller->GetDefaultSessionStorageNamespace();
440 // Delegate may be NULL during unit tests.
441 if (delegate) {
442 tab->browser_id = delegate->GetSessionID().id();
443 tab->pinned = delegate->IsTabPinned(tab->tabstrip_index);
447 TabRestoreServiceDelegate* TabRestoreServiceHelper::RestoreTab(
448 const Tab& tab,
449 TabRestoreServiceDelegate* delegate,
450 chrome::HostDesktopType host_desktop_type,
451 WindowOpenDisposition disposition,
452 WebContents** contents) {
453 WebContents* web_contents;
454 if (disposition == CURRENT_TAB && delegate) {
455 web_contents = delegate->ReplaceRestoredTab(
456 tab.navigations,
457 tab.current_navigation_index,
458 tab.from_last_session,
459 tab.extension_app_id,
460 tab.session_storage_namespace.get(),
461 tab.user_agent_override);
462 } else {
463 // We only respsect the tab's original browser if there's no disposition.
464 if (disposition == UNKNOWN && tab.has_browser()) {
465 delegate = TabRestoreServiceDelegate::FindDelegateWithID(
466 tab.browser_id, host_desktop_type);
469 int tab_index = -1;
471 // |delegate| will be NULL in cases where one isn't already available (eg,
472 // when invoked on Mac OS X with no windows open). In this case, create a
473 // new browser into which we restore the tabs.
474 if (delegate && disposition != NEW_WINDOW) {
475 tab_index = tab.tabstrip_index;
476 } else {
477 delegate = TabRestoreServiceDelegate::Create(profile_, host_desktop_type,
478 std::string());
479 if (tab.has_browser())
480 UpdateTabBrowserIDs(tab.browser_id, delegate->GetSessionID().id());
483 // Place the tab at the end if the tab index is no longer valid or
484 // we were passed a specific disposition.
485 if (tab_index < 0 || tab_index > delegate->GetTabCount() ||
486 disposition != UNKNOWN) {
487 tab_index = delegate->GetTabCount();
490 web_contents = delegate->AddRestoredTab(tab.navigations,
491 tab_index,
492 tab.current_navigation_index,
493 tab.extension_app_id,
494 disposition != NEW_BACKGROUND_TAB,
495 tab.pinned,
496 tab.from_last_session,
497 tab.session_storage_namespace.get(),
498 tab.user_agent_override);
499 web_contents->GetController().LoadIfNecessary();
501 RecordAppLaunch(profile_, tab);
502 if (contents)
503 *contents = web_contents;
505 return delegate;
509 bool TabRestoreServiceHelper::ValidateTab(Tab* tab) {
510 if (tab->navigations.empty())
511 return false;
513 tab->current_navigation_index =
514 std::max(0, std::min(tab->current_navigation_index,
515 static_cast<int>(tab->navigations.size()) - 1));
517 return true;
520 bool TabRestoreServiceHelper::ValidateWindow(Window* window) {
521 window->selected_tab_index =
522 std::max(0, std::min(window->selected_tab_index,
523 static_cast<int>(window->tabs.size() - 1)));
525 int i = 0;
526 for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
527 tab_i != window->tabs.end();) {
528 if (!ValidateTab(&(*tab_i))) {
529 tab_i = window->tabs.erase(tab_i);
530 if (i < window->selected_tab_index)
531 window->selected_tab_index--;
532 else if (i == window->selected_tab_index)
533 window->selected_tab_index = 0;
534 } else {
535 ++tab_i;
536 ++i;
540 if (window->tabs.empty())
541 return false;
543 return true;
546 bool TabRestoreServiceHelper::IsTabInteresting(const Tab* tab) {
547 if (tab->navigations.empty())
548 return false;
550 if (tab->navigations.size() > 1)
551 return true;
553 return tab->pinned ||
554 tab->navigations.at(0).virtual_url() !=
555 GURL(chrome::kChromeUINewTabURL);
558 bool TabRestoreServiceHelper::IsWindowInteresting(const Window* window) {
559 if (window->tabs.empty())
560 return false;
562 if (window->tabs.size() > 1)
563 return true;
565 return IsTabInteresting(&window->tabs[0]);
568 bool TabRestoreServiceHelper::FilterEntry(Entry* entry) {
569 if (!ValidateEntry(entry))
570 return false;
572 if (entry->type == TabRestoreService::TAB)
573 return IsTabInteresting(static_cast<Tab*>(entry));
574 else if (entry->type == TabRestoreService::WINDOW)
575 return IsWindowInteresting(static_cast<Window*>(entry));
577 NOTREACHED();
578 return false;
581 void TabRestoreServiceHelper::UpdateTabBrowserIDs(SessionID::id_type old_id,
582 SessionID::id_type new_id) {
583 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
584 Entry* entry = *i;
585 if (entry->type == TabRestoreService::TAB) {
586 Tab* tab = static_cast<Tab*>(entry);
587 if (tab->browser_id == old_id)
588 tab->browser_id = new_id;
593 base::Time TabRestoreServiceHelper::TimeNow() const {
594 return time_factory_ ? time_factory_->TimeNow() : base::Time::Now();