Home | History | Annotate | Download | only in sessions
      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 }
    599