blob: 7404bda07e9422ad199d6f65dc73b386db014631 [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 <algorithm>
#include <utility>
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "components/sync/protocol/session_specifics.pb.h"
#include "components/sync_sessions/sync_sessions_client.h"
#include "components/sync_sessions/synced_tab_delegate.h"
namespace sync_sessions {
const base::Feature kDeferRecyclingOfSyncTabNodesIfUnsynced{
"DeferRecyclingOfSyncTabNodesIfUnsynced",
base::FEATURE_DISABLED_BY_DEFAULT};
namespace {
// Maximum time we allow a local tab stay unmapped (i.e. closed) but not freed
// due to data not having been committed yet. After that time, the data will
// be dropped.
constexpr base::TimeDelta kMaxUnmappedButUnsyncedLocalTabAge =
base::TimeDelta::FromDays(1);
// This is a generous cap to avoid issues with situations like sync being in
// error state (e.g. auth error) during which many tabs could be opened and
// closed, and still the information would not be committed.
constexpr int kMaxUnmappedButUnsyncedLocalTabCount = 20;
// 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(SyncSessionsClient* sessions_client,
const sessions::SessionWindow& window) {
for (const auto& tab : window.tabs) {
for (const sessions::SerializedNavigationEntry& navigation :
tab->navigations) {
if (sessions_client->ShouldSyncURL(navigation.virtual_url())) {
return true;
}
}
}
return false;
}
// Presentable means |foreign_session| must have syncable content.
bool IsPresentable(SyncSessionsClient* sessions_client,
const SyncedSession& foreign_session) {
for (auto iter = foreign_session.windows.begin();
iter != foreign_session.windows.end(); ++iter) {
if (ShouldSyncSessionWindow(sessions_client,
iter->second->wrapped_window)) {
return true;
}
}
return false;
}
// Verify that tab IDs appear only once within a session. Intended to prevent
// http://crbug.com/360822.
bool IsValidSessionHeader(const sync_pb::SessionHeader& header) {
std::set<int> session_tab_ids;
for (int i = 0; i < header.window_size(); ++i) {
const sync_pb::SessionWindow& window = header.window(i);
for (int j = 0; j < window.tab_size(); ++j) {
const int tab_id = window.tab(j);
bool success = session_tab_ids.insert(tab_id).second;
if (!success)
return false;
}
}
return true;
}
void PopulateSyncedSessionWindowFromSpecifics(
const std::string& session_tag,
const sync_pb::SessionWindow& specifics,
base::Time mtime,
SyncedSessionWindow* synced_session_window,
SyncedSessionTracker* tracker) {
sessions::SessionWindow* session_window =
&synced_session_window->wrapped_window;
if (specifics.has_window_id()) {
session_window->window_id =
SessionID::FromSerializedValue(specifics.window_id());
}
if (specifics.has_selected_tab_index())
session_window->selected_tab_index = specifics.selected_tab_index();
synced_session_window->window_type = specifics.browser_type();
if (specifics.has_browser_type()) {
if (specifics.browser_type() ==
sync_pb::SessionWindow_BrowserType_TYPE_TABBED) {
session_window->type = sessions::SessionWindow::TYPE_TABBED;
} else {
// Note: custom tabs are treated like popup windows on restore, as you can
// restore a custom tab on a platform that doesn't support them.
session_window->type = sessions::SessionWindow::TYPE_POPUP;
}
}
session_window->timestamp = mtime;
session_window->tabs.clear();
for (int i = 0; i < specifics.tab_size(); i++) {
SessionID tab_id = SessionID::FromSerializedValue(specifics.tab(i));
tracker->PutTabInWindow(session_tag, session_window->window_id, tab_id);
}
}
void PopulateSyncedSessionFromSpecifics(
const std::string& session_tag,
const sync_pb::SessionHeader& header_specifics,
base::Time mtime,
SyncedSession* synced_session,
SyncedSessionTracker* tracker) {
if (header_specifics.has_client_name())
synced_session->session_name = header_specifics.client_name();
if (header_specifics.has_device_type()) {
synced_session->device_type = header_specifics.device_type();
}
synced_session->modified_time =
std::max(mtime, synced_session->modified_time);
// Process all the windows and their tab information.
int num_windows = header_specifics.window_size();
DVLOG(1) << "Populating " << session_tag << " with " << num_windows
<< " windows.";
for (int i = 0; i < num_windows; ++i) {
const sync_pb::SessionWindow& window_s = header_specifics.window(i);
SessionID window_id = SessionID::FromSerializedValue(window_s.window_id());
tracker->PutWindowInSession(session_tag, window_id);
PopulateSyncedSessionWindowFromSpecifics(
session_tag, window_s, synced_session->modified_time,
synced_session->windows[window_id].get(), tracker);
}
}
} // namespace
SyncedSessionTracker::TrackedSession::TrackedSession() {}
SyncedSessionTracker::TrackedSession::~TrackedSession() {}
SyncedSessionTracker::SyncedSessionTracker(SyncSessionsClient* sessions_client)
: sessions_client_(sessions_client) {}
SyncedSessionTracker::~SyncedSessionTracker() {}
void SyncedSessionTracker::InitLocalSession(
const std::string& local_session_tag,
const std::string& local_session_name,
sync_pb::SyncEnums::DeviceType local_device_type) {
DCHECK(local_session_tag_.empty());
DCHECK(!local_session_tag.empty());
local_session_tag_ = local_session_tag;
SyncedSession* local_session = GetSession(local_session_tag);
local_session->session_name = local_session_name;
local_session->device_type = local_device_type;
local_session->session_tag = local_session_tag;
}
const std::string& SyncedSessionTracker::GetLocalSessionTag() const {
return local_session_tag_;
}
std::vector<const SyncedSession*> SyncedSessionTracker::LookupAllSessions(
SessionLookup lookup) const {
return LookupSessions(lookup, /*exclude_local_session=*/false);
}
std::vector<const SyncedSession*>
SyncedSessionTracker::LookupAllForeignSessions(SessionLookup lookup) const {
return LookupSessions(lookup, /*exclude_local_session=*/true);
}
bool SyncedSessionTracker::LookupSessionWindows(
const std::string& session_tag,
std::vector<const sessions::SessionWindow*>* windows) const {
DCHECK(windows);
windows->clear();
const TrackedSession* session = LookupTrackedSession(session_tag);
if (!session)
return false; // We have no record of this session.
for (const auto& window_pair : session->synced_session.windows)
windows->push_back(&window_pair.second->wrapped_window);
return true;
}
const sessions::SessionTab* SyncedSessionTracker::LookupSessionTab(
const std::string& tag,
SessionID tab_id) const {
if (!tab_id.is_valid())
return nullptr;
const TrackedSession* session = LookupTrackedSession(tag);
if (!session)
return nullptr; // We have no record of this session.
auto tab_iter = session->synced_tab_map.find(tab_id);
if (tab_iter == session->synced_tab_map.end())
return nullptr; // We have no record of this tab.
return tab_iter->second;
}
std::set<int> SyncedSessionTracker::LookupTabNodeIds(
const std::string& session_tag) const {
const TrackedSession* session = LookupTrackedSession(session_tag);
return session ? session->tab_node_pool.GetAllTabNodeIds() : std::set<int>();
}
std::vector<const sessions::SessionTab*>
SyncedSessionTracker::LookupUnmappedTabs(const std::string& session_tag) const {
const TrackedSession* session = LookupTrackedSession(session_tag);
std::vector<const sessions::SessionTab*> unmapped_tabs;
if (session) {
for (const auto& unmapped_tab_entry : session->unmapped_tabs) {
unmapped_tabs.push_back(unmapped_tab_entry.second.get());
}
}
return unmapped_tabs;
}
const SyncedSession* SyncedSessionTracker::LookupLocalSession() const {
return LookupSession(local_session_tag_);
}
const SyncedSession* SyncedSessionTracker::LookupSession(
const std::string& session_tag) const {
const TrackedSession* session = LookupTrackedSession(session_tag);
return session ? &session->synced_session : nullptr;
}
SyncedSession* SyncedSessionTracker::GetSession(
const std::string& session_tag) {
return &GetTrackedSession(session_tag)->synced_session;
}
bool SyncedSessionTracker::DeleteForeignSession(
const std::string& session_tag) {
DCHECK_NE(local_session_tag_, session_tag);
auto iter = session_map_.find(session_tag);
if (iter == session_map_.end())
return false;
// An implicitly created session that has children tabs but no header node
// will have never had the device_type changed from unset.
const bool header_existed =
iter->second.synced_session.device_type != sync_pb::SyncEnums::TYPE_UNSET;
// SyncedSession's destructor will trigger deletion of windows which will in
// turn trigger the deletion of tabs. This doesn't affect the convenience
// maps.
session_map_.erase(iter);
return header_existed;
}
void SyncedSessionTracker::ResetSessionTracking(
const std::string& session_tag) {
TrackedSession* session = GetTrackedSession(session_tag);
for (auto& window_pair : session->synced_session.windows) {
// First unmap the tabs in the window.
for (auto& tab : window_pair.second->wrapped_window.tabs) {
SessionID tab_id = tab->tab_id;
session->unmapped_tabs[tab_id] = std::move(tab);
}
window_pair.second->wrapped_window.tabs.clear();
// Then unmap the window itself.
session->unmapped_windows[window_pair.first] =
std::move(window_pair.second);
}
session->synced_session.windows.clear();
}
void SyncedSessionTracker::DeleteForeignTab(const std::string& session_tag,
int tab_node_id) {
DCHECK_NE(local_session_tag_, session_tag);
TrackedSession* session = LookupTrackedSession(session_tag);
if (session)
session->tab_node_pool.DeleteTabNode(tab_node_id);
}
const SyncedSessionTracker::TrackedSession*
SyncedSessionTracker::LookupTrackedSession(
const std::string& session_tag) const {
auto it = session_map_.find(session_tag);
return it == session_map_.end() ? nullptr : &it->second;
}
SyncedSessionTracker::TrackedSession*
SyncedSessionTracker::LookupTrackedSession(const std::string& session_tag) {
auto it = session_map_.find(session_tag);
return it == session_map_.end() ? nullptr : &it->second;
}
SyncedSessionTracker::TrackedSession* SyncedSessionTracker::GetTrackedSession(
const std::string& session_tag) {
TrackedSession* session = LookupTrackedSession(session_tag);
if (session)
return session;
session = &session_map_[session_tag];
DVLOG(1) << "Creating new session with tag " << session_tag << " at "
<< session;
session->synced_session.session_tag = session_tag;
return session;
}
std::vector<const SyncedSession*> SyncedSessionTracker::LookupSessions(
SessionLookup lookup,
bool exclude_local_session) const {
std::vector<const SyncedSession*> sessions;
for (const auto& session_pair : session_map_) {
const SyncedSession& session = session_pair.second.synced_session;
if (lookup == PRESENTABLE && !IsPresentable(sessions_client_, session)) {
continue;
}
if (exclude_local_session && session_pair.first == local_session_tag_) {
continue;
}
sessions.push_back(&session);
}
return sessions;
}
void SyncedSessionTracker::CleanupSessionImpl(
const std::string& session_tag,
const base::RepeatingCallback<bool(int /*tab_node_id*/)>&
is_tab_node_unsynced_cb) {
TrackedSession* session = LookupTrackedSession(session_tag);
if (!session)
return;
for (const auto& window_pair : session->unmapped_windows)
session->synced_window_map.erase(window_pair.first);
session->unmapped_windows.clear();
int num_unmapped_and_unsynced = 0;
auto tab_it = session->unmapped_tabs.begin();
while (tab_it != session->unmapped_tabs.end()) {
SessionID tab_id = tab_it->first;
if (session_tag == local_session_tag_) {
int tab_node_id = session->tab_node_pool.GetTabNodeIdFromTabId(tab_id);
const base::TimeDelta time_since_last_modified =
base::Time::Now() - tab_it->second->timestamp;
if ((time_since_last_modified < kMaxUnmappedButUnsyncedLocalTabAge) &&
num_unmapped_and_unsynced < kMaxUnmappedButUnsyncedLocalTabCount &&
is_tab_node_unsynced_cb.Run(tab_node_id) &&
base::FeatureList::IsEnabled(
kDeferRecyclingOfSyncTabNodesIfUnsynced)) {
// Our caller has decided that this tab node cannot be reused at this
// point because there are pending changes to be committed that would
// otherwise be lost). Hence, it stays unmapped but we do not free the
// tab node for now (until future retries).
++tab_it;
++num_unmapped_and_unsynced;
continue;
}
session->tab_node_pool.FreeTab(tab_id);
}
session->synced_tab_map.erase(tab_id);
tab_it = session->unmapped_tabs.erase(tab_it);
}
}
bool SyncedSessionTracker::IsTabUnmappedForTesting(SessionID tab_id) {
const TrackedSession* session = LookupTrackedSession(local_session_tag_);
if (!session)
return false;
return session->unmapped_tabs.count(tab_id) != 0;
}
void SyncedSessionTracker::PutWindowInSession(const std::string& session_tag,
SessionID window_id) {
TrackedSession* session = GetTrackedSession(session_tag);
if (session->synced_session.windows.count(window_id) != 0) {
DVLOG(1) << "Window " << window_id << " already added to session "
<< session_tag;
return;
}
std::unique_ptr<SyncedSessionWindow> window;
auto iter = session->unmapped_windows.find(window_id);
if (iter != session->unmapped_windows.end()) {
DCHECK_EQ(session->synced_window_map[window_id], iter->second.get());
window = std::move(iter->second);
session->unmapped_windows.erase(iter);
DVLOG(1) << "Putting seen window " << window_id << " at " << window.get()
<< "in " << (session_tag == local_session_tag_ ? "local session"
: session_tag);
} else {
// Create the window.
window = std::make_unique<SyncedSessionWindow>();
window->wrapped_window.window_id = window_id;
session->synced_window_map[window_id] = window.get();
DVLOG(1) << "Putting new window " << window_id << " at " << window.get()
<< "in " << (session_tag == local_session_tag_ ? "local session"
: session_tag);
}
DCHECK_EQ(window->wrapped_window.window_id, window_id);
DCHECK(GetSession(session_tag)->windows.end() ==
GetSession(session_tag)->windows.find(window_id));
GetSession(session_tag)->windows[window_id] = std::move(window);
}
void SyncedSessionTracker::PutTabInWindow(const std::string& session_tag,
SessionID window_id,
SessionID tab_id) {
TrackedSession* session = LookupTrackedSession(session_tag);
DCHECK(session);
// We're called here for two reasons. 1) We've received an update to the
// SessionWindow information of a SessionHeader node for a 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 just call GetTab and ignore the result,
// creating a placeholder SessionTab in the process.
sessions::SessionTab* tab_ptr = GetTab(session_tag, tab_id);
// The tab should be unmapped.
std::unique_ptr<sessions::SessionTab> tab;
auto it = session->unmapped_tabs.find(tab_id);
if (it != session->unmapped_tabs.end()) {
tab = std::move(it->second);
session->unmapped_tabs.erase(it);
} else {
// The tab has already been mapped, possibly because of the tab node id
// being reused across tabs. Find the existing tab and move it to the right
// window.
for (auto& window_iter_pair : GetSession(session_tag)->windows) {
auto tab_iter = std::find_if(
window_iter_pair.second->wrapped_window.tabs.begin(),
window_iter_pair.second->wrapped_window.tabs.end(),
[&tab_ptr](const std::unique_ptr<sessions::SessionTab>& tab) {
return tab.get() == tab_ptr;
});
if (tab_iter != window_iter_pair.second->wrapped_window.tabs.end()) {
tab = std::move(*tab_iter);
window_iter_pair.second->wrapped_window.tabs.erase(tab_iter);
DVLOG(1) << "Moving tab " << tab_id << " from window "
<< window_iter_pair.first << " to " << window_id;
break;
}
}
// TODO(zea): remove this once PutTabInWindow isn't crashing anymore.
CHECK(tab) << " Unable to find tab " << tab_id
<< " within unmapped tabs or previously mapped windows."
<< " https://crbug.com/639009";
}
tab->window_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());
GetSession(session_tag)
->windows[window_id]
->wrapped_window.tabs.push_back(std::move(tab));
}
void SyncedSessionTracker::OnTabNodeSeen(const std::string& session_tag,
int tab_node_id,
SessionID tab_id) {
// TODO(mastiz): Revisit if the exception for |local_session_tag_| can be
// avoided and treat all sessions equally, which ideally involves merging this
// function with ReassociateLocalTab().
if (session_tag != local_session_tag_ &&
tab_node_id != TabNodePool::kInvalidTabNodeID) {
GetTrackedSession(session_tag)
->tab_node_pool.ReassociateTabNode(tab_node_id, tab_id);
}
}
sessions::SessionTab* SyncedSessionTracker::GetTab(
const std::string& session_tag,
SessionID tab_id) {
CHECK(tab_id.is_valid()) << "https://crbug.com/639009";
TrackedSession* session = GetTrackedSession(session_tag);
sessions::SessionTab* tab_ptr = nullptr;
auto iter = session->synced_tab_map.find(tab_id);
if (iter != session->synced_tab_map.end()) {
tab_ptr = iter->second;
if (VLOG_IS_ON(1)) {
std::string title;
if (tab_ptr->navigations.size() > 0) {
title =
" (" + base::UTF16ToUTF8(tab_ptr->navigations.back().title()) + ")";
}
DVLOG(1) << "Getting "
<< (session_tag == local_session_tag_ ? "local session"
: session_tag)
<< "'s seen tab " << tab_id << " at " << tab_ptr << " " << title;
}
} else {
auto tab = std::make_unique<sessions::SessionTab>();
tab_ptr = tab.get();
tab->tab_id = tab_id;
session->synced_tab_map[tab_id] = tab_ptr;
session->unmapped_tabs[tab_id] = std::move(tab);
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, tab_id);
return tab_ptr;
}
void SyncedSessionTracker::CleanupSession(const std::string& session_tag) {
DCHECK_NE(session_tag, local_session_tag_);
// |is_tab_node_unsynced_cb| is only used for the local session, not needed
// here.
CleanupSessionImpl(
session_tag,
/*=is_tab_node_unsynced_cb=*/base::RepeatingCallback<bool(int)>());
}
std::set<int> SyncedSessionTracker::CleanupLocalTabs(
const base::RepeatingCallback<bool(int /*tab_node_id*/)>&
is_tab_node_unsynced_cb) {
DCHECK(!local_session_tag_.empty());
TrackedSession* session = GetTrackedSession(local_session_tag_);
CleanupSessionImpl(local_session_tag_, is_tab_node_unsynced_cb);
std::set<int> deleted_node_ids;
session->tab_node_pool.CleanupTabNodes(&deleted_node_ids);
return deleted_node_ids;
}
int SyncedSessionTracker::LookupTabNodeFromTabId(const std::string& session_tag,
SessionID tab_id) const {
const TrackedSession* session = LookupTrackedSession(session_tag);
DCHECK(session);
return session->tab_node_pool.GetTabNodeIdFromTabId(tab_id);
}
SessionID SyncedSessionTracker::LookupTabIdFromTabNodeId(
const std::string& session_tag,
int tab_node_id) const {
const TrackedSession* session = LookupTrackedSession(session_tag);
DCHECK(session);
return session->tab_node_pool.GetTabIdFromTabNodeId(tab_node_id);
}
int SyncedSessionTracker::AssociateLocalTabWithFreeTabNode(SessionID tab_id) {
DCHECK(!local_session_tag_.empty());
TrackedSession* session = GetTrackedSession(local_session_tag_);
// Ensure a placeholder SessionTab is in place, if not already. Although we
// don't need a SessionTab to fulfill this request, this forces the creation
// of one if it doesn't already exist. This helps to make sure we're tracking
// this |tab_id| if TabNodePool is, and everyone's data structures are kept in
// sync and as consistent as possible.
GetTab(local_session_tag_, tab_id); // Ignore result.
return session->tab_node_pool.AssociateWithFreeTabNode(tab_id);
}
void SyncedSessionTracker::ReassociateLocalTab(int tab_node_id,
SessionID new_tab_id) {
DCHECK(!local_session_tag_.empty());
DCHECK_NE(TabNodePool::kInvalidTabNodeID, tab_node_id);
DCHECK(new_tab_id.is_valid());
TrackedSession* session = LookupTrackedSession(local_session_tag_);
DCHECK(session);
SessionID old_tab_id =
session->tab_node_pool.GetTabIdFromTabNodeId(tab_node_id);
if (new_tab_id == old_tab_id) {
return;
}
session->tab_node_pool.ReassociateTabNode(tab_node_id, new_tab_id);
sessions::SessionTab* tab_ptr = nullptr;
auto new_tab_iter = session->synced_tab_map.find(new_tab_id);
auto old_tab_iter = session->synced_tab_map.find(old_tab_id);
if (old_tab_id.is_valid() && old_tab_iter != session->synced_tab_map.end()) {
tab_ptr = old_tab_iter->second;
DCHECK(tab_ptr);
// Remove the tab from the synced tab map under the old id.
session->synced_tab_map.erase(old_tab_iter);
if (new_tab_iter != session->synced_tab_map.end()) {
// If both the old and the new tab already exist, delete the new tab
// and use the old tab in its place.
auto unmapped_tabs_iter = session->unmapped_tabs.find(new_tab_id);
if (unmapped_tabs_iter != session->unmapped_tabs.end()) {
session->unmapped_tabs.erase(unmapped_tabs_iter);
} else {
sessions::SessionTab* new_tab_ptr = new_tab_iter->second;
for (auto& window_iter_pair : session->synced_session.windows) {
auto& window_tabs = window_iter_pair.second->wrapped_window.tabs;
auto tab_iter = std::find_if(
window_tabs.begin(), window_tabs.end(),
[&new_tab_ptr](const std::unique_ptr<sessions::SessionTab>& tab) {
return tab.get() == new_tab_ptr;
});
if (tab_iter != window_tabs.end()) {
window_tabs.erase(tab_iter);
break;
}
}
}
session->synced_tab_map.erase(new_tab_iter);
}
// If the old tab is unmapped, update the tab id under which it is
// indexed.
auto unmapped_tabs_iter = session->unmapped_tabs.find(old_tab_id);
if (old_tab_id.is_valid() &&
unmapped_tabs_iter != session->unmapped_tabs.end()) {
std::unique_ptr<sessions::SessionTab> tab =
std::move(unmapped_tabs_iter->second);
DCHECK_EQ(tab_ptr, tab.get());
session->unmapped_tabs.erase(unmapped_tabs_iter);
session->unmapped_tabs[new_tab_id] = std::move(tab);
}
}
if (tab_ptr == nullptr) {
// It's possible a placeholder is already in place for the new tab. If so,
// reuse it, otherwise create a new one (which will default to unmapped).
tab_ptr = GetTab(local_session_tag_, new_tab_id);
}
// Update the tab id.
if (old_tab_id.is_valid()) {
DVLOG(1) << "Remapped tab " << old_tab_id << " with node " << tab_node_id
<< " to tab " << new_tab_id;
} else {
DVLOG(1) << "Mapped new tab node " << tab_node_id << " to tab "
<< new_tab_id;
}
tab_ptr->tab_id = new_tab_id;
// Add the tab back into the tab map with the new id.
session->synced_tab_map[new_tab_id] = tab_ptr;
}
void SyncedSessionTracker::Clear() {
session_map_.clear();
local_session_tag_.clear();
}
void UpdateTrackerWithSpecifics(const sync_pb::SessionSpecifics& specifics,
base::Time modification_time,
SyncedSessionTracker* tracker) {
std::string session_tag = specifics.session_tag();
SyncedSession* session = tracker->GetSession(session_tag);
if (specifics.has_header()) {
// Read in the header data for this session. Header data is
// essentially a collection of windows, each of which has an ordered id list
// for their tabs.
if (!IsValidSessionHeader(specifics.header())) {
DLOG(WARNING) << "Ignoring session node with invalid header "
<< "and tag " << session_tag << ".";
return;
}
// Load (or create) the SyncedSession object for this client.
const sync_pb::SessionHeader& header = specifics.header();
// Reset the tab/window tracking for this session (must do this before
// we start calling PutWindowInSession and PutTabInWindow so that all
// unused tabs/windows get cleared by the CleanupSession(...) call).
tracker->ResetSessionTracking(session_tag);
PopulateSyncedSessionFromSpecifics(session_tag, header, modification_time,
session, tracker);
// Delete any closed windows and unused tabs as necessary. We exclude the
// local session here because it should be cleaned up explicitly with
// CleanupLocalTabs().
if (session_tag != tracker->GetLocalSessionTag())
tracker->CleanupSession(session_tag);
} else if (specifics.has_tab()) {
const sync_pb::SessionTab& tab_s = specifics.tab();
SessionID tab_id = SessionID::FromSerializedValue(tab_s.tab_id());
if (!tab_id.is_valid()) {
DLOG(WARNING) << "Ignoring session tab with invalid tab ID for session "
<< "tag " << session_tag << " and node ID "
<< specifics.tab_node_id() << ".";
return;
}
DVLOG(1) << "Populating " << session_tag << "'s tab id " << tab_id
<< " from node " << specifics.tab_node_id();
// Ensure the tracker is aware of the tab node id. Deleting foreign sessions
// requires deleting all relevant tab nodes, and it's easier to track the
// tab node ids themselves separately from the tab ids.
//
// Note that 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 happened 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 currently kInvalidTabNodeID.
//
// In both cases, we can safely throw it into the set of node ids.
tracker->OnTabNodeSeen(session_tag, specifics.tab_node_id(), tab_id);
sessions::SessionTab* tab = tracker->GetTab(session_tag, tab_id);
if (!tab->timestamp.is_null() && tab->timestamp > modification_time) {
DVLOG(1) << "Ignoring " << session_tag << "'s session tab " << tab_id
<< " with earlier modification time: " << tab->timestamp
<< " vs " << modification_time;
return;
}
// Update SessionTab based on protobuf.
SetSessionTabFromSyncData(tab_s, modification_time, tab);
// Update the last modified time.
if (session->modified_time < modification_time)
session->modified_time = modification_time;
} else {
LOG(WARNING) << "Ignoring session node with missing header/tab "
<< "fields and tag " << session_tag << ".";
}
}
void SerializeTrackerToSpecifics(
const SyncedSessionTracker& tracker,
const base::RepeatingCallback<void(const std::string& session_name,
sync_pb::SessionSpecifics* specifics)>&
output_cb) {
std::map<std::string, std::set<int>> session_tag_to_node_ids;
for (const SyncedSession* session :
tracker.LookupAllSessions(SyncedSessionTracker::RAW)) {
// Request all tabs.
session_tag_to_node_ids[session->session_tag] =
tracker.LookupTabNodeIds(session->session_tag);
// Request the header too.
session_tag_to_node_ids[session->session_tag].insert(
TabNodePool::kInvalidTabNodeID);
}
SerializePartialTrackerToSpecifics(tracker, session_tag_to_node_ids,
output_cb);
}
void SerializePartialTrackerToSpecifics(
const SyncedSessionTracker& tracker,
const std::map<std::string, std::set<int>>& session_tag_to_node_ids,
const base::RepeatingCallback<void(const std::string& session_name,
sync_pb::SessionSpecifics* specifics)>&
output_cb) {
for (const auto& session_entry : session_tag_to_node_ids) {
const std::string& session_tag = session_entry.first;
const SyncedSession* session = tracker.LookupSession(session_tag);
if (!session) {
// Unknown session.
continue;
}
const std::set<int> known_tab_node_ids =
tracker.LookupTabNodeIds(session_tag);
for (int tab_node_id : session_entry.second) {
// Header entity.
if (tab_node_id == TabNodePool::kInvalidTabNodeID) {
sync_pb::SessionSpecifics header_pb;
header_pb.set_session_tag(session_tag);
session->ToSessionHeaderProto().Swap(header_pb.mutable_header());
output_cb.Run(session->session_name, &header_pb);
continue;
}
// Check if |tab_node_id| is known by the tracker.
if (known_tab_node_ids.count(tab_node_id) == 0) {
continue;
}
// Tab entities.
const SessionID tab_id =
tracker.LookupTabIdFromTabNodeId(session_tag, tab_node_id);
if (!tab_id.is_valid()) {
// This can be the case for tabs that were unmapped because we received
// a new foreign tab with the same tab ID (the last one wins), so we
// don't remember the tab ID for the original |tab_node_id|. Instead of
// returning partially populated SessionSpecifics (without tab ID), we
// simply drop them, because older clients don't handle well such
// invalid specifics.
continue;
}
const sessions::SessionTab* tab =
tracker.LookupSessionTab(session_tag, tab_id);
if (tab) {
// Associated/mapped tab node.
sync_pb::SessionSpecifics tab_pb;
tab_pb.set_session_tag(session_tag);
tab_pb.set_tab_node_id(tab_node_id);
SessionTabToSyncData(*tab).Swap(tab_pb.mutable_tab());
output_cb.Run(session->session_name, &tab_pb);
continue;
}
// Create entities for unmapped tabs nodes.
sync_pb::SessionSpecifics tab_pb;
tab_pb.set_tab_node_id(tab_node_id);
tab_pb.set_session_tag(session_tag);
tab_pb.mutable_tab()->set_tab_id(tab_id.id());
output_cb.Run(session->session_name, &tab_pb);
}
}
}
} // namespace sync_sessions