blob: 8362fe384a53aa98dfa9987e8391ebaaeead8be2 [file] [log] [blame]
// Copyright 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 "components/sessions/core/tab_restore_service_helper.h"
#include <inttypes.h>
#include <stddef.h>
#include <algorithm>
#include <iterator>
#include <memory>
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_usage_estimator.h"
#include "base/trace_event/process_memory_dump.h"
#include "components/sessions/core/live_tab.h"
#include "components/sessions/core/live_tab_context.h"
#include "components/sessions/core/serialized_navigation_entry.h"
#include "components/sessions/core/session_types.h"
#include "components/sessions/core/tab_restore_service_client.h"
#include "components/sessions/core/tab_restore_service_observer.h"
namespace sessions {
// TabRestoreServiceHelper::Observer -------------------------------------------
TabRestoreServiceHelper::Observer::~Observer() {}
void TabRestoreServiceHelper::Observer::OnClearEntries() {}
void TabRestoreServiceHelper::Observer::OnNavigationEntriesDeleted() {}
void TabRestoreServiceHelper::Observer::OnRestoreEntryById(
SessionID id,
Entries::const_iterator entry_iterator) {}
void TabRestoreServiceHelper::Observer::OnAddEntry() {}
// TabRestoreServiceHelper -----------------------------------------------------
TabRestoreServiceHelper::TabRestoreServiceHelper(
TabRestoreService* tab_restore_service,
Observer* observer,
TabRestoreServiceClient* client,
TabRestoreService::TimeFactory* time_factory)
: tab_restore_service_(tab_restore_service),
observer_(observer),
client_(client),
restoring_(false),
time_factory_(time_factory) {
DCHECK(tab_restore_service_);
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
this,
"TabRestoreServiceHelper",
base::ThreadTaskRunnerHandle::Get());
}
TabRestoreServiceHelper::~TabRestoreServiceHelper() {
for (auto& observer : observer_list_)
observer.TabRestoreServiceDestroyed(tab_restore_service_);
base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
this);
}
void TabRestoreServiceHelper::AddObserver(
TabRestoreServiceObserver* observer) {
observer_list_.AddObserver(observer);
}
void TabRestoreServiceHelper::RemoveObserver(
TabRestoreServiceObserver* observer) {
observer_list_.RemoveObserver(observer);
}
void TabRestoreServiceHelper::CreateHistoricalTab(LiveTab* live_tab,
int index) {
if (restoring_)
return;
// If an entire window is being closed than all of the tabs have already
// been persisted via "BrowserClosing". Ignore the subsequent tab closing
// notifications.
LiveTabContext* context = client_->FindLiveTabContextForTab(live_tab);
if (closing_contexts_.find(context) != closing_contexts_.end())
return;
auto local_tab = std::make_unique<Tab>();
PopulateTab(local_tab.get(), index, context, live_tab);
if (local_tab->navigations.empty())
return;
AddEntry(std::move(local_tab), true, true);
}
void TabRestoreServiceHelper::BrowserClosing(LiveTabContext* context) {
closing_contexts_.insert(context);
auto window = std::make_unique<Window>();
window->selected_tab_index = context->GetSelectedIndex();
window->timestamp = TimeNow();
window->app_name = context->GetAppName();
window->bounds = context->GetRestoredBounds();
window->show_state = context->GetRestoredState();
window->workspace = context->GetWorkspace();
for (int tab_index = 0; tab_index < context->GetTabCount(); ++tab_index) {
auto tab = std::make_unique<Tab>();
PopulateTab(tab.get(), tab_index, context,
context->GetLiveTabAt(tab_index));
if (!tab->navigations.empty()) {
tab->browser_id = context->GetSessionID().id();
window->tabs.push_back(std::move(tab));
}
}
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.
AddEntry(std::move(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(std::move(window), true, true);
}
}
void TabRestoreServiceHelper::BrowserClosed(LiveTabContext* context) {
closing_contexts_.erase(context);
}
void TabRestoreServiceHelper::ClearEntries() {
if (observer_)
observer_->OnClearEntries();
entries_.clear();
NotifyTabsChanged();
}
bool TabRestoreServiceHelper::DeleteFromTab(const DeletionPredicate& predicate,
Tab* tab) {
// TODO(dullweber): Change to DCHECK() when this is tested to be true.
CHECK(ValidateTab(*tab));
std::vector<SerializedNavigationEntry> new_navigations;
int deleted_navigations_count = 0;
for (size_t i = 0; i < tab->navigations.size(); i++) {
SerializedNavigationEntry& navigation = tab->navigations[i];
if (predicate.Run(navigation)) {
// If the current navigation is deleted, remove this tab.
if (static_cast<int>(i) == tab->current_navigation_index)
return true;
deleted_navigations_count++;
} else {
// Adjust indices according to number of deleted navigations.
if (static_cast<int>(i) == tab->current_navigation_index)
tab->current_navigation_index -= deleted_navigations_count;
DCHECK_GE(navigation.index(), deleted_navigations_count);
navigation.set_index(navigation.index() - deleted_navigations_count);
new_navigations.push_back(std::move(navigation));
}
}
tab->navigations = std::move(new_navigations);
// TODO(dullweber): Change to DCHECK() when this is tested to be true.
CHECK(tab->navigations.empty() || ValidateTab(*tab));
return tab->navigations.empty();
}
bool TabRestoreServiceHelper::DeleteFromWindow(
const DeletionPredicate& predicate,
Window* window) {
// TODO(dullweber): Change to DCHECK() when this is tested to be true.
CHECK(ValidateWindow(*window));
std::vector<std::unique_ptr<Tab>> new_tabs;
int deleted_tabs_count = 0;
for (size_t i = 0; i < window->tabs.size(); i++) {
std::unique_ptr<Tab>& tab = window->tabs[i];
if (DeleteFromTab(predicate, tab.get())) {
if (static_cast<int>(i) == window->selected_tab_index)
window->selected_tab_index = new_tabs.empty() ? 0 : new_tabs.size() - 1;
deleted_tabs_count++;
} else {
// Adjust indices according to number of deleted tabs.
if (static_cast<int>(i) == window->selected_tab_index)
window->selected_tab_index -= deleted_tabs_count;
if (tab->tabstrip_index >= 0) {
DCHECK_GE(tab->tabstrip_index, deleted_tabs_count);
tab->tabstrip_index -= deleted_tabs_count;
}
new_tabs.push_back(std::move(tab));
}
}
window->tabs = std::move(new_tabs);
// TODO(dullweber): Change to DCHECK() when this is tested to be true.
CHECK(window->tabs.empty() || ValidateWindow(*window));
return window->tabs.empty();
}
void TabRestoreServiceHelper::DeleteNavigationEntries(
const DeletionPredicate& predicate) {
Entries new_entries;
for (std::unique_ptr<Entry>& entry : entries_) {
switch (entry->type) {
case TabRestoreService::TAB: {
Tab* tab = static_cast<Tab*>(entry.get());
if (!DeleteFromTab(predicate, tab))
new_entries.push_back(std::move(entry));
break;
}
case TabRestoreService::WINDOW: {
Window* window = static_cast<Window*>(entry.get());
if (!DeleteFromWindow(predicate, window)) {
// If only a single tab is left, just keep the tab.
if (window->tabs.size() == 1U)
new_entries.push_back(std::move(window->tabs.front()));
else
new_entries.push_back(std::move(entry));
}
break;
}
}
}
entries_ = std::move(new_entries);
if (observer_)
observer_->OnNavigationEntriesDeleted();
NotifyTabsChanged();
}
const TabRestoreService::Entries& TabRestoreServiceHelper::entries() const {
return entries_;
}
std::vector<LiveTab*> TabRestoreServiceHelper::RestoreMostRecentEntry(
LiveTabContext* context) {
if (entries_.empty())
return std::vector<LiveTab*>();
return RestoreEntryById(context, entries_.front()->id,
WindowOpenDisposition::UNKNOWN);
}
std::unique_ptr<TabRestoreService::Tab>
TabRestoreServiceHelper::RemoveTabEntryById(SessionID id) {
auto it = GetEntryIteratorById(id);
if (it == entries_.end())
return nullptr;
if ((*it)->type != TabRestoreService::TAB)
return nullptr;
auto tab = std::unique_ptr<Tab>(static_cast<Tab*>(it->release()));
entries_.erase(it);
NotifyTabsChanged();
return tab;
}
std::vector<LiveTab*> TabRestoreServiceHelper::RestoreEntryById(
LiveTabContext* context,
SessionID id,
WindowOpenDisposition disposition) {
auto entry_iterator = GetEntryIteratorById(id);
if (entry_iterator == entries_.end()) {
// Don't hoark here, we allow an invalid id.
return std::vector<LiveTab*>();
}
if (observer_)
observer_->OnRestoreEntryById(id, entry_iterator);
restoring_ = true;
auto& 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;
// |context| 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.
std::vector<LiveTab*> live_tabs;
switch (entry.type) {
case TabRestoreService::TAB: {
auto& tab = static_cast<const Tab&>(entry);
LiveTab* restored_tab = nullptr;
context = RestoreTab(tab, context, disposition, &restored_tab);
live_tabs.push_back(restored_tab);
context->ShowBrowserWindow();
break;
}
case TabRestoreService::WINDOW: {
LiveTabContext* current_context = context;
auto& 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) {
context =
client_->CreateLiveTabContext(window.app_name, window.bounds,
window.show_state, window.workspace);
for (size_t tab_i = 0; tab_i < window.tabs.size(); ++tab_i) {
const Tab& tab = *window.tabs[tab_i];
LiveTab* restored_tab = context->AddRestoredTab(
tab.navigations, context->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.platform_data.get(),
tab.user_agent_override);
if (restored_tab) {
client_->OnTabRestored(
tab.navigations.at(tab.current_navigation_index).virtual_url());
live_tabs.push_back(restored_tab);
}
}
// All the window's tabs had the same former browser_id.
if (auto browser_id = window.tabs[0]->browser_id) {
UpdateTabBrowserIDs(browser_id, context->GetSessionID());
}
} else {
// Restore a single tab from the window. Find the tab that matches the
// ID in the window and restore it.
for (size_t tab_i = 0; tab_i < window.tabs.size(); tab_i++) {
SessionID::id_type restored_tab_browser_id;
{
const Tab& tab = *window.tabs[tab_i];
if (tab.id != id)
continue;
restored_tab_browser_id = tab.browser_id;
LiveTab* restored_tab = nullptr;
context = RestoreTab(tab, context, disposition, &restored_tab);
live_tabs.push_back(restored_tab);
DCHECK(ValidateWindow(window));
window.tabs.erase(window.tabs.begin() + tab_i);
}
// If restoring the tab leaves the window with nothing else, delete it
// as well.
if (window.tabs.empty()) {
entries_.erase(entry_iterator);
} else {
// Adjust |selected_tab index| to keep the window in a valid state.
if (static_cast<int>(tab_i) <= window.selected_tab_index) {
window.selected_tab_index =
std::max(0, window.selected_tab_index - 1);
}
DCHECK(ValidateWindow(window));
// 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(restored_tab_browser_id,
context->GetSessionID());
for (auto& tab_j : window.tabs)
tab_j->browser_id = context->GetSessionID().id();
}
break;
}
}
context->ShowBrowserWindow();
if (disposition == WindowOpenDisposition::CURRENT_TAB &&
current_context && current_context->GetActiveLiveTab()) {
current_context->CloseTab();
}
break;
}
}
if (!restoring_tab_in_window) {
entries_.erase(entry_iterator);
}
restoring_ = false;
NotifyTabsChanged();
return live_tabs;
}
bool TabRestoreServiceHelper::IsRestoring() const {
return restoring_;
}
void TabRestoreServiceHelper::NotifyTabsChanged() {
for (auto& observer : observer_list_)
observer.TabRestoreServiceChanged(tab_restore_service_);
}
void TabRestoreServiceHelper::NotifyLoaded() {
for (auto& observer : observer_list_)
observer.TabRestoreServiceLoaded(tab_restore_service_);
}
void TabRestoreServiceHelper::AddEntry(std::unique_ptr<Entry> entry,
bool notify,
bool to_front) {
if (!FilterEntry(*entry) || (entries_.size() >= kMaxEntries && !to_front)) {
return;
}
if (to_front)
entries_.push_front(std::move(entry));
else
entries_.push_back(std::move(entry));
PruneEntries();
if (notify)
NotifyTabsChanged();
if (observer_)
observer_->OnAddEntry();
}
void TabRestoreServiceHelper::PruneEntries() {
Entries new_entries;
for (auto& entry : entries_) {
if (FilterEntry(*entry) && new_entries.size() < kMaxEntries) {
new_entries.push_back(std::move(entry));
}
}
entries_ = std::move(new_entries);
}
TabRestoreService::Entries::iterator
TabRestoreServiceHelper::GetEntryIteratorById(SessionID id) {
for (auto 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 == TabRestoreService::WINDOW) {
auto& window = static_cast<const Window&>(**i);
for (const auto& tab : window.tabs) {
if (tab->id == id) {
return i;
}
}
}
}
return entries_.end();
}
bool TabRestoreServiceHelper::OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) {
using base::trace_event::MemoryAllocatorDump;
const char* system_allocator_name =
base::trace_event::MemoryDumpManager::GetInstance()
->system_allocator_pool_name();
if (entries_.empty()) {
// Nothing to report
return true;
}
std::string entries_dump_name = base::StringPrintf(
"tab_restore/service_helper_0x%" PRIXPTR "/entries",
reinterpret_cast<uintptr_t>(this));
pmd->CreateAllocatorDump(entries_dump_name)
->AddScalar(MemoryAllocatorDump::kNameObjectCount,
MemoryAllocatorDump::kUnitsObjects,
entries_.size());
for (const auto& entry : entries_) {
const char* type_string = "";
switch (entry->type) {
case TabRestoreService::WINDOW:
type_string = "window";
break;
case TabRestoreService::TAB:
type_string = "tab";
break;
}
std::string entry_dump_name = base::StringPrintf(
"%s/%s_0x%" PRIXPTR,
entries_dump_name.c_str(),
type_string,
reinterpret_cast<uintptr_t>(entry.get()));
auto* entry_dump = pmd->CreateAllocatorDump(entry_dump_name);
entry_dump->AddScalar(MemoryAllocatorDump::kNameSize,
MemoryAllocatorDump::kUnitsBytes,
entry->EstimateMemoryUsage());
auto age = base::Time::Now() - entry->timestamp;
entry_dump->AddScalar("age",
MemoryAllocatorDump::kUnitsObjects,
age.InSeconds());
if (system_allocator_name)
pmd->AddSuballocation(entry_dump->guid(), system_allocator_name);
}
return true;
}
// static
bool TabRestoreServiceHelper::ValidateEntry(const Entry& entry) {
switch (entry.type) {
case TabRestoreService::TAB:
return ValidateTab(static_cast<const Tab&>(entry));
case TabRestoreService::WINDOW:
return ValidateWindow(static_cast<const Window&>(entry));
}
NOTREACHED();
return false;
}
void TabRestoreServiceHelper::PopulateTab(Tab* tab,
int index,
LiveTabContext* context,
LiveTab* live_tab) {
int entry_count =
live_tab->IsInitialBlankNavigation() ? 0 : live_tab->GetEntryCount();
tab->navigations.resize(static_cast<int>(entry_count));
for (int i = 0; i < entry_count; ++i) {
SerializedNavigationEntry entry = live_tab->GetEntryAtIndex(i);
tab->navigations[i] = entry;
}
tab->timestamp = TimeNow();
tab->current_navigation_index = live_tab->GetCurrentEntryIndex();
tab->tabstrip_index = index;
tab->extension_app_id = client_->GetExtensionAppIDForTab(live_tab);
tab->user_agent_override = live_tab->GetUserAgentOverride();
tab->platform_data = live_tab->GetPlatformSpecificTabData();
// Delegate may be NULL during unit tests.
if (context) {
tab->browser_id = context->GetSessionID().id();
tab->pinned = context->IsTabPinned(tab->tabstrip_index);
}
}
LiveTabContext* TabRestoreServiceHelper::RestoreTab(
const Tab& tab,
LiveTabContext* context,
WindowOpenDisposition disposition,
LiveTab** live_tab) {
LiveTab* restored_tab;
if (disposition == WindowOpenDisposition::CURRENT_TAB && context) {
restored_tab = context->ReplaceRestoredTab(
tab.navigations, tab.current_navigation_index, tab.from_last_session,
tab.extension_app_id, tab.platform_data.get(), tab.user_agent_override);
} else {
// We only respect the tab's original browser if there's no disposition.
if (disposition == WindowOpenDisposition::UNKNOWN && tab.browser_id) {
context = client_->FindLiveTabContextWithID(
SessionID::FromSerializedValue(tab.browser_id));
}
int tab_index = -1;
// |context| 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 (context && disposition != WindowOpenDisposition::NEW_WINDOW) {
tab_index = tab.tabstrip_index;
} else {
context = client_->CreateLiveTabContext(
std::string(), gfx::Rect(), ui::SHOW_STATE_NORMAL, std::string());
if (tab.browser_id)
UpdateTabBrowserIDs(tab.browser_id, context->GetSessionID());
}
// 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 > context->GetTabCount() ||
disposition != WindowOpenDisposition::UNKNOWN) {
tab_index = context->GetTabCount();
}
restored_tab = context->AddRestoredTab(
tab.navigations, tab_index, tab.current_navigation_index,
tab.extension_app_id,
disposition != WindowOpenDisposition::NEW_BACKGROUND_TAB, tab.pinned,
tab.from_last_session, tab.platform_data.get(),
tab.user_agent_override);
}
client_->OnTabRestored(
tab.navigations.at(tab.current_navigation_index).virtual_url());
if (live_tab)
*live_tab = restored_tab;
return context;
}
bool TabRestoreServiceHelper::ValidateTab(const Tab& tab) {
return !tab.navigations.empty() &&
static_cast<size_t>(tab.current_navigation_index) <
tab.navigations.size();
}
bool TabRestoreServiceHelper::ValidateWindow(const Window& window) {
if (static_cast<size_t>(window.selected_tab_index) >= window.tabs.size())
return false;
for (const auto& tab : window.tabs) {
if (!ValidateTab(*tab))
return false;
}
return true;
}
bool TabRestoreServiceHelper::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() != client_->GetNewTabURL();
}
bool TabRestoreServiceHelper::IsWindowInteresting(const Window& window) {
if (window.tabs.empty())
return false;
if (window.tabs.size() > 1)
return true;
return IsTabInteresting(*window.tabs[0]);
}
bool TabRestoreServiceHelper::FilterEntry(const Entry& entry) {
if (!ValidateEntry(entry))
return false;
switch (entry.type) {
case TabRestoreService::TAB:
return IsTabInteresting(static_cast<const Tab&>(entry));
case TabRestoreService::WINDOW:
return IsWindowInteresting(static_cast<const Window&>(entry));
}
NOTREACHED();
return false;
}
void TabRestoreServiceHelper::UpdateTabBrowserIDs(SessionID::id_type old_id,
SessionID new_id) {
for (const auto& entry : entries_) {
if (entry->type == TabRestoreService::TAB) {
auto& tab = static_cast<Tab&>(*entry);
if (tab.browser_id == old_id)
tab.browser_id = new_id.id();
}
}
}
base::Time TabRestoreServiceHelper::TimeNow() const {
return time_factory_ ? time_factory_->TimeNow() : base::Time::Now();
}
} // namespace sessions