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 "content/public/browser/favicon_status.h" 15 #include "content/public/browser/navigation_entry.h" 16 #include "content/public/browser/notification_details.h" 17 #include "content/public/browser/notification_service.h" 18 #include "content/public/browser/notification_source.h" 19 #include "content/public/common/url_constants.h" 20 #include "sync/api/sync_error.h" 21 #include "sync/api/sync_error_factory.h" 22 #include "sync/api/sync_merge_result.h" 23 #include "sync/api/time.h" 24 25 using content::NavigationEntry; 26 using sessions::SerializedNavigationEntry; 27 using syncer::SyncChange; 28 using syncer::SyncData; 29 30 namespace browser_sync { 31 32 // Maximum number of favicons to sync. 33 // TODO(zea): pull this from the server. 34 static const int kMaxSyncFavicons = 200; 35 36 // The maximum number of navigations in each direction we care to sync. 37 static const int kMaxSyncNavigationCount = 6; 38 39 // The URL at which the set of synced tabs is displayed. We treat it differently 40 // from all other URL's as accessing it triggers a sync refresh of Sessions. 41 static const char kNTPOpenTabSyncURL[] = "chrome://newtab/#open_tabs"; 42 43 // Default number of days without activity after which a session is considered 44 // stale and becomes a candidate for garbage collection. 45 static const size_t kDefaultStaleSessionThresholdDays = 14; // 2 weeks. 46 47 SessionsSyncManager::SessionsSyncManager( 48 Profile* profile, 49 SyncInternalApiDelegate* delegate, 50 scoped_ptr<LocalSessionEventRouter> router) 51 : favicon_cache_(profile, kMaxSyncFavicons), 52 local_tab_pool_out_of_sync_(true), 53 sync_prefs_(profile->GetPrefs()), 54 profile_(profile), 55 delegate_(delegate), 56 local_session_header_node_id_(TabNodePool::kInvalidTabNodeID), 57 stale_session_threshold_days_(kDefaultStaleSessionThresholdDays), 58 local_event_router_(router.Pass()), 59 synced_window_getter_(new SyncedWindowDelegatesGetter()) { 60 } 61 62 LocalSessionEventRouter::~LocalSessionEventRouter() {} 63 64 SessionsSyncManager::~SessionsSyncManager() { 65 } 66 67 // Returns the GUID-based string that should be used for 68 // |SessionsSyncManager::current_machine_tag_|. 69 static std::string BuildMachineTag(const std::string& cache_guid) { 70 std::string machine_tag = "session_sync"; 71 machine_tag.append(cache_guid); 72 return machine_tag; 73 } 74 75 syncer::SyncMergeResult SessionsSyncManager::MergeDataAndStartSyncing( 76 syncer::ModelType type, 77 const syncer::SyncDataList& initial_sync_data, 78 scoped_ptr<syncer::SyncChangeProcessor> sync_processor, 79 scoped_ptr<syncer::SyncErrorFactory> error_handler) { 80 syncer::SyncMergeResult merge_result(type); 81 DCHECK(session_tracker_.Empty()); 82 DCHECK_EQ(0U, local_tab_pool_.Capacity()); 83 84 error_handler_ = error_handler.Pass(); 85 sync_processor_ = sync_processor.Pass(); 86 87 local_session_header_node_id_ = TabNodePool::kInvalidTabNodeID; 88 scoped_ptr<DeviceInfo> local_device_info(delegate_->GetLocalDeviceInfo()); 89 syncer::SyncChangeList new_changes; 90 91 // Make sure we have a machine tag. We do this now (versus earlier) as it's 92 // a conveniently safe time to assert sync is ready and the cache_guid is 93 // initialized. 94 if (current_machine_tag_.empty()) 95 InitializeCurrentMachineTag(); 96 if (local_device_info) { 97 current_session_name_ = local_device_info->client_name(); 98 } else { 99 merge_result.set_error(error_handler_->CreateAndUploadError( 100 FROM_HERE, 101 "Failed to get device info for machine tag.")); 102 return merge_result; 103 } 104 session_tracker_.SetLocalSessionTag(current_machine_tag_); 105 106 // First, we iterate over sync data to update our session_tracker_. 107 syncer::SyncDataList restored_tabs; 108 if (!InitFromSyncModel(initial_sync_data, &restored_tabs, &new_changes)) { 109 // The sync db didn't have a header node for us. Create one. 110 sync_pb::EntitySpecifics specifics; 111 sync_pb::SessionSpecifics* base_specifics = specifics.mutable_session(); 112 base_specifics->set_session_tag(current_machine_tag()); 113 sync_pb::SessionHeader* header_s = base_specifics->mutable_header(); 114 header_s->set_client_name(current_session_name_); 115 header_s->set_device_type(DeviceInfo::GetLocalDeviceType()); 116 syncer::SyncData data = syncer::SyncData::CreateLocalData( 117 current_machine_tag(), current_session_name_, specifics); 118 new_changes.push_back(syncer::SyncChange( 119 FROM_HERE, syncer::SyncChange::ACTION_ADD, data)); 120 } 121 122 #if defined(OS_ANDROID) 123 std::string sync_machine_tag(BuildMachineTag( 124 delegate_->GetLocalSyncCacheGUID())); 125 if (current_machine_tag_.compare(sync_machine_tag) != 0) 126 DeleteForeignSessionInternal(sync_machine_tag, &new_changes); 127 #endif 128 129 // Check if anything has changed on the local client side. 130 AssociateWindows(RELOAD_TABS, restored_tabs, &new_changes); 131 local_tab_pool_out_of_sync_ = false; 132 133 merge_result.set_error( 134 sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes)); 135 136 local_event_router_->StartRoutingTo(this); 137 return merge_result; 138 } 139 140 void SessionsSyncManager::AssociateWindows( 141 ReloadTabsOption option, 142 const syncer::SyncDataList& restored_tabs, 143 syncer::SyncChangeList* change_output) { 144 const std::string local_tag = current_machine_tag(); 145 sync_pb::SessionSpecifics specifics; 146 specifics.set_session_tag(local_tag); 147 sync_pb::SessionHeader* header_s = specifics.mutable_header(); 148 SyncedSession* current_session = session_tracker_.GetSession(local_tag); 149 current_session->modified_time = base::Time::Now(); 150 header_s->set_client_name(current_session_name_); 151 header_s->set_device_type(DeviceInfo::GetLocalDeviceType()); 152 153 session_tracker_.ResetSessionTracking(local_tag); 154 std::set<SyncedWindowDelegate*> windows = 155 synced_window_getter_->GetSyncedWindowDelegates(); 156 157 for (std::set<SyncedWindowDelegate*>::const_iterator i = 158 windows.begin(); i != windows.end(); ++i) { 159 // Make sure the window has tabs and a viewable window. The viewable window 160 // check is necessary because, for example, when a browser is closed the 161 // destructor is not necessarily run immediately. This means its possible 162 // for us to get a handle to a browser that is about to be removed. If 163 // the tab count is 0 or the window is NULL, the browser is about to be 164 // deleted, so we ignore it. 165 if (sessions_util::ShouldSyncWindow(*i) && 166 (*i)->GetTabCount() && (*i)->HasWindow()) { 167 sync_pb::SessionWindow window_s; 168 SessionID::id_type window_id = (*i)->GetSessionId(); 169 DVLOG(1) << "Associating window " << window_id << " with " 170 << (*i)->GetTabCount() << " tabs."; 171 window_s.set_window_id(window_id); 172 // Note: We don't bother to set selected tab index anymore. We still 173 // consume it when receiving foreign sessions, as reading it is free, but 174 // it triggers too many sync cycles with too little value to make setting 175 // it worthwhile. 176 if ((*i)->IsTypeTabbed()) { 177 window_s.set_browser_type( 178 sync_pb::SessionWindow_BrowserType_TYPE_TABBED); 179 } else { 180 window_s.set_browser_type( 181 sync_pb::SessionWindow_BrowserType_TYPE_POPUP); 182 } 183 184 bool found_tabs = false; 185 for (int j = 0; j < (*i)->GetTabCount(); ++j) { 186 SessionID::id_type tab_id = (*i)->GetTabIdAt(j); 187 SyncedTabDelegate* synced_tab = (*i)->GetTabAt(j); 188 189 // GetTabAt can return a null tab; in that case just skip it. 190 if (!synced_tab) 191 continue; 192 193 if (!synced_tab->HasWebContents()) { 194 // For tabs without WebContents update the |tab_id|, as it could have 195 // changed after a session restore. 196 // Note: We cannot check if a tab is valid if it has no WebContents. 197 // We assume any such tab is valid and leave the contents of 198 // corresponding sync node unchanged. 199 if (synced_tab->GetSyncId() > TabNodePool::kInvalidTabNodeID && 200 tab_id > TabNodePool::kInvalidTabID) { 201 AssociateRestoredPlaceholderTab(*synced_tab, tab_id, 202 restored_tabs, change_output); 203 found_tabs = true; 204 window_s.add_tab(tab_id); 205 } 206 continue; 207 } 208 209 if (RELOAD_TABS == option) 210 AssociateTab(synced_tab, change_output); 211 212 // If the tab is valid, it would have been added to the tracker either 213 // by the above AssociateTab call (at association time), or by the 214 // change processor calling AssociateTab for all modified tabs. 215 // Therefore, we can key whether this window has valid tabs based on 216 // the tab's presence in the tracker. 217 const SessionTab* tab = NULL; 218 if (session_tracker_.LookupSessionTab(local_tag, tab_id, &tab)) { 219 found_tabs = true; 220 window_s.add_tab(tab_id); 221 } 222 } 223 if (found_tabs) { 224 sync_pb::SessionWindow* header_window = header_s->add_window(); 225 *header_window = window_s; 226 227 // Update this window's representation in the synced session tracker. 228 session_tracker_.PutWindowInSession(local_tag, window_id); 229 BuildSyncedSessionFromSpecifics(local_tag, 230 window_s, 231 current_session->modified_time, 232 current_session->windows[window_id]); 233 } 234 } 235 } 236 local_tab_pool_.DeleteUnassociatedTabNodes(change_output); 237 session_tracker_.CleanupSession(local_tag); 238 239 // Always update the header. Sync takes care of dropping this update 240 // if the entity specifics are identical (i.e windows, client name did 241 // not change). 242 sync_pb::EntitySpecifics entity; 243 entity.mutable_session()->CopyFrom(specifics); 244 syncer::SyncData data = syncer::SyncData::CreateLocalData( 245 current_machine_tag(), current_session_name_, entity); 246 change_output->push_back(syncer::SyncChange( 247 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data)); 248 } 249 250 void SessionsSyncManager::AssociateTab(SyncedTabDelegate* const tab, 251 syncer::SyncChangeList* change_output) { 252 DCHECK(tab->HasWebContents()); 253 SessionID::id_type tab_id = tab->GetSessionId(); 254 if (tab->profile() != profile_) 255 return; 256 257 if (tab->IsBeingDestroyed()) { 258 // This tab is closing. 259 TabLinksMap::iterator tab_iter = local_tab_map_.find(tab_id); 260 if (tab_iter == local_tab_map_.end()) { 261 // We aren't tracking this tab (for example, sync setting page). 262 return; 263 } 264 local_tab_pool_.FreeTabNode(tab_iter->second->tab_node_id(), 265 change_output); 266 local_tab_map_.erase(tab_iter); 267 return; 268 } 269 270 if (!sessions_util::ShouldSyncTab(*tab)) 271 return; 272 273 TabLinksMap::iterator local_tab_map_iter = local_tab_map_.find(tab_id); 274 TabLink* tab_link = NULL; 275 276 if (local_tab_map_iter == local_tab_map_.end()) { 277 int tab_node_id = tab->GetSyncId(); 278 // If there is an old sync node for the tab, reuse it. If this is a new 279 // tab, get a sync node for it. 280 if (!local_tab_pool_.IsUnassociatedTabNode(tab_node_id)) { 281 tab_node_id = local_tab_pool_.GetFreeTabNode(change_output); 282 tab->SetSyncId(tab_node_id); 283 } 284 local_tab_pool_.AssociateTabNode(tab_node_id, tab_id); 285 tab_link = new TabLink(tab_node_id, tab); 286 local_tab_map_[tab_id] = make_linked_ptr<TabLink>(tab_link); 287 } else { 288 // This tab is already associated with a sync node, reuse it. 289 // Note: on some platforms the tab object may have changed, so we ensure 290 // the tab link is up to date. 291 tab_link = local_tab_map_iter->second.get(); 292 local_tab_map_iter->second->set_tab(tab); 293 } 294 DCHECK(tab_link); 295 DCHECK_NE(tab_link->tab_node_id(), TabNodePool::kInvalidTabNodeID); 296 DVLOG(1) << "Reloading tab " << tab_id << " from window " 297 << tab->GetWindowId(); 298 299 // Write to sync model. 300 sync_pb::EntitySpecifics specifics; 301 LocalTabDelegateToSpecifics(*tab, specifics.mutable_session()); 302 syncer::SyncData data = syncer::SyncData::CreateLocalData( 303 TabNodePool::TabIdToTag(current_machine_tag_, 304 tab_link->tab_node_id()), 305 current_session_name_, 306 specifics); 307 change_output->push_back(syncer::SyncChange( 308 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data)); 309 310 const GURL new_url = GetCurrentVirtualURL(*tab); 311 if (new_url != tab_link->url()) { 312 tab_link->set_url(new_url); 313 favicon_cache_.OnFaviconVisited(new_url, GetCurrentFaviconURL(*tab)); 314 } 315 316 session_tracker_.GetSession(current_machine_tag())->modified_time = 317 base::Time::Now(); 318 } 319 320 void SessionsSyncManager::RebuildAssociations() { 321 syncer::SyncDataList data( 322 sync_processor_->GetAllSyncData(syncer::SESSIONS)); 323 scoped_ptr<syncer::SyncErrorFactory> error_handler(error_handler_.Pass()); 324 scoped_ptr<syncer::SyncChangeProcessor> processor(sync_processor_.Pass()); 325 326 StopSyncing(syncer::SESSIONS); 327 MergeDataAndStartSyncing( 328 syncer::SESSIONS, data, processor.Pass(), error_handler.Pass()); 329 } 330 331 void SessionsSyncManager::OnLocalTabModified(SyncedTabDelegate* modified_tab) { 332 const content::NavigationEntry* entry = modified_tab->GetActiveEntry(); 333 if (!modified_tab->IsBeingDestroyed() && 334 entry && 335 entry->GetVirtualURL().is_valid() && 336 entry->GetVirtualURL().spec() == kNTPOpenTabSyncURL) { 337 DVLOG(1) << "Triggering sync refresh for sessions datatype."; 338 const syncer::ModelTypeSet types(syncer::SESSIONS); 339 content::NotificationService::current()->Notify( 340 chrome::NOTIFICATION_SYNC_REFRESH_LOCAL, 341 content::Source<Profile>(profile_), 342 content::Details<const syncer::ModelTypeSet>(&types)); 343 } 344 345 if (local_tab_pool_out_of_sync_) { 346 // If our tab pool is corrupt, pay the price of a full re-association to 347 // fix things up. This takes care of the new tab modification as well. 348 RebuildAssociations(); 349 DCHECK(!local_tab_pool_out_of_sync_); 350 return; 351 } 352 353 syncer::SyncChangeList changes; 354 // Associate tabs first so the synced session tracker is aware of them. 355 AssociateTab(modified_tab, &changes); 356 // Note, we always associate windows because it's possible a tab became 357 // "interesting" by going to a valid URL, in which case it needs to be added 358 // to the window's tab information. 359 AssociateWindows(DONT_RELOAD_TABS, syncer::SyncDataList(), &changes); 360 sync_processor_->ProcessSyncChanges(FROM_HERE, changes); 361 } 362 363 void SessionsSyncManager::OnFaviconPageUrlsUpdated( 364 const std::set<GURL>& updated_favicon_page_urls) { 365 // TODO(zea): consider a separate container for tabs with outstanding favicon 366 // loads so we don't have to iterate through all tabs comparing urls. 367 for (std::set<GURL>::const_iterator i = updated_favicon_page_urls.begin(); 368 i != updated_favicon_page_urls.end(); ++i) { 369 for (TabLinksMap::iterator tab_iter = local_tab_map_.begin(); 370 tab_iter != local_tab_map_.end(); 371 ++tab_iter) { 372 if (tab_iter->second->url() == *i) 373 favicon_cache_.OnPageFaviconUpdated(*i); 374 } 375 } 376 } 377 378 void SessionsSyncManager::StopSyncing(syncer::ModelType type) { 379 local_event_router_->Stop(); 380 sync_processor_.reset(NULL); 381 error_handler_.reset(); 382 session_tracker_.Clear(); 383 local_tab_map_.clear(); 384 local_tab_pool_.Clear(); 385 current_machine_tag_.clear(); 386 current_session_name_.clear(); 387 local_session_header_node_id_ = TabNodePool::kInvalidTabNodeID; 388 } 389 390 syncer::SyncDataList SessionsSyncManager::GetAllSyncData( 391 syncer::ModelType type) const { 392 syncer::SyncDataList list; 393 const SyncedSession* session = NULL; 394 if (!session_tracker_.LookupLocalSession(&session)) 395 return syncer::SyncDataList(); 396 397 // First construct the header node. 398 sync_pb::EntitySpecifics header_entity; 399 header_entity.mutable_session()->set_session_tag(current_machine_tag()); 400 sync_pb::SessionHeader* header_specifics = 401 header_entity.mutable_session()->mutable_header(); 402 header_specifics->MergeFrom(session->ToSessionHeader()); 403 syncer::SyncData data = syncer::SyncData::CreateLocalData( 404 current_machine_tag(), current_session_name_, header_entity); 405 list.push_back(data); 406 407 SyncedSession::SyncedWindowMap::const_iterator win_iter; 408 for (win_iter = session->windows.begin(); 409 win_iter != session->windows.end(); ++win_iter) { 410 std::vector<SessionTab*>::const_iterator tabs_iter; 411 for (tabs_iter = win_iter->second->tabs.begin(); 412 tabs_iter != win_iter->second->tabs.end(); ++tabs_iter) { 413 sync_pb::EntitySpecifics entity; 414 sync_pb::SessionSpecifics* specifics = entity.mutable_session(); 415 specifics->mutable_tab()->MergeFrom((*tabs_iter)->ToSyncData()); 416 specifics->set_session_tag(current_machine_tag_); 417 418 TabLinksMap::const_iterator tab_map_iter = local_tab_map_.find( 419 (*tabs_iter)->tab_id.id()); 420 DCHECK(tab_map_iter != local_tab_map_.end()); 421 specifics->set_tab_node_id(tab_map_iter->second->tab_node_id()); 422 syncer::SyncData data = syncer::SyncData::CreateLocalData( 423 TabNodePool::TabIdToTag(current_machine_tag_, 424 specifics->tab_node_id()), 425 current_session_name_, 426 entity); 427 list.push_back(data); 428 } 429 } 430 return list; 431 } 432 433 bool SessionsSyncManager::GetLocalSession( 434 const SyncedSession* * local_session) { 435 if (current_machine_tag_.empty()) 436 return false; 437 *local_session = session_tracker_.GetSession(current_machine_tag()); 438 return true; 439 } 440 441 syncer::SyncError SessionsSyncManager::ProcessSyncChanges( 442 const tracked_objects::Location& from_here, 443 const syncer::SyncChangeList& change_list) { 444 if (!sync_processor_.get()) { 445 syncer::SyncError error(FROM_HERE, 446 syncer::SyncError::DATATYPE_ERROR, 447 "Models not yet associated.", 448 syncer::SESSIONS); 449 return error; 450 } 451 452 for (syncer::SyncChangeList::const_iterator it = change_list.begin(); 453 it != change_list.end(); ++it) { 454 DCHECK(it->IsValid()); 455 DCHECK(it->sync_data().GetSpecifics().has_session()); 456 const sync_pb::SessionSpecifics& session = 457 it->sync_data().GetSpecifics().session(); 458 switch (it->change_type()) { 459 case syncer::SyncChange::ACTION_DELETE: 460 // Deletions are all or nothing (since we only ever delete entire 461 // sessions). Therefore we don't care if it's a tab node or meta node, 462 // and just ensure we've disassociated. 463 if (current_machine_tag() == session.session_tag()) { 464 // Another client has attempted to delete our local data (possibly by 465 // error or a clock is inaccurate). Just ignore the deletion for now 466 // to avoid any possible ping-pong delete/reassociate sequence, but 467 // remember that this happened as our TabNodePool is inconsistent. 468 local_tab_pool_out_of_sync_ = true; 469 LOG(WARNING) << "Local session data deleted. Ignoring until next " 470 << "local navigation event."; 471 } else if (session.has_header()) { 472 // Disassociate only when header node is deleted. For tab node 473 // deletions, the header node will be updated and foreign tab will 474 // get deleted. 475 DisassociateForeignSession(session.session_tag()); 476 } 477 continue; 478 case syncer::SyncChange::ACTION_ADD: 479 case syncer::SyncChange::ACTION_UPDATE: 480 if (current_machine_tag() == session.session_tag()) { 481 // We should only ever receive a change to our own machine's session 482 // info if encryption was turned on. In that case, the data is still 483 // the same, so we can ignore. 484 LOG(WARNING) << "Dropping modification to local session."; 485 return syncer::SyncError(); 486 } 487 UpdateTrackerWithForeignSession( 488 session, syncer::SyncDataRemote(it->sync_data()).GetModifiedTime()); 489 break; 490 default: 491 NOTREACHED() << "Processing sync changes failed, unknown change type."; 492 } 493 } 494 495 content::NotificationService::current()->Notify( 496 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED, 497 content::Source<Profile>(profile_), 498 content::NotificationService::NoDetails()); 499 return syncer::SyncError(); 500 } 501 502 syncer::SyncChange SessionsSyncManager::TombstoneTab( 503 const sync_pb::SessionSpecifics& tab) { 504 if (!tab.has_tab_node_id()) { 505 LOG(WARNING) << "Old sessions node without tab node id; can't tombstone."; 506 return syncer::SyncChange(); 507 } else { 508 return syncer::SyncChange( 509 FROM_HERE, 510 SyncChange::ACTION_DELETE, 511 SyncData::CreateLocalDelete( 512 TabNodePool::TabIdToTag(current_machine_tag(), 513 tab.tab_node_id()), 514 syncer::SESSIONS)); 515 } 516 } 517 518 bool SessionsSyncManager::GetAllForeignSessions( 519 std::vector<const SyncedSession*>* sessions) { 520 return session_tracker_.LookupAllForeignSessions(sessions); 521 } 522 523 bool SessionsSyncManager::InitFromSyncModel( 524 const syncer::SyncDataList& sync_data, 525 syncer::SyncDataList* restored_tabs, 526 syncer::SyncChangeList* new_changes) { 527 bool found_current_header = false; 528 for (syncer::SyncDataList::const_iterator it = sync_data.begin(); 529 it != sync_data.end(); 530 ++it) { 531 const syncer::SyncData& data = *it; 532 DCHECK(data.GetSpecifics().has_session()); 533 const sync_pb::SessionSpecifics& specifics = data.GetSpecifics().session(); 534 if (specifics.session_tag().empty() || 535 (specifics.has_tab() && (!specifics.has_tab_node_id() || 536 !specifics.tab().has_tab_id()))) { 537 syncer::SyncChange tombstone(TombstoneTab(specifics)); 538 if (tombstone.IsValid()) 539 new_changes->push_back(tombstone); 540 } else if (specifics.session_tag() != current_machine_tag()) { 541 UpdateTrackerWithForeignSession( 542 specifics, syncer::SyncDataRemote(data).GetModifiedTime()); 543 } else { 544 // This is previously stored local session information. 545 if (specifics.has_header() && !found_current_header) { 546 // This is our previous header node, reuse it. 547 found_current_header = true; 548 if (specifics.header().has_client_name()) 549 current_session_name_ = specifics.header().client_name(); 550 } else { 551 if (specifics.has_header() || !specifics.has_tab()) { 552 LOG(WARNING) << "Found more than one session header node with local " 553 << "tag."; 554 syncer::SyncChange tombstone(TombstoneTab(specifics)); 555 if (tombstone.IsValid()) 556 new_changes->push_back(tombstone); 557 } else { 558 // This is a valid old tab node, add it to the pool so it can be 559 // reused for reassociation. 560 local_tab_pool_.AddTabNode(specifics.tab_node_id()); 561 restored_tabs->push_back(*it); 562 } 563 } 564 } 565 } 566 return found_current_header; 567 } 568 569 void SessionsSyncManager::UpdateTrackerWithForeignSession( 570 const sync_pb::SessionSpecifics& specifics, 571 const base::Time& modification_time) { 572 std::string foreign_session_tag = specifics.session_tag(); 573 DCHECK_NE(foreign_session_tag, current_machine_tag()); 574 575 SyncedSession* foreign_session = 576 session_tracker_.GetSession(foreign_session_tag); 577 if (specifics.has_header()) { 578 // Read in the header data for this foreign session. 579 // Header data contains window information and ordered tab id's for each 580 // window. 581 582 // Load (or create) the SyncedSession object for this client. 583 const sync_pb::SessionHeader& header = specifics.header(); 584 PopulateSessionHeaderFromSpecifics(header, 585 modification_time, 586 foreign_session); 587 588 // Reset the tab/window tracking for this session (must do this before 589 // we start calling PutWindowInSession and PutTabInWindow so that all 590 // unused tabs/windows get cleared by the CleanupSession(...) call). 591 session_tracker_.ResetSessionTracking(foreign_session_tag); 592 593 // Process all the windows and their tab information. 594 int num_windows = header.window_size(); 595 DVLOG(1) << "Associating " << foreign_session_tag << " with " 596 << num_windows << " windows."; 597 598 for (int i = 0; i < num_windows; ++i) { 599 const sync_pb::SessionWindow& window_s = header.window(i); 600 SessionID::id_type window_id = window_s.window_id(); 601 session_tracker_.PutWindowInSession(foreign_session_tag, 602 window_id); 603 BuildSyncedSessionFromSpecifics(foreign_session_tag, 604 window_s, 605 modification_time, 606 foreign_session->windows[window_id]); 607 } 608 // Delete any closed windows and unused tabs as necessary. 609 session_tracker_.CleanupSession(foreign_session_tag); 610 } else if (specifics.has_tab()) { 611 const sync_pb::SessionTab& tab_s = specifics.tab(); 612 SessionID::id_type tab_id = tab_s.tab_id(); 613 SessionTab* tab = 614 session_tracker_.GetTab(foreign_session_tag, 615 tab_id, 616 specifics.tab_node_id()); 617 618 // Update SessionTab based on protobuf. 619 tab->SetFromSyncData(tab_s, modification_time); 620 621 // If a favicon or favicon urls are present, load the URLs and visit 622 // times into the in-memory favicon cache. 623 RefreshFaviconVisitTimesFromForeignTab(tab_s, modification_time); 624 625 // Update the last modified time. 626 if (foreign_session->modified_time < modification_time) 627 foreign_session->modified_time = modification_time; 628 } else { 629 LOG(WARNING) << "Ignoring foreign session node with missing header/tab " 630 << "fields and tag " << foreign_session_tag << "."; 631 } 632 } 633 634 void SessionsSyncManager::InitializeCurrentMachineTag() { 635 DCHECK(current_machine_tag_.empty()); 636 std::string persisted_guid; 637 persisted_guid = sync_prefs_.GetSyncSessionsGUID(); 638 if (!persisted_guid.empty()) { 639 current_machine_tag_ = persisted_guid; 640 DVLOG(1) << "Restoring persisted session sync guid: " << persisted_guid; 641 } else { 642 current_machine_tag_ = BuildMachineTag(delegate_->GetLocalSyncCacheGUID()); 643 DVLOG(1) << "Creating session sync guid: " << current_machine_tag_; 644 sync_prefs_.SetSyncSessionsGUID(current_machine_tag_); 645 } 646 647 local_tab_pool_.SetMachineTag(current_machine_tag_); 648 } 649 650 // static 651 void SessionsSyncManager::PopulateSessionHeaderFromSpecifics( 652 const sync_pb::SessionHeader& header_specifics, 653 base::Time mtime, 654 SyncedSession* session_header) { 655 if (header_specifics.has_client_name()) 656 session_header->session_name = header_specifics.client_name(); 657 if (header_specifics.has_device_type()) { 658 switch (header_specifics.device_type()) { 659 case sync_pb::SyncEnums_DeviceType_TYPE_WIN: 660 session_header->device_type = SyncedSession::TYPE_WIN; 661 break; 662 case sync_pb::SyncEnums_DeviceType_TYPE_MAC: 663 session_header->device_type = SyncedSession::TYPE_MACOSX; 664 break; 665 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX: 666 session_header->device_type = SyncedSession::TYPE_LINUX; 667 break; 668 case sync_pb::SyncEnums_DeviceType_TYPE_CROS: 669 session_header->device_type = SyncedSession::TYPE_CHROMEOS; 670 break; 671 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE: 672 session_header->device_type = SyncedSession::TYPE_PHONE; 673 break; 674 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET: 675 session_header->device_type = SyncedSession::TYPE_TABLET; 676 break; 677 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER: 678 // Intentionally fall-through 679 default: 680 session_header->device_type = SyncedSession::TYPE_OTHER; 681 break; 682 } 683 } 684 session_header->modified_time = mtime; 685 } 686 687 // static 688 void SessionsSyncManager::BuildSyncedSessionFromSpecifics( 689 const std::string& session_tag, 690 const sync_pb::SessionWindow& specifics, 691 base::Time mtime, 692 SessionWindow* session_window) { 693 if (specifics.has_window_id()) 694 session_window->window_id.set_id(specifics.window_id()); 695 if (specifics.has_selected_tab_index()) 696 session_window->selected_tab_index = specifics.selected_tab_index(); 697 if (specifics.has_browser_type()) { 698 if (specifics.browser_type() == 699 sync_pb::SessionWindow_BrowserType_TYPE_TABBED) { 700 session_window->type = 1; 701 } else { 702 session_window->type = 2; 703 } 704 } 705 session_window->timestamp = mtime; 706 session_window->tabs.resize(specifics.tab_size(), NULL); 707 for (int i = 0; i < specifics.tab_size(); i++) { 708 SessionID::id_type tab_id = specifics.tab(i); 709 session_tracker_.PutTabInWindow(session_tag, 710 session_window->window_id.id(), 711 tab_id, 712 i); 713 } 714 } 715 716 void SessionsSyncManager::RefreshFaviconVisitTimesFromForeignTab( 717 const sync_pb::SessionTab& tab, const base::Time& modification_time) { 718 // First go through and iterate over all the navigations, checking if any 719 // have valid favicon urls. 720 for (int i = 0; i < tab.navigation_size(); ++i) { 721 if (!tab.navigation(i).favicon_url().empty()) { 722 const std::string& page_url = tab.navigation(i).virtual_url(); 723 const std::string& favicon_url = tab.navigation(i).favicon_url(); 724 favicon_cache_.OnReceivedSyncFavicon(GURL(page_url), 725 GURL(favicon_url), 726 std::string(), 727 syncer::TimeToProtoTime( 728 modification_time)); 729 } 730 } 731 } 732 733 bool SessionsSyncManager::GetSyncedFaviconForPageURL( 734 const std::string& page_url, 735 scoped_refptr<base::RefCountedMemory>* favicon_png) const { 736 return favicon_cache_.GetSyncedFaviconForPageURL(GURL(page_url), favicon_png); 737 } 738 739 void SessionsSyncManager::DeleteForeignSession(const std::string& tag) { 740 syncer::SyncChangeList changes; 741 DeleteForeignSessionInternal(tag, &changes); 742 sync_processor_->ProcessSyncChanges(FROM_HERE, changes); 743 } 744 745 void SessionsSyncManager::DeleteForeignSessionInternal( 746 const std::string& tag, syncer::SyncChangeList* change_output) { 747 if (tag == current_machine_tag()) { 748 LOG(ERROR) << "Attempting to delete local session. This is not currently " 749 << "supported."; 750 return; 751 } 752 753 std::set<int> tab_node_ids_to_delete; 754 session_tracker_.LookupTabNodeIds(tag, &tab_node_ids_to_delete); 755 if (!DisassociateForeignSession(tag)) { 756 // We don't have any data for this session, our work here is done! 757 return; 758 } 759 760 // Prepare deletes for the meta-node as well as individual tab nodes. 761 change_output->push_back(syncer::SyncChange( 762 FROM_HERE, 763 SyncChange::ACTION_DELETE, 764 SyncData::CreateLocalDelete(tag, syncer::SESSIONS))); 765 766 for (std::set<int>::const_iterator it = tab_node_ids_to_delete.begin(); 767 it != tab_node_ids_to_delete.end(); 768 ++it) { 769 change_output->push_back(syncer::SyncChange( 770 FROM_HERE, 771 SyncChange::ACTION_DELETE, 772 SyncData::CreateLocalDelete(TabNodePool::TabIdToTag(tag, *it), 773 syncer::SESSIONS))); 774 } 775 content::NotificationService::current()->Notify( 776 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED, 777 content::Source<Profile>(profile_), 778 content::NotificationService::NoDetails()); 779 } 780 781 bool SessionsSyncManager::DisassociateForeignSession( 782 const std::string& foreign_session_tag) { 783 if (foreign_session_tag == current_machine_tag()) { 784 DVLOG(1) << "Local session deleted! Doing nothing until a navigation is " 785 << "triggered."; 786 return false; 787 } 788 DVLOG(1) << "Disassociating session " << foreign_session_tag; 789 return session_tracker_.DeleteSession(foreign_session_tag); 790 } 791 792 // static 793 GURL SessionsSyncManager::GetCurrentVirtualURL( 794 const SyncedTabDelegate& tab_delegate) { 795 const int current_index = tab_delegate.GetCurrentEntryIndex(); 796 const int pending_index = tab_delegate.GetPendingEntryIndex(); 797 const NavigationEntry* current_entry = 798 (current_index == pending_index) ? 799 tab_delegate.GetPendingEntry() : 800 tab_delegate.GetEntryAtIndex(current_index); 801 return current_entry->GetVirtualURL(); 802 } 803 804 // static 805 GURL SessionsSyncManager::GetCurrentFaviconURL( 806 const SyncedTabDelegate& tab_delegate) { 807 const int current_index = tab_delegate.GetCurrentEntryIndex(); 808 const int pending_index = tab_delegate.GetPendingEntryIndex(); 809 const NavigationEntry* current_entry = 810 (current_index == pending_index) ? 811 tab_delegate.GetPendingEntry() : 812 tab_delegate.GetEntryAtIndex(current_index); 813 return (current_entry->GetFavicon().valid ? 814 current_entry->GetFavicon().url : 815 GURL()); 816 } 817 818 bool SessionsSyncManager::GetForeignSession( 819 const std::string& tag, 820 std::vector<const SessionWindow*>* windows) { 821 return session_tracker_.LookupSessionWindows(tag, windows); 822 } 823 824 bool SessionsSyncManager::GetForeignTab( 825 const std::string& tag, 826 const SessionID::id_type tab_id, 827 const SessionTab** tab) { 828 const SessionTab* synced_tab = NULL; 829 bool success = session_tracker_.LookupSessionTab(tag, 830 tab_id, 831 &synced_tab); 832 if (success) 833 *tab = synced_tab; 834 return success; 835 } 836 837 void SessionsSyncManager::LocalTabDelegateToSpecifics( 838 const SyncedTabDelegate& tab_delegate, 839 sync_pb::SessionSpecifics* specifics) { 840 SessionTab* session_tab = NULL; 841 session_tab = 842 session_tracker_.GetTab(current_machine_tag(), 843 tab_delegate.GetSessionId(), 844 tab_delegate.GetSyncId()); 845 SetSessionTabFromDelegate(tab_delegate, base::Time::Now(), session_tab); 846 sync_pb::SessionTab tab_s = session_tab->ToSyncData(); 847 specifics->set_session_tag(current_machine_tag_); 848 specifics->set_tab_node_id(tab_delegate.GetSyncId()); 849 specifics->mutable_tab()->CopyFrom(tab_s); 850 } 851 852 void SessionsSyncManager::AssociateRestoredPlaceholderTab( 853 const SyncedTabDelegate& tab_delegate, 854 SessionID::id_type new_tab_id, 855 const syncer::SyncDataList& restored_tabs, 856 syncer::SyncChangeList* change_output) { 857 DCHECK_NE(tab_delegate.GetSyncId(), TabNodePool::kInvalidTabNodeID); 858 // Rewrite the tab using |restored_tabs| to retrieve the specifics. 859 if (restored_tabs.empty()) { 860 DLOG(WARNING) << "Can't Update tab ID."; 861 return; 862 } 863 864 for (syncer::SyncDataList::const_iterator it = restored_tabs.begin(); 865 it != restored_tabs.end(); 866 ++it) { 867 if (it->GetSpecifics().session().tab_node_id() != 868 tab_delegate.GetSyncId()) { 869 continue; 870 } 871 872 sync_pb::EntitySpecifics entity; 873 sync_pb::SessionSpecifics* specifics = entity.mutable_session(); 874 specifics->CopyFrom(it->GetSpecifics().session()); 875 DCHECK(specifics->has_tab()); 876 877 // Update tab node pool with the new association. 878 local_tab_pool_.ReassociateTabNode(tab_delegate.GetSyncId(), 879 new_tab_id); 880 TabLink* tab_link = new TabLink(tab_delegate.GetSyncId(), 881 &tab_delegate); 882 local_tab_map_[new_tab_id] = make_linked_ptr<TabLink>(tab_link); 883 884 if (specifics->tab().tab_id() == new_tab_id) 885 return; 886 887 // The tab_id changed (e.g due to session restore), so update sync. 888 specifics->mutable_tab()->set_tab_id(new_tab_id); 889 syncer::SyncData data = syncer::SyncData::CreateLocalData( 890 TabNodePool::TabIdToTag(current_machine_tag_, 891 specifics->tab_node_id()), 892 current_session_name_, 893 entity); 894 change_output->push_back(syncer::SyncChange( 895 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data)); 896 return; 897 } 898 } 899 900 // static. 901 void SessionsSyncManager::SetSessionTabFromDelegate( 902 const SyncedTabDelegate& tab_delegate, 903 base::Time mtime, 904 SessionTab* session_tab) { 905 DCHECK(session_tab); 906 session_tab->window_id.set_id(tab_delegate.GetWindowId()); 907 session_tab->tab_id.set_id(tab_delegate.GetSessionId()); 908 session_tab->tab_visual_index = 0; 909 session_tab->current_navigation_index = tab_delegate.GetCurrentEntryIndex(); 910 session_tab->pinned = tab_delegate.IsPinned(); 911 session_tab->extension_app_id = tab_delegate.GetExtensionAppId(); 912 session_tab->user_agent_override.clear(); 913 session_tab->timestamp = mtime; 914 const int current_index = tab_delegate.GetCurrentEntryIndex(); 915 const int pending_index = tab_delegate.GetPendingEntryIndex(); 916 const int min_index = std::max(0, current_index - kMaxSyncNavigationCount); 917 const int max_index = std::min(current_index + kMaxSyncNavigationCount, 918 tab_delegate.GetEntryCount()); 919 bool is_supervised = tab_delegate.ProfileIsSupervised(); 920 session_tab->navigations.clear(); 921 922 for (int i = min_index; i < max_index; ++i) { 923 const NavigationEntry* entry = (i == pending_index) ? 924 tab_delegate.GetPendingEntry() : tab_delegate.GetEntryAtIndex(i); 925 DCHECK(entry); 926 if (!entry->GetVirtualURL().is_valid()) 927 continue; 928 929 session_tab->navigations.push_back( 930 SerializedNavigationEntry::FromNavigationEntry(i, *entry)); 931 if (is_supervised) { 932 session_tab->navigations.back().set_blocked_state( 933 SerializedNavigationEntry::STATE_ALLOWED); 934 } 935 } 936 937 if (is_supervised) { 938 const std::vector<const NavigationEntry*>& blocked_navigations = 939 *tab_delegate.GetBlockedNavigations(); 940 int offset = session_tab->navigations.size(); 941 for (size_t i = 0; i < blocked_navigations.size(); ++i) { 942 session_tab->navigations.push_back( 943 SerializedNavigationEntry::FromNavigationEntry( 944 i + offset, *blocked_navigations[i])); 945 session_tab->navigations.back().set_blocked_state( 946 SerializedNavigationEntry::STATE_BLOCKED); 947 // TODO(bauerb): Add categories 948 } 949 } 950 session_tab->session_storage_persistent_id.clear(); 951 } 952 953 FaviconCache* SessionsSyncManager::GetFaviconCache() { 954 return &favicon_cache_; 955 } 956 957 void SessionsSyncManager::DoGarbageCollection() { 958 std::vector<const SyncedSession*> sessions; 959 if (!GetAllForeignSessions(&sessions)) 960 return; // No foreign sessions. 961 962 // Iterate through all the sessions and delete any with age older than 963 // |stale_session_threshold_days_|. 964 syncer::SyncChangeList changes; 965 for (std::vector<const SyncedSession*>::const_iterator iter = 966 sessions.begin(); iter != sessions.end(); ++iter) { 967 const SyncedSession* session = *iter; 968 int session_age_in_days = 969 (base::Time::Now() - session->modified_time).InDays(); 970 std::string session_tag = session->session_tag; 971 if (session_age_in_days > 0 && // If false, local clock is not trustworty. 972 static_cast<size_t>(session_age_in_days) > 973 stale_session_threshold_days_) { 974 DVLOG(1) << "Found stale session " << session_tag 975 << " with age " << session_age_in_days << ", deleting."; 976 DeleteForeignSessionInternal(session_tag, &changes); 977 } 978 } 979 980 if (!changes.empty()) 981 sync_processor_->ProcessSyncChanges(FROM_HERE, changes); 982 } 983 984 }; // namespace browser_sync 985