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