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