1 // Copyright 2012 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/glue/session_model_associator.h" 6 7 #include <algorithm> 8 #include <set> 9 #include <utility> 10 11 #include "base/bind.h" 12 #include "base/location.h" 13 #include "base/logging.h" 14 #include "base/safe_numerics.h" 15 #include "base/strings/utf_string_conversions.h" 16 #include "base/threading/sequenced_worker_pool.h" 17 #include "chrome/browser/chrome_notification_types.h" 18 #include "chrome/browser/favicon/favicon_service_factory.h" 19 #include "chrome/browser/history/history_service.h" 20 #include "chrome/browser/prefs/pref_service_syncable.h" 21 #include "chrome/browser/profiles/profile.h" 22 #include "chrome/browser/sessions/session_id.h" 23 #include "chrome/browser/sync/glue/device_info.h" 24 #include "chrome/browser/sync/glue/synced_device_tracker.h" 25 #include "chrome/browser/sync/glue/synced_session.h" 26 #include "chrome/browser/sync/glue/synced_tab_delegate.h" 27 #include "chrome/browser/sync/glue/synced_window_delegate.h" 28 #include "chrome/browser/sync/profile_sync_service.h" 29 #include "chrome/common/chrome_switches.h" 30 #include "chrome/common/pref_names.h" 31 #include "chrome/common/url_constants.h" 32 #include "components/sessions/serialized_navigation_entry.h" 33 #include "components/user_prefs/pref_registry_syncable.h" 34 #include "content/public/browser/favicon_status.h" 35 #include "content/public/browser/navigation_entry.h" 36 #include "content/public/browser/notification_details.h" 37 #include "content/public/browser/notification_service.h" 38 #include "sync/api/sync_error.h" 39 #include "sync/api/time.h" 40 #include "sync/internal_api/public/base/model_type.h" 41 #include "sync/internal_api/public/base/model_type_invalidation_map.h" 42 #include "sync/internal_api/public/read_node.h" 43 #include "sync/internal_api/public/read_transaction.h" 44 #include "sync/internal_api/public/write_node.h" 45 #include "sync/internal_api/public/write_transaction.h" 46 #include "sync/protocol/session_specifics.pb.h" 47 #include "sync/syncable/directory.h" 48 #include "sync/syncable/syncable_read_transaction.h" 49 #include "sync/syncable/syncable_write_transaction.h" 50 #if defined(OS_LINUX) 51 #include "base/linux_util.h" 52 #elif defined(OS_WIN) 53 #include <windows.h> 54 #endif 55 56 using content::BrowserThread; 57 using content::NavigationEntry; 58 using prefs::kSyncSessionsGUID; 59 using sessions::SerializedNavigationEntry; 60 using syncer::SESSIONS; 61 62 namespace { 63 64 std::string SessionTagPrefix() { 65 return std::string("session_sync"); 66 } 67 68 // Given a transaction, returns the GUID-based string that should be used for 69 // |current_machine_tag_|. 70 std::string GetMachineTagFromTransaction( 71 syncer::WriteTransaction* trans) { 72 syncer::syncable::Directory* dir = trans->GetWrappedWriteTrans()->directory(); 73 std::string machine_tag = SessionTagPrefix(); 74 machine_tag.append(dir->cache_guid()); 75 return machine_tag; 76 } 77 78 // Given a session tag this function returns the client_id(cache_guid). 79 std::string GetClientIdFromSessionTag(const std::string& session_tag) { 80 if (session_tag.find_first_of(SessionTagPrefix()) == std::string::npos) { 81 LOG(ERROR) << "Session tag is malformatted"; 82 return std::string(); 83 } 84 85 std::string client_id = session_tag.substr( 86 SessionTagPrefix().length(), 87 session_tag.length()); 88 89 return client_id; 90 } 91 92 } // namespace 93 94 namespace browser_sync { 95 96 namespace { 97 static const char kNoSessionsFolderError[] = 98 "Server did not create the top-level sessions node. We " 99 "might be running against an out-of-date server."; 100 101 // The maximum number of navigations in each direction we care to sync. 102 static const int kMaxSyncNavigationCount = 6; 103 104 // Default number of days without activity after which a session is considered 105 // stale and becomes a candidate for garbage collection. 106 static const size_t kDefaultStaleSessionThresholdDays = 14; // 2 weeks. 107 108 // Maximum number of favicons to sync. 109 // TODO(zea): pull this from the server. 110 static const int kMaxSyncFavicons = 200; 111 112 } // namespace 113 114 SessionModelAssociator::SessionModelAssociator( 115 ProfileSyncService* sync_service, 116 DataTypeErrorHandler* error_handler) 117 : local_tab_pool_(sync_service), 118 local_session_syncid_(syncer::kInvalidId), 119 sync_service_(sync_service), 120 stale_session_threshold_days_(kDefaultStaleSessionThresholdDays), 121 setup_for_test_(false), 122 waiting_for_change_(false), 123 test_weak_factory_(this), 124 profile_(sync_service->profile()), 125 error_handler_(error_handler), 126 favicon_cache_(profile_, 127 sync_service->current_experiments().favicon_sync_limit) { 128 DCHECK(CalledOnValidThread()); 129 DCHECK(sync_service_); 130 DCHECK(profile_); 131 } 132 133 SessionModelAssociator::SessionModelAssociator(ProfileSyncService* sync_service, 134 bool setup_for_test) 135 : local_tab_pool_(sync_service), 136 local_session_syncid_(syncer::kInvalidId), 137 sync_service_(sync_service), 138 stale_session_threshold_days_(kDefaultStaleSessionThresholdDays), 139 setup_for_test_(setup_for_test), 140 waiting_for_change_(false), 141 test_weak_factory_(this), 142 profile_(sync_service->profile()), 143 error_handler_(NULL), 144 favicon_cache_(profile_, kMaxSyncFavicons) { 145 DCHECK(CalledOnValidThread()); 146 DCHECK(sync_service_); 147 DCHECK(profile_); 148 DCHECK(setup_for_test); 149 } 150 151 SessionModelAssociator::~SessionModelAssociator() { 152 DCHECK(CalledOnValidThread()); 153 } 154 155 bool SessionModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) { 156 DCHECK(CalledOnValidThread()); 157 CHECK(has_nodes); 158 *has_nodes = false; 159 syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare()); 160 syncer::ReadNode root(&trans); 161 if (root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)) != 162 syncer::BaseNode::INIT_OK) { 163 LOG(ERROR) << kNoSessionsFolderError; 164 return false; 165 } 166 // The sync model has user created nodes iff the sessions folder has 167 // any children. 168 *has_nodes = root.HasChildren(); 169 return true; 170 } 171 172 int64 SessionModelAssociator::GetSyncIdFromSessionTag(const std::string& tag) { 173 DCHECK(CalledOnValidThread()); 174 syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare()); 175 syncer::ReadNode node(&trans); 176 if (node.InitByClientTagLookup(SESSIONS, tag) != syncer::BaseNode::INIT_OK) 177 return syncer::kInvalidId; 178 return node.GetId(); 179 } 180 181 bool SessionModelAssociator::AssociateWindows(bool reload_tabs, 182 syncer::SyncError* error) { 183 DCHECK(CalledOnValidThread()); 184 std::string local_tag = GetCurrentMachineTag(); 185 sync_pb::SessionSpecifics specifics; 186 specifics.set_session_tag(local_tag); 187 sync_pb::SessionHeader* header_s = specifics.mutable_header(); 188 SyncedSession* current_session = 189 synced_session_tracker_.GetSession(local_tag); 190 current_session->modified_time = base::Time::Now(); 191 header_s->set_client_name(current_session_name_); 192 header_s->set_device_type(DeviceInfo::GetLocalDeviceType()); 193 194 synced_session_tracker_.ResetSessionTracking(local_tag); 195 std::set<SyncedWindowDelegate*> windows = 196 SyncedWindowDelegate::GetSyncedWindowDelegates(); 197 for (std::set<SyncedWindowDelegate*>::const_iterator i = 198 windows.begin(); i != windows.end(); ++i) { 199 // Make sure the window has tabs and a viewable window. The viewable window 200 // check is necessary because, for example, when a browser is closed the 201 // destructor is not necessarily run immediately. This means its possible 202 // for us to get a handle to a browser that is about to be removed. If 203 // the tab count is 0 or the window is NULL, the browser is about to be 204 // deleted, so we ignore it. 205 if (ShouldSyncWindow(*i) && (*i)->GetTabCount() && (*i)->HasWindow()) { 206 sync_pb::SessionWindow window_s; 207 SessionID::id_type window_id = (*i)->GetSessionId(); 208 DVLOG(1) << "Associating window " << window_id << " with " 209 << (*i)->GetTabCount() << " tabs."; 210 window_s.set_window_id(window_id); 211 // Note: We don't bother to set selected tab index anymore. We still 212 // consume it when receiving foreign sessions, as reading it is free, but 213 // it triggers too many sync cycles with too little value to make setting 214 // it worthwhile. 215 if ((*i)->IsTypeTabbed()) { 216 window_s.set_browser_type( 217 sync_pb::SessionWindow_BrowserType_TYPE_TABBED); 218 } else { 219 window_s.set_browser_type( 220 sync_pb::SessionWindow_BrowserType_TYPE_POPUP); 221 } 222 223 // Store the order of tabs. 224 bool found_tabs = false; 225 for (int j = 0; j < (*i)->GetTabCount(); ++j) { 226 SessionID::id_type tab_id = (*i)->GetTabIdAt(j); 227 SyncedTabDelegate* synced_tab = (*i)->GetTabAt(j); 228 229 // GetTabAt can return a null tab; in that case just skip it. 230 if (!synced_tab) 231 continue; 232 233 if (!synced_tab->HasWebContents()) { 234 // For tabs without WebContents update the |tab_id|, as it could have 235 // changed after a session restore. 236 // Note: We cannot check if a tab is valid if it has no WebContents. 237 // We assume any such tab is valid and leave the contents of 238 // corresponding sync node unchanged. 239 if (synced_tab->GetSyncId() > TabNodePool::kInvalidTabNodeID && 240 tab_id > TabNodePool::kInvalidTabID) { 241 UpdateTabIdIfNecessary(synced_tab->GetSyncId(), tab_id); 242 found_tabs = true; 243 window_s.add_tab(tab_id); 244 } 245 continue; 246 } 247 248 if (reload_tabs) { 249 // It's possible for GetTabAt to return a tab which has no web 250 // contents. We can assume this means the tab already existed but 251 // hasn't changed, so no need to reassociate. 252 if (synced_tab->HasWebContents() && 253 !AssociateTab(synced_tab, error)) { 254 // Association failed. Either we need to re-associate, or this is an 255 // unrecoverable error. 256 return false; 257 } 258 } 259 260 // If the tab is valid, it would have been added to the tracker either 261 // by the above AssociateTab call (at association time), or by the 262 // change processor calling AssociateTab for all modified tabs. 263 // Therefore, we can key whether this window has valid tabs based on 264 // the tab's presence in the tracker. 265 const SessionTab* tab = NULL; 266 if (synced_session_tracker_.LookupSessionTab(local_tag, tab_id, &tab)) { 267 found_tabs = true; 268 window_s.add_tab(tab_id); 269 } 270 } 271 // Only add a window if it contains valid tabs. 272 if (found_tabs) { 273 sync_pb::SessionWindow* header_window = header_s->add_window(); 274 *header_window = window_s; 275 276 // Update this window's representation in the synced session tracker. 277 synced_session_tracker_.PutWindowInSession(local_tag, window_id); 278 PopulateSessionWindowFromSpecifics( 279 local_tag, 280 window_s, 281 base::Time::Now(), 282 current_session->windows[window_id], 283 &synced_session_tracker_); 284 } 285 } 286 } 287 288 local_tab_pool_.DeleteUnassociatedTabNodes(); 289 // Free memory for closed windows and tabs. 290 synced_session_tracker_.CleanupSession(local_tag); 291 292 syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); 293 syncer::WriteNode header_node(&trans); 294 if (header_node.InitByIdLookup(local_session_syncid_) != 295 syncer::BaseNode::INIT_OK) { 296 if (error) { 297 *error = error_handler_->CreateAndUploadError( 298 FROM_HERE, 299 "Failed to load local session header node.", 300 model_type()); 301 } 302 return false; 303 } 304 header_node.SetSessionSpecifics(specifics); 305 if (waiting_for_change_) QuitLoopForSubtleTesting(); 306 return true; 307 } 308 309 // Static. 310 bool SessionModelAssociator::ShouldSyncWindow( 311 const SyncedWindowDelegate* window) { 312 if (window->IsApp()) 313 return false; 314 return window->IsTypeTabbed() || window->IsTypePopup(); 315 } 316 317 bool SessionModelAssociator::AssociateTabs( 318 const std::vector<SyncedTabDelegate*>& tabs, 319 syncer::SyncError* error) { 320 DCHECK(CalledOnValidThread()); 321 for (std::vector<SyncedTabDelegate*>::const_iterator i = tabs.begin(); 322 i != tabs.end(); 323 ++i) { 324 if (!AssociateTab(*i, error)) 325 return false; 326 } 327 if (waiting_for_change_) QuitLoopForSubtleTesting(); 328 return true; 329 } 330 331 bool SessionModelAssociator::AssociateTab(SyncedTabDelegate* const tab, 332 syncer::SyncError* error) { 333 DCHECK(CalledOnValidThread()); 334 DCHECK(tab->HasWebContents()); 335 int tab_node_id(TabNodePool::kInvalidTabNodeID); 336 SessionID::id_type tab_id = tab->GetSessionId(); 337 if (tab->IsBeingDestroyed()) { 338 // This tab is closing. 339 TabLinksMap::iterator tab_iter = local_tab_map_.find(tab_id); 340 if (tab_iter == local_tab_map_.end()) { 341 // We aren't tracking this tab (for example, sync setting page). 342 return true; 343 } 344 local_tab_pool_.FreeTabNode(tab_iter->second->tab_node_id()); 345 local_tab_map_.erase(tab_iter); 346 return true; 347 } 348 349 if (!ShouldSyncTab(*tab)) 350 return true; 351 352 TabLinksMap::iterator local_tab_map_iter = local_tab_map_.find(tab_id); 353 TabLink* tab_link = NULL; 354 if (local_tab_map_iter == local_tab_map_.end()) { 355 tab_node_id = tab->GetSyncId(); 356 // if there is an old sync node for the tab, reuse it. 357 if (!local_tab_pool_.IsUnassociatedTabNode(tab_node_id)) { 358 // This is a new tab, get a sync node for it. 359 tab_node_id = local_tab_pool_.GetFreeTabNode(); 360 if (tab_node_id == TabNodePool::kInvalidTabNodeID) { 361 if (error) { 362 *error = error_handler_->CreateAndUploadError( 363 FROM_HERE, 364 "Received invalid tab node from tab pool.", 365 model_type()); 366 } 367 return false; 368 } 369 tab->SetSyncId(tab_node_id); 370 } 371 local_tab_pool_.AssociateTabNode(tab_node_id, tab_id); 372 tab_link = new TabLink(tab_node_id, tab); 373 local_tab_map_[tab_id] = make_linked_ptr<TabLink>(tab_link); 374 } else { 375 // This tab is already associated with a sync node, reuse it. 376 // Note: on some platforms the tab object may have changed, so we ensure 377 // the tab link is up to date. 378 tab_link = local_tab_map_iter->second.get(); 379 local_tab_map_iter->second->set_tab(tab); 380 } 381 DCHECK(tab_link); 382 DCHECK_NE(tab_link->tab_node_id(), TabNodePool::kInvalidTabNodeID); 383 384 DVLOG(1) << "Reloading tab " << tab_id << " from window " 385 << tab->GetWindowId(); 386 return WriteTabContentsToSyncModel(tab_link, error); 387 } 388 389 // static 390 GURL SessionModelAssociator::GetCurrentVirtualURL( 391 const SyncedTabDelegate& tab_delegate) { 392 const int current_index = tab_delegate.GetCurrentEntryIndex(); 393 const int pending_index = tab_delegate.GetPendingEntryIndex(); 394 const NavigationEntry* current_entry = 395 (current_index == pending_index) ? 396 tab_delegate.GetPendingEntry() : 397 tab_delegate.GetEntryAtIndex(current_index); 398 return current_entry->GetVirtualURL(); 399 } 400 401 // static 402 GURL SessionModelAssociator::GetCurrentFaviconURL( 403 const SyncedTabDelegate& tab_delegate) { 404 const int current_index = tab_delegate.GetCurrentEntryIndex(); 405 const int pending_index = tab_delegate.GetPendingEntryIndex(); 406 const NavigationEntry* current_entry = 407 (current_index == pending_index) ? 408 tab_delegate.GetPendingEntry() : 409 tab_delegate.GetEntryAtIndex(current_index); 410 return (current_entry->GetFavicon().valid ? 411 current_entry->GetFavicon().url : 412 GURL()); 413 } 414 415 bool SessionModelAssociator::WriteTabContentsToSyncModel( 416 TabLink* tab_link, 417 syncer::SyncError* error) { 418 DCHECK(CalledOnValidThread()); 419 const SyncedTabDelegate& tab_delegate = *(tab_link->tab()); 420 int tab_node_id = tab_link->tab_node_id(); 421 GURL old_tab_url = tab_link->url(); 422 const GURL new_url = GetCurrentVirtualURL(tab_delegate); 423 DVLOG(1) << "Local tab " << tab_delegate.GetSessionId() 424 << " now has URL " << new_url.spec(); 425 426 SessionTab* session_tab = NULL; 427 { 428 syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); 429 syncer::WriteNode tab_node(&trans); 430 if (tab_node.InitByClientTagLookup( 431 syncer::SESSIONS, 432 TabNodePool::TabIdToTag(current_machine_tag_, tab_node_id)) != 433 syncer::BaseNode::INIT_OK) { 434 if (error) { 435 *error = error_handler_->CreateAndUploadError( 436 FROM_HERE, 437 "Failed to look up local tab node", 438 model_type()); 439 } 440 return false; 441 } 442 443 // Load the last stored version of this tab so we can compare changes. If 444 // this is a new tab, session_tab will be a new, blank SessionTab object. 445 sync_pb::SessionSpecifics specifics = tab_node.GetSessionSpecifics(); 446 const int s_tab_node_id(specifics.tab_node_id()); 447 DCHECK_EQ(tab_node_id, s_tab_node_id); 448 session_tab = 449 synced_session_tracker_.GetTab(GetCurrentMachineTag(), 450 tab_delegate.GetSessionId(), 451 specifics.tab_node_id()); 452 SetSessionTabFromDelegate(tab_delegate, base::Time::Now(), session_tab); 453 sync_pb::SessionTab tab_s = session_tab->ToSyncData(); 454 455 if (new_url == old_tab_url) { 456 // Load the old specifics and copy over the favicon data if needed. 457 // TODO(zea): remove this once favicon sync is enabled as a separate type. 458 tab_s.set_favicon(specifics.tab().favicon()); 459 tab_s.set_favicon_source(specifics.tab().favicon_source()); 460 tab_s.set_favicon_type(specifics.tab().favicon_type()); 461 } 462 // Retain the base SessionSpecifics data (tag, tab_node_id, etc.), and just 463 // write the new SessionTabSpecifics. 464 specifics.mutable_tab()->CopyFrom(tab_s); 465 466 // Write into the actual sync model. 467 tab_node.SetSessionSpecifics(specifics); 468 } 469 470 // Trigger the favicon load if needed. We do this outside the write 471 // transaction to avoid jank. 472 tab_link->set_url(new_url); 473 if (new_url != old_tab_url) { 474 favicon_cache_.OnFaviconVisited(new_url, 475 GetCurrentFaviconURL(tab_delegate)); 476 } 477 478 // Update our last modified time. 479 synced_session_tracker_.GetSession(GetCurrentMachineTag())->modified_time = 480 base::Time::Now(); 481 482 return true; 483 } 484 485 // static 486 void SessionModelAssociator::SetSessionTabFromDelegate( 487 const SyncedTabDelegate& tab_delegate, 488 base::Time mtime, 489 SessionTab* session_tab) { 490 DCHECK(session_tab); 491 session_tab->window_id.set_id(tab_delegate.GetWindowId()); 492 session_tab->tab_id.set_id(tab_delegate.GetSessionId()); 493 session_tab->tab_visual_index = 0; 494 session_tab->current_navigation_index = tab_delegate.GetCurrentEntryIndex(); 495 session_tab->pinned = tab_delegate.IsPinned(); 496 session_tab->extension_app_id = tab_delegate.GetExtensionAppId(); 497 session_tab->user_agent_override.clear(); 498 session_tab->timestamp = mtime; 499 const int current_index = tab_delegate.GetCurrentEntryIndex(); 500 const int pending_index = tab_delegate.GetPendingEntryIndex(); 501 const int min_index = std::max(0, current_index - kMaxSyncNavigationCount); 502 const int max_index = std::min(current_index + kMaxSyncNavigationCount, 503 tab_delegate.GetEntryCount()); 504 bool is_managed = tab_delegate.ProfileIsManaged(); 505 session_tab->navigations.clear(); 506 for (int i = min_index; i < max_index; ++i) { 507 const NavigationEntry* entry = (i == pending_index) ? 508 tab_delegate.GetPendingEntry() : tab_delegate.GetEntryAtIndex(i); 509 DCHECK(entry); 510 if (!entry->GetVirtualURL().is_valid()) 511 continue; 512 513 session_tab->navigations.push_back( 514 SerializedNavigationEntry::FromNavigationEntry(i, *entry)); 515 if (is_managed) { 516 session_tab->navigations.back().set_blocked_state( 517 SerializedNavigationEntry::STATE_ALLOWED); 518 } 519 } 520 521 if (is_managed) { 522 const std::vector<const NavigationEntry*>& blocked_navigations = 523 *tab_delegate.GetBlockedNavigations(); 524 int offset = session_tab->navigations.size(); 525 for (size_t i = 0; i < blocked_navigations.size(); ++i) { 526 session_tab->navigations.push_back( 527 SerializedNavigationEntry::FromNavigationEntry( 528 i + offset, *blocked_navigations[i])); 529 session_tab->navigations.back().set_blocked_state( 530 SerializedNavigationEntry::STATE_BLOCKED); 531 // TODO(bauerb): Add categories 532 } 533 } 534 session_tab->session_storage_persistent_id.clear(); 535 } 536 537 void SessionModelAssociator::FaviconsUpdated( 538 const std::set<GURL>& urls) { 539 // TODO(zea): consider a separate container for tabs with outstanding favicon 540 // loads so we don't have to iterate through all tabs comparing urls. 541 for (std::set<GURL>::const_iterator i = urls.begin(); i != urls.end(); ++i) { 542 for (TabLinksMap::iterator tab_iter = local_tab_map_.begin(); 543 tab_iter != local_tab_map_.end(); 544 ++tab_iter) { 545 if (tab_iter->second->url() == *i) 546 favicon_cache_.OnPageFaviconUpdated(*i); 547 } 548 } 549 } 550 551 syncer::SyncError SessionModelAssociator::AssociateModels( 552 syncer::SyncMergeResult* local_merge_result, 553 syncer::SyncMergeResult* syncer_merge_result) { 554 DCHECK(CalledOnValidThread()); 555 syncer::SyncError error; 556 557 // Ensure that we disassociated properly, otherwise memory might leak. 558 DCHECK(synced_session_tracker_.Empty()); 559 DCHECK_EQ(0U, local_tab_pool_.Capacity()); 560 561 local_session_syncid_ = syncer::kInvalidId; 562 563 scoped_ptr<DeviceInfo> local_device_info(sync_service_->GetLocalDeviceInfo()); 564 565 #if defined(OS_ANDROID) 566 std::string transaction_tag; 567 #endif 568 // Read any available foreign sessions and load any session data we may have. 569 // If we don't have any local session data in the db, create a header node. 570 { 571 syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); 572 573 syncer::ReadNode root(&trans); 574 if (root.InitByTagLookup(syncer::ModelTypeToRootTag(model_type())) != 575 syncer::BaseNode::INIT_OK) { 576 return error_handler_->CreateAndUploadError( 577 FROM_HERE, 578 kNoSessionsFolderError, 579 model_type()); 580 } 581 582 // Make sure we have a machine tag. 583 if (current_machine_tag_.empty()) 584 InitializeCurrentMachineTag(&trans); 585 if (local_device_info) { 586 current_session_name_ = local_device_info->client_name(); 587 } else { 588 return error_handler_->CreateAndUploadError( 589 FROM_HERE, 590 "Failed to get device info.", 591 model_type()); 592 } 593 synced_session_tracker_.SetLocalSessionTag(current_machine_tag_); 594 if (!UpdateAssociationsFromSyncModel(root, &trans, &error)) { 595 DCHECK(error.IsSet()); 596 return error; 597 } 598 599 if (local_session_syncid_ == syncer::kInvalidId) { 600 // The sync db didn't have a header node for us, we need to create one. 601 syncer::WriteNode write_node(&trans); 602 syncer::WriteNode::InitUniqueByCreationResult result = 603 write_node.InitUniqueByCreation(SESSIONS, root, current_machine_tag_); 604 if (result != syncer::WriteNode::INIT_SUCCESS) { 605 // If we can't look it up, and we can't create it, chances are there's 606 // a pre-existing node that has encryption issues. But, since we can't 607 // load the item, we can't remove it, and error out at this point. 608 return error_handler_->CreateAndUploadError( 609 FROM_HERE, 610 "Failed to create sessions header sync node.", 611 model_type()); 612 } 613 614 // Write the initial values to the specifics so that in case of a crash or 615 // error we don't persist a half-written node. 616 write_node.SetTitle(UTF8ToWide(current_machine_tag_)); 617 sync_pb::SessionSpecifics base_specifics; 618 base_specifics.set_session_tag(current_machine_tag_); 619 sync_pb::SessionHeader* header_s = base_specifics.mutable_header(); 620 header_s->set_client_name(current_session_name_); 621 header_s->set_device_type(DeviceInfo::GetLocalDeviceType()); 622 write_node.SetSessionSpecifics(base_specifics); 623 624 local_session_syncid_ = write_node.GetId(); 625 } 626 #if defined(OS_ANDROID) 627 transaction_tag = GetMachineTagFromTransaction(&trans); 628 #endif 629 } 630 #if defined(OS_ANDROID) 631 // We need to delete foreign sessions after giving up our 632 // syncer::WriteTransaction, since DeleteForeignSession(std::string&) uses 633 // its own syncer::WriteTransaction. 634 if (current_machine_tag_.compare(transaction_tag) != 0) 635 DeleteForeignSession(transaction_tag); 636 #endif 637 638 // Check if anything has changed on the client side. 639 if (!UpdateSyncModelDataFromClient(&error)) { 640 DCHECK(error.IsSet()); 641 return error; 642 } 643 644 DVLOG(1) << "Session models associated."; 645 DCHECK(!error.IsSet()); 646 return error; 647 } 648 649 syncer::SyncError SessionModelAssociator::DisassociateModels() { 650 DCHECK(CalledOnValidThread()); 651 DVLOG(1) << "Disassociating local session " << GetCurrentMachineTag(); 652 synced_session_tracker_.Clear(); 653 local_tab_map_.clear(); 654 local_tab_pool_.Clear(); 655 local_session_syncid_ = syncer::kInvalidId; 656 current_machine_tag_ = ""; 657 current_session_name_ = ""; 658 659 // There is no local model stored with which to disassociate, just notify 660 // foreign session handlers. 661 content::NotificationService::current()->Notify( 662 chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED, 663 content::Source<Profile>(sync_service_->profile()), 664 content::NotificationService::NoDetails()); 665 return syncer::SyncError(); 666 } 667 668 void SessionModelAssociator::InitializeCurrentMachineTag( 669 syncer::WriteTransaction* trans) { 670 DCHECK(CalledOnValidThread()); 671 DCHECK(current_machine_tag_.empty()); 672 std::string persisted_guid; 673 browser_sync::SyncPrefs prefs(profile_->GetPrefs()); 674 persisted_guid = prefs.GetSyncSessionsGUID(); 675 if (!persisted_guid.empty()) { 676 current_machine_tag_ = persisted_guid; 677 DVLOG(1) << "Restoring persisted session sync guid: " 678 << persisted_guid; 679 } else { 680 current_machine_tag_ = GetMachineTagFromTransaction(trans); 681 DVLOG(1) << "Creating session sync guid: " << current_machine_tag_; 682 prefs.SetSyncSessionsGUID(current_machine_tag_); 683 } 684 685 local_tab_pool_.SetMachineTag(current_machine_tag_); 686 } 687 688 bool SessionModelAssociator::GetSyncedFaviconForPageURL( 689 const std::string& page_url, 690 scoped_refptr<base::RefCountedMemory>* favicon_png) const { 691 return favicon_cache_.GetSyncedFaviconForPageURL(GURL(page_url), favicon_png); 692 } 693 694 scoped_ptr<browser_sync::DeviceInfo> 695 SessionModelAssociator::GetDeviceInfoForSessionTag( 696 const std::string& session_tag) { 697 std::string client_id = GetClientIdFromSessionTag(session_tag); 698 return sync_service_->GetDeviceInfo(client_id); 699 } 700 701 bool SessionModelAssociator::UpdateAssociationsFromSyncModel( 702 const syncer::ReadNode& root, 703 syncer::WriteTransaction* trans, 704 syncer::SyncError* error) { 705 DCHECK(CalledOnValidThread()); 706 DCHECK(local_tab_pool_.Empty()); 707 DCHECK_EQ(local_session_syncid_, syncer::kInvalidId); 708 709 // Iterate through the nodes and associate any foreign sessions. 710 int64 id = root.GetFirstChildId(); 711 while (id != syncer::kInvalidId) { 712 syncer::WriteNode sync_node(trans); 713 if (sync_node.InitByIdLookup(id) != syncer::BaseNode::INIT_OK) { 714 if (error) { 715 *error = error_handler_->CreateAndUploadError( 716 FROM_HERE, 717 "Failed to load sync node", 718 model_type()); 719 } 720 return false; 721 } 722 int64 next_id = sync_node.GetSuccessorId(); 723 724 const sync_pb::SessionSpecifics& specifics = 725 sync_node.GetSessionSpecifics(); 726 const base::Time& modification_time = sync_node.GetModificationTime(); 727 if (specifics.session_tag().empty() || 728 (specifics.has_tab() && (!specifics.has_tab_node_id() || 729 !specifics.tab().has_tab_id()))) { 730 // This is a corrupted node. Just delete it. 731 LOG(WARNING) << "Found invalid session node, deleting."; 732 sync_node.Tombstone(); 733 } else if (specifics.session_tag() != GetCurrentMachineTag()) { 734 AssociateForeignSpecifics(specifics, modification_time); 735 } else { 736 // This is previously stored local session information. 737 if (specifics.has_header() && 738 local_session_syncid_ == syncer::kInvalidId) { 739 // This is our previous header node, reuse it. 740 local_session_syncid_ = id; 741 if (specifics.header().has_client_name()) { 742 current_session_name_ = specifics.header().client_name(); 743 } 744 } else { 745 if (specifics.has_header() || !specifics.has_tab()) { 746 LOG(WARNING) << "Found invalid session node, deleting."; 747 sync_node.Tombstone(); 748 } else { 749 // This is a valid old tab node, add it to the pool so it can be 750 // reused for reassociation. 751 local_tab_pool_.AddTabNode(specifics.tab_node_id()); 752 } 753 } 754 } 755 id = next_id; 756 } 757 758 return true; 759 } 760 761 void SessionModelAssociator::AssociateForeignSpecifics( 762 const sync_pb::SessionSpecifics& specifics, 763 const base::Time& modification_time) { 764 DCHECK(CalledOnValidThread()); 765 std::string foreign_session_tag = specifics.session_tag(); 766 if (foreign_session_tag == GetCurrentMachineTag() && !setup_for_test_) 767 return; 768 769 SyncedSession* foreign_session = 770 synced_session_tracker_.GetSession(foreign_session_tag); 771 if (specifics.has_header()) { 772 // Read in the header data for this foreign session. 773 // Header data contains window information and ordered tab id's for each 774 // window. 775 776 // Load (or create) the SyncedSession object for this client. 777 const sync_pb::SessionHeader& header = specifics.header(); 778 PopulateSessionHeaderFromSpecifics(header, 779 modification_time, 780 foreign_session); 781 782 // Reset the tab/window tracking for this session (must do this before 783 // we start calling PutWindowInSession and PutTabInWindow so that all 784 // unused tabs/windows get cleared by the CleanupSession(...) call). 785 synced_session_tracker_.ResetSessionTracking(foreign_session_tag); 786 787 // Process all the windows and their tab information. 788 int num_windows = header.window_size(); 789 DVLOG(1) << "Associating " << foreign_session_tag << " with " 790 << num_windows << " windows."; 791 for (int i = 0; i < num_windows; ++i) { 792 const sync_pb::SessionWindow& window_s = header.window(i); 793 SessionID::id_type window_id = window_s.window_id(); 794 synced_session_tracker_.PutWindowInSession(foreign_session_tag, 795 window_id); 796 PopulateSessionWindowFromSpecifics(foreign_session_tag, 797 window_s, 798 modification_time, 799 foreign_session->windows[window_id], 800 &synced_session_tracker_); 801 } 802 803 // Delete any closed windows and unused tabs as necessary. 804 synced_session_tracker_.CleanupSession(foreign_session_tag); 805 } else if (specifics.has_tab()) { 806 const sync_pb::SessionTab& tab_s = specifics.tab(); 807 SessionID::id_type tab_id = tab_s.tab_id(); 808 SessionTab* tab = 809 synced_session_tracker_.GetTab(foreign_session_tag, 810 tab_id, 811 specifics.tab_node_id()); 812 813 // Update SessionTab based on protobuf. 814 tab->SetFromSyncData(tab_s, modification_time); 815 816 // If a favicon or favicon urls are present, load them into the in-memory 817 // favicon cache. 818 LoadForeignTabFavicon(tab_s); 819 820 // Update the last modified time. 821 if (foreign_session->modified_time < modification_time) 822 foreign_session->modified_time = modification_time; 823 } else { 824 LOG(WARNING) << "Ignoring foreign session node with missing header/tab " 825 << "fields and tag " << foreign_session_tag << "."; 826 } 827 } 828 829 bool SessionModelAssociator::DisassociateForeignSession( 830 const std::string& foreign_session_tag) { 831 DCHECK(CalledOnValidThread()); 832 if (foreign_session_tag == GetCurrentMachineTag()) { 833 DVLOG(1) << "Local session deleted! Doing nothing until a navigation is " 834 << "triggered."; 835 return false; 836 } 837 DVLOG(1) << "Disassociating session " << foreign_session_tag; 838 return synced_session_tracker_.DeleteSession(foreign_session_tag); 839 } 840 841 // Static 842 void SessionModelAssociator::PopulateSessionHeaderFromSpecifics( 843 const sync_pb::SessionHeader& header_specifics, 844 base::Time mtime, 845 SyncedSession* session_header) { 846 if (header_specifics.has_client_name()) { 847 session_header->session_name = header_specifics.client_name(); 848 } 849 if (header_specifics.has_device_type()) { 850 switch (header_specifics.device_type()) { 851 case sync_pb::SyncEnums_DeviceType_TYPE_WIN: 852 session_header->device_type = SyncedSession::TYPE_WIN; 853 break; 854 case sync_pb::SyncEnums_DeviceType_TYPE_MAC: 855 session_header->device_type = SyncedSession::TYPE_MACOSX; 856 break; 857 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX: 858 session_header->device_type = SyncedSession::TYPE_LINUX; 859 break; 860 case sync_pb::SyncEnums_DeviceType_TYPE_CROS: 861 session_header->device_type = SyncedSession::TYPE_CHROMEOS; 862 break; 863 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE: 864 session_header->device_type = SyncedSession::TYPE_PHONE; 865 break; 866 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET: 867 session_header->device_type = SyncedSession::TYPE_TABLET; 868 break; 869 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER: 870 // Intentionally fall-through 871 default: 872 session_header->device_type = SyncedSession::TYPE_OTHER; 873 break; 874 } 875 } 876 session_header->modified_time = mtime; 877 } 878 879 // Static 880 void SessionModelAssociator::PopulateSessionWindowFromSpecifics( 881 const std::string& session_tag, 882 const sync_pb::SessionWindow& specifics, 883 base::Time mtime, 884 SessionWindow* session_window, 885 SyncedSessionTracker* tracker) { 886 if (specifics.has_window_id()) 887 session_window->window_id.set_id(specifics.window_id()); 888 if (specifics.has_selected_tab_index()) 889 session_window->selected_tab_index = specifics.selected_tab_index(); 890 if (specifics.has_browser_type()) { 891 if (specifics.browser_type() == 892 sync_pb::SessionWindow_BrowserType_TYPE_TABBED) { 893 session_window->type = 1; 894 } else { 895 session_window->type = 2; 896 } 897 } 898 session_window->timestamp = mtime; 899 session_window->tabs.resize(specifics.tab_size(), NULL); 900 for (int i = 0; i < specifics.tab_size(); i++) { 901 SessionID::id_type tab_id = specifics.tab(i); 902 tracker->PutTabInWindow(session_tag, 903 session_window->window_id.id(), 904 tab_id, 905 i); 906 } 907 } 908 909 void SessionModelAssociator::LoadForeignTabFavicon( 910 const sync_pb::SessionTab& tab) { 911 // First go through and iterate over all the navigations, checking if any 912 // have valid favicon urls. 913 for (int i = 0; i < tab.navigation_size(); ++i) { 914 if (!tab.navigation(i).favicon_url().empty()) { 915 const std::string& page_url = tab.navigation(i).virtual_url(); 916 const std::string& favicon_url = tab.navigation(i).favicon_url(); 917 favicon_cache_.OnReceivedSyncFavicon(GURL(page_url), 918 GURL(favicon_url), 919 std::string(), 920 syncer::TimeToProtoTime( 921 base::Time::Now())); 922 } 923 } 924 925 // Then go through and check for any legacy favicon data. 926 if (!tab.has_favicon() || tab.favicon().empty()) 927 return; 928 if (!tab.has_favicon_type() || 929 tab.favicon_type() != sync_pb::SessionTab::TYPE_WEB_FAVICON) { 930 DVLOG(1) << "Ignoring non-web favicon."; 931 return; 932 } 933 if (tab.navigation_size() == 0) 934 return; 935 int selected_index = tab.current_navigation_index(); 936 selected_index = std::max( 937 0, 938 std::min(selected_index, 939 static_cast<int>(tab.navigation_size() - 1))); 940 GURL navigation_url(tab.navigation(selected_index).virtual_url()); 941 if (!navigation_url.is_valid()) 942 return; 943 GURL favicon_source(tab.favicon_source()); 944 if (!favicon_source.is_valid()) 945 return; 946 947 const std::string& favicon = tab.favicon(); 948 DVLOG(1) << "Storing synced favicon for url " << navigation_url.spec() 949 << " with size " << favicon.size() << " bytes."; 950 favicon_cache_.OnReceivedSyncFavicon(navigation_url, 951 favicon_source, 952 favicon, 953 syncer::TimeToProtoTime( 954 base::Time::Now())); 955 } 956 957 bool SessionModelAssociator::UpdateSyncModelDataFromClient( 958 syncer::SyncError* error) { 959 DCHECK(CalledOnValidThread()); 960 961 // Associate all open windows and their tabs. 962 return AssociateWindows(true, error); 963 } 964 965 void SessionModelAssociator::AttemptSessionsDataRefresh() const { 966 DVLOG(1) << "Triggering sync refresh for sessions datatype."; 967 const syncer::ModelTypeSet types(syncer::SESSIONS); 968 content::NotificationService::current()->Notify( 969 chrome::NOTIFICATION_SYNC_REFRESH_LOCAL, 970 content::Source<Profile>(profile_), 971 content::Details<const syncer::ModelTypeSet>(&types)); 972 } 973 974 bool SessionModelAssociator::GetLocalSession( 975 const SyncedSession* * local_session) { 976 DCHECK(CalledOnValidThread()); 977 if (current_machine_tag_.empty()) 978 return false; 979 *local_session = synced_session_tracker_.GetSession(GetCurrentMachineTag()); 980 return true; 981 } 982 983 bool SessionModelAssociator::GetAllForeignSessions( 984 std::vector<const SyncedSession*>* sessions) { 985 DCHECK(CalledOnValidThread()); 986 return synced_session_tracker_.LookupAllForeignSessions(sessions); 987 } 988 989 bool SessionModelAssociator::GetForeignSession( 990 const std::string& tag, 991 std::vector<const SessionWindow*>* windows) { 992 DCHECK(CalledOnValidThread()); 993 return synced_session_tracker_.LookupSessionWindows(tag, windows); 994 } 995 996 bool SessionModelAssociator::GetForeignTab( 997 const std::string& tag, 998 const SessionID::id_type tab_id, 999 const SessionTab** tab) { 1000 DCHECK(CalledOnValidThread()); 1001 const SessionTab* synced_tab = NULL; 1002 bool success = synced_session_tracker_.LookupSessionTab(tag, 1003 tab_id, 1004 &synced_tab); 1005 if (success) 1006 *tab = synced_tab; 1007 return success; 1008 } 1009 1010 void SessionModelAssociator::DeleteStaleSessions() { 1011 DCHECK(CalledOnValidThread()); 1012 std::vector<const SyncedSession*> sessions; 1013 if (!GetAllForeignSessions(&sessions)) 1014 return; // No foreign sessions. 1015 1016 // Iterate through all the sessions and delete any with age older than 1017 // |stale_session_threshold_days_|. 1018 for (std::vector<const SyncedSession*>::const_iterator iter = 1019 sessions.begin(); iter != sessions.end(); ++iter) { 1020 const SyncedSession* session = *iter; 1021 int session_age_in_days = 1022 (base::Time::Now() - session->modified_time).InDays(); 1023 std::string session_tag = session->session_tag; 1024 if (session_age_in_days > 0 && // If false, local clock is not trustworty. 1025 static_cast<size_t>(session_age_in_days) > 1026 stale_session_threshold_days_) { 1027 DVLOG(1) << "Found stale session " << session_tag 1028 << " with age " << session_age_in_days << ", deleting."; 1029 DeleteForeignSession(session_tag); 1030 } 1031 } 1032 } 1033 1034 void SessionModelAssociator::SetStaleSessionThreshold( 1035 size_t stale_session_threshold_days) { 1036 DCHECK(CalledOnValidThread()); 1037 if (stale_session_threshold_days_ == 0) { 1038 NOTREACHED() << "Attempted to set invalid stale session threshold."; 1039 return; 1040 } 1041 stale_session_threshold_days_ = stale_session_threshold_days; 1042 // TODO(zea): maybe make this preference-based? Might be nice to let users be 1043 // able to modify this once and forget about it. At the moment, if we want a 1044 // different threshold we will need to call this everytime we create a new 1045 // model associator and before we AssociateModels (probably from DTC). 1046 } 1047 1048 void SessionModelAssociator::DeleteForeignSession(const std::string& tag) { 1049 DCHECK(CalledOnValidThread()); 1050 if (tag == GetCurrentMachineTag()) { 1051 LOG(ERROR) << "Attempting to delete local session. This is not currently " 1052 << "supported."; 1053 return; 1054 } 1055 1056 if (!DisassociateForeignSession(tag)) { 1057 // We don't have any data for this session, our work here is done! 1058 return; 1059 } 1060 1061 syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); 1062 syncer::ReadNode root(&trans); 1063 if (root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)) != 1064 syncer::BaseNode::INIT_OK) { 1065 LOG(ERROR) << kNoSessionsFolderError; 1066 return; 1067 } 1068 int64 id = root.GetFirstChildId(); 1069 while (id != syncer::kInvalidId) { 1070 syncer::WriteNode sync_node(&trans); 1071 if (sync_node.InitByIdLookup(id) != syncer::BaseNode::INIT_OK) { 1072 LOG(ERROR) << "Failed to fetch sync node for id " << id; 1073 continue; 1074 } 1075 id = sync_node.GetSuccessorId(); 1076 const sync_pb::SessionSpecifics& specifics = 1077 sync_node.GetSessionSpecifics(); 1078 if (specifics.session_tag() == tag) 1079 sync_node.Tombstone(); 1080 } 1081 } 1082 1083 bool SessionModelAssociator::IsValidTab(const SyncedTabDelegate& tab) const { 1084 if ((!sync_service_ || tab.profile() != sync_service_->profile()) && 1085 !setup_for_test_) { 1086 return false; 1087 } 1088 const SyncedWindowDelegate* window = 1089 SyncedWindowDelegate::FindSyncedWindowDelegateWithId( 1090 tab.GetWindowId()); 1091 if (!window && !setup_for_test_) 1092 return false; 1093 return true; 1094 } 1095 1096 bool SessionModelAssociator::TabHasValidEntry( 1097 const SyncedTabDelegate& tab) const { 1098 if (tab.ProfileIsManaged() && tab.GetBlockedNavigations()->size() > 0) 1099 return true; 1100 1101 int entry_count = tab.GetEntryCount(); 1102 if (entry_count == 0) 1103 return false; // This deliberately ignores a new pending entry. 1104 1105 int pending_index = tab.GetPendingEntryIndex(); 1106 bool found_valid_url = false; 1107 for (int i = 0; i < entry_count; ++i) { 1108 const content::NavigationEntry* entry = (i == pending_index) ? 1109 tab.GetPendingEntry() : tab.GetEntryAtIndex(i); 1110 if (!entry) 1111 return false; 1112 if (entry->GetVirtualURL().is_valid() && 1113 !entry->GetVirtualURL().SchemeIs("chrome") && 1114 !entry->GetVirtualURL().SchemeIsFile()) { 1115 found_valid_url = true; 1116 } 1117 } 1118 return found_valid_url; 1119 } 1120 1121 // If this functionality changes, browser_sync::ShouldSyncSessionTab should be 1122 // modified to match. 1123 bool SessionModelAssociator::ShouldSyncTab(const SyncedTabDelegate& tab) const { 1124 DCHECK(CalledOnValidThread()); 1125 if (!IsValidTab(tab)) 1126 return false; 1127 return TabHasValidEntry(tab); 1128 } 1129 1130 void SessionModelAssociator::QuitLoopForSubtleTesting() { 1131 if (waiting_for_change_) { 1132 DVLOG(1) << "Quitting base::MessageLoop for test."; 1133 waiting_for_change_ = false; 1134 test_weak_factory_.InvalidateWeakPtrs(); 1135 base::MessageLoop::current()->Quit(); 1136 } 1137 } 1138 1139 FaviconCache* SessionModelAssociator::GetFaviconCache() { 1140 return &favicon_cache_; 1141 } 1142 1143 void SessionModelAssociator::BlockUntilLocalChangeForTest( 1144 base::TimeDelta timeout) { 1145 if (test_weak_factory_.HasWeakPtrs()) 1146 return; 1147 waiting_for_change_ = true; 1148 base::MessageLoop::current()->PostDelayedTask( 1149 FROM_HERE, 1150 base::Bind(&SessionModelAssociator::QuitLoopForSubtleTesting, 1151 test_weak_factory_.GetWeakPtr()), 1152 timeout); 1153 } 1154 1155 bool SessionModelAssociator::CryptoReadyIfNecessary() { 1156 // We only access the cryptographer while holding a transaction. 1157 syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare()); 1158 const syncer::ModelTypeSet encrypted_types = trans.GetEncryptedTypes(); 1159 return !encrypted_types.Has(SESSIONS) || 1160 sync_service_->IsCryptographerReady(&trans); 1161 } 1162 1163 void SessionModelAssociator::UpdateTabIdIfNecessary( 1164 int tab_node_id, 1165 SessionID::id_type new_tab_id) { 1166 DCHECK_NE(tab_node_id, TabNodePool::kInvalidTabNodeID); 1167 SessionID::id_type old_tab_id = 1168 local_tab_pool_.GetTabIdFromTabNodeId(tab_node_id); 1169 if (old_tab_id != new_tab_id) { 1170 // Rewrite tab id if required. 1171 syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); 1172 syncer::WriteNode tab_node(&trans); 1173 if (tab_node.InitByClientTagLookup(syncer::SESSIONS, 1174 TabNodePool::TabIdToTag(current_machine_tag_, tab_node_id)) == 1175 syncer::BaseNode::INIT_OK) { 1176 sync_pb::SessionSpecifics session_specifics = 1177 tab_node.GetSessionSpecifics(); 1178 DCHECK(session_specifics.has_tab()); 1179 if (session_specifics.has_tab()) { 1180 sync_pb::SessionTab* tab_s = session_specifics.mutable_tab(); 1181 tab_s->set_tab_id(new_tab_id); 1182 tab_node.SetSessionSpecifics(session_specifics); 1183 // Update tab node pool with the new association. 1184 local_tab_pool_.ReassociateTabNode(tab_node_id, new_tab_id); 1185 } 1186 } 1187 } 1188 } 1189 1190 } // namespace browser_sync 1191