blob: 5dbcf146f5579c063071a5fa69ee6951f99f895c [file] [log] [blame]
// 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 "components/sync_sessions/synced_session_tracker.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/sync_sessions/sync_sessions_client.h"
namespace browser_sync {
namespace {
// Helper for iterating through all tabs within a window, and all navigations
// within a tab, to find if there's a valid syncable url.
bool ShouldSyncSessionWindow(sync_sessions::SyncSessionsClient* sessions_client,
const sessions::SessionWindow& window) {
for (sessions::SessionTab* const tab : window.tabs) {
for (const sessions::SerializedNavigationEntry& navigation :
tab->navigations) {
if (sessions_client->ShouldSyncURL(navigation.virtual_url())) {
return true;
}
}
}
return false;
}
} // namespace
SyncedSessionTracker::SyncedSessionTracker(
sync_sessions::SyncSessionsClient* sessions_client)
: sessions_client_(sessions_client) {}
SyncedSessionTracker::~SyncedSessionTracker() {
Clear();
}
void SyncedSessionTracker::SetLocalSessionTag(
const std::string& local_session_tag) {
local_session_tag_ = local_session_tag;
}
bool SyncedSessionTracker::LookupAllForeignSessions(
std::vector<const sync_driver::SyncedSession*>* sessions) const {
DCHECK(sessions);
sessions->clear();
// Fill vector of sessions from our synced session map.
for (SyncedSessionMap::const_iterator i = synced_session_map_.begin();
i != synced_session_map_.end(); ++i) {
// Only include foreign sessions with open tabs.
sync_driver::SyncedSession* foreign_session = i->second;
if (i->first != local_session_tag_ && !foreign_session->windows.empty()) {
bool found_tabs = false;
for (sync_driver::SyncedSession::SyncedWindowMap::const_iterator iter =
foreign_session->windows.begin();
iter != foreign_session->windows.end(); ++iter) {
if (ShouldSyncSessionWindow(sessions_client_, *(iter->second))) {
found_tabs = true;
break;
}
}
if (found_tabs)
sessions->push_back(foreign_session);
}
}
return !sessions->empty();
}
bool SyncedSessionTracker::LookupSessionWindows(
const std::string& session_tag,
std::vector<const sessions::SessionWindow*>* windows) const {
DCHECK(windows);
windows->clear();
SyncedSessionMap::const_iterator iter = synced_session_map_.find(session_tag);
if (iter == synced_session_map_.end())
return false;
windows->clear();
for (sync_driver::SyncedSession::SyncedWindowMap::const_iterator window_iter =
iter->second->windows.begin();
window_iter != iter->second->windows.end(); ++window_iter) {
windows->push_back(window_iter->second);
}
return true;
}
bool SyncedSessionTracker::LookupSessionTab(
const std::string& tag,
SessionID::id_type tab_id,
const sessions::SessionTab** tab) const {
DCHECK(tab);
SyncedTabMap::const_iterator tab_map_iter = synced_tab_map_.find(tag);
if (tab_map_iter == synced_tab_map_.end()) {
// We have no record of this session.
*tab = NULL;
return false;
}
IDToSessionTabMap::const_iterator tab_iter =
tab_map_iter->second.find(tab_id);
if (tab_iter == tab_map_iter->second.end()) {
// We have no record of this tab.
*tab = NULL;
return false;
}
*tab = tab_iter->second.tab_ptr;
return true;
}
bool SyncedSessionTracker::LookupTabNodeIds(const std::string& session_tag,
std::set<int>* tab_node_ids) {
tab_node_ids->clear();
SyncedTabMap::const_iterator tab_map_iter = synced_tab_map_.find(session_tag);
if (tab_map_iter == synced_tab_map_.end())
return false;
IDToSessionTabMap::const_iterator tab_iter = tab_map_iter->second.begin();
while (tab_iter != tab_map_iter->second.end()) {
if (tab_iter->second.tab_node_id != TabNodePool::kInvalidTabNodeID)
tab_node_ids->insert(tab_iter->second.tab_node_id);
++tab_iter;
}
return true;
}
bool SyncedSessionTracker::LookupLocalSession(
const sync_driver::SyncedSession** output) const {
SyncedSessionMap::const_iterator it =
synced_session_map_.find(local_session_tag_);
if (it != synced_session_map_.end()) {
*output = it->second;
return true;
}
return false;
}
sync_driver::SyncedSession* SyncedSessionTracker::GetSession(
const std::string& session_tag) {
sync_driver::SyncedSession* synced_session = NULL;
if (synced_session_map_.find(session_tag) != synced_session_map_.end()) {
synced_session = synced_session_map_[session_tag];
} else {
synced_session = new sync_driver::SyncedSession;
DVLOG(1) << "Creating new session with tag " << session_tag << " at "
<< synced_session;
synced_session->session_tag = session_tag;
synced_session_map_[session_tag] = synced_session;
}
DCHECK(synced_session);
return synced_session;
}
bool SyncedSessionTracker::DeleteSession(const std::string& session_tag) {
bool found_session = false;
SyncedSessionMap::iterator iter = synced_session_map_.find(session_tag);
if (iter != synced_session_map_.end()) {
sync_driver::SyncedSession* session = iter->second;
synced_session_map_.erase(iter);
// SyncedSession's destructor will trigger deletion of windows which will in
// turn trigger the deletion of tabs. This doens't affect wrappers.
delete session;
found_session = true;
}
// These two erase(...) calls only affect the wrappers.
synced_window_map_.erase(session_tag);
// It's possible there was no header node but there were tab nodes.
if (synced_tab_map_.erase(session_tag) > 0) {
found_session = true;
}
return found_session;
}
void SyncedSessionTracker::ResetSessionTracking(
const std::string& session_tag) {
// Reset window tracking.
GetSession(session_tag)->windows.clear();
SyncedWindowMap::iterator window_iter = synced_window_map_.find(session_tag);
if (window_iter != synced_window_map_.end()) {
for (IDToSessionWindowMap::iterator window_map_iter =
window_iter->second.begin();
window_map_iter != window_iter->second.end(); ++window_map_iter) {
window_map_iter->second.owned = false;
// We clear out the tabs to prevent double referencing of the same tab.
// All tabs that are in use will be added back as needed.
window_map_iter->second.window_ptr->tabs.clear();
}
}
// Reset tab tracking.
SyncedTabMap::iterator tab_iter = synced_tab_map_.find(session_tag);
if (tab_iter != synced_tab_map_.end()) {
for (IDToSessionTabMap::iterator tab_map_iter = tab_iter->second.begin();
tab_map_iter != tab_iter->second.end(); ++tab_map_iter) {
tab_map_iter->second.owned = false;
}
}
}
bool SyncedSessionTracker::DeleteOldSessionWindowIfNecessary(
SessionWindowWrapper window_wrapper) {
if (!window_wrapper.owned) {
DVLOG(1) << "Deleting closed window "
<< window_wrapper.window_ptr->window_id.id();
// Clear the tabs first, since we don't want the destructor to destroy
// them. Their deletion will be handled by DeleteOldSessionTabIfNecessary.
window_wrapper.window_ptr->tabs.clear();
delete window_wrapper.window_ptr;
return true;
}
return false;
}
bool SyncedSessionTracker::DeleteOldSessionTabIfNecessary(
SessionTabWrapper tab_wrapper) {
if (!tab_wrapper.owned) {
if (VLOG_IS_ON(1)) {
sessions::SessionTab* tab_ptr = tab_wrapper.tab_ptr;
std::string title;
if (tab_ptr->navigations.size() > 0) {
title =
" (" +
base::UTF16ToUTF8(
tab_ptr->navigations[tab_ptr->navigations.size() - 1].title()) +
")";
}
DVLOG(1) << "Deleting closed tab " << tab_ptr->tab_id.id() << title
<< " from window " << tab_ptr->window_id.id();
}
unmapped_tabs_.erase(tab_wrapper.tab_ptr);
delete tab_wrapper.tab_ptr;
return true;
}
return false;
}
void SyncedSessionTracker::CleanupSession(const std::string& session_tag) {
// Go through and delete any windows or tabs without owners.
SyncedWindowMap::iterator window_iter = synced_window_map_.find(session_tag);
if (window_iter != synced_window_map_.end()) {
for (IDToSessionWindowMap::iterator iter = window_iter->second.begin();
iter != window_iter->second.end();) {
SessionWindowWrapper window_wrapper = iter->second;
if (DeleteOldSessionWindowIfNecessary(window_wrapper))
window_iter->second.erase(iter++);
else
++iter;
}
}
SyncedTabMap::iterator tab_iter = synced_tab_map_.find(session_tag);
if (tab_iter != synced_tab_map_.end()) {
for (IDToSessionTabMap::iterator iter = tab_iter->second.begin();
iter != tab_iter->second.end();) {
SessionTabWrapper tab_wrapper = iter->second;
if (DeleteOldSessionTabIfNecessary(tab_wrapper)) {
tab_iter->second.erase(iter++);
} else {
++iter;
}
}
}
}
void SyncedSessionTracker::PutWindowInSession(const std::string& session_tag,
SessionID::id_type window_id) {
sessions::SessionWindow* window_ptr = NULL;
IDToSessionWindowMap::iterator iter =
synced_window_map_[session_tag].find(window_id);
if (iter != synced_window_map_[session_tag].end()) {
iter->second.owned = true;
window_ptr = iter->second.window_ptr;
DVLOG(1) << "Putting seen window " << window_id << " at " << window_ptr
<< "in " << (session_tag == local_session_tag_ ? "local session"
: session_tag);
} else {
// Create the window.
window_ptr = new sessions::SessionWindow();
window_ptr->window_id.set_id(window_id);
synced_window_map_[session_tag][window_id] =
SessionWindowWrapper(window_ptr, IS_OWNED);
DVLOG(1) << "Putting new window " << window_id << " at " << window_ptr
<< "in " << (session_tag == local_session_tag_ ? "local session"
: session_tag);
}
DCHECK(window_ptr);
DCHECK_EQ(window_ptr->window_id.id(), window_id);
DCHECK_EQ(reinterpret_cast<sessions::SessionWindow*>(NULL),
GetSession(session_tag)->windows[window_id]);
GetSession(session_tag)->windows[window_id] = window_ptr;
}
void SyncedSessionTracker::PutTabInWindow(const std::string& session_tag,
SessionID::id_type window_id,
SessionID::id_type tab_id,
size_t tab_index) {
// We're called here for two reasons. 1) We've received an update to the
// SessionWindow information of a SessionHeader node for a foreign session,
// and 2) The SessionHeader node for our local session changed. In both cases
// we need to update our tracking state to reflect the change.
//
// Because the SessionHeader nodes are separate from the individual tab nodes
// and we don't store tab_node_ids in the header / SessionWindow specifics,
// the tab_node_ids are not always available when processing headers.
// We know that we will eventually process (via GetTab) every single tab node
// in the system, so we permit ourselves to use kInvalidTabNodeID here and
// rely on the later update to build the mapping (or a restart).
// TODO(tim): Bug 98892. Update comment when Sync API conversion finishes to
// mention that in the meantime, the only ill effect is that we may not be
// able to fully clean up a stale foreign session, but it will get garbage
// collected eventually.
sessions::SessionTab* tab_ptr =
GetTabImpl(session_tag, tab_id, TabNodePool::kInvalidTabNodeID);
// It's up to the caller to ensure this never happens. Tabs should not
// belong to more than one window or appear twice within the same window.
//
// If this condition were violated, we would double-free during shutdown.
// That could cause all sorts of hard to diagnose crashes, possibly in code
// far away from here. We crash early to avoid this.
//
// See http://crbug.com/360822.
CHECK(!synced_tab_map_[session_tag][tab_id].owned);
// Only tabs that were just created in GetTabImpl(...) are still present in
// unmapped_tabs_. Most of the time when PutTabInWindow(...) is invoked, the
// specified tab was already a child of the specified window, and hasn't been
// in unmapped_tabs_ for quite some time. However, this is not a problem, as
// std::set::erase(...) will simply have no effect in these cases.
unmapped_tabs_.erase(tab_ptr);
synced_tab_map_[session_tag][tab_id].owned = true;
tab_ptr->window_id.set_id(window_id);
DVLOG(1) << " - tab " << tab_id << " added to window " << window_id;
DCHECK(GetSession(session_tag)->windows.find(window_id) !=
GetSession(session_tag)->windows.end());
std::vector<sessions::SessionTab*>& window_tabs =
GetSession(session_tag)->windows[window_id]->tabs;
if (window_tabs.size() <= tab_index) {
window_tabs.resize(tab_index + 1, NULL);
}
DCHECK(!window_tabs[tab_index]);
window_tabs[tab_index] = tab_ptr;
}
sessions::SessionTab* SyncedSessionTracker::GetTab(
const std::string& session_tag,
SessionID::id_type tab_id,
int tab_node_id) {
DCHECK_NE(TabNodePool::kInvalidTabNodeID, tab_node_id);
return GetTabImpl(session_tag, tab_id, tab_node_id);
}
sessions::SessionTab* SyncedSessionTracker::GetTabImpl(
const std::string& session_tag,
SessionID::id_type tab_id,
int tab_node_id) {
sessions::SessionTab* tab_ptr = NULL;
IDToSessionTabMap::iterator iter = synced_tab_map_[session_tag].find(tab_id);
if (iter != synced_tab_map_[session_tag].end()) {
tab_ptr = iter->second.tab_ptr;
if (tab_node_id != TabNodePool::kInvalidTabNodeID &&
tab_id != TabNodePool::kInvalidTabID) {
// TabIDs are not stable across restarts of a client. Consider this
// example with two tabs:
//
// http://a.com TabID1 --> NodeIDA
// http://b.com TabID2 --> NodeIDB
//
// After restart, tab ids are reallocated. e.g, one possibility:
// http://a.com TabID2 --> NodeIDA
// http://b.com TabID1 --> NodeIDB
//
// If that happend on a remote client, here we will see an update to
// TabID1 with tab_node_id changing from NodeIDA to NodeIDB, and TabID2
// with tab_node_id changing from NodeIDB to NodeIDA.
//
// We can also wind up here if we created this tab as an out-of-order
// update to the header node for this session before actually associating
// the tab itself, so the tab node id wasn't available at the time and
// is currenlty kInvalidTabNodeID.
//
// In both cases, we update the tab_node_id.
iter->second.tab_node_id = tab_node_id;
}
if (VLOG_IS_ON(1)) {
std::string title;
if (tab_ptr->navigations.size() > 0) {
title =
" (" +
base::UTF16ToUTF8(
tab_ptr->navigations[tab_ptr->navigations.size() - 1].title()) +
")";
}
DVLOG(1) << "Getting "
<< (session_tag == local_session_tag_ ? "local session"
: session_tag)
<< "'s seen tab " << tab_id << " at " << tab_ptr << title;
}
} else {
tab_ptr = new sessions::SessionTab();
tab_ptr->tab_id.set_id(tab_id);
synced_tab_map_[session_tag][tab_id] =
SessionTabWrapper(tab_ptr, NOT_OWNED, tab_node_id);
unmapped_tabs_.insert(tab_ptr);
DVLOG(1) << "Getting "
<< (session_tag == local_session_tag_ ? "local session"
: session_tag)
<< "'s new tab " << tab_id << " at " << tab_ptr;
}
DCHECK(tab_ptr);
DCHECK_EQ(tab_ptr->tab_id.id(), tab_id);
return tab_ptr;
}
void SyncedSessionTracker::Clear() {
// Delete SyncedSession objects (which also deletes all their windows/tabs).
STLDeleteValues(&synced_session_map_);
// Go through and delete any tabs we had allocated but had not yet placed into
// a SyncedSession object.
STLDeleteElements(&unmapped_tabs_);
// Get rid of our Window/Tab maps (does not delete the actual Window/Tabs
// themselves; they should have all been deleted above).
synced_window_map_.clear();
synced_tab_map_.clear();
local_session_tag_.clear();
}
} // namespace browser_sync