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