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. 4 5 #include "chrome/browser/sync/sessions2/sessions_sync_manager.h" 6 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" 25 26 using content::NavigationEntry; 27 using sessions::SerializedNavigationEntry; 28 using syncer::SyncChange; 29 using syncer::SyncData; 30 31 namespace browser_sync { 32 33 // Maximum number of favicons to sync. 34 // TODO(zea): pull this from the server. 35 static const int kMaxSyncFavicons = 200; 36 37 // The maximum number of navigations in each direction we care to sync. 38 static const int kMaxSyncNavigationCount = 6; 39 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"; 43 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. 47 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()) { 59 } 60 61 LocalSessionEventRouter::~LocalSessionEventRouter() {} 62 63 SessionsSyncManager::~SessionsSyncManager() { 64 } 65 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; 72 } 73 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()); 82 83 error_handler_ = error_handler.Pass(); 84 sync_processor_ = sync_processor.Pass(); 85 86 local_session_header_node_id_ = TabNodePool2::kInvalidTabNodeID; 87 scoped_ptr<DeviceInfo> local_device_info(delegate_->GetLocalDeviceInfo()); 88 syncer::SyncChangeList new_changes; 89 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; 102 } 103 session_tracker_.SetLocalSessionTag(current_machine_tag_); 104 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)); 118 } 119 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 126 127 // Check if anything has changed on the local client side. 128 AssociateWindows(RELOAD_TABS, &new_changes); 129 130 merge_result.set_error( 131 sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes)); 132 133 local_event_router_->StartRoutingTo(this); 134 return merge_result; 135 } 136 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()); 148 149 session_tracker_.ResetSessionTracking(local_tag); 150 std::set<SyncedWindowDelegate*> windows = 151 SyncedWindowDelegate::GetSyncedWindowDelegates(); 152 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); 177 } 178 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); 183 184 // GetTabAt can return a null tab; in that case just skip it. 185 if (!synced_tab) 186 continue; 187 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); 199 } 200 continue; 201 } 202 203 if (RELOAD_TABS == option) 204 AssociateTab(synced_tab, change_output); 205 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); 215 } 216 } 217 if (found_tabs) { 218 sync_pb::SessionWindow* header_window = header_s->add_window(); 219 *header_window = window_s; 220 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]); 227 } 228 } 229 } 230 local_tab_pool_.DeleteUnassociatedTabNodes(change_output); 231 session_tracker_.CleanupSession(local_tag); 232 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)); 242 } 243 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; 254 } 255 local_tab_pool_.FreeTabNode(tab_iter->second->tab_node_id(), 256 change_output); 257 local_tab_map_.erase(tab_iter); 258 return; 259 } 260 if (!ShouldSyncTab(*tab)) 261 return; 262 263 TabLinksMap::iterator local_tab_map_iter = local_tab_map_.find(tab_id); 264 TabLink* tab_link = NULL; 265 int tab_node_id(TabNodePool2::kInvalidTabNodeID); 266 267 if (local_tab_map_iter == local_tab_map_.end()) { 268 tab_node_id = tab->GetSyncId(); 269 // If there is an old sync node for the tab, reuse it. If this is a new 270 // tab, get a sync node for it. 271 if (!local_tab_pool_.IsUnassociatedTabNode(tab_node_id)) { 272 tab_node_id = local_tab_pool_.GetFreeTabNode(change_output); 273 tab->SetSyncId(tab_node_id); 274 } 275 local_tab_pool_.AssociateTabNode(tab_node_id, tab_id); 276 tab_link = new TabLink(tab_node_id, tab); 277 local_tab_map_[tab_id] = make_linked_ptr<TabLink>(tab_link); 278 } else { 279 // This tab is already associated with a sync node, reuse it. 280 // Note: on some platforms the tab object may have changed, so we ensure 281 // the tab link is up to date. 282 tab_link = local_tab_map_iter->second.get(); 283 local_tab_map_iter->second->set_tab(tab); 284 } 285 DCHECK(tab_link); 286 DCHECK_NE(tab_link->tab_node_id(), TabNodePool2::kInvalidTabNodeID); 287 DVLOG(1) << "Reloading tab " << tab_id << " from window " 288 << tab->GetWindowId(); 289 290 // Write to sync model. 291 sync_pb::EntitySpecifics specifics; 292 LocalTabDelegateToSpecifics(*tab, specifics.mutable_session()); 293 syncer::SyncData data = syncer::SyncData::CreateLocalData( 294 TabNodePool2::TabIdToTag(current_machine_tag_, 295 tab_node_id), 296 current_session_name_, 297 specifics); 298 change_output->push_back(syncer::SyncChange( 299 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data)); 300 301 const GURL new_url = GetCurrentVirtualURL(*tab); 302 if (new_url != tab_link->url()) { 303 tab_link->set_url(new_url); 304 favicon_cache_.OnFaviconVisited(new_url, GetCurrentFaviconURL(*tab)); 305 } 306 307 session_tracker_.GetSession(current_machine_tag())->modified_time = 308 base::Time::Now(); 309 } 310 311 void SessionsSyncManager::OnLocalTabModified(SyncedTabDelegate* modified_tab) { 312 const content::NavigationEntry* entry = modified_tab->GetActiveEntry(); 313 if (!modified_tab->IsBeingDestroyed() && 314 entry && 315 entry->GetVirtualURL().is_valid() && 316 entry->GetVirtualURL().spec() == kNTPOpenTabSyncURL) { 317 DVLOG(1) << "Triggering sync refresh for sessions datatype."; 318 const syncer::ModelTypeSet types(syncer::SESSIONS); 319 content::NotificationService::current()->Notify( 320 chrome::NOTIFICATION_SYNC_REFRESH_LOCAL, 321 content::Source<Profile>(profile_), 322 content::Details<const syncer::ModelTypeSet>(&types)); 323 } 324 325 syncer::SyncChangeList changes; 326 // Associate tabs first so the synced session tracker is aware of them. 327 AssociateTab(modified_tab, &changes); 328 // Note, we always associate windows because it's possible a tab became 329 // "interesting" by going to a valid URL, in which case it needs to be added 330 // to the window's tab information. 331 AssociateWindows(DONT_RELOAD_TABS, &changes); 332 sync_processor_->ProcessSyncChanges(FROM_HERE, changes); 333 } 334 335 void SessionsSyncManager::OnFaviconPageUrlsUpdated( 336 const std::set<GURL>& updated_favicon_page_urls) { 337 // TODO(zea): consider a separate container for tabs with outstanding favicon 338 // loads so we don't have to iterate through all tabs comparing urls. 339 for (std::set<GURL>::const_iterator i = updated_favicon_page_urls.begin(); 340 i != updated_favicon_page_urls.end(); ++i) { 341 for (TabLinksMap::iterator tab_iter = local_tab_map_.begin(); 342 tab_iter != local_tab_map_.end(); 343 ++tab_iter) { 344 if (tab_iter->second->url() == *i) 345 favicon_cache_.OnPageFaviconUpdated(*i); 346 } 347 } 348 } 349 350 bool SessionsSyncManager::ShouldSyncTab(const SyncedTabDelegate& tab) const { 351 if (tab.profile() != profile_) 352 return false; 353 354 if (SyncedWindowDelegate::FindSyncedWindowDelegateWithId( 355 tab.GetWindowId()) == NULL) { 356 return false; 357 } 358 359 // Does the tab have a valid NavigationEntry? 360 if (tab.ProfileIsManaged() && tab.GetBlockedNavigations()->size() > 0) 361 return true; 362 363 int entry_count = tab.GetEntryCount(); 364 if (entry_count == 0) 365 return false; // This deliberately ignores a new pending entry. 366 367 int pending_index = tab.GetPendingEntryIndex(); 368 bool found_valid_url = false; 369 for (int i = 0; i < entry_count; ++i) { 370 const content::NavigationEntry* entry = (i == pending_index) ? 371 tab.GetPendingEntry() : tab.GetEntryAtIndex(i); 372 if (!entry) 373 return false; 374 const GURL& virtual_url = entry->GetVirtualURL(); 375 if (virtual_url.is_valid() && 376 !virtual_url.SchemeIs(chrome::kChromeUIScheme) && 377 !virtual_url.SchemeIs(chrome::kChromeNativeScheme) && 378 !virtual_url.SchemeIsFile()) { 379 found_valid_url = true; 380 } 381 } 382 return found_valid_url; 383 } 384 385 // static. 386 bool SessionsSyncManager::ShouldSyncWindow( 387 const SyncedWindowDelegate* window) { 388 if (window->IsApp()) 389 return false; 390 return window->IsTypeTabbed() || window->IsTypePopup(); 391 } 392 393 void SessionsSyncManager::StopSyncing(syncer::ModelType type) { 394 NOTIMPLEMENTED(); 395 } 396 397 syncer::SyncDataList SessionsSyncManager::GetAllSyncData( 398 syncer::ModelType type) const { 399 NOTIMPLEMENTED(); 400 return syncer::SyncDataList(); 401 } 402 403 syncer::SyncError SessionsSyncManager::ProcessSyncChanges( 404 const tracked_objects::Location& from_here, 405 const syncer::SyncChangeList& change_list) { 406 if (!sync_processor_.get()) { 407 syncer::SyncError error(FROM_HERE, 408 syncer::SyncError::DATATYPE_ERROR, 409 "Models not yet associated.", 410 syncer::SESSIONS); 411 return error; 412 } 413 414 for (syncer::SyncChangeList::const_iterator it = change_list.begin(); 415 it != change_list.end(); ++it) { 416 DCHECK(it->IsValid()); 417 DCHECK(it->sync_data().GetSpecifics().has_session()); 418 const sync_pb::SessionSpecifics& session = 419 it->sync_data().GetSpecifics().session(); 420 switch (it->change_type()) { 421 case syncer::SyncChange::ACTION_DELETE: 422 // Deletions are all or nothing (since we only ever delete entire 423 // sessions). Therefore we don't care if it's a tab node or meta node, 424 // and just ensure we've disassociated. 425 if (current_machine_tag() == session.session_tag()) { 426 // Another client has attempted to delete our local data (possibly by 427 // error or a clock is inaccurate). Just ignore the deletion for now 428 // to avoid any possible ping-pong delete/reassociate sequence. 429 // TODO(tim): Bug 98892. This corrupts TabNodePool. Perform full 430 // re-association. 431 LOG(WARNING) << "Local session data deleted. Ignoring until next " 432 << "local navigation event."; 433 } else if (session.has_header()) { 434 // Disassociate only when header node is deleted. For tab node 435 // deletions, the header node will be updated and foreign tab will 436 // get deleted. 437 DisassociateForeignSession(session.session_tag()); 438 } 439 continue; 440 case syncer::SyncChange::ACTION_ADD: 441 case syncer::SyncChange::ACTION_UPDATE: 442 if (current_machine_tag() == session.session_tag()) { 443 // We should only ever receive a change to our own machine's session 444 // info if encryption was turned on. In that case, the data is still 445 // the same, so we can ignore. 446 LOG(WARNING) << "Dropping modification to local session."; 447 return syncer::SyncError(); 448 } 449 UpdateTrackerWithForeignSession( 450 session, it->sync_data().GetRemoteModifiedTime()); 451 break; 452 default: 453 NOTREACHED() << "Processing sync changes failed, unknown change type."; 454 } 455 } 456 457 content::NotificationService::current()->Notify( 458 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED, 459 content::Source<Profile>(profile_), 460 content::NotificationService::NoDetails()); 461 return syncer::SyncError(); 462 } 463 464 syncer::SyncChange SessionsSyncManager::TombstoneTab( 465 const sync_pb::SessionSpecifics& tab) { 466 if (!tab.has_tab_node_id()) { 467 LOG(WARNING) << "Old sessions node without tab node id; can't tombstone."; 468 return syncer::SyncChange(); 469 } else { 470 return syncer::SyncChange( 471 FROM_HERE, 472 SyncChange::ACTION_DELETE, 473 SyncData::CreateLocalDelete( 474 TabNodePool2::TabIdToTag(current_machine_tag(), 475 tab.tab_node_id()), 476 syncer::SESSIONS)); 477 } 478 } 479 480 bool SessionsSyncManager::GetAllForeignSessions( 481 std::vector<const SyncedSession*>* sessions) { 482 return session_tracker_.LookupAllForeignSessions(sessions); 483 } 484 485 bool SessionsSyncManager::InitFromSyncModel( 486 const syncer::SyncDataList& sync_data, 487 syncer::SyncChangeList* new_changes) { 488 bool found_current_header = false; 489 for (syncer::SyncDataList::const_iterator it = sync_data.begin(); 490 it != sync_data.end(); 491 ++it) { 492 const syncer::SyncData& data = *it; 493 DCHECK(data.GetSpecifics().has_session()); 494 const sync_pb::SessionSpecifics& specifics = data.GetSpecifics().session(); 495 if (specifics.session_tag().empty() || 496 (specifics.has_tab() && (!specifics.has_tab_node_id() || 497 !specifics.tab().has_tab_id()))) { 498 syncer::SyncChange tombstone(TombstoneTab(specifics)); 499 if (tombstone.IsValid()) 500 new_changes->push_back(tombstone); 501 } else if (specifics.session_tag() != current_machine_tag()) { 502 UpdateTrackerWithForeignSession(specifics, data.GetRemoteModifiedTime()); 503 } else { 504 // This is previously stored local session information. 505 if (specifics.has_header() && !found_current_header) { 506 // This is our previous header node, reuse it. 507 found_current_header = true; 508 if (specifics.header().has_client_name()) 509 current_session_name_ = specifics.header().client_name(); 510 } else { 511 if (specifics.has_header() || !specifics.has_tab()) { 512 LOG(WARNING) << "Found more than one session header node with local " 513 << "tag."; 514 syncer::SyncChange tombstone(TombstoneTab(specifics)); 515 if (tombstone.IsValid()) 516 new_changes->push_back(tombstone); 517 } else { 518 // This is a valid old tab node, add it to the pool so it can be 519 // reused for reassociation. 520 local_tab_pool_.AddTabNode(specifics.tab_node_id()); 521 } 522 } 523 } 524 } 525 return found_current_header; 526 } 527 528 void SessionsSyncManager::UpdateTrackerWithForeignSession( 529 const sync_pb::SessionSpecifics& specifics, 530 const base::Time& modification_time) { 531 std::string foreign_session_tag = specifics.session_tag(); 532 DCHECK_NE(foreign_session_tag, current_machine_tag()); 533 534 SyncedSession* foreign_session = 535 session_tracker_.GetSession(foreign_session_tag); 536 if (specifics.has_header()) { 537 // Read in the header data for this foreign session. 538 // Header data contains window information and ordered tab id's for each 539 // window. 540 541 // Load (or create) the SyncedSession object for this client. 542 const sync_pb::SessionHeader& header = specifics.header(); 543 PopulateSessionHeaderFromSpecifics(header, 544 modification_time, 545 foreign_session); 546 547 // Reset the tab/window tracking for this session (must do this before 548 // we start calling PutWindowInSession and PutTabInWindow so that all 549 // unused tabs/windows get cleared by the CleanupSession(...) call). 550 session_tracker_.ResetSessionTracking(foreign_session_tag); 551 552 // Process all the windows and their tab information. 553 int num_windows = header.window_size(); 554 DVLOG(1) << "Associating " << foreign_session_tag << " with " 555 << num_windows << " windows."; 556 557 for (int i = 0; i < num_windows; ++i) { 558 const sync_pb::SessionWindow& window_s = header.window(i); 559 SessionID::id_type window_id = window_s.window_id(); 560 session_tracker_.PutWindowInSession(foreign_session_tag, 561 window_id); 562 BuildSyncedSessionFromSpecifics(foreign_session_tag, 563 window_s, 564 modification_time, 565 foreign_session->windows[window_id]); 566 } 567 // Delete any closed windows and unused tabs as necessary. 568 session_tracker_.CleanupSession(foreign_session_tag); 569 } else if (specifics.has_tab()) { 570 const sync_pb::SessionTab& tab_s = specifics.tab(); 571 SessionID::id_type tab_id = tab_s.tab_id(); 572 SessionTab* tab = 573 session_tracker_.GetTab(foreign_session_tag, 574 tab_id, 575 specifics.tab_node_id()); 576 577 // Update SessionTab based on protobuf. 578 tab->SetFromSyncData(tab_s, modification_time); 579 580 // If a favicon or favicon urls are present, load the URLs and visit 581 // times into the in-memory favicon cache. 582 RefreshFaviconVisitTimesFromForeignTab(tab_s, modification_time); 583 584 // Update the last modified time. 585 if (foreign_session->modified_time < modification_time) 586 foreign_session->modified_time = modification_time; 587 } else { 588 LOG(WARNING) << "Ignoring foreign session node with missing header/tab " 589 << "fields and tag " << foreign_session_tag << "."; 590 } 591 } 592 593 void SessionsSyncManager::InitializeCurrentMachineTag() { 594 DCHECK(current_machine_tag_.empty()); 595 std::string persisted_guid; 596 persisted_guid = sync_prefs_.GetSyncSessionsGUID(); 597 if (!persisted_guid.empty()) { 598 current_machine_tag_ = persisted_guid; 599 DVLOG(1) << "Restoring persisted session sync guid: " << persisted_guid; 600 } else { 601 current_machine_tag_ = BuildMachineTag(delegate_->GetLocalSyncCacheGUID()); 602 DVLOG(1) << "Creating session sync guid: " << current_machine_tag_; 603 sync_prefs_.SetSyncSessionsGUID(current_machine_tag_); 604 } 605 606 local_tab_pool_.SetMachineTag(current_machine_tag_); 607 } 608 609 // static 610 void SessionsSyncManager::PopulateSessionHeaderFromSpecifics( 611 const sync_pb::SessionHeader& header_specifics, 612 base::Time mtime, 613 SyncedSession* session_header) { 614 if (header_specifics.has_client_name()) 615 session_header->session_name = header_specifics.client_name(); 616 if (header_specifics.has_device_type()) { 617 switch (header_specifics.device_type()) { 618 case sync_pb::SyncEnums_DeviceType_TYPE_WIN: 619 session_header->device_type = SyncedSession::TYPE_WIN; 620 break; 621 case sync_pb::SyncEnums_DeviceType_TYPE_MAC: 622 session_header->device_type = SyncedSession::TYPE_MACOSX; 623 break; 624 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX: 625 session_header->device_type = SyncedSession::TYPE_LINUX; 626 break; 627 case sync_pb::SyncEnums_DeviceType_TYPE_CROS: 628 session_header->device_type = SyncedSession::TYPE_CHROMEOS; 629 break; 630 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE: 631 session_header->device_type = SyncedSession::TYPE_PHONE; 632 break; 633 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET: 634 session_header->device_type = SyncedSession::TYPE_TABLET; 635 break; 636 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER: 637 // Intentionally fall-through 638 default: 639 session_header->device_type = SyncedSession::TYPE_OTHER; 640 break; 641 } 642 } 643 session_header->modified_time = mtime; 644 } 645 646 // static 647 void SessionsSyncManager::BuildSyncedSessionFromSpecifics( 648 const std::string& session_tag, 649 const sync_pb::SessionWindow& specifics, 650 base::Time mtime, 651 SessionWindow* session_window) { 652 if (specifics.has_window_id()) 653 session_window->window_id.set_id(specifics.window_id()); 654 if (specifics.has_selected_tab_index()) 655 session_window->selected_tab_index = specifics.selected_tab_index(); 656 if (specifics.has_browser_type()) { 657 if (specifics.browser_type() == 658 sync_pb::SessionWindow_BrowserType_TYPE_TABBED) { 659 session_window->type = 1; 660 } else { 661 session_window->type = 2; 662 } 663 } 664 session_window->timestamp = mtime; 665 session_window->tabs.resize(specifics.tab_size(), NULL); 666 for (int i = 0; i < specifics.tab_size(); i++) { 667 SessionID::id_type tab_id = specifics.tab(i); 668 session_tracker_.PutTabInWindow(session_tag, 669 session_window->window_id.id(), 670 tab_id, 671 i); 672 } 673 } 674 675 void SessionsSyncManager::RefreshFaviconVisitTimesFromForeignTab( 676 const sync_pb::SessionTab& tab, const base::Time& modification_time) { 677 // First go through and iterate over all the navigations, checking if any 678 // have valid favicon urls. 679 for (int i = 0; i < tab.navigation_size(); ++i) { 680 if (!tab.navigation(i).favicon_url().empty()) { 681 const std::string& page_url = tab.navigation(i).virtual_url(); 682 const std::string& favicon_url = tab.navigation(i).favicon_url(); 683 favicon_cache_.OnReceivedSyncFavicon(GURL(page_url), 684 GURL(favicon_url), 685 std::string(), 686 syncer::TimeToProtoTime( 687 modification_time)); 688 } 689 } 690 } 691 692 bool SessionsSyncManager::GetSyncedFaviconForPageURL( 693 const std::string& page_url, 694 scoped_refptr<base::RefCountedMemory>* favicon_png) const { 695 return favicon_cache_.GetSyncedFaviconForPageURL(GURL(page_url), favicon_png); 696 } 697 698 void SessionsSyncManager::DeleteForeignSession(const std::string& tag) { 699 syncer::SyncChangeList changes; 700 DeleteForeignSessionInternal(tag, &changes); 701 sync_processor_->ProcessSyncChanges(FROM_HERE, changes); 702 } 703 704 void SessionsSyncManager::DeleteForeignSessionInternal( 705 const std::string& tag, syncer::SyncChangeList* change_output) { 706 if (tag == current_machine_tag()) { 707 LOG(ERROR) << "Attempting to delete local session. This is not currently " 708 << "supported."; 709 return; 710 } 711 712 std::set<int> tab_node_ids_to_delete; 713 session_tracker_.LookupTabNodeIds(tag, &tab_node_ids_to_delete); 714 if (!DisassociateForeignSession(tag)) { 715 // We don't have any data for this session, our work here is done! 716 return; 717 } 718 719 // Prepare deletes for the meta-node as well as individual tab nodes. 720 change_output->push_back(syncer::SyncChange( 721 FROM_HERE, 722 SyncChange::ACTION_DELETE, 723 SyncData::CreateLocalDelete(tag, syncer::SESSIONS))); 724 725 for (std::set<int>::const_iterator it = tab_node_ids_to_delete.begin(); 726 it != tab_node_ids_to_delete.end(); 727 ++it) { 728 change_output->push_back(syncer::SyncChange( 729 FROM_HERE, 730 SyncChange::ACTION_DELETE, 731 SyncData::CreateLocalDelete(TabNodePool2::TabIdToTag(tag, *it), 732 syncer::SESSIONS))); 733 } 734 } 735 736 bool SessionsSyncManager::DisassociateForeignSession( 737 const std::string& foreign_session_tag) { 738 if (foreign_session_tag == current_machine_tag()) { 739 DVLOG(1) << "Local session deleted! Doing nothing until a navigation is " 740 << "triggered."; 741 return false; 742 } 743 DVLOG(1) << "Disassociating session " << foreign_session_tag; 744 return session_tracker_.DeleteSession(foreign_session_tag); 745 } 746 747 // static 748 GURL SessionsSyncManager::GetCurrentVirtualURL( 749 const SyncedTabDelegate& tab_delegate) { 750 const int current_index = tab_delegate.GetCurrentEntryIndex(); 751 const int pending_index = tab_delegate.GetPendingEntryIndex(); 752 const NavigationEntry* current_entry = 753 (current_index == pending_index) ? 754 tab_delegate.GetPendingEntry() : 755 tab_delegate.GetEntryAtIndex(current_index); 756 return current_entry->GetVirtualURL(); 757 } 758 759 // static 760 GURL SessionsSyncManager::GetCurrentFaviconURL( 761 const SyncedTabDelegate& tab_delegate) { 762 const int current_index = tab_delegate.GetCurrentEntryIndex(); 763 const int pending_index = tab_delegate.GetPendingEntryIndex(); 764 const NavigationEntry* current_entry = 765 (current_index == pending_index) ? 766 tab_delegate.GetPendingEntry() : 767 tab_delegate.GetEntryAtIndex(current_index); 768 return (current_entry->GetFavicon().valid ? 769 current_entry->GetFavicon().url : 770 GURL()); 771 } 772 773 bool SessionsSyncManager::GetForeignSession( 774 const std::string& tag, 775 std::vector<const SessionWindow*>* windows) { 776 return session_tracker_.LookupSessionWindows(tag, windows); 777 } 778 779 bool SessionsSyncManager::GetForeignTab( 780 const std::string& tag, 781 const SessionID::id_type tab_id, 782 const SessionTab** tab) { 783 const SessionTab* synced_tab = NULL; 784 bool success = session_tracker_.LookupSessionTab(tag, 785 tab_id, 786 &synced_tab); 787 if (success) 788 *tab = synced_tab; 789 return success; 790 } 791 792 void SessionsSyncManager::LocalTabDelegateToSpecifics( 793 const SyncedTabDelegate& tab_delegate, 794 sync_pb::SessionSpecifics* specifics) { 795 SessionTab* session_tab = NULL; 796 session_tab = 797 session_tracker_.GetTab(current_machine_tag(), 798 tab_delegate.GetSessionId(), 799 tab_delegate.GetSyncId()); 800 SetSessionTabFromDelegate(tab_delegate, base::Time::Now(), session_tab); 801 sync_pb::SessionTab tab_s = session_tab->ToSyncData(); 802 specifics->set_session_tag(current_machine_tag_); 803 specifics->set_tab_node_id(tab_delegate.GetSyncId()); 804 specifics->mutable_tab()->CopyFrom(tab_s); 805 } 806 807 void SessionsSyncManager::UpdateTabIdIfNecessary( 808 const SyncedTabDelegate& tab_delegate, 809 SessionID::id_type new_tab_id, 810 syncer::SyncChangeList* change_output) { 811 DCHECK_NE(tab_delegate.GetSyncId(), TabNodePool2::kInvalidTabNodeID); 812 SessionID::id_type old_tab_id = 813 local_tab_pool_.GetTabIdFromTabNodeId(tab_delegate.GetSyncId()); 814 if (old_tab_id != new_tab_id) { 815 // Rewrite the tab. We don't have a way to get the old 816 // specifics here currently. 817 // TODO(tim): Is this too slow? Should we cache specifics? 818 sync_pb::EntitySpecifics specifics; 819 LocalTabDelegateToSpecifics(tab_delegate, 820 specifics.mutable_session()); 821 822 // Update tab node pool with the new association. 823 local_tab_pool_.ReassociateTabNode(tab_delegate.GetSyncId(), new_tab_id); 824 syncer::SyncData data = syncer::SyncData::CreateLocalData( 825 TabNodePool2::TabIdToTag(current_machine_tag_, 826 tab_delegate.GetSyncId()), 827 current_session_name_, specifics); 828 change_output->push_back(syncer::SyncChange( 829 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data)); 830 } 831 } 832 833 // static. 834 void SessionsSyncManager::SetSessionTabFromDelegate( 835 const SyncedTabDelegate& tab_delegate, 836 base::Time mtime, 837 SessionTab* session_tab) { 838 DCHECK(session_tab); 839 session_tab->window_id.set_id(tab_delegate.GetWindowId()); 840 session_tab->tab_id.set_id(tab_delegate.GetSessionId()); 841 session_tab->tab_visual_index = 0; 842 session_tab->current_navigation_index = tab_delegate.GetCurrentEntryIndex(); 843 session_tab->pinned = tab_delegate.IsPinned(); 844 session_tab->extension_app_id = tab_delegate.GetExtensionAppId(); 845 session_tab->user_agent_override.clear(); 846 session_tab->timestamp = mtime; 847 const int current_index = tab_delegate.GetCurrentEntryIndex(); 848 const int pending_index = tab_delegate.GetPendingEntryIndex(); 849 const int min_index = std::max(0, current_index - kMaxSyncNavigationCount); 850 const int max_index = std::min(current_index + kMaxSyncNavigationCount, 851 tab_delegate.GetEntryCount()); 852 bool is_managed = tab_delegate.ProfileIsManaged(); 853 session_tab->navigations.clear(); 854 855 #if !defined(OS_ANDROID) 856 // For getting navigation time in network time. 857 NavigationTimeHelper* nav_time_helper = 858 tab_delegate.HasWebContents() ? 859 NavigationTimeHelper::FromWebContents(tab_delegate.GetWebContents()) : 860 NULL; 861 #endif 862 863 for (int i = min_index; i < max_index; ++i) { 864 const NavigationEntry* entry = (i == pending_index) ? 865 tab_delegate.GetPendingEntry() : tab_delegate.GetEntryAtIndex(i); 866 DCHECK(entry); 867 if (!entry->GetVirtualURL().is_valid()) 868 continue; 869 870 scoped_ptr<content::NavigationEntry> network_time_entry( 871 content::NavigationEntry::Create(*entry)); 872 #if !defined(OS_ANDROID) 873 if (nav_time_helper) { 874 network_time_entry->SetTimestamp( 875 nav_time_helper->GetNavigationTime(entry)); 876 } 877 #endif 878 879 session_tab->navigations.push_back( 880 SerializedNavigationEntry::FromNavigationEntry(i, *network_time_entry)); 881 if (is_managed) { 882 session_tab->navigations.back().set_blocked_state( 883 SerializedNavigationEntry::STATE_ALLOWED); 884 } 885 } 886 887 if (is_managed) { 888 const std::vector<const NavigationEntry*>& blocked_navigations = 889 *tab_delegate.GetBlockedNavigations(); 890 int offset = session_tab->navigations.size(); 891 for (size_t i = 0; i < blocked_navigations.size(); ++i) { 892 session_tab->navigations.push_back( 893 SerializedNavigationEntry::FromNavigationEntry( 894 i + offset, *blocked_navigations[i])); 895 session_tab->navigations.back().set_blocked_state( 896 SerializedNavigationEntry::STATE_BLOCKED); 897 // TODO(bauerb): Add categories 898 } 899 } 900 session_tab->session_storage_persistent_id.clear(); 901 } 902 903 FaviconCache* SessionsSyncManager::GetFaviconCache() { 904 return &favicon_cache_; 905 } 906 907 void SessionsSyncManager::DoGarbageCollection() { 908 std::vector<const SyncedSession*> sessions; 909 if (!GetAllForeignSessions(&sessions)) 910 return; // No foreign sessions. 911 912 // Iterate through all the sessions and delete any with age older than 913 // |stale_session_threshold_days_|. 914 syncer::SyncChangeList changes; 915 for (std::vector<const SyncedSession*>::const_iterator iter = 916 sessions.begin(); iter != sessions.end(); ++iter) { 917 const SyncedSession* session = *iter; 918 int session_age_in_days = 919 (base::Time::Now() - session->modified_time).InDays(); 920 std::string session_tag = session->session_tag; 921 if (session_age_in_days > 0 && // If false, local clock is not trustworty. 922 static_cast<size_t>(session_age_in_days) > 923 stale_session_threshold_days_) { 924 DVLOG(1) << "Found stale session " << session_tag 925 << " with age " << session_age_in_days << ", deleting."; 926 DeleteForeignSessionInternal(session_tag, &changes); 927 } 928 } 929 930 if (!changes.empty()) 931 sync_processor_->ProcessSyncChanges(FROM_HERE, changes); 932 } 933 934 }; // namespace browser_sync 935