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