// Copyright (c) 2011 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/sync/glue/session_model_associator.h"
#include <algorithm>
#include <utility>
#include "base/logging.h"
#include "chrome/browser/extensions/extension_tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/syncable/syncable.h"
#include "chrome/browser/tabs/tab_strip_model.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/url_constants.h"
#include "content/browser/tab_contents/navigation_controller.h"
#include "content/browser/tab_contents/navigation_entry.h"
#include "content/common/notification_details.h"
#include "content/common/notification_service.h"
namespace browser_sync {
namespace {
static const char kNoSessionsFolderError[] =
"Server did not create the top-level sessions node. We "
"might be running against an out-of-date server.";
// The maximum number of navigations in each direction we care to sync.
static const int max_sync_navigation_count = 6;
} // namespace
SessionModelAssociator::SessionModelAssociator(ProfileSyncService* sync_service)
: tab_pool_(sync_service),
local_session_syncid_(sync_api::kInvalidId),
sync_service_(sync_service),
setup_for_test_(false) {
DCHECK(CalledOnValidThread());
DCHECK(sync_service_);
}
SessionModelAssociator::SessionModelAssociator(ProfileSyncService* sync_service,
bool setup_for_test)
: tab_pool_(sync_service),
local_session_syncid_(sync_api::kInvalidId),
sync_service_(sync_service),
setup_for_test_(setup_for_test) {
DCHECK(CalledOnValidThread());
DCHECK(sync_service_);
}
SessionModelAssociator::~SessionModelAssociator() {
DCHECK(CalledOnValidThread());
}
bool SessionModelAssociator::InitSyncNodeFromChromeId(
const std::string& id,
sync_api::BaseNode* sync_node) {
NOTREACHED();
return false;
}
bool SessionModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
DCHECK(CalledOnValidThread());
CHECK(has_nodes);
*has_nodes = false;
sync_api::ReadTransaction trans(sync_service_->GetUserShare());
sync_api::ReadNode root(&trans);
if (!root.InitByTagLookup(kSessionsTag)) {
LOG(ERROR) << kNoSessionsFolderError;
return false;
}
// The sync model has user created nodes iff the sessions folder has
// any children.
*has_nodes = root.GetFirstChildId() != sync_api::kInvalidId;
return true;
}
int64 SessionModelAssociator::GetSyncIdFromChromeId(const size_t& id) {
DCHECK(CalledOnValidThread());
return GetSyncIdFromSessionTag(TabIdToTag(GetCurrentMachineTag(), id));
}
int64 SessionModelAssociator::GetSyncIdFromSessionTag(const std::string& tag) {
DCHECK(CalledOnValidThread());
sync_api::ReadTransaction trans(sync_service_->GetUserShare());
sync_api::ReadNode node(&trans);
if (!node.InitByClientTagLookup(syncable::SESSIONS, tag))
return sync_api::kInvalidId;
return node.GetId();
}
const TabContents*
SessionModelAssociator::GetChromeNodeFromSyncId(int64 sync_id) {
NOTREACHED();
return NULL;
}
bool SessionModelAssociator::InitSyncNodeFromChromeId(
const size_t& id,
sync_api::BaseNode* sync_node) {
NOTREACHED();
return false;
}
void SessionModelAssociator::ReassociateWindows(bool reload_tabs) {
DCHECK(CalledOnValidThread());
sync_pb::SessionSpecifics specifics;
specifics.set_session_tag(GetCurrentMachineTag());
sync_pb::SessionHeader* header_s = specifics.mutable_header();
for (BrowserList::const_iterator i = BrowserList::begin();
i != BrowserList::end(); ++i) {
// Make sure the browser has tabs and a window. Browsers destructor
// removes itself from the BrowserList. When a browser is closed the
// destructor is not necessarily run immediately. This means its possible
// for us to get a handle to a browser that is about to be removed. If
// the tab count is 0 or the window is NULL, the browser is about to be
// deleted, so we ignore it.
if (ShouldSyncWindowType((*i)->type()) && (*i)->tab_count() &&
(*i)->window()) {
sync_pb::SessionWindow window_s;
SessionID::id_type window_id = (*i)->session_id().id();
VLOG(1) << "Reassociating window " << window_id << " with " <<
(*i)->tab_count() << " tabs.";
window_s.set_window_id(window_id);
window_s.set_selected_tab_index((*i)->active_index());
if ((*i)->type() ==
Browser::TYPE_NORMAL) {
window_s.set_browser_type(
sync_pb::SessionWindow_BrowserType_TYPE_NORMAL);
} else {
window_s.set_browser_type(
sync_pb::SessionWindow_BrowserType_TYPE_POPUP);
}
// Store the order of tabs.
bool found_tabs = false;
for (int j = 0; j < (*i)->tab_count(); ++j) {
TabContents* tab = (*i)->GetTabContentsAt(j);
DCHECK(tab);
if (IsValidTab(*tab)) {
found_tabs = true;
window_s.add_tab(tab->controller().session_id().id());
if (reload_tabs) {
ReassociateTab(*tab);
}
}
}
// Only add a window if it contains valid tabs.
if (found_tabs) {
sync_pb::SessionWindow* header_window = header_s->add_window();
*header_window = window_s;
}
}
}
sync_api::WriteTransaction trans(sync_service_->GetUserShare());
sync_api::WriteNode header_node(&trans);
if (!header_node.InitByIdLookup(local_session_syncid_)) {
LOG(ERROR) << "Failed to load local session header node.";
return;
}
header_node.SetSessionSpecifics(specifics);
}
// Static.
bool SessionModelAssociator::ShouldSyncWindowType(const Browser::Type& type) {
switch (type) {
case Browser::TYPE_POPUP:
return true;
case Browser::TYPE_APP:
return false;
case Browser::TYPE_APP_POPUP:
return false;
case Browser::TYPE_DEVTOOLS:
return false;
case Browser::TYPE_APP_PANEL:
return false;
case Browser::TYPE_NORMAL:
default:
return true;
}
}
void SessionModelAssociator::ReassociateTabs(
const std::vector<TabContents*>& tabs) {
DCHECK(CalledOnValidThread());
for (std::vector<TabContents*>::const_iterator i = tabs.begin();
i != tabs.end();
++i) {
ReassociateTab(**i);
}
}
void SessionModelAssociator::ReassociateTab(const TabContents& tab) {
DCHECK(CalledOnValidThread());
if (!IsValidTab(tab))
return;
int64 sync_id;
SessionID::id_type id = tab.controller().session_id().id();
if (tab.is_being_destroyed()) {
// This tab is closing.
TabLinksMap::iterator tab_iter = tab_map_.find(id);
if (tab_iter == tab_map_.end()) {
// We aren't tracking this tab (for example, sync setting page).
return;
}
tab_pool_.FreeTabNode(tab_iter->second.sync_id());
tab_map_.erase(tab_iter);
return;
}
TabLinksMap::const_iterator tablink = tab_map_.find(id);
if (tablink == tab_map_.end()) {
// This is a new tab, get a sync node for it.
sync_id = tab_pool_.GetFreeTabNode();
} else {
// This tab is already associated with a sync node, reuse it.
sync_id = tablink->second.sync_id();
}
Associate(&tab, sync_id);
}
void SessionModelAssociator::Associate(const TabContents* tab, int64 sync_id) {
DCHECK(CalledOnValidThread());
SessionID::id_type session_id = tab->controller().session_id().id();
Browser* browser = BrowserList::FindBrowserWithID(
tab->controller().window_id().id());
if (!browser) // Can happen for weird things like developer console.
return;
TabLinks t(sync_id, tab);
tab_map_[session_id] = t;
sync_api::WriteTransaction trans(sync_service_->GetUserShare());
WriteTabContentsToSyncModel(*browser, *tab, sync_id, &trans);
}
bool SessionModelAssociator::WriteTabContentsToSyncModel(
const Browser& browser,
const TabContents& tab,
int64 sync_id,
sync_api::WriteTransaction* trans) {
DCHECK(CalledOnValidThread());
sync_api::WriteNode tab_node(trans);
if (!tab_node.InitByIdLookup(sync_id)) {
LOG(ERROR) << "Failed to look up tab node " << sync_id;
return false;
}
sync_pb::SessionSpecifics session_s;
session_s.set_session_tag(GetCurrentMachineTag());
sync_pb::SessionTab* tab_s = session_s.mutable_tab();
SessionID::id_type tab_id = tab.controller().session_id().id();
tab_s->set_tab_id(tab_id);
tab_s->set_window_id(tab.controller().window_id().id());
const int current_index = tab.controller().GetCurrentEntryIndex();
const int min_index = std::max(0,
current_index - max_sync_navigation_count);
const int max_index = std::min(current_index + max_sync_navigation_count,
tab.controller().entry_count());
const int pending_index = tab.controller().pending_entry_index();
int index_in_window = browser.tabstrip_model()->GetWrapperIndex(&tab);
DCHECK(index_in_window != TabStripModel::kNoTab);
tab_s->set_pinned(browser.tabstrip_model()->IsTabPinned(index_in_window));
TabContentsWrapper* wrapper =
TabContentsWrapper::GetCurrentWrapperForContents(
const_cast<TabContents*>(&tab));
if (wrapper->extension_tab_helper()->extension_app()) {
tab_s->set_extension_app_id(
wrapper->extension_tab_helper()->extension_app()->id());
}
for (int i = min_index; i < max_index; ++i) {
const NavigationEntry* entry = (i == pending_index) ?
tab.controller().pending_entry() : tab.controller().GetEntryAtIndex(i);
DCHECK(entry);
if (entry->virtual_url().is_valid()) {
if (i == max_index - 1) {
VLOG(1) << "Associating tab " << tab_id << " with sync id " << sync_id
<< " and url " << entry->virtual_url().possibly_invalid_spec();
}
TabNavigation tab_nav;
tab_nav.SetFromNavigationEntry(*entry);
sync_pb::TabNavigation* nav_s = tab_s->add_navigation();
PopulateSessionSpecificsNavigation(&tab_nav, nav_s);
}
}
tab_s->set_current_navigation_index(current_index);
tab_node.SetSessionSpecifics(session_s);
return true;
}
// Static
// TODO(zea): perhaps sync state (scroll position, form entries, etc.) as well?
// See http://crbug.com/67068.
void SessionModelAssociator::PopulateSessionSpecificsNavigation(
const TabNavigation* navigation,
sync_pb::TabNavigation* tab_navigation) {
tab_navigation->set_index(navigation->index());
tab_navigation->set_virtual_url(navigation->virtual_url().spec());
tab_navigation->set_referrer(navigation->referrer().spec());
tab_navigation->set_title(UTF16ToUTF8(navigation->title()));
switch (navigation->transition()) {
case PageTransition::LINK:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_LINK);
break;
case PageTransition::TYPED:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_TYPED);
break;
case PageTransition::AUTO_BOOKMARK:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_AUTO_BOOKMARK);
break;
case PageTransition::AUTO_SUBFRAME:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_AUTO_SUBFRAME);
break;
case PageTransition::MANUAL_SUBFRAME:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_MANUAL_SUBFRAME);
break;
case PageTransition::GENERATED:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_GENERATED);
break;
case PageTransition::START_PAGE:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_START_PAGE);
break;
case PageTransition::FORM_SUBMIT:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_FORM_SUBMIT);
break;
case PageTransition::RELOAD:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_RELOAD);
break;
case PageTransition::KEYWORD:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_KEYWORD);
break;
case PageTransition::KEYWORD_GENERATED:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_KEYWORD_GENERATED);
break;
case PageTransition::CHAIN_START:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_CHAIN_START);
break;
case PageTransition::CHAIN_END:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_CHAIN_END);
break;
case PageTransition::CLIENT_REDIRECT:
tab_navigation->set_navigation_qualifier(
sync_pb::TabNavigation_PageTransitionQualifier_CLIENT_REDIRECT);
break;
case PageTransition::SERVER_REDIRECT:
tab_navigation->set_navigation_qualifier(
sync_pb::TabNavigation_PageTransitionQualifier_SERVER_REDIRECT);
break;
default:
tab_navigation->set_page_transition(
sync_pb::TabNavigation_PageTransition_TYPED);
}
}
void SessionModelAssociator::Disassociate(int64 sync_id) {
DCHECK(CalledOnValidThread());
NOTIMPLEMENTED();
// TODO(zea): we will need this once we support deleting foreign sessions.
}
bool SessionModelAssociator::AssociateModels() {
DCHECK(CalledOnValidThread());
// Ensure that we disassociated properly, otherwise memory might leak.
DCHECK(foreign_session_tracker_.empty());
DCHECK_EQ(0U, tab_pool_.capacity());
local_session_syncid_ = sync_api::kInvalidId;
// Read any available foreign sessions and load any session data we may have.
// If we don't have any local session data in the db, create a header node.
{
sync_api::WriteTransaction trans(sync_service_->GetUserShare());
sync_api::ReadNode root(&trans);
if (!root.InitByTagLookup(kSessionsTag)) {
LOG(ERROR) << kNoSessionsFolderError;
return false;
}
// Make sure we have a machine tag.
if (current_machine_tag_.empty())
InitializeCurrentMachineTag(&trans);
UpdateAssociationsFromSyncModel(root, &trans);
if (local_session_syncid_ == sync_api::kInvalidId) {
// The sync db didn't have a header node for us, we need to create one.
sync_api::WriteNode write_node(&trans);
if (!write_node.InitUniqueByCreation(syncable::SESSIONS, root,
current_machine_tag_)) {
LOG(ERROR) << "Failed to create sessions header sync node.";
return false;
}
write_node.SetTitle(UTF8ToWide(current_machine_tag_));
local_session_syncid_ = write_node.GetId();
}
}
// Check if anything has changed on the client side.
UpdateSyncModelDataFromClient();
VLOG(1) << "Session models associated.";
return true;
}
bool SessionModelAssociator::DisassociateModels() {
DCHECK(CalledOnValidThread());
foreign_session_tracker_.clear();
tab_map_.clear();
tab_pool_.clear();
local_session_syncid_ = sync_api::kInvalidId;
// There is no local model stored with which to disassociate, just notify
// foreign session handlers.
NotificationService::current()->Notify(
NotificationType::FOREIGN_SESSION_DISABLED,
NotificationService::AllSources(),
NotificationService::NoDetails());
return true;
}
void SessionModelAssociator::InitializeCurrentMachineTag(
sync_api::WriteTransaction* trans) {
DCHECK(CalledOnValidThread());
syncable::Directory* dir = trans->GetWrappedWriteTrans()->directory();
// TODO(zea): We need a better way of creating a machine tag. The directory
// kernel's cache_guid changes every time syncing is turned on and off. This
// will result in session's associated with stale machine tags persisting on
// the server since that tag will not be reused. Eventually this should
// become some string identifiable to the user. (Home, Work, Laptop, etc.)
// See issue at http://crbug.com/59672
current_machine_tag_ = "session_sync";
current_machine_tag_.append(dir->cache_guid());
VLOG(1) << "Creating machine tag: " << current_machine_tag_;
tab_pool_.set_machine_tag(current_machine_tag_);
}
bool SessionModelAssociator::UpdateAssociationsFromSyncModel(
const sync_api::ReadNode& root,
const sync_api::BaseTransaction* trans) {
DCHECK(CalledOnValidThread());
// Iterate through the nodes and associate any foreign sessions.
int64 id = root.GetFirstChildId();
while (id != sync_api::kInvalidId) {
sync_api::ReadNode sync_node(trans);
if (!sync_node.InitByIdLookup(id)) {
LOG(ERROR) << "Failed to fetch sync node for id " << id;
return false;
}
const sync_pb::SessionSpecifics& specifics =
sync_node.GetSessionSpecifics();
const int64 modification_time = sync_node.GetModificationTime();
if (specifics.session_tag() != GetCurrentMachineTag()) {
if (!AssociateForeignSpecifics(specifics, modification_time)) {
return false;
}
} else if (id != local_session_syncid_) {
// This is previously stored local session information.
if (specifics.has_header()) {
DCHECK_EQ(sync_api::kInvalidId, local_session_syncid_);
// This is our previous header node, reuse it.
local_session_syncid_ = id;
} else {
DCHECK(specifics.has_tab());
// This is a tab node. We want to track these to reuse them in our free
// tab node pool. They will be overwritten eventually, so need to do
// anything else.
tab_pool_.AddTabNode(id);
}
}
id = sync_node.GetSuccessorId();
}
// After updating from sync model all tabid's should be free.
DCHECK(tab_pool_.full());
return true;
}
bool SessionModelAssociator::AssociateForeignSpecifics(
const sync_pb::SessionSpecifics& specifics,
const int64 modification_time) {
DCHECK(CalledOnValidThread());
std::string foreign_session_tag = specifics.session_tag();
DCHECK(foreign_session_tag != GetCurrentMachineTag() || setup_for_test_);
if (specifics.has_header()) {
// Read in the header data for this foreign session.
// Header data contains window information and ordered tab id's for each
// window.
// Load (or create) the ForeignSession object for this client.
ForeignSession* foreign_session =
foreign_session_tracker_.GetForeignSession(foreign_session_tag);
const sync_pb::SessionHeader& header = specifics.header();
foreign_session->windows.reserve(header.window_size());
VLOG(1) << "Associating " << foreign_session_tag << " with " <<
header.window_size() << " windows.";
size_t i;
for (i = 0; i < static_cast<size_t>(header.window_size()); ++i) {
if (i >= foreign_session->windows.size()) {
// This a new window, create it.
foreign_session->windows.push_back(new SessionWindow());
}
const sync_pb::SessionWindow& window_s = header.window(i);
PopulateSessionWindowFromSpecifics(foreign_session_tag,
window_s,
modification_time,
foreign_session->windows[i],
&foreign_session_tracker_);
}
// Remove any remaining windows (in case windows were closed)
for (; i < foreign_session->windows.size(); ++i) {
delete foreign_session->windows[i];
}
foreign_session->windows.resize(header.window_size());
} else if (specifics.has_tab()) {
const sync_pb::SessionTab& tab_s = specifics.tab();
SessionID::id_type tab_id = tab_s.tab_id();
SessionTab* tab =
foreign_session_tracker_.GetSessionTab(foreign_session_tag,
tab_id,
false);
PopulateSessionTabFromSpecifics(tab_s, modification_time, tab);
} else {
NOTREACHED();
return false;
}
return true;
}
void SessionModelAssociator::DisassociateForeignSession(
const std::string& foreign_session_tag) {
DCHECK(CalledOnValidThread());
foreign_session_tracker_.DeleteForeignSession(foreign_session_tag);
}
// Static
void SessionModelAssociator::PopulateSessionWindowFromSpecifics(
const std::string& foreign_session_tag,
const sync_pb::SessionWindow& specifics,
int64 mtime,
SessionWindow* session_window,
ForeignSessionTracker* tracker) {
if (specifics.has_window_id())
session_window->window_id.set_id(specifics.window_id());
if (specifics.has_selected_tab_index())
session_window->selected_tab_index = specifics.selected_tab_index();
if (specifics.has_browser_type()) {
if (specifics.browser_type() ==
sync_pb::SessionWindow_BrowserType_TYPE_NORMAL) {
session_window->type = 1;
} else {
session_window->type = 2;
}
}
session_window->timestamp = base::Time::FromInternalValue(mtime);
session_window->tabs.resize(specifics.tab_size());
for (int i = 0; i < specifics.tab_size(); i++) {
SessionID::id_type tab_id = specifics.tab(i);
session_window->tabs[i] =
tracker->GetSessionTab(foreign_session_tag, tab_id, true);
}
}
// Static
void SessionModelAssociator::PopulateSessionTabFromSpecifics(
const sync_pb::SessionTab& specifics,
const int64 mtime,
SessionTab* tab) {
if (specifics.has_tab_id())
tab->tab_id.set_id(specifics.tab_id());
if (specifics.has_window_id())
tab->window_id.set_id(specifics.window_id());
if (specifics.has_tab_visual_index())
tab->tab_visual_index = specifics.tab_visual_index();
if (specifics.has_current_navigation_index())
tab->current_navigation_index = specifics.current_navigation_index();
if (specifics.has_pinned())
tab->pinned = specifics.pinned();
if (specifics.has_extension_app_id())
tab->extension_app_id = specifics.extension_app_id();
tab->timestamp = base::Time::FromInternalValue(mtime);
tab->navigations.clear(); // In case we are reusing a previous SessionTab.
for (int i = 0; i < specifics.navigation_size(); i++) {
AppendSessionTabNavigation(specifics.navigation(i), &tab->navigations);
}
}
// Static
void SessionModelAssociator::AppendSessionTabNavigation(
const sync_pb::TabNavigation& specifics,
std::vector<TabNavigation>* navigations) {
int index = 0;
GURL virtual_url;
GURL referrer;
string16 title;
std::string state;
PageTransition::Type transition(PageTransition::LINK);
if (specifics.has_index())
index = specifics.index();
if (specifics.has_virtual_url()) {
GURL gurl(specifics.virtual_url());
virtual_url = gurl;
}
if (specifics.has_referrer()) {
GURL gurl(specifics.referrer());
referrer = gurl;
}
if (specifics.has_title())
title = UTF8ToUTF16(specifics.title());
if (specifics.has_state())
state = specifics.state();
if (specifics.has_page_transition() ||
specifics.has_navigation_qualifier()) {
switch (specifics.page_transition()) {
case sync_pb::TabNavigation_PageTransition_LINK:
transition = PageTransition::LINK;
break;
case sync_pb::TabNavigation_PageTransition_TYPED:
transition = PageTransition::TYPED;
break;
case sync_pb::TabNavigation_PageTransition_AUTO_BOOKMARK:
transition = PageTransition::AUTO_BOOKMARK;
break;
case sync_pb::TabNavigation_PageTransition_AUTO_SUBFRAME:
transition = PageTransition::AUTO_SUBFRAME;
break;
case sync_pb::TabNavigation_PageTransition_MANUAL_SUBFRAME:
transition = PageTransition::MANUAL_SUBFRAME;
break;
case sync_pb::TabNavigation_PageTransition_GENERATED:
transition = PageTransition::GENERATED;
break;
case sync_pb::TabNavigation_PageTransition_START_PAGE:
transition = PageTransition::START_PAGE;
break;
case sync_pb::TabNavigation_PageTransition_FORM_SUBMIT:
transition = PageTransition::FORM_SUBMIT;
break;
case sync_pb::TabNavigation_PageTransition_RELOAD:
transition = PageTransition::RELOAD;
break;
case sync_pb::TabNavigation_PageTransition_KEYWORD:
transition = PageTransition::KEYWORD;
break;
case sync_pb::TabNavigation_PageTransition_KEYWORD_GENERATED:
transition = PageTransition::KEYWORD_GENERATED;
break;
case sync_pb::TabNavigation_PageTransition_CHAIN_START:
transition = sync_pb::TabNavigation_PageTransition_CHAIN_START;
break;
case sync_pb::TabNavigation_PageTransition_CHAIN_END:
transition = PageTransition::CHAIN_END;
break;
default:
switch (specifics.navigation_qualifier()) {
case sync_pb::
TabNavigation_PageTransitionQualifier_CLIENT_REDIRECT:
transition = PageTransition::CLIENT_REDIRECT;
break;
case sync_pb::
TabNavigation_PageTransitionQualifier_SERVER_REDIRECT:
transition = PageTransition::SERVER_REDIRECT;
break;
default:
transition = PageTransition::TYPED;
}
}
}
TabNavigation tab_navigation(index, virtual_url, referrer, title, state,
transition);
navigations->insert(navigations->end(), tab_navigation);
}
void SessionModelAssociator::UpdateSyncModelDataFromClient() {
DCHECK(CalledOnValidThread());
// TODO(zea): the logic for determining if we want to sync and the loading of
// the previous session should go here. We can probably reuse the code for
// loading the current session from the old session implementation.
// SessionService::SessionCallback* callback =
// NewCallback(this, &SessionModelAssociator::OnGotSession);
// GetSessionService()->GetCurrentSession(&consumer_, callback);
// Associate all open windows and their tabs.
ReassociateWindows(true);
}
SessionModelAssociator::TabNodePool::TabNodePool(
ProfileSyncService* sync_service)
: tab_pool_fp_(-1),
sync_service_(sync_service) {
}
SessionModelAssociator::TabNodePool::~TabNodePool() {}
void SessionModelAssociator::TabNodePool::AddTabNode(int64 sync_id) {
tab_syncid_pool_.resize(tab_syncid_pool_.size() + 1);
tab_syncid_pool_[static_cast<size_t>(++tab_pool_fp_)] = sync_id;
}
int64 SessionModelAssociator::TabNodePool::GetFreeTabNode() {
DCHECK_GT(machine_tag_.length(), 0U);
if (tab_pool_fp_ == -1) {
// Tab pool has no free nodes, allocate new one.
sync_api::WriteTransaction trans(sync_service_->GetUserShare());
sync_api::ReadNode root(&trans);
if (!root.InitByTagLookup(kSessionsTag)) {
LOG(ERROR) << kNoSessionsFolderError;
return 0;
}
size_t tab_node_id = tab_syncid_pool_.size();
std::string tab_node_tag = TabIdToTag(machine_tag_, tab_node_id);
sync_api::WriteNode tab_node(&trans);
if (!tab_node.InitUniqueByCreation(syncable::SESSIONS, root,
tab_node_tag)) {
LOG(ERROR) << "Could not create new node!";
return -1;
}
tab_node.SetTitle(UTF8ToWide(tab_node_tag));
// Grow the pool by 1 since we created a new node. We don't actually need
// to put the node's id in the pool now, since the pool is still empty.
// The id will be added when that tab is closed and the node is freed.
tab_syncid_pool_.resize(tab_node_id + 1);
VLOG(1) << "Adding sync node " << tab_node.GetId() << " to tab syncid pool";
return tab_node.GetId();
} else {
// There are nodes available, grab next free and decrement free pointer.
return tab_syncid_pool_[static_cast<size_t>(tab_pool_fp_--)];
}
}
void SessionModelAssociator::TabNodePool::FreeTabNode(int64 sync_id) {
// Pool size should always match # of free tab nodes.
DCHECK_LT(tab_pool_fp_, static_cast<int64>(tab_syncid_pool_.size()));
tab_syncid_pool_[static_cast<size_t>(++tab_pool_fp_)] = sync_id;
}
bool SessionModelAssociator::GetAllForeignSessions(
std::vector<const ForeignSession*>* sessions) {
DCHECK(CalledOnValidThread());
return foreign_session_tracker_.LookupAllForeignSessions(sessions);
}
bool SessionModelAssociator::GetForeignSession(
const std::string& tag,
std::vector<SessionWindow*>* windows) {
DCHECK(CalledOnValidThread());
return foreign_session_tracker_.LookupSessionWindows(tag, windows);
}
bool SessionModelAssociator::GetForeignTab(
const std::string& tag,
const SessionID::id_type tab_id,
const SessionTab** tab) {
DCHECK(CalledOnValidThread());
return foreign_session_tracker_.LookupSessionTab(tag, tab_id, tab);
}
// Static
bool SessionModelAssociator::SessionWindowHasNoTabsToSync(
const SessionWindow& window) {
int num_populated = 0;
for (std::vector<SessionTab*>::const_iterator i = window.tabs.begin();
i != window.tabs.end(); ++i) {
const SessionTab* tab = *i;
if (IsValidSessionTab(*tab))
num_populated++;
}
if (num_populated == 0)
return true;
return false;
}
// Valid local tab?
bool SessionModelAssociator::IsValidTab(const TabContents& tab) {
DCHECK(CalledOnValidThread());
if ((tab.profile() == sync_service_->profile() ||
sync_service_->profile() == NULL)) {
const NavigationEntry* entry = tab.controller().GetActiveEntry();
if (!entry)
return false;
if (entry->virtual_url().is_valid() &&
(entry->virtual_url() != GURL(chrome::kChromeUINewTabURL) ||
tab.controller().entry_count() > 1)) {
return true;
}
}
return false;
}
// Static
bool SessionModelAssociator::IsValidSessionTab(const SessionTab& tab) {
if (tab.navigations.empty())
return false;
int selected_index = tab.current_navigation_index;
selected_index = std::max(
0,
std::min(selected_index,
static_cast<int>(tab.navigations.size() - 1)));
if (selected_index == 0 &&
tab.navigations.size() == 1 &&
tab.navigations.at(selected_index).virtual_url() ==
GURL(chrome::kChromeUINewTabURL)) {
// This is a new tab with no further history, skip.
return false;
}
return true;
}
// ==========================================================================
// The following methods are not currently used but will likely become useful
// if we choose to sync the previous browser session.
SessionService* SessionModelAssociator::GetSessionService() {
DCHECK(CalledOnValidThread());
DCHECK(sync_service_);
Profile* profile = sync_service_->profile();
DCHECK(profile);
SessionService* sessions_service = profile->GetSessionService();
DCHECK(sessions_service);
return sessions_service;
}
void SessionModelAssociator::OnGotSession(
int handle,
std::vector<SessionWindow*>* windows) {
DCHECK(CalledOnValidThread());
DCHECK(local_session_syncid_);
sync_pb::SessionSpecifics specifics;
specifics.set_session_tag(GetCurrentMachineTag());
sync_pb::SessionHeader* header_s = specifics.mutable_header();
PopulateSessionSpecificsHeader(*windows, header_s);
sync_api::WriteTransaction trans(sync_service_->GetUserShare());
sync_api::ReadNode root(&trans);
if (!root.InitByTagLookup(kSessionsTag)) {
LOG(ERROR) << kNoSessionsFolderError;
return;
}
sync_api::WriteNode header_node(&trans);
if (!header_node.InitByIdLookup(local_session_syncid_)) {
LOG(ERROR) << "Failed to load local session header node.";
return;
}
header_node.SetSessionSpecifics(specifics);
}
void SessionModelAssociator::PopulateSessionSpecificsHeader(
const std::vector<SessionWindow*>& windows,
sync_pb::SessionHeader* header_s) {
DCHECK(CalledOnValidThread());
// Iterate through the vector of windows, extracting the window data, along
// with the tab data to populate the session specifics.
for (size_t i = 0; i < windows.size(); ++i) {
if (SessionWindowHasNoTabsToSync(*(windows[i])))
continue;
sync_pb::SessionWindow* window_s = header_s->add_window();
PopulateSessionSpecificsWindow(*(windows[i]), window_s);
if (!SyncLocalWindowToSyncModel(*(windows[i])))
return;
}
}
// Called when populating session specifics to send to the sync model, called
// when associating models, or updating the sync model.
void SessionModelAssociator::PopulateSessionSpecificsWindow(
const SessionWindow& window,
sync_pb::SessionWindow* session_window) {
DCHECK(CalledOnValidThread());
session_window->set_window_id(window.window_id.id());
session_window->set_selected_tab_index(window.selected_tab_index);
if (window.type == Browser::TYPE_NORMAL) {
session_window->set_browser_type(
sync_pb::SessionWindow_BrowserType_TYPE_NORMAL);
} else if (window.type == Browser::TYPE_POPUP) {
session_window->set_browser_type(
sync_pb::SessionWindow_BrowserType_TYPE_POPUP);
} else {
// ignore
LOG(WARNING) << "Session Sync unable to handle windows of type" <<
window.type;
return;
}
for (std::vector<SessionTab*>::const_iterator i = window.tabs.begin();
i != window.tabs.end(); ++i) {
const SessionTab* tab = *i;
if (!IsValidSessionTab(*tab))
continue;
session_window->add_tab(tab->tab_id.id());
}
}
bool SessionModelAssociator::SyncLocalWindowToSyncModel(
const SessionWindow& window) {
DCHECK(CalledOnValidThread());
DCHECK(tab_map_.empty());
for (size_t i = 0; i < window.tabs.size(); ++i) {
SessionTab* tab = window.tabs[i];
int64 id = tab_pool_.GetFreeTabNode();
if (id == -1) {
LOG(ERROR) << "Failed to find/generate free sync node for tab.";
return false;
}
sync_api::WriteTransaction trans(sync_service_->GetUserShare());
if (!WriteSessionTabToSyncModel(*tab, id, &trans)) {
return false;
}
TabLinks t(id, tab);
tab_map_[tab->tab_id.id()] = t;
}
return true;
}
bool SessionModelAssociator::WriteSessionTabToSyncModel(
const SessionTab& tab,
const int64 sync_id,
sync_api::WriteTransaction* trans) {
DCHECK(CalledOnValidThread());
sync_api::WriteNode tab_node(trans);
if (!tab_node.InitByIdLookup(sync_id)) {
LOG(ERROR) << "Failed to look up tab node " << sync_id;
return false;
}
sync_pb::SessionSpecifics specifics;
specifics.set_session_tag(GetCurrentMachineTag());
sync_pb::SessionTab* tab_s = specifics.mutable_tab();
PopulateSessionSpecificsTab(tab, tab_s);
tab_node.SetSessionSpecifics(specifics);
return true;
}
// See PopulateSessionSpecificsWindow for use.
void SessionModelAssociator::PopulateSessionSpecificsTab(
const SessionTab& tab,
sync_pb::SessionTab* session_tab) {
DCHECK(CalledOnValidThread());
session_tab->set_tab_id(tab.tab_id.id());
session_tab->set_window_id(tab.window_id.id());
session_tab->set_tab_visual_index(tab.tab_visual_index);
session_tab->set_current_navigation_index(
tab.current_navigation_index);
session_tab->set_pinned(tab.pinned);
session_tab->set_extension_app_id(tab.extension_app_id);
for (std::vector<TabNavigation>::const_iterator i =
tab.navigations.begin(); i != tab.navigations.end(); ++i) {
const TabNavigation navigation = *i;
sync_pb::TabNavigation* tab_navigation =
session_tab->add_navigation();
PopulateSessionSpecificsNavigation(&navigation, tab_navigation);
}
}
bool SessionModelAssociator::CryptoReadyIfNecessary() {
// We only access the cryptographer while holding a transaction.
sync_api::ReadTransaction trans(sync_service_->GetUserShare());
syncable::ModelTypeSet encrypted_types;
sync_service_->GetEncryptedDataTypes(&encrypted_types);
return encrypted_types.count(syncable::SESSIONS) == 0 ||
sync_service_->IsCryptographerReady(&trans);
}
} // namespace browser_sync