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. 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 67 LocalSessionEventRouter::~LocalSessionEventRouter() {} 68 69 SessionsSyncManager::~SessionsSyncManager() { 70 } 71 72 // Returns the GUID-based string that should be used for 73 // |SessionsSyncManager::current_machine_tag_|. 74 static std::string BuildMachineTag(const std::string& cache_guid) { 75 std::string machine_tag = "session_sync"; 76 machine_tag.append(cache_guid); 77 return machine_tag; 78 } 79 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 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 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 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 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 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 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 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 424 syncer::SyncDataList SessionsSyncManager::GetAllSyncData( 425 syncer::ModelType type) const { 426 syncer::SyncDataList list; 427 const SyncedSession* session = NULL; 428 if (!session_tracker_.LookupLocalSession(&session)) 429 return syncer::SyncDataList(); 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 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 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 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 552 bool SessionsSyncManager::GetAllForeignSessions( 553 std::vector<const SyncedSession*>* sessions) { 554 return session_tracker_.LookupAllForeignSessions(sessions); 555 } 556 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 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 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 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 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 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 776 bool SessionsSyncManager::GetSyncedFaviconForPageURL( 777 const std::string& page_url, 778 scoped_refptr<base::RefCountedMemory>* favicon_png) const { 779 return favicon_cache_.GetSyncedFaviconForPageURL(GURL(page_url), favicon_png); 780 } 781 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 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 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 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 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 861 bool SessionsSyncManager::GetForeignSession( 862 const std::string& tag, 863 std::vector<const SessionWindow*>* windows) { 864 return session_tracker_.LookupSessionWindows(tag, windows); 865 } 866 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 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 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. 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 1008 FaviconCache* SessionsSyncManager::GetFaviconCache() { 1009 return &favicon_cache_; 1010 } 1011 1012 SyncedWindowDelegatesGetter* 1013 SessionsSyncManager::GetSyncedWindowDelegatesGetter() const { 1014 return synced_window_getter_.get(); 1015 } 1016 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