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.
4
5 #include "chrome/browser/sync/sessions/sessions_sync_manager.h"
6
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"
25
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;
32
33 namespace browser_sync {
34
35 // Maximum number of favicons to sync.
36 // TODO(zea): pull this from the server.
37 static const int kMaxSyncFavicons = 200;
38
39 // The maximum number of navigations in each direction we care to sync.
40 static const int kMaxSyncNavigationCount = 6;
41
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";
45
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.
49
50 // |local_device| is owned by ProfileSyncService, its lifetime exceeds
51 // lifetime of SessionSyncManager.
SessionsSyncManager(Profile * profile,LocalDeviceInfoProvider * local_device,scoped_ptr<LocalSessionEventRouter> router)52 SessionsSyncManager::SessionsSyncManager(
53 Profile* profile,
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()),
59 profile_(profile),
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()) {
65 }
66
~LocalSessionEventRouter()67 LocalSessionEventRouter::~LocalSessionEventRouter() {}
68
~SessionsSyncManager()69 SessionsSyncManager::~SessionsSyncManager() {
70 }
71
72 // Returns the GUID-based string that should be used for
73 // |SessionsSyncManager::current_machine_tag_|.
BuildMachineTag(const std::string & cache_guid)74 static std::string BuildMachineTag(const std::string& cache_guid) {
75 std::string machine_tag = "session_sync";
76 machine_tag.append(cache_guid);
77 return machine_tag;
78 }
79
MergeDataAndStartSyncing(syncer::ModelType type,const syncer::SyncDataList & initial_sync_data,scoped_ptr<syncer::SyncChangeProcessor> sync_processor,scoped_ptr<syncer::SyncErrorFactory> error_handler)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());
88
89 error_handler_ = error_handler.Pass();
90 sync_processor_ = sync_processor.Pass();
91
92 local_session_header_node_id_ = TabNodePool::kInvalidTabNodeID;
93
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
96 // initialized.
97 if (current_machine_tag_.empty()) {
98 InitializeCurrentMachineTag();
99 }
100
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();
107 } else {
108 merge_result.set_error(error_handler_->CreateAndUploadError(
109 FROM_HERE,
110 "Failed to get local device info."));
111 return merge_result;
112 }
113
114 session_tracker_.SetLocalSessionTag(current_machine_tag_);
115
116 syncer::SyncChangeList new_changes;
117
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));
132 }
133
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);
139 #endif
140
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;
144
145 merge_result.set_error(
146 sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes));
147
148 local_event_router_->StartRoutingTo(this);
149 return merge_result;
150 }
151
AssociateWindows(ReloadTabsOption option,const syncer::SyncDataList & restored_tabs,syncer::SyncChangeList * change_output)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());
168
169 session_tracker_.ResetSessionTracking(local_tag);
170 std::set<SyncedWindowDelegate*> windows =
171 synced_window_getter_->GetSyncedWindowDelegates();
172
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
191 // it worthwhile.
192 if ((*i)->IsTypeTabbed()) {
193 window_s.set_browser_type(
194 sync_pb::SessionWindow_BrowserType_TYPE_TABBED);
195 } else {
196 window_s.set_browser_type(
197 sync_pb::SessionWindow_BrowserType_TYPE_POPUP);
198 }
199
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);
204
205 // GetTabAt can return a null tab; in that case just skip it.
206 if (!synced_tab)
207 continue;
208
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);
219 found_tabs = true;
220 window_s.add_tab(tab_id);
221 }
222 continue;
223 }
224
225 if (RELOAD_TABS == option)
226 AssociateTab(synced_tab, change_output);
227
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)) {
235 found_tabs = true;
236 window_s.add_tab(tab_id);
237 }
238 }
239 if (found_tabs) {
240 sync_pb::SessionWindow* header_window = header_s->add_window();
241 *header_window = window_s;
242
243 // Update this window's representation in the synced session tracker.
244 session_tracker_.PutWindowInSession(local_tag, window_id);
245 BuildSyncedSessionFromSpecifics(local_tag,
246 window_s,
247 current_session->modified_time,
248 current_session->windows[window_id]);
249 }
250 }
251 }
252 local_tab_pool_.DeleteUnassociatedTabNodes(change_output);
253 session_tracker_.CleanupSession(local_tag);
254
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
257 // not change).
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));
264 }
265
AssociateTab(SyncedTabDelegate * const tab,syncer::SyncChangeList * change_output)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_)
271 return;
272
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).
278 return;
279 }
280 local_tab_pool_.FreeTabNode(tab_iter->second->tab_node_id(),
281 change_output);
282 local_tab_map_.erase(tab_iter);
283 return;
284 }
285
286 if (!sessions_util::ShouldSyncTab(*tab))
287 return;
288
289 TabLinksMap::iterator local_tab_map_iter = local_tab_map_.find(tab_id);
290 TabLink* tab_link = NULL;
291
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);
299 }
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);
303 } else {
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);
309 }
310 DCHECK(tab_link);
311 DCHECK_NE(tab_link->tab_node_id(), TabNodePool::kInvalidTabNodeID);
312 DVLOG(1) << "Reloading tab " << tab_id << " from window "
313 << tab->GetWindowId();
314
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_,
322 specifics);
323 change_output->push_back(syncer::SyncChange(
324 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data));
325
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));
330 }
331
332 session_tracker_.GetSession(current_machine_tag())->modified_time =
333 base::Time::Now();
334 }
335
RebuildAssociations()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());
341
342 StopSyncing(syncer::SESSIONS);
343 MergeDataAndStartSyncing(
344 syncer::SESSIONS, data, processor.Pass(), error_handler.Pass());
345 }
346
IsValidSessionHeader(const sync_pb::SessionHeader & header)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;
357 if (!success)
358 return false;
359 }
360 }
361
362 return true;
363 }
364
OnLocalTabModified(SyncedTabDelegate * modified_tab)365 void SessionsSyncManager::OnLocalTabModified(SyncedTabDelegate* modified_tab) {
366 const content::NavigationEntry* entry = modified_tab->GetActiveEntry();
367 if (!modified_tab->IsBeingDestroyed() &&
368 entry &&
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));
377 }
378
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_);
384 return;
385 }
386
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);
395 }
396
OnFaviconPageUrlsUpdated(const std::set<GURL> & updated_favicon_page_urls)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();
405 ++tab_iter) {
406 if (tab_iter->second->url() == *i)
407 favicon_cache_.OnPageFaviconUpdated(*i);
408 }
409 }
410 }
411
StopSyncing(syncer::ModelType type)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;
422 }
423
GetAllSyncData(syncer::ModelType type) const424 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();
430
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);
440
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_);
451
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_,
460 entity);
461 list.push_back(data);
462 }
463 }
464 return list;
465 }
466
GetLocalSession(const SyncedSession ** local_session)467 bool SessionsSyncManager::GetLocalSession(
468 const SyncedSession* * local_session) {
469 if (current_machine_tag_.empty())
470 return false;
471 *local_session = session_tracker_.GetSession(current_machine_tag());
472 return true;
473 }
474
ProcessSyncChanges(const tracked_objects::Location & from_here,const syncer::SyncChangeList & change_list)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.",
482 syncer::SESSIONS);
483 return error;
484 }
485
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
508 // get deleted.
509 DisassociateForeignSession(session.session_tag());
510 }
511 continue;
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();
520 }
521 UpdateTrackerWithForeignSession(
522 session, syncer::SyncDataRemote(it->sync_data()).GetModifiedTime());
523 break;
524 default:
525 NOTREACHED() << "Processing sync changes failed, unknown change type.";
526 }
527 }
528
529 content::NotificationService::current()->Notify(
530 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
531 content::Source<Profile>(profile_),
532 content::NotificationService::NoDetails());
533 return syncer::SyncError();
534 }
535
TombstoneTab(const sync_pb::SessionSpecifics & tab)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();
541 } else {
542 return syncer::SyncChange(
543 FROM_HERE,
544 SyncChange::ACTION_DELETE,
545 SyncData::CreateLocalDelete(
546 TabNodePool::TabIdToTag(current_machine_tag(),
547 tab.tab_node_id()),
548 syncer::SESSIONS));
549 }
550 }
551
GetAllForeignSessions(std::vector<const SyncedSession * > * sessions)552 bool SessionsSyncManager::GetAllForeignSessions(
553 std::vector<const SyncedSession*>* sessions) {
554 return session_tracker_.LookupAllForeignSessions(sessions);
555 }
556
InitFromSyncModel(const syncer::SyncDataList & sync_data,syncer::SyncDataList * restored_tabs,syncer::SyncChangeList * new_changes)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();
564 ++it) {
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());
577 } else {
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();
584 } else {
585 if (specifics.has_header() || !specifics.has_tab()) {
586 LOG(WARNING) << "Found more than one session header node with local "
587 << "tag.";
588 syncer::SyncChange tombstone(TombstoneTab(specifics));
589 if (tombstone.IsValid())
590 new_changes->push_back(tombstone);
591 } else {
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);
596 }
597 }
598 }
599 }
600 return found_current_header;
601 }
602
UpdateTrackerWithForeignSession(const sync_pb::SessionSpecifics & specifics,const base::Time & modification_time)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());
608
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
614 // window.
615
616 if (!IsValidSessionHeader(specifics.header())) {
617 LOG(WARNING) << "Ignoring foreign session node with invalid header "
618 << "and tag " << foreign_session_tag << ".";
619 return;
620 }
621
622 // Load (or create) the SyncedSession object for this client.
623 const sync_pb::SessionHeader& header = specifics.header();
624 PopulateSessionHeaderFromSpecifics(header,
625 modification_time,
626 foreign_session);
627
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);
632
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.";
637
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,
642 window_id);
643 BuildSyncedSessionFromSpecifics(foreign_session_tag,
644 window_s,
645 modification_time,
646 foreign_session->windows[window_id]);
647 }
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();
653 SessionTab* tab =
654 session_tracker_.GetTab(foreign_session_tag,
655 tab_id,
656 specifics.tab_node_id());
657
658 // Update SessionTab based on protobuf.
659 tab->SetFromSyncData(tab_s, modification_time);
660
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);
664
665 // Update the last modified time.
666 if (foreign_session->modified_time < modification_time)
667 foreign_session->modified_time = modification_time;
668 } else {
669 LOG(WARNING) << "Ignoring foreign session node with missing header/tab "
670 << "fields and tag " << foreign_session_tag << ".";
671 }
672 }
673
InitializeCurrentMachineTag()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;
681 } else {
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_);
688 }
689
690 local_tab_pool_.SetMachineTag(current_machine_tag_);
691 }
692
693 // static
PopulateSessionHeaderFromSpecifics(const sync_pb::SessionHeader & header_specifics,base::Time mtime,SyncedSession * session_header)694 void SessionsSyncManager::PopulateSessionHeaderFromSpecifics(
695 const sync_pb::SessionHeader& header_specifics,
696 base::Time mtime,
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;
704 break;
705 case sync_pb::SyncEnums_DeviceType_TYPE_MAC:
706 session_header->device_type = SyncedSession::TYPE_MACOSX;
707 break;
708 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX:
709 session_header->device_type = SyncedSession::TYPE_LINUX;
710 break;
711 case sync_pb::SyncEnums_DeviceType_TYPE_CROS:
712 session_header->device_type = SyncedSession::TYPE_CHROMEOS;
713 break;
714 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE:
715 session_header->device_type = SyncedSession::TYPE_PHONE;
716 break;
717 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET:
718 session_header->device_type = SyncedSession::TYPE_TABLET;
719 break;
720 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER:
721 // Intentionally fall-through
722 default:
723 session_header->device_type = SyncedSession::TYPE_OTHER;
724 break;
725 }
726 }
727 session_header->modified_time = mtime;
728 }
729
730 // static
BuildSyncedSessionFromSpecifics(const std::string & session_tag,const sync_pb::SessionWindow & specifics,base::Time mtime,SessionWindow * session_window)731 void SessionsSyncManager::BuildSyncedSessionFromSpecifics(
732 const std::string& session_tag,
733 const sync_pb::SessionWindow& specifics,
734 base::Time mtime,
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;
744 } else {
745 session_window->type = 2;
746 }
747 }
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(),
754 tab_id,
755 i);
756 }
757 }
758
RefreshFaviconVisitTimesFromForeignTab(const sync_pb::SessionTab & tab,const base::Time & modification_time)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),
768 GURL(favicon_url),
769 std::string(),
770 syncer::TimeToProtoTime(
771 modification_time));
772 }
773 }
774 }
775
GetSyncedFaviconForPageURL(const std::string & page_url,scoped_refptr<base::RefCountedMemory> * favicon_png) const776 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);
780 }
781
DeleteForeignSession(const std::string & tag)782 void SessionsSyncManager::DeleteForeignSession(const std::string& tag) {
783 syncer::SyncChangeList changes;
784 DeleteForeignSessionInternal(tag, &changes);
785 sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
786 }
787
DeleteForeignSessionInternal(const std::string & tag,syncer::SyncChangeList * change_output)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 "
792 << "supported.";
793 return;
794 }
795
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!
800 return;
801 }
802
803 // Prepare deletes for the meta-node as well as individual tab nodes.
804 change_output->push_back(syncer::SyncChange(
805 FROM_HERE,
806 SyncChange::ACTION_DELETE,
807 SyncData::CreateLocalDelete(tag, syncer::SESSIONS)));
808
809 for (std::set<int>::const_iterator it = tab_node_ids_to_delete.begin();
810 it != tab_node_ids_to_delete.end();
811 ++it) {
812 change_output->push_back(syncer::SyncChange(
813 FROM_HERE,
814 SyncChange::ACTION_DELETE,
815 SyncData::CreateLocalDelete(TabNodePool::TabIdToTag(tag, *it),
816 syncer::SESSIONS)));
817 }
818 content::NotificationService::current()->Notify(
819 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
820 content::Source<Profile>(profile_),
821 content::NotificationService::NoDetails());
822 }
823
DisassociateForeignSession(const std::string & foreign_session_tag)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 "
828 << "triggered.";
829 return false;
830 }
831 DVLOG(1) << "Disassociating session " << foreign_session_tag;
832 return session_tracker_.DeleteSession(foreign_session_tag);
833 }
834
835 // static
GetCurrentVirtualURL(const SyncedTabDelegate & tab_delegate)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();
845 }
846
847 // static
GetCurrentFaviconURL(const SyncedTabDelegate & tab_delegate)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 :
858 GURL());
859 }
860
GetForeignSession(const std::string & tag,std::vector<const SessionWindow * > * windows)861 bool SessionsSyncManager::GetForeignSession(
862 const std::string& tag,
863 std::vector<const SessionWindow*>* windows) {
864 return session_tracker_.LookupSessionWindows(tag, windows);
865 }
866
GetForeignTab(const std::string & tag,const SessionID::id_type tab_id,const SessionTab ** tab)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,
873 tab_id,
874 &synced_tab);
875 if (success)
876 *tab = synced_tab;
877 return success;
878 }
879
LocalTabDelegateToSpecifics(const SyncedTabDelegate & tab_delegate,sync_pb::SessionSpecifics * specifics)880 void SessionsSyncManager::LocalTabDelegateToSpecifics(
881 const SyncedTabDelegate& tab_delegate,
882 sync_pb::SessionSpecifics* specifics) {
883 SessionTab* session_tab = NULL;
884 session_tab =
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);
893 }
894
AssociateRestoredPlaceholderTab(const SyncedTabDelegate & tab_delegate,SessionID::id_type new_tab_id,const syncer::SyncDataList & restored_tabs,syncer::SyncChangeList * change_output)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.";
904 return;
905 }
906
907 for (syncer::SyncDataList::const_iterator it = restored_tabs.begin();
908 it != restored_tabs.end();
909 ++it) {
910 if (it->GetSpecifics().session().tab_node_id() !=
911 tab_delegate.GetSyncId()) {
912 continue;
913 }
914
915 sync_pb::EntitySpecifics entity;
916 sync_pb::SessionSpecifics* specifics = entity.mutable_session();
917 specifics->CopyFrom(it->GetSpecifics().session());
918 DCHECK(specifics->has_tab());
919
920 // Update tab node pool with the new association.
921 local_tab_pool_.ReassociateTabNode(tab_delegate.GetSyncId(),
922 new_tab_id);
923 TabLink* tab_link = new TabLink(tab_delegate.GetSyncId(),
924 &tab_delegate);
925 local_tab_map_[new_tab_id] = make_linked_ptr<TabLink>(tab_link);
926
927 if (specifics->tab().tab_id() == new_tab_id)
928 return;
929
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_,
936 entity);
937 change_output->push_back(syncer::SyncChange(
938 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data));
939 return;
940 }
941 }
942
943 // static.
SetSessionTabFromDelegate(const SyncedTabDelegate & tab_delegate,base::Time mtime,SessionTab * session_tab)944 void SessionsSyncManager::SetSessionTabFromDelegate(
945 const SyncedTabDelegate& tab_delegate,
946 base::Time mtime,
947 SessionTab* session_tab) {
948 DCHECK(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();
965
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);
969 DCHECK(entry);
970 if (!entry->GetVirtualURL().is_valid())
971 continue;
972
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();
976
977 session_tab->navigations.push_back(
978 SerializedNavigationEntry::FromNavigationEntry(i, *entry));
979 if (is_supervised) {
980 session_tab->navigations.back().set_blocked_state(
981 SerializedNavigationEntry::STATE_ALLOWED);
982 }
983 }
984
985 // If the current navigation is invalid, set the index to the end of the
986 // navigation array.
987 if (session_tab->current_navigation_index < 0) {
988 session_tab->current_navigation_index =
989 session_tab->navigations.size() - 1;
990 }
991
992 if (is_supervised) {
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
1003 }
1004 }
1005 session_tab->session_storage_persistent_id.clear();
1006 }
1007
GetFaviconCache()1008 FaviconCache* SessionsSyncManager::GetFaviconCache() {
1009 return &favicon_cache_;
1010 }
1011
1012 SyncedWindowDelegatesGetter*
GetSyncedWindowDelegatesGetter() const1013 SessionsSyncManager::GetSyncedWindowDelegatesGetter() const {
1014 return synced_window_getter_.get();
1015 }
1016
DoGarbageCollection()1017 void SessionsSyncManager::DoGarbageCollection() {
1018 std::vector<const SyncedSession*> sessions;
1019 if (!GetAllForeignSessions(&sessions))
1020 return; // No foreign sessions.
1021
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);
1037 }
1038 }
1039
1040 if (!changes.empty())
1041 sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
1042 }
1043
1044 }; // namespace browser_sync
1045