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