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