Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / sessions / tab_restore_service_helper.cc
blobb5ec2580218bc9c27807070804c0388e7db88682
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/profiles/profile.h"
14 #include "chrome/browser/sessions/session_types.h"
15 #include "chrome/browser/sessions/tab_restore_service_delegate.h"
16 #include "chrome/browser/sessions/tab_restore_service_observer.h"
17 #include "chrome/common/url_constants.h"
18 #include "content/public/browser/navigation_controller.h"
19 #include "content/public/browser/navigation_entry.h"
20 #include "content/public/browser/session_storage_namespace.h"
21 #include "content/public/browser/web_contents.h"
23 #if defined(ENABLE_EXTENSIONS)
24 #include "chrome/browser/extensions/tab_helper.h"
25 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
26 #include "chrome/common/extensions/extension_constants.h"
27 #include "extensions/browser/extension_registry.h"
28 #include "extensions/common/extension.h"
29 #include "extensions/common/extension_set.h"
30 #endif
32 using content::NavigationController;
33 using content::NavigationEntry;
34 using content::WebContents;
36 namespace {
38 void RecordAppLaunch(Profile* profile, const TabRestoreService::Tab& tab) {
39 #if defined(ENABLE_EXTENSIONS)
40 GURL url = tab.navigations.at(tab.current_navigation_index).virtual_url();
41 const extensions::Extension* extension =
42 extensions::ExtensionRegistry::Get(profile)
43 ->enabled_extensions().GetAppByURL(url);
44 if (!extension)
45 return;
47 CoreAppLauncherHandler::RecordAppLaunchType(
48 extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED,
49 extension->GetType());
50 #endif // defined(ENABLE_EXTENSIONS)
53 } // namespace
55 // TabRestoreServiceHelper::Observer -------------------------------------------
57 TabRestoreServiceHelper::Observer::~Observer() {}
59 void TabRestoreServiceHelper::Observer::OnClearEntries() {}
61 void TabRestoreServiceHelper::Observer::OnRestoreEntryById(
62 SessionID::id_type id,
63 Entries::const_iterator entry_iterator) {
66 void TabRestoreServiceHelper::Observer::OnAddEntry() {}
68 // TabRestoreServiceHelper -----------------------------------------------------
70 TabRestoreServiceHelper::TabRestoreServiceHelper(
71 TabRestoreService* tab_restore_service,
72 Observer* observer,
73 Profile* profile,
74 TabRestoreService::TimeFactory* time_factory)
75 : tab_restore_service_(tab_restore_service),
76 observer_(observer),
77 profile_(profile),
78 restoring_(false),
79 time_factory_(time_factory) {
80 DCHECK(tab_restore_service_);
83 TabRestoreServiceHelper::~TabRestoreServiceHelper() {
84 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
85 TabRestoreServiceDestroyed(tab_restore_service_));
86 STLDeleteElements(&entries_);
89 void TabRestoreServiceHelper::AddObserver(
90 TabRestoreServiceObserver* observer) {
91 observer_list_.AddObserver(observer);
94 void TabRestoreServiceHelper::RemoveObserver(
95 TabRestoreServiceObserver* observer) {
96 observer_list_.RemoveObserver(observer);
99 void TabRestoreServiceHelper::CreateHistoricalTab(
100 content::WebContents* contents,
101 int index) {
102 if (restoring_)
103 return;
105 TabRestoreServiceDelegate* delegate =
106 TabRestoreServiceDelegate::FindDelegateForWebContents(contents);
107 if (closing_delegates_.find(delegate) != closing_delegates_.end())
108 return;
110 scoped_ptr<Tab> local_tab(new Tab());
111 PopulateTab(local_tab.get(), index, delegate, &contents->GetController());
112 if (local_tab->navigations.empty())
113 return;
115 AddEntry(local_tab.release(), true, true);
118 void TabRestoreServiceHelper::BrowserClosing(
119 TabRestoreServiceDelegate* delegate) {
120 closing_delegates_.insert(delegate);
122 scoped_ptr<Window> window(new Window());
123 window->selected_tab_index = delegate->GetSelectedIndex();
124 window->timestamp = TimeNow();
125 window->app_name = delegate->GetAppName();
127 // Don't use std::vector::resize() because it will push copies of an empty tab
128 // into the vector, which will give all tabs in a window the same ID.
129 for (int i = 0; i < delegate->GetTabCount(); ++i) {
130 window->tabs.push_back(Tab());
132 size_t entry_index = 0;
133 for (int tab_index = 0; tab_index < delegate->GetTabCount(); ++tab_index) {
134 PopulateTab(&(window->tabs[entry_index]),
135 tab_index,
136 delegate,
137 &delegate->GetWebContentsAt(tab_index)->GetController());
138 if (window->tabs[entry_index].navigations.empty()) {
139 window->tabs.erase(window->tabs.begin() + entry_index);
140 } else {
141 window->tabs[entry_index].browser_id = delegate->GetSessionID().id();
142 entry_index++;
145 if (window->tabs.size() == 1 && window->app_name.empty()) {
146 // Short-circuit creating a Window if only 1 tab was present. This fixes
147 // http://crbug.com/56744. Copy the Tab because it's owned by an object on
148 // the stack.
149 AddEntry(new Tab(window->tabs[0]), true, true);
150 } else if (!window->tabs.empty()) {
151 window->selected_tab_index =
152 std::min(static_cast<int>(window->tabs.size() - 1),
153 window->selected_tab_index);
154 AddEntry(window.release(), true, true);
158 void TabRestoreServiceHelper::BrowserClosed(
159 TabRestoreServiceDelegate* delegate) {
160 closing_delegates_.erase(delegate);
163 void TabRestoreServiceHelper::ClearEntries() {
164 if (observer_)
165 observer_->OnClearEntries();
166 STLDeleteElements(&entries_);
167 NotifyTabsChanged();
170 const TabRestoreService::Entries& TabRestoreServiceHelper::entries() const {
171 return entries_;
174 std::vector<content::WebContents*>
175 TabRestoreServiceHelper::RestoreMostRecentEntry(
176 TabRestoreServiceDelegate* delegate,
177 chrome::HostDesktopType host_desktop_type) {
178 if (entries_.empty())
179 return std::vector<WebContents*>();
181 return RestoreEntryById(delegate, entries_.front()->id, host_desktop_type,
182 UNKNOWN);
185 TabRestoreService::Tab* TabRestoreServiceHelper::RemoveTabEntryById(
186 SessionID::id_type id) {
187 Entries::iterator i = GetEntryIteratorById(id);
188 if (i == entries_.end())
189 return NULL;
191 Entry* entry = *i;
192 if (entry->type != TabRestoreService::TAB)
193 return NULL;
195 Tab* tab = static_cast<Tab*>(entry);
196 entries_.erase(i);
197 return tab;
200 std::vector<content::WebContents*> TabRestoreServiceHelper::RestoreEntryById(
201 TabRestoreServiceDelegate* delegate,
202 SessionID::id_type id,
203 chrome::HostDesktopType host_desktop_type,
204 WindowOpenDisposition disposition) {
205 Entries::iterator entry_iterator = GetEntryIteratorById(id);
206 if (entry_iterator == entries_.end())
207 // Don't hoark here, we allow an invalid id.
208 return std::vector<WebContents*>();
210 if (observer_)
211 observer_->OnRestoreEntryById(id, entry_iterator);
212 restoring_ = true;
213 Entry* entry = *entry_iterator;
215 // If the entry's ID does not match the ID that is being restored, then the
216 // entry is a window from which a single tab will be restored.
217 bool restoring_tab_in_window = entry->id != id;
219 if (!restoring_tab_in_window) {
220 entries_.erase(entry_iterator);
221 entry_iterator = entries_.end();
224 // |delegate| will be NULL in cases where one isn't already available (eg,
225 // when invoked on Mac OS X with no windows open). In this case, create a
226 // new browser into which we restore the tabs.
227 std::vector<WebContents*> web_contents;
228 if (entry->type == TabRestoreService::TAB) {
229 Tab* tab = static_cast<Tab*>(entry);
230 WebContents* restored_tab = NULL;
231 delegate = RestoreTab(*tab, delegate, host_desktop_type, disposition,
232 &restored_tab);
233 web_contents.push_back(restored_tab);
234 delegate->ShowBrowserWindow();
235 } else if (entry->type == TabRestoreService::WINDOW) {
236 TabRestoreServiceDelegate* current_delegate = delegate;
237 Window* window = static_cast<Window*>(entry);
239 // When restoring a window, either the entire window can be restored, or a
240 // single tab within it. If the entry's ID matches the one to restore, then
241 // the entire window will be restored.
242 if (!restoring_tab_in_window) {
243 delegate = TabRestoreServiceDelegate::Create(profile_, host_desktop_type,
244 window->app_name);
245 for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) {
246 const Tab& tab = window->tabs[tab_i];
247 WebContents* restored_tab = delegate->AddRestoredTab(
248 tab.navigations,
249 delegate->GetTabCount(),
250 tab.current_navigation_index,
251 tab.extension_app_id,
252 static_cast<int>(tab_i) == window->selected_tab_index,
253 tab.pinned,
254 tab.from_last_session,
255 tab.session_storage_namespace.get(),
256 tab.user_agent_override);
257 if (restored_tab) {
258 restored_tab->GetController().LoadIfNecessary();
259 RecordAppLaunch(profile_, tab);
260 web_contents.push_back(restored_tab);
263 // All the window's tabs had the same former browser_id.
264 if (window->tabs[0].has_browser()) {
265 UpdateTabBrowserIDs(window->tabs[0].browser_id,
266 delegate->GetSessionID().id());
268 } else {
269 // Restore a single tab from the window. Find the tab that matches the ID
270 // in the window and restore it.
271 for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
272 tab_i != window->tabs.end(); ++tab_i) {
273 const Tab& tab = *tab_i;
274 if (tab.id == id) {
275 WebContents* restored_tab = NULL;
276 delegate = RestoreTab(tab, delegate, host_desktop_type, disposition,
277 &restored_tab);
278 web_contents.push_back(restored_tab);
279 window->tabs.erase(tab_i);
280 // If restoring the tab leaves the window with nothing else, delete it
281 // as well.
282 if (!window->tabs.size()) {
283 entries_.erase(entry_iterator);
284 delete entry;
285 } else {
286 // Update the browser ID of the rest of the tabs in the window so if
287 // any one is restored, it goes into the same window as the tab
288 // being restored now.
289 UpdateTabBrowserIDs(tab.browser_id,
290 delegate->GetSessionID().id());
291 for (std::vector<Tab>::iterator tab_j = window->tabs.begin();
292 tab_j != window->tabs.end(); ++tab_j) {
293 (*tab_j).browser_id = delegate->GetSessionID().id();
296 break;
300 delegate->ShowBrowserWindow();
302 if (disposition == CURRENT_TAB && current_delegate &&
303 current_delegate->GetActiveWebContents()) {
304 current_delegate->CloseTab();
306 } else {
307 NOTREACHED();
310 if (!restoring_tab_in_window) {
311 delete entry;
314 restoring_ = false;
315 NotifyTabsChanged();
316 return web_contents;
319 void TabRestoreServiceHelper::NotifyTabsChanged() {
320 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
321 TabRestoreServiceChanged(tab_restore_service_));
324 void TabRestoreServiceHelper::NotifyLoaded() {
325 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
326 TabRestoreServiceLoaded(tab_restore_service_));
329 void TabRestoreServiceHelper::AddEntry(Entry* entry,
330 bool notify,
331 bool to_front) {
332 if (!FilterEntry(entry) || (entries_.size() >= kMaxEntries && !to_front)) {
333 delete entry;
334 return;
337 if (to_front)
338 entries_.push_front(entry);
339 else
340 entries_.push_back(entry);
342 PruneEntries();
344 if (notify)
345 NotifyTabsChanged();
347 if (observer_)
348 observer_->OnAddEntry();
351 void TabRestoreServiceHelper::PruneEntries() {
352 Entries new_entries;
354 for (TabRestoreService::Entries::const_iterator iter = entries_.begin();
355 iter != entries_.end(); ++iter) {
356 TabRestoreService::Entry* entry = *iter;
358 if (FilterEntry(entry) &&
359 new_entries.size() < kMaxEntries) {
360 new_entries.push_back(entry);
361 } else {
362 delete entry;
366 entries_ = new_entries;
369 TabRestoreService::Entries::iterator
370 TabRestoreServiceHelper::GetEntryIteratorById(SessionID::id_type id) {
371 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
372 if ((*i)->id == id)
373 return i;
375 // For Window entries, see if the ID matches a tab. If so, report the window
376 // as the Entry.
377 if ((*i)->type == TabRestoreService::WINDOW) {
378 std::vector<Tab>& tabs = static_cast<Window*>(*i)->tabs;
379 for (std::vector<Tab>::iterator j = tabs.begin();
380 j != tabs.end(); ++j) {
381 if ((*j).id == id) {
382 return i;
387 return entries_.end();
390 // static
391 bool TabRestoreServiceHelper::ValidateEntry(Entry* entry) {
392 if (entry->type == TabRestoreService::TAB)
393 return ValidateTab(static_cast<Tab*>(entry));
395 if (entry->type == TabRestoreService::WINDOW)
396 return ValidateWindow(static_cast<Window*>(entry));
398 NOTREACHED();
399 return false;
402 void TabRestoreServiceHelper::PopulateTab(
403 Tab* tab,
404 int index,
405 TabRestoreServiceDelegate* delegate,
406 NavigationController* controller) {
407 const int pending_index = controller->GetPendingEntryIndex();
408 int entry_count = controller->GetEntryCount();
409 if (entry_count == 0 && pending_index == 0)
410 entry_count++;
411 tab->navigations.resize(static_cast<int>(entry_count));
412 for (int i = 0; i < entry_count; ++i) {
413 NavigationEntry* entry = (i == pending_index) ?
414 controller->GetPendingEntry() : controller->GetEntryAtIndex(i);
415 tab->navigations[i] =
416 sessions::SerializedNavigationEntry::FromNavigationEntry(i, *entry);
418 tab->timestamp = TimeNow();
419 tab->current_navigation_index = controller->GetCurrentEntryIndex();
420 if (tab->current_navigation_index == -1 && entry_count > 0)
421 tab->current_navigation_index = 0;
422 tab->tabstrip_index = index;
424 #if defined(ENABLE_EXTENSIONS)
425 extensions::TabHelper* extensions_tab_helper =
426 extensions::TabHelper::FromWebContents(controller->GetWebContents());
427 // extensions_tab_helper is NULL in some browser tests.
428 if (extensions_tab_helper) {
429 const extensions::Extension* extension =
430 extensions_tab_helper->extension_app();
431 if (extension)
432 tab->extension_app_id = extension->id();
434 #endif
436 tab->user_agent_override =
437 controller->GetWebContents()->GetUserAgentOverride();
439 // TODO(ajwong): This does not correctly handle storage for isolated apps.
440 tab->session_storage_namespace =
441 controller->GetDefaultSessionStorageNamespace();
443 // Delegate may be NULL during unit tests.
444 if (delegate) {
445 tab->browser_id = delegate->GetSessionID().id();
446 tab->pinned = delegate->IsTabPinned(tab->tabstrip_index);
450 TabRestoreServiceDelegate* TabRestoreServiceHelper::RestoreTab(
451 const Tab& tab,
452 TabRestoreServiceDelegate* delegate,
453 chrome::HostDesktopType host_desktop_type,
454 WindowOpenDisposition disposition,
455 WebContents** contents) {
456 WebContents* web_contents;
457 if (disposition == CURRENT_TAB && delegate) {
458 web_contents = delegate->ReplaceRestoredTab(
459 tab.navigations,
460 tab.current_navigation_index,
461 tab.from_last_session,
462 tab.extension_app_id,
463 tab.session_storage_namespace.get(),
464 tab.user_agent_override);
465 } else {
466 // We only respsect the tab's original browser if there's no disposition.
467 if (disposition == UNKNOWN && tab.has_browser()) {
468 delegate = TabRestoreServiceDelegate::FindDelegateWithID(
469 tab.browser_id, host_desktop_type);
472 int tab_index = -1;
474 // |delegate| will be NULL in cases where one isn't already available (eg,
475 // when invoked on Mac OS X with no windows open). In this case, create a
476 // new browser into which we restore the tabs.
477 if (delegate && disposition != NEW_WINDOW) {
478 tab_index = tab.tabstrip_index;
479 } else {
480 delegate = TabRestoreServiceDelegate::Create(profile_, host_desktop_type,
481 std::string());
482 if (tab.has_browser())
483 UpdateTabBrowserIDs(tab.browser_id, delegate->GetSessionID().id());
486 // Place the tab at the end if the tab index is no longer valid or
487 // we were passed a specific disposition.
488 if (tab_index < 0 || tab_index > delegate->GetTabCount() ||
489 disposition != UNKNOWN) {
490 tab_index = delegate->GetTabCount();
493 web_contents = delegate->AddRestoredTab(tab.navigations,
494 tab_index,
495 tab.current_navigation_index,
496 tab.extension_app_id,
497 disposition != NEW_BACKGROUND_TAB,
498 tab.pinned,
499 tab.from_last_session,
500 tab.session_storage_namespace.get(),
501 tab.user_agent_override);
502 web_contents->GetController().LoadIfNecessary();
504 RecordAppLaunch(profile_, tab);
505 if (contents)
506 *contents = web_contents;
508 return delegate;
512 bool TabRestoreServiceHelper::ValidateTab(Tab* tab) {
513 if (tab->navigations.empty())
514 return false;
516 tab->current_navigation_index =
517 std::max(0, std::min(tab->current_navigation_index,
518 static_cast<int>(tab->navigations.size()) - 1));
520 return true;
523 bool TabRestoreServiceHelper::ValidateWindow(Window* window) {
524 window->selected_tab_index =
525 std::max(0, std::min(window->selected_tab_index,
526 static_cast<int>(window->tabs.size() - 1)));
528 int i = 0;
529 for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
530 tab_i != window->tabs.end();) {
531 if (!ValidateTab(&(*tab_i))) {
532 tab_i = window->tabs.erase(tab_i);
533 if (i < window->selected_tab_index)
534 window->selected_tab_index--;
535 else if (i == window->selected_tab_index)
536 window->selected_tab_index = 0;
537 } else {
538 ++tab_i;
539 ++i;
543 if (window->tabs.empty())
544 return false;
546 return true;
549 bool TabRestoreServiceHelper::IsTabInteresting(const Tab* tab) {
550 if (tab->navigations.empty())
551 return false;
553 if (tab->navigations.size() > 1)
554 return true;
556 return tab->pinned ||
557 tab->navigations.at(0).virtual_url() !=
558 GURL(chrome::kChromeUINewTabURL);
561 bool TabRestoreServiceHelper::IsWindowInteresting(const Window* window) {
562 if (window->tabs.empty())
563 return false;
565 if (window->tabs.size() > 1)
566 return true;
568 return IsTabInteresting(&window->tabs[0]);
571 bool TabRestoreServiceHelper::FilterEntry(Entry* entry) {
572 if (!ValidateEntry(entry))
573 return false;
575 if (entry->type == TabRestoreService::TAB)
576 return IsTabInteresting(static_cast<Tab*>(entry));
577 else if (entry->type == TabRestoreService::WINDOW)
578 return IsWindowInteresting(static_cast<Window*>(entry));
580 NOTREACHED();
581 return false;
584 void TabRestoreServiceHelper::UpdateTabBrowserIDs(SessionID::id_type old_id,
585 SessionID::id_type new_id) {
586 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
587 Entry* entry = *i;
588 if (entry->type == TabRestoreService::TAB) {
589 Tab* tab = static_cast<Tab*>(entry);
590 if (tab->browser_id == old_id)
591 tab->browser_id = new_id;
596 base::Time TabRestoreServiceHelper::TimeNow() const {
597 return time_factory_ ? time_factory_->TimeNow() : base::Time::Now();