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 "components/sync_driver/local_device_info_provider.h"
15 #include "content/public/browser/favicon_status.h"
16 #include "content/public/browser/navigation_entry.h"
17 #include "content/public/browser/notification_details.h"
18 #include "content/public/browser/notification_service.h"
19 #include "content/public/browser/notification_source.h"
20 #include "content/public/common/url_constants.h"
21 #include "sync/api/sync_error.h"
22 #include "sync/api/sync_error_factory.h"
23 #include "sync/api/sync_merge_result.h"
24 #include "sync/api/time.h"
26 using content::NavigationEntry
;
27 using sessions::SerializedNavigationEntry
;
28 using sync_driver::DeviceInfo
;
29 using sync_driver::LocalDeviceInfoProvider
;
30 using syncer::SyncChange
;
31 using syncer::SyncData
;
33 namespace browser_sync
{
35 // Maximum number of favicons to sync.
36 // TODO(zea): pull this from the server.
37 static const int kMaxSyncFavicons
= 200;
39 // The maximum number of navigations in each direction we care to sync.
40 static const int kMaxSyncNavigationCount
= 6;
42 // The URL at which the set of synced tabs is displayed. We treat it differently
43 // from all other URL's as accessing it triggers a sync refresh of Sessions.
44 static const char kNTPOpenTabSyncURL
[] = "chrome://newtab/#open_tabs";
46 // Default number of days without activity after which a session is considered
47 // stale and becomes a candidate for garbage collection.
48 static const size_t kDefaultStaleSessionThresholdDays
= 14; // 2 weeks.
50 // |local_device| is owned by ProfileSyncService, its lifetime exceeds
51 // lifetime of SessionSyncManager.
52 SessionsSyncManager::SessionsSyncManager(
54 LocalDeviceInfoProvider
* local_device
,
55 scoped_ptr
<LocalSessionEventRouter
> router
)
56 : favicon_cache_(profile
, kMaxSyncFavicons
),
57 local_tab_pool_out_of_sync_(true),
58 sync_prefs_(profile
->GetPrefs()),
60 local_device_(local_device
),
61 local_session_header_node_id_(TabNodePool::kInvalidTabNodeID
),
62 stale_session_threshold_days_(kDefaultStaleSessionThresholdDays
),
63 local_event_router_(router
.Pass()),
64 synced_window_getter_(new SyncedWindowDelegatesGetter()) {
67 LocalSessionEventRouter::~LocalSessionEventRouter() {}
69 SessionsSyncManager::~SessionsSyncManager() {
72 // Returns the GUID-based string that should be used for
73 // |SessionsSyncManager::current_machine_tag_|.
74 static std::string
BuildMachineTag(const std::string
& cache_guid
) {
75 std::string machine_tag
= "session_sync";
76 machine_tag
.append(cache_guid
);
80 syncer::SyncMergeResult
SessionsSyncManager::MergeDataAndStartSyncing(
81 syncer::ModelType type
,
82 const syncer::SyncDataList
& initial_sync_data
,
83 scoped_ptr
<syncer::SyncChangeProcessor
> sync_processor
,
84 scoped_ptr
<syncer::SyncErrorFactory
> error_handler
) {
85 syncer::SyncMergeResult
merge_result(type
);
86 DCHECK(session_tracker_
.Empty());
87 DCHECK_EQ(0U, local_tab_pool_
.Capacity());
89 error_handler_
= error_handler
.Pass();
90 sync_processor_
= sync_processor
.Pass();
92 local_session_header_node_id_
= TabNodePool::kInvalidTabNodeID
;
94 // Make sure we have a machine tag. We do this now (versus earlier) as it's
95 // a conveniently safe time to assert sync is ready and the cache_guid is
97 if (current_machine_tag_
.empty()) {
98 InitializeCurrentMachineTag();
101 // SessionDataTypeController ensures that the local device info
102 // is available before activating this datatype.
103 DCHECK(local_device_
);
104 const DeviceInfo
* local_device_info
= local_device_
->GetLocalDeviceInfo();
105 if (local_device_info
) {
106 current_session_name_
= local_device_info
->client_name();
108 merge_result
.set_error(error_handler_
->CreateAndUploadError(
110 "Failed to get local device info."));
114 session_tracker_
.SetLocalSessionTag(current_machine_tag_
);
116 syncer::SyncChangeList new_changes
;
118 // First, we iterate over sync data to update our session_tracker_.
119 syncer::SyncDataList restored_tabs
;
120 if (!InitFromSyncModel(initial_sync_data
, &restored_tabs
, &new_changes
)) {
121 // The sync db didn't have a header node for us. Create one.
122 sync_pb::EntitySpecifics specifics
;
123 sync_pb::SessionSpecifics
* base_specifics
= specifics
.mutable_session();
124 base_specifics
->set_session_tag(current_machine_tag());
125 sync_pb::SessionHeader
* header_s
= base_specifics
->mutable_header();
126 header_s
->set_client_name(current_session_name_
);
127 header_s
->set_device_type(local_device_info
->device_type());
128 syncer::SyncData data
= syncer::SyncData::CreateLocalData(
129 current_machine_tag(), current_session_name_
, specifics
);
130 new_changes
.push_back(syncer::SyncChange(
131 FROM_HERE
, syncer::SyncChange::ACTION_ADD
, data
));
134 #if defined(OS_ANDROID)
135 std::string
sync_machine_tag(BuildMachineTag(
136 local_device_
->GetLocalSyncCacheGUID()));
137 if (current_machine_tag_
.compare(sync_machine_tag
) != 0)
138 DeleteForeignSessionInternal(sync_machine_tag
, &new_changes
);
141 // Check if anything has changed on the local client side.
142 AssociateWindows(RELOAD_TABS
, restored_tabs
, &new_changes
);
143 local_tab_pool_out_of_sync_
= false;
145 merge_result
.set_error(
146 sync_processor_
->ProcessSyncChanges(FROM_HERE
, new_changes
));
148 local_event_router_
->StartRoutingTo(this);
152 void SessionsSyncManager::AssociateWindows(
153 ReloadTabsOption option
,
154 const syncer::SyncDataList
& restored_tabs
,
155 syncer::SyncChangeList
* change_output
) {
156 const std::string local_tag
= current_machine_tag();
157 sync_pb::SessionSpecifics specifics
;
158 specifics
.set_session_tag(local_tag
);
159 sync_pb::SessionHeader
* header_s
= specifics
.mutable_header();
160 SyncedSession
* current_session
= session_tracker_
.GetSession(local_tag
);
161 current_session
->modified_time
= base::Time::Now();
162 header_s
->set_client_name(current_session_name_
);
163 // SessionDataTypeController ensures that the local device info
164 // is available before activating this datatype.
165 DCHECK(local_device_
);
166 const DeviceInfo
* local_device_info
= local_device_
->GetLocalDeviceInfo();
167 header_s
->set_device_type(local_device_info
->device_type());
169 session_tracker_
.ResetSessionTracking(local_tag
);
170 std::set
<SyncedWindowDelegate
*> windows
=
171 synced_window_getter_
->GetSyncedWindowDelegates();
173 for (std::set
<SyncedWindowDelegate
*>::const_iterator i
=
174 windows
.begin(); i
!= windows
.end(); ++i
) {
175 // Make sure the window has tabs and a viewable window. The viewable window
176 // check is necessary because, for example, when a browser is closed the
177 // destructor is not necessarily run immediately. This means its possible
178 // for us to get a handle to a browser that is about to be removed. If
179 // the tab count is 0 or the window is NULL, the browser is about to be
180 // deleted, so we ignore it.
181 if (sessions_util::ShouldSyncWindow(*i
) &&
182 (*i
)->GetTabCount() && (*i
)->HasWindow()) {
183 sync_pb::SessionWindow window_s
;
184 SessionID::id_type window_id
= (*i
)->GetSessionId();
185 DVLOG(1) << "Associating window " << window_id
<< " with "
186 << (*i
)->GetTabCount() << " tabs.";
187 window_s
.set_window_id(window_id
);
188 // Note: We don't bother to set selected tab index anymore. We still
189 // consume it when receiving foreign sessions, as reading it is free, but
190 // it triggers too many sync cycles with too little value to make setting
192 if ((*i
)->IsTypeTabbed()) {
193 window_s
.set_browser_type(
194 sync_pb::SessionWindow_BrowserType_TYPE_TABBED
);
196 window_s
.set_browser_type(
197 sync_pb::SessionWindow_BrowserType_TYPE_POPUP
);
200 bool found_tabs
= false;
201 for (int j
= 0; j
< (*i
)->GetTabCount(); ++j
) {
202 SessionID::id_type tab_id
= (*i
)->GetTabIdAt(j
);
203 SyncedTabDelegate
* synced_tab
= (*i
)->GetTabAt(j
);
205 // GetTabAt can return a null tab; in that case just skip it.
209 if (!synced_tab
->HasWebContents()) {
210 // For tabs without WebContents update the |tab_id|, as it could have
211 // changed after a session restore.
212 // Note: We cannot check if a tab is valid if it has no WebContents.
213 // We assume any such tab is valid and leave the contents of
214 // corresponding sync node unchanged.
215 if (synced_tab
->GetSyncId() > TabNodePool::kInvalidTabNodeID
&&
216 tab_id
> TabNodePool::kInvalidTabID
) {
217 AssociateRestoredPlaceholderTab(*synced_tab
, tab_id
,
218 restored_tabs
, change_output
);
220 window_s
.add_tab(tab_id
);
225 if (RELOAD_TABS
== option
)
226 AssociateTab(synced_tab
, change_output
);
228 // If the tab is valid, it would have been added to the tracker either
229 // by the above AssociateTab call (at association time), or by the
230 // change processor calling AssociateTab for all modified tabs.
231 // Therefore, we can key whether this window has valid tabs based on
232 // the tab's presence in the tracker.
233 const SessionTab
* tab
= NULL
;
234 if (session_tracker_
.LookupSessionTab(local_tag
, tab_id
, &tab
)) {
236 window_s
.add_tab(tab_id
);
240 sync_pb::SessionWindow
* header_window
= header_s
->add_window();
241 *header_window
= window_s
;
243 // Update this window's representation in the synced session tracker.
244 session_tracker_
.PutWindowInSession(local_tag
, window_id
);
245 BuildSyncedSessionFromSpecifics(local_tag
,
247 current_session
->modified_time
,
248 current_session
->windows
[window_id
]);
252 local_tab_pool_
.DeleteUnassociatedTabNodes(change_output
);
253 session_tracker_
.CleanupSession(local_tag
);
255 // Always update the header. Sync takes care of dropping this update
256 // if the entity specifics are identical (i.e windows, client name did
258 sync_pb::EntitySpecifics entity
;
259 entity
.mutable_session()->CopyFrom(specifics
);
260 syncer::SyncData data
= syncer::SyncData::CreateLocalData(
261 current_machine_tag(), current_session_name_
, entity
);
262 change_output
->push_back(syncer::SyncChange(
263 FROM_HERE
, syncer::SyncChange::ACTION_UPDATE
, data
));
266 void SessionsSyncManager::AssociateTab(SyncedTabDelegate
* const tab
,
267 syncer::SyncChangeList
* change_output
) {
268 DCHECK(tab
->HasWebContents());
269 SessionID::id_type tab_id
= tab
->GetSessionId();
270 if (tab
->profile() != profile_
)
273 if (tab
->IsBeingDestroyed()) {
274 // This tab is closing.
275 TabLinksMap::iterator tab_iter
= local_tab_map_
.find(tab_id
);
276 if (tab_iter
== local_tab_map_
.end()) {
277 // We aren't tracking this tab (for example, sync setting page).
280 local_tab_pool_
.FreeTabNode(tab_iter
->second
->tab_node_id(),
282 local_tab_map_
.erase(tab_iter
);
286 if (!sessions_util::ShouldSyncTab(*tab
))
289 TabLinksMap::iterator local_tab_map_iter
= local_tab_map_
.find(tab_id
);
290 TabLink
* tab_link
= NULL
;
292 if (local_tab_map_iter
== local_tab_map_
.end()) {
293 int tab_node_id
= tab
->GetSyncId();
294 // If there is an old sync node for the tab, reuse it. If this is a new
295 // tab, get a sync node for it.
296 if (!local_tab_pool_
.IsUnassociatedTabNode(tab_node_id
)) {
297 tab_node_id
= local_tab_pool_
.GetFreeTabNode(change_output
);
298 tab
->SetSyncId(tab_node_id
);
300 local_tab_pool_
.AssociateTabNode(tab_node_id
, tab_id
);
301 tab_link
= new TabLink(tab_node_id
, tab
);
302 local_tab_map_
[tab_id
] = make_linked_ptr
<TabLink
>(tab_link
);
304 // This tab is already associated with a sync node, reuse it.
305 // Note: on some platforms the tab object may have changed, so we ensure
306 // the tab link is up to date.
307 tab_link
= local_tab_map_iter
->second
.get();
308 local_tab_map_iter
->second
->set_tab(tab
);
311 DCHECK_NE(tab_link
->tab_node_id(), TabNodePool::kInvalidTabNodeID
);
312 DVLOG(1) << "Reloading tab " << tab_id
<< " from window "
313 << tab
->GetWindowId();
315 // Write to sync model.
316 sync_pb::EntitySpecifics specifics
;
317 LocalTabDelegateToSpecifics(*tab
, specifics
.mutable_session());
318 syncer::SyncData data
= syncer::SyncData::CreateLocalData(
319 TabNodePool::TabIdToTag(current_machine_tag_
,
320 tab_link
->tab_node_id()),
321 current_session_name_
,
323 change_output
->push_back(syncer::SyncChange(
324 FROM_HERE
, syncer::SyncChange::ACTION_UPDATE
, data
));
326 const GURL new_url
= GetCurrentVirtualURL(*tab
);
327 if (new_url
!= tab_link
->url()) {
328 tab_link
->set_url(new_url
);
329 favicon_cache_
.OnFaviconVisited(new_url
, GetCurrentFaviconURL(*tab
));
332 session_tracker_
.GetSession(current_machine_tag())->modified_time
=
336 void SessionsSyncManager::RebuildAssociations() {
337 syncer::SyncDataList
data(
338 sync_processor_
->GetAllSyncData(syncer::SESSIONS
));
339 scoped_ptr
<syncer::SyncErrorFactory
> error_handler(error_handler_
.Pass());
340 scoped_ptr
<syncer::SyncChangeProcessor
> processor(sync_processor_
.Pass());
342 StopSyncing(syncer::SESSIONS
);
343 MergeDataAndStartSyncing(
344 syncer::SESSIONS
, data
, processor
.Pass(), error_handler
.Pass());
347 bool SessionsSyncManager::IsValidSessionHeader(
348 const sync_pb::SessionHeader
& header
) {
349 // Verify that tab IDs appear only once within a session.
350 // Intended to prevent http://crbug.com/360822.
351 std::set
<int> session_tab_ids
;
352 for (int i
= 0; i
< header
.window_size(); ++i
) {
353 const sync_pb::SessionWindow
& window
= header
.window(i
);
354 for (int j
= 0; j
< window
.tab_size(); ++j
) {
355 const int tab_id
= window
.tab(j
);
356 bool success
= session_tab_ids
.insert(tab_id
).second
;
365 void SessionsSyncManager::OnLocalTabModified(SyncedTabDelegate
* modified_tab
) {
366 const content::NavigationEntry
* entry
= modified_tab
->GetActiveEntry();
367 if (!modified_tab
->IsBeingDestroyed() &&
369 entry
->GetVirtualURL().is_valid() &&
370 entry
->GetVirtualURL().spec() == kNTPOpenTabSyncURL
) {
371 DVLOG(1) << "Triggering sync refresh for sessions datatype.";
372 const syncer::ModelTypeSet
types(syncer::SESSIONS
);
373 content::NotificationService::current()->Notify(
374 chrome::NOTIFICATION_SYNC_REFRESH_LOCAL
,
375 content::Source
<Profile
>(profile_
),
376 content::Details
<const syncer::ModelTypeSet
>(&types
));
379 if (local_tab_pool_out_of_sync_
) {
380 // If our tab pool is corrupt, pay the price of a full re-association to
381 // fix things up. This takes care of the new tab modification as well.
382 RebuildAssociations();
383 DCHECK(!local_tab_pool_out_of_sync_
);
387 syncer::SyncChangeList changes
;
388 // Associate tabs first so the synced session tracker is aware of them.
389 AssociateTab(modified_tab
, &changes
);
390 // Note, we always associate windows because it's possible a tab became
391 // "interesting" by going to a valid URL, in which case it needs to be added
392 // to the window's tab information.
393 AssociateWindows(DONT_RELOAD_TABS
, syncer::SyncDataList(), &changes
);
394 sync_processor_
->ProcessSyncChanges(FROM_HERE
, changes
);
397 void SessionsSyncManager::OnFaviconPageUrlsUpdated(
398 const std::set
<GURL
>& updated_favicon_page_urls
) {
399 // TODO(zea): consider a separate container for tabs with outstanding favicon
400 // loads so we don't have to iterate through all tabs comparing urls.
401 for (std::set
<GURL
>::const_iterator i
= updated_favicon_page_urls
.begin();
402 i
!= updated_favicon_page_urls
.end(); ++i
) {
403 for (TabLinksMap::iterator tab_iter
= local_tab_map_
.begin();
404 tab_iter
!= local_tab_map_
.end();
406 if (tab_iter
->second
->url() == *i
)
407 favicon_cache_
.OnPageFaviconUpdated(*i
);
412 void SessionsSyncManager::StopSyncing(syncer::ModelType type
) {
413 local_event_router_
->Stop();
414 sync_processor_
.reset(NULL
);
415 error_handler_
.reset();
416 session_tracker_
.Clear();
417 local_tab_map_
.clear();
418 local_tab_pool_
.Clear();
419 current_machine_tag_
.clear();
420 current_session_name_
.clear();
421 local_session_header_node_id_
= TabNodePool::kInvalidTabNodeID
;
424 syncer::SyncDataList
SessionsSyncManager::GetAllSyncData(
425 syncer::ModelType type
) const {
426 syncer::SyncDataList list
;
427 const SyncedSession
* session
= NULL
;
428 if (!session_tracker_
.LookupLocalSession(&session
))
429 return syncer::SyncDataList();
431 // First construct the header node.
432 sync_pb::EntitySpecifics header_entity
;
433 header_entity
.mutable_session()->set_session_tag(current_machine_tag());
434 sync_pb::SessionHeader
* header_specifics
=
435 header_entity
.mutable_session()->mutable_header();
436 header_specifics
->MergeFrom(session
->ToSessionHeader());
437 syncer::SyncData data
= syncer::SyncData::CreateLocalData(
438 current_machine_tag(), current_session_name_
, header_entity
);
439 list
.push_back(data
);
441 SyncedSession::SyncedWindowMap::const_iterator win_iter
;
442 for (win_iter
= session
->windows
.begin();
443 win_iter
!= session
->windows
.end(); ++win_iter
) {
444 std::vector
<SessionTab
*>::const_iterator tabs_iter
;
445 for (tabs_iter
= win_iter
->second
->tabs
.begin();
446 tabs_iter
!= win_iter
->second
->tabs
.end(); ++tabs_iter
) {
447 sync_pb::EntitySpecifics entity
;
448 sync_pb::SessionSpecifics
* specifics
= entity
.mutable_session();
449 specifics
->mutable_tab()->MergeFrom((*tabs_iter
)->ToSyncData());
450 specifics
->set_session_tag(current_machine_tag_
);
452 TabLinksMap::const_iterator tab_map_iter
= local_tab_map_
.find(
453 (*tabs_iter
)->tab_id
.id());
454 DCHECK(tab_map_iter
!= local_tab_map_
.end());
455 specifics
->set_tab_node_id(tab_map_iter
->second
->tab_node_id());
456 syncer::SyncData data
= syncer::SyncData::CreateLocalData(
457 TabNodePool::TabIdToTag(current_machine_tag_
,
458 specifics
->tab_node_id()),
459 current_session_name_
,
461 list
.push_back(data
);
467 bool SessionsSyncManager::GetLocalSession(
468 const SyncedSession
* * local_session
) {
469 if (current_machine_tag_
.empty())
471 *local_session
= session_tracker_
.GetSession(current_machine_tag());
475 syncer::SyncError
SessionsSyncManager::ProcessSyncChanges(
476 const tracked_objects::Location
& from_here
,
477 const syncer::SyncChangeList
& change_list
) {
478 if (!sync_processor_
.get()) {
479 syncer::SyncError
error(FROM_HERE
,
480 syncer::SyncError::DATATYPE_ERROR
,
481 "Models not yet associated.",
486 for (syncer::SyncChangeList::const_iterator it
= change_list
.begin();
487 it
!= change_list
.end(); ++it
) {
488 DCHECK(it
->IsValid());
489 DCHECK(it
->sync_data().GetSpecifics().has_session());
490 const sync_pb::SessionSpecifics
& session
=
491 it
->sync_data().GetSpecifics().session();
492 switch (it
->change_type()) {
493 case syncer::SyncChange::ACTION_DELETE
:
494 // Deletions are all or nothing (since we only ever delete entire
495 // sessions). Therefore we don't care if it's a tab node or meta node,
496 // and just ensure we've disassociated.
497 if (current_machine_tag() == session
.session_tag()) {
498 // Another client has attempted to delete our local data (possibly by
499 // error or a clock is inaccurate). Just ignore the deletion for now
500 // to avoid any possible ping-pong delete/reassociate sequence, but
501 // remember that this happened as our TabNodePool is inconsistent.
502 local_tab_pool_out_of_sync_
= true;
503 LOG(WARNING
) << "Local session data deleted. Ignoring until next "
504 << "local navigation event.";
505 } else if (session
.has_header()) {
506 // Disassociate only when header node is deleted. For tab node
507 // deletions, the header node will be updated and foreign tab will
509 DisassociateForeignSession(session
.session_tag());
512 case syncer::SyncChange::ACTION_ADD
:
513 case syncer::SyncChange::ACTION_UPDATE
:
514 if (current_machine_tag() == session
.session_tag()) {
515 // We should only ever receive a change to our own machine's session
516 // info if encryption was turned on. In that case, the data is still
517 // the same, so we can ignore.
518 LOG(WARNING
) << "Dropping modification to local session.";
519 return syncer::SyncError();
521 UpdateTrackerWithForeignSession(
522 session
, syncer::SyncDataRemote(it
->sync_data()).GetModifiedTime());
525 NOTREACHED() << "Processing sync changes failed, unknown change type.";
529 content::NotificationService::current()->Notify(
530 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED
,
531 content::Source
<Profile
>(profile_
),
532 content::NotificationService::NoDetails());
533 return syncer::SyncError();
536 syncer::SyncChange
SessionsSyncManager::TombstoneTab(
537 const sync_pb::SessionSpecifics
& tab
) {
538 if (!tab
.has_tab_node_id()) {
539 LOG(WARNING
) << "Old sessions node without tab node id; can't tombstone.";
540 return syncer::SyncChange();
542 return syncer::SyncChange(
544 SyncChange::ACTION_DELETE
,
545 SyncData::CreateLocalDelete(
546 TabNodePool::TabIdToTag(current_machine_tag(),
552 bool SessionsSyncManager::GetAllForeignSessions(
553 std::vector
<const SyncedSession
*>* sessions
) {
554 return session_tracker_
.LookupAllForeignSessions(sessions
);
557 bool SessionsSyncManager::InitFromSyncModel(
558 const syncer::SyncDataList
& sync_data
,
559 syncer::SyncDataList
* restored_tabs
,
560 syncer::SyncChangeList
* new_changes
) {
561 bool found_current_header
= false;
562 for (syncer::SyncDataList::const_iterator it
= sync_data
.begin();
563 it
!= sync_data
.end();
565 const syncer::SyncData
& data
= *it
;
566 DCHECK(data
.GetSpecifics().has_session());
567 const sync_pb::SessionSpecifics
& specifics
= data
.GetSpecifics().session();
568 if (specifics
.session_tag().empty() ||
569 (specifics
.has_tab() && (!specifics
.has_tab_node_id() ||
570 !specifics
.tab().has_tab_id()))) {
571 syncer::SyncChange
tombstone(TombstoneTab(specifics
));
572 if (tombstone
.IsValid())
573 new_changes
->push_back(tombstone
);
574 } else if (specifics
.session_tag() != current_machine_tag()) {
575 UpdateTrackerWithForeignSession(
576 specifics
, syncer::SyncDataRemote(data
).GetModifiedTime());
578 // This is previously stored local session information.
579 if (specifics
.has_header() && !found_current_header
) {
580 // This is our previous header node, reuse it.
581 found_current_header
= true;
582 if (specifics
.header().has_client_name())
583 current_session_name_
= specifics
.header().client_name();
585 if (specifics
.has_header() || !specifics
.has_tab()) {
586 LOG(WARNING
) << "Found more than one session header node with local "
588 syncer::SyncChange
tombstone(TombstoneTab(specifics
));
589 if (tombstone
.IsValid())
590 new_changes
->push_back(tombstone
);
592 // This is a valid old tab node, add it to the pool so it can be
593 // reused for reassociation.
594 local_tab_pool_
.AddTabNode(specifics
.tab_node_id());
595 restored_tabs
->push_back(*it
);
600 return found_current_header
;
603 void SessionsSyncManager::UpdateTrackerWithForeignSession(
604 const sync_pb::SessionSpecifics
& specifics
,
605 const base::Time
& modification_time
) {
606 std::string foreign_session_tag
= specifics
.session_tag();
607 DCHECK_NE(foreign_session_tag
, current_machine_tag());
609 SyncedSession
* foreign_session
=
610 session_tracker_
.GetSession(foreign_session_tag
);
611 if (specifics
.has_header()) {
612 // Read in the header data for this foreign session.
613 // Header data contains window information and ordered tab id's for each
616 if (!IsValidSessionHeader(specifics
.header())) {
617 LOG(WARNING
) << "Ignoring foreign session node with invalid header "
618 << "and tag " << foreign_session_tag
<< ".";
622 // Load (or create) the SyncedSession object for this client.
623 const sync_pb::SessionHeader
& header
= specifics
.header();
624 PopulateSessionHeaderFromSpecifics(header
,
628 // Reset the tab/window tracking for this session (must do this before
629 // we start calling PutWindowInSession and PutTabInWindow so that all
630 // unused tabs/windows get cleared by the CleanupSession(...) call).
631 session_tracker_
.ResetSessionTracking(foreign_session_tag
);
633 // Process all the windows and their tab information.
634 int num_windows
= header
.window_size();
635 DVLOG(1) << "Associating " << foreign_session_tag
<< " with "
636 << num_windows
<< " windows.";
638 for (int i
= 0; i
< num_windows
; ++i
) {
639 const sync_pb::SessionWindow
& window_s
= header
.window(i
);
640 SessionID::id_type window_id
= window_s
.window_id();
641 session_tracker_
.PutWindowInSession(foreign_session_tag
,
643 BuildSyncedSessionFromSpecifics(foreign_session_tag
,
646 foreign_session
->windows
[window_id
]);
648 // Delete any closed windows and unused tabs as necessary.
649 session_tracker_
.CleanupSession(foreign_session_tag
);
650 } else if (specifics
.has_tab()) {
651 const sync_pb::SessionTab
& tab_s
= specifics
.tab();
652 SessionID::id_type tab_id
= tab_s
.tab_id();
654 session_tracker_
.GetTab(foreign_session_tag
,
656 specifics
.tab_node_id());
658 // Update SessionTab based on protobuf.
659 tab
->SetFromSyncData(tab_s
, modification_time
);
661 // If a favicon or favicon urls are present, load the URLs and visit
662 // times into the in-memory favicon cache.
663 RefreshFaviconVisitTimesFromForeignTab(tab_s
, modification_time
);
665 // Update the last modified time.
666 if (foreign_session
->modified_time
< modification_time
)
667 foreign_session
->modified_time
= modification_time
;
669 LOG(WARNING
) << "Ignoring foreign session node with missing header/tab "
670 << "fields and tag " << foreign_session_tag
<< ".";
674 void SessionsSyncManager::InitializeCurrentMachineTag() {
675 DCHECK(current_machine_tag_
.empty());
676 std::string persisted_guid
;
677 persisted_guid
= sync_prefs_
.GetSyncSessionsGUID();
678 if (!persisted_guid
.empty()) {
679 current_machine_tag_
= persisted_guid
;
680 DVLOG(1) << "Restoring persisted session sync guid: " << persisted_guid
;
682 DCHECK(local_device_
);
683 std::string cache_guid
= local_device_
->GetLocalSyncCacheGUID();
684 DCHECK(!cache_guid
.empty());
685 current_machine_tag_
= BuildMachineTag(cache_guid
);
686 DVLOG(1) << "Creating session sync guid: " << current_machine_tag_
;
687 sync_prefs_
.SetSyncSessionsGUID(current_machine_tag_
);
690 local_tab_pool_
.SetMachineTag(current_machine_tag_
);
694 void SessionsSyncManager::PopulateSessionHeaderFromSpecifics(
695 const sync_pb::SessionHeader
& header_specifics
,
697 SyncedSession
* session_header
) {
698 if (header_specifics
.has_client_name())
699 session_header
->session_name
= header_specifics
.client_name();
700 if (header_specifics
.has_device_type()) {
701 switch (header_specifics
.device_type()) {
702 case sync_pb::SyncEnums_DeviceType_TYPE_WIN
:
703 session_header
->device_type
= SyncedSession::TYPE_WIN
;
705 case sync_pb::SyncEnums_DeviceType_TYPE_MAC
:
706 session_header
->device_type
= SyncedSession::TYPE_MACOSX
;
708 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX
:
709 session_header
->device_type
= SyncedSession::TYPE_LINUX
;
711 case sync_pb::SyncEnums_DeviceType_TYPE_CROS
:
712 session_header
->device_type
= SyncedSession::TYPE_CHROMEOS
;
714 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE
:
715 session_header
->device_type
= SyncedSession::TYPE_PHONE
;
717 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET
:
718 session_header
->device_type
= SyncedSession::TYPE_TABLET
;
720 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER
:
721 // Intentionally fall-through
723 session_header
->device_type
= SyncedSession::TYPE_OTHER
;
727 session_header
->modified_time
= mtime
;
731 void SessionsSyncManager::BuildSyncedSessionFromSpecifics(
732 const std::string
& session_tag
,
733 const sync_pb::SessionWindow
& specifics
,
735 SessionWindow
* session_window
) {
736 if (specifics
.has_window_id())
737 session_window
->window_id
.set_id(specifics
.window_id());
738 if (specifics
.has_selected_tab_index())
739 session_window
->selected_tab_index
= specifics
.selected_tab_index();
740 if (specifics
.has_browser_type()) {
741 if (specifics
.browser_type() ==
742 sync_pb::SessionWindow_BrowserType_TYPE_TABBED
) {
743 session_window
->type
= 1;
745 session_window
->type
= 2;
748 session_window
->timestamp
= mtime
;
749 session_window
->tabs
.resize(specifics
.tab_size(), NULL
);
750 for (int i
= 0; i
< specifics
.tab_size(); i
++) {
751 SessionID::id_type tab_id
= specifics
.tab(i
);
752 session_tracker_
.PutTabInWindow(session_tag
,
753 session_window
->window_id
.id(),
759 void SessionsSyncManager::RefreshFaviconVisitTimesFromForeignTab(
760 const sync_pb::SessionTab
& tab
, const base::Time
& modification_time
) {
761 // First go through and iterate over all the navigations, checking if any
762 // have valid favicon urls.
763 for (int i
= 0; i
< tab
.navigation_size(); ++i
) {
764 if (!tab
.navigation(i
).favicon_url().empty()) {
765 const std::string
& page_url
= tab
.navigation(i
).virtual_url();
766 const std::string
& favicon_url
= tab
.navigation(i
).favicon_url();
767 favicon_cache_
.OnReceivedSyncFavicon(GURL(page_url
),
770 syncer::TimeToProtoTime(
776 bool SessionsSyncManager::GetSyncedFaviconForPageURL(
777 const std::string
& page_url
,
778 scoped_refptr
<base::RefCountedMemory
>* favicon_png
) const {
779 return favicon_cache_
.GetSyncedFaviconForPageURL(GURL(page_url
), favicon_png
);
782 void SessionsSyncManager::DeleteForeignSession(const std::string
& tag
) {
783 syncer::SyncChangeList changes
;
784 DeleteForeignSessionInternal(tag
, &changes
);
785 sync_processor_
->ProcessSyncChanges(FROM_HERE
, changes
);
788 void SessionsSyncManager::DeleteForeignSessionInternal(
789 const std::string
& tag
, syncer::SyncChangeList
* change_output
) {
790 if (tag
== current_machine_tag()) {
791 LOG(ERROR
) << "Attempting to delete local session. This is not currently "
796 std::set
<int> tab_node_ids_to_delete
;
797 session_tracker_
.LookupTabNodeIds(tag
, &tab_node_ids_to_delete
);
798 if (!DisassociateForeignSession(tag
)) {
799 // We don't have any data for this session, our work here is done!
803 // Prepare deletes for the meta-node as well as individual tab nodes.
804 change_output
->push_back(syncer::SyncChange(
806 SyncChange::ACTION_DELETE
,
807 SyncData::CreateLocalDelete(tag
, syncer::SESSIONS
)));
809 for (std::set
<int>::const_iterator it
= tab_node_ids_to_delete
.begin();
810 it
!= tab_node_ids_to_delete
.end();
812 change_output
->push_back(syncer::SyncChange(
814 SyncChange::ACTION_DELETE
,
815 SyncData::CreateLocalDelete(TabNodePool::TabIdToTag(tag
, *it
),
818 content::NotificationService::current()->Notify(
819 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED
,
820 content::Source
<Profile
>(profile_
),
821 content::NotificationService::NoDetails());
824 bool SessionsSyncManager::DisassociateForeignSession(
825 const std::string
& foreign_session_tag
) {
826 if (foreign_session_tag
== current_machine_tag()) {
827 DVLOG(1) << "Local session deleted! Doing nothing until a navigation is "
831 DVLOG(1) << "Disassociating session " << foreign_session_tag
;
832 return session_tracker_
.DeleteSession(foreign_session_tag
);
836 GURL
SessionsSyncManager::GetCurrentVirtualURL(
837 const SyncedTabDelegate
& tab_delegate
) {
838 const int current_index
= tab_delegate
.GetCurrentEntryIndex();
839 const int pending_index
= tab_delegate
.GetPendingEntryIndex();
840 const NavigationEntry
* current_entry
=
841 (current_index
== pending_index
) ?
842 tab_delegate
.GetPendingEntry() :
843 tab_delegate
.GetEntryAtIndex(current_index
);
844 return current_entry
->GetVirtualURL();
848 GURL
SessionsSyncManager::GetCurrentFaviconURL(
849 const SyncedTabDelegate
& tab_delegate
) {
850 const int current_index
= tab_delegate
.GetCurrentEntryIndex();
851 const int pending_index
= tab_delegate
.GetPendingEntryIndex();
852 const NavigationEntry
* current_entry
=
853 (current_index
== pending_index
) ?
854 tab_delegate
.GetPendingEntry() :
855 tab_delegate
.GetEntryAtIndex(current_index
);
856 return (current_entry
->GetFavicon().valid
?
857 current_entry
->GetFavicon().url
:
861 bool SessionsSyncManager::GetForeignSession(
862 const std::string
& tag
,
863 std::vector
<const SessionWindow
*>* windows
) {
864 return session_tracker_
.LookupSessionWindows(tag
, windows
);
867 bool SessionsSyncManager::GetForeignTab(
868 const std::string
& tag
,
869 const SessionID::id_type tab_id
,
870 const SessionTab
** tab
) {
871 const SessionTab
* synced_tab
= NULL
;
872 bool success
= session_tracker_
.LookupSessionTab(tag
,
880 void SessionsSyncManager::LocalTabDelegateToSpecifics(
881 const SyncedTabDelegate
& tab_delegate
,
882 sync_pb::SessionSpecifics
* specifics
) {
883 SessionTab
* session_tab
= NULL
;
885 session_tracker_
.GetTab(current_machine_tag(),
886 tab_delegate
.GetSessionId(),
887 tab_delegate
.GetSyncId());
888 SetSessionTabFromDelegate(tab_delegate
, base::Time::Now(), session_tab
);
889 sync_pb::SessionTab tab_s
= session_tab
->ToSyncData();
890 specifics
->set_session_tag(current_machine_tag_
);
891 specifics
->set_tab_node_id(tab_delegate
.GetSyncId());
892 specifics
->mutable_tab()->CopyFrom(tab_s
);
895 void SessionsSyncManager::AssociateRestoredPlaceholderTab(
896 const SyncedTabDelegate
& tab_delegate
,
897 SessionID::id_type new_tab_id
,
898 const syncer::SyncDataList
& restored_tabs
,
899 syncer::SyncChangeList
* change_output
) {
900 DCHECK_NE(tab_delegate
.GetSyncId(), TabNodePool::kInvalidTabNodeID
);
901 // Rewrite the tab using |restored_tabs| to retrieve the specifics.
902 if (restored_tabs
.empty()) {
903 DLOG(WARNING
) << "Can't Update tab ID.";
907 for (syncer::SyncDataList::const_iterator it
= restored_tabs
.begin();
908 it
!= restored_tabs
.end();
910 if (it
->GetSpecifics().session().tab_node_id() !=
911 tab_delegate
.GetSyncId()) {
915 sync_pb::EntitySpecifics entity
;
916 sync_pb::SessionSpecifics
* specifics
= entity
.mutable_session();
917 specifics
->CopyFrom(it
->GetSpecifics().session());
918 DCHECK(specifics
->has_tab());
920 // Update tab node pool with the new association.
921 local_tab_pool_
.ReassociateTabNode(tab_delegate
.GetSyncId(),
923 TabLink
* tab_link
= new TabLink(tab_delegate
.GetSyncId(),
925 local_tab_map_
[new_tab_id
] = make_linked_ptr
<TabLink
>(tab_link
);
927 if (specifics
->tab().tab_id() == new_tab_id
)
930 // The tab_id changed (e.g due to session restore), so update sync.
931 specifics
->mutable_tab()->set_tab_id(new_tab_id
);
932 syncer::SyncData data
= syncer::SyncData::CreateLocalData(
933 TabNodePool::TabIdToTag(current_machine_tag_
,
934 specifics
->tab_node_id()),
935 current_session_name_
,
937 change_output
->push_back(syncer::SyncChange(
938 FROM_HERE
, syncer::SyncChange::ACTION_UPDATE
, data
));
944 void SessionsSyncManager::SetSessionTabFromDelegate(
945 const SyncedTabDelegate
& tab_delegate
,
947 SessionTab
* session_tab
) {
949 session_tab
->window_id
.set_id(tab_delegate
.GetWindowId());
950 session_tab
->tab_id
.set_id(tab_delegate
.GetSessionId());
951 session_tab
->tab_visual_index
= 0;
952 // Use -1 to indicate that the index hasn't been set properly yet.
953 session_tab
->current_navigation_index
= -1;
954 session_tab
->pinned
= tab_delegate
.IsPinned();
955 session_tab
->extension_app_id
= tab_delegate
.GetExtensionAppId();
956 session_tab
->user_agent_override
.clear();
957 session_tab
->timestamp
= mtime
;
958 const int current_index
= tab_delegate
.GetCurrentEntryIndex();
959 const int pending_index
= tab_delegate
.GetPendingEntryIndex();
960 const int min_index
= std::max(0, current_index
- kMaxSyncNavigationCount
);
961 const int max_index
= std::min(current_index
+ kMaxSyncNavigationCount
,
962 tab_delegate
.GetEntryCount());
963 bool is_supervised
= tab_delegate
.ProfileIsSupervised();
964 session_tab
->navigations
.clear();
966 for (int i
= min_index
; i
< max_index
; ++i
) {
967 const NavigationEntry
* entry
= (i
== pending_index
) ?
968 tab_delegate
.GetPendingEntry() : tab_delegate
.GetEntryAtIndex(i
);
970 if (!entry
->GetVirtualURL().is_valid())
973 // Set current_navigation_index to the index in navigations.
974 if (i
== current_index
)
975 session_tab
->current_navigation_index
= session_tab
->navigations
.size();
977 session_tab
->navigations
.push_back(
978 SerializedNavigationEntry::FromNavigationEntry(i
, *entry
));
980 session_tab
->navigations
.back().set_blocked_state(
981 SerializedNavigationEntry::STATE_ALLOWED
);
985 // If the current navigation is invalid, set the index to the end of the
987 if (session_tab
->current_navigation_index
< 0) {
988 session_tab
->current_navigation_index
=
989 session_tab
->navigations
.size() - 1;
993 const std::vector
<const NavigationEntry
*>& blocked_navigations
=
994 *tab_delegate
.GetBlockedNavigations();
995 int offset
= session_tab
->navigations
.size();
996 for (size_t i
= 0; i
< blocked_navigations
.size(); ++i
) {
997 session_tab
->navigations
.push_back(
998 SerializedNavigationEntry::FromNavigationEntry(
999 i
+ offset
, *blocked_navigations
[i
]));
1000 session_tab
->navigations
.back().set_blocked_state(
1001 SerializedNavigationEntry::STATE_BLOCKED
);
1002 // TODO(bauerb): Add categories
1005 session_tab
->session_storage_persistent_id
.clear();
1008 FaviconCache
* SessionsSyncManager::GetFaviconCache() {
1009 return &favicon_cache_
;
1012 SyncedWindowDelegatesGetter
*
1013 SessionsSyncManager::GetSyncedWindowDelegatesGetter() const {
1014 return synced_window_getter_
.get();
1017 void SessionsSyncManager::DoGarbageCollection() {
1018 std::vector
<const SyncedSession
*> sessions
;
1019 if (!GetAllForeignSessions(&sessions
))
1020 return; // No foreign sessions.
1022 // Iterate through all the sessions and delete any with age older than
1023 // |stale_session_threshold_days_|.
1024 syncer::SyncChangeList changes
;
1025 for (std::vector
<const SyncedSession
*>::const_iterator iter
=
1026 sessions
.begin(); iter
!= sessions
.end(); ++iter
) {
1027 const SyncedSession
* session
= *iter
;
1028 int session_age_in_days
=
1029 (base::Time::Now() - session
->modified_time
).InDays();
1030 std::string session_tag
= session
->session_tag
;
1031 if (session_age_in_days
> 0 && // If false, local clock is not trustworty.
1032 static_cast<size_t>(session_age_in_days
) >
1033 stale_session_threshold_days_
) {
1034 DVLOG(1) << "Found stale session " << session_tag
1035 << " with age " << session_age_in_days
<< ", deleting.";
1036 DeleteForeignSessionInternal(session_tag
, &changes
);
1040 if (!changes
.empty())
1041 sync_processor_
->ProcessSyncChanges(FROM_HERE
, changes
);
1044 }; // namespace browser_sync