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