| Index: chrome/browser/sessions/in_memory_tab_restore_service.cc
|
| diff --git a/chrome/browser/sessions/in_memory_tab_restore_service.cc b/chrome/browser/sessions/in_memory_tab_restore_service.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..dc71e5eee7d1eff835f4c4f16401e0bfbc6a5c9b
|
| --- /dev/null
|
| +++ b/chrome/browser/sessions/in_memory_tab_restore_service.cc
|
| @@ -0,0 +1,585 @@
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chrome/browser/sessions/in_memory_tab_restore_service.h"
|
| +
|
| +#include <algorithm>
|
| +#include <iterator>
|
| +#include <map>
|
| +
|
| +#include "base/metrics/histogram.h"
|
| +#include "base/stl_util.h"
|
| +#include "chrome/browser/extensions/extension_service.h"
|
| +#include "chrome/browser/extensions/tab_helper.h"
|
| +#include "chrome/browser/profiles/profile.h"
|
| +#include "chrome/browser/sessions/session_command.h"
|
| +#include "chrome/browser/sessions/session_service.h"
|
| +#include "chrome/browser/sessions/session_service_factory.h"
|
| +#include "chrome/browser/sessions/session_types.h"
|
| +#include "chrome/browser/sessions/tab_restore_service_delegate.h"
|
| +#include "chrome/browser/sessions/tab_restore_service_observer.h"
|
| +#include "chrome/browser/ui/tab_contents/tab_contents.h"
|
| +#include "chrome/browser/ui/webui/ntp/app_launcher_handler.h"
|
| +#include "chrome/common/extensions/extension.h"
|
| +#include "chrome/common/extensions/extension_constants.h"
|
| +#include "chrome/common/url_constants.h"
|
| +#include "content/public/browser/navigation_controller.h"
|
| +#include "content/public/browser/navigation_entry.h"
|
| +#include "content/public/browser/web_contents.h"
|
| +#include "content/public/browser/session_storage_namespace.h"
|
| +
|
| +using content::NavigationController;
|
| +using content::NavigationEntry;
|
| +using content::WebContents;
|
| +
|
| +namespace {
|
| +
|
| +void RecordAppLaunch(Profile* profile, const TabRestoreService::Tab& tab) {
|
| +#if !defined(OS_ANDROID)
|
| + GURL url = tab.navigations.at(tab.current_navigation_index).virtual_url();
|
| + DCHECK(profile->GetExtensionService());
|
| + if (!profile->GetExtensionService()->IsInstalledApp(url))
|
| + return;
|
| +
|
| + AppLauncherHandler::RecordAppLaunchType(
|
| + extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED);
|
| +#endif // !defined(OS_ANDROID)
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +InMemoryTabRestoreService::InMemoryTabRestoreService(
|
| + Profile* profile,
|
| + TabRestoreService::TimeFactory* time_factory)
|
| + : profile_(profile),
|
| + restoring_(false),
|
| + time_factory_(time_factory) {
|
| +}
|
| +
|
| +InMemoryTabRestoreService::~InMemoryTabRestoreService() {
|
| + FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
|
| + TabRestoreServiceDestroyed(this));
|
| + STLDeleteElements(&entries_);
|
| + time_factory_ = NULL;
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::AddObserver(
|
| + TabRestoreServiceObserver* observer) {
|
| + observer_list_.AddObserver(observer);
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::RemoveObserver(
|
| + TabRestoreServiceObserver* observer) {
|
| + observer_list_.RemoveObserver(observer);
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::CreateHistoricalTab(
|
| + content::WebContents* contents,
|
| + int index) {
|
| + if (restoring_)
|
| + return;
|
| +
|
| + TabRestoreServiceDelegate* delegate =
|
| + TabRestoreServiceDelegate::FindDelegateForWebContents(contents);
|
| + if (closing_delegates_.find(delegate) != closing_delegates_.end())
|
| + return;
|
| +
|
| + scoped_ptr<Tab> local_tab(new Tab());
|
| + PopulateTab(local_tab.get(), index, delegate, &contents->GetController());
|
| + if (local_tab->navigations.empty())
|
| + return;
|
| +
|
| + AddEntry(local_tab.release(), true, true);
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::BrowserClosing(
|
| + TabRestoreServiceDelegate* delegate) {
|
| + closing_delegates_.insert(delegate);
|
| +
|
| + scoped_ptr<Window> window(new Window());
|
| + window->selected_tab_index = delegate->GetSelectedIndex();
|
| + window->timestamp = TimeNow();
|
| + window->app_name = delegate->GetAppName();
|
| +
|
| + // Don't use std::vector::resize() because it will push copies of an empty tab
|
| + // into the vector, which will give all tabs in a window the same ID.
|
| + for (int i = 0; i < delegate->GetTabCount(); ++i) {
|
| + window->tabs.push_back(Tab());
|
| + }
|
| + size_t entry_index = 0;
|
| + for (int tab_index = 0; tab_index < delegate->GetTabCount(); ++tab_index) {
|
| + PopulateTab(&(window->tabs[entry_index]),
|
| + tab_index,
|
| + delegate,
|
| + &delegate->GetWebContentsAt(tab_index)->GetController());
|
| + if (window->tabs[entry_index].navigations.empty()) {
|
| + window->tabs.erase(window->tabs.begin() + entry_index);
|
| + } else {
|
| + window->tabs[entry_index].browser_id = delegate->GetSessionID().id();
|
| + entry_index++;
|
| + }
|
| + }
|
| + if (window->tabs.size() == 1 && window->app_name.empty()) {
|
| + // Short-circuit creating a Window if only 1 tab was present. This fixes
|
| + // http://crbug.com/56744. Copy the Tab because it's owned by an object on
|
| + // the stack.
|
| + AddEntry(new Tab(window->tabs[0]), true, true);
|
| + } else if (!window->tabs.empty()) {
|
| + window->selected_tab_index =
|
| + std::min(static_cast<int>(window->tabs.size() - 1),
|
| + window->selected_tab_index);
|
| + AddEntry(window.release(), true, true);
|
| + }
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::BrowserClosed(
|
| + TabRestoreServiceDelegate* delegate) {
|
| + closing_delegates_.erase(delegate);
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::ClearEntries() {
|
| + OnClearEntries();
|
| + STLDeleteElements(&entries_);
|
| + NotifyTabsChanged();
|
| +}
|
| +
|
| +const TabRestoreService::Entries& InMemoryTabRestoreService::entries() const {
|
| + return entries_;
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::RestoreMostRecentEntry(
|
| + TabRestoreServiceDelegate* delegate) {
|
| + if (entries_.empty())
|
| + return;
|
| +
|
| + RestoreEntryById(delegate, entries_.front()->id, UNKNOWN);
|
| +}
|
| +
|
| +TabRestoreService::Tab* InMemoryTabRestoreService::RemoveTabEntryById(
|
| + SessionID::id_type id) {
|
| + Entries::iterator i = GetEntryIteratorById(id);
|
| + if (i == entries_.end())
|
| + return NULL;
|
| +
|
| + Entry* entry = *i;
|
| + if (entry->type != TAB)
|
| + return NULL;
|
| +
|
| + Tab* tab = static_cast<Tab*>(entry);
|
| + entries_.erase(i);
|
| + return tab;
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::RestoreEntryById(
|
| + TabRestoreServiceDelegate* delegate,
|
| + SessionID::id_type id,
|
| + WindowOpenDisposition disposition) {
|
| + Entries::iterator entry_iterator = GetEntryIteratorById(id);
|
| + if (entry_iterator == entries_.end())
|
| + // Don't hoark here, we allow an invalid id.
|
| + return;
|
| +
|
| + OnRestoreEntryById(id, entry_iterator);
|
| + restoring_ = true;
|
| + Entry* entry = *entry_iterator;
|
| +
|
| + // If the entry's ID does not match the ID that is being restored, then the
|
| + // entry is a window from which a single tab will be restored.
|
| + bool restoring_tab_in_window = entry->id != id;
|
| +
|
| + if (!restoring_tab_in_window) {
|
| + entries_.erase(entry_iterator);
|
| + entry_iterator = entries_.end();
|
| + }
|
| +
|
| + // |delegate| will be NULL in cases where one isn't already available (eg,
|
| + // when invoked on Mac OS X with no windows open). In this case, create a
|
| + // new browser into which we restore the tabs.
|
| + if (entry->type == TAB) {
|
| + Tab* tab = static_cast<Tab*>(entry);
|
| + delegate = RestoreTab(*tab, delegate, disposition);
|
| + delegate->ShowBrowserWindow();
|
| + } else if (entry->type == WINDOW) {
|
| + TabRestoreServiceDelegate* current_delegate = delegate;
|
| + Window* window = static_cast<Window*>(entry);
|
| +
|
| + // When restoring a window, either the entire window can be restored, or a
|
| + // single tab within it. If the entry's ID matches the one to restore, then
|
| + // the entire window will be restored.
|
| + if (!restoring_tab_in_window) {
|
| + delegate = TabRestoreServiceDelegate::Create(profile_, window->app_name);
|
| + for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) {
|
| + const Tab& tab = window->tabs[tab_i];
|
| + WebContents* restored_tab =
|
| + delegate->AddRestoredTab(tab.navigations, delegate->GetTabCount(),
|
| + tab.current_navigation_index,
|
| + tab.extension_app_id,
|
| + static_cast<int>(tab_i) ==
|
| + window->selected_tab_index,
|
| + tab.pinned, tab.from_last_session,
|
| + tab.session_storage_namespace,
|
| + tab.user_agent_override);
|
| + if (restored_tab) {
|
| + restored_tab->GetController().LoadIfNecessary();
|
| + RecordAppLaunch(profile_, tab);
|
| + }
|
| + }
|
| + // All the window's tabs had the same former browser_id.
|
| + if (window->tabs[0].has_browser()) {
|
| + UpdateTabBrowserIDs(window->tabs[0].browser_id,
|
| + delegate->GetSessionID().id());
|
| + }
|
| + } else {
|
| + // Restore a single tab from the window. Find the tab that matches the ID
|
| + // in the window and restore it.
|
| + for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
|
| + tab_i != window->tabs.end(); ++tab_i) {
|
| + const Tab& tab = *tab_i;
|
| + if (tab.id == id) {
|
| + delegate = RestoreTab(tab, delegate, disposition);
|
| + window->tabs.erase(tab_i);
|
| + // If restoring the tab leaves the window with nothing else, delete it
|
| + // as well.
|
| + if (!window->tabs.size()) {
|
| + entries_.erase(entry_iterator);
|
| + delete entry;
|
| + } else {
|
| + // Update the browser ID of the rest of the tabs in the window so if
|
| + // any one is restored, it goes into the same window as the tab
|
| + // being restored now.
|
| + UpdateTabBrowserIDs(tab.browser_id,
|
| + delegate->GetSessionID().id());
|
| + for (std::vector<Tab>::iterator tab_j = window->tabs.begin();
|
| + tab_j != window->tabs.end(); ++tab_j) {
|
| + (*tab_j).browser_id = delegate->GetSessionID().id();
|
| + }
|
| + }
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + delegate->ShowBrowserWindow();
|
| +
|
| + if (disposition == CURRENT_TAB && current_delegate &&
|
| + current_delegate->GetActiveWebContents()) {
|
| + current_delegate->CloseTab();
|
| + }
|
| + } else {
|
| + NOTREACHED();
|
| + }
|
| +
|
| + if (!restoring_tab_in_window) {
|
| + delete entry;
|
| + }
|
| +
|
| + restoring_ = false;
|
| + NotifyTabsChanged();
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::LoadTabsFromLastSession() {
|
| + NOTIMPLEMENTED();
|
| +}
|
| +
|
| +bool InMemoryTabRestoreService::IsLoaded() const {
|
| + NOTIMPLEMENTED();
|
| + return false;
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::Shutdown() {
|
| + NOTIMPLEMENTED();
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::PopulateTab(
|
| + Tab* tab,
|
| + int index,
|
| + TabRestoreServiceDelegate* delegate,
|
| + NavigationController* controller) {
|
| + const int pending_index = controller->GetPendingEntryIndex();
|
| + int entry_count = controller->GetEntryCount();
|
| + if (entry_count == 0 && pending_index == 0)
|
| + entry_count++;
|
| + tab->navigations.resize(static_cast<int>(entry_count));
|
| + for (int i = 0; i < entry_count; ++i) {
|
| + NavigationEntry* entry = (i == pending_index) ?
|
| + controller->GetPendingEntry() : controller->GetEntryAtIndex(i);
|
| + tab->navigations[i] =
|
| + TabNavigation::FromNavigationEntry(i, *entry);
|
| + }
|
| + tab->timestamp = TimeNow();
|
| + tab->current_navigation_index = controller->GetCurrentEntryIndex();
|
| + if (tab->current_navigation_index == -1 && entry_count > 0)
|
| + tab->current_navigation_index = 0;
|
| + tab->tabstrip_index = index;
|
| +
|
| + TabContents* tab_contents =
|
| + TabContents::FromWebContents(controller->GetWebContents());
|
| + // tab_contents is NULL in some browser tests.
|
| + if (tab_contents) {
|
| + const extensions::Extension* extension =
|
| + extensions::TabHelper::FromWebContents(controller->GetWebContents())->
|
| + extension_app();
|
| + if (extension)
|
| + tab->extension_app_id = extension->id();
|
| + }
|
| +
|
| + tab->user_agent_override =
|
| + controller->GetWebContents()->GetUserAgentOverride();
|
| +
|
| + // TODO(ajwong): This does not correctly handle storage for isolated apps.
|
| + tab->session_storage_namespace =
|
| + controller->GetDefaultSessionStorageNamespace();
|
| +
|
| + // Delegate may be NULL during unit tests.
|
| + if (delegate) {
|
| + tab->browser_id = delegate->GetSessionID().id();
|
| + tab->pinned = delegate->IsTabPinned(tab->tabstrip_index);
|
| + }
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::AddEntry(Entry* entry,
|
| + bool notify,
|
| + bool to_front) {
|
| + if (!FilterEntry(entry) || (entries_.size() >= kMaxEntries && !to_front)) {
|
| + delete entry;
|
| + return;
|
| + }
|
| +
|
| + if (to_front)
|
| + entries_.push_front(entry);
|
| + else
|
| + entries_.push_back(entry);
|
| +
|
| + PruneEntries();
|
| +
|
| + if (notify)
|
| + NotifyTabsChanged();
|
| +
|
| + OnAddEntry();
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::PruneEntries() {
|
| + Entries new_entries;
|
| +
|
| + for (TabRestoreService::Entries::const_iterator iter = entries_.begin();
|
| + iter != entries_.end(); ++iter) {
|
| + TabRestoreService::Entry* entry = *iter;
|
| +
|
| + if (FilterEntry(entry) &&
|
| + new_entries.size() < kMaxEntries) {
|
| + new_entries.push_back(entry);
|
| + } else {
|
| + delete entry;
|
| + }
|
| + }
|
| +
|
| + entries_ = new_entries;
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::NotifyTabsChanged() {
|
| + FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
|
| + TabRestoreServiceChanged(this));
|
| +}
|
| +
|
| +TabRestoreService::Entries::iterator
|
| +InMemoryTabRestoreService::GetEntryIteratorById(SessionID::id_type id) {
|
| + for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
|
| + if ((*i)->id == id)
|
| + return i;
|
| +
|
| + // For Window entries, see if the ID matches a tab. If so, report the window
|
| + // as the Entry.
|
| + if ((*i)->type == WINDOW) {
|
| + std::vector<Tab>& tabs = static_cast<Window*>(*i)->tabs;
|
| + for (std::vector<Tab>::iterator j = tabs.begin();
|
| + j != tabs.end(); ++j) {
|
| + if ((*j).id == id) {
|
| + return i;
|
| + }
|
| + }
|
| + }
|
| + }
|
| + return entries_.end();
|
| +}
|
| +
|
| +bool InMemoryTabRestoreService::ConvertSessionWindowToWindow(
|
| + SessionWindow* session_window,
|
| + Window* window) {
|
| + for (size_t i = 0; i < session_window->tabs.size(); ++i) {
|
| + if (!session_window->tabs[i]->navigations.empty()) {
|
| + window->tabs.resize(window->tabs.size() + 1);
|
| + Tab& tab = window->tabs.back();
|
| + tab.pinned = session_window->tabs[i]->pinned;
|
| + tab.navigations.swap(session_window->tabs[i]->navigations);
|
| + tab.current_navigation_index =
|
| + session_window->tabs[i]->current_navigation_index;
|
| + tab.extension_app_id = session_window->tabs[i]->extension_app_id;
|
| + tab.timestamp = base::Time();
|
| + }
|
| + }
|
| + if (window->tabs.empty())
|
| + return false;
|
| +
|
| + window->selected_tab_index =
|
| + std::min(session_window->selected_tab_index,
|
| + static_cast<int>(window->tabs.size() - 1));
|
| + window->timestamp = base::Time();
|
| + return true;
|
| +}
|
| +
|
| +// static
|
| +bool InMemoryTabRestoreService::ValidateEntry(Entry* entry) {
|
| + if (entry->type == TAB)
|
| + return ValidateTab(static_cast<Tab*>(entry));
|
| +
|
| + if (entry->type == WINDOW)
|
| + return ValidateWindow(static_cast<Window*>(entry));
|
| +
|
| + NOTREACHED();
|
| + return false;
|
| +}
|
| +
|
| +TabRestoreServiceDelegate* InMemoryTabRestoreService::RestoreTab(
|
| + const Tab& tab,
|
| + TabRestoreServiceDelegate* delegate,
|
| + WindowOpenDisposition disposition) {
|
| + if (disposition == CURRENT_TAB && delegate) {
|
| + delegate->ReplaceRestoredTab(tab.navigations,
|
| + tab.current_navigation_index,
|
| + tab.from_last_session,
|
| + tab.extension_app_id,
|
| + tab.session_storage_namespace,
|
| + tab.user_agent_override);
|
| + } else {
|
| + // We only respsect the tab's original browser if there's no disposition.
|
| + if (disposition == UNKNOWN && tab.has_browser())
|
| + delegate = TabRestoreServiceDelegate::FindDelegateWithID(tab.browser_id);
|
| +
|
| + int tab_index = -1;
|
| +
|
| + // |delegate| will be NULL in cases where one isn't already available (eg,
|
| + // when invoked on Mac OS X with no windows open). In this case, create a
|
| + // new browser into which we restore the tabs.
|
| + if (delegate && disposition != NEW_WINDOW) {
|
| + tab_index = tab.tabstrip_index;
|
| + } else {
|
| + delegate = TabRestoreServiceDelegate::Create(profile_, std::string());
|
| + if (tab.has_browser())
|
| + UpdateTabBrowserIDs(tab.browser_id, delegate->GetSessionID().id());
|
| + }
|
| +
|
| + // Place the tab at the end if the tab index is no longer valid or
|
| + // we were passed a specific disposition.
|
| + if (tab_index < 0 || tab_index > delegate->GetTabCount() ||
|
| + disposition != UNKNOWN) {
|
| + tab_index = delegate->GetTabCount();
|
| + }
|
| +
|
| + WebContents* web_contents = delegate->AddRestoredTab(
|
| + tab.navigations,
|
| + tab_index,
|
| + tab.current_navigation_index,
|
| + tab.extension_app_id,
|
| + disposition != NEW_BACKGROUND_TAB,
|
| + tab.pinned,
|
| + tab.from_last_session,
|
| + tab.session_storage_namespace,
|
| + tab.user_agent_override);
|
| + web_contents->GetController().LoadIfNecessary();
|
| + }
|
| + RecordAppLaunch(profile_, tab);
|
| + return delegate;
|
| +}
|
| +
|
| +
|
| +bool InMemoryTabRestoreService::ValidateTab(Tab* tab) {
|
| + if (tab->navigations.empty())
|
| + return false;
|
| +
|
| + tab->current_navigation_index =
|
| + std::max(0, std::min(tab->current_navigation_index,
|
| + static_cast<int>(tab->navigations.size()) - 1));
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool InMemoryTabRestoreService::ValidateWindow(Window* window) {
|
| + window->selected_tab_index =
|
| + std::max(0, std::min(window->selected_tab_index,
|
| + static_cast<int>(window->tabs.size() - 1)));
|
| +
|
| + int i = 0;
|
| + for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
|
| + tab_i != window->tabs.end();) {
|
| + if (!ValidateTab(&(*tab_i))) {
|
| + tab_i = window->tabs.erase(tab_i);
|
| + if (i < window->selected_tab_index)
|
| + window->selected_tab_index--;
|
| + else if (i == window->selected_tab_index)
|
| + window->selected_tab_index = 0;
|
| + } else {
|
| + ++tab_i;
|
| + ++i;
|
| + }
|
| + }
|
| +
|
| + if (window->tabs.empty())
|
| + return false;
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool InMemoryTabRestoreService::IsTabInteresting(const Tab* tab) {
|
| + if (tab->navigations.empty())
|
| + return false;
|
| +
|
| + if (tab->navigations.size() > 1)
|
| + return true;
|
| +
|
| + return tab->pinned ||
|
| + tab->navigations.at(0).virtual_url() !=
|
| + GURL(chrome::kChromeUINewTabURL);
|
| +}
|
| +
|
| +bool InMemoryTabRestoreService::IsWindowInteresting(const Window* window) {
|
| + if (window->tabs.empty())
|
| + return false;
|
| +
|
| + if (window->tabs.size() > 1)
|
| + return true;
|
| +
|
| + return IsTabInteresting(&window->tabs[0]);
|
| +}
|
| +
|
| +bool InMemoryTabRestoreService::FilterEntry(Entry* entry) {
|
| + if (!ValidateEntry(entry))
|
| + return false;
|
| +
|
| + if (entry->type == TAB)
|
| + return IsTabInteresting(static_cast<Tab*>(entry));
|
| + else if (entry->type == WINDOW)
|
| + return IsWindowInteresting(static_cast<Window*>(entry));
|
| +
|
| + NOTREACHED();
|
| + return false;
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::UpdateTabBrowserIDs(SessionID::id_type old_id,
|
| + SessionID::id_type new_id) {
|
| + for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
|
| + Entry* entry = *i;
|
| + if (entry->type == TAB) {
|
| + Tab* tab = static_cast<Tab*>(entry);
|
| + if (tab->browser_id == old_id)
|
| + tab->browser_id = new_id;
|
| + }
|
| + }
|
| +}
|
| +
|
| +void InMemoryTabRestoreService::DeleteLastSession() {
|
| + NOTIMPLEMENTED();
|
| +}
|
| +
|
| +base::Time InMemoryTabRestoreService::TimeNow() const {
|
| + return time_factory_ ? time_factory_->TimeNow() : base::Time::Now();
|
| +}
|
|
|