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