1 // Copyright 2013 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/sessions2/sessions_sync_manager.h"
7 #include "chrome/browser/chrome_notification_types.h"
8 #if !defined(OS_ANDROID)
9 #include "chrome/browser/network_time/navigation_time_helper.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/sync/glue/synced_tab_delegate.h"
13 #include "chrome/browser/sync/glue/synced_window_delegate.h"
14 #include "chrome/common/url_constants.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 syncer::SyncChange
;
29 using syncer::SyncData
;
31 namespace browser_sync
{
33 // Maximum number of favicons to sync.
34 // TODO(zea): pull this from the server.
35 static const int kMaxSyncFavicons
= 200;
37 // The maximum number of navigations in each direction we care to sync.
38 static const int kMaxSyncNavigationCount
= 6;
40 // The URL at which the set of synced tabs is displayed. We treat it differently
41 // from all other URL's as accessing it triggers a sync refresh of Sessions.
42 static const char kNTPOpenTabSyncURL
[] = "chrome://newtab/#open_tabs";
44 // Default number of days without activity after which a session is considered
45 // stale and becomes a candidate for garbage collection.
46 static const size_t kDefaultStaleSessionThresholdDays
= 14; // 2 weeks.
48 SessionsSyncManager::SessionsSyncManager(
50 SyncInternalApiDelegate
* delegate
,
51 scoped_ptr
<LocalSessionEventRouter
> router
)
52 : favicon_cache_(profile
, kMaxSyncFavicons
),
53 sync_prefs_(profile
->GetPrefs()),
56 local_session_header_node_id_(TabNodePool2::kInvalidTabNodeID
),
57 stale_session_threshold_days_(kDefaultStaleSessionThresholdDays
),
58 local_event_router_(router
.Pass()) {
61 LocalSessionEventRouter::~LocalSessionEventRouter() {}
63 SessionsSyncManager::~SessionsSyncManager() {
66 // Returns the GUID-based string that should be used for
67 // |SessionsSyncManager::current_machine_tag_|.
68 static std::string
BuildMachineTag(const std::string
& cache_guid
) {
69 std::string machine_tag
= "session_sync";
70 machine_tag
.append(cache_guid
);
74 syncer::SyncMergeResult
SessionsSyncManager::MergeDataAndStartSyncing(
75 syncer::ModelType type
,
76 const syncer::SyncDataList
& initial_sync_data
,
77 scoped_ptr
<syncer::SyncChangeProcessor
> sync_processor
,
78 scoped_ptr
<syncer::SyncErrorFactory
> error_handler
) {
79 syncer::SyncMergeResult
merge_result(type
);
80 DCHECK(session_tracker_
.Empty());
81 DCHECK_EQ(0U, local_tab_pool_
.Capacity());
83 error_handler_
= error_handler
.Pass();
84 sync_processor_
= sync_processor
.Pass();
86 local_session_header_node_id_
= TabNodePool2::kInvalidTabNodeID
;
87 scoped_ptr
<DeviceInfo
> local_device_info(delegate_
->GetLocalDeviceInfo());
88 syncer::SyncChangeList new_changes
;
90 // Make sure we have a machine tag. We do this now (versus earlier) as it's
91 // a conveniently safe time to assert sync is ready and the cache_guid is
93 if (current_machine_tag_
.empty())
94 InitializeCurrentMachineTag();
95 if (local_device_info
) {
96 current_session_name_
= local_device_info
->client_name();
98 merge_result
.set_error(error_handler_
->CreateAndUploadError(
100 "Failed to get device info for machine tag."));
103 session_tracker_
.SetLocalSessionTag(current_machine_tag_
);
105 // First, we iterate over sync data to update our session_tracker_.
106 if (!InitFromSyncModel(initial_sync_data
, &new_changes
)) {
107 // The sync db didn't have a header node for us. Create one.
108 sync_pb::EntitySpecifics specifics
;
109 sync_pb::SessionSpecifics
* base_specifics
= specifics
.mutable_session();
110 base_specifics
->set_session_tag(current_machine_tag());
111 sync_pb::SessionHeader
* header_s
= base_specifics
->mutable_header();
112 header_s
->set_client_name(current_session_name_
);
113 header_s
->set_device_type(DeviceInfo::GetLocalDeviceType());
114 syncer::SyncData data
= syncer::SyncData::CreateLocalData(
115 current_machine_tag(), current_session_name_
, specifics
);
116 new_changes
.push_back(syncer::SyncChange(
117 FROM_HERE
, syncer::SyncChange::ACTION_ADD
, data
));
120 #if defined(OS_ANDROID)
121 std::string
sync_machine_tag(BuildMachineTag(
122 delegate_
->GetLocalSyncCacheGUID()));
123 if (current_machine_tag_
.compare(sync_machine_tag
) != 0)
124 DeleteForeignSessionInternal(sync_machine_tag
, &new_changes
);
127 // Check if anything has changed on the local client side.
128 AssociateWindows(RELOAD_TABS
, &new_changes
);
130 merge_result
.set_error(
131 sync_processor_
->ProcessSyncChanges(FROM_HERE
, new_changes
));
133 local_event_router_
->StartRoutingTo(this);
137 void SessionsSyncManager::AssociateWindows(
138 ReloadTabsOption option
,
139 syncer::SyncChangeList
* change_output
) {
140 const std::string local_tag
= current_machine_tag();
141 sync_pb::SessionSpecifics specifics
;
142 specifics
.set_session_tag(local_tag
);
143 sync_pb::SessionHeader
* header_s
= specifics
.mutable_header();
144 SyncedSession
* current_session
= session_tracker_
.GetSession(local_tag
);
145 current_session
->modified_time
= base::Time::Now();
146 header_s
->set_client_name(current_session_name_
);
147 header_s
->set_device_type(DeviceInfo::GetLocalDeviceType());
149 session_tracker_
.ResetSessionTracking(local_tag
);
150 std::set
<SyncedWindowDelegate
*> windows
=
151 SyncedWindowDelegate::GetSyncedWindowDelegates();
153 for (std::set
<SyncedWindowDelegate
*>::const_iterator i
=
154 windows
.begin(); i
!= windows
.end(); ++i
) {
155 // Make sure the window has tabs and a viewable window. The viewable window
156 // check is necessary because, for example, when a browser is closed the
157 // destructor is not necessarily run immediately. This means its possible
158 // for us to get a handle to a browser that is about to be removed. If
159 // the tab count is 0 or the window is NULL, the browser is about to be
160 // deleted, so we ignore it.
161 if (ShouldSyncWindow(*i
) && (*i
)->GetTabCount() && (*i
)->HasWindow()) {
162 sync_pb::SessionWindow window_s
;
163 SessionID::id_type window_id
= (*i
)->GetSessionId();
164 DVLOG(1) << "Associating window " << window_id
<< " with "
165 << (*i
)->GetTabCount() << " tabs.";
166 window_s
.set_window_id(window_id
);
167 // Note: We don't bother to set selected tab index anymore. We still
168 // consume it when receiving foreign sessions, as reading it is free, but
169 // it triggers too many sync cycles with too little value to make setting
171 if ((*i
)->IsTypeTabbed()) {
172 window_s
.set_browser_type(
173 sync_pb::SessionWindow_BrowserType_TYPE_TABBED
);
175 window_s
.set_browser_type(
176 sync_pb::SessionWindow_BrowserType_TYPE_POPUP
);
179 bool found_tabs
= false;
180 for (int j
= 0; j
< (*i
)->GetTabCount(); ++j
) {
181 SessionID::id_type tab_id
= (*i
)->GetTabIdAt(j
);
182 SyncedTabDelegate
* synced_tab
= (*i
)->GetTabAt(j
);
184 // GetTabAt can return a null tab; in that case just skip it.
188 if (!synced_tab
->HasWebContents()) {
189 // For tabs without WebContents update the |tab_id|, as it could have
190 // changed after a session restore.
191 // Note: We cannot check if a tab is valid if it has no WebContents.
192 // We assume any such tab is valid and leave the contents of
193 // corresponding sync node unchanged.
194 if (synced_tab
->GetSyncId() > TabNodePool2::kInvalidTabNodeID
&&
195 tab_id
> TabNodePool2::kInvalidTabID
) {
196 UpdateTabIdIfNecessary(*synced_tab
, tab_id
, change_output
);
198 window_s
.add_tab(tab_id
);
203 if (RELOAD_TABS
== option
)
204 AssociateTab(synced_tab
, change_output
);
206 // If the tab is valid, it would have been added to the tracker either
207 // by the above AssociateTab call (at association time), or by the
208 // change processor calling AssociateTab for all modified tabs.
209 // Therefore, we can key whether this window has valid tabs based on
210 // the tab's presence in the tracker.
211 const SessionTab
* tab
= NULL
;
212 if (session_tracker_
.LookupSessionTab(local_tag
, tab_id
, &tab
)) {
214 window_s
.add_tab(tab_id
);
218 sync_pb::SessionWindow
* header_window
= header_s
->add_window();
219 *header_window
= window_s
;
221 // Update this window's representation in the synced session tracker.
222 session_tracker_
.PutWindowInSession(local_tag
, window_id
);
223 BuildSyncedSessionFromSpecifics(local_tag
,
225 current_session
->modified_time
,
226 current_session
->windows
[window_id
]);
230 local_tab_pool_
.DeleteUnassociatedTabNodes(change_output
);
231 session_tracker_
.CleanupSession(local_tag
);
233 // Always update the header. Sync takes care of dropping this update
234 // if the entity specifics are identical (i.e windows, client name did
236 sync_pb::EntitySpecifics entity
;
237 entity
.mutable_session()->CopyFrom(specifics
);
238 syncer::SyncData data
= syncer::SyncData::CreateLocalData(
239 current_machine_tag(), current_session_name_
, entity
);
240 change_output
->push_back(syncer::SyncChange(
241 FROM_HERE
, syncer::SyncChange::ACTION_UPDATE
, data
));
244 void SessionsSyncManager::AssociateTab(SyncedTabDelegate
* const tab
,
245 syncer::SyncChangeList
* change_output
) {
246 DCHECK(tab
->HasWebContents());
247 SessionID::id_type tab_id
= tab
->GetSessionId();
248 if (tab
->IsBeingDestroyed()) {
249 // This tab is closing.
250 TabLinksMap::iterator tab_iter
= local_tab_map_
.find(tab_id
);
251 if (tab_iter
== local_tab_map_
.end()) {
252 // We aren't tracking this tab (for example, sync setting page).
255 local_tab_pool_
.FreeTabNode(tab_iter
->second
->tab_node_id(),
257 local_tab_map_
.erase(tab_iter
);
260 if (!ShouldSyncTab(*tab
))
263 TabLinksMap::iterator local_tab_map_iter
= local_tab_map_
.find(tab_id
);
264 TabLink
* tab_link
= NULL
;
266 if (local_tab_map_iter
== local_tab_map_
.end()) {
267 int tab_node_id
= tab
->GetSyncId();
268 // If there is an old sync node for the tab, reuse it. If this is a new
269 // tab, get a sync node for it.
270 if (!local_tab_pool_
.IsUnassociatedTabNode(tab_node_id
)) {
271 tab_node_id
= local_tab_pool_
.GetFreeTabNode(change_output
);
272 tab
->SetSyncId(tab_node_id
);
274 local_tab_pool_
.AssociateTabNode(tab_node_id
, tab_id
);
275 tab_link
= new TabLink(tab_node_id
, tab
);
276 local_tab_map_
[tab_id
] = make_linked_ptr
<TabLink
>(tab_link
);
278 // This tab is already associated with a sync node, reuse it.
279 // Note: on some platforms the tab object may have changed, so we ensure
280 // the tab link is up to date.
281 tab_link
= local_tab_map_iter
->second
.get();
282 local_tab_map_iter
->second
->set_tab(tab
);
285 DCHECK_NE(tab_link
->tab_node_id(), TabNodePool2::kInvalidTabNodeID
);
286 DVLOG(1) << "Reloading tab " << tab_id
<< " from window "
287 << tab
->GetWindowId();
289 // Write to sync model.
290 sync_pb::EntitySpecifics specifics
;
291 LocalTabDelegateToSpecifics(*tab
, specifics
.mutable_session());
292 syncer::SyncData data
= syncer::SyncData::CreateLocalData(
293 TabNodePool2::TabIdToTag(current_machine_tag_
,
294 tab_link
->tab_node_id()),
295 current_session_name_
,
297 change_output
->push_back(syncer::SyncChange(
298 FROM_HERE
, syncer::SyncChange::ACTION_UPDATE
, data
));
300 const GURL new_url
= GetCurrentVirtualURL(*tab
);
301 if (new_url
!= tab_link
->url()) {
302 tab_link
->set_url(new_url
);
303 favicon_cache_
.OnFaviconVisited(new_url
, GetCurrentFaviconURL(*tab
));
306 session_tracker_
.GetSession(current_machine_tag())->modified_time
=
310 void SessionsSyncManager::OnLocalTabModified(SyncedTabDelegate
* modified_tab
) {
311 const content::NavigationEntry
* entry
= modified_tab
->GetActiveEntry();
312 if (!modified_tab
->IsBeingDestroyed() &&
314 entry
->GetVirtualURL().is_valid() &&
315 entry
->GetVirtualURL().spec() == kNTPOpenTabSyncURL
) {
316 DVLOG(1) << "Triggering sync refresh for sessions datatype.";
317 const syncer::ModelTypeSet
types(syncer::SESSIONS
);
318 content::NotificationService::current()->Notify(
319 chrome::NOTIFICATION_SYNC_REFRESH_LOCAL
,
320 content::Source
<Profile
>(profile_
),
321 content::Details
<const syncer::ModelTypeSet
>(&types
));
324 syncer::SyncChangeList changes
;
325 // Associate tabs first so the synced session tracker is aware of them.
326 AssociateTab(modified_tab
, &changes
);
327 // Note, we always associate windows because it's possible a tab became
328 // "interesting" by going to a valid URL, in which case it needs to be added
329 // to the window's tab information.
330 AssociateWindows(DONT_RELOAD_TABS
, &changes
);
331 sync_processor_
->ProcessSyncChanges(FROM_HERE
, changes
);
334 void SessionsSyncManager::OnFaviconPageUrlsUpdated(
335 const std::set
<GURL
>& updated_favicon_page_urls
) {
336 // TODO(zea): consider a separate container for tabs with outstanding favicon
337 // loads so we don't have to iterate through all tabs comparing urls.
338 for (std::set
<GURL
>::const_iterator i
= updated_favicon_page_urls
.begin();
339 i
!= updated_favicon_page_urls
.end(); ++i
) {
340 for (TabLinksMap::iterator tab_iter
= local_tab_map_
.begin();
341 tab_iter
!= local_tab_map_
.end();
343 if (tab_iter
->second
->url() == *i
)
344 favicon_cache_
.OnPageFaviconUpdated(*i
);
349 bool SessionsSyncManager::ShouldSyncTab(const SyncedTabDelegate
& tab
) const {
350 if (tab
.profile() != profile_
)
353 if (SyncedWindowDelegate::FindSyncedWindowDelegateWithId(
354 tab
.GetWindowId()) == NULL
) {
358 // Does the tab have a valid NavigationEntry?
359 if (tab
.ProfileIsManaged() && tab
.GetBlockedNavigations()->size() > 0)
362 int entry_count
= tab
.GetEntryCount();
363 if (entry_count
== 0)
364 return false; // This deliberately ignores a new pending entry.
366 int pending_index
= tab
.GetPendingEntryIndex();
367 bool found_valid_url
= false;
368 for (int i
= 0; i
< entry_count
; ++i
) {
369 const content::NavigationEntry
* entry
= (i
== pending_index
) ?
370 tab
.GetPendingEntry() : tab
.GetEntryAtIndex(i
);
373 const GURL
& virtual_url
= entry
->GetVirtualURL();
374 if (virtual_url
.is_valid() &&
375 !virtual_url
.SchemeIs(chrome::kChromeUIScheme
) &&
376 !virtual_url
.SchemeIs(chrome::kChromeNativeScheme
) &&
377 !virtual_url
.SchemeIsFile()) {
378 found_valid_url
= true;
381 return found_valid_url
;
385 bool SessionsSyncManager::ShouldSyncWindow(
386 const SyncedWindowDelegate
* window
) {
389 return window
->IsTypeTabbed() || window
->IsTypePopup();
392 void SessionsSyncManager::StopSyncing(syncer::ModelType type
) {
393 local_event_router_
->Stop();
394 sync_processor_
.reset(NULL
);
395 error_handler_
.reset();
396 session_tracker_
.Clear();
397 local_tab_map_
.clear();
398 local_tab_pool_
.Clear();
399 current_machine_tag_
.clear();
400 current_session_name_
.clear();
401 local_session_header_node_id_
= TabNodePool2::kInvalidTabNodeID
;
404 syncer::SyncDataList
SessionsSyncManager::GetAllSyncData(
405 syncer::ModelType type
) const {
406 syncer::SyncDataList list
;
407 const SyncedSession
* session
= NULL
;
408 if (!session_tracker_
.LookupLocalSession(&session
))
409 return syncer::SyncDataList();
411 // First construct the header node.
412 sync_pb::EntitySpecifics header_entity
;
413 header_entity
.mutable_session()->set_session_tag(current_machine_tag());
414 sync_pb::SessionHeader
* header_specifics
=
415 header_entity
.mutable_session()->mutable_header();
416 header_specifics
->MergeFrom(session
->ToSessionHeader());
417 syncer::SyncData data
= syncer::SyncData::CreateLocalData(
418 current_machine_tag(), current_session_name_
, header_entity
);
419 list
.push_back(data
);
421 SyncedSession::SyncedWindowMap::const_iterator win_iter
;
422 for (win_iter
= session
->windows
.begin();
423 win_iter
!= session
->windows
.end(); ++win_iter
) {
424 std::vector
<SessionTab
*>::const_iterator tabs_iter
;
425 for (tabs_iter
= win_iter
->second
->tabs
.begin();
426 tabs_iter
!= win_iter
->second
->tabs
.end(); ++tabs_iter
) {
427 sync_pb::EntitySpecifics entity
;
428 sync_pb::SessionSpecifics
* specifics
= entity
.mutable_session();
429 specifics
->mutable_tab()->MergeFrom((*tabs_iter
)->ToSyncData());
430 specifics
->set_session_tag(current_machine_tag_
);
432 TabLinksMap::const_iterator tab_map_iter
= local_tab_map_
.find(
433 (*tabs_iter
)->tab_id
.id());
434 DCHECK(tab_map_iter
!= local_tab_map_
.end());
435 specifics
->set_tab_node_id(tab_map_iter
->second
->tab_node_id());
436 syncer::SyncData data
= syncer::SyncData::CreateLocalData(
437 TabNodePool2::TabIdToTag(current_machine_tag_
,
438 specifics
->tab_node_id()),
439 current_session_name_
,
441 list
.push_back(data
);
447 bool SessionsSyncManager::GetLocalSession(
448 const SyncedSession
* * local_session
) {
449 if (current_machine_tag_
.empty())
451 *local_session
= session_tracker_
.GetSession(current_machine_tag());
455 syncer::SyncError
SessionsSyncManager::ProcessSyncChanges(
456 const tracked_objects::Location
& from_here
,
457 const syncer::SyncChangeList
& change_list
) {
458 if (!sync_processor_
.get()) {
459 syncer::SyncError
error(FROM_HERE
,
460 syncer::SyncError::DATATYPE_ERROR
,
461 "Models not yet associated.",
466 for (syncer::SyncChangeList::const_iterator it
= change_list
.begin();
467 it
!= change_list
.end(); ++it
) {
468 DCHECK(it
->IsValid());
469 DCHECK(it
->sync_data().GetSpecifics().has_session());
470 const sync_pb::SessionSpecifics
& session
=
471 it
->sync_data().GetSpecifics().session();
472 switch (it
->change_type()) {
473 case syncer::SyncChange::ACTION_DELETE
:
474 // Deletions are all or nothing (since we only ever delete entire
475 // sessions). Therefore we don't care if it's a tab node or meta node,
476 // and just ensure we've disassociated.
477 if (current_machine_tag() == session
.session_tag()) {
478 // Another client has attempted to delete our local data (possibly by
479 // error or a clock is inaccurate). Just ignore the deletion for now
480 // to avoid any possible ping-pong delete/reassociate sequence.
481 // TODO(tim): Bug 98892. This corrupts TabNodePool. Perform full
483 LOG(WARNING
) << "Local session data deleted. Ignoring until next "
484 << "local navigation event.";
485 } else if (session
.has_header()) {
486 // Disassociate only when header node is deleted. For tab node
487 // deletions, the header node will be updated and foreign tab will
489 DisassociateForeignSession(session
.session_tag());
492 case syncer::SyncChange::ACTION_ADD
:
493 case syncer::SyncChange::ACTION_UPDATE
:
494 if (current_machine_tag() == session
.session_tag()) {
495 // We should only ever receive a change to our own machine's session
496 // info if encryption was turned on. In that case, the data is still
497 // the same, so we can ignore.
498 LOG(WARNING
) << "Dropping modification to local session.";
499 return syncer::SyncError();
501 UpdateTrackerWithForeignSession(
502 session
, it
->sync_data().GetRemoteModifiedTime());
505 NOTREACHED() << "Processing sync changes failed, unknown change type.";
509 content::NotificationService::current()->Notify(
510 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED
,
511 content::Source
<Profile
>(profile_
),
512 content::NotificationService::NoDetails());
513 return syncer::SyncError();
516 syncer::SyncChange
SessionsSyncManager::TombstoneTab(
517 const sync_pb::SessionSpecifics
& tab
) {
518 if (!tab
.has_tab_node_id()) {
519 LOG(WARNING
) << "Old sessions node without tab node id; can't tombstone.";
520 return syncer::SyncChange();
522 return syncer::SyncChange(
524 SyncChange::ACTION_DELETE
,
525 SyncData::CreateLocalDelete(
526 TabNodePool2::TabIdToTag(current_machine_tag(),
532 bool SessionsSyncManager::GetAllForeignSessions(
533 std::vector
<const SyncedSession
*>* sessions
) {
534 return session_tracker_
.LookupAllForeignSessions(sessions
);
537 bool SessionsSyncManager::InitFromSyncModel(
538 const syncer::SyncDataList
& sync_data
,
539 syncer::SyncChangeList
* new_changes
) {
540 bool found_current_header
= false;
541 for (syncer::SyncDataList::const_iterator it
= sync_data
.begin();
542 it
!= sync_data
.end();
544 const syncer::SyncData
& data
= *it
;
545 DCHECK(data
.GetSpecifics().has_session());
546 const sync_pb::SessionSpecifics
& specifics
= data
.GetSpecifics().session();
547 if (specifics
.session_tag().empty() ||
548 (specifics
.has_tab() && (!specifics
.has_tab_node_id() ||
549 !specifics
.tab().has_tab_id()))) {
550 syncer::SyncChange
tombstone(TombstoneTab(specifics
));
551 if (tombstone
.IsValid())
552 new_changes
->push_back(tombstone
);
553 } else if (specifics
.session_tag() != current_machine_tag()) {
554 UpdateTrackerWithForeignSession(specifics
, data
.GetRemoteModifiedTime());
556 // This is previously stored local session information.
557 if (specifics
.has_header() && !found_current_header
) {
558 // This is our previous header node, reuse it.
559 found_current_header
= true;
560 if (specifics
.header().has_client_name())
561 current_session_name_
= specifics
.header().client_name();
563 if (specifics
.has_header() || !specifics
.has_tab()) {
564 LOG(WARNING
) << "Found more than one session header node with local "
566 syncer::SyncChange
tombstone(TombstoneTab(specifics
));
567 if (tombstone
.IsValid())
568 new_changes
->push_back(tombstone
);
570 // This is a valid old tab node, add it to the pool so it can be
571 // reused for reassociation.
572 local_tab_pool_
.AddTabNode(specifics
.tab_node_id());
577 return found_current_header
;
580 void SessionsSyncManager::UpdateTrackerWithForeignSession(
581 const sync_pb::SessionSpecifics
& specifics
,
582 const base::Time
& modification_time
) {
583 std::string foreign_session_tag
= specifics
.session_tag();
584 DCHECK_NE(foreign_session_tag
, current_machine_tag());
586 SyncedSession
* foreign_session
=
587 session_tracker_
.GetSession(foreign_session_tag
);
588 if (specifics
.has_header()) {
589 // Read in the header data for this foreign session.
590 // Header data contains window information and ordered tab id's for each
593 // Load (or create) the SyncedSession object for this client.
594 const sync_pb::SessionHeader
& header
= specifics
.header();
595 PopulateSessionHeaderFromSpecifics(header
,
599 // Reset the tab/window tracking for this session (must do this before
600 // we start calling PutWindowInSession and PutTabInWindow so that all
601 // unused tabs/windows get cleared by the CleanupSession(...) call).
602 session_tracker_
.ResetSessionTracking(foreign_session_tag
);
604 // Process all the windows and their tab information.
605 int num_windows
= header
.window_size();
606 DVLOG(1) << "Associating " << foreign_session_tag
<< " with "
607 << num_windows
<< " windows.";
609 for (int i
= 0; i
< num_windows
; ++i
) {
610 const sync_pb::SessionWindow
& window_s
= header
.window(i
);
611 SessionID::id_type window_id
= window_s
.window_id();
612 session_tracker_
.PutWindowInSession(foreign_session_tag
,
614 BuildSyncedSessionFromSpecifics(foreign_session_tag
,
617 foreign_session
->windows
[window_id
]);
619 // Delete any closed windows and unused tabs as necessary.
620 session_tracker_
.CleanupSession(foreign_session_tag
);
621 } else if (specifics
.has_tab()) {
622 const sync_pb::SessionTab
& tab_s
= specifics
.tab();
623 SessionID::id_type tab_id
= tab_s
.tab_id();
625 session_tracker_
.GetTab(foreign_session_tag
,
627 specifics
.tab_node_id());
629 // Update SessionTab based on protobuf.
630 tab
->SetFromSyncData(tab_s
, modification_time
);
632 // If a favicon or favicon urls are present, load the URLs and visit
633 // times into the in-memory favicon cache.
634 RefreshFaviconVisitTimesFromForeignTab(tab_s
, modification_time
);
636 // Update the last modified time.
637 if (foreign_session
->modified_time
< modification_time
)
638 foreign_session
->modified_time
= modification_time
;
640 LOG(WARNING
) << "Ignoring foreign session node with missing header/tab "
641 << "fields and tag " << foreign_session_tag
<< ".";
645 void SessionsSyncManager::InitializeCurrentMachineTag() {
646 DCHECK(current_machine_tag_
.empty());
647 std::string persisted_guid
;
648 persisted_guid
= sync_prefs_
.GetSyncSessionsGUID();
649 if (!persisted_guid
.empty()) {
650 current_machine_tag_
= persisted_guid
;
651 DVLOG(1) << "Restoring persisted session sync guid: " << persisted_guid
;
653 current_machine_tag_
= BuildMachineTag(delegate_
->GetLocalSyncCacheGUID());
654 DVLOG(1) << "Creating session sync guid: " << current_machine_tag_
;
655 sync_prefs_
.SetSyncSessionsGUID(current_machine_tag_
);
658 local_tab_pool_
.SetMachineTag(current_machine_tag_
);
662 void SessionsSyncManager::PopulateSessionHeaderFromSpecifics(
663 const sync_pb::SessionHeader
& header_specifics
,
665 SyncedSession
* session_header
) {
666 if (header_specifics
.has_client_name())
667 session_header
->session_name
= header_specifics
.client_name();
668 if (header_specifics
.has_device_type()) {
669 switch (header_specifics
.device_type()) {
670 case sync_pb::SyncEnums_DeviceType_TYPE_WIN
:
671 session_header
->device_type
= SyncedSession::TYPE_WIN
;
673 case sync_pb::SyncEnums_DeviceType_TYPE_MAC
:
674 session_header
->device_type
= SyncedSession::TYPE_MACOSX
;
676 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX
:
677 session_header
->device_type
= SyncedSession::TYPE_LINUX
;
679 case sync_pb::SyncEnums_DeviceType_TYPE_CROS
:
680 session_header
->device_type
= SyncedSession::TYPE_CHROMEOS
;
682 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE
:
683 session_header
->device_type
= SyncedSession::TYPE_PHONE
;
685 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET
:
686 session_header
->device_type
= SyncedSession::TYPE_TABLET
;
688 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER
:
689 // Intentionally fall-through
691 session_header
->device_type
= SyncedSession::TYPE_OTHER
;
695 session_header
->modified_time
= mtime
;
699 void SessionsSyncManager::BuildSyncedSessionFromSpecifics(
700 const std::string
& session_tag
,
701 const sync_pb::SessionWindow
& specifics
,
703 SessionWindow
* session_window
) {
704 if (specifics
.has_window_id())
705 session_window
->window_id
.set_id(specifics
.window_id());
706 if (specifics
.has_selected_tab_index())
707 session_window
->selected_tab_index
= specifics
.selected_tab_index();
708 if (specifics
.has_browser_type()) {
709 if (specifics
.browser_type() ==
710 sync_pb::SessionWindow_BrowserType_TYPE_TABBED
) {
711 session_window
->type
= 1;
713 session_window
->type
= 2;
716 session_window
->timestamp
= mtime
;
717 session_window
->tabs
.resize(specifics
.tab_size(), NULL
);
718 for (int i
= 0; i
< specifics
.tab_size(); i
++) {
719 SessionID::id_type tab_id
= specifics
.tab(i
);
720 session_tracker_
.PutTabInWindow(session_tag
,
721 session_window
->window_id
.id(),
727 void SessionsSyncManager::RefreshFaviconVisitTimesFromForeignTab(
728 const sync_pb::SessionTab
& tab
, const base::Time
& modification_time
) {
729 // First go through and iterate over all the navigations, checking if any
730 // have valid favicon urls.
731 for (int i
= 0; i
< tab
.navigation_size(); ++i
) {
732 if (!tab
.navigation(i
).favicon_url().empty()) {
733 const std::string
& page_url
= tab
.navigation(i
).virtual_url();
734 const std::string
& favicon_url
= tab
.navigation(i
).favicon_url();
735 favicon_cache_
.OnReceivedSyncFavicon(GURL(page_url
),
738 syncer::TimeToProtoTime(
744 bool SessionsSyncManager::GetSyncedFaviconForPageURL(
745 const std::string
& page_url
,
746 scoped_refptr
<base::RefCountedMemory
>* favicon_png
) const {
747 return favicon_cache_
.GetSyncedFaviconForPageURL(GURL(page_url
), favicon_png
);
750 void SessionsSyncManager::DeleteForeignSession(const std::string
& tag
) {
751 syncer::SyncChangeList changes
;
752 DeleteForeignSessionInternal(tag
, &changes
);
753 sync_processor_
->ProcessSyncChanges(FROM_HERE
, changes
);
756 void SessionsSyncManager::DeleteForeignSessionInternal(
757 const std::string
& tag
, syncer::SyncChangeList
* change_output
) {
758 if (tag
== current_machine_tag()) {
759 LOG(ERROR
) << "Attempting to delete local session. This is not currently "
764 std::set
<int> tab_node_ids_to_delete
;
765 session_tracker_
.LookupTabNodeIds(tag
, &tab_node_ids_to_delete
);
766 if (!DisassociateForeignSession(tag
)) {
767 // We don't have any data for this session, our work here is done!
771 // Prepare deletes for the meta-node as well as individual tab nodes.
772 change_output
->push_back(syncer::SyncChange(
774 SyncChange::ACTION_DELETE
,
775 SyncData::CreateLocalDelete(tag
, syncer::SESSIONS
)));
777 for (std::set
<int>::const_iterator it
= tab_node_ids_to_delete
.begin();
778 it
!= tab_node_ids_to_delete
.end();
780 change_output
->push_back(syncer::SyncChange(
782 SyncChange::ACTION_DELETE
,
783 SyncData::CreateLocalDelete(TabNodePool2::TabIdToTag(tag
, *it
),
788 bool SessionsSyncManager::DisassociateForeignSession(
789 const std::string
& foreign_session_tag
) {
790 if (foreign_session_tag
== current_machine_tag()) {
791 DVLOG(1) << "Local session deleted! Doing nothing until a navigation is "
795 DVLOG(1) << "Disassociating session " << foreign_session_tag
;
796 return session_tracker_
.DeleteSession(foreign_session_tag
);
800 GURL
SessionsSyncManager::GetCurrentVirtualURL(
801 const SyncedTabDelegate
& tab_delegate
) {
802 const int current_index
= tab_delegate
.GetCurrentEntryIndex();
803 const int pending_index
= tab_delegate
.GetPendingEntryIndex();
804 const NavigationEntry
* current_entry
=
805 (current_index
== pending_index
) ?
806 tab_delegate
.GetPendingEntry() :
807 tab_delegate
.GetEntryAtIndex(current_index
);
808 return current_entry
->GetVirtualURL();
812 GURL
SessionsSyncManager::GetCurrentFaviconURL(
813 const SyncedTabDelegate
& tab_delegate
) {
814 const int current_index
= tab_delegate
.GetCurrentEntryIndex();
815 const int pending_index
= tab_delegate
.GetPendingEntryIndex();
816 const NavigationEntry
* current_entry
=
817 (current_index
== pending_index
) ?
818 tab_delegate
.GetPendingEntry() :
819 tab_delegate
.GetEntryAtIndex(current_index
);
820 return (current_entry
->GetFavicon().valid
?
821 current_entry
->GetFavicon().url
:
825 bool SessionsSyncManager::GetForeignSession(
826 const std::string
& tag
,
827 std::vector
<const SessionWindow
*>* windows
) {
828 return session_tracker_
.LookupSessionWindows(tag
, windows
);
831 bool SessionsSyncManager::GetForeignTab(
832 const std::string
& tag
,
833 const SessionID::id_type tab_id
,
834 const SessionTab
** tab
) {
835 const SessionTab
* synced_tab
= NULL
;
836 bool success
= session_tracker_
.LookupSessionTab(tag
,
844 void SessionsSyncManager::LocalTabDelegateToSpecifics(
845 const SyncedTabDelegate
& tab_delegate
,
846 sync_pb::SessionSpecifics
* specifics
) {
847 SessionTab
* session_tab
= NULL
;
849 session_tracker_
.GetTab(current_machine_tag(),
850 tab_delegate
.GetSessionId(),
851 tab_delegate
.GetSyncId());
852 SetSessionTabFromDelegate(tab_delegate
, base::Time::Now(), session_tab
);
853 sync_pb::SessionTab tab_s
= session_tab
->ToSyncData();
854 specifics
->set_session_tag(current_machine_tag_
);
855 specifics
->set_tab_node_id(tab_delegate
.GetSyncId());
856 specifics
->mutable_tab()->CopyFrom(tab_s
);
859 void SessionsSyncManager::UpdateTabIdIfNecessary(
860 const SyncedTabDelegate
& tab_delegate
,
861 SessionID::id_type new_tab_id
,
862 syncer::SyncChangeList
* change_output
) {
863 DCHECK_NE(tab_delegate
.GetSyncId(), TabNodePool2::kInvalidTabNodeID
);
864 SessionID::id_type old_tab_id
=
865 local_tab_pool_
.GetTabIdFromTabNodeId(tab_delegate
.GetSyncId());
866 if (old_tab_id
!= new_tab_id
) {
867 // Rewrite the tab. We don't have a way to get the old
868 // specifics here currently.
869 // TODO(tim): Is this too slow? Should we cache specifics?
870 sync_pb::EntitySpecifics specifics
;
871 LocalTabDelegateToSpecifics(tab_delegate
,
872 specifics
.mutable_session());
874 // Update tab node pool with the new association.
875 local_tab_pool_
.ReassociateTabNode(tab_delegate
.GetSyncId(), new_tab_id
);
876 syncer::SyncData data
= syncer::SyncData::CreateLocalData(
877 TabNodePool2::TabIdToTag(current_machine_tag_
,
878 tab_delegate
.GetSyncId()),
879 current_session_name_
, specifics
);
880 change_output
->push_back(syncer::SyncChange(
881 FROM_HERE
, syncer::SyncChange::ACTION_UPDATE
, data
));
886 void SessionsSyncManager::SetSessionTabFromDelegate(
887 const SyncedTabDelegate
& tab_delegate
,
889 SessionTab
* session_tab
) {
891 session_tab
->window_id
.set_id(tab_delegate
.GetWindowId());
892 session_tab
->tab_id
.set_id(tab_delegate
.GetSessionId());
893 session_tab
->tab_visual_index
= 0;
894 session_tab
->current_navigation_index
= tab_delegate
.GetCurrentEntryIndex();
895 session_tab
->pinned
= tab_delegate
.IsPinned();
896 session_tab
->extension_app_id
= tab_delegate
.GetExtensionAppId();
897 session_tab
->user_agent_override
.clear();
898 session_tab
->timestamp
= mtime
;
899 const int current_index
= tab_delegate
.GetCurrentEntryIndex();
900 const int pending_index
= tab_delegate
.GetPendingEntryIndex();
901 const int min_index
= std::max(0, current_index
- kMaxSyncNavigationCount
);
902 const int max_index
= std::min(current_index
+ kMaxSyncNavigationCount
,
903 tab_delegate
.GetEntryCount());
904 bool is_managed
= tab_delegate
.ProfileIsManaged();
905 session_tab
->navigations
.clear();
907 #if !defined(OS_ANDROID)
908 // For getting navigation time in network time.
909 NavigationTimeHelper
* nav_time_helper
=
910 tab_delegate
.HasWebContents() ?
911 NavigationTimeHelper::FromWebContents(tab_delegate
.GetWebContents()) :
915 for (int i
= min_index
; i
< max_index
; ++i
) {
916 const NavigationEntry
* entry
= (i
== pending_index
) ?
917 tab_delegate
.GetPendingEntry() : tab_delegate
.GetEntryAtIndex(i
);
919 if (!entry
->GetVirtualURL().is_valid())
922 scoped_ptr
<content::NavigationEntry
> network_time_entry(
923 content::NavigationEntry::Create(*entry
));
924 #if !defined(OS_ANDROID)
925 if (nav_time_helper
) {
926 network_time_entry
->SetTimestamp(
927 nav_time_helper
->GetNavigationTime(entry
));
931 session_tab
->navigations
.push_back(
932 SerializedNavigationEntry::FromNavigationEntry(i
, *network_time_entry
));
934 session_tab
->navigations
.back().set_blocked_state(
935 SerializedNavigationEntry::STATE_ALLOWED
);
940 const std::vector
<const NavigationEntry
*>& blocked_navigations
=
941 *tab_delegate
.GetBlockedNavigations();
942 int offset
= session_tab
->navigations
.size();
943 for (size_t i
= 0; i
< blocked_navigations
.size(); ++i
) {
944 session_tab
->navigations
.push_back(
945 SerializedNavigationEntry::FromNavigationEntry(
946 i
+ offset
, *blocked_navigations
[i
]));
947 session_tab
->navigations
.back().set_blocked_state(
948 SerializedNavigationEntry::STATE_BLOCKED
);
949 // TODO(bauerb): Add categories
952 session_tab
->session_storage_persistent_id
.clear();
955 FaviconCache
* SessionsSyncManager::GetFaviconCache() {
956 return &favicon_cache_
;
959 void SessionsSyncManager::DoGarbageCollection() {
960 std::vector
<const SyncedSession
*> sessions
;
961 if (!GetAllForeignSessions(&sessions
))
962 return; // No foreign sessions.
964 // Iterate through all the sessions and delete any with age older than
965 // |stale_session_threshold_days_|.
966 syncer::SyncChangeList changes
;
967 for (std::vector
<const SyncedSession
*>::const_iterator iter
=
968 sessions
.begin(); iter
!= sessions
.end(); ++iter
) {
969 const SyncedSession
* session
= *iter
;
970 int session_age_in_days
=
971 (base::Time::Now() - session
->modified_time
).InDays();
972 std::string session_tag
= session
->session_tag
;
973 if (session_age_in_days
> 0 && // If false, local clock is not trustworty.
974 static_cast<size_t>(session_age_in_days
) >
975 stale_session_threshold_days_
) {
976 DVLOG(1) << "Found stale session " << session_tag
977 << " with age " << session_age_in_days
<< ", deleting.";
978 DeleteForeignSessionInternal(session_tag
, &changes
);
982 if (!changes
.empty())
983 sync_processor_
->ProcessSyncChanges(FROM_HERE
, changes
);
986 }; // namespace browser_sync