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