| // 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 "chrome/browser/sync/glue/session_model_associator.h" |
| |
| #include <algorithm> |
| #include <set> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/safe_numerics.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/favicon/favicon_service_factory.h" |
| #include "chrome/browser/history/history_service.h" |
| #if !defined(OS_ANDROID) |
| #include "chrome/browser/network_time/navigation_time_helper.h" |
| #endif |
| #include "chrome/browser/prefs/pref_service_syncable.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/sessions/session_id.h" |
| #include "chrome/browser/sync/glue/device_info.h" |
| #include "chrome/browser/sync/glue/synced_device_tracker.h" |
| #include "chrome/browser/sync/glue/synced_session.h" |
| #include "chrome/browser/sync/glue/synced_tab_delegate.h" |
| #include "chrome/browser/sync/glue/synced_window_delegate.h" |
| #include "chrome/browser/sync/profile_sync_service.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/url_constants.h" |
| #include "components/sessions/serialized_navigation_entry.h" |
| #include "components/user_prefs/pref_registry_syncable.h" |
| #include "content/public/browser/favicon_status.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/common/url_constants.h" |
| #include "sync/api/sync_error.h" |
| #include "sync/api/time.h" |
| #include "sync/internal_api/public/base/model_type.h" |
| #include "sync/internal_api/public/read_node.h" |
| #include "sync/internal_api/public/read_transaction.h" |
| #include "sync/internal_api/public/write_node.h" |
| #include "sync/internal_api/public/write_transaction.h" |
| #include "sync/protocol/session_specifics.pb.h" |
| #include "sync/syncable/directory.h" |
| #include "sync/syncable/syncable_read_transaction.h" |
| #include "sync/syncable/syncable_write_transaction.h" |
| #if defined(OS_LINUX) |
| #include "base/linux_util.h" |
| #elif defined(OS_WIN) |
| #include <windows.h> |
| #endif |
| |
| using content::BrowserThread; |
| using content::NavigationEntry; |
| using prefs::kSyncSessionsGUID; |
| using sessions::SerializedNavigationEntry; |
| using syncer::SESSIONS; |
| |
| namespace { |
| |
| std::string SessionTagPrefix() { |
| return std::string("session_sync"); |
| } |
| |
| // Given a transaction, returns the GUID-based string that should be used for |
| // |current_machine_tag_|. |
| std::string GetMachineTagFromTransaction( |
| syncer::WriteTransaction* trans) { |
| syncer::syncable::Directory* dir = trans->GetWrappedWriteTrans()->directory(); |
| std::string machine_tag = SessionTagPrefix(); |
| machine_tag.append(dir->cache_guid()); |
| return machine_tag; |
| } |
| |
| // Given a session tag this function returns the client_id(cache_guid). |
| std::string GetClientIdFromSessionTag(const std::string& session_tag) { |
| if (session_tag.find_first_of(SessionTagPrefix()) == std::string::npos) { |
| LOG(ERROR) << "Session tag is malformatted"; |
| return std::string(); |
| } |
| |
| std::string client_id = session_tag.substr( |
| SessionTagPrefix().length(), |
| session_tag.length()); |
| |
| return client_id; |
| } |
| |
| } // namespace |
| |
| 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 kMaxSyncNavigationCount = 6; |
| |
| // Default number of days without activity after which a session is considered |
| // stale and becomes a candidate for garbage collection. |
| static const size_t kDefaultStaleSessionThresholdDays = 14; // 2 weeks. |
| |
| // Maximum number of favicons to sync. |
| // TODO(zea): pull this from the server. |
| static const int kMaxSyncFavicons = 200; |
| |
| } // namespace |
| |
| SessionModelAssociator::SessionModelAssociator( |
| ProfileSyncService* sync_service, |
| DataTypeErrorHandler* error_handler) |
| : local_tab_pool_(sync_service), |
| local_session_syncid_(syncer::kInvalidId), |
| sync_service_(sync_service), |
| stale_session_threshold_days_(kDefaultStaleSessionThresholdDays), |
| setup_for_test_(false), |
| waiting_for_change_(false), |
| profile_(sync_service->profile()), |
| error_handler_(error_handler), |
| favicon_cache_(profile_, |
| sync_service->current_experiments().favicon_sync_limit), |
| test_weak_factory_(this) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(sync_service_); |
| DCHECK(profile_); |
| } |
| |
| SessionModelAssociator::SessionModelAssociator(ProfileSyncService* sync_service, |
| bool setup_for_test) |
| : local_tab_pool_(sync_service), |
| local_session_syncid_(syncer::kInvalidId), |
| sync_service_(sync_service), |
| stale_session_threshold_days_(kDefaultStaleSessionThresholdDays), |
| setup_for_test_(setup_for_test), |
| waiting_for_change_(false), |
| profile_(sync_service->profile()), |
| error_handler_(NULL), |
| favicon_cache_(profile_, kMaxSyncFavicons), |
| test_weak_factory_(this) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(sync_service_); |
| DCHECK(profile_); |
| DCHECK(setup_for_test); |
| } |
| |
| SessionModelAssociator::~SessionModelAssociator() { |
| DCHECK(CalledOnValidThread()); |
| } |
| |
| bool SessionModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) { |
| DCHECK(CalledOnValidThread()); |
| CHECK(has_nodes); |
| *has_nodes = false; |
| syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare()); |
| syncer::ReadNode root(&trans); |
| if (root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)) != |
| syncer::BaseNode::INIT_OK) { |
| LOG(ERROR) << kNoSessionsFolderError; |
| return false; |
| } |
| // The sync model has user created nodes iff the sessions folder has |
| // any children. |
| *has_nodes = root.HasChildren(); |
| return true; |
| } |
| |
| int64 SessionModelAssociator::GetSyncIdFromSessionTag(const std::string& tag) { |
| DCHECK(CalledOnValidThread()); |
| syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare()); |
| syncer::ReadNode node(&trans); |
| if (node.InitByClientTagLookup(SESSIONS, tag) != syncer::BaseNode::INIT_OK) |
| return syncer::kInvalidId; |
| return node.GetId(); |
| } |
| |
| bool SessionModelAssociator::AssociateWindows(bool reload_tabs, |
| syncer::SyncError* error) { |
| DCHECK(CalledOnValidThread()); |
| std::string local_tag = GetCurrentMachineTag(); |
| sync_pb::SessionSpecifics specifics; |
| specifics.set_session_tag(local_tag); |
| sync_pb::SessionHeader* header_s = specifics.mutable_header(); |
| SyncedSession* current_session = |
| synced_session_tracker_.GetSession(local_tag); |
| current_session->modified_time = base::Time::Now(); |
| header_s->set_client_name(current_session_name_); |
| header_s->set_device_type(DeviceInfo::GetLocalDeviceType()); |
| |
| synced_session_tracker_.ResetSessionTracking(local_tag); |
| std::set<SyncedWindowDelegate*> windows = |
| SyncedWindowDelegate::GetSyncedWindowDelegates(); |
| for (std::set<SyncedWindowDelegate*>::const_iterator i = |
| windows.begin(); i != windows.end(); ++i) { |
| // Make sure the window has tabs and a viewable window. The viewable window |
| // check is necessary because, for example, 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 (ShouldSyncWindow(*i) && (*i)->GetTabCount() && (*i)->HasWindow()) { |
| sync_pb::SessionWindow window_s; |
| SessionID::id_type window_id = (*i)->GetSessionId(); |
| DVLOG(1) << "Associating window " << window_id << " with " |
| << (*i)->GetTabCount() << " tabs."; |
| window_s.set_window_id(window_id); |
| // Note: We don't bother to set selected tab index anymore. We still |
| // consume it when receiving foreign sessions, as reading it is free, but |
| // it triggers too many sync cycles with too little value to make setting |
| // it worthwhile. |
| if ((*i)->IsTypeTabbed()) { |
| window_s.set_browser_type( |
| sync_pb::SessionWindow_BrowserType_TYPE_TABBED); |
| } 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)->GetTabCount(); ++j) { |
| SessionID::id_type tab_id = (*i)->GetTabIdAt(j); |
| SyncedTabDelegate* synced_tab = (*i)->GetTabAt(j); |
| |
| // GetTabAt can return a null tab; in that case just skip it. |
| if (!synced_tab) |
| continue; |
| |
| if (!synced_tab->HasWebContents()) { |
| // For tabs without WebContents update the |tab_id|, as it could have |
| // changed after a session restore. |
| // Note: We cannot check if a tab is valid if it has no WebContents. |
| // We assume any such tab is valid and leave the contents of |
| // corresponding sync node unchanged. |
| if (synced_tab->GetSyncId() > TabNodePool::kInvalidTabNodeID && |
| tab_id > TabNodePool::kInvalidTabID) { |
| UpdateTabIdIfNecessary(synced_tab->GetSyncId(), tab_id); |
| found_tabs = true; |
| window_s.add_tab(tab_id); |
| } |
| continue; |
| } |
| |
| if (reload_tabs) { |
| // It's possible for GetTabAt to return a tab which has no web |
| // contents. We can assume this means the tab already existed but |
| // hasn't changed, so no need to reassociate. |
| if (synced_tab->HasWebContents() && |
| !AssociateTab(synced_tab, error)) { |
| // Association failed. Either we need to re-associate, or this is an |
| // unrecoverable error. |
| return false; |
| } |
| } |
| |
| // If the tab is valid, it would have been added to the tracker either |
| // by the above AssociateTab call (at association time), or by the |
| // change processor calling AssociateTab for all modified tabs. |
| // Therefore, we can key whether this window has valid tabs based on |
| // the tab's presence in the tracker. |
| const SessionTab* tab = NULL; |
| if (synced_session_tracker_.LookupSessionTab(local_tag, tab_id, &tab)) { |
| found_tabs = true; |
| window_s.add_tab(tab_id); |
| } |
| } |
| // 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; |
| |
| // Update this window's representation in the synced session tracker. |
| synced_session_tracker_.PutWindowInSession(local_tag, window_id); |
| PopulateSessionWindowFromSpecifics( |
| local_tag, |
| window_s, |
| base::Time::Now(), |
| current_session->windows[window_id], |
| &synced_session_tracker_); |
| } |
| } |
| } |
| |
| local_tab_pool_.DeleteUnassociatedTabNodes(); |
| // Free memory for closed windows and tabs. |
| synced_session_tracker_.CleanupSession(local_tag); |
| |
| syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); |
| syncer::WriteNode header_node(&trans); |
| if (header_node.InitByIdLookup(local_session_syncid_) != |
| syncer::BaseNode::INIT_OK) { |
| if (error) { |
| *error = error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| "Failed to load local session header node.", |
| model_type()); |
| } |
| return false; |
| } |
| header_node.SetSessionSpecifics(specifics); |
| if (waiting_for_change_) QuitLoopForSubtleTesting(); |
| return true; |
| } |
| |
| // Static. |
| bool SessionModelAssociator::ShouldSyncWindow( |
| const SyncedWindowDelegate* window) { |
| if (window->IsApp()) |
| return false; |
| return window->IsTypeTabbed() || window->IsTypePopup(); |
| } |
| |
| bool SessionModelAssociator::AssociateTabs( |
| const std::vector<SyncedTabDelegate*>& tabs, |
| syncer::SyncError* error) { |
| DCHECK(CalledOnValidThread()); |
| for (std::vector<SyncedTabDelegate*>::const_iterator i = tabs.begin(); |
| i != tabs.end(); |
| ++i) { |
| if (!AssociateTab(*i, error)) |
| return false; |
| } |
| if (waiting_for_change_) QuitLoopForSubtleTesting(); |
| return true; |
| } |
| |
| bool SessionModelAssociator::AssociateTab(SyncedTabDelegate* const tab, |
| syncer::SyncError* error) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(tab->HasWebContents()); |
| int tab_node_id(TabNodePool::kInvalidTabNodeID); |
| SessionID::id_type tab_id = tab->GetSessionId(); |
| if (tab->IsBeingDestroyed()) { |
| // This tab is closing. |
| TabLinksMap::iterator tab_iter = local_tab_map_.find(tab_id); |
| if (tab_iter == local_tab_map_.end()) { |
| // We aren't tracking this tab (for example, sync setting page). |
| return true; |
| } |
| local_tab_pool_.FreeTabNode(tab_iter->second->tab_node_id()); |
| local_tab_map_.erase(tab_iter); |
| return true; |
| } |
| |
| if (!ShouldSyncTab(*tab)) |
| return true; |
| |
| TabLinksMap::iterator local_tab_map_iter = local_tab_map_.find(tab_id); |
| TabLink* tab_link = NULL; |
| if (local_tab_map_iter == local_tab_map_.end()) { |
| tab_node_id = tab->GetSyncId(); |
| // if there is an old sync node for the tab, reuse it. |
| if (!local_tab_pool_.IsUnassociatedTabNode(tab_node_id)) { |
| // This is a new tab, get a sync node for it. |
| tab_node_id = local_tab_pool_.GetFreeTabNode(); |
| if (tab_node_id == TabNodePool::kInvalidTabNodeID) { |
| if (error) { |
| *error = error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| "Received invalid tab node from tab pool.", |
| model_type()); |
| } |
| return false; |
| } |
| tab->SetSyncId(tab_node_id); |
| } |
| local_tab_pool_.AssociateTabNode(tab_node_id, tab_id); |
| tab_link = new TabLink(tab_node_id, tab); |
| local_tab_map_[tab_id] = make_linked_ptr<TabLink>(tab_link); |
| } else { |
| // This tab is already associated with a sync node, reuse it. |
| // Note: on some platforms the tab object may have changed, so we ensure |
| // the tab link is up to date. |
| tab_link = local_tab_map_iter->second.get(); |
| local_tab_map_iter->second->set_tab(tab); |
| } |
| DCHECK(tab_link); |
| DCHECK_NE(tab_link->tab_node_id(), TabNodePool::kInvalidTabNodeID); |
| |
| DVLOG(1) << "Reloading tab " << tab_id << " from window " |
| << tab->GetWindowId(); |
| return WriteTabContentsToSyncModel(tab_link, error); |
| } |
| |
| // static |
| GURL SessionModelAssociator::GetCurrentVirtualURL( |
| const SyncedTabDelegate& tab_delegate) { |
| const int current_index = tab_delegate.GetCurrentEntryIndex(); |
| const int pending_index = tab_delegate.GetPendingEntryIndex(); |
| const NavigationEntry* current_entry = |
| (current_index == pending_index) ? |
| tab_delegate.GetPendingEntry() : |
| tab_delegate.GetEntryAtIndex(current_index); |
| return current_entry->GetVirtualURL(); |
| } |
| |
| // static |
| GURL SessionModelAssociator::GetCurrentFaviconURL( |
| const SyncedTabDelegate& tab_delegate) { |
| const int current_index = tab_delegate.GetCurrentEntryIndex(); |
| const int pending_index = tab_delegate.GetPendingEntryIndex(); |
| const NavigationEntry* current_entry = |
| (current_index == pending_index) ? |
| tab_delegate.GetPendingEntry() : |
| tab_delegate.GetEntryAtIndex(current_index); |
| return (current_entry->GetFavicon().valid ? |
| current_entry->GetFavicon().url : |
| GURL()); |
| } |
| |
| bool SessionModelAssociator::WriteTabContentsToSyncModel( |
| TabLink* tab_link, |
| syncer::SyncError* error) { |
| DCHECK(CalledOnValidThread()); |
| const SyncedTabDelegate& tab_delegate = *(tab_link->tab()); |
| int tab_node_id = tab_link->tab_node_id(); |
| GURL old_tab_url = tab_link->url(); |
| const GURL new_url = GetCurrentVirtualURL(tab_delegate); |
| DVLOG(1) << "Local tab " << tab_delegate.GetSessionId() |
| << " now has URL " << new_url.spec(); |
| |
| SessionTab* session_tab = NULL; |
| { |
| syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); |
| syncer::WriteNode tab_node(&trans); |
| if (tab_node.InitByClientTagLookup( |
| syncer::SESSIONS, |
| TabNodePool::TabIdToTag(current_machine_tag_, tab_node_id)) != |
| syncer::BaseNode::INIT_OK) { |
| if (error) { |
| *error = error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| "Failed to look up local tab node", |
| model_type()); |
| } |
| return false; |
| } |
| |
| // Load the last stored version of this tab so we can compare changes. If |
| // this is a new tab, session_tab will be a new, blank SessionTab object. |
| sync_pb::SessionSpecifics specifics = tab_node.GetSessionSpecifics(); |
| const int s_tab_node_id(specifics.tab_node_id()); |
| DCHECK_EQ(tab_node_id, s_tab_node_id); |
| session_tab = |
| synced_session_tracker_.GetTab(GetCurrentMachineTag(), |
| tab_delegate.GetSessionId(), |
| specifics.tab_node_id()); |
| SetSessionTabFromDelegate(tab_delegate, base::Time::Now(), session_tab); |
| sync_pb::SessionTab tab_s = session_tab->ToSyncData(); |
| |
| if (new_url == old_tab_url) { |
| // Load the old specifics and copy over the favicon data if needed. |
| // TODO(zea): remove this once favicon sync is enabled as a separate type. |
| tab_s.set_favicon(specifics.tab().favicon()); |
| tab_s.set_favicon_source(specifics.tab().favicon_source()); |
| tab_s.set_favicon_type(specifics.tab().favicon_type()); |
| } |
| // Retain the base SessionSpecifics data (tag, tab_node_id, etc.), and just |
| // write the new SessionTabSpecifics. |
| specifics.mutable_tab()->CopyFrom(tab_s); |
| |
| // Write into the actual sync model. |
| tab_node.SetSessionSpecifics(specifics); |
| } |
| |
| // Trigger the favicon load if needed. We do this outside the write |
| // transaction to avoid jank. |
| tab_link->set_url(new_url); |
| if (new_url != old_tab_url) { |
| favicon_cache_.OnFaviconVisited(new_url, |
| GetCurrentFaviconURL(tab_delegate)); |
| } |
| |
| // Update our last modified time. |
| synced_session_tracker_.GetSession(GetCurrentMachineTag())->modified_time = |
| base::Time::Now(); |
| |
| return true; |
| } |
| |
| // static |
| void SessionModelAssociator::SetSessionTabFromDelegate( |
| const SyncedTabDelegate& tab_delegate, |
| base::Time mtime, |
| SessionTab* session_tab) { |
| DCHECK(session_tab); |
| session_tab->window_id.set_id(tab_delegate.GetWindowId()); |
| session_tab->tab_id.set_id(tab_delegate.GetSessionId()); |
| session_tab->tab_visual_index = 0; |
| session_tab->current_navigation_index = tab_delegate.GetCurrentEntryIndex(); |
| session_tab->pinned = tab_delegate.IsPinned(); |
| session_tab->extension_app_id = tab_delegate.GetExtensionAppId(); |
| session_tab->user_agent_override.clear(); |
| session_tab->timestamp = mtime; |
| const int current_index = tab_delegate.GetCurrentEntryIndex(); |
| const int pending_index = tab_delegate.GetPendingEntryIndex(); |
| const int min_index = std::max(0, current_index - kMaxSyncNavigationCount); |
| const int max_index = std::min(current_index + kMaxSyncNavigationCount, |
| tab_delegate.GetEntryCount()); |
| bool is_managed = tab_delegate.ProfileIsManaged(); |
| session_tab->navigations.clear(); |
| |
| #if !defined(OS_ANDROID) |
| // For getting navigation time in network time. |
| NavigationTimeHelper* nav_time_helper = |
| tab_delegate.HasWebContents() ? |
| NavigationTimeHelper::FromWebContents(tab_delegate.GetWebContents()) : |
| NULL; |
| #endif |
| |
| for (int i = min_index; i < max_index; ++i) { |
| const NavigationEntry* entry = (i == pending_index) ? |
| tab_delegate.GetPendingEntry() : tab_delegate.GetEntryAtIndex(i); |
| DCHECK(entry); |
| if (!entry->GetVirtualURL().is_valid()) |
| continue; |
| |
| scoped_ptr<content::NavigationEntry> network_time_entry( |
| content::NavigationEntry::Create(*entry)); |
| #if !defined(OS_ANDROID) |
| if (nav_time_helper) { |
| network_time_entry->SetTimestamp( |
| nav_time_helper->GetNavigationTime(entry)); |
| } |
| #endif |
| |
| session_tab->navigations.push_back( |
| SerializedNavigationEntry::FromNavigationEntry(i, *network_time_entry)); |
| if (is_managed) { |
| session_tab->navigations.back().set_blocked_state( |
| SerializedNavigationEntry::STATE_ALLOWED); |
| } |
| } |
| |
| if (is_managed) { |
| const std::vector<const NavigationEntry*>& blocked_navigations = |
| *tab_delegate.GetBlockedNavigations(); |
| int offset = session_tab->navigations.size(); |
| for (size_t i = 0; i < blocked_navigations.size(); ++i) { |
| session_tab->navigations.push_back( |
| SerializedNavigationEntry::FromNavigationEntry( |
| i + offset, *blocked_navigations[i])); |
| // Blocked navigations already use network navigation time. |
| session_tab->navigations.back().set_blocked_state( |
| SerializedNavigationEntry::STATE_BLOCKED); |
| // TODO(bauerb): Add categories |
| } |
| } |
| session_tab->session_storage_persistent_id.clear(); |
| } |
| |
| void SessionModelAssociator::FaviconsUpdated( |
| const std::set<GURL>& urls) { |
| // TODO(zea): consider a separate container for tabs with outstanding favicon |
| // loads so we don't have to iterate through all tabs comparing urls. |
| for (std::set<GURL>::const_iterator i = urls.begin(); i != urls.end(); ++i) { |
| for (TabLinksMap::iterator tab_iter = local_tab_map_.begin(); |
| tab_iter != local_tab_map_.end(); |
| ++tab_iter) { |
| if (tab_iter->second->url() == *i) |
| favicon_cache_.OnPageFaviconUpdated(*i); |
| } |
| } |
| } |
| |
| syncer::SyncError SessionModelAssociator::AssociateModels( |
| syncer::SyncMergeResult* local_merge_result, |
| syncer::SyncMergeResult* syncer_merge_result) { |
| DCHECK(CalledOnValidThread()); |
| syncer::SyncError error; |
| |
| // Ensure that we disassociated properly, otherwise memory might leak. |
| DCHECK(synced_session_tracker_.Empty()); |
| DCHECK_EQ(0U, local_tab_pool_.Capacity()); |
| |
| local_session_syncid_ = syncer::kInvalidId; |
| |
| scoped_ptr<DeviceInfo> local_device_info(sync_service_->GetLocalDeviceInfo()); |
| |
| #if defined(OS_ANDROID) |
| std::string transaction_tag; |
| #endif |
| // 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. |
| { |
| syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); |
| |
| syncer::ReadNode root(&trans); |
| if (root.InitByTagLookup(syncer::ModelTypeToRootTag(model_type())) != |
| syncer::BaseNode::INIT_OK) { |
| return error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| kNoSessionsFolderError, |
| model_type()); |
| } |
| |
| // Make sure we have a machine tag. |
| if (current_machine_tag_.empty()) |
| InitializeCurrentMachineTag(&trans); |
| if (local_device_info) { |
| current_session_name_ = local_device_info->client_name(); |
| } else { |
| return error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| "Failed to get device info.", |
| model_type()); |
| } |
| synced_session_tracker_.SetLocalSessionTag(current_machine_tag_); |
| if (!UpdateAssociationsFromSyncModel(root, &trans, &error)) { |
| DCHECK(error.IsSet()); |
| return error; |
| } |
| |
| if (local_session_syncid_ == syncer::kInvalidId) { |
| // The sync db didn't have a header node for us, we need to create one. |
| syncer::WriteNode write_node(&trans); |
| syncer::WriteNode::InitUniqueByCreationResult result = |
| write_node.InitUniqueByCreation(SESSIONS, root, current_machine_tag_); |
| if (result != syncer::WriteNode::INIT_SUCCESS) { |
| // If we can't look it up, and we can't create it, chances are there's |
| // a pre-existing node that has encryption issues. But, since we can't |
| // load the item, we can't remove it, and error out at this point. |
| return error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| "Failed to create sessions header sync node.", |
| model_type()); |
| } |
| |
| // Write the initial values to the specifics so that in case of a crash or |
| // error we don't persist a half-written node. |
| write_node.SetTitle(UTF8ToWide(current_machine_tag_)); |
| sync_pb::SessionSpecifics base_specifics; |
| base_specifics.set_session_tag(current_machine_tag_); |
| sync_pb::SessionHeader* header_s = base_specifics.mutable_header(); |
| header_s->set_client_name(current_session_name_); |
| header_s->set_device_type(DeviceInfo::GetLocalDeviceType()); |
| write_node.SetSessionSpecifics(base_specifics); |
| |
| local_session_syncid_ = write_node.GetId(); |
| } |
| #if defined(OS_ANDROID) |
| transaction_tag = GetMachineTagFromTransaction(&trans); |
| #endif |
| } |
| #if defined(OS_ANDROID) |
| // We need to delete foreign sessions after giving up our |
| // syncer::WriteTransaction, since DeleteForeignSession(std::string&) uses |
| // its own syncer::WriteTransaction. |
| if (current_machine_tag_.compare(transaction_tag) != 0) |
| DeleteForeignSession(transaction_tag); |
| #endif |
| |
| // Check if anything has changed on the client side. |
| if (!UpdateSyncModelDataFromClient(&error)) { |
| DCHECK(error.IsSet()); |
| return error; |
| } |
| |
| DVLOG(1) << "Session models associated."; |
| DCHECK(!error.IsSet()); |
| return error; |
| } |
| |
| syncer::SyncError SessionModelAssociator::DisassociateModels() { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(1) << "Disassociating local session " << GetCurrentMachineTag(); |
| synced_session_tracker_.Clear(); |
| local_tab_map_.clear(); |
| local_tab_pool_.Clear(); |
| local_session_syncid_ = syncer::kInvalidId; |
| current_machine_tag_ = ""; |
| current_session_name_ = ""; |
| |
| // There is no local model stored with which to disassociate, just notify |
| // foreign session handlers. |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED, |
| content::Source<Profile>(sync_service_->profile()), |
| content::NotificationService::NoDetails()); |
| return syncer::SyncError(); |
| } |
| |
| void SessionModelAssociator::InitializeCurrentMachineTag( |
| syncer::WriteTransaction* trans) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(current_machine_tag_.empty()); |
| std::string persisted_guid; |
| browser_sync::SyncPrefs prefs(profile_->GetPrefs()); |
| persisted_guid = prefs.GetSyncSessionsGUID(); |
| if (!persisted_guid.empty()) { |
| current_machine_tag_ = persisted_guid; |
| DVLOG(1) << "Restoring persisted session sync guid: " |
| << persisted_guid; |
| } else { |
| current_machine_tag_ = GetMachineTagFromTransaction(trans); |
| DVLOG(1) << "Creating session sync guid: " << current_machine_tag_; |
| prefs.SetSyncSessionsGUID(current_machine_tag_); |
| } |
| |
| local_tab_pool_.SetMachineTag(current_machine_tag_); |
| } |
| |
| bool SessionModelAssociator::GetSyncedFaviconForPageURL( |
| const std::string& page_url, |
| scoped_refptr<base::RefCountedMemory>* favicon_png) const { |
| return favicon_cache_.GetSyncedFaviconForPageURL(GURL(page_url), favicon_png); |
| } |
| |
| scoped_ptr<browser_sync::DeviceInfo> |
| SessionModelAssociator::GetDeviceInfoForSessionTag( |
| const std::string& session_tag) { |
| std::string client_id = GetClientIdFromSessionTag(session_tag); |
| return sync_service_->GetDeviceInfo(client_id); |
| } |
| |
| bool SessionModelAssociator::UpdateAssociationsFromSyncModel( |
| const syncer::ReadNode& root, |
| syncer::WriteTransaction* trans, |
| syncer::SyncError* error) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(local_tab_pool_.Empty()); |
| DCHECK_EQ(local_session_syncid_, syncer::kInvalidId); |
| |
| // Iterate through the nodes and associate any foreign sessions. |
| int64 id = root.GetFirstChildId(); |
| while (id != syncer::kInvalidId) { |
| syncer::WriteNode sync_node(trans); |
| if (sync_node.InitByIdLookup(id) != syncer::BaseNode::INIT_OK) { |
| if (error) { |
| *error = error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| "Failed to load sync node", |
| model_type()); |
| } |
| return false; |
| } |
| int64 next_id = sync_node.GetSuccessorId(); |
| |
| const sync_pb::SessionSpecifics& specifics = |
| sync_node.GetSessionSpecifics(); |
| const base::Time& modification_time = sync_node.GetModificationTime(); |
| if (specifics.session_tag().empty() || |
| (specifics.has_tab() && (!specifics.has_tab_node_id() || |
| !specifics.tab().has_tab_id()))) { |
| // This is a corrupted node. Just delete it. |
| LOG(WARNING) << "Found invalid session node, deleting."; |
| sync_node.Tombstone(); |
| } else if (specifics.session_tag() != GetCurrentMachineTag()) { |
| AssociateForeignSpecifics(specifics, modification_time); |
| } else { |
| // This is previously stored local session information. |
| if (specifics.has_header() && |
| local_session_syncid_ == syncer::kInvalidId) { |
| // This is our previous header node, reuse it. |
| local_session_syncid_ = id; |
| if (specifics.header().has_client_name()) { |
| current_session_name_ = specifics.header().client_name(); |
| } |
| } else { |
| if (specifics.has_header() || !specifics.has_tab()) { |
| LOG(WARNING) << "Found invalid session node, deleting."; |
| sync_node.Tombstone(); |
| } else { |
| // This is a valid old tab node, add it to the pool so it can be |
| // reused for reassociation. |
| local_tab_pool_.AddTabNode(specifics.tab_node_id()); |
| } |
| } |
| } |
| id = next_id; |
| } |
| |
| return true; |
| } |
| |
| void SessionModelAssociator::AssociateForeignSpecifics( |
| const sync_pb::SessionSpecifics& specifics, |
| const base::Time& modification_time) { |
| DCHECK(CalledOnValidThread()); |
| std::string foreign_session_tag = specifics.session_tag(); |
| if (foreign_session_tag == GetCurrentMachineTag() && !setup_for_test_) |
| return; |
| |
| SyncedSession* foreign_session = |
| synced_session_tracker_.GetSession(foreign_session_tag); |
| 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 SyncedSession object for this client. |
| const sync_pb::SessionHeader& header = specifics.header(); |
| PopulateSessionHeaderFromSpecifics(header, |
| modification_time, |
| foreign_session); |
| |
| // 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). |
| synced_session_tracker_.ResetSessionTracking(foreign_session_tag); |
| |
| // Process all the windows and their tab information. |
| int num_windows = header.window_size(); |
| DVLOG(1) << "Associating " << foreign_session_tag << " with " |
| << num_windows << " windows."; |
| for (int i = 0; i < num_windows; ++i) { |
| const sync_pb::SessionWindow& window_s = header.window(i); |
| SessionID::id_type window_id = window_s.window_id(); |
| synced_session_tracker_.PutWindowInSession(foreign_session_tag, |
| window_id); |
| PopulateSessionWindowFromSpecifics(foreign_session_tag, |
| window_s, |
| modification_time, |
| foreign_session->windows[window_id], |
| &synced_session_tracker_); |
| } |
| |
| // Delete any closed windows and unused tabs as necessary. |
| synced_session_tracker_.CleanupSession(foreign_session_tag); |
| } else if (specifics.has_tab()) { |
| const sync_pb::SessionTab& tab_s = specifics.tab(); |
| SessionID::id_type tab_id = tab_s.tab_id(); |
| SessionTab* tab = |
| synced_session_tracker_.GetTab(foreign_session_tag, |
| tab_id, |
| specifics.tab_node_id()); |
| |
| // Update SessionTab based on protobuf. |
| tab->SetFromSyncData(tab_s, modification_time); |
| |
| // If a favicon or favicon urls are present, load them into the in-memory |
| // favicon cache. |
| LoadForeignTabFavicon(tab_s); |
| |
| // Update the last modified time. |
| if (foreign_session->modified_time < modification_time) |
| foreign_session->modified_time = modification_time; |
| } else { |
| LOG(WARNING) << "Ignoring foreign session node with missing header/tab " |
| << "fields and tag " << foreign_session_tag << "."; |
| } |
| } |
| |
| bool SessionModelAssociator::DisassociateForeignSession( |
| const std::string& foreign_session_tag) { |
| DCHECK(CalledOnValidThread()); |
| if (foreign_session_tag == GetCurrentMachineTag()) { |
| DVLOG(1) << "Local session deleted! Doing nothing until a navigation is " |
| << "triggered."; |
| return false; |
| } |
| DVLOG(1) << "Disassociating session " << foreign_session_tag; |
| return synced_session_tracker_.DeleteSession(foreign_session_tag); |
| } |
| |
| // Static |
| void SessionModelAssociator::PopulateSessionHeaderFromSpecifics( |
| const sync_pb::SessionHeader& header_specifics, |
| base::Time mtime, |
| SyncedSession* session_header) { |
| if (header_specifics.has_client_name()) { |
| session_header->session_name = header_specifics.client_name(); |
| } |
| if (header_specifics.has_device_type()) { |
| switch (header_specifics.device_type()) { |
| case sync_pb::SyncEnums_DeviceType_TYPE_WIN: |
| session_header->device_type = SyncedSession::TYPE_WIN; |
| break; |
| case sync_pb::SyncEnums_DeviceType_TYPE_MAC: |
| session_header->device_type = SyncedSession::TYPE_MACOSX; |
| break; |
| case sync_pb::SyncEnums_DeviceType_TYPE_LINUX: |
| session_header->device_type = SyncedSession::TYPE_LINUX; |
| break; |
| case sync_pb::SyncEnums_DeviceType_TYPE_CROS: |
| session_header->device_type = SyncedSession::TYPE_CHROMEOS; |
| break; |
| case sync_pb::SyncEnums_DeviceType_TYPE_PHONE: |
| session_header->device_type = SyncedSession::TYPE_PHONE; |
| break; |
| case sync_pb::SyncEnums_DeviceType_TYPE_TABLET: |
| session_header->device_type = SyncedSession::TYPE_TABLET; |
| break; |
| case sync_pb::SyncEnums_DeviceType_TYPE_OTHER: |
| // Intentionally fall-through |
| default: |
| session_header->device_type = SyncedSession::TYPE_OTHER; |
| break; |
| } |
| } |
| session_header->modified_time = mtime; |
| } |
| |
| // Static |
| void SessionModelAssociator::PopulateSessionWindowFromSpecifics( |
| const std::string& session_tag, |
| const sync_pb::SessionWindow& specifics, |
| base::Time mtime, |
| SessionWindow* session_window, |
| SyncedSessionTracker* 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_TABBED) { |
| session_window->type = 1; |
| } else { |
| session_window->type = 2; |
| } |
| } |
| session_window->timestamp = mtime; |
| session_window->tabs.resize(specifics.tab_size(), NULL); |
| for (int i = 0; i < specifics.tab_size(); i++) { |
| SessionID::id_type tab_id = specifics.tab(i); |
| tracker->PutTabInWindow(session_tag, |
| session_window->window_id.id(), |
| tab_id, |
| i); |
| } |
| } |
| |
| void SessionModelAssociator::LoadForeignTabFavicon( |
| const sync_pb::SessionTab& tab) { |
| // First go through and iterate over all the navigations, checking if any |
| // have valid favicon urls. |
| for (int i = 0; i < tab.navigation_size(); ++i) { |
| if (!tab.navigation(i).favicon_url().empty()) { |
| const std::string& page_url = tab.navigation(i).virtual_url(); |
| const std::string& favicon_url = tab.navigation(i).favicon_url(); |
| favicon_cache_.OnReceivedSyncFavicon(GURL(page_url), |
| GURL(favicon_url), |
| std::string(), |
| syncer::TimeToProtoTime( |
| base::Time::Now())); |
| } |
| } |
| |
| // Then go through and check for any legacy favicon data. |
| if (!tab.has_favicon() || tab.favicon().empty()) |
| return; |
| if (!tab.has_favicon_type() || |
| tab.favicon_type() != sync_pb::SessionTab::TYPE_WEB_FAVICON) { |
| DVLOG(1) << "Ignoring non-web favicon."; |
| return; |
| } |
| if (tab.navigation_size() == 0) |
| return; |
| int selected_index = tab.current_navigation_index(); |
| selected_index = std::max( |
| 0, |
| std::min(selected_index, |
| static_cast<int>(tab.navigation_size() - 1))); |
| GURL navigation_url(tab.navigation(selected_index).virtual_url()); |
| if (!navigation_url.is_valid()) |
| return; |
| GURL favicon_source(tab.favicon_source()); |
| if (!favicon_source.is_valid()) |
| return; |
| |
| const std::string& favicon = tab.favicon(); |
| DVLOG(1) << "Storing synced favicon for url " << navigation_url.spec() |
| << " with size " << favicon.size() << " bytes."; |
| favicon_cache_.OnReceivedSyncFavicon(navigation_url, |
| favicon_source, |
| favicon, |
| syncer::TimeToProtoTime( |
| base::Time::Now())); |
| } |
| |
| bool SessionModelAssociator::UpdateSyncModelDataFromClient( |
| syncer::SyncError* error) { |
| DCHECK(CalledOnValidThread()); |
| |
| // Associate all open windows and their tabs. |
| return AssociateWindows(true, error); |
| } |
| |
| void SessionModelAssociator::AttemptSessionsDataRefresh() const { |
| DVLOG(1) << "Triggering sync refresh for sessions datatype."; |
| const syncer::ModelTypeSet types(syncer::SESSIONS); |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_SYNC_REFRESH_LOCAL, |
| content::Source<Profile>(profile_), |
| content::Details<const syncer::ModelTypeSet>(&types)); |
| } |
| |
| bool SessionModelAssociator::GetLocalSession( |
| const SyncedSession* * local_session) { |
| DCHECK(CalledOnValidThread()); |
| if (current_machine_tag_.empty()) |
| return false; |
| *local_session = synced_session_tracker_.GetSession(GetCurrentMachineTag()); |
| return true; |
| } |
| |
| bool SessionModelAssociator::GetAllForeignSessions( |
| std::vector<const SyncedSession*>* sessions) { |
| DCHECK(CalledOnValidThread()); |
| return synced_session_tracker_.LookupAllForeignSessions(sessions); |
| } |
| |
| bool SessionModelAssociator::GetForeignSession( |
| const std::string& tag, |
| std::vector<const SessionWindow*>* windows) { |
| DCHECK(CalledOnValidThread()); |
| return synced_session_tracker_.LookupSessionWindows(tag, windows); |
| } |
| |
| bool SessionModelAssociator::GetForeignTab( |
| const std::string& tag, |
| const SessionID::id_type tab_id, |
| const SessionTab** tab) { |
| DCHECK(CalledOnValidThread()); |
| const SessionTab* synced_tab = NULL; |
| bool success = synced_session_tracker_.LookupSessionTab(tag, |
| tab_id, |
| &synced_tab); |
| if (success) |
| *tab = synced_tab; |
| return success; |
| } |
| |
| void SessionModelAssociator::DeleteStaleSessions() { |
| DCHECK(CalledOnValidThread()); |
| std::vector<const SyncedSession*> sessions; |
| if (!GetAllForeignSessions(&sessions)) |
| return; // No foreign sessions. |
| |
| // Iterate through all the sessions and delete any with age older than |
| // |stale_session_threshold_days_|. |
| for (std::vector<const SyncedSession*>::const_iterator iter = |
| sessions.begin(); iter != sessions.end(); ++iter) { |
| const SyncedSession* session = *iter; |
| int session_age_in_days = |
| (base::Time::Now() - session->modified_time).InDays(); |
| std::string session_tag = session->session_tag; |
| if (session_age_in_days > 0 && // If false, local clock is not trustworty. |
| static_cast<size_t>(session_age_in_days) > |
| stale_session_threshold_days_) { |
| DVLOG(1) << "Found stale session " << session_tag |
| << " with age " << session_age_in_days << ", deleting."; |
| DeleteForeignSession(session_tag); |
| } |
| } |
| } |
| |
| void SessionModelAssociator::SetStaleSessionThreshold( |
| size_t stale_session_threshold_days) { |
| DCHECK(CalledOnValidThread()); |
| if (stale_session_threshold_days_ == 0) { |
| NOTREACHED() << "Attempted to set invalid stale session threshold."; |
| return; |
| } |
| stale_session_threshold_days_ = stale_session_threshold_days; |
| // TODO(zea): maybe make this preference-based? Might be nice to let users be |
| // able to modify this once and forget about it. At the moment, if we want a |
| // different threshold we will need to call this everytime we create a new |
| // model associator and before we AssociateModels (probably from DTC). |
| } |
| |
| void SessionModelAssociator::DeleteForeignSession(const std::string& tag) { |
| DCHECK(CalledOnValidThread()); |
| if (tag == GetCurrentMachineTag()) { |
| LOG(ERROR) << "Attempting to delete local session. This is not currently " |
| << "supported."; |
| return; |
| } |
| |
| if (!DisassociateForeignSession(tag)) { |
| // We don't have any data for this session, our work here is done! |
| return; |
| } |
| |
| syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); |
| syncer::ReadNode root(&trans); |
| if (root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)) != |
| syncer::BaseNode::INIT_OK) { |
| LOG(ERROR) << kNoSessionsFolderError; |
| return; |
| } |
| int64 id = root.GetFirstChildId(); |
| while (id != syncer::kInvalidId) { |
| syncer::WriteNode sync_node(&trans); |
| if (sync_node.InitByIdLookup(id) != syncer::BaseNode::INIT_OK) { |
| LOG(ERROR) << "Failed to fetch sync node for id " << id; |
| continue; |
| } |
| id = sync_node.GetSuccessorId(); |
| const sync_pb::SessionSpecifics& specifics = |
| sync_node.GetSessionSpecifics(); |
| if (specifics.session_tag() == tag) |
| sync_node.Tombstone(); |
| } |
| |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED, |
| content::Source<Profile>(sync_service_->profile()), |
| content::NotificationService::NoDetails()); |
| } |
| |
| bool SessionModelAssociator::IsValidTab(const SyncedTabDelegate& tab) const { |
| if ((!sync_service_ || tab.profile() != sync_service_->profile()) && |
| !setup_for_test_) { |
| return false; |
| } |
| const SyncedWindowDelegate* window = |
| SyncedWindowDelegate::FindSyncedWindowDelegateWithId( |
| tab.GetWindowId()); |
| if (!window && !setup_for_test_) |
| return false; |
| return true; |
| } |
| |
| bool SessionModelAssociator::TabHasValidEntry( |
| const SyncedTabDelegate& tab) const { |
| if (tab.ProfileIsManaged() && tab.GetBlockedNavigations()->size() > 0) |
| return true; |
| |
| int entry_count = tab.GetEntryCount(); |
| if (entry_count == 0) |
| return false; // This deliberately ignores a new pending entry. |
| |
| int pending_index = tab.GetPendingEntryIndex(); |
| bool found_valid_url = false; |
| for (int i = 0; i < entry_count; ++i) { |
| const content::NavigationEntry* entry = (i == pending_index) ? |
| tab.GetPendingEntry() : tab.GetEntryAtIndex(i); |
| if (!entry) |
| return false; |
| const GURL& virtual_url = entry->GetVirtualURL(); |
| if (virtual_url.is_valid() && |
| !virtual_url.SchemeIs(chrome::kChromeUIScheme) && |
| !virtual_url.SchemeIs(chrome::kChromeNativeScheme) && |
| !virtual_url.SchemeIsFile()) { |
| found_valid_url = true; |
| } |
| } |
| return found_valid_url; |
| } |
| |
| // If this functionality changes, browser_sync::ShouldSyncSessionTab should be |
| // modified to match. |
| bool SessionModelAssociator::ShouldSyncTab(const SyncedTabDelegate& tab) const { |
| DCHECK(CalledOnValidThread()); |
| if (!IsValidTab(tab)) |
| return false; |
| return TabHasValidEntry(tab); |
| } |
| |
| void SessionModelAssociator::QuitLoopForSubtleTesting() { |
| if (waiting_for_change_) { |
| DVLOG(1) << "Quitting base::MessageLoop for test."; |
| waiting_for_change_ = false; |
| test_weak_factory_.InvalidateWeakPtrs(); |
| base::MessageLoop::current()->Quit(); |
| } |
| } |
| |
| FaviconCache* SessionModelAssociator::GetFaviconCache() { |
| return &favicon_cache_; |
| } |
| |
| void SessionModelAssociator::BlockUntilLocalChangeForTest( |
| base::TimeDelta timeout) { |
| if (test_weak_factory_.HasWeakPtrs()) |
| return; |
| waiting_for_change_ = true; |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&SessionModelAssociator::QuitLoopForSubtleTesting, |
| test_weak_factory_.GetWeakPtr()), |
| timeout); |
| } |
| |
| bool SessionModelAssociator::CryptoReadyIfNecessary() { |
| // We only access the cryptographer while holding a transaction. |
| syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare()); |
| const syncer::ModelTypeSet encrypted_types = trans.GetEncryptedTypes(); |
| return !encrypted_types.Has(SESSIONS) || |
| sync_service_->IsCryptographerReady(&trans); |
| } |
| |
| void SessionModelAssociator::UpdateTabIdIfNecessary( |
| int tab_node_id, |
| SessionID::id_type new_tab_id) { |
| DCHECK_NE(tab_node_id, TabNodePool::kInvalidTabNodeID); |
| SessionID::id_type old_tab_id = |
| local_tab_pool_.GetTabIdFromTabNodeId(tab_node_id); |
| if (old_tab_id != new_tab_id) { |
| // Rewrite tab id if required. |
| syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); |
| syncer::WriteNode tab_node(&trans); |
| if (tab_node.InitByClientTagLookup(syncer::SESSIONS, |
| TabNodePool::TabIdToTag(current_machine_tag_, tab_node_id)) == |
| syncer::BaseNode::INIT_OK) { |
| sync_pb::SessionSpecifics session_specifics = |
| tab_node.GetSessionSpecifics(); |
| DCHECK(session_specifics.has_tab()); |
| if (session_specifics.has_tab()) { |
| sync_pb::SessionTab* tab_s = session_specifics.mutable_tab(); |
| tab_s->set_tab_id(new_tab_id); |
| tab_node.SetSessionSpecifics(session_specifics); |
| // Update tab node pool with the new association. |
| local_tab_pool_.ReassociateTabNode(tab_node_id, new_tab_id); |
| } |
| } |
| } |
| } |
| |
| } // namespace browser_sync |