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