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