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