Home | History | Annotate | Download | only in sessions
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/sync/sessions/sessions_sync_manager.h"
      6 
      7 #include "chrome/browser/chrome_notification_types.h"
      8 #include "chrome/browser/profiles/profile.h"
      9 #include "chrome/browser/sync/glue/synced_tab_delegate.h"
     10 #include "chrome/browser/sync/glue/synced_window_delegate.h"
     11 #include "chrome/browser/sync/sessions/sessions_util.h"
     12 #include "chrome/browser/sync/sessions/synced_window_delegates_getter.h"
     13 #include "chrome/common/url_constants.h"
     14 #include "components/sync_driver/local_device_info_provider.h"
     15 #include "content/public/browser/favicon_status.h"
     16 #include "content/public/browser/navigation_entry.h"
     17 #include "content/public/browser/notification_details.h"
     18 #include "content/public/browser/notification_service.h"
     19 #include "content/public/browser/notification_source.h"
     20 #include "content/public/common/url_constants.h"
     21 #include "sync/api/sync_error.h"
     22 #include "sync/api/sync_error_factory.h"
     23 #include "sync/api/sync_merge_result.h"
     24 #include "sync/api/time.h"
     25 
     26 using content::NavigationEntry;
     27 using sessions::SerializedNavigationEntry;
     28 using sync_driver::DeviceInfo;
     29 using sync_driver::LocalDeviceInfoProvider;
     30 using syncer::SyncChange;
     31 using syncer::SyncData;
     32 
     33 namespace browser_sync {
     34 
     35 // Maximum number of favicons to sync.
     36 // TODO(zea): pull this from the server.
     37 static const int kMaxSyncFavicons = 200;
     38 
     39 // The maximum number of navigations in each direction we care to sync.
     40 static const int kMaxSyncNavigationCount = 6;
     41 
     42 // The URL at which the set of synced tabs is displayed. We treat it differently
     43 // from all other URL's as accessing it triggers a sync refresh of Sessions.
     44 static const char kNTPOpenTabSyncURL[] = "chrome://newtab/#open_tabs";
     45 
     46 // Default number of days without activity after which a session is considered
     47 // stale and becomes a candidate for garbage collection.
     48 static const size_t kDefaultStaleSessionThresholdDays = 14;  // 2 weeks.
     49 
     50 // |local_device| is owned by ProfileSyncService, its lifetime exceeds
     51 // lifetime of SessionSyncManager.
     52 SessionsSyncManager::SessionsSyncManager(
     53     Profile* profile,
     54     LocalDeviceInfoProvider* local_device,
     55     scoped_ptr<LocalSessionEventRouter> router)
     56     : favicon_cache_(profile, kMaxSyncFavicons),
     57       local_tab_pool_out_of_sync_(true),
     58       sync_prefs_(profile->GetPrefs()),
     59       profile_(profile),
     60       local_device_(local_device),
     61       local_session_header_node_id_(TabNodePool::kInvalidTabNodeID),
     62       stale_session_threshold_days_(kDefaultStaleSessionThresholdDays),
     63       local_event_router_(router.Pass()),
     64       synced_window_getter_(new SyncedWindowDelegatesGetter()) {
     65 }
     66 
     67 LocalSessionEventRouter::~LocalSessionEventRouter() {}
     68 
     69 SessionsSyncManager::~SessionsSyncManager() {
     70 }
     71 
     72 // Returns the GUID-based string that should be used for
     73 // |SessionsSyncManager::current_machine_tag_|.
     74 static std::string BuildMachineTag(const std::string& cache_guid) {
     75   std::string machine_tag = "session_sync";
     76   machine_tag.append(cache_guid);
     77   return machine_tag;
     78 }
     79 
     80 syncer::SyncMergeResult SessionsSyncManager::MergeDataAndStartSyncing(
     81      syncer::ModelType type,
     82      const syncer::SyncDataList& initial_sync_data,
     83      scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
     84      scoped_ptr<syncer::SyncErrorFactory> error_handler) {
     85   syncer::SyncMergeResult merge_result(type);
     86   DCHECK(session_tracker_.Empty());
     87   DCHECK_EQ(0U, local_tab_pool_.Capacity());
     88 
     89   error_handler_ = error_handler.Pass();
     90   sync_processor_ = sync_processor.Pass();
     91 
     92   local_session_header_node_id_ = TabNodePool::kInvalidTabNodeID;
     93 
     94   // Make sure we have a machine tag.  We do this now (versus earlier) as it's
     95   // a conveniently safe time to assert sync is ready and the cache_guid is
     96   // initialized.
     97   if (current_machine_tag_.empty()) {
     98     InitializeCurrentMachineTag();
     99   }
    100 
    101   // SessionDataTypeController ensures that the local device info
    102   // is available before activating this datatype.
    103   DCHECK(local_device_);
    104   const DeviceInfo* local_device_info = local_device_->GetLocalDeviceInfo();
    105   if (local_device_info) {
    106     current_session_name_ = local_device_info->client_name();
    107   } else {
    108     merge_result.set_error(error_handler_->CreateAndUploadError(
    109         FROM_HERE,
    110         "Failed to get local device info."));
    111     return merge_result;
    112   }
    113 
    114   session_tracker_.SetLocalSessionTag(current_machine_tag_);
    115 
    116   syncer::SyncChangeList new_changes;
    117 
    118   // First, we iterate over sync data to update our session_tracker_.
    119   syncer::SyncDataList restored_tabs;
    120   if (!InitFromSyncModel(initial_sync_data, &restored_tabs, &new_changes)) {
    121     // The sync db didn't have a header node for us. Create one.
    122     sync_pb::EntitySpecifics specifics;
    123     sync_pb::SessionSpecifics* base_specifics = specifics.mutable_session();
    124     base_specifics->set_session_tag(current_machine_tag());
    125     sync_pb::SessionHeader* header_s = base_specifics->mutable_header();
    126     header_s->set_client_name(current_session_name_);
    127     header_s->set_device_type(local_device_info->device_type());
    128     syncer::SyncData data = syncer::SyncData::CreateLocalData(
    129         current_machine_tag(), current_session_name_, specifics);
    130     new_changes.push_back(syncer::SyncChange(
    131         FROM_HERE, syncer::SyncChange::ACTION_ADD, data));
    132   }
    133 
    134 #if defined(OS_ANDROID)
    135   std::string sync_machine_tag(BuildMachineTag(
    136       local_device_->GetLocalSyncCacheGUID()));
    137   if (current_machine_tag_.compare(sync_machine_tag) != 0)
    138     DeleteForeignSessionInternal(sync_machine_tag, &new_changes);
    139 #endif
    140 
    141   // Check if anything has changed on the local client side.
    142   AssociateWindows(RELOAD_TABS, restored_tabs, &new_changes);
    143   local_tab_pool_out_of_sync_ = false;
    144 
    145   merge_result.set_error(
    146       sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes));
    147 
    148   local_event_router_->StartRoutingTo(this);
    149   return merge_result;
    150 }
    151 
    152 void SessionsSyncManager::AssociateWindows(
    153     ReloadTabsOption option,
    154     const syncer::SyncDataList& restored_tabs,
    155     syncer::SyncChangeList* change_output) {
    156   const std::string local_tag = current_machine_tag();
    157   sync_pb::SessionSpecifics specifics;
    158   specifics.set_session_tag(local_tag);
    159   sync_pb::SessionHeader* header_s = specifics.mutable_header();
    160   SyncedSession* current_session = session_tracker_.GetSession(local_tag);
    161   current_session->modified_time = base::Time::Now();
    162   header_s->set_client_name(current_session_name_);
    163   // SessionDataTypeController ensures that the local device info
    164   // is available before activating this datatype.
    165   DCHECK(local_device_);
    166   const DeviceInfo* local_device_info = local_device_->GetLocalDeviceInfo();
    167   header_s->set_device_type(local_device_info->device_type());
    168 
    169   session_tracker_.ResetSessionTracking(local_tag);
    170   std::set<SyncedWindowDelegate*> windows =
    171       synced_window_getter_->GetSyncedWindowDelegates();
    172 
    173   for (std::set<SyncedWindowDelegate*>::const_iterator i =
    174            windows.begin(); i != windows.end(); ++i) {
    175     // Make sure the window has tabs and a viewable window. The viewable window
    176     // check is necessary because, for example, when a browser is closed the
    177     // destructor is not necessarily run immediately. This means its possible
    178     // for us to get a handle to a browser that is about to be removed. If
    179     // the tab count is 0 or the window is NULL, the browser is about to be
    180     // deleted, so we ignore it.
    181     if (sessions_util::ShouldSyncWindow(*i) &&
    182         (*i)->GetTabCount() && (*i)->HasWindow()) {
    183       sync_pb::SessionWindow window_s;
    184       SessionID::id_type window_id = (*i)->GetSessionId();
    185       DVLOG(1) << "Associating window " << window_id << " with "
    186                << (*i)->GetTabCount() << " tabs.";
    187       window_s.set_window_id(window_id);
    188       // Note: We don't bother to set selected tab index anymore. We still
    189       // consume it when receiving foreign sessions, as reading it is free, but
    190       // it triggers too many sync cycles with too little value to make setting
    191       // it worthwhile.
    192       if ((*i)->IsTypeTabbed()) {
    193         window_s.set_browser_type(
    194             sync_pb::SessionWindow_BrowserType_TYPE_TABBED);
    195       } else {
    196         window_s.set_browser_type(
    197             sync_pb::SessionWindow_BrowserType_TYPE_POPUP);
    198       }
    199 
    200       bool found_tabs = false;
    201       for (int j = 0; j < (*i)->GetTabCount(); ++j) {
    202         SessionID::id_type tab_id = (*i)->GetTabIdAt(j);
    203         SyncedTabDelegate* synced_tab = (*i)->GetTabAt(j);
    204 
    205         // GetTabAt can return a null tab; in that case just skip it.
    206         if (!synced_tab)
    207           continue;
    208 
    209         if (!synced_tab->HasWebContents()) {
    210           // For tabs without WebContents update the |tab_id|, as it could have
    211           // changed after a session restore.
    212           // Note: We cannot check if a tab is valid if it has no WebContents.
    213           // We assume any such tab is valid and leave the contents of
    214           // corresponding sync node unchanged.
    215           if (synced_tab->GetSyncId() > TabNodePool::kInvalidTabNodeID &&
    216               tab_id > TabNodePool::kInvalidTabID) {
    217             AssociateRestoredPlaceholderTab(*synced_tab, tab_id,
    218                                             restored_tabs, change_output);
    219             found_tabs = true;
    220             window_s.add_tab(tab_id);
    221           }
    222           continue;
    223         }
    224 
    225         if (RELOAD_TABS == option)
    226           AssociateTab(synced_tab, change_output);
    227 
    228         // If the tab is valid, it would have been added to the tracker either
    229         // by the above AssociateTab call (at association time), or by the
    230         // change processor calling AssociateTab for all modified tabs.
    231         // Therefore, we can key whether this window has valid tabs based on
    232         // the tab's presence in the tracker.
    233         const SessionTab* tab = NULL;
    234         if (session_tracker_.LookupSessionTab(local_tag, tab_id, &tab)) {
    235           found_tabs = true;
    236           window_s.add_tab(tab_id);
    237         }
    238       }
    239       if (found_tabs) {
    240         sync_pb::SessionWindow* header_window = header_s->add_window();
    241         *header_window = window_s;
    242 
    243         // Update this window's representation in the synced session tracker.
    244         session_tracker_.PutWindowInSession(local_tag, window_id);
    245         BuildSyncedSessionFromSpecifics(local_tag,
    246                                         window_s,
    247                                         current_session->modified_time,
    248                                         current_session->windows[window_id]);
    249       }
    250     }
    251   }
    252   local_tab_pool_.DeleteUnassociatedTabNodes(change_output);
    253   session_tracker_.CleanupSession(local_tag);
    254 
    255   // Always update the header.  Sync takes care of dropping this update
    256   // if the entity specifics are identical (i.e windows, client name did
    257   // not change).
    258   sync_pb::EntitySpecifics entity;
    259   entity.mutable_session()->CopyFrom(specifics);
    260   syncer::SyncData data = syncer::SyncData::CreateLocalData(
    261         current_machine_tag(), current_session_name_, entity);
    262   change_output->push_back(syncer::SyncChange(
    263         FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data));
    264 }
    265 
    266 void SessionsSyncManager::AssociateTab(SyncedTabDelegate* const tab,
    267                                        syncer::SyncChangeList* change_output) {
    268   DCHECK(tab->HasWebContents());
    269   SessionID::id_type tab_id = tab->GetSessionId();
    270   if (tab->profile() != profile_)
    271     return;
    272 
    273   if (tab->IsBeingDestroyed()) {
    274     // This tab is closing.
    275     TabLinksMap::iterator tab_iter = local_tab_map_.find(tab_id);
    276     if (tab_iter == local_tab_map_.end()) {
    277       // We aren't tracking this tab (for example, sync setting page).
    278       return;
    279     }
    280     local_tab_pool_.FreeTabNode(tab_iter->second->tab_node_id(),
    281                                 change_output);
    282     local_tab_map_.erase(tab_iter);
    283     return;
    284   }
    285 
    286   if (!sessions_util::ShouldSyncTab(*tab))
    287     return;
    288 
    289   TabLinksMap::iterator local_tab_map_iter = local_tab_map_.find(tab_id);
    290   TabLink* tab_link = NULL;
    291 
    292   if (local_tab_map_iter == local_tab_map_.end()) {
    293     int tab_node_id = tab->GetSyncId();
    294     // If there is an old sync node for the tab, reuse it.  If this is a new
    295     // tab, get a sync node for it.
    296     if (!local_tab_pool_.IsUnassociatedTabNode(tab_node_id)) {
    297       tab_node_id = local_tab_pool_.GetFreeTabNode(change_output);
    298       tab->SetSyncId(tab_node_id);
    299     }
    300     local_tab_pool_.AssociateTabNode(tab_node_id, tab_id);
    301     tab_link = new TabLink(tab_node_id, tab);
    302     local_tab_map_[tab_id] = make_linked_ptr<TabLink>(tab_link);
    303   } else {
    304     // This tab is already associated with a sync node, reuse it.
    305     // Note: on some platforms the tab object may have changed, so we ensure
    306     // the tab link is up to date.
    307     tab_link = local_tab_map_iter->second.get();
    308     local_tab_map_iter->second->set_tab(tab);
    309   }
    310   DCHECK(tab_link);
    311   DCHECK_NE(tab_link->tab_node_id(), TabNodePool::kInvalidTabNodeID);
    312   DVLOG(1) << "Reloading tab " << tab_id << " from window "
    313            << tab->GetWindowId();
    314 
    315   // Write to sync model.
    316   sync_pb::EntitySpecifics specifics;
    317   LocalTabDelegateToSpecifics(*tab, specifics.mutable_session());
    318   syncer::SyncData data = syncer::SyncData::CreateLocalData(
    319       TabNodePool::TabIdToTag(current_machine_tag_,
    320                                tab_link->tab_node_id()),
    321       current_session_name_,
    322       specifics);
    323   change_output->push_back(syncer::SyncChange(
    324       FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data));
    325 
    326   const GURL new_url = GetCurrentVirtualURL(*tab);
    327   if (new_url != tab_link->url()) {
    328     tab_link->set_url(new_url);
    329     favicon_cache_.OnFaviconVisited(new_url, GetCurrentFaviconURL(*tab));
    330   }
    331 
    332   session_tracker_.GetSession(current_machine_tag())->modified_time =
    333       base::Time::Now();
    334 }
    335 
    336 void SessionsSyncManager::RebuildAssociations() {
    337   syncer::SyncDataList data(
    338       sync_processor_->GetAllSyncData(syncer::SESSIONS));
    339   scoped_ptr<syncer::SyncErrorFactory> error_handler(error_handler_.Pass());
    340   scoped_ptr<syncer::SyncChangeProcessor> processor(sync_processor_.Pass());
    341 
    342   StopSyncing(syncer::SESSIONS);
    343   MergeDataAndStartSyncing(
    344       syncer::SESSIONS, data, processor.Pass(), error_handler.Pass());
    345 }
    346 
    347 bool SessionsSyncManager::IsValidSessionHeader(
    348     const sync_pb::SessionHeader& header) {
    349   // Verify that tab IDs appear only once within a session.
    350   // Intended to prevent http://crbug.com/360822.
    351   std::set<int> session_tab_ids;
    352   for (int i = 0; i < header.window_size(); ++i) {
    353     const sync_pb::SessionWindow& window = header.window(i);
    354     for (int j = 0; j < window.tab_size(); ++j) {
    355       const int tab_id = window.tab(j);
    356       bool success = session_tab_ids.insert(tab_id).second;
    357       if (!success)
    358         return false;
    359     }
    360   }
    361 
    362   return true;
    363 }
    364 
    365 void SessionsSyncManager::OnLocalTabModified(SyncedTabDelegate* modified_tab) {
    366   const content::NavigationEntry* entry = modified_tab->GetActiveEntry();
    367   if (!modified_tab->IsBeingDestroyed() &&
    368       entry &&
    369       entry->GetVirtualURL().is_valid() &&
    370       entry->GetVirtualURL().spec() == kNTPOpenTabSyncURL) {
    371     DVLOG(1) << "Triggering sync refresh for sessions datatype.";
    372     const syncer::ModelTypeSet types(syncer::SESSIONS);
    373     content::NotificationService::current()->Notify(
    374         chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
    375         content::Source<Profile>(profile_),
    376         content::Details<const syncer::ModelTypeSet>(&types));
    377   }
    378 
    379   if (local_tab_pool_out_of_sync_) {
    380     // If our tab pool is corrupt, pay the price of a full re-association to
    381     // fix things up.  This takes care of the new tab modification as well.
    382     RebuildAssociations();
    383     DCHECK(!local_tab_pool_out_of_sync_);
    384     return;
    385   }
    386 
    387   syncer::SyncChangeList changes;
    388   // Associate tabs first so the synced session tracker is aware of them.
    389   AssociateTab(modified_tab, &changes);
    390   // Note, we always associate windows because it's possible a tab became
    391   // "interesting" by going to a valid URL, in which case it needs to be added
    392   // to the window's tab information.
    393   AssociateWindows(DONT_RELOAD_TABS, syncer::SyncDataList(), &changes);
    394   sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
    395 }
    396 
    397 void SessionsSyncManager::OnFaviconPageUrlsUpdated(
    398     const std::set<GURL>& updated_favicon_page_urls) {
    399   // TODO(zea): consider a separate container for tabs with outstanding favicon
    400   // loads so we don't have to iterate through all tabs comparing urls.
    401   for (std::set<GURL>::const_iterator i = updated_favicon_page_urls.begin();
    402        i != updated_favicon_page_urls.end(); ++i) {
    403     for (TabLinksMap::iterator tab_iter = local_tab_map_.begin();
    404          tab_iter != local_tab_map_.end();
    405          ++tab_iter) {
    406       if (tab_iter->second->url() == *i)
    407         favicon_cache_.OnPageFaviconUpdated(*i);
    408     }
    409   }
    410 }
    411 
    412 void SessionsSyncManager::StopSyncing(syncer::ModelType type) {
    413   local_event_router_->Stop();
    414   sync_processor_.reset(NULL);
    415   error_handler_.reset();
    416   session_tracker_.Clear();
    417   local_tab_map_.clear();
    418   local_tab_pool_.Clear();
    419   current_machine_tag_.clear();
    420   current_session_name_.clear();
    421   local_session_header_node_id_ = TabNodePool::kInvalidTabNodeID;
    422 }
    423 
    424 syncer::SyncDataList SessionsSyncManager::GetAllSyncData(
    425     syncer::ModelType type) const {
    426   syncer::SyncDataList list;
    427   const SyncedSession* session = NULL;
    428   if (!session_tracker_.LookupLocalSession(&session))
    429     return syncer::SyncDataList();
    430 
    431   // First construct the header node.
    432   sync_pb::EntitySpecifics header_entity;
    433   header_entity.mutable_session()->set_session_tag(current_machine_tag());
    434   sync_pb::SessionHeader* header_specifics =
    435       header_entity.mutable_session()->mutable_header();
    436   header_specifics->MergeFrom(session->ToSessionHeader());
    437   syncer::SyncData data = syncer::SyncData::CreateLocalData(
    438         current_machine_tag(), current_session_name_, header_entity);
    439   list.push_back(data);
    440 
    441   SyncedSession::SyncedWindowMap::const_iterator win_iter;
    442   for (win_iter = session->windows.begin();
    443        win_iter != session->windows.end(); ++win_iter) {
    444     std::vector<SessionTab*>::const_iterator tabs_iter;
    445     for (tabs_iter = win_iter->second->tabs.begin();
    446          tabs_iter != win_iter->second->tabs.end(); ++tabs_iter) {
    447       sync_pb::EntitySpecifics entity;
    448       sync_pb::SessionSpecifics* specifics = entity.mutable_session();
    449       specifics->mutable_tab()->MergeFrom((*tabs_iter)->ToSyncData());
    450       specifics->set_session_tag(current_machine_tag_);
    451 
    452       TabLinksMap::const_iterator tab_map_iter = local_tab_map_.find(
    453           (*tabs_iter)->tab_id.id());
    454       DCHECK(tab_map_iter != local_tab_map_.end());
    455       specifics->set_tab_node_id(tab_map_iter->second->tab_node_id());
    456       syncer::SyncData data = syncer::SyncData::CreateLocalData(
    457           TabNodePool::TabIdToTag(current_machine_tag_,
    458                                    specifics->tab_node_id()),
    459           current_session_name_,
    460           entity);
    461       list.push_back(data);
    462     }
    463   }
    464   return list;
    465 }
    466 
    467 bool SessionsSyncManager::GetLocalSession(
    468     const SyncedSession* * local_session) {
    469   if (current_machine_tag_.empty())
    470     return false;
    471   *local_session = session_tracker_.GetSession(current_machine_tag());
    472   return true;
    473 }
    474 
    475 syncer::SyncError SessionsSyncManager::ProcessSyncChanges(
    476     const tracked_objects::Location& from_here,
    477     const syncer::SyncChangeList& change_list) {
    478   if (!sync_processor_.get()) {
    479     syncer::SyncError error(FROM_HERE,
    480                             syncer::SyncError::DATATYPE_ERROR,
    481                             "Models not yet associated.",
    482                             syncer::SESSIONS);
    483     return error;
    484   }
    485 
    486   for (syncer::SyncChangeList::const_iterator it = change_list.begin();
    487        it != change_list.end(); ++it) {
    488     DCHECK(it->IsValid());
    489     DCHECK(it->sync_data().GetSpecifics().has_session());
    490     const sync_pb::SessionSpecifics& session =
    491         it->sync_data().GetSpecifics().session();
    492     switch (it->change_type()) {
    493       case syncer::SyncChange::ACTION_DELETE:
    494         // Deletions are all or nothing (since we only ever delete entire
    495         // sessions). Therefore we don't care if it's a tab node or meta node,
    496         // and just ensure we've disassociated.
    497         if (current_machine_tag() == session.session_tag()) {
    498           // Another client has attempted to delete our local data (possibly by
    499           // error or a clock is inaccurate). Just ignore the deletion for now
    500           // to avoid any possible ping-pong delete/reassociate sequence, but
    501           // remember that this happened as our TabNodePool is inconsistent.
    502           local_tab_pool_out_of_sync_ = true;
    503           LOG(WARNING) << "Local session data deleted. Ignoring until next "
    504                        << "local navigation event.";
    505         } else if (session.has_header()) {
    506           // Disassociate only when header node is deleted. For tab node
    507           // deletions, the header node will be updated and foreign tab will
    508           // get deleted.
    509           DisassociateForeignSession(session.session_tag());
    510         }
    511         continue;
    512       case syncer::SyncChange::ACTION_ADD:
    513       case syncer::SyncChange::ACTION_UPDATE:
    514         if (current_machine_tag() == session.session_tag()) {
    515           // We should only ever receive a change to our own machine's session
    516           // info if encryption was turned on. In that case, the data is still
    517           // the same, so we can ignore.
    518           LOG(WARNING) << "Dropping modification to local session.";
    519           return syncer::SyncError();
    520         }
    521         UpdateTrackerWithForeignSession(
    522             session, syncer::SyncDataRemote(it->sync_data()).GetModifiedTime());
    523         break;
    524       default:
    525         NOTREACHED() << "Processing sync changes failed, unknown change type.";
    526     }
    527   }
    528 
    529   content::NotificationService::current()->Notify(
    530       chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
    531       content::Source<Profile>(profile_),
    532       content::NotificationService::NoDetails());
    533   return syncer::SyncError();
    534 }
    535 
    536 syncer::SyncChange SessionsSyncManager::TombstoneTab(
    537     const sync_pb::SessionSpecifics& tab) {
    538   if (!tab.has_tab_node_id()) {
    539     LOG(WARNING) << "Old sessions node without tab node id; can't tombstone.";
    540     return syncer::SyncChange();
    541   } else {
    542     return syncer::SyncChange(
    543         FROM_HERE,
    544         SyncChange::ACTION_DELETE,
    545         SyncData::CreateLocalDelete(
    546             TabNodePool::TabIdToTag(current_machine_tag(),
    547                                      tab.tab_node_id()),
    548             syncer::SESSIONS));
    549   }
    550 }
    551 
    552 bool SessionsSyncManager::GetAllForeignSessions(
    553     std::vector<const SyncedSession*>* sessions) {
    554   return session_tracker_.LookupAllForeignSessions(sessions);
    555 }
    556 
    557 bool SessionsSyncManager::InitFromSyncModel(
    558     const syncer::SyncDataList& sync_data,
    559     syncer::SyncDataList* restored_tabs,
    560     syncer::SyncChangeList* new_changes) {
    561   bool found_current_header = false;
    562   for (syncer::SyncDataList::const_iterator it = sync_data.begin();
    563        it != sync_data.end();
    564        ++it) {
    565     const syncer::SyncData& data = *it;
    566     DCHECK(data.GetSpecifics().has_session());
    567     const sync_pb::SessionSpecifics& specifics = data.GetSpecifics().session();
    568     if (specifics.session_tag().empty() ||
    569            (specifics.has_tab() && (!specifics.has_tab_node_id() ||
    570                                     !specifics.tab().has_tab_id()))) {
    571       syncer::SyncChange tombstone(TombstoneTab(specifics));
    572       if (tombstone.IsValid())
    573         new_changes->push_back(tombstone);
    574     } else if (specifics.session_tag() != current_machine_tag()) {
    575       UpdateTrackerWithForeignSession(
    576           specifics, syncer::SyncDataRemote(data).GetModifiedTime());
    577     } else {
    578       // This is previously stored local session information.
    579       if (specifics.has_header() && !found_current_header) {
    580         // This is our previous header node, reuse it.
    581         found_current_header = true;
    582         if (specifics.header().has_client_name())
    583           current_session_name_ = specifics.header().client_name();
    584       } else {
    585         if (specifics.has_header() || !specifics.has_tab()) {
    586           LOG(WARNING) << "Found more than one session header node with local "
    587                        << "tag.";
    588           syncer::SyncChange tombstone(TombstoneTab(specifics));
    589           if (tombstone.IsValid())
    590             new_changes->push_back(tombstone);
    591         } else {
    592           // This is a valid old tab node, add it to the pool so it can be
    593           // reused for reassociation.
    594           local_tab_pool_.AddTabNode(specifics.tab_node_id());
    595           restored_tabs->push_back(*it);
    596         }
    597       }
    598     }
    599   }
    600   return found_current_header;
    601 }
    602 
    603 void SessionsSyncManager::UpdateTrackerWithForeignSession(
    604     const sync_pb::SessionSpecifics& specifics,
    605     const base::Time& modification_time) {
    606   std::string foreign_session_tag = specifics.session_tag();
    607   DCHECK_NE(foreign_session_tag, current_machine_tag());
    608 
    609   SyncedSession* foreign_session =
    610       session_tracker_.GetSession(foreign_session_tag);
    611   if (specifics.has_header()) {
    612     // Read in the header data for this foreign session.
    613     // Header data contains window information and ordered tab id's for each
    614     // window.
    615 
    616     if (!IsValidSessionHeader(specifics.header())) {
    617       LOG(WARNING) << "Ignoring foreign session node with invalid header "
    618                    << "and tag " << foreign_session_tag << ".";
    619       return;
    620     }
    621 
    622     // Load (or create) the SyncedSession object for this client.
    623     const sync_pb::SessionHeader& header = specifics.header();
    624     PopulateSessionHeaderFromSpecifics(header,
    625                                        modification_time,
    626                                        foreign_session);
    627 
    628     // Reset the tab/window tracking for this session (must do this before
    629     // we start calling PutWindowInSession and PutTabInWindow so that all
    630     // unused tabs/windows get cleared by the CleanupSession(...) call).
    631     session_tracker_.ResetSessionTracking(foreign_session_tag);
    632 
    633     // Process all the windows and their tab information.
    634     int num_windows = header.window_size();
    635     DVLOG(1) << "Associating " << foreign_session_tag << " with "
    636              << num_windows << " windows.";
    637 
    638     for (int i = 0; i < num_windows; ++i) {
    639       const sync_pb::SessionWindow& window_s = header.window(i);
    640       SessionID::id_type window_id = window_s.window_id();
    641       session_tracker_.PutWindowInSession(foreign_session_tag,
    642                                           window_id);
    643       BuildSyncedSessionFromSpecifics(foreign_session_tag,
    644                                       window_s,
    645                                       modification_time,
    646                                       foreign_session->windows[window_id]);
    647     }
    648     // Delete any closed windows and unused tabs as necessary.
    649     session_tracker_.CleanupSession(foreign_session_tag);
    650   } else if (specifics.has_tab()) {
    651     const sync_pb::SessionTab& tab_s = specifics.tab();
    652     SessionID::id_type tab_id = tab_s.tab_id();
    653     SessionTab* tab =
    654         session_tracker_.GetTab(foreign_session_tag,
    655                                 tab_id,
    656                                 specifics.tab_node_id());
    657 
    658     // Update SessionTab based on protobuf.
    659     tab->SetFromSyncData(tab_s, modification_time);
    660 
    661     // If a favicon or favicon urls are present, load the URLs and visit
    662     // times into the in-memory favicon cache.
    663     RefreshFaviconVisitTimesFromForeignTab(tab_s, modification_time);
    664 
    665     // Update the last modified time.
    666     if (foreign_session->modified_time < modification_time)
    667       foreign_session->modified_time = modification_time;
    668   } else {
    669     LOG(WARNING) << "Ignoring foreign session node with missing header/tab "
    670                  << "fields and tag " << foreign_session_tag << ".";
    671   }
    672 }
    673 
    674 void SessionsSyncManager::InitializeCurrentMachineTag() {
    675   DCHECK(current_machine_tag_.empty());
    676   std::string persisted_guid;
    677   persisted_guid = sync_prefs_.GetSyncSessionsGUID();
    678   if (!persisted_guid.empty()) {
    679     current_machine_tag_ = persisted_guid;
    680     DVLOG(1) << "Restoring persisted session sync guid: " << persisted_guid;
    681   } else {
    682     DCHECK(local_device_);
    683     std::string cache_guid = local_device_->GetLocalSyncCacheGUID();
    684     DCHECK(!cache_guid.empty());
    685     current_machine_tag_ = BuildMachineTag(cache_guid);
    686     DVLOG(1) << "Creating session sync guid: " << current_machine_tag_;
    687     sync_prefs_.SetSyncSessionsGUID(current_machine_tag_);
    688   }
    689 
    690   local_tab_pool_.SetMachineTag(current_machine_tag_);
    691 }
    692 
    693 // static
    694 void SessionsSyncManager::PopulateSessionHeaderFromSpecifics(
    695     const sync_pb::SessionHeader& header_specifics,
    696     base::Time mtime,
    697     SyncedSession* session_header) {
    698   if (header_specifics.has_client_name())
    699     session_header->session_name = header_specifics.client_name();
    700   if (header_specifics.has_device_type()) {
    701     switch (header_specifics.device_type()) {
    702       case sync_pb::SyncEnums_DeviceType_TYPE_WIN:
    703         session_header->device_type = SyncedSession::TYPE_WIN;
    704         break;
    705       case sync_pb::SyncEnums_DeviceType_TYPE_MAC:
    706         session_header->device_type = SyncedSession::TYPE_MACOSX;
    707         break;
    708       case sync_pb::SyncEnums_DeviceType_TYPE_LINUX:
    709         session_header->device_type = SyncedSession::TYPE_LINUX;
    710         break;
    711       case sync_pb::SyncEnums_DeviceType_TYPE_CROS:
    712         session_header->device_type = SyncedSession::TYPE_CHROMEOS;
    713         break;
    714       case sync_pb::SyncEnums_DeviceType_TYPE_PHONE:
    715         session_header->device_type = SyncedSession::TYPE_PHONE;
    716         break;
    717       case sync_pb::SyncEnums_DeviceType_TYPE_TABLET:
    718         session_header->device_type = SyncedSession::TYPE_TABLET;
    719         break;
    720       case sync_pb::SyncEnums_DeviceType_TYPE_OTHER:
    721         // Intentionally fall-through
    722       default:
    723         session_header->device_type = SyncedSession::TYPE_OTHER;
    724         break;
    725     }
    726   }
    727   session_header->modified_time = mtime;
    728 }
    729 
    730 // static
    731 void SessionsSyncManager::BuildSyncedSessionFromSpecifics(
    732     const std::string& session_tag,
    733     const sync_pb::SessionWindow& specifics,
    734     base::Time mtime,
    735     SessionWindow* session_window) {
    736   if (specifics.has_window_id())
    737     session_window->window_id.set_id(specifics.window_id());
    738   if (specifics.has_selected_tab_index())
    739     session_window->selected_tab_index = specifics.selected_tab_index();
    740   if (specifics.has_browser_type()) {
    741     if (specifics.browser_type() ==
    742         sync_pb::SessionWindow_BrowserType_TYPE_TABBED) {
    743       session_window->type = 1;
    744     } else {
    745       session_window->type = 2;
    746     }
    747   }
    748   session_window->timestamp = mtime;
    749   session_window->tabs.resize(specifics.tab_size(), NULL);
    750   for (int i = 0; i < specifics.tab_size(); i++) {
    751     SessionID::id_type tab_id = specifics.tab(i);
    752     session_tracker_.PutTabInWindow(session_tag,
    753                                     session_window->window_id.id(),
    754                                     tab_id,
    755                                     i);
    756   }
    757 }
    758 
    759 void SessionsSyncManager::RefreshFaviconVisitTimesFromForeignTab(
    760     const sync_pb::SessionTab& tab, const base::Time& modification_time) {
    761   // First go through and iterate over all the navigations, checking if any
    762   // have valid favicon urls.
    763   for (int i = 0; i < tab.navigation_size(); ++i) {
    764     if (!tab.navigation(i).favicon_url().empty()) {
    765       const std::string& page_url = tab.navigation(i).virtual_url();
    766       const std::string& favicon_url = tab.navigation(i).favicon_url();
    767       favicon_cache_.OnReceivedSyncFavicon(GURL(page_url),
    768                                            GURL(favicon_url),
    769                                            std::string(),
    770                                            syncer::TimeToProtoTime(
    771                                                modification_time));
    772     }
    773   }
    774 }
    775 
    776 bool SessionsSyncManager::GetSyncedFaviconForPageURL(
    777     const std::string& page_url,
    778     scoped_refptr<base::RefCountedMemory>* favicon_png) const {
    779   return favicon_cache_.GetSyncedFaviconForPageURL(GURL(page_url), favicon_png);
    780 }
    781 
    782 void SessionsSyncManager::DeleteForeignSession(const std::string& tag) {
    783   syncer::SyncChangeList changes;
    784   DeleteForeignSessionInternal(tag, &changes);
    785   sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
    786 }
    787 
    788 void SessionsSyncManager::DeleteForeignSessionInternal(
    789     const std::string& tag, syncer::SyncChangeList* change_output) {
    790  if (tag == current_machine_tag()) {
    791     LOG(ERROR) << "Attempting to delete local session. This is not currently "
    792                << "supported.";
    793     return;
    794   }
    795 
    796   std::set<int> tab_node_ids_to_delete;
    797   session_tracker_.LookupTabNodeIds(tag, &tab_node_ids_to_delete);
    798   if (!DisassociateForeignSession(tag)) {
    799     // We don't have any data for this session, our work here is done!
    800     return;
    801   }
    802 
    803   // Prepare deletes for the meta-node as well as individual tab nodes.
    804   change_output->push_back(syncer::SyncChange(
    805       FROM_HERE,
    806       SyncChange::ACTION_DELETE,
    807       SyncData::CreateLocalDelete(tag, syncer::SESSIONS)));
    808 
    809   for (std::set<int>::const_iterator it = tab_node_ids_to_delete.begin();
    810        it != tab_node_ids_to_delete.end();
    811        ++it) {
    812     change_output->push_back(syncer::SyncChange(
    813         FROM_HERE,
    814         SyncChange::ACTION_DELETE,
    815         SyncData::CreateLocalDelete(TabNodePool::TabIdToTag(tag, *it),
    816                                     syncer::SESSIONS)));
    817   }
    818   content::NotificationService::current()->Notify(
    819       chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
    820       content::Source<Profile>(profile_),
    821       content::NotificationService::NoDetails());
    822 }
    823 
    824 bool SessionsSyncManager::DisassociateForeignSession(
    825     const std::string& foreign_session_tag) {
    826   if (foreign_session_tag == current_machine_tag()) {
    827     DVLOG(1) << "Local session deleted! Doing nothing until a navigation is "
    828              << "triggered.";
    829     return false;
    830   }
    831   DVLOG(1) << "Disassociating session " << foreign_session_tag;
    832   return session_tracker_.DeleteSession(foreign_session_tag);
    833 }
    834 
    835 // static
    836 GURL SessionsSyncManager::GetCurrentVirtualURL(
    837     const SyncedTabDelegate& tab_delegate) {
    838   const int current_index = tab_delegate.GetCurrentEntryIndex();
    839   const int pending_index = tab_delegate.GetPendingEntryIndex();
    840   const NavigationEntry* current_entry =
    841       (current_index == pending_index) ?
    842       tab_delegate.GetPendingEntry() :
    843       tab_delegate.GetEntryAtIndex(current_index);
    844   return current_entry->GetVirtualURL();
    845 }
    846 
    847 // static
    848 GURL SessionsSyncManager::GetCurrentFaviconURL(
    849     const SyncedTabDelegate& tab_delegate) {
    850   const int current_index = tab_delegate.GetCurrentEntryIndex();
    851   const int pending_index = tab_delegate.GetPendingEntryIndex();
    852   const NavigationEntry* current_entry =
    853       (current_index == pending_index) ?
    854       tab_delegate.GetPendingEntry() :
    855       tab_delegate.GetEntryAtIndex(current_index);
    856   return (current_entry->GetFavicon().valid ?
    857           current_entry->GetFavicon().url :
    858           GURL());
    859 }
    860 
    861 bool SessionsSyncManager::GetForeignSession(
    862     const std::string& tag,
    863     std::vector<const SessionWindow*>* windows) {
    864   return session_tracker_.LookupSessionWindows(tag, windows);
    865 }
    866 
    867 bool SessionsSyncManager::GetForeignTab(
    868     const std::string& tag,
    869     const SessionID::id_type tab_id,
    870     const SessionTab** tab) {
    871   const SessionTab* synced_tab = NULL;
    872   bool success = session_tracker_.LookupSessionTab(tag,
    873                                                    tab_id,
    874                                                    &synced_tab);
    875   if (success)
    876     *tab = synced_tab;
    877   return success;
    878 }
    879 
    880 void SessionsSyncManager::LocalTabDelegateToSpecifics(
    881     const SyncedTabDelegate& tab_delegate,
    882     sync_pb::SessionSpecifics* specifics) {
    883   SessionTab* session_tab = NULL;
    884   session_tab =
    885       session_tracker_.GetTab(current_machine_tag(),
    886                               tab_delegate.GetSessionId(),
    887                               tab_delegate.GetSyncId());
    888   SetSessionTabFromDelegate(tab_delegate, base::Time::Now(), session_tab);
    889   sync_pb::SessionTab tab_s = session_tab->ToSyncData();
    890   specifics->set_session_tag(current_machine_tag_);
    891   specifics->set_tab_node_id(tab_delegate.GetSyncId());
    892   specifics->mutable_tab()->CopyFrom(tab_s);
    893 }
    894 
    895 void SessionsSyncManager::AssociateRestoredPlaceholderTab(
    896     const SyncedTabDelegate& tab_delegate,
    897     SessionID::id_type new_tab_id,
    898     const syncer::SyncDataList& restored_tabs,
    899     syncer::SyncChangeList* change_output) {
    900   DCHECK_NE(tab_delegate.GetSyncId(), TabNodePool::kInvalidTabNodeID);
    901   // Rewrite the tab using |restored_tabs| to retrieve the specifics.
    902   if (restored_tabs.empty()) {
    903     DLOG(WARNING) << "Can't Update tab ID.";
    904     return;
    905   }
    906 
    907   for (syncer::SyncDataList::const_iterator it = restored_tabs.begin();
    908        it != restored_tabs.end();
    909        ++it) {
    910     if (it->GetSpecifics().session().tab_node_id() !=
    911         tab_delegate.GetSyncId()) {
    912       continue;
    913     }
    914 
    915     sync_pb::EntitySpecifics entity;
    916     sync_pb::SessionSpecifics* specifics = entity.mutable_session();
    917     specifics->CopyFrom(it->GetSpecifics().session());
    918     DCHECK(specifics->has_tab());
    919 
    920     // Update tab node pool with the new association.
    921     local_tab_pool_.ReassociateTabNode(tab_delegate.GetSyncId(),
    922                                        new_tab_id);
    923     TabLink* tab_link = new TabLink(tab_delegate.GetSyncId(),
    924                                     &tab_delegate);
    925     local_tab_map_[new_tab_id] = make_linked_ptr<TabLink>(tab_link);
    926 
    927     if (specifics->tab().tab_id() == new_tab_id)
    928       return;
    929 
    930     // The tab_id changed (e.g due to session restore), so update sync.
    931     specifics->mutable_tab()->set_tab_id(new_tab_id);
    932     syncer::SyncData data = syncer::SyncData::CreateLocalData(
    933         TabNodePool::TabIdToTag(current_machine_tag_,
    934                                  specifics->tab_node_id()),
    935         current_session_name_,
    936         entity);
    937     change_output->push_back(syncer::SyncChange(
    938         FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data));
    939     return;
    940   }
    941 }
    942 
    943 // static.
    944 void SessionsSyncManager::SetSessionTabFromDelegate(
    945       const SyncedTabDelegate& tab_delegate,
    946       base::Time mtime,
    947       SessionTab* session_tab) {
    948   DCHECK(session_tab);
    949   session_tab->window_id.set_id(tab_delegate.GetWindowId());
    950   session_tab->tab_id.set_id(tab_delegate.GetSessionId());
    951   session_tab->tab_visual_index = 0;
    952   // Use -1 to indicate that the index hasn't been set properly yet.
    953   session_tab->current_navigation_index = -1;
    954   session_tab->pinned = tab_delegate.IsPinned();
    955   session_tab->extension_app_id = tab_delegate.GetExtensionAppId();
    956   session_tab->user_agent_override.clear();
    957   session_tab->timestamp = mtime;
    958   const int current_index = tab_delegate.GetCurrentEntryIndex();
    959   const int pending_index = tab_delegate.GetPendingEntryIndex();
    960   const int min_index = std::max(0, current_index - kMaxSyncNavigationCount);
    961   const int max_index = std::min(current_index + kMaxSyncNavigationCount,
    962                                  tab_delegate.GetEntryCount());
    963   bool is_supervised = tab_delegate.ProfileIsSupervised();
    964   session_tab->navigations.clear();
    965 
    966   for (int i = min_index; i < max_index; ++i) {
    967     const NavigationEntry* entry = (i == pending_index) ?
    968         tab_delegate.GetPendingEntry() : tab_delegate.GetEntryAtIndex(i);
    969     DCHECK(entry);
    970     if (!entry->GetVirtualURL().is_valid())
    971       continue;
    972 
    973     // Set current_navigation_index to the index in navigations.
    974     if (i == current_index)
    975       session_tab->current_navigation_index = session_tab->navigations.size();
    976 
    977     session_tab->navigations.push_back(
    978         SerializedNavigationEntry::FromNavigationEntry(i, *entry));
    979     if (is_supervised) {
    980       session_tab->navigations.back().set_blocked_state(
    981           SerializedNavigationEntry::STATE_ALLOWED);
    982     }
    983   }
    984 
    985   // If the current navigation is invalid, set the index to the end of the
    986   // navigation array.
    987   if (session_tab->current_navigation_index < 0) {
    988     session_tab->current_navigation_index =
    989         session_tab->navigations.size() - 1;
    990   }
    991 
    992   if (is_supervised) {
    993     const std::vector<const NavigationEntry*>& blocked_navigations =
    994         *tab_delegate.GetBlockedNavigations();
    995     int offset = session_tab->navigations.size();
    996     for (size_t i = 0; i < blocked_navigations.size(); ++i) {
    997       session_tab->navigations.push_back(
    998           SerializedNavigationEntry::FromNavigationEntry(
    999               i + offset, *blocked_navigations[i]));
   1000       session_tab->navigations.back().set_blocked_state(
   1001           SerializedNavigationEntry::STATE_BLOCKED);
   1002       // TODO(bauerb): Add categories
   1003     }
   1004   }
   1005   session_tab->session_storage_persistent_id.clear();
   1006 }
   1007 
   1008 FaviconCache* SessionsSyncManager::GetFaviconCache() {
   1009   return &favicon_cache_;
   1010 }
   1011 
   1012 SyncedWindowDelegatesGetter*
   1013 SessionsSyncManager::GetSyncedWindowDelegatesGetter() const {
   1014   return synced_window_getter_.get();
   1015 }
   1016 
   1017 void SessionsSyncManager::DoGarbageCollection() {
   1018   std::vector<const SyncedSession*> sessions;
   1019   if (!GetAllForeignSessions(&sessions))
   1020     return;  // No foreign sessions.
   1021 
   1022   // Iterate through all the sessions and delete any with age older than
   1023   // |stale_session_threshold_days_|.
   1024   syncer::SyncChangeList changes;
   1025   for (std::vector<const SyncedSession*>::const_iterator iter =
   1026            sessions.begin(); iter != sessions.end(); ++iter) {
   1027     const SyncedSession* session = *iter;
   1028     int session_age_in_days =
   1029         (base::Time::Now() - session->modified_time).InDays();
   1030     std::string session_tag = session->session_tag;
   1031     if (session_age_in_days > 0 &&  // If false, local clock is not trustworty.
   1032         static_cast<size_t>(session_age_in_days) >
   1033             stale_session_threshold_days_) {
   1034       DVLOG(1) << "Found stale session " << session_tag
   1035                << " with age " << session_age_in_days << ", deleting.";
   1036       DeleteForeignSessionInternal(session_tag, &changes);
   1037     }
   1038   }
   1039 
   1040   if (!changes.empty())
   1041     sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
   1042 }
   1043 
   1044 };  // namespace browser_sync
   1045