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