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 "chrome/browser/chrome_notification_types.h"
8 #include "chrome/browser/profiles/profile.h"
9 #include "chrome/browser/sync/glue/synced_tab_delegate.h"
10 #include "chrome/browser/sync/glue/synced_window_delegate.h"
11 #include "chrome/browser/sync/sessions/sessions_util.h"
12 #include "chrome/browser/sync/sessions/synced_window_delegates_getter.h"
13 #include "chrome/common/url_constants.h"
14 #include "content/public/browser/favicon_status.h"
15 #include "content/public/browser/navigation_entry.h"
16 #include "content/public/browser/notification_details.h"
17 #include "content/public/browser/notification_service.h"
18 #include "content/public/browser/notification_source.h"
19 #include "content/public/common/url_constants.h"
20 #include "sync/api/sync_error.h"
21 #include "sync/api/sync_error_factory.h"
22 #include "sync/api/sync_merge_result.h"
23 #include "sync/api/time.h"
25 using content::NavigationEntry;
26 using sessions::SerializedNavigationEntry;
27 using syncer::SyncChange;
28 using syncer::SyncData;
30 namespace browser_sync {
32 // Maximum number of favicons to sync.
33 // TODO(zea): pull this from the server.
34 static const int kMaxSyncFavicons = 200;
36 // The maximum number of navigations in each direction we care to sync.
37 static const int kMaxSyncNavigationCount = 6;
39 // The URL at which the set of synced tabs is displayed. We treat it differently
40 // from all other URL's as accessing it triggers a sync refresh of Sessions.
41 static const char kNTPOpenTabSyncURL[] = "chrome://newtab/#open_tabs";
43 // Default number of days without activity after which a session is considered
44 // stale and becomes a candidate for garbage collection.
45 static const size_t kDefaultStaleSessionThresholdDays = 14; // 2 weeks.
47 SessionsSyncManager::SessionsSyncManager(
49 SyncInternalApiDelegate* delegate,
50 scoped_ptr<LocalSessionEventRouter> router)
51 : favicon_cache_(profile, kMaxSyncFavicons),
52 local_tab_pool_out_of_sync_(true),
53 sync_prefs_(profile->GetPrefs()),
56 local_session_header_node_id_(TabNodePool::kInvalidTabNodeID),
57 stale_session_threshold_days_(kDefaultStaleSessionThresholdDays),
58 local_event_router_(router.Pass()),
59 synced_window_getter_(new SyncedWindowDelegatesGetter()) {
62 LocalSessionEventRouter::~LocalSessionEventRouter() {}
64 SessionsSyncManager::~SessionsSyncManager() {
67 // Returns the GUID-based string that should be used for
68 // |SessionsSyncManager::current_machine_tag_|.
69 static std::string BuildMachineTag(const std::string& cache_guid) {
70 std::string machine_tag = "session_sync";
71 machine_tag.append(cache_guid);
75 syncer::SyncMergeResult SessionsSyncManager::MergeDataAndStartSyncing(
76 syncer::ModelType type,
77 const syncer::SyncDataList& initial_sync_data,
78 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
79 scoped_ptr<syncer::SyncErrorFactory> error_handler) {
80 syncer::SyncMergeResult merge_result(type);
81 DCHECK(session_tracker_.Empty());
82 DCHECK_EQ(0U, local_tab_pool_.Capacity());
84 error_handler_ = error_handler.Pass();
85 sync_processor_ = sync_processor.Pass();
87 local_session_header_node_id_ = TabNodePool::kInvalidTabNodeID;
88 scoped_ptr<DeviceInfo> local_device_info(delegate_->GetLocalDeviceInfo());
89 syncer::SyncChangeList new_changes;
91 // Make sure we have a machine tag. We do this now (versus earlier) as it's
92 // a conveniently safe time to assert sync is ready and the cache_guid is
94 if (current_machine_tag_.empty())
95 InitializeCurrentMachineTag();
96 if (local_device_info) {
97 current_session_name_ = local_device_info->client_name();
99 merge_result.set_error(error_handler_->CreateAndUploadError(
101 "Failed to get device info for machine tag."));
104 session_tracker_.SetLocalSessionTag(current_machine_tag_);
106 // First, we iterate over sync data to update our session_tracker_.
107 syncer::SyncDataList restored_tabs;
108 if (!InitFromSyncModel(initial_sync_data, &restored_tabs, &new_changes)) {
109 // The sync db didn't have a header node for us. Create one.
110 sync_pb::EntitySpecifics specifics;
111 sync_pb::SessionSpecifics* base_specifics = specifics.mutable_session();
112 base_specifics->set_session_tag(current_machine_tag());
113 sync_pb::SessionHeader* header_s = base_specifics->mutable_header();
114 header_s->set_client_name(current_session_name_);
115 header_s->set_device_type(DeviceInfo::GetLocalDeviceType());
116 syncer::SyncData data = syncer::SyncData::CreateLocalData(
117 current_machine_tag(), current_session_name_, specifics);
118 new_changes.push_back(syncer::SyncChange(
119 FROM_HERE, syncer::SyncChange::ACTION_ADD, data));
122 #if defined(OS_ANDROID)
123 std::string sync_machine_tag(BuildMachineTag(
124 delegate_->GetLocalSyncCacheGUID()));
125 if (current_machine_tag_.compare(sync_machine_tag) != 0)
126 DeleteForeignSessionInternal(sync_machine_tag, &new_changes);
129 // Check if anything has changed on the local client side.
130 AssociateWindows(RELOAD_TABS, restored_tabs, &new_changes);
131 local_tab_pool_out_of_sync_ = false;
133 merge_result.set_error(
134 sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes));
136 local_event_router_->StartRoutingTo(this);
140 void SessionsSyncManager::AssociateWindows(
141 ReloadTabsOption option,
142 const syncer::SyncDataList& restored_tabs,
143 syncer::SyncChangeList* change_output) {
144 const std::string local_tag = current_machine_tag();
145 sync_pb::SessionSpecifics specifics;
146 specifics.set_session_tag(local_tag);
147 sync_pb::SessionHeader* header_s = specifics.mutable_header();
148 SyncedSession* current_session = session_tracker_.GetSession(local_tag);
149 current_session->modified_time = base::Time::Now();
150 header_s->set_client_name(current_session_name_);
151 header_s->set_device_type(DeviceInfo::GetLocalDeviceType());
153 session_tracker_.ResetSessionTracking(local_tag);
154 std::set<SyncedWindowDelegate*> windows =
155 synced_window_getter_->GetSyncedWindowDelegates();
157 for (std::set<SyncedWindowDelegate*>::const_iterator i =
158 windows.begin(); i != windows.end(); ++i) {
159 // Make sure the window has tabs and a viewable window. The viewable window
160 // check is necessary because, for example, when a browser is closed the
161 // destructor is not necessarily run immediately. This means its possible
162 // for us to get a handle to a browser that is about to be removed. If
163 // the tab count is 0 or the window is NULL, the browser is about to be
164 // deleted, so we ignore it.
165 if (sessions_util::ShouldSyncWindow(*i) &&
166 (*i)->GetTabCount() && (*i)->HasWindow()) {
167 sync_pb::SessionWindow window_s;
168 SessionID::id_type window_id = (*i)->GetSessionId();
169 DVLOG(1) << "Associating window " << window_id << " with "
170 << (*i)->GetTabCount() << " tabs.";
171 window_s.set_window_id(window_id);
172 // Note: We don't bother to set selected tab index anymore. We still
173 // consume it when receiving foreign sessions, as reading it is free, but
174 // it triggers too many sync cycles with too little value to make setting
176 if ((*i)->IsTypeTabbed()) {
177 window_s.set_browser_type(
178 sync_pb::SessionWindow_BrowserType_TYPE_TABBED);
180 window_s.set_browser_type(
181 sync_pb::SessionWindow_BrowserType_TYPE_POPUP);
184 bool found_tabs = false;
185 for (int j = 0; j < (*i)->GetTabCount(); ++j) {
186 SessionID::id_type tab_id = (*i)->GetTabIdAt(j);
187 SyncedTabDelegate* synced_tab = (*i)->GetTabAt(j);
189 // GetTabAt can return a null tab; in that case just skip it.
193 if (!synced_tab->HasWebContents()) {
194 // For tabs without WebContents update the |tab_id|, as it could have
195 // changed after a session restore.
196 // Note: We cannot check if a tab is valid if it has no WebContents.
197 // We assume any such tab is valid and leave the contents of
198 // corresponding sync node unchanged.
199 if (synced_tab->GetSyncId() > TabNodePool::kInvalidTabNodeID &&
200 tab_id > TabNodePool::kInvalidTabID) {
201 AssociateRestoredPlaceholderTab(*synced_tab, tab_id,
202 restored_tabs, change_output);
204 window_s.add_tab(tab_id);
209 if (RELOAD_TABS == option)
210 AssociateTab(synced_tab, change_output);
212 // If the tab is valid, it would have been added to the tracker either
213 // by the above AssociateTab call (at association time), or by the
214 // change processor calling AssociateTab for all modified tabs.
215 // Therefore, we can key whether this window has valid tabs based on
216 // the tab's presence in the tracker.
217 const SessionTab* tab = NULL;
218 if (session_tracker_.LookupSessionTab(local_tag, tab_id, &tab)) {
220 window_s.add_tab(tab_id);
224 sync_pb::SessionWindow* header_window = header_s->add_window();
225 *header_window = window_s;
227 // Update this window's representation in the synced session tracker.
228 session_tracker_.PutWindowInSession(local_tag, window_id);
229 BuildSyncedSessionFromSpecifics(local_tag,
231 current_session->modified_time,
232 current_session->windows[window_id]);
236 local_tab_pool_.DeleteUnassociatedTabNodes(change_output);
237 session_tracker_.CleanupSession(local_tag);
239 // Always update the header. Sync takes care of dropping this update
240 // if the entity specifics are identical (i.e windows, client name did
242 sync_pb::EntitySpecifics entity;
243 entity.mutable_session()->CopyFrom(specifics);
244 syncer::SyncData data = syncer::SyncData::CreateLocalData(
245 current_machine_tag(), current_session_name_, entity);
246 change_output->push_back(syncer::SyncChange(
247 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data));
250 void SessionsSyncManager::AssociateTab(SyncedTabDelegate* const tab,
251 syncer::SyncChangeList* change_output) {
252 DCHECK(tab->HasWebContents());
253 SessionID::id_type tab_id = tab->GetSessionId();
254 if (tab->profile() != profile_)
257 if (tab->IsBeingDestroyed()) {
258 // This tab is closing.
259 TabLinksMap::iterator tab_iter = local_tab_map_.find(tab_id);
260 if (tab_iter == local_tab_map_.end()) {
261 // We aren't tracking this tab (for example, sync setting page).
264 local_tab_pool_.FreeTabNode(tab_iter->second->tab_node_id(),
266 local_tab_map_.erase(tab_iter);
270 if (!sessions_util::ShouldSyncTab(*tab))
273 TabLinksMap::iterator local_tab_map_iter = local_tab_map_.find(tab_id);
274 TabLink* tab_link = NULL;
276 if (local_tab_map_iter == local_tab_map_.end()) {
277 int tab_node_id = tab->GetSyncId();
278 // If there is an old sync node for the tab, reuse it. If this is a new
279 // tab, get a sync node for it.
280 if (!local_tab_pool_.IsUnassociatedTabNode(tab_node_id)) {
281 tab_node_id = local_tab_pool_.GetFreeTabNode(change_output);
282 tab->SetSyncId(tab_node_id);
284 local_tab_pool_.AssociateTabNode(tab_node_id, tab_id);
285 tab_link = new TabLink(tab_node_id, tab);
286 local_tab_map_[tab_id] = make_linked_ptr<TabLink>(tab_link);
288 // This tab is already associated with a sync node, reuse it.
289 // Note: on some platforms the tab object may have changed, so we ensure
290 // the tab link is up to date.
291 tab_link = local_tab_map_iter->second.get();
292 local_tab_map_iter->second->set_tab(tab);
295 DCHECK_NE(tab_link->tab_node_id(), TabNodePool::kInvalidTabNodeID);
296 DVLOG(1) << "Reloading tab " << tab_id << " from window "
297 << tab->GetWindowId();
299 // Write to sync model.
300 sync_pb::EntitySpecifics specifics;
301 LocalTabDelegateToSpecifics(*tab, specifics.mutable_session());
302 syncer::SyncData data = syncer::SyncData::CreateLocalData(
303 TabNodePool::TabIdToTag(current_machine_tag_,
304 tab_link->tab_node_id()),
305 current_session_name_,
307 change_output->push_back(syncer::SyncChange(
308 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data));
310 const GURL new_url = GetCurrentVirtualURL(*tab);
311 if (new_url != tab_link->url()) {
312 tab_link->set_url(new_url);
313 favicon_cache_.OnFaviconVisited(new_url, GetCurrentFaviconURL(*tab));
316 session_tracker_.GetSession(current_machine_tag())->modified_time =
320 void SessionsSyncManager::RebuildAssociations() {
321 syncer::SyncDataList data(
322 sync_processor_->GetAllSyncData(syncer::SESSIONS));
323 scoped_ptr<syncer::SyncErrorFactory> error_handler(error_handler_.Pass());
324 scoped_ptr<syncer::SyncChangeProcessor> processor(sync_processor_.Pass());
326 StopSyncing(syncer::SESSIONS);
327 MergeDataAndStartSyncing(
328 syncer::SESSIONS, data, processor.Pass(), error_handler.Pass());
331 void SessionsSyncManager::OnLocalTabModified(SyncedTabDelegate* modified_tab) {
332 const content::NavigationEntry* entry = modified_tab->GetActiveEntry();
333 if (!modified_tab->IsBeingDestroyed() &&
335 entry->GetVirtualURL().is_valid() &&
336 entry->GetVirtualURL().spec() == kNTPOpenTabSyncURL) {
337 DVLOG(1) << "Triggering sync refresh for sessions datatype.";
338 const syncer::ModelTypeSet types(syncer::SESSIONS);
339 content::NotificationService::current()->Notify(
340 chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
341 content::Source<Profile>(profile_),
342 content::Details<const syncer::ModelTypeSet>(&types));
345 if (local_tab_pool_out_of_sync_) {
346 // If our tab pool is corrupt, pay the price of a full re-association to
347 // fix things up. This takes care of the new tab modification as well.
348 RebuildAssociations();
349 DCHECK(!local_tab_pool_out_of_sync_);
353 syncer::SyncChangeList changes;
354 // Associate tabs first so the synced session tracker is aware of them.
355 AssociateTab(modified_tab, &changes);
356 // Note, we always associate windows because it's possible a tab became
357 // "interesting" by going to a valid URL, in which case it needs to be added
358 // to the window's tab information.
359 AssociateWindows(DONT_RELOAD_TABS, syncer::SyncDataList(), &changes);
360 sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
363 void SessionsSyncManager::OnFaviconPageUrlsUpdated(
364 const std::set<GURL>& updated_favicon_page_urls) {
365 // TODO(zea): consider a separate container for tabs with outstanding favicon
366 // loads so we don't have to iterate through all tabs comparing urls.
367 for (std::set<GURL>::const_iterator i = updated_favicon_page_urls.begin();
368 i != updated_favicon_page_urls.end(); ++i) {
369 for (TabLinksMap::iterator tab_iter = local_tab_map_.begin();
370 tab_iter != local_tab_map_.end();
372 if (tab_iter->second->url() == *i)
373 favicon_cache_.OnPageFaviconUpdated(*i);
378 void SessionsSyncManager::StopSyncing(syncer::ModelType type) {
379 local_event_router_->Stop();
380 sync_processor_.reset(NULL);
381 error_handler_.reset();
382 session_tracker_.Clear();
383 local_tab_map_.clear();
384 local_tab_pool_.Clear();
385 current_machine_tag_.clear();
386 current_session_name_.clear();
387 local_session_header_node_id_ = TabNodePool::kInvalidTabNodeID;
390 syncer::SyncDataList SessionsSyncManager::GetAllSyncData(
391 syncer::ModelType type) const {
392 syncer::SyncDataList list;
393 const SyncedSession* session = NULL;
394 if (!session_tracker_.LookupLocalSession(&session))
395 return syncer::SyncDataList();
397 // First construct the header node.
398 sync_pb::EntitySpecifics header_entity;
399 header_entity.mutable_session()->set_session_tag(current_machine_tag());
400 sync_pb::SessionHeader* header_specifics =
401 header_entity.mutable_session()->mutable_header();
402 header_specifics->MergeFrom(session->ToSessionHeader());
403 syncer::SyncData data = syncer::SyncData::CreateLocalData(
404 current_machine_tag(), current_session_name_, header_entity);
405 list.push_back(data);
407 SyncedSession::SyncedWindowMap::const_iterator win_iter;
408 for (win_iter = session->windows.begin();
409 win_iter != session->windows.end(); ++win_iter) {
410 std::vector<SessionTab*>::const_iterator tabs_iter;
411 for (tabs_iter = win_iter->second->tabs.begin();
412 tabs_iter != win_iter->second->tabs.end(); ++tabs_iter) {
413 sync_pb::EntitySpecifics entity;
414 sync_pb::SessionSpecifics* specifics = entity.mutable_session();
415 specifics->mutable_tab()->MergeFrom((*tabs_iter)->ToSyncData());
416 specifics->set_session_tag(current_machine_tag_);
418 TabLinksMap::const_iterator tab_map_iter = local_tab_map_.find(
419 (*tabs_iter)->tab_id.id());
420 DCHECK(tab_map_iter != local_tab_map_.end());
421 specifics->set_tab_node_id(tab_map_iter->second->tab_node_id());
422 syncer::SyncData data = syncer::SyncData::CreateLocalData(
423 TabNodePool::TabIdToTag(current_machine_tag_,
424 specifics->tab_node_id()),
425 current_session_name_,
427 list.push_back(data);
433 bool SessionsSyncManager::GetLocalSession(
434 const SyncedSession* * local_session) {
435 if (current_machine_tag_.empty())
437 *local_session = session_tracker_.GetSession(current_machine_tag());
441 syncer::SyncError SessionsSyncManager::ProcessSyncChanges(
442 const tracked_objects::Location& from_here,
443 const syncer::SyncChangeList& change_list) {
444 if (!sync_processor_.get()) {
445 syncer::SyncError error(FROM_HERE,
446 syncer::SyncError::DATATYPE_ERROR,
447 "Models not yet associated.",
452 for (syncer::SyncChangeList::const_iterator it = change_list.begin();
453 it != change_list.end(); ++it) {
454 DCHECK(it->IsValid());
455 DCHECK(it->sync_data().GetSpecifics().has_session());
456 const sync_pb::SessionSpecifics& session =
457 it->sync_data().GetSpecifics().session();
458 switch (it->change_type()) {
459 case syncer::SyncChange::ACTION_DELETE:
460 // Deletions are all or nothing (since we only ever delete entire
461 // sessions). Therefore we don't care if it's a tab node or meta node,
462 // and just ensure we've disassociated.
463 if (current_machine_tag() == session.session_tag()) {
464 // Another client has attempted to delete our local data (possibly by
465 // error or a clock is inaccurate). Just ignore the deletion for now
466 // to avoid any possible ping-pong delete/reassociate sequence, but
467 // remember that this happened as our TabNodePool is inconsistent.
468 local_tab_pool_out_of_sync_ = true;
469 LOG(WARNING) << "Local session data deleted. Ignoring until next "
470 << "local navigation event.";
471 } else if (session.has_header()) {
472 // Disassociate only when header node is deleted. For tab node
473 // deletions, the header node will be updated and foreign tab will
475 DisassociateForeignSession(session.session_tag());
478 case syncer::SyncChange::ACTION_ADD:
479 case syncer::SyncChange::ACTION_UPDATE:
480 if (current_machine_tag() == session.session_tag()) {
481 // We should only ever receive a change to our own machine's session
482 // info if encryption was turned on. In that case, the data is still
483 // the same, so we can ignore.
484 LOG(WARNING) << "Dropping modification to local session.";
485 return syncer::SyncError();
487 UpdateTrackerWithForeignSession(
488 session, syncer::SyncDataRemote(it->sync_data()).GetModifiedTime());
491 NOTREACHED() << "Processing sync changes failed, unknown change type.";
495 content::NotificationService::current()->Notify(
496 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
497 content::Source<Profile>(profile_),
498 content::NotificationService::NoDetails());
499 return syncer::SyncError();
502 syncer::SyncChange SessionsSyncManager::TombstoneTab(
503 const sync_pb::SessionSpecifics& tab) {
504 if (!tab.has_tab_node_id()) {
505 LOG(WARNING) << "Old sessions node without tab node id; can't tombstone.";
506 return syncer::SyncChange();
508 return syncer::SyncChange(
510 SyncChange::ACTION_DELETE,
511 SyncData::CreateLocalDelete(
512 TabNodePool::TabIdToTag(current_machine_tag(),
518 bool SessionsSyncManager::GetAllForeignSessions(
519 std::vector<const SyncedSession*>* sessions) {
520 return session_tracker_.LookupAllForeignSessions(sessions);
523 bool SessionsSyncManager::InitFromSyncModel(
524 const syncer::SyncDataList& sync_data,
525 syncer::SyncDataList* restored_tabs,
526 syncer::SyncChangeList* new_changes) {
527 bool found_current_header = false;
528 for (syncer::SyncDataList::const_iterator it = sync_data.begin();
529 it != sync_data.end();
531 const syncer::SyncData& data = *it;
532 DCHECK(data.GetSpecifics().has_session());
533 const sync_pb::SessionSpecifics& specifics = data.GetSpecifics().session();
534 if (specifics.session_tag().empty() ||
535 (specifics.has_tab() && (!specifics.has_tab_node_id() ||
536 !specifics.tab().has_tab_id()))) {
537 syncer::SyncChange tombstone(TombstoneTab(specifics));
538 if (tombstone.IsValid())
539 new_changes->push_back(tombstone);
540 } else if (specifics.session_tag() != current_machine_tag()) {
541 UpdateTrackerWithForeignSession(
542 specifics, syncer::SyncDataRemote(data).GetModifiedTime());
544 // This is previously stored local session information.
545 if (specifics.has_header() && !found_current_header) {
546 // This is our previous header node, reuse it.
547 found_current_header = true;
548 if (specifics.header().has_client_name())
549 current_session_name_ = specifics.header().client_name();
551 if (specifics.has_header() || !specifics.has_tab()) {
552 LOG(WARNING) << "Found more than one session header node with local "
554 syncer::SyncChange tombstone(TombstoneTab(specifics));
555 if (tombstone.IsValid())
556 new_changes->push_back(tombstone);
558 // This is a valid old tab node, add it to the pool so it can be
559 // reused for reassociation.
560 local_tab_pool_.AddTabNode(specifics.tab_node_id());
561 restored_tabs->push_back(*it);
566 return found_current_header;
569 void SessionsSyncManager::UpdateTrackerWithForeignSession(
570 const sync_pb::SessionSpecifics& specifics,
571 const base::Time& modification_time) {
572 std::string foreign_session_tag = specifics.session_tag();
573 DCHECK_NE(foreign_session_tag, current_machine_tag());
575 SyncedSession* foreign_session =
576 session_tracker_.GetSession(foreign_session_tag);
577 if (specifics.has_header()) {
578 // Read in the header data for this foreign session.
579 // Header data contains window information and ordered tab id's for each
582 // Load (or create) the SyncedSession object for this client.
583 const sync_pb::SessionHeader& header = specifics.header();
584 PopulateSessionHeaderFromSpecifics(header,
588 // Reset the tab/window tracking for this session (must do this before
589 // we start calling PutWindowInSession and PutTabInWindow so that all
590 // unused tabs/windows get cleared by the CleanupSession(...) call).
591 session_tracker_.ResetSessionTracking(foreign_session_tag);
593 // Process all the windows and their tab information.
594 int num_windows = header.window_size();
595 DVLOG(1) << "Associating " << foreign_session_tag << " with "
596 << num_windows << " windows.";
598 for (int i = 0; i < num_windows; ++i) {
599 const sync_pb::SessionWindow& window_s = header.window(i);
600 SessionID::id_type window_id = window_s.window_id();
601 session_tracker_.PutWindowInSession(foreign_session_tag,
603 BuildSyncedSessionFromSpecifics(foreign_session_tag,
606 foreign_session->windows[window_id]);
608 // Delete any closed windows and unused tabs as necessary.
609 session_tracker_.CleanupSession(foreign_session_tag);
610 } else if (specifics.has_tab()) {
611 const sync_pb::SessionTab& tab_s = specifics.tab();
612 SessionID::id_type tab_id = tab_s.tab_id();
614 session_tracker_.GetTab(foreign_session_tag,
616 specifics.tab_node_id());
618 // Update SessionTab based on protobuf.
619 tab->SetFromSyncData(tab_s, modification_time);
621 // If a favicon or favicon urls are present, load the URLs and visit
622 // times into the in-memory favicon cache.
623 RefreshFaviconVisitTimesFromForeignTab(tab_s, modification_time);
625 // Update the last modified time.
626 if (foreign_session->modified_time < modification_time)
627 foreign_session->modified_time = modification_time;
629 LOG(WARNING) << "Ignoring foreign session node with missing header/tab "
630 << "fields and tag " << foreign_session_tag << ".";
634 void SessionsSyncManager::InitializeCurrentMachineTag() {
635 DCHECK(current_machine_tag_.empty());
636 std::string persisted_guid;
637 persisted_guid = sync_prefs_.GetSyncSessionsGUID();
638 if (!persisted_guid.empty()) {
639 current_machine_tag_ = persisted_guid;
640 DVLOG(1) << "Restoring persisted session sync guid: " << persisted_guid;
642 current_machine_tag_ = BuildMachineTag(delegate_->GetLocalSyncCacheGUID());
643 DVLOG(1) << "Creating session sync guid: " << current_machine_tag_;
644 sync_prefs_.SetSyncSessionsGUID(current_machine_tag_);
647 local_tab_pool_.SetMachineTag(current_machine_tag_);
651 void SessionsSyncManager::PopulateSessionHeaderFromSpecifics(
652 const sync_pb::SessionHeader& header_specifics,
654 SyncedSession* session_header) {
655 if (header_specifics.has_client_name())
656 session_header->session_name = header_specifics.client_name();
657 if (header_specifics.has_device_type()) {
658 switch (header_specifics.device_type()) {
659 case sync_pb::SyncEnums_DeviceType_TYPE_WIN:
660 session_header->device_type = SyncedSession::TYPE_WIN;
662 case sync_pb::SyncEnums_DeviceType_TYPE_MAC:
663 session_header->device_type = SyncedSession::TYPE_MACOSX;
665 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX:
666 session_header->device_type = SyncedSession::TYPE_LINUX;
668 case sync_pb::SyncEnums_DeviceType_TYPE_CROS:
669 session_header->device_type = SyncedSession::TYPE_CHROMEOS;
671 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE:
672 session_header->device_type = SyncedSession::TYPE_PHONE;
674 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET:
675 session_header->device_type = SyncedSession::TYPE_TABLET;
677 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER:
678 // Intentionally fall-through
680 session_header->device_type = SyncedSession::TYPE_OTHER;
684 session_header->modified_time = mtime;
688 void SessionsSyncManager::BuildSyncedSessionFromSpecifics(
689 const std::string& session_tag,
690 const sync_pb::SessionWindow& specifics,
692 SessionWindow* session_window) {
693 if (specifics.has_window_id())
694 session_window->window_id.set_id(specifics.window_id());
695 if (specifics.has_selected_tab_index())
696 session_window->selected_tab_index = specifics.selected_tab_index();
697 if (specifics.has_browser_type()) {
698 if (specifics.browser_type() ==
699 sync_pb::SessionWindow_BrowserType_TYPE_TABBED) {
700 session_window->type = 1;
702 session_window->type = 2;
705 session_window->timestamp = mtime;
706 session_window->tabs.resize(specifics.tab_size(), NULL);
707 for (int i = 0; i < specifics.tab_size(); i++) {
708 SessionID::id_type tab_id = specifics.tab(i);
709 session_tracker_.PutTabInWindow(session_tag,
710 session_window->window_id.id(),
716 void SessionsSyncManager::RefreshFaviconVisitTimesFromForeignTab(
717 const sync_pb::SessionTab& tab, const base::Time& modification_time) {
718 // First go through and iterate over all the navigations, checking if any
719 // have valid favicon urls.
720 for (int i = 0; i < tab.navigation_size(); ++i) {
721 if (!tab.navigation(i).favicon_url().empty()) {
722 const std::string& page_url = tab.navigation(i).virtual_url();
723 const std::string& favicon_url = tab.navigation(i).favicon_url();
724 favicon_cache_.OnReceivedSyncFavicon(GURL(page_url),
727 syncer::TimeToProtoTime(
733 bool SessionsSyncManager::GetSyncedFaviconForPageURL(
734 const std::string& page_url,
735 scoped_refptr<base::RefCountedMemory>* favicon_png) const {
736 return favicon_cache_.GetSyncedFaviconForPageURL(GURL(page_url), favicon_png);
739 void SessionsSyncManager::DeleteForeignSession(const std::string& tag) {
740 syncer::SyncChangeList changes;
741 DeleteForeignSessionInternal(tag, &changes);
742 sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
745 void SessionsSyncManager::DeleteForeignSessionInternal(
746 const std::string& tag, syncer::SyncChangeList* change_output) {
747 if (tag == current_machine_tag()) {
748 LOG(ERROR) << "Attempting to delete local session. This is not currently "
753 std::set<int> tab_node_ids_to_delete;
754 session_tracker_.LookupTabNodeIds(tag, &tab_node_ids_to_delete);
755 if (!DisassociateForeignSession(tag)) {
756 // We don't have any data for this session, our work here is done!
760 // Prepare deletes for the meta-node as well as individual tab nodes.
761 change_output->push_back(syncer::SyncChange(
763 SyncChange::ACTION_DELETE,
764 SyncData::CreateLocalDelete(tag, syncer::SESSIONS)));
766 for (std::set<int>::const_iterator it = tab_node_ids_to_delete.begin();
767 it != tab_node_ids_to_delete.end();
769 change_output->push_back(syncer::SyncChange(
771 SyncChange::ACTION_DELETE,
772 SyncData::CreateLocalDelete(TabNodePool::TabIdToTag(tag, *it),
775 content::NotificationService::current()->Notify(
776 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
777 content::Source<Profile>(profile_),
778 content::NotificationService::NoDetails());
781 bool SessionsSyncManager::DisassociateForeignSession(
782 const std::string& foreign_session_tag) {
783 if (foreign_session_tag == current_machine_tag()) {
784 DVLOG(1) << "Local session deleted! Doing nothing until a navigation is "
788 DVLOG(1) << "Disassociating session " << foreign_session_tag;
789 return session_tracker_.DeleteSession(foreign_session_tag);
793 GURL SessionsSyncManager::GetCurrentVirtualURL(
794 const SyncedTabDelegate& tab_delegate) {
795 const int current_index = tab_delegate.GetCurrentEntryIndex();
796 const int pending_index = tab_delegate.GetPendingEntryIndex();
797 const NavigationEntry* current_entry =
798 (current_index == pending_index) ?
799 tab_delegate.GetPendingEntry() :
800 tab_delegate.GetEntryAtIndex(current_index);
801 return current_entry->GetVirtualURL();
805 GURL SessionsSyncManager::GetCurrentFaviconURL(
806 const SyncedTabDelegate& tab_delegate) {
807 const int current_index = tab_delegate.GetCurrentEntryIndex();
808 const int pending_index = tab_delegate.GetPendingEntryIndex();
809 const NavigationEntry* current_entry =
810 (current_index == pending_index) ?
811 tab_delegate.GetPendingEntry() :
812 tab_delegate.GetEntryAtIndex(current_index);
813 return (current_entry->GetFavicon().valid ?
814 current_entry->GetFavicon().url :
818 bool SessionsSyncManager::GetForeignSession(
819 const std::string& tag,
820 std::vector<const SessionWindow*>* windows) {
821 return session_tracker_.LookupSessionWindows(tag, windows);
824 bool SessionsSyncManager::GetForeignTab(
825 const std::string& tag,
826 const SessionID::id_type tab_id,
827 const SessionTab** tab) {
828 const SessionTab* synced_tab = NULL;
829 bool success = session_tracker_.LookupSessionTab(tag,
837 void SessionsSyncManager::LocalTabDelegateToSpecifics(
838 const SyncedTabDelegate& tab_delegate,
839 sync_pb::SessionSpecifics* specifics) {
840 SessionTab* session_tab = NULL;
842 session_tracker_.GetTab(current_machine_tag(),
843 tab_delegate.GetSessionId(),
844 tab_delegate.GetSyncId());
845 SetSessionTabFromDelegate(tab_delegate, base::Time::Now(), session_tab);
846 sync_pb::SessionTab tab_s = session_tab->ToSyncData();
847 specifics->set_session_tag(current_machine_tag_);
848 specifics->set_tab_node_id(tab_delegate.GetSyncId());
849 specifics->mutable_tab()->CopyFrom(tab_s);
852 void SessionsSyncManager::AssociateRestoredPlaceholderTab(
853 const SyncedTabDelegate& tab_delegate,
854 SessionID::id_type new_tab_id,
855 const syncer::SyncDataList& restored_tabs,
856 syncer::SyncChangeList* change_output) {
857 DCHECK_NE(tab_delegate.GetSyncId(), TabNodePool::kInvalidTabNodeID);
858 // Rewrite the tab using |restored_tabs| to retrieve the specifics.
859 if (restored_tabs.empty()) {
860 DLOG(WARNING) << "Can't Update tab ID.";
864 for (syncer::SyncDataList::const_iterator it = restored_tabs.begin();
865 it != restored_tabs.end();
867 if (it->GetSpecifics().session().tab_node_id() !=
868 tab_delegate.GetSyncId()) {
872 sync_pb::EntitySpecifics entity;
873 sync_pb::SessionSpecifics* specifics = entity.mutable_session();
874 specifics->CopyFrom(it->GetSpecifics().session());
875 DCHECK(specifics->has_tab());
877 // Update tab node pool with the new association.
878 local_tab_pool_.ReassociateTabNode(tab_delegate.GetSyncId(),
880 TabLink* tab_link = new TabLink(tab_delegate.GetSyncId(),
882 local_tab_map_[new_tab_id] = make_linked_ptr<TabLink>(tab_link);
884 if (specifics->tab().tab_id() == new_tab_id)
887 // The tab_id changed (e.g due to session restore), so update sync.
888 specifics->mutable_tab()->set_tab_id(new_tab_id);
889 syncer::SyncData data = syncer::SyncData::CreateLocalData(
890 TabNodePool::TabIdToTag(current_machine_tag_,
891 specifics->tab_node_id()),
892 current_session_name_,
894 change_output->push_back(syncer::SyncChange(
895 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data));
901 void SessionsSyncManager::SetSessionTabFromDelegate(
902 const SyncedTabDelegate& tab_delegate,
904 SessionTab* session_tab) {
906 session_tab->window_id.set_id(tab_delegate.GetWindowId());
907 session_tab->tab_id.set_id(tab_delegate.GetSessionId());
908 session_tab->tab_visual_index = 0;
909 session_tab->current_navigation_index = tab_delegate.GetCurrentEntryIndex();
910 session_tab->pinned = tab_delegate.IsPinned();
911 session_tab->extension_app_id = tab_delegate.GetExtensionAppId();
912 session_tab->user_agent_override.clear();
913 session_tab->timestamp = mtime;
914 const int current_index = tab_delegate.GetCurrentEntryIndex();
915 const int pending_index = tab_delegate.GetPendingEntryIndex();
916 const int min_index = std::max(0, current_index - kMaxSyncNavigationCount);
917 const int max_index = std::min(current_index + kMaxSyncNavigationCount,
918 tab_delegate.GetEntryCount());
919 bool is_supervised = tab_delegate.ProfileIsSupervised();
920 session_tab->navigations.clear();
922 for (int i = min_index; i < max_index; ++i) {
923 const NavigationEntry* entry = (i == pending_index) ?
924 tab_delegate.GetPendingEntry() : tab_delegate.GetEntryAtIndex(i);
926 if (!entry->GetVirtualURL().is_valid())
929 session_tab->navigations.push_back(
930 SerializedNavigationEntry::FromNavigationEntry(i, *entry));
932 session_tab->navigations.back().set_blocked_state(
933 SerializedNavigationEntry::STATE_ALLOWED);
938 const std::vector<const NavigationEntry*>& blocked_navigations =
939 *tab_delegate.GetBlockedNavigations();
940 int offset = session_tab->navigations.size();
941 for (size_t i = 0; i < blocked_navigations.size(); ++i) {
942 session_tab->navigations.push_back(
943 SerializedNavigationEntry::FromNavigationEntry(
944 i + offset, *blocked_navigations[i]));
945 session_tab->navigations.back().set_blocked_state(
946 SerializedNavigationEntry::STATE_BLOCKED);
947 // TODO(bauerb): Add categories
950 session_tab->session_storage_persistent_id.clear();
953 FaviconCache* SessionsSyncManager::GetFaviconCache() {
954 return &favicon_cache_;
957 void SessionsSyncManager::DoGarbageCollection() {
958 std::vector<const SyncedSession*> sessions;
959 if (!GetAllForeignSessions(&sessions))
960 return; // No foreign sessions.
962 // Iterate through all the sessions and delete any with age older than
963 // |stale_session_threshold_days_|.
964 syncer::SyncChangeList changes;
965 for (std::vector<const SyncedSession*>::const_iterator iter =
966 sessions.begin(); iter != sessions.end(); ++iter) {
967 const SyncedSession* session = *iter;
968 int session_age_in_days =
969 (base::Time::Now() - session->modified_time).InDays();
970 std::string session_tag = session->session_tag;
971 if (session_age_in_days > 0 && // If false, local clock is not trustworty.
972 static_cast<size_t>(session_age_in_days) >
973 stale_session_threshold_days_) {
974 DVLOG(1) << "Found stale session " << session_tag
975 << " with age " << session_age_in_days << ", deleting.";
976 DeleteForeignSessionInternal(session_tag, &changes);
980 if (!changes.empty())
981 sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
984 }; // namespace browser_sync