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