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