Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / sessions / tab_restore_service_helper.cc
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.
4
5 #include "chrome/browser/sessions/tab_restore_service_helper.h"
6
7 #include <algorithm>
8 #include <iterator>
9
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"
22
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
31
32 using content::NavigationController;
33 using content::NavigationEntry;
34 using content::WebContents;
35
36 namespace {
37
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;
46
47   CoreAppLauncherHandler::RecordAppLaunchType(
48       extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED,
49       extension->GetType());
50 #endif  // defined(ENABLE_EXTENSIONS)
51 }
52
53 }  // namespace
54
55 // TabRestoreServiceHelper::Observer -------------------------------------------
56
57 TabRestoreServiceHelper::Observer::~Observer() {}
58
59 void TabRestoreServiceHelper::Observer::OnClearEntries() {}
60
61 void TabRestoreServiceHelper::Observer::OnRestoreEntryById(
62     SessionID::id_type id,
63     Entries::const_iterator entry_iterator) {
64 }
65
66 void TabRestoreServiceHelper::Observer::OnAddEntry() {}
67
68 // TabRestoreServiceHelper -----------------------------------------------------
69
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_);
81 }
82
83 TabRestoreServiceHelper::~TabRestoreServiceHelper() {
84   FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
85                     TabRestoreServiceDestroyed(tab_restore_service_));
86   STLDeleteElements(&entries_);
87 }
88
89 void TabRestoreServiceHelper::AddObserver(
90     TabRestoreServiceObserver* observer) {
91   observer_list_.AddObserver(observer);
92 }
93
94 void TabRestoreServiceHelper::RemoveObserver(
95     TabRestoreServiceObserver* observer) {
96   observer_list_.RemoveObserver(observer);
97 }
98
99 void TabRestoreServiceHelper::CreateHistoricalTab(
100     content::WebContents* contents,
101     int index) {
102   if (restoring_)
103     return;
104
105   TabRestoreServiceDelegate* delegate =
106       TabRestoreServiceDelegate::FindDelegateForWebContents(contents);
107   if (closing_delegates_.find(delegate) != closing_delegates_.end())
108     return;
109
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;
114
115   AddEntry(local_tab.release(), true, true);
116 }
117
118 void TabRestoreServiceHelper::BrowserClosing(
119     TabRestoreServiceDelegate* delegate) {
120   closing_delegates_.insert(delegate);
121
122   scoped_ptr<Window> window(new Window());
123   window->selected_tab_index = delegate->GetSelectedIndex();
124   window->timestamp = TimeNow();
125   window->app_name = delegate->GetAppName();
126
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());
131   }
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++;
143     }
144   }
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);
155   }
156 }
157
158 void TabRestoreServiceHelper::BrowserClosed(
159     TabRestoreServiceDelegate* delegate) {
160   closing_delegates_.erase(delegate);
161 }
162
163 void TabRestoreServiceHelper::ClearEntries() {
164   if (observer_)
165     observer_->OnClearEntries();
166   STLDeleteElements(&entries_);
167   NotifyTabsChanged();
168 }
169
170 const TabRestoreService::Entries& TabRestoreServiceHelper::entries() const {
171   return entries_;
172 }
173
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*>();
180
181   return RestoreEntryById(delegate, entries_.front()->id, host_desktop_type,
182       UNKNOWN);
183 }
184
185 TabRestoreService::Tab* TabRestoreServiceHelper::RemoveTabEntryById(
186     SessionID::id_type id) {
187   Entries::iterator i = GetEntryIteratorById(id);
188   if (i == entries_.end())
189     return NULL;
190
191   Entry* entry = *i;
192   if (entry->type != TabRestoreService::TAB)
193     return NULL;
194
195   Tab* tab = static_cast<Tab*>(entry);
196   entries_.erase(i);
197   return tab;
198 }
199
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*>();
209
210   if (observer_)
211     observer_->OnRestoreEntryById(id, entry_iterator);
212   restoring_ = true;
213   Entry* entry = *entry_iterator;
214
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;
218
219   if (!restoring_tab_in_window) {
220     entries_.erase(entry_iterator);
221     entry_iterator = entries_.end();
222   }
223
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);
238
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);
261         }
262       }
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());
267       }
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();
294             }
295           }
296           break;
297         }
298       }
299     }
300     delegate->ShowBrowserWindow();
301
302     if (disposition == CURRENT_TAB && current_delegate &&
303         current_delegate->GetActiveWebContents()) {
304       current_delegate->CloseTab();
305     }
306   } else {
307     NOTREACHED();
308   }
309
310   if (!restoring_tab_in_window) {
311     delete entry;
312   }
313
314   restoring_ = false;
315   NotifyTabsChanged();
316   return web_contents;
317 }
318
319 void TabRestoreServiceHelper::NotifyTabsChanged() {
320   FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
321                     TabRestoreServiceChanged(tab_restore_service_));
322 }
323
324 void TabRestoreServiceHelper::NotifyLoaded() {
325   FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
326                     TabRestoreServiceLoaded(tab_restore_service_));
327 }
328
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;
335   }
336
337   if (to_front)
338     entries_.push_front(entry);
339   else
340     entries_.push_back(entry);
341
342   PruneEntries();
343
344   if (notify)
345     NotifyTabsChanged();
346
347   if (observer_)
348     observer_->OnAddEntry();
349 }
350
351 void TabRestoreServiceHelper::PruneEntries() {
352   Entries new_entries;
353
354   for (TabRestoreService::Entries::const_iterator iter = entries_.begin();
355        iter != entries_.end(); ++iter) {
356     TabRestoreService::Entry* entry = *iter;
357
358     if (FilterEntry(entry) &&
359         new_entries.size() < kMaxEntries) {
360       new_entries.push_back(entry);
361     } else {
362       delete entry;
363     }
364   }
365
366   entries_ = new_entries;
367 }
368
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;
374
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;
383         }
384       }
385     }
386   }
387   return entries_.end();
388 }
389
390 // static
391 bool TabRestoreServiceHelper::ValidateEntry(Entry* entry) {
392   if (entry->type == TabRestoreService::TAB)
393     return ValidateTab(static_cast<Tab*>(entry));
394
395   if (entry->type == TabRestoreService::WINDOW)
396     return ValidateWindow(static_cast<Window*>(entry));
397
398   NOTREACHED();
399   return false;
400 }
401
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);
417   }
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;
423
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();
433   }
434 #endif
435
436   tab->user_agent_override =
437       controller->GetWebContents()->GetUserAgentOverride();
438
439   // TODO(ajwong): This does not correctly handle storage for isolated apps.
440   tab->session_storage_namespace =
441       controller->GetDefaultSessionStorageNamespace();
442
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);
447   }
448 }
449
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);
470     }
471
472     int tab_index = -1;
473
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());
484     }
485
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();
491     }
492
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();
503   }
504   RecordAppLaunch(profile_, tab);
505   if (contents)
506     *contents = web_contents;
507
508   return delegate;
509 }
510
511
512 bool TabRestoreServiceHelper::ValidateTab(Tab* tab) {
513   if (tab->navigations.empty())
514     return false;
515
516   tab->current_navigation_index =
517       std::max(0, std::min(tab->current_navigation_index,
518                            static_cast<int>(tab->navigations.size()) - 1));
519
520   return true;
521 }
522
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)));
527
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;
540     }
541   }
542
543   if (window->tabs.empty())
544     return false;
545
546   return true;
547 }
548
549 bool TabRestoreServiceHelper::IsTabInteresting(const Tab* tab) {
550   if (tab->navigations.empty())
551     return false;
552
553   if (tab->navigations.size() > 1)
554     return true;
555
556   return tab->pinned ||
557       tab->navigations.at(0).virtual_url() !=
558           GURL(chrome::kChromeUINewTabURL);
559 }
560
561 bool TabRestoreServiceHelper::IsWindowInteresting(const Window* window) {
562   if (window->tabs.empty())
563     return false;
564
565   if (window->tabs.size() > 1)
566     return true;
567
568   return IsTabInteresting(&window->tabs[0]);
569 }
570
571 bool TabRestoreServiceHelper::FilterEntry(Entry* entry) {
572   if (!ValidateEntry(entry))
573     return false;
574
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));
579
580   NOTREACHED();
581   return false;
582 }
583
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;
592     }
593   }
594 }
595
596 base::Time TabRestoreServiceHelper::TimeNow() const {
597   return time_factory_ ? time_factory_->TimeNow() : base::Time::Now();
598 }