Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / sync / sessions2 / sessions_sync_manager.cc
blobd22849e83ad35e96728f5eee045986b8dc37e896
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"
10 #endif
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(
49 Profile* profile,
50 SyncInternalApiDelegate* delegate,
51 scoped_ptr<LocalSessionEventRouter> router)
52 : favicon_cache_(profile, kMaxSyncFavicons),
53 sync_prefs_(profile->GetPrefs()),
54 profile_(profile),
55 delegate_(delegate),
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);
71 return machine_tag;
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
92 // initialized.
93 if (current_machine_tag_.empty())
94 InitializeCurrentMachineTag();
95 if (local_device_info) {
96 current_session_name_ = local_device_info->client_name();
97 } else {
98 merge_result.set_error(error_handler_->CreateAndUploadError(
99 FROM_HERE,
100 "Failed to get device info for machine tag."));
101 return merge_result;
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);
125 #endif
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);
134 return merge_result;
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
170 // it worthwhile.
171 if ((*i)->IsTypeTabbed()) {
172 window_s.set_browser_type(
173 sync_pb::SessionWindow_BrowserType_TYPE_TABBED);
174 } else {
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.
185 if (!synced_tab)
186 continue;
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);
197 found_tabs = true;
198 window_s.add_tab(tab_id);
200 continue;
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)) {
213 found_tabs = true;
214 window_s.add_tab(tab_id);
217 if (found_tabs) {
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,
224 window_s,
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
235 // not change).
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).
253 return;
255 local_tab_pool_.FreeTabNode(tab_iter->second->tab_node_id(),
256 change_output);
257 local_tab_map_.erase(tab_iter);
258 return;
260 if (!ShouldSyncTab(*tab))
261 return;
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);
277 } else {
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);
284 DCHECK(tab_link);
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_,
296 specifics);
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 =
307 base::Time::Now();
310 void SessionsSyncManager::OnLocalTabModified(SyncedTabDelegate* modified_tab) {
311 const content::NavigationEntry* entry = modified_tab->GetActiveEntry();
312 if (!modified_tab->IsBeingDestroyed() &&
313 entry &&
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();
342 ++tab_iter) {
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_)
351 return false;
353 if (SyncedWindowDelegate::FindSyncedWindowDelegateWithId(
354 tab.GetWindowId()) == NULL) {
355 return false;
358 // Does the tab have a valid NavigationEntry?
359 if (tab.ProfileIsManaged() && tab.GetBlockedNavigations()->size() > 0)
360 return true;
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);
371 if (!entry)
372 return false;
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;
384 // static.
385 bool SessionsSyncManager::ShouldSyncWindow(
386 const SyncedWindowDelegate* window) {
387 if (window->IsApp())
388 return false;
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_,
440 entity);
441 list.push_back(data);
444 return list;
447 bool SessionsSyncManager::GetLocalSession(
448 const SyncedSession* * local_session) {
449 if (current_machine_tag_.empty())
450 return false;
451 *local_session = session_tracker_.GetSession(current_machine_tag());
452 return true;
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.",
462 syncer::SESSIONS);
463 return error;
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
482 // re-association.
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
488 // get deleted.
489 DisassociateForeignSession(session.session_tag());
491 continue;
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());
503 break;
504 default:
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();
521 } else {
522 return syncer::SyncChange(
523 FROM_HERE,
524 SyncChange::ACTION_DELETE,
525 SyncData::CreateLocalDelete(
526 TabNodePool2::TabIdToTag(current_machine_tag(),
527 tab.tab_node_id()),
528 syncer::SESSIONS));
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();
543 ++it) {
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());
555 } else {
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();
562 } else {
563 if (specifics.has_header() || !specifics.has_tab()) {
564 LOG(WARNING) << "Found more than one session header node with local "
565 << "tag.";
566 syncer::SyncChange tombstone(TombstoneTab(specifics));
567 if (tombstone.IsValid())
568 new_changes->push_back(tombstone);
569 } else {
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
591 // window.
593 // Load (or create) the SyncedSession object for this client.
594 const sync_pb::SessionHeader& header = specifics.header();
595 PopulateSessionHeaderFromSpecifics(header,
596 modification_time,
597 foreign_session);
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,
613 window_id);
614 BuildSyncedSessionFromSpecifics(foreign_session_tag,
615 window_s,
616 modification_time,
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();
624 SessionTab* tab =
625 session_tracker_.GetTab(foreign_session_tag,
626 tab_id,
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;
639 } else {
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;
652 } else {
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_);
661 // static
662 void SessionsSyncManager::PopulateSessionHeaderFromSpecifics(
663 const sync_pb::SessionHeader& header_specifics,
664 base::Time mtime,
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;
672 break;
673 case sync_pb::SyncEnums_DeviceType_TYPE_MAC:
674 session_header->device_type = SyncedSession::TYPE_MACOSX;
675 break;
676 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX:
677 session_header->device_type = SyncedSession::TYPE_LINUX;
678 break;
679 case sync_pb::SyncEnums_DeviceType_TYPE_CROS:
680 session_header->device_type = SyncedSession::TYPE_CHROMEOS;
681 break;
682 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE:
683 session_header->device_type = SyncedSession::TYPE_PHONE;
684 break;
685 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET:
686 session_header->device_type = SyncedSession::TYPE_TABLET;
687 break;
688 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER:
689 // Intentionally fall-through
690 default:
691 session_header->device_type = SyncedSession::TYPE_OTHER;
692 break;
695 session_header->modified_time = mtime;
698 // static
699 void SessionsSyncManager::BuildSyncedSessionFromSpecifics(
700 const std::string& session_tag,
701 const sync_pb::SessionWindow& specifics,
702 base::Time mtime,
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;
712 } else {
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(),
722 tab_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),
736 GURL(favicon_url),
737 std::string(),
738 syncer::TimeToProtoTime(
739 modification_time));
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 "
760 << "supported.";
761 return;
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!
768 return;
771 // Prepare deletes for the meta-node as well as individual tab nodes.
772 change_output->push_back(syncer::SyncChange(
773 FROM_HERE,
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();
779 ++it) {
780 change_output->push_back(syncer::SyncChange(
781 FROM_HERE,
782 SyncChange::ACTION_DELETE,
783 SyncData::CreateLocalDelete(TabNodePool2::TabIdToTag(tag, *it),
784 syncer::SESSIONS)));
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 "
792 << "triggered.";
793 return false;
795 DVLOG(1) << "Disassociating session " << foreign_session_tag;
796 return session_tracker_.DeleteSession(foreign_session_tag);
799 // static
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();
811 // static
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 :
822 GURL());
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,
837 tab_id,
838 &synced_tab);
839 if (success)
840 *tab = synced_tab;
841 return success;
844 void SessionsSyncManager::LocalTabDelegateToSpecifics(
845 const SyncedTabDelegate& tab_delegate,
846 sync_pb::SessionSpecifics* specifics) {
847 SessionTab* session_tab = NULL;
848 session_tab =
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));
885 // static.
886 void SessionsSyncManager::SetSessionTabFromDelegate(
887 const SyncedTabDelegate& tab_delegate,
888 base::Time mtime,
889 SessionTab* session_tab) {
890 DCHECK(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()) :
912 NULL;
913 #endif
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);
918 DCHECK(entry);
919 if (!entry->GetVirtualURL().is_valid())
920 continue;
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));
929 #endif
931 session_tab->navigations.push_back(
932 SerializedNavigationEntry::FromNavigationEntry(i, *network_time_entry));
933 if (is_managed) {
934 session_tab->navigations.back().set_blocked_state(
935 SerializedNavigationEntry::STATE_ALLOWED);
939 if (is_managed) {
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