Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / sync / sessions / sessions_sync_manager.cc
blob58eda452d8e9881ccc6b0c464bf0c4b2395a4f8a
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/favicon/favicon_service_factory.h"
10 #include "chrome/browser/history/history_service_factory.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/search/search.h"
13 #include "chrome/browser/sync/glue/synced_tab_delegate.h"
14 #include "chrome/browser/sync/glue/synced_window_delegate.h"
15 #include "chrome/browser/sync/sessions/synced_window_delegates_getter.h"
16 #include "chrome/common/url_constants.h"
17 #include "components/sessions/content/content_serialized_navigation_builder.h"
18 #include "components/sync_driver/local_device_info_provider.h"
19 #include "content/public/browser/favicon_status.h"
20 #include "content/public/browser/navigation_entry.h"
21 #include "content/public/browser/notification_details.h"
22 #include "content/public/browser/notification_service.h"
23 #include "content/public/browser/notification_source.h"
24 #include "content/public/common/url_constants.h"
25 #include "sync/api/sync_error.h"
26 #include "sync/api/sync_error_factory.h"
27 #include "sync/api/sync_merge_result.h"
28 #include "sync/api/time.h"
30 using content::NavigationEntry;
31 using sessions::ContentSerializedNavigationBuilder;
32 using sessions::SerializedNavigationEntry;
33 using sync_driver::DeviceInfo;
34 using sync_driver::LocalDeviceInfoProvider;
35 using syncer::SyncChange;
36 using syncer::SyncData;
38 namespace browser_sync {
40 namespace {
42 // Maximum number of favicons to sync.
43 // TODO(zea): pull this from the server.
44 const int kMaxSyncFavicons = 200;
46 // The maximum number of navigations in each direction we care to sync.
47 const int kMaxSyncNavigationCount = 6;
49 // The URL at which the set of synced tabs is displayed. We treat it differently
50 // from all other URL's as accessing it triggers a sync refresh of Sessions.
51 const char kNTPOpenTabSyncURL[] = "chrome://newtab/#open_tabs";
53 // Default number of days without activity after which a session is considered
54 // stale and becomes a candidate for garbage collection.
55 const size_t kDefaultStaleSessionThresholdDays = 14; // 2 weeks.
57 // Comparator function for use with std::sort that will sort tabs by
58 // descending timestamp (i.e., most recent first).
59 bool TabsRecencyComparator(const sessions::SessionTab* t1,
60 const sessions::SessionTab* t2) {
61 return t1->timestamp > t2->timestamp;
64 // Comparator function for use with std::sort that will sort sessions by
65 // descending modified_time (i.e., most recent first).
66 bool SessionsRecencyComparator(const sync_driver::SyncedSession* s1,
67 const sync_driver::SyncedSession* s2) {
68 return s1->modified_time > s2->modified_time;
71 } // namespace
73 // |local_device| is owned by ProfileSyncService, its lifetime exceeds
74 // lifetime of SessionSyncManager.
75 SessionsSyncManager::SessionsSyncManager(
76 Profile* profile,
77 LocalDeviceInfoProvider* local_device,
78 scoped_ptr<LocalSessionEventRouter> router,
79 scoped_ptr<SyncedWindowDelegatesGetter> synced_window_getter)
80 : favicon_cache_(FaviconServiceFactory::GetForProfile(
81 profile,
82 ServiceAccessType::EXPLICIT_ACCESS),
83 HistoryServiceFactory::GetForProfile(
84 profile,
85 ServiceAccessType::EXPLICIT_ACCESS),
86 kMaxSyncFavicons),
87 local_tab_pool_out_of_sync_(true),
88 sync_prefs_(profile->GetPrefs()),
89 profile_(profile),
90 local_device_(local_device),
91 local_session_header_node_id_(TabNodePool::kInvalidTabNodeID),
92 stale_session_threshold_days_(kDefaultStaleSessionThresholdDays),
93 local_event_router_(router.Pass()),
94 synced_window_getter_(synced_window_getter.Pass()) {}
96 LocalSessionEventRouter::~LocalSessionEventRouter() {}
98 SessionsSyncManager::~SessionsSyncManager() {
101 // Returns the GUID-based string that should be used for
102 // |SessionsSyncManager::current_machine_tag_|.
103 static std::string BuildMachineTag(const std::string& cache_guid) {
104 std::string machine_tag = "session_sync";
105 machine_tag.append(cache_guid);
106 return machine_tag;
109 syncer::SyncMergeResult SessionsSyncManager::MergeDataAndStartSyncing(
110 syncer::ModelType type,
111 const syncer::SyncDataList& initial_sync_data,
112 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
113 scoped_ptr<syncer::SyncErrorFactory> error_handler) {
114 syncer::SyncMergeResult merge_result(type);
115 DCHECK(session_tracker_.Empty());
116 DCHECK_EQ(0U, local_tab_pool_.Capacity());
118 error_handler_ = error_handler.Pass();
119 sync_processor_ = sync_processor.Pass();
121 local_session_header_node_id_ = TabNodePool::kInvalidTabNodeID;
123 // Make sure we have a machine tag. We do this now (versus earlier) as it's
124 // a conveniently safe time to assert sync is ready and the cache_guid is
125 // initialized.
126 if (current_machine_tag_.empty()) {
127 InitializeCurrentMachineTag();
130 // SessionDataTypeController ensures that the local device info
131 // is available before activating this datatype.
132 DCHECK(local_device_);
133 const DeviceInfo* local_device_info = local_device_->GetLocalDeviceInfo();
134 if (local_device_info) {
135 current_session_name_ = local_device_info->client_name();
136 } else {
137 merge_result.set_error(error_handler_->CreateAndUploadError(
138 FROM_HERE,
139 "Failed to get local device info."));
140 return merge_result;
143 session_tracker_.SetLocalSessionTag(current_machine_tag_);
145 syncer::SyncChangeList new_changes;
147 // First, we iterate over sync data to update our session_tracker_.
148 syncer::SyncDataList restored_tabs;
149 if (!InitFromSyncModel(initial_sync_data, &restored_tabs, &new_changes)) {
150 // The sync db didn't have a header node for us. Create one.
151 sync_pb::EntitySpecifics specifics;
152 sync_pb::SessionSpecifics* base_specifics = specifics.mutable_session();
153 base_specifics->set_session_tag(current_machine_tag());
154 sync_pb::SessionHeader* header_s = base_specifics->mutable_header();
155 header_s->set_client_name(current_session_name_);
156 header_s->set_device_type(local_device_info->device_type());
157 syncer::SyncData data = syncer::SyncData::CreateLocalData(
158 current_machine_tag(), current_session_name_, specifics);
159 new_changes.push_back(syncer::SyncChange(
160 FROM_HERE, syncer::SyncChange::ACTION_ADD, data));
163 #if defined(OS_ANDROID)
164 std::string sync_machine_tag(BuildMachineTag(
165 local_device_->GetLocalSyncCacheGUID()));
166 if (current_machine_tag_.compare(sync_machine_tag) != 0)
167 DeleteForeignSessionInternal(sync_machine_tag, &new_changes);
168 #endif
170 // Check if anything has changed on the local client side.
171 AssociateWindows(RELOAD_TABS, restored_tabs, &new_changes);
172 local_tab_pool_out_of_sync_ = false;
174 merge_result.set_error(
175 sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes));
177 local_event_router_->StartRoutingTo(this);
178 return merge_result;
181 void SessionsSyncManager::AssociateWindows(
182 ReloadTabsOption option,
183 const syncer::SyncDataList& restored_tabs,
184 syncer::SyncChangeList* change_output) {
185 const std::string local_tag = current_machine_tag();
186 sync_pb::SessionSpecifics specifics;
187 specifics.set_session_tag(local_tag);
188 sync_pb::SessionHeader* header_s = specifics.mutable_header();
189 sync_driver::SyncedSession* current_session =
190 session_tracker_.GetSession(local_tag);
191 current_session->modified_time = base::Time::Now();
192 header_s->set_client_name(current_session_name_);
193 // SessionDataTypeController ensures that the local device info
194 // is available before activating this datatype.
195 DCHECK(local_device_);
196 const DeviceInfo* local_device_info = local_device_->GetLocalDeviceInfo();
197 header_s->set_device_type(local_device_info->device_type());
199 session_tracker_.ResetSessionTracking(local_tag);
200 std::set<const SyncedWindowDelegate*> windows =
201 synced_window_getter_->GetSyncedWindowDelegates();
203 for (std::set<const SyncedWindowDelegate*>::const_iterator i =
204 windows.begin(); i != windows.end(); ++i) {
205 // Make sure the window has tabs and a viewable window. The viewable window
206 // check is necessary because, for example, when a browser is closed the
207 // destructor is not necessarily run immediately. This means its possible
208 // for us to get a handle to a browser that is about to be removed. If
209 // the tab count is 0 or the window is NULL, the browser is about to be
210 // deleted, so we ignore it.
211 if ((*i)->ShouldSync() && (*i)->GetTabCount() && (*i)->HasWindow()) {
212 sync_pb::SessionWindow window_s;
213 SessionID::id_type window_id = (*i)->GetSessionId();
214 DVLOG(1) << "Associating window " << window_id << " with "
215 << (*i)->GetTabCount() << " tabs.";
216 window_s.set_window_id(window_id);
217 // Note: We don't bother to set selected tab index anymore. We still
218 // consume it when receiving foreign sessions, as reading it is free, but
219 // it triggers too many sync cycles with too little value to make setting
220 // it worthwhile.
221 if ((*i)->IsTypeTabbed()) {
222 window_s.set_browser_type(
223 sync_pb::SessionWindow_BrowserType_TYPE_TABBED);
224 } else {
225 window_s.set_browser_type(
226 sync_pb::SessionWindow_BrowserType_TYPE_POPUP);
229 bool found_tabs = false;
230 for (int j = 0; j < (*i)->GetTabCount(); ++j) {
231 SessionID::id_type tab_id = (*i)->GetTabIdAt(j);
232 SyncedTabDelegate* synced_tab = (*i)->GetTabAt(j);
234 // GetTabAt can return a null tab; in that case just skip it.
235 if (!synced_tab)
236 continue;
238 if (!synced_tab->HasWebContents()) {
239 // For tabs without WebContents update the |tab_id|, as it could have
240 // changed after a session restore.
241 // Note: We cannot check if a tab is valid if it has no WebContents.
242 // We assume any such tab is valid and leave the contents of
243 // corresponding sync node unchanged.
244 if (synced_tab->GetSyncId() > TabNodePool::kInvalidTabNodeID &&
245 tab_id > TabNodePool::kInvalidTabID) {
246 AssociateRestoredPlaceholderTab(*synced_tab, tab_id,
247 restored_tabs, change_output);
248 found_tabs = true;
249 window_s.add_tab(tab_id);
251 continue;
254 if (RELOAD_TABS == option)
255 AssociateTab(synced_tab, change_output);
257 // If the tab is valid, it would have been added to the tracker either
258 // by the above AssociateTab call (at association time), or by the
259 // change processor calling AssociateTab for all modified tabs.
260 // Therefore, we can key whether this window has valid tabs based on
261 // the tab's presence in the tracker.
262 const sessions::SessionTab* tab = NULL;
263 if (session_tracker_.LookupSessionTab(local_tag, tab_id, &tab)) {
264 found_tabs = true;
265 window_s.add_tab(tab_id);
268 if (found_tabs) {
269 sync_pb::SessionWindow* header_window = header_s->add_window();
270 *header_window = window_s;
272 // Update this window's representation in the synced session tracker.
273 session_tracker_.PutWindowInSession(local_tag, window_id);
274 BuildSyncedSessionFromSpecifics(local_tag,
275 window_s,
276 current_session->modified_time,
277 current_session->windows[window_id]);
281 local_tab_pool_.DeleteUnassociatedTabNodes(change_output);
282 session_tracker_.CleanupSession(local_tag);
284 // Always update the header. Sync takes care of dropping this update
285 // if the entity specifics are identical (i.e windows, client name did
286 // not change).
287 sync_pb::EntitySpecifics entity;
288 entity.mutable_session()->CopyFrom(specifics);
289 syncer::SyncData data = syncer::SyncData::CreateLocalData(
290 current_machine_tag(), current_session_name_, entity);
291 change_output->push_back(syncer::SyncChange(
292 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data));
295 void SessionsSyncManager::AssociateTab(SyncedTabDelegate* const tab,
296 syncer::SyncChangeList* change_output) {
297 DCHECK(tab->HasWebContents());
298 SessionID::id_type tab_id = tab->GetSessionId();
299 if (tab->profile() != profile_)
300 return;
302 if (tab->IsBeingDestroyed()) {
303 // This tab is closing.
304 TabLinksMap::iterator tab_iter = local_tab_map_.find(tab_id);
305 if (tab_iter == local_tab_map_.end()) {
306 // We aren't tracking this tab (for example, sync setting page).
307 return;
309 local_tab_pool_.FreeTabNode(tab_iter->second->tab_node_id(),
310 change_output);
311 local_tab_map_.erase(tab_iter);
312 return;
315 if (!tab->ShouldSync())
316 return;
318 TabLinksMap::iterator local_tab_map_iter = local_tab_map_.find(tab_id);
319 TabLink* tab_link = NULL;
321 if (local_tab_map_iter == local_tab_map_.end()) {
322 int tab_node_id = tab->GetSyncId();
323 // If there is an old sync node for the tab, reuse it. If this is a new
324 // tab, get a sync node for it.
325 if (!local_tab_pool_.IsUnassociatedTabNode(tab_node_id)) {
326 tab_node_id = local_tab_pool_.GetFreeTabNode(change_output);
327 tab->SetSyncId(tab_node_id);
329 local_tab_pool_.AssociateTabNode(tab_node_id, tab_id);
330 tab_link = new TabLink(tab_node_id, tab);
331 local_tab_map_[tab_id] = make_linked_ptr<TabLink>(tab_link);
332 } else {
333 // This tab is already associated with a sync node, reuse it.
334 // Note: on some platforms the tab object may have changed, so we ensure
335 // the tab link is up to date.
336 tab_link = local_tab_map_iter->second.get();
337 local_tab_map_iter->second->set_tab(tab);
339 DCHECK(tab_link);
340 DCHECK_NE(tab_link->tab_node_id(), TabNodePool::kInvalidTabNodeID);
341 DVLOG(1) << "Reloading tab " << tab_id << " from window "
342 << tab->GetWindowId();
344 // Write to sync model.
345 sync_pb::EntitySpecifics specifics;
346 LocalTabDelegateToSpecifics(*tab, specifics.mutable_session());
347 syncer::SyncData data = syncer::SyncData::CreateLocalData(
348 TabNodePool::TabIdToTag(current_machine_tag_,
349 tab_link->tab_node_id()),
350 current_session_name_,
351 specifics);
352 change_output->push_back(syncer::SyncChange(
353 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data));
355 const GURL new_url = GetCurrentVirtualURL(*tab);
356 if (new_url != tab_link->url()) {
357 tab_link->set_url(new_url);
358 favicon_cache_.OnFaviconVisited(new_url, GetCurrentFaviconURL(*tab));
361 session_tracker_.GetSession(current_machine_tag())->modified_time =
362 base::Time::Now();
365 void SessionsSyncManager::RebuildAssociations() {
366 syncer::SyncDataList data(
367 sync_processor_->GetAllSyncData(syncer::SESSIONS));
368 scoped_ptr<syncer::SyncErrorFactory> error_handler(error_handler_.Pass());
369 scoped_ptr<syncer::SyncChangeProcessor> processor(sync_processor_.Pass());
371 StopSyncing(syncer::SESSIONS);
372 MergeDataAndStartSyncing(
373 syncer::SESSIONS, data, processor.Pass(), error_handler.Pass());
376 bool SessionsSyncManager::IsValidSessionHeader(
377 const sync_pb::SessionHeader& header) {
378 // Verify that tab IDs appear only once within a session.
379 // Intended to prevent http://crbug.com/360822.
380 std::set<int> session_tab_ids;
381 for (int i = 0; i < header.window_size(); ++i) {
382 const sync_pb::SessionWindow& window = header.window(i);
383 for (int j = 0; j < window.tab_size(); ++j) {
384 const int tab_id = window.tab(j);
385 bool success = session_tab_ids.insert(tab_id).second;
386 if (!success)
387 return false;
391 return true;
394 void SessionsSyncManager::OnLocalTabModified(SyncedTabDelegate* modified_tab) {
395 const content::NavigationEntry* entry = modified_tab->GetActiveEntry();
396 if (!modified_tab->IsBeingDestroyed() &&
397 entry &&
398 entry->GetVirtualURL().is_valid() &&
399 entry->GetVirtualURL().spec() == kNTPOpenTabSyncURL) {
400 DVLOG(1) << "Triggering sync refresh for sessions datatype.";
401 const syncer::ModelTypeSet types(syncer::SESSIONS);
402 content::NotificationService::current()->Notify(
403 chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
404 content::Source<Profile>(profile_),
405 content::Details<const syncer::ModelTypeSet>(&types));
408 if (local_tab_pool_out_of_sync_) {
409 // If our tab pool is corrupt, pay the price of a full re-association to
410 // fix things up. This takes care of the new tab modification as well.
411 RebuildAssociations();
412 DCHECK(!local_tab_pool_out_of_sync_);
413 return;
416 syncer::SyncChangeList changes;
417 // Associate tabs first so the synced session tracker is aware of them.
418 AssociateTab(modified_tab, &changes);
419 // Note, we always associate windows because it's possible a tab became
420 // "interesting" by going to a valid URL, in which case it needs to be added
421 // to the window's tab information.
422 AssociateWindows(DONT_RELOAD_TABS, syncer::SyncDataList(), &changes);
423 sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
426 void SessionsSyncManager::OnFaviconsChanged(
427 const std::set<GURL>& page_urls,
428 const GURL& /* icon_url */) {
429 // TODO(zea): consider a separate container for tabs with outstanding favicon
430 // loads so we don't have to iterate through all tabs comparing urls.
431 for (const GURL& page_url : page_urls) {
432 for (TabLinksMap::iterator tab_iter = local_tab_map_.begin();
433 tab_iter != local_tab_map_.end();
434 ++tab_iter) {
435 if (tab_iter->second->url() == page_url)
436 favicon_cache_.OnPageFaviconUpdated(page_url);
441 void SessionsSyncManager::StopSyncing(syncer::ModelType type) {
442 local_event_router_->Stop();
443 sync_processor_.reset(NULL);
444 error_handler_.reset();
445 session_tracker_.Clear();
446 local_tab_map_.clear();
447 local_tab_pool_.Clear();
448 current_machine_tag_.clear();
449 current_session_name_.clear();
450 local_session_header_node_id_ = TabNodePool::kInvalidTabNodeID;
453 syncer::SyncDataList SessionsSyncManager::GetAllSyncData(
454 syncer::ModelType type) const {
455 syncer::SyncDataList list;
456 const sync_driver::SyncedSession* session = NULL;
457 if (!session_tracker_.LookupLocalSession(&session))
458 return syncer::SyncDataList();
460 // First construct the header node.
461 sync_pb::EntitySpecifics header_entity;
462 header_entity.mutable_session()->set_session_tag(current_machine_tag());
463 sync_pb::SessionHeader* header_specifics =
464 header_entity.mutable_session()->mutable_header();
465 header_specifics->MergeFrom(session->ToSessionHeader());
466 syncer::SyncData data = syncer::SyncData::CreateLocalData(
467 current_machine_tag(), current_session_name_, header_entity);
468 list.push_back(data);
470 sync_driver::SyncedSession::SyncedWindowMap::const_iterator win_iter;
471 for (win_iter = session->windows.begin();
472 win_iter != session->windows.end(); ++win_iter) {
473 std::vector<sessions::SessionTab*>::const_iterator tabs_iter;
474 for (tabs_iter = win_iter->second->tabs.begin();
475 tabs_iter != win_iter->second->tabs.end(); ++tabs_iter) {
476 sync_pb::EntitySpecifics entity;
477 sync_pb::SessionSpecifics* specifics = entity.mutable_session();
478 specifics->mutable_tab()->MergeFrom((*tabs_iter)->ToSyncData());
479 specifics->set_session_tag(current_machine_tag_);
481 TabLinksMap::const_iterator tab_map_iter = local_tab_map_.find(
482 (*tabs_iter)->tab_id.id());
483 DCHECK(tab_map_iter != local_tab_map_.end());
484 specifics->set_tab_node_id(tab_map_iter->second->tab_node_id());
485 syncer::SyncData data = syncer::SyncData::CreateLocalData(
486 TabNodePool::TabIdToTag(current_machine_tag_,
487 specifics->tab_node_id()),
488 current_session_name_,
489 entity);
490 list.push_back(data);
493 return list;
496 bool SessionsSyncManager::GetLocalSession(
497 const sync_driver::SyncedSession** local_session) {
498 if (current_machine_tag_.empty())
499 return false;
500 *local_session = session_tracker_.GetSession(current_machine_tag());
501 return true;
504 syncer::SyncError SessionsSyncManager::ProcessSyncChanges(
505 const tracked_objects::Location& from_here,
506 const syncer::SyncChangeList& change_list) {
507 if (!sync_processor_.get()) {
508 syncer::SyncError error(FROM_HERE,
509 syncer::SyncError::DATATYPE_ERROR,
510 "Models not yet associated.",
511 syncer::SESSIONS);
512 return error;
515 for (syncer::SyncChangeList::const_iterator it = change_list.begin();
516 it != change_list.end(); ++it) {
517 DCHECK(it->IsValid());
518 DCHECK(it->sync_data().GetSpecifics().has_session());
519 const sync_pb::SessionSpecifics& session =
520 it->sync_data().GetSpecifics().session();
521 switch (it->change_type()) {
522 case syncer::SyncChange::ACTION_DELETE:
523 // Deletions are all or nothing (since we only ever delete entire
524 // sessions). Therefore we don't care if it's a tab node or meta node,
525 // and just ensure we've disassociated.
526 if (current_machine_tag() == session.session_tag()) {
527 // Another client has attempted to delete our local data (possibly by
528 // error or a clock is inaccurate). Just ignore the deletion for now
529 // to avoid any possible ping-pong delete/reassociate sequence, but
530 // remember that this happened as our TabNodePool is inconsistent.
531 local_tab_pool_out_of_sync_ = true;
532 LOG(WARNING) << "Local session data deleted. Ignoring until next "
533 << "local navigation event.";
534 } else if (session.has_header()) {
535 // Disassociate only when header node is deleted. For tab node
536 // deletions, the header node will be updated and foreign tab will
537 // get deleted.
538 DisassociateForeignSession(session.session_tag());
540 continue;
541 case syncer::SyncChange::ACTION_ADD:
542 case syncer::SyncChange::ACTION_UPDATE:
543 if (current_machine_tag() == session.session_tag()) {
544 // We should only ever receive a change to our own machine's session
545 // info if encryption was turned on. In that case, the data is still
546 // the same, so we can ignore.
547 LOG(WARNING) << "Dropping modification to local session.";
548 return syncer::SyncError();
550 UpdateTrackerWithForeignSession(
551 session, syncer::SyncDataRemote(it->sync_data()).GetModifiedTime());
552 break;
553 default:
554 NOTREACHED() << "Processing sync changes failed, unknown change type.";
558 content::NotificationService::current()->Notify(
559 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
560 content::Source<Profile>(profile_),
561 content::NotificationService::NoDetails());
562 return syncer::SyncError();
565 syncer::SyncChange SessionsSyncManager::TombstoneTab(
566 const sync_pb::SessionSpecifics& tab) {
567 if (!tab.has_tab_node_id()) {
568 LOG(WARNING) << "Old sessions node without tab node id; can't tombstone.";
569 return syncer::SyncChange();
570 } else {
571 return syncer::SyncChange(
572 FROM_HERE,
573 SyncChange::ACTION_DELETE,
574 SyncData::CreateLocalDelete(
575 TabNodePool::TabIdToTag(current_machine_tag(),
576 tab.tab_node_id()),
577 syncer::SESSIONS));
581 bool SessionsSyncManager::GetAllForeignSessions(
582 std::vector<const sync_driver::SyncedSession*>* sessions) {
583 if (!session_tracker_.LookupAllForeignSessions(sessions))
584 return false;
585 std::sort(sessions->begin(), sessions->end(), SessionsRecencyComparator);
586 return true;
589 bool SessionsSyncManager::InitFromSyncModel(
590 const syncer::SyncDataList& sync_data,
591 syncer::SyncDataList* restored_tabs,
592 syncer::SyncChangeList* new_changes) {
593 bool found_current_header = false;
594 for (syncer::SyncDataList::const_iterator it = sync_data.begin();
595 it != sync_data.end();
596 ++it) {
597 const syncer::SyncData& data = *it;
598 DCHECK(data.GetSpecifics().has_session());
599 const sync_pb::SessionSpecifics& specifics = data.GetSpecifics().session();
600 if (specifics.session_tag().empty() ||
601 (specifics.has_tab() && (!specifics.has_tab_node_id() ||
602 !specifics.tab().has_tab_id()))) {
603 syncer::SyncChange tombstone(TombstoneTab(specifics));
604 if (tombstone.IsValid())
605 new_changes->push_back(tombstone);
606 } else if (specifics.session_tag() != current_machine_tag()) {
607 UpdateTrackerWithForeignSession(
608 specifics, syncer::SyncDataRemote(data).GetModifiedTime());
609 } else {
610 // This is previously stored local session information.
611 if (specifics.has_header() && !found_current_header) {
612 // This is our previous header node, reuse it.
613 found_current_header = true;
614 if (specifics.header().has_client_name())
615 current_session_name_ = specifics.header().client_name();
616 } else {
617 if (specifics.has_header() || !specifics.has_tab()) {
618 LOG(WARNING) << "Found more than one session header node with local "
619 << "tag.";
620 syncer::SyncChange tombstone(TombstoneTab(specifics));
621 if (tombstone.IsValid())
622 new_changes->push_back(tombstone);
623 } else {
624 // This is a valid old tab node, add it to the pool so it can be
625 // reused for reassociation.
626 local_tab_pool_.AddTabNode(specifics.tab_node_id());
627 restored_tabs->push_back(*it);
632 return found_current_header;
635 void SessionsSyncManager::UpdateTrackerWithForeignSession(
636 const sync_pb::SessionSpecifics& specifics,
637 const base::Time& modification_time) {
638 std::string foreign_session_tag = specifics.session_tag();
639 DCHECK_NE(foreign_session_tag, current_machine_tag());
641 sync_driver::SyncedSession* foreign_session =
642 session_tracker_.GetSession(foreign_session_tag);
643 if (specifics.has_header()) {
644 // Read in the header data for this foreign session.
645 // Header data contains window information and ordered tab id's for each
646 // window.
648 if (!IsValidSessionHeader(specifics.header())) {
649 LOG(WARNING) << "Ignoring foreign session node with invalid header "
650 << "and tag " << foreign_session_tag << ".";
651 return;
654 // Load (or create) the SyncedSession object for this client.
655 const sync_pb::SessionHeader& header = specifics.header();
656 PopulateSessionHeaderFromSpecifics(header,
657 modification_time,
658 foreign_session);
660 // Reset the tab/window tracking for this session (must do this before
661 // we start calling PutWindowInSession and PutTabInWindow so that all
662 // unused tabs/windows get cleared by the CleanupSession(...) call).
663 session_tracker_.ResetSessionTracking(foreign_session_tag);
665 // Process all the windows and their tab information.
666 int num_windows = header.window_size();
667 DVLOG(1) << "Associating " << foreign_session_tag << " with "
668 << num_windows << " windows.";
670 for (int i = 0; i < num_windows; ++i) {
671 const sync_pb::SessionWindow& window_s = header.window(i);
672 SessionID::id_type window_id = window_s.window_id();
673 session_tracker_.PutWindowInSession(foreign_session_tag,
674 window_id);
675 BuildSyncedSessionFromSpecifics(foreign_session_tag,
676 window_s,
677 modification_time,
678 foreign_session->windows[window_id]);
680 // Delete any closed windows and unused tabs as necessary.
681 session_tracker_.CleanupSession(foreign_session_tag);
682 } else if (specifics.has_tab()) {
683 const sync_pb::SessionTab& tab_s = specifics.tab();
684 SessionID::id_type tab_id = tab_s.tab_id();
686 const sessions::SessionTab* existing_tab;
687 if (session_tracker_.LookupSessionTab(
688 foreign_session_tag, tab_id, &existing_tab) &&
689 existing_tab->timestamp > modification_time) {
690 DVLOG(1) << "Ignoring " << foreign_session_tag << "'s session tab "
691 << tab_id << " with earlier modification time";
692 return;
695 sessions::SessionTab* tab =
696 session_tracker_.GetTab(foreign_session_tag,
697 tab_id,
698 specifics.tab_node_id());
700 // Update SessionTab based on protobuf.
701 tab->SetFromSyncData(tab_s, modification_time);
703 // If a favicon or favicon urls are present, load the URLs and visit
704 // times into the in-memory favicon cache.
705 RefreshFaviconVisitTimesFromForeignTab(tab_s, modification_time);
707 // Update the last modified time.
708 if (foreign_session->modified_time < modification_time)
709 foreign_session->modified_time = modification_time;
710 } else {
711 LOG(WARNING) << "Ignoring foreign session node with missing header/tab "
712 << "fields and tag " << foreign_session_tag << ".";
716 void SessionsSyncManager::InitializeCurrentMachineTag() {
717 DCHECK(current_machine_tag_.empty());
718 std::string persisted_guid;
719 persisted_guid = sync_prefs_.GetSyncSessionsGUID();
720 if (!persisted_guid.empty()) {
721 current_machine_tag_ = persisted_guid;
722 DVLOG(1) << "Restoring persisted session sync guid: " << persisted_guid;
723 } else {
724 DCHECK(local_device_);
725 std::string cache_guid = local_device_->GetLocalSyncCacheGUID();
726 DCHECK(!cache_guid.empty());
727 current_machine_tag_ = BuildMachineTag(cache_guid);
728 DVLOG(1) << "Creating session sync guid: " << current_machine_tag_;
729 sync_prefs_.SetSyncSessionsGUID(current_machine_tag_);
732 local_tab_pool_.SetMachineTag(current_machine_tag_);
735 // static
736 void SessionsSyncManager::PopulateSessionHeaderFromSpecifics(
737 const sync_pb::SessionHeader& header_specifics,
738 base::Time mtime,
739 sync_driver::SyncedSession* session_header) {
740 if (header_specifics.has_client_name())
741 session_header->session_name = header_specifics.client_name();
742 if (header_specifics.has_device_type()) {
743 switch (header_specifics.device_type()) {
744 case sync_pb::SyncEnums_DeviceType_TYPE_WIN:
745 session_header->device_type = sync_driver::SyncedSession::TYPE_WIN;
746 break;
747 case sync_pb::SyncEnums_DeviceType_TYPE_MAC:
748 session_header->device_type = sync_driver::SyncedSession::TYPE_MACOSX;
749 break;
750 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX:
751 session_header->device_type = sync_driver::SyncedSession::TYPE_LINUX;
752 break;
753 case sync_pb::SyncEnums_DeviceType_TYPE_CROS:
754 session_header->device_type = sync_driver::SyncedSession::TYPE_CHROMEOS;
755 break;
756 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE:
757 session_header->device_type = sync_driver::SyncedSession::TYPE_PHONE;
758 break;
759 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET:
760 session_header->device_type = sync_driver::SyncedSession::TYPE_TABLET;
761 break;
762 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER:
763 // Intentionally fall-through
764 default:
765 session_header->device_type = sync_driver::SyncedSession::TYPE_OTHER;
766 break;
769 session_header->modified_time = mtime;
772 // static
773 void SessionsSyncManager::BuildSyncedSessionFromSpecifics(
774 const std::string& session_tag,
775 const sync_pb::SessionWindow& specifics,
776 base::Time mtime,
777 sessions::SessionWindow* session_window) {
778 if (specifics.has_window_id())
779 session_window->window_id.set_id(specifics.window_id());
780 if (specifics.has_selected_tab_index())
781 session_window->selected_tab_index = specifics.selected_tab_index();
782 if (specifics.has_browser_type()) {
783 // TODO(skuhne): Sync data writes |BrowserType| not
784 // |SessionWindow::WindowType|. This should get changed.
785 if (specifics.browser_type() ==
786 sync_pb::SessionWindow_BrowserType_TYPE_TABBED) {
787 session_window->type = sessions::SessionWindow::TYPE_TABBED;
788 } else {
789 session_window->type = sessions::SessionWindow::TYPE_POPUP;
792 session_window->timestamp = mtime;
793 session_window->tabs.resize(specifics.tab_size(), NULL);
794 for (int i = 0; i < specifics.tab_size(); i++) {
795 SessionID::id_type tab_id = specifics.tab(i);
796 session_tracker_.PutTabInWindow(session_tag,
797 session_window->window_id.id(),
798 tab_id,
803 void SessionsSyncManager::RefreshFaviconVisitTimesFromForeignTab(
804 const sync_pb::SessionTab& tab, const base::Time& modification_time) {
805 // First go through and iterate over all the navigations, checking if any
806 // have valid favicon urls.
807 for (int i = 0; i < tab.navigation_size(); ++i) {
808 if (!tab.navigation(i).favicon_url().empty()) {
809 const std::string& page_url = tab.navigation(i).virtual_url();
810 const std::string& favicon_url = tab.navigation(i).favicon_url();
811 favicon_cache_.OnReceivedSyncFavicon(GURL(page_url),
812 GURL(favicon_url),
813 std::string(),
814 syncer::TimeToProtoTime(
815 modification_time));
820 bool SessionsSyncManager::GetSyncedFaviconForPageURL(
821 const std::string& page_url,
822 scoped_refptr<base::RefCountedMemory>* favicon_png) const {
823 return favicon_cache_.GetSyncedFaviconForPageURL(GURL(page_url), favicon_png);
826 void SessionsSyncManager::DeleteForeignSession(const std::string& tag) {
827 syncer::SyncChangeList changes;
828 DeleteForeignSessionInternal(tag, &changes);
829 sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
832 void SessionsSyncManager::DeleteForeignSessionInternal(
833 const std::string& tag, syncer::SyncChangeList* change_output) {
834 if (tag == current_machine_tag()) {
835 LOG(ERROR) << "Attempting to delete local session. This is not currently "
836 << "supported.";
837 return;
840 std::set<int> tab_node_ids_to_delete;
841 session_tracker_.LookupTabNodeIds(tag, &tab_node_ids_to_delete);
842 if (!DisassociateForeignSession(tag)) {
843 // We don't have any data for this session, our work here is done!
844 return;
847 // Prepare deletes for the meta-node as well as individual tab nodes.
848 change_output->push_back(syncer::SyncChange(
849 FROM_HERE,
850 SyncChange::ACTION_DELETE,
851 SyncData::CreateLocalDelete(tag, syncer::SESSIONS)));
853 for (std::set<int>::const_iterator it = tab_node_ids_to_delete.begin();
854 it != tab_node_ids_to_delete.end();
855 ++it) {
856 change_output->push_back(syncer::SyncChange(
857 FROM_HERE,
858 SyncChange::ACTION_DELETE,
859 SyncData::CreateLocalDelete(TabNodePool::TabIdToTag(tag, *it),
860 syncer::SESSIONS)));
862 content::NotificationService::current()->Notify(
863 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
864 content::Source<Profile>(profile_),
865 content::NotificationService::NoDetails());
868 bool SessionsSyncManager::DisassociateForeignSession(
869 const std::string& foreign_session_tag) {
870 if (foreign_session_tag == current_machine_tag()) {
871 DVLOG(1) << "Local session deleted! Doing nothing until a navigation is "
872 << "triggered.";
873 return false;
875 DVLOG(1) << "Disassociating session " << foreign_session_tag;
876 return session_tracker_.DeleteSession(foreign_session_tag);
879 // static
880 GURL SessionsSyncManager::GetCurrentVirtualURL(
881 const SyncedTabDelegate& tab_delegate) {
882 const NavigationEntry* current_entry =
883 tab_delegate.GetCurrentEntryMaybePending();
884 return current_entry->GetVirtualURL();
887 // static
888 GURL SessionsSyncManager::GetCurrentFaviconURL(
889 const SyncedTabDelegate& tab_delegate) {
890 const NavigationEntry* current_entry =
891 tab_delegate.GetCurrentEntryMaybePending();
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 min_index = std::max(0, current_index - kMaxSyncNavigationCount);
1025 const int max_index = std::min(current_index + kMaxSyncNavigationCount,
1026 tab_delegate.GetEntryCount());
1027 bool is_supervised = tab_delegate.ProfileIsSupervised();
1028 session_tab->navigations.clear();
1030 for (int i = min_index; i < max_index; ++i) {
1031 const NavigationEntry* entry = tab_delegate.GetEntryAtIndexMaybePending(i);
1032 DCHECK(entry);
1033 if (!entry->GetVirtualURL().is_valid())
1034 continue;
1036 // Set current_navigation_index to the index in navigations.
1037 if (i == current_index)
1038 session_tab->current_navigation_index = session_tab->navigations.size();
1040 session_tab->navigations.push_back(
1041 ContentSerializedNavigationBuilder::FromNavigationEntry(i, *entry));
1042 if (is_supervised) {
1043 session_tab->navigations.back().set_blocked_state(
1044 SerializedNavigationEntry::STATE_ALLOWED);
1048 // If the current navigation is invalid, set the index to the end of the
1049 // navigation array.
1050 if (session_tab->current_navigation_index < 0) {
1051 session_tab->current_navigation_index =
1052 session_tab->navigations.size() - 1;
1055 if (is_supervised) {
1056 const std::vector<const NavigationEntry*>& blocked_navigations =
1057 *tab_delegate.GetBlockedNavigations();
1058 int offset = session_tab->navigations.size();
1059 for (size_t i = 0; i < blocked_navigations.size(); ++i) {
1060 session_tab->navigations.push_back(
1061 ContentSerializedNavigationBuilder::FromNavigationEntry(
1062 i + offset, *blocked_navigations[i]));
1063 session_tab->navigations.back().set_blocked_state(
1064 SerializedNavigationEntry::STATE_BLOCKED);
1065 // TODO(bauerb): Add categories
1068 session_tab->session_storage_persistent_id.clear();
1071 // static
1072 void SessionsSyncManager::SetVariationIds(sessions::SessionTab* session_tab) {
1073 base::FieldTrial::ActiveGroups active_groups;
1074 base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
1075 for (const base::FieldTrial::ActiveGroup& group : active_groups) {
1076 const variations::VariationID id =
1077 variations::GetGoogleVariationID(variations::CHROME_SYNC_SERVICE,
1078 group.trial_name, group.group_name);
1079 if (id != variations::EMPTY_ID)
1080 session_tab->variation_ids.push_back(id);
1084 FaviconCache* SessionsSyncManager::GetFaviconCache() {
1085 return &favicon_cache_;
1088 SyncedWindowDelegatesGetter*
1089 SessionsSyncManager::GetSyncedWindowDelegatesGetter() const {
1090 return synced_window_getter_.get();
1093 void SessionsSyncManager::DoGarbageCollection() {
1094 std::vector<const sync_driver::SyncedSession*> sessions;
1095 if (!GetAllForeignSessions(&sessions))
1096 return; // No foreign sessions.
1098 // Iterate through all the sessions and delete any with age older than
1099 // |stale_session_threshold_days_|.
1100 syncer::SyncChangeList changes;
1101 for (std::vector<const sync_driver::SyncedSession*>::const_iterator iter =
1102 sessions.begin();
1103 iter != sessions.end(); ++iter) {
1104 const sync_driver::SyncedSession* session = *iter;
1105 int session_age_in_days =
1106 (base::Time::Now() - session->modified_time).InDays();
1107 std::string session_tag = session->session_tag;
1108 if (session_age_in_days > 0 && // If false, local clock is not trustworty.
1109 static_cast<size_t>(session_age_in_days) >
1110 stale_session_threshold_days_) {
1111 DVLOG(1) << "Found stale session " << session_tag
1112 << " with age " << session_age_in_days << ", deleting.";
1113 DeleteForeignSessionInternal(session_tag, &changes);
1117 if (!changes.empty())
1118 sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
1121 }; // namespace browser_sync