1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/sync/sessions/sessions_sync_manager.h" 6 7 #include "base/strings/string_util.h" 8 #include "chrome/browser/chrome_notification_types.h" 9 #include "chrome/browser/sessions/session_id.h" 10 #include "chrome/browser/sessions/session_tab_helper.h" 11 #include "chrome/browser/sessions/session_types.h" 12 #include "chrome/browser/sync/glue/device_info.h" 13 #include "chrome/browser/sync/glue/session_sync_test_helper.h" 14 #include "chrome/browser/sync/glue/synced_tab_delegate.h" 15 #include "chrome/browser/sync/glue/synced_window_delegate.h" 16 #include "chrome/browser/sync/sessions/notification_service_sessions_router.h" 17 #include "chrome/browser/sync/sessions/sessions_util.h" 18 #include "chrome/browser/sync/sessions/synced_window_delegates_getter.h" 19 #include "chrome/browser/ui/sync/tab_contents_synced_tab_delegate.h" 20 #include "chrome/browser/ui/tabs/tab_strip_model.h" 21 #include "chrome/test/base/browser_with_test_window_test.h" 22 #include "components/sessions/serialized_navigation_entry_test_helper.h" 23 #include "content/public/browser/navigation_entry.h" 24 #include "content/public/browser/notification_details.h" 25 #include "content/public/browser/notification_service.h" 26 #include "content/public/browser/notification_source.h" 27 #include "content/public/browser/web_contents.h" 28 #include "sync/api/attachments/attachment_id.h" 29 #include "sync/api/attachments/attachment_service_proxy_for_test.h" 30 #include "sync/api/sync_error_factory_mock.h" 31 #include "testing/gmock/include/gmock/gmock.h" 32 #include "testing/gtest/include/gtest/gtest.h" 33 34 using content::WebContents; 35 using sessions::SerializedNavigationEntry; 36 using sessions::SerializedNavigationEntryTestHelper; 37 using syncer::SyncChange; 38 using syncer::SyncData; 39 40 namespace browser_sync { 41 42 namespace { 43 44 class SyncedWindowDelegateOverride : public SyncedWindowDelegate { 45 public: 46 explicit SyncedWindowDelegateOverride(SyncedWindowDelegate* wrapped) 47 : wrapped_(wrapped) { 48 } 49 virtual ~SyncedWindowDelegateOverride() {} 50 51 virtual bool HasWindow() const OVERRIDE { 52 return wrapped_->HasWindow(); 53 } 54 55 virtual SessionID::id_type GetSessionId() const OVERRIDE { 56 return wrapped_->GetSessionId(); 57 } 58 59 virtual int GetTabCount() const OVERRIDE { 60 return wrapped_->GetTabCount(); 61 } 62 63 virtual int GetActiveIndex() const OVERRIDE { 64 return wrapped_->GetActiveIndex(); 65 } 66 67 virtual bool IsApp() const OVERRIDE { 68 return wrapped_->IsApp(); 69 } 70 71 virtual bool IsTypeTabbed() const OVERRIDE { 72 return wrapped_->IsTypeTabbed(); 73 } 74 75 virtual bool IsTypePopup() const OVERRIDE { 76 return wrapped_->IsTypePopup(); 77 } 78 79 virtual bool IsTabPinned(const SyncedTabDelegate* tab) const OVERRIDE { 80 return wrapped_->IsTabPinned(tab); 81 } 82 83 virtual SyncedTabDelegate* GetTabAt(int index) const OVERRIDE { 84 if (tab_overrides_.find(index) != tab_overrides_.end()) 85 return tab_overrides_.find(index)->second; 86 87 return wrapped_->GetTabAt(index); 88 } 89 90 void OverrideTabAt(int index, 91 SyncedTabDelegate* delegate, 92 SessionID::id_type tab_id) { 93 tab_overrides_[index] = delegate; 94 tab_id_overrides_[index] = tab_id; 95 } 96 97 virtual SessionID::id_type GetTabIdAt(int index) const OVERRIDE { 98 if (tab_id_overrides_.find(index) != tab_id_overrides_.end()) 99 return tab_id_overrides_.find(index)->second; 100 return wrapped_->GetTabIdAt(index); 101 } 102 103 virtual bool IsSessionRestoreInProgress() const OVERRIDE { 104 return wrapped_->IsSessionRestoreInProgress(); 105 } 106 107 private: 108 std::map<int, SyncedTabDelegate*> tab_overrides_; 109 std::map<int, SessionID::id_type> tab_id_overrides_; 110 SyncedWindowDelegate* wrapped_; 111 }; 112 113 class TestSyncedWindowDelegatesGetter : public SyncedWindowDelegatesGetter { 114 public: 115 TestSyncedWindowDelegatesGetter( 116 const std::set<SyncedWindowDelegate*>& delegates) 117 : delegates_(delegates) {} 118 119 virtual const std::set<SyncedWindowDelegate*> GetSyncedWindowDelegates() 120 OVERRIDE { 121 return delegates_; 122 } 123 private: 124 const std::set<SyncedWindowDelegate*> delegates_; 125 }; 126 127 class TestSyncProcessorStub : public syncer::SyncChangeProcessor { 128 public: 129 explicit TestSyncProcessorStub(syncer::SyncChangeList* output) 130 : output_(output) {} 131 virtual syncer::SyncError ProcessSyncChanges( 132 const tracked_objects::Location& from_here, 133 const syncer::SyncChangeList& change_list) OVERRIDE { 134 if (error_.IsSet()) { 135 syncer::SyncError error = error_; 136 error_ = syncer::SyncError(); 137 return error; 138 } 139 140 if (output_) 141 output_->insert(output_->end(), change_list.begin(), change_list.end()); 142 143 return syncer::SyncError(); 144 } 145 146 virtual syncer::SyncDataList GetAllSyncData(syncer::ModelType type) 147 const OVERRIDE { 148 return sync_data_to_return_; 149 } 150 151 void FailProcessSyncChangesWith(const syncer::SyncError& error) { 152 error_ = error; 153 } 154 155 void SetSyncDataToReturn(const syncer::SyncDataList& data) { 156 sync_data_to_return_ = data; 157 } 158 159 private: 160 syncer::SyncError error_; 161 syncer::SyncChangeList* output_; 162 syncer::SyncDataList sync_data_to_return_; 163 }; 164 165 syncer::SyncChange MakeRemoteChange( 166 int64 id, 167 const sync_pb::SessionSpecifics& specifics, 168 SyncChange::SyncChangeType type) { 169 sync_pb::EntitySpecifics entity; 170 entity.mutable_session()->CopyFrom(specifics); 171 return syncer::SyncChange( 172 FROM_HERE, 173 type, 174 syncer::SyncData::CreateRemoteData( 175 id, 176 entity, 177 base::Time(), 178 syncer::AttachmentIdList(), 179 syncer::AttachmentServiceProxyForTest::Create())); 180 } 181 182 void AddTabsToChangeList( 183 const std::vector<sync_pb::SessionSpecifics>& batch, 184 SyncChange::SyncChangeType type, 185 syncer::SyncChangeList* change_list) { 186 std::vector<sync_pb::SessionSpecifics>::const_iterator iter; 187 for (iter = batch.begin(); 188 iter != batch.end(); ++iter) { 189 sync_pb::EntitySpecifics entity; 190 entity.mutable_session()->CopyFrom(*iter); 191 change_list->push_back(syncer::SyncChange( 192 FROM_HERE, 193 type, 194 syncer::SyncData::CreateRemoteData( 195 iter->tab_node_id(), 196 entity, 197 base::Time(), 198 syncer::AttachmentIdList(), 199 syncer::AttachmentServiceProxyForTest::Create()))); 200 } 201 } 202 203 void AddTabsToSyncDataList(const std::vector<sync_pb::SessionSpecifics> tabs, 204 syncer::SyncDataList* list) { 205 for (size_t i = 0; i < tabs.size(); i++) { 206 sync_pb::EntitySpecifics entity; 207 entity.mutable_session()->CopyFrom(tabs[i]); 208 list->push_back(SyncData::CreateRemoteData( 209 i + 2, 210 entity, 211 base::Time(), 212 syncer::AttachmentIdList(), 213 syncer::AttachmentServiceProxyForTest::Create())); 214 } 215 } 216 217 class DummyRouter : public LocalSessionEventRouter { 218 public: 219 virtual ~DummyRouter() {} 220 virtual void StartRoutingTo(LocalSessionEventHandler* handler) OVERRIDE {} 221 virtual void Stop() OVERRIDE {} 222 }; 223 224 scoped_ptr<LocalSessionEventRouter> NewDummyRouter() { 225 return scoped_ptr<LocalSessionEventRouter>(new DummyRouter()); 226 } 227 228 } // namespace 229 230 class SessionsSyncManagerTest 231 : public BrowserWithTestWindowTest, 232 public SessionsSyncManager::SyncInternalApiDelegate { 233 public: 234 SessionsSyncManagerTest() : test_processor_(NULL) {} 235 236 virtual void SetUp() OVERRIDE { 237 BrowserWithTestWindowTest::SetUp(); 238 browser_sync::NotificationServiceSessionsRouter* router( 239 new browser_sync::NotificationServiceSessionsRouter( 240 profile(), syncer::SyncableService::StartSyncFlare())); 241 manager_.reset(new SessionsSyncManager(profile(), this, 242 scoped_ptr<LocalSessionEventRouter>(router))); 243 } 244 245 virtual void TearDown() OVERRIDE { 246 test_processor_ = NULL; 247 helper()->Reset(); 248 manager_.reset(); 249 BrowserWithTestWindowTest::TearDown(); 250 } 251 252 virtual scoped_ptr<DeviceInfo> GetLocalDeviceInfo() const OVERRIDE { 253 return scoped_ptr<DeviceInfo>( 254 new DeviceInfo(GetLocalSyncCacheGUID(), 255 "Wayne Gretzky's Hacking Box", 256 "Chromium 10k", 257 "Chrome 10k", 258 sync_pb::SyncEnums_DeviceType_TYPE_LINUX)); 259 } 260 261 virtual std::string GetLocalSyncCacheGUID() const OVERRIDE { 262 return "cache_guid"; 263 } 264 265 SessionsSyncManager* manager() { return manager_.get(); } 266 SessionSyncTestHelper* helper() { return &helper_; } 267 268 void InitWithSyncDataTakeOutput(const syncer::SyncDataList& initial_data, 269 syncer::SyncChangeList* output) { 270 test_processor_ = new TestSyncProcessorStub(output); 271 syncer::SyncMergeResult result = manager_->MergeDataAndStartSyncing( 272 syncer::SESSIONS, initial_data, 273 scoped_ptr<syncer::SyncChangeProcessor>(test_processor_), 274 scoped_ptr<syncer::SyncErrorFactory>( 275 new syncer::SyncErrorFactoryMock())); 276 EXPECT_FALSE(result.error().IsSet()); 277 } 278 279 void InitWithNoSyncData() { 280 InitWithSyncDataTakeOutput(syncer::SyncDataList(), NULL); 281 } 282 283 void TriggerProcessSyncChangesError() { 284 test_processor_->FailProcessSyncChangesWith(syncer::SyncError( 285 FROM_HERE, syncer::SyncError::DATATYPE_ERROR, "Error", 286 syncer::SESSIONS)); 287 } 288 289 void SetSyncData(const syncer::SyncDataList& data) { 290 test_processor_->SetSyncDataToReturn(data); 291 } 292 293 syncer::SyncChangeList* FilterOutLocalHeaderChanges( 294 syncer::SyncChangeList* list) { 295 syncer::SyncChangeList::iterator it = list->begin(); 296 bool found = false; 297 while (it != list->end()) { 298 if (syncer::SyncDataLocal(it->sync_data()).GetTag() == 299 manager_->current_machine_tag()) { 300 EXPECT_TRUE(SyncChange::ACTION_ADD == it->change_type() || 301 SyncChange::ACTION_UPDATE == it->change_type()); 302 it = list->erase(it); 303 found = true; 304 } else { 305 ++it; 306 } 307 } 308 EXPECT_TRUE(found); 309 return list; 310 } 311 312 private: 313 scoped_ptr<SessionsSyncManager> manager_; 314 SessionSyncTestHelper helper_; 315 TestSyncProcessorStub* test_processor_; 316 }; 317 318 // Test that the SyncSessionManager can properly fill in a SessionHeader. 319 TEST_F(SessionsSyncManagerTest, PopulateSessionHeader) { 320 sync_pb::SessionHeader header_s; 321 header_s.set_client_name("Client 1"); 322 header_s.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_WIN); 323 324 SyncedSession session; 325 base::Time time = base::Time::Now(); 326 SessionsSyncManager::PopulateSessionHeaderFromSpecifics( 327 header_s, time, &session); 328 ASSERT_EQ("Client 1", session.session_name); 329 ASSERT_EQ(SyncedSession::TYPE_WIN, session.device_type); 330 ASSERT_EQ(time, session.modified_time); 331 } 332 333 // Test translation between protobuf types and chrome session types. 334 TEST_F(SessionsSyncManagerTest, PopulateSessionWindow) { 335 sync_pb::SessionWindow window_s; 336 window_s.add_tab(0); 337 window_s.set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_TABBED); 338 window_s.set_selected_tab_index(1); 339 340 std::string tag = "tag"; 341 SyncedSession* session = manager()->session_tracker_.GetSession(tag); 342 manager()->session_tracker_.PutWindowInSession(tag, 0); 343 manager()->BuildSyncedSessionFromSpecifics( 344 tag, window_s, base::Time(), session->windows[0]); 345 ASSERT_EQ(1U, session->windows[0]->tabs.size()); 346 ASSERT_EQ(1, session->windows[0]->selected_tab_index); 347 ASSERT_EQ(1, session->windows[0]->type); 348 ASSERT_EQ(1U, manager()->session_tracker_.num_synced_sessions()); 349 ASSERT_EQ(1U, 350 manager()->session_tracker_.num_synced_tabs(std::string("tag"))); 351 } 352 353 namespace { 354 355 class SyncedTabDelegateFake : public SyncedTabDelegate { 356 public: 357 SyncedTabDelegateFake() : current_entry_index_(0), 358 pending_entry_index_(-1), 359 is_supervised_(false), 360 sync_id_(-1), 361 blocked_navigations_(NULL) {} 362 virtual ~SyncedTabDelegateFake() {} 363 364 virtual int GetCurrentEntryIndex() const OVERRIDE { 365 return current_entry_index_; 366 } 367 void set_current_entry_index(int i) { 368 current_entry_index_ = i; 369 } 370 371 virtual content::NavigationEntry* GetEntryAtIndex(int i) const OVERRIDE { 372 const int size = entries_.size(); 373 return (size < i + 1) ? NULL : entries_[i]; 374 } 375 376 void AppendEntry(content::NavigationEntry* entry) { 377 entries_.push_back(entry); 378 } 379 380 virtual int GetEntryCount() const OVERRIDE { 381 return entries_.size(); 382 } 383 384 virtual int GetPendingEntryIndex() const OVERRIDE { 385 return pending_entry_index_; 386 } 387 void set_pending_entry_index(int i) { 388 pending_entry_index_ = i; 389 } 390 391 virtual SessionID::id_type GetWindowId() const OVERRIDE { 392 return SessionID::id_type(); 393 } 394 395 virtual SessionID::id_type GetSessionId() const OVERRIDE { 396 return SessionID::id_type(); 397 } 398 399 virtual bool IsBeingDestroyed() const OVERRIDE { return false; } 400 virtual Profile* profile() const OVERRIDE { return NULL; } 401 virtual std::string GetExtensionAppId() const OVERRIDE { 402 return std::string(); 403 } 404 virtual content::NavigationEntry* GetPendingEntry() const OVERRIDE { 405 return NULL; 406 } 407 virtual content::NavigationEntry* GetActiveEntry() const OVERRIDE { 408 return NULL; 409 } 410 virtual bool ProfileIsSupervised() const OVERRIDE { 411 return is_supervised_; 412 } 413 void set_is_supervised(bool is_supervised) { is_supervised_ = is_supervised; } 414 virtual const std::vector<const content::NavigationEntry*>* 415 GetBlockedNavigations() const OVERRIDE { 416 return blocked_navigations_; 417 } 418 void set_blocked_navigations( 419 std::vector<const content::NavigationEntry*>* navs) { 420 blocked_navigations_ = navs; 421 } 422 virtual bool IsPinned() const OVERRIDE { 423 return false; 424 } 425 virtual bool HasWebContents() const OVERRIDE { 426 return false; 427 } 428 virtual content::WebContents* GetWebContents() const OVERRIDE { 429 return NULL; 430 } 431 432 // Session sync related methods. 433 virtual int GetSyncId() const OVERRIDE { 434 return sync_id_; 435 } 436 virtual void SetSyncId(int sync_id) OVERRIDE { 437 sync_id_ = sync_id; 438 } 439 440 void reset() { 441 current_entry_index_ = 0; 442 pending_entry_index_ = -1; 443 sync_id_ = -1; 444 entries_.clear(); 445 } 446 447 private: 448 int current_entry_index_; 449 int pending_entry_index_; 450 bool is_supervised_; 451 int sync_id_; 452 std::vector<const content::NavigationEntry*>* blocked_navigations_; 453 ScopedVector<content::NavigationEntry> entries_; 454 }; 455 456 } // namespace 457 458 // Test that we exclude tabs with only chrome:// and file:// schemed navigations 459 // from ShouldSyncTab(..). 460 TEST_F(SessionsSyncManagerTest, ValidTabs) { 461 SyncedTabDelegateFake tab; 462 463 // A null entry shouldn't crash. 464 tab.AppendEntry(NULL); 465 EXPECT_FALSE(sessions_util::ShouldSyncTab(tab)); 466 tab.reset(); 467 468 // A chrome:// entry isn't valid. 469 content::NavigationEntry* entry(content::NavigationEntry::Create()); 470 entry->SetVirtualURL(GURL("chrome://preferences/")); 471 tab.AppendEntry(entry); 472 EXPECT_FALSE(sessions_util::ShouldSyncTab(tab)); 473 474 475 // A file:// entry isn't valid, even in addition to another entry. 476 content::NavigationEntry* entry2(content::NavigationEntry::Create()); 477 entry2->SetVirtualURL(GURL("file://bla")); 478 tab.AppendEntry(entry2); 479 EXPECT_FALSE(sessions_util::ShouldSyncTab(tab)); 480 481 // Add a valid scheme entry to tab, making the tab valid. 482 content::NavigationEntry* entry3(content::NavigationEntry::Create()); 483 entry3->SetVirtualURL(GURL("http://www.google.com")); 484 tab.AppendEntry(entry3); 485 EXPECT_FALSE(sessions_util::ShouldSyncTab(tab)); 486 } 487 488 // Make sure GetCurrentVirtualURL() returns the virtual URL of the pending 489 // entry if the current entry is pending. 490 TEST_F(SessionsSyncManagerTest, GetCurrentVirtualURLPending) { 491 SyncedTabDelegateFake tab; 492 content::NavigationEntry* entry(content::NavigationEntry::Create()); 493 entry->SetVirtualURL(GURL("http://www.google.com")); 494 tab.AppendEntry(entry); 495 EXPECT_EQ(entry->GetVirtualURL(), manager()->GetCurrentVirtualURL(tab)); 496 } 497 498 // Make sure GetCurrentVirtualURL() returns the virtual URL of the current 499 // entry if the current entry is non-pending. 500 TEST_F(SessionsSyncManagerTest, GetCurrentVirtualURLNonPending) { 501 SyncedTabDelegateFake tab; 502 content::NavigationEntry* entry(content::NavigationEntry::Create()); 503 entry->SetVirtualURL(GURL("http://www.google.com")); 504 tab.AppendEntry(entry); 505 EXPECT_EQ(entry->GetVirtualURL(), manager()->GetCurrentVirtualURL(tab)); 506 } 507 508 static const base::Time kTime1 = base::Time::FromInternalValue(100); 509 static const base::Time kTime2 = base::Time::FromInternalValue(105); 510 static const base::Time kTime3 = base::Time::FromInternalValue(110); 511 static const base::Time kTime4 = base::Time::FromInternalValue(120); 512 static const base::Time kTime5 = base::Time::FromInternalValue(130); 513 514 // Populate the mock tab delegate with some data and navigation 515 // entries and make sure that setting a SessionTab from it preserves 516 // those entries (and clobbers any existing data). 517 TEST_F(SessionsSyncManagerTest, SetSessionTabFromDelegate) { 518 // Create a tab with three valid entries. 519 SyncedTabDelegateFake tab; 520 content::NavigationEntry* entry1(content::NavigationEntry::Create()); 521 entry1->SetVirtualURL(GURL("http://www.google.com")); 522 entry1->SetTimestamp(kTime1); 523 entry1->SetHttpStatusCode(200); 524 content::NavigationEntry* entry2(content::NavigationEntry::Create()); 525 entry2->SetVirtualURL(GURL("http://www.noodle.com")); 526 entry2->SetTimestamp(kTime2); 527 entry2->SetHttpStatusCode(201); 528 content::NavigationEntry* entry3(content::NavigationEntry::Create()); 529 entry3->SetVirtualURL(GURL("http://www.doodle.com")); 530 entry3->SetTimestamp(kTime3); 531 entry3->SetHttpStatusCode(202); 532 533 tab.AppendEntry(entry1); 534 tab.AppendEntry(entry2); 535 tab.AppendEntry(entry3); 536 tab.set_current_entry_index(2); 537 538 SessionTab session_tab; 539 session_tab.window_id.set_id(1); 540 session_tab.tab_id.set_id(1); 541 session_tab.tab_visual_index = 1; 542 session_tab.current_navigation_index = 1; 543 session_tab.pinned = true; 544 session_tab.extension_app_id = "app id"; 545 session_tab.user_agent_override = "override"; 546 session_tab.timestamp = kTime5; 547 session_tab.navigations.push_back( 548 SerializedNavigationEntryTestHelper::CreateNavigation( 549 "http://www.example.com", "Example")); 550 session_tab.session_storage_persistent_id = "persistent id"; 551 manager()->SetSessionTabFromDelegate(tab, kTime4, &session_tab); 552 553 EXPECT_EQ(0, session_tab.window_id.id()); 554 EXPECT_EQ(0, session_tab.tab_id.id()); 555 EXPECT_EQ(0, session_tab.tab_visual_index); 556 EXPECT_EQ(2, session_tab.current_navigation_index); 557 EXPECT_FALSE(session_tab.pinned); 558 EXPECT_TRUE(session_tab.extension_app_id.empty()); 559 EXPECT_TRUE(session_tab.user_agent_override.empty()); 560 EXPECT_EQ(kTime4, session_tab.timestamp); 561 ASSERT_EQ(3u, session_tab.navigations.size()); 562 EXPECT_EQ(entry1->GetVirtualURL(), 563 session_tab.navigations[0].virtual_url()); 564 EXPECT_EQ(entry2->GetVirtualURL(), 565 session_tab.navigations[1].virtual_url()); 566 EXPECT_EQ(entry3->GetVirtualURL(), 567 session_tab.navigations[2].virtual_url()); 568 EXPECT_EQ(kTime1, session_tab.navigations[0].timestamp()); 569 EXPECT_EQ(kTime2, session_tab.navigations[1].timestamp()); 570 EXPECT_EQ(kTime3, session_tab.navigations[2].timestamp()); 571 EXPECT_EQ(200, session_tab.navigations[0].http_status_code()); 572 EXPECT_EQ(201, session_tab.navigations[1].http_status_code()); 573 EXPECT_EQ(202, session_tab.navigations[2].http_status_code()); 574 EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID, 575 session_tab.navigations[0].blocked_state()); 576 EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID, 577 session_tab.navigations[1].blocked_state()); 578 EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID, 579 session_tab.navigations[2].blocked_state()); 580 EXPECT_TRUE(session_tab.session_storage_persistent_id.empty()); 581 } 582 583 // Tests that for supervised users blocked navigations are recorded and marked 584 // as such, while regular navigations are marked as allowed. 585 TEST_F(SessionsSyncManagerTest, BlockedNavigations) { 586 SyncedTabDelegateFake tab; 587 content::NavigationEntry* entry1(content::NavigationEntry::Create()); 588 entry1->SetVirtualURL(GURL("http://www.google.com")); 589 entry1->SetTimestamp(kTime1); 590 tab.AppendEntry(entry1); 591 592 content::NavigationEntry* entry2 = content::NavigationEntry::Create(); 593 entry2->SetVirtualURL(GURL("http://blocked.com/foo")); 594 entry2->SetTimestamp(kTime2); 595 content::NavigationEntry* entry3 = content::NavigationEntry::Create(); 596 entry3->SetVirtualURL(GURL("http://evil.com")); 597 entry3->SetTimestamp(kTime3); 598 ScopedVector<const content::NavigationEntry> blocked_navigations; 599 blocked_navigations.push_back(entry2); 600 blocked_navigations.push_back(entry3); 601 602 tab.set_is_supervised(true); 603 tab.set_blocked_navigations(&blocked_navigations.get()); 604 605 SessionTab session_tab; 606 session_tab.window_id.set_id(1); 607 session_tab.tab_id.set_id(1); 608 session_tab.tab_visual_index = 1; 609 session_tab.current_navigation_index = 1; 610 session_tab.pinned = true; 611 session_tab.extension_app_id = "app id"; 612 session_tab.user_agent_override = "override"; 613 session_tab.timestamp = kTime5; 614 session_tab.navigations.push_back( 615 SerializedNavigationEntryTestHelper::CreateNavigation( 616 "http://www.example.com", "Example")); 617 session_tab.session_storage_persistent_id = "persistent id"; 618 manager()->SetSessionTabFromDelegate(tab, kTime4, &session_tab); 619 620 EXPECT_EQ(0, session_tab.window_id.id()); 621 EXPECT_EQ(0, session_tab.tab_id.id()); 622 EXPECT_EQ(0, session_tab.tab_visual_index); 623 EXPECT_EQ(0, session_tab.current_navigation_index); 624 EXPECT_FALSE(session_tab.pinned); 625 EXPECT_TRUE(session_tab.extension_app_id.empty()); 626 EXPECT_TRUE(session_tab.user_agent_override.empty()); 627 EXPECT_EQ(kTime4, session_tab.timestamp); 628 ASSERT_EQ(3u, session_tab.navigations.size()); 629 EXPECT_EQ(entry1->GetVirtualURL(), 630 session_tab.navigations[0].virtual_url()); 631 EXPECT_EQ(entry2->GetVirtualURL(), 632 session_tab.navigations[1].virtual_url()); 633 EXPECT_EQ(entry3->GetVirtualURL(), 634 session_tab.navigations[2].virtual_url()); 635 EXPECT_EQ(kTime1, session_tab.navigations[0].timestamp()); 636 EXPECT_EQ(kTime2, session_tab.navigations[1].timestamp()); 637 EXPECT_EQ(kTime3, session_tab.navigations[2].timestamp()); 638 EXPECT_EQ(SerializedNavigationEntry::STATE_ALLOWED, 639 session_tab.navigations[0].blocked_state()); 640 EXPECT_EQ(SerializedNavigationEntry::STATE_BLOCKED, 641 session_tab.navigations[1].blocked_state()); 642 EXPECT_EQ(SerializedNavigationEntry::STATE_BLOCKED, 643 session_tab.navigations[2].blocked_state()); 644 EXPECT_TRUE(session_tab.session_storage_persistent_id.empty()); 645 } 646 647 // Tests that the local session header objects is created properly in 648 // presence of no other session activity, once and only once. 649 TEST_F(SessionsSyncManagerTest, MergeLocalSessionNoTabs) { 650 syncer::SyncChangeList out; 651 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); 652 EXPECT_FALSE(manager()->current_machine_tag().empty()); 653 654 EXPECT_EQ(2U, out.size()); 655 EXPECT_TRUE(out[0].IsValid()); 656 EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type()); 657 const SyncData data(out[0].sync_data()); 658 EXPECT_EQ(manager()->current_machine_tag(), 659 syncer::SyncDataLocal(data).GetTag()); 660 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session()); 661 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag()); 662 EXPECT_TRUE(specifics.has_header()); 663 const sync_pb::SessionHeader& header_s = specifics.header(); 664 EXPECT_TRUE(header_s.has_device_type()); 665 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name()); 666 EXPECT_EQ(0, header_s.window_size()); 667 668 EXPECT_TRUE(out[1].IsValid()); 669 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[1].change_type()); 670 const SyncData data_2(out[1].sync_data()); 671 EXPECT_EQ(manager()->current_machine_tag(), 672 syncer::SyncDataLocal(data_2).GetTag()); 673 const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session()); 674 EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag()); 675 EXPECT_TRUE(specifics2.has_header()); 676 const sync_pb::SessionHeader& header_s2 = specifics2.header(); 677 EXPECT_EQ(0, header_s2.window_size()); 678 679 // Now take that header node and feed it in as input. 680 SyncData d(SyncData::CreateRemoteData( 681 1, 682 data.GetSpecifics(), 683 base::Time(), 684 syncer::AttachmentIdList(), 685 syncer::AttachmentServiceProxyForTest::Create())); 686 syncer::SyncDataList in(&d, &d + 1); 687 out.clear(); 688 SessionsSyncManager manager2(profile(), this, NewDummyRouter()); 689 syncer::SyncMergeResult result = manager2.MergeDataAndStartSyncing( 690 syncer::SESSIONS, in, 691 scoped_ptr<syncer::SyncChangeProcessor>( 692 new TestSyncProcessorStub(&out)), 693 scoped_ptr<syncer::SyncErrorFactory>( 694 new syncer::SyncErrorFactoryMock())); 695 ASSERT_FALSE(result.error().IsSet()); 696 697 EXPECT_EQ(1U, out.size()); 698 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[0].change_type()); 699 EXPECT_TRUE(out[0].sync_data().GetSpecifics().session().has_header()); 700 } 701 702 // Ensure model association associates the pre-existing tabs. 703 TEST_F(SessionsSyncManagerTest, SwappedOutOnRestore) { 704 AddTab(browser(), GURL("http://foo1")); 705 NavigateAndCommitActiveTab(GURL("http://foo2")); 706 AddTab(browser(), GURL("http://bar1")); 707 NavigateAndCommitActiveTab(GURL("http://bar2")); 708 AddTab(browser(), GURL("http://baz1")); 709 NavigateAndCommitActiveTab(GURL("http://baz2")); 710 const int kRestoredTabId = 1337; 711 const int kNewTabId = 2468; 712 713 syncer::SyncDataList in; 714 syncer::SyncChangeList out; 715 InitWithSyncDataTakeOutput(in, &out); 716 717 // Should be one header add, 3 tab add/update pairs, one header update. 718 ASSERT_EQ(8U, out.size()); 719 720 // For input, we set up: 721 // * one "normal" fully loaded tab 722 // * one "frozen" tab with no WebContents and a tab_id change 723 // * one "frozen" tab with no WebContents and no tab_id change 724 SyncData t0(SyncData::CreateRemoteData( 725 1, 726 out[2].sync_data().GetSpecifics(), 727 base::Time(), 728 syncer::AttachmentIdList(), 729 syncer::AttachmentServiceProxyForTest::Create())); 730 sync_pb::EntitySpecifics entity(out[4].sync_data().GetSpecifics()); 731 entity.mutable_session()->mutable_tab()->set_tab_id(kRestoredTabId); 732 SyncData t1(SyncData::CreateRemoteData( 733 2, 734 entity, 735 base::Time(), 736 syncer::AttachmentIdList(), 737 syncer::AttachmentServiceProxyForTest::Create())); 738 SyncData t2(SyncData::CreateRemoteData( 739 3, 740 out[6].sync_data().GetSpecifics(), 741 base::Time(), 742 syncer::AttachmentIdList(), 743 syncer::AttachmentServiceProxyForTest::Create())); 744 in.push_back(t0); 745 in.push_back(t1); 746 in.push_back(t2); 747 out.clear(); 748 manager()->StopSyncing(syncer::SESSIONS); 749 750 const std::set<SyncedWindowDelegate*> windows( 751 SyncedWindowDelegate::GetSyncedWindowDelegates()); 752 ASSERT_EQ(1U, windows.size()); 753 SyncedTabDelegateFake t1_override, t2_override; 754 t1_override.SetSyncId(1); // No WebContents by default. 755 t2_override.SetSyncId(2); // No WebContents by default. 756 SyncedWindowDelegateOverride window_override(*windows.begin()); 757 window_override.OverrideTabAt(1, &t1_override, kNewTabId); 758 window_override.OverrideTabAt(2, &t2_override, 759 t2.GetSpecifics().session().tab().tab_id()); 760 std::set<SyncedWindowDelegate*> delegates; 761 delegates.insert(&window_override); 762 scoped_ptr<TestSyncedWindowDelegatesGetter> getter( 763 new TestSyncedWindowDelegatesGetter(delegates)); 764 manager()->synced_window_getter_.reset(getter.release()); 765 766 syncer::SyncMergeResult result = manager()->MergeDataAndStartSyncing( 767 syncer::SESSIONS, in, 768 scoped_ptr<syncer::SyncChangeProcessor>( 769 new TestSyncProcessorStub(&out)), 770 scoped_ptr<syncer::SyncErrorFactory>( 771 new syncer::SyncErrorFactoryMock())); 772 773 // There should be two changes, one for the fully associated tab, and 774 // one for the tab_id update to t1. t2 shouldn't need to be updated. 775 ASSERT_EQ(2U, FilterOutLocalHeaderChanges(&out)->size()); 776 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[0].change_type()); 777 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[1].change_type()); 778 EXPECT_EQ(kNewTabId, 779 out[1].sync_data().GetSpecifics().session().tab().tab_id()); 780 781 // Verify TabLinks. 782 SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_; 783 ASSERT_EQ(3U, tab_map.size()); 784 int t2_tab_id = t2.GetSpecifics().session().tab().tab_id(); 785 EXPECT_EQ(2, tab_map.find(t2_tab_id)->second->tab_node_id()); 786 EXPECT_EQ(1, tab_map.find(kNewTabId)->second->tab_node_id()); 787 int t0_tab_id = out[0].sync_data().GetSpecifics().session().tab().tab_id(); 788 EXPECT_EQ(0, tab_map.find(t0_tab_id)->second->tab_node_id()); 789 // TODO(tim): Once bug 337057 is fixed, we can issue an OnLocalTabModified 790 // from here (using an override similar to above) to return a new tab id 791 // and verify that we don't see any node creations in the SyncChangeProcessor 792 // (similar to how SessionsSyncManagerTest.OnLocalTabModified works.) 793 } 794 795 // Tests MergeDataAndStartSyncing with sync data but no local data. 796 TEST_F(SessionsSyncManagerTest, MergeWithInitialForeignSession) { 797 std::string tag = "tag1"; 798 799 SessionID::id_type n1[] = {5, 10, 13, 17}; 800 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1)); 801 std::vector<sync_pb::SessionSpecifics> tabs1; 802 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 803 tag, tab_list1, &tabs1)); 804 // Add a second window. 805 SessionID::id_type n2[] = {7, 15, 18, 20}; 806 std::vector<SessionID::id_type> tab_list2(n2, n2 + arraysize(n2)); 807 helper()->AddWindowSpecifics(1, tab_list2, &meta); 808 809 // Set up initial data. 810 syncer::SyncDataList initial_data; 811 sync_pb::EntitySpecifics entity; 812 entity.mutable_session()->CopyFrom(meta); 813 initial_data.push_back(SyncData::CreateRemoteData( 814 1, 815 entity, 816 base::Time(), 817 syncer::AttachmentIdList(), 818 syncer::AttachmentServiceProxyForTest::Create())); 819 AddTabsToSyncDataList(tabs1, &initial_data); 820 821 for (size_t i = 0; i < tab_list2.size(); ++i) { 822 sync_pb::EntitySpecifics entity; 823 helper()->BuildTabSpecifics(tag, 0, tab_list2[i], 824 entity.mutable_session()); 825 initial_data.push_back(SyncData::CreateRemoteData( 826 i + 10, 827 entity, 828 base::Time(), 829 syncer::AttachmentIdList(), 830 syncer::AttachmentServiceProxyForTest::Create())); 831 } 832 833 syncer::SyncChangeList output; 834 InitWithSyncDataTakeOutput(initial_data, &output); 835 EXPECT_TRUE(FilterOutLocalHeaderChanges(&output)->empty()); 836 837 std::vector<const SyncedSession*> foreign_sessions; 838 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 839 ASSERT_EQ(1U, foreign_sessions.size()); 840 std::vector<std::vector<SessionID::id_type> > session_reference; 841 session_reference.push_back(tab_list1); 842 session_reference.push_back(tab_list2); 843 helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); 844 } 845 846 // This is a combination of MergeWithInitialForeignSession and 847 // MergeLocalSessionExistingTabs. We repeat some checks performed in each of 848 // those tests to ensure the common mixed scenario works. 849 TEST_F(SessionsSyncManagerTest, MergeWithLocalAndForeignTabs) { 850 // Local. 851 AddTab(browser(), GURL("http://foo1")); 852 NavigateAndCommitActiveTab(GURL("http://foo2")); 853 854 // Foreign. 855 std::string tag = "tag1"; 856 SessionID::id_type n1[] = {5, 10, 13, 17}; 857 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1)); 858 std::vector<sync_pb::SessionSpecifics> tabs1; 859 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 860 tag, tab_list1, &tabs1)); 861 syncer::SyncDataList foreign_data; 862 sync_pb::EntitySpecifics entity; 863 entity.mutable_session()->CopyFrom(meta); 864 foreign_data.push_back(SyncData::CreateRemoteData( 865 1, 866 entity, 867 base::Time(), 868 syncer::AttachmentIdList(), 869 syncer::AttachmentServiceProxyForTest::Create())); 870 AddTabsToSyncDataList(tabs1, &foreign_data); 871 872 syncer::SyncChangeList output; 873 InitWithSyncDataTakeOutput(foreign_data, &output); 874 ASSERT_EQ(4U, output.size()); 875 876 // Verify the local header. 877 EXPECT_TRUE(output[0].IsValid()); 878 EXPECT_EQ(SyncChange::ACTION_ADD, output[0].change_type()); 879 const SyncData data(output[0].sync_data()); 880 EXPECT_EQ(manager()->current_machine_tag(), 881 syncer::SyncDataLocal(data).GetTag()); 882 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session()); 883 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag()); 884 EXPECT_TRUE(specifics.has_header()); 885 const sync_pb::SessionHeader& header_s = specifics.header(); 886 EXPECT_TRUE(header_s.has_device_type()); 887 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name()); 888 EXPECT_EQ(0, header_s.window_size()); 889 890 // Verify the tab node creations and updates with content. 891 for (int i = 1; i < 3; i++) { 892 EXPECT_TRUE(output[i].IsValid()); 893 const SyncData data(output[i].sync_data()); 894 EXPECT_TRUE(StartsWithASCII(syncer::SyncDataLocal(data).GetTag(), 895 manager()->current_machine_tag(), true)); 896 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session()); 897 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag()); 898 } 899 EXPECT_EQ(SyncChange::ACTION_ADD, output[1].change_type()); 900 EXPECT_EQ(SyncChange::ACTION_UPDATE, output[2].change_type()); 901 EXPECT_TRUE(output[2].sync_data().GetSpecifics().session().has_tab()); 902 903 // Verify the header was updated to reflect window state. 904 EXPECT_TRUE(output[3].IsValid()); 905 EXPECT_EQ(SyncChange::ACTION_UPDATE, output[3].change_type()); 906 const SyncData data_2(output[3].sync_data()); 907 EXPECT_EQ(manager()->current_machine_tag(), 908 syncer::SyncDataLocal(data_2).GetTag()); 909 const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session()); 910 EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag()); 911 EXPECT_TRUE(specifics2.has_header()); 912 const sync_pb::SessionHeader& header_s2 = specifics2.header(); 913 EXPECT_EQ(1, header_s2.window_size()); 914 915 // Verify foreign data. 916 std::vector<const SyncedSession*> foreign_sessions; 917 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 918 std::vector<std::vector<SessionID::id_type> > session_reference; 919 session_reference.push_back(tab_list1); 920 helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); 921 // There should be one and only one foreign session. If VerifySyncedSession 922 // was successful above this EXPECT call ensures the local session didn't 923 // get mistakenly added to foreign tracking (Similar to ExistingTabs test). 924 EXPECT_EQ(1U, foreign_sessions.size()); 925 } 926 927 // Tests the common scenario. Merge with both local and foreign session data 928 // followed by updates flowing from sync and local. 929 TEST_F(SessionsSyncManagerTest, UpdatesAfterMixedMerge) { 930 // Add local and foreign data. 931 AddTab(browser(), GURL("http://foo1")); 932 NavigateAndCommitActiveTab(GURL("http://foo2")); 933 934 std::string tag1 = "tag1"; 935 syncer::SyncDataList foreign_data1; 936 std::vector<std::vector<SessionID::id_type> > meta1_reference; 937 sync_pb::SessionSpecifics meta1; 938 939 SessionID::id_type n1[] = {5, 10, 13, 17}; 940 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1)); 941 meta1_reference.push_back(tab_list1); 942 std::vector<sync_pb::SessionSpecifics> tabs1; 943 meta1 = helper()->BuildForeignSession(tag1, tab_list1, &tabs1); 944 sync_pb::EntitySpecifics entity; 945 entity.mutable_session()->CopyFrom(meta1); 946 foreign_data1.push_back(SyncData::CreateRemoteData( 947 1, 948 entity, 949 base::Time(), 950 syncer::AttachmentIdList(), 951 syncer::AttachmentServiceProxyForTest::Create())); 952 AddTabsToSyncDataList(tabs1, &foreign_data1); 953 954 syncer::SyncChangeList output1; 955 InitWithSyncDataTakeOutput(foreign_data1, &output1); 956 ASSERT_EQ(4U, output1.size()); 957 958 // Add a second window to the foreign session. 959 // TODO(tim): Bug 98892. Add local window too when observers are hooked up. 960 SessionID::id_type tab_nums2[] = {7, 15, 18, 20}; 961 std::vector<SessionID::id_type> tab_list2( 962 tab_nums2, tab_nums2 + arraysize(tab_nums2)); 963 meta1_reference.push_back(tab_list2); 964 helper()->AddWindowSpecifics(1, tab_list2, &meta1); 965 std::vector<sync_pb::SessionSpecifics> tabs2; 966 tabs2.resize(tab_list2.size()); 967 for (size_t i = 0; i < tab_list2.size(); ++i) { 968 helper()->BuildTabSpecifics(tag1, 0, tab_list2[i], &tabs2[i]); 969 } 970 971 syncer::SyncChangeList changes; 972 changes.push_back(MakeRemoteChange(1, meta1, SyncChange::ACTION_UPDATE)); 973 AddTabsToChangeList(tabs2, SyncChange::ACTION_ADD, &changes); 974 manager()->ProcessSyncChanges(FROM_HERE, changes); 975 changes.clear(); 976 977 // Check that the foreign session was associated and retrieve the data. 978 std::vector<const SyncedSession*> foreign_sessions; 979 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 980 ASSERT_EQ(1U, foreign_sessions.size()); 981 ASSERT_EQ(4U, foreign_sessions[0]->windows.find(0)->second->tabs.size()); 982 ASSERT_EQ(4U, foreign_sessions[0]->windows.find(1)->second->tabs.size()); 983 helper()->VerifySyncedSession(tag1, meta1_reference, *(foreign_sessions[0])); 984 985 // Add a new foreign session. 986 std::string tag2 = "tag2"; 987 SessionID::id_type n2[] = {107, 115}; 988 std::vector<SessionID::id_type> tag2_tab_list(n2, n2 + arraysize(n2)); 989 std::vector<sync_pb::SessionSpecifics> tag2_tabs; 990 sync_pb::SessionSpecifics meta2(helper()->BuildForeignSession( 991 tag2, tag2_tab_list, &tag2_tabs)); 992 changes.push_back(MakeRemoteChange(100, meta2, SyncChange::ACTION_ADD)); 993 AddTabsToChangeList(tag2_tabs, SyncChange::ACTION_ADD, &changes); 994 995 manager()->ProcessSyncChanges(FROM_HERE, changes); 996 changes.clear(); 997 998 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 999 std::vector<std::vector<SessionID::id_type> > meta2_reference; 1000 meta2_reference.push_back(tag2_tab_list); 1001 ASSERT_EQ(2U, foreign_sessions.size()); 1002 ASSERT_EQ(2U, foreign_sessions[1]->windows.find(0)->second->tabs.size()); 1003 helper()->VerifySyncedSession(tag2, meta2_reference, *(foreign_sessions[1])); 1004 foreign_sessions.clear(); 1005 1006 // Remove a tab from a window. 1007 meta1_reference[0].pop_back(); 1008 tab_list1.pop_back(); 1009 sync_pb::SessionWindow* win = meta1.mutable_header()->mutable_window(0); 1010 win->clear_tab(); 1011 for (std::vector<int>::const_iterator iter = tab_list1.begin(); 1012 iter != tab_list1.end(); ++iter) { 1013 win->add_tab(*iter); 1014 } 1015 syncer::SyncChangeList removal; 1016 removal.push_back(MakeRemoteChange(1, meta1, SyncChange::ACTION_UPDATE)); 1017 AddTabsToChangeList(tabs1, SyncChange::ACTION_UPDATE, &removal); 1018 manager()->ProcessSyncChanges(FROM_HERE, removal); 1019 1020 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1021 ASSERT_EQ(2U, foreign_sessions.size()); 1022 ASSERT_EQ(3U, foreign_sessions[0]->windows.find(0)->second->tabs.size()); 1023 helper()->VerifySyncedSession(tag1, meta1_reference, *(foreign_sessions[0])); 1024 } 1025 1026 // Tests that this SyncSessionManager knows how to delete foreign sessions 1027 // if it wants to. 1028 TEST_F(SessionsSyncManagerTest, DeleteForeignSession) { 1029 InitWithNoSyncData(); 1030 std::string tag = "tag1"; 1031 syncer::SyncChangeList changes; 1032 1033 std::vector<const SyncedSession*> foreign_sessions; 1034 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions)); 1035 manager()->DeleteForeignSessionInternal(tag, &changes); 1036 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions)); 1037 EXPECT_TRUE(changes.empty()); 1038 1039 // Fill an instance of session specifics with a foreign session's data. 1040 std::vector<sync_pb::SessionSpecifics> tabs; 1041 SessionID::id_type n1[] = {5, 10, 13, 17}; 1042 std::vector<SessionID::id_type> tab_nums1(n1, n1 + arraysize(n1)); 1043 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 1044 tag, tab_nums1, &tabs)); 1045 1046 // Update associator with the session's meta node, window, and tabs. 1047 manager()->UpdateTrackerWithForeignSession(meta, base::Time()); 1048 for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs.begin(); 1049 iter != tabs.end(); ++iter) { 1050 manager()->UpdateTrackerWithForeignSession(*iter, base::Time()); 1051 } 1052 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1053 ASSERT_EQ(1U, foreign_sessions.size()); 1054 1055 // Now delete the foreign session. 1056 manager()->DeleteForeignSessionInternal(tag, &changes); 1057 EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions)); 1058 1059 EXPECT_EQ(5U, changes.size()); 1060 std::set<std::string> expected_tags(&tag, &tag + 1); 1061 for (int i = 0; i < 5; i++) 1062 expected_tags.insert(TabNodePool::TabIdToTag(tag, i)); 1063 1064 for (int i = 0; i < 5; i++) { 1065 SCOPED_TRACE(changes[i].ToString()); 1066 EXPECT_TRUE(changes[i].IsValid()); 1067 EXPECT_EQ(SyncChange::ACTION_DELETE, changes[i].change_type()); 1068 EXPECT_TRUE(changes[i].sync_data().IsValid()); 1069 EXPECT_EQ(1U, 1070 expected_tags.erase( 1071 syncer::SyncDataLocal(changes[i].sync_data()).GetTag())); 1072 } 1073 } 1074 1075 // Write a foreign session to a node, with the tabs arriving first, and then 1076 // retrieve it. 1077 TEST_F(SessionsSyncManagerTest, WriteForeignSessionToNodeTabsFirst) { 1078 InitWithNoSyncData(); 1079 1080 // Fill an instance of session specifics with a foreign session's data. 1081 std::string tag = "tag1"; 1082 SessionID::id_type nums1[] = {5, 10, 13, 17}; 1083 std::vector<sync_pb::SessionSpecifics> tabs1; 1084 std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1)); 1085 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 1086 tag, tab_list1, &tabs1)); 1087 1088 syncer::SyncChangeList adds; 1089 // Add tabs for first window, then the meta node. 1090 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &adds); 1091 adds.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD)); 1092 manager()->ProcessSyncChanges(FROM_HERE, adds); 1093 1094 // Check that the foreign session was associated and retrieve the data. 1095 std::vector<const SyncedSession*> foreign_sessions; 1096 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1097 ASSERT_EQ(1U, foreign_sessions.size()); 1098 std::vector<std::vector<SessionID::id_type> > session_reference; 1099 session_reference.push_back(tab_list1); 1100 helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); 1101 } 1102 1103 // Write a foreign session to a node with some tabs that never arrive. 1104 TEST_F(SessionsSyncManagerTest, WriteForeignSessionToNodeMissingTabs) { 1105 InitWithNoSyncData(); 1106 1107 // Fill an instance of session specifics with a foreign session's data. 1108 std::string tag = "tag1"; 1109 SessionID::id_type nums1[] = {5, 10, 13, 17}; 1110 std::vector<sync_pb::SessionSpecifics> tabs1; 1111 std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1)); 1112 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 1113 tag, tab_list1, &tabs1)); 1114 // Add a second window, but this time only create two tab nodes, despite the 1115 // window expecting four tabs. 1116 SessionID::id_type tab_nums2[] = {7, 15, 18, 20}; 1117 std::vector<SessionID::id_type> tab_list2( 1118 tab_nums2, tab_nums2 + arraysize(tab_nums2)); 1119 helper()->AddWindowSpecifics(1, tab_list2, &meta); 1120 std::vector<sync_pb::SessionSpecifics> tabs2; 1121 tabs2.resize(2); 1122 for (size_t i = 0; i < 2; ++i) { 1123 helper()->BuildTabSpecifics(tag, 0, tab_list2[i], &tabs2[i]); 1124 } 1125 1126 syncer::SyncChangeList changes; 1127 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD)); 1128 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes); 1129 AddTabsToChangeList(tabs2, SyncChange::ACTION_ADD, &changes); 1130 manager()->ProcessSyncChanges(FROM_HERE, changes); 1131 changes.clear(); 1132 1133 // Check that the foreign session was associated and retrieve the data. 1134 std::vector<const SyncedSession*> foreign_sessions; 1135 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1136 ASSERT_EQ(1U, foreign_sessions.size()); 1137 ASSERT_EQ(2U, foreign_sessions[0]->windows.size()); 1138 ASSERT_EQ(4U, foreign_sessions[0]->windows.find(0)->second->tabs.size()); 1139 ASSERT_EQ(4U, foreign_sessions[0]->windows.find(1)->second->tabs.size()); 1140 1141 // Close the second window. 1142 meta.mutable_header()->clear_window(); 1143 helper()->AddWindowSpecifics(0, tab_list1, &meta); 1144 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_UPDATE)); 1145 // Update associator with the session's meta node containing one window. 1146 manager()->ProcessSyncChanges(FROM_HERE, changes); 1147 1148 // Check that the foreign session was associated and retrieve the data. 1149 foreign_sessions.clear(); 1150 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1151 ASSERT_EQ(1U, foreign_sessions.size()); 1152 ASSERT_EQ(1U, foreign_sessions[0]->windows.size()); 1153 std::vector<std::vector<SessionID::id_type> > session_reference; 1154 session_reference.push_back(tab_list1); 1155 helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); 1156 } 1157 1158 // Tests that the SessionsSyncManager can handle a remote client deleting 1159 // sync nodes that belong to this local session. 1160 TEST_F(SessionsSyncManagerTest, ProcessRemoteDeleteOfLocalSession) { 1161 syncer::SyncChangeList out; 1162 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); 1163 ASSERT_EQ(2U, out.size()); 1164 sync_pb::EntitySpecifics entity(out[0].sync_data().GetSpecifics()); 1165 SyncData d(SyncData::CreateRemoteData( 1166 1, 1167 entity, 1168 base::Time(), 1169 syncer::AttachmentIdList(), 1170 syncer::AttachmentServiceProxyForTest::Create())); 1171 SetSyncData(syncer::SyncDataList(&d, &d + 1)); 1172 out.clear(); 1173 1174 syncer::SyncChangeList changes; 1175 changes.push_back( 1176 MakeRemoteChange(1, entity.session(), SyncChange::ACTION_DELETE)); 1177 manager()->ProcessSyncChanges(FROM_HERE, changes); 1178 EXPECT_TRUE(manager()->local_tab_pool_out_of_sync_); 1179 EXPECT_TRUE(out.empty()); // ChangeProcessor shouldn't see any activity. 1180 1181 // This should trigger repair of the TabNodePool. 1182 const GURL foo1("http://foo/1"); 1183 AddTab(browser(), foo1); 1184 EXPECT_FALSE(manager()->local_tab_pool_out_of_sync_); 1185 1186 // AddTab triggers two notifications, one for the tab insertion and one for 1187 // committing the NavigationEntry. The first notification results in a tab 1188 // we don't associate although we do update the header node. The second 1189 // notification triggers association of the tab, and the subsequent window 1190 // update. So we should see 4 changes at the SyncChangeProcessor. 1191 ASSERT_EQ(4U, out.size()); 1192 1193 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[0].change_type()); 1194 ASSERT_TRUE(out[0].sync_data().GetSpecifics().session().has_header()); 1195 EXPECT_EQ(SyncChange::ACTION_ADD, out[1].change_type()); 1196 int tab_node_id = out[1].sync_data().GetSpecifics().session().tab_node_id(); 1197 EXPECT_EQ(TabNodePool::TabIdToTag( 1198 manager()->current_machine_tag(), tab_node_id), 1199 syncer::SyncDataLocal(out[1].sync_data()).GetTag()); 1200 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[2].change_type()); 1201 ASSERT_TRUE(out[2].sync_data().GetSpecifics().session().has_tab()); 1202 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[3].change_type()); 1203 ASSERT_TRUE(out[3].sync_data().GetSpecifics().session().has_header()); 1204 1205 // Verify the actual content. 1206 const sync_pb::SessionHeader& session_header = 1207 out[3].sync_data().GetSpecifics().session().header(); 1208 ASSERT_EQ(1, session_header.window_size()); 1209 EXPECT_EQ(1, session_header.window(0).tab_size()); 1210 const sync_pb::SessionTab& tab1 = 1211 out[2].sync_data().GetSpecifics().session().tab(); 1212 ASSERT_EQ(1, tab1.navigation_size()); 1213 EXPECT_EQ(foo1.spec(), tab1.navigation(0).virtual_url()); 1214 1215 // Verify TabNodePool integrity. 1216 EXPECT_EQ(1U, manager()->local_tab_pool_.Capacity()); 1217 EXPECT_TRUE(manager()->local_tab_pool_.Empty()); 1218 1219 // Verify TabLinks. 1220 SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_; 1221 ASSERT_EQ(1U, tab_map.size()); 1222 int tab_id = out[2].sync_data().GetSpecifics().session().tab().tab_id(); 1223 EXPECT_EQ(tab_node_id, tab_map.find(tab_id)->second->tab_node_id()); 1224 } 1225 1226 // Test that receiving a session delete from sync removes the session 1227 // from tracking. 1228 TEST_F(SessionsSyncManagerTest, ProcessForeignDelete) { 1229 InitWithNoSyncData(); 1230 SessionID::id_type n[] = {5}; 1231 std::vector<sync_pb::SessionSpecifics> tabs1; 1232 std::vector<SessionID::id_type> tab_list(n, n + arraysize(n)); 1233 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 1234 "tag1", tab_list, &tabs1)); 1235 1236 syncer::SyncChangeList changes; 1237 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD)); 1238 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes); 1239 manager()->ProcessSyncChanges(FROM_HERE, changes); 1240 1241 std::vector<const SyncedSession*> foreign_sessions; 1242 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1243 ASSERT_EQ(1U, foreign_sessions.size()); 1244 1245 changes.clear(); 1246 foreign_sessions.clear(); 1247 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_DELETE)); 1248 manager()->ProcessSyncChanges(FROM_HERE, changes); 1249 1250 EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions)); 1251 } 1252 1253 // TODO(shashishekhar): "Move this to TabNodePool unittests." 1254 TEST_F(SessionsSyncManagerTest, SaveUnassociatedNodesForReassociation) { 1255 syncer::SyncChangeList changes; 1256 InitWithNoSyncData(); 1257 1258 std::string local_tag = manager()->current_machine_tag(); 1259 // Create a free node and then dissassociate sessions so that it ends up 1260 // unassociated. 1261 manager()->local_tab_pool_.GetFreeTabNode(&changes); 1262 1263 // Update the tab_id of the node, so that it is considered a valid 1264 // unassociated node otherwise it will be mistaken for a corrupted node and 1265 // will be deleted before being added to the tab node pool. 1266 sync_pb::EntitySpecifics entity(changes[0].sync_data().GetSpecifics()); 1267 entity.mutable_session()->mutable_tab()->set_tab_id(1); 1268 SyncData d(SyncData::CreateRemoteData( 1269 1, 1270 entity, 1271 base::Time(), 1272 syncer::AttachmentIdList(), 1273 syncer::AttachmentServiceProxyForTest::Create())); 1274 syncer::SyncDataList in(&d, &d + 1); 1275 changes.clear(); 1276 SessionsSyncManager manager2(profile(), this, NewDummyRouter()); 1277 syncer::SyncMergeResult result = manager2.MergeDataAndStartSyncing( 1278 syncer::SESSIONS, in, 1279 scoped_ptr<syncer::SyncChangeProcessor>( 1280 new TestSyncProcessorStub(&changes)), 1281 scoped_ptr<syncer::SyncErrorFactory>( 1282 new syncer::SyncErrorFactoryMock())); 1283 ASSERT_FALSE(result.error().IsSet()); 1284 EXPECT_TRUE(FilterOutLocalHeaderChanges(&changes)->empty()); 1285 } 1286 1287 TEST_F(SessionsSyncManagerTest, MergeDeletesCorruptNode) { 1288 syncer::SyncChangeList changes; 1289 InitWithNoSyncData(); 1290 1291 std::string local_tag = manager()->current_machine_tag(); 1292 int tab_node_id = manager()->local_tab_pool_.GetFreeTabNode(&changes); 1293 SyncData d(SyncData::CreateRemoteData( 1294 1, 1295 changes[0].sync_data().GetSpecifics(), 1296 base::Time(), 1297 syncer::AttachmentIdList(), 1298 syncer::AttachmentServiceProxyForTest::Create())); 1299 syncer::SyncDataList in(&d, &d + 1); 1300 changes.clear(); 1301 TearDown(); 1302 SetUp(); 1303 InitWithSyncDataTakeOutput(in, &changes); 1304 EXPECT_EQ(1U, FilterOutLocalHeaderChanges(&changes)->size()); 1305 EXPECT_EQ(SyncChange::ACTION_DELETE, changes[0].change_type()); 1306 EXPECT_EQ(TabNodePool::TabIdToTag(local_tag, tab_node_id), 1307 syncer::SyncDataLocal(changes[0].sync_data()).GetTag()); 1308 } 1309 1310 // Test that things work if a tab is initially ignored. 1311 TEST_F(SessionsSyncManagerTest, AssociateWindowsDontReloadTabs) { 1312 syncer::SyncChangeList out; 1313 // Go to a URL that is ignored by session syncing. 1314 AddTab(browser(), GURL("chrome://preferences/")); 1315 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); 1316 ASSERT_EQ(2U, out.size()); // Header add and update. 1317 EXPECT_EQ( 1318 0, 1319 out[1].sync_data().GetSpecifics().session().header().window_size()); 1320 out.clear(); 1321 1322 // Go to a sync-interesting URL. 1323 NavigateAndCommitActiveTab(GURL("http://foo2")); 1324 1325 EXPECT_EQ(3U, out.size()); // Tab add, update, and header update. 1326 1327 EXPECT_TRUE( 1328 StartsWithASCII(syncer::SyncDataLocal(out[0].sync_data()).GetTag(), 1329 manager()->current_machine_tag(), 1330 true)); 1331 EXPECT_EQ(manager()->current_machine_tag(), 1332 out[0].sync_data().GetSpecifics().session().session_tag()); 1333 EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type()); 1334 1335 EXPECT_TRUE( 1336 StartsWithASCII(syncer::SyncDataLocal(out[1].sync_data()).GetTag(), 1337 manager()->current_machine_tag(), 1338 true)); 1339 EXPECT_EQ(manager()->current_machine_tag(), 1340 out[1].sync_data().GetSpecifics().session().session_tag()); 1341 EXPECT_TRUE(out[1].sync_data().GetSpecifics().session().has_tab()); 1342 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[1].change_type()); 1343 1344 EXPECT_TRUE(out[2].IsValid()); 1345 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[2].change_type()); 1346 const SyncData data(out[2].sync_data()); 1347 EXPECT_EQ(manager()->current_machine_tag(), 1348 syncer::SyncDataLocal(data).GetTag()); 1349 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session()); 1350 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag()); 1351 EXPECT_TRUE(specifics.has_header()); 1352 const sync_pb::SessionHeader& header_s = specifics.header(); 1353 EXPECT_EQ(1, header_s.window_size()); 1354 EXPECT_EQ(1, header_s.window(0).tab_size()); 1355 } 1356 1357 // Tests that the SyncSessionManager responds to local tab events properly. 1358 TEST_F(SessionsSyncManagerTest, OnLocalTabModified) { 1359 syncer::SyncChangeList out; 1360 // Init with no local data, relies on MergeLocalSessionNoTabs. 1361 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); 1362 ASSERT_FALSE(manager()->current_machine_tag().empty()); 1363 ASSERT_EQ(2U, out.size()); 1364 1365 // Copy the original header. 1366 sync_pb::EntitySpecifics header(out[0].sync_data().GetSpecifics()); 1367 out.clear(); 1368 1369 const GURL foo1("http://foo/1"); 1370 const GURL foo2("http://foo/2"); 1371 const GURL bar1("http://bar/1"); 1372 const GURL bar2("http://bar/2"); 1373 AddTab(browser(), foo1); 1374 NavigateAndCommitActiveTab(foo2); 1375 AddTab(browser(), bar1); 1376 NavigateAndCommitActiveTab(bar2); 1377 1378 // One add, one update for each AddTab. 1379 // One update for each NavigateAndCommit. 1380 // = 6 total tab updates. 1381 // One header update corresponding to each of those. 1382 // = 6 total header updates. 1383 // 12 total updates. 1384 ASSERT_EQ(12U, out.size()); 1385 1386 // Verify the tab node creations and updates to ensure the SyncProcessor 1387 // sees the right operations. 1388 for (int i = 0; i < 12; i++) { 1389 SCOPED_TRACE(i); 1390 EXPECT_TRUE(out[i].IsValid()); 1391 const SyncData data(out[i].sync_data()); 1392 EXPECT_TRUE(StartsWithASCII(syncer::SyncDataLocal(data).GetTag(), 1393 manager()->current_machine_tag(), true)); 1394 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session()); 1395 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag()); 1396 if (i % 6 == 0) { 1397 // First thing on an AddTab is a no-op header update for parented tab. 1398 EXPECT_EQ(header.SerializeAsString(), 1399 data.GetSpecifics().SerializeAsString()); 1400 EXPECT_EQ(manager()->current_machine_tag(), 1401 syncer::SyncDataLocal(data).GetTag()); 1402 } else if (i % 6 == 1) { 1403 // Next, the TabNodePool should create the tab node. 1404 EXPECT_EQ(SyncChange::ACTION_ADD, out[i].change_type()); 1405 EXPECT_EQ(TabNodePool::TabIdToTag( 1406 manager()->current_machine_tag(), 1407 data.GetSpecifics().session().tab_node_id()), 1408 syncer::SyncDataLocal(data).GetTag()); 1409 } else if (i % 6 == 2) { 1410 // Then we see the tab update to the URL. 1411 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type()); 1412 EXPECT_EQ(TabNodePool::TabIdToTag( 1413 manager()->current_machine_tag(), 1414 data.GetSpecifics().session().tab_node_id()), 1415 syncer::SyncDataLocal(data).GetTag()); 1416 ASSERT_TRUE(specifics.has_tab()); 1417 } else if (i % 6 == 3) { 1418 // The header needs to be updated to reflect the new window state. 1419 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type()); 1420 EXPECT_TRUE(specifics.has_header()); 1421 } else if (i % 6 == 4) { 1422 // Now we move on to NavigateAndCommit. Update the tab. 1423 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type()); 1424 EXPECT_EQ(TabNodePool::TabIdToTag( 1425 manager()->current_machine_tag(), 1426 data.GetSpecifics().session().tab_node_id()), 1427 syncer::SyncDataLocal(data).GetTag()); 1428 ASSERT_TRUE(specifics.has_tab()); 1429 } else if (i % 6 == 5) { 1430 // The header needs to be updated to reflect the new window state. 1431 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type()); 1432 ASSERT_TRUE(specifics.has_header()); 1433 header = data.GetSpecifics(); 1434 } 1435 } 1436 1437 // Verify the actual content to ensure sync sees the right data. 1438 // When it's all said and done, the header should reflect two tabs. 1439 const sync_pb::SessionHeader& session_header = header.session().header(); 1440 ASSERT_EQ(1, session_header.window_size()); 1441 EXPECT_EQ(2, session_header.window(0).tab_size()); 1442 1443 // ASSERT_TRUEs above allow us to dive in freely here. 1444 // Verify first tab. 1445 const sync_pb::SessionTab& tab1_1 = 1446 out[2].sync_data().GetSpecifics().session().tab(); 1447 ASSERT_EQ(1, tab1_1.navigation_size()); 1448 EXPECT_EQ(foo1.spec(), tab1_1.navigation(0).virtual_url()); 1449 const sync_pb::SessionTab& tab1_2 = 1450 out[4].sync_data().GetSpecifics().session().tab(); 1451 ASSERT_EQ(2, tab1_2.navigation_size()); 1452 EXPECT_EQ(foo1.spec(), tab1_2.navigation(0).virtual_url()); 1453 EXPECT_EQ(foo2.spec(), tab1_2.navigation(1).virtual_url()); 1454 1455 // Verify second tab. 1456 const sync_pb::SessionTab& tab2_1 = 1457 out[8].sync_data().GetSpecifics().session().tab(); 1458 ASSERT_EQ(1, tab2_1.navigation_size()); 1459 EXPECT_EQ(bar1.spec(), tab2_1.navigation(0).virtual_url()); 1460 const sync_pb::SessionTab& tab2_2 = 1461 out[10].sync_data().GetSpecifics().session().tab(); 1462 ASSERT_EQ(2, tab2_2.navigation_size()); 1463 EXPECT_EQ(bar1.spec(), tab2_2.navigation(0).virtual_url()); 1464 EXPECT_EQ(bar2.spec(), tab2_2.navigation(1).virtual_url()); 1465 } 1466 1467 // Ensure model association associates the pre-existing tabs. 1468 TEST_F(SessionsSyncManagerTest, MergeLocalSessionExistingTabs) { 1469 AddTab(browser(), GURL("http://foo1")); 1470 NavigateAndCommitActiveTab(GURL("http://foo2")); // Adds back entry. 1471 AddTab(browser(), GURL("http://bar1")); 1472 NavigateAndCommitActiveTab(GURL("http://bar2")); // Adds back entry. 1473 1474 syncer::SyncChangeList out; 1475 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); 1476 ASSERT_EQ(6U, out.size()); 1477 1478 // Check that this machine's data is not included in the foreign windows. 1479 std::vector<const SyncedSession*> foreign_sessions; 1480 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions)); 1481 1482 // Verify the header. 1483 EXPECT_TRUE(out[0].IsValid()); 1484 EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type()); 1485 const SyncData data(out[0].sync_data()); 1486 EXPECT_EQ(manager()->current_machine_tag(), 1487 syncer::SyncDataLocal(data).GetTag()); 1488 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session()); 1489 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag()); 1490 EXPECT_TRUE(specifics.has_header()); 1491 const sync_pb::SessionHeader& header_s = specifics.header(); 1492 EXPECT_TRUE(header_s.has_device_type()); 1493 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name()); 1494 EXPECT_EQ(0, header_s.window_size()); 1495 1496 // Verify the tab node creations and updates with content. 1497 for (int i = 1; i < 5; i++) { 1498 EXPECT_TRUE(out[i].IsValid()); 1499 const SyncData data(out[i].sync_data()); 1500 EXPECT_TRUE(StartsWithASCII(syncer::SyncDataLocal(data).GetTag(), 1501 manager()->current_machine_tag(), true)); 1502 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session()); 1503 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag()); 1504 if (i % 2 == 1) { 1505 EXPECT_EQ(SyncChange::ACTION_ADD, out[i].change_type()); 1506 } else { 1507 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type()); 1508 EXPECT_TRUE(specifics.has_tab()); 1509 } 1510 } 1511 1512 // Verify the header was updated to reflect new window state. 1513 EXPECT_TRUE(out[5].IsValid()); 1514 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[5].change_type()); 1515 const SyncData data_2(out[5].sync_data()); 1516 EXPECT_EQ(manager()->current_machine_tag(), 1517 syncer::SyncDataLocal(data_2).GetTag()); 1518 const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session()); 1519 EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag()); 1520 EXPECT_TRUE(specifics2.has_header()); 1521 const sync_pb::SessionHeader& header_s2 = specifics2.header(); 1522 EXPECT_EQ(1, header_s2.window_size()); 1523 1524 // Verify TabLinks. 1525 SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_; 1526 ASSERT_EQ(2U, tab_map.size()); 1527 // Tabs are ordered by sessionid in tab_map, so should be able to traverse 1528 // the tree based on order of tabs created 1529 SessionsSyncManager::TabLinksMap::iterator iter = tab_map.begin(); 1530 ASSERT_EQ(2, iter->second->tab()->GetEntryCount()); 1531 EXPECT_EQ(GURL("http://foo1"), iter->second->tab()-> 1532 GetEntryAtIndex(0)->GetVirtualURL()); 1533 EXPECT_EQ(GURL("http://foo2"), iter->second->tab()-> 1534 GetEntryAtIndex(1)->GetVirtualURL()); 1535 iter++; 1536 ASSERT_EQ(2, iter->second->tab()->GetEntryCount()); 1537 EXPECT_EQ(GURL("http://bar1"), iter->second->tab()-> 1538 GetEntryAtIndex(0)->GetVirtualURL()); 1539 EXPECT_EQ(GURL("http://bar2"), iter->second->tab()-> 1540 GetEntryAtIndex(1)->GetVirtualURL()); 1541 } 1542 1543 // Test garbage collection of stale foreign sessions. 1544 TEST_F(SessionsSyncManagerTest, DoGarbageCollection) { 1545 // Fill two instances of session specifics with a foreign session's data. 1546 std::string tag1 = "tag1"; 1547 SessionID::id_type n1[] = {5, 10, 13, 17}; 1548 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1)); 1549 std::vector<sync_pb::SessionSpecifics> tabs1; 1550 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 1551 tag1, tab_list1, &tabs1)); 1552 std::string tag2 = "tag2"; 1553 SessionID::id_type n2[] = {8, 15, 18, 20}; 1554 std::vector<SessionID::id_type> tab_list2(n2, n2 + arraysize(n2)); 1555 std::vector<sync_pb::SessionSpecifics> tabs2; 1556 sync_pb::SessionSpecifics meta2(helper()->BuildForeignSession( 1557 tag2, tab_list2, &tabs2)); 1558 // Set the modification time for tag1 to be 21 days ago, tag2 to 5 days ago. 1559 base::Time tag1_time = base::Time::Now() - base::TimeDelta::FromDays(21); 1560 base::Time tag2_time = base::Time::Now() - base::TimeDelta::FromDays(5); 1561 1562 syncer::SyncDataList foreign_data; 1563 sync_pb::EntitySpecifics entity1, entity2; 1564 entity1.mutable_session()->CopyFrom(meta); 1565 entity2.mutable_session()->CopyFrom(meta2); 1566 foreign_data.push_back(SyncData::CreateRemoteData( 1567 1, 1568 entity1, 1569 tag1_time, 1570 syncer::AttachmentIdList(), 1571 syncer::AttachmentServiceProxyForTest::Create())); 1572 foreign_data.push_back(SyncData::CreateRemoteData( 1573 1, 1574 entity2, 1575 tag2_time, 1576 syncer::AttachmentIdList(), 1577 syncer::AttachmentServiceProxyForTest::Create())); 1578 AddTabsToSyncDataList(tabs1, &foreign_data); 1579 AddTabsToSyncDataList(tabs2, &foreign_data); 1580 1581 syncer::SyncChangeList output; 1582 InitWithSyncDataTakeOutput(foreign_data, &output); 1583 ASSERT_EQ(2U, output.size()); 1584 output.clear(); 1585 1586 // Check that the foreign session was associated and retrieve the data. 1587 std::vector<const SyncedSession*> foreign_sessions; 1588 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1589 ASSERT_EQ(2U, foreign_sessions.size()); 1590 foreign_sessions.clear(); 1591 1592 // Now garbage collect and verify the non-stale session is still there. 1593 manager()->DoGarbageCollection(); 1594 ASSERT_EQ(5U, output.size()); 1595 EXPECT_EQ(SyncChange::ACTION_DELETE, output[0].change_type()); 1596 const SyncData data(output[0].sync_data()); 1597 EXPECT_EQ(tag1, syncer::SyncDataLocal(data).GetTag()); 1598 for (int i = 1; i < 5; i++) { 1599 EXPECT_EQ(SyncChange::ACTION_DELETE, output[i].change_type()); 1600 const SyncData data(output[i].sync_data()); 1601 EXPECT_EQ(TabNodePool::TabIdToTag(tag1, i), 1602 syncer::SyncDataLocal(data).GetTag()); 1603 } 1604 1605 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1606 ASSERT_EQ(1U, foreign_sessions.size()); 1607 std::vector<std::vector<SessionID::id_type> > session_reference; 1608 session_reference.push_back(tab_list2); 1609 helper()->VerifySyncedSession(tag2, session_reference, 1610 *(foreign_sessions[0])); 1611 } 1612 1613 // Test that an update to a previously considered "stale" session, 1614 // prior to garbage collection, will save the session from deletion. 1615 TEST_F(SessionsSyncManagerTest, GarbageCollectionHonoursUpdate) { 1616 std::string tag1 = "tag1"; 1617 SessionID::id_type n1[] = {5, 10, 13, 17}; 1618 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1)); 1619 std::vector<sync_pb::SessionSpecifics> tabs1; 1620 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 1621 tag1, tab_list1, &tabs1)); 1622 syncer::SyncDataList foreign_data; 1623 sync_pb::EntitySpecifics entity1; 1624 base::Time tag1_time = base::Time::Now() - base::TimeDelta::FromDays(21); 1625 entity1.mutable_session()->CopyFrom(meta); 1626 foreign_data.push_back(SyncData::CreateRemoteData( 1627 1, 1628 entity1, 1629 tag1_time, 1630 syncer::AttachmentIdList(), 1631 syncer::AttachmentServiceProxyForTest::Create())); 1632 AddTabsToSyncDataList(tabs1, &foreign_data); 1633 syncer::SyncChangeList output; 1634 InitWithSyncDataTakeOutput(foreign_data, &output); 1635 ASSERT_EQ(2U, output.size()); 1636 1637 // Update to a non-stale time. 1638 sync_pb::EntitySpecifics update_entity; 1639 update_entity.mutable_session()->CopyFrom(tabs1[0]); 1640 syncer::SyncChangeList changes; 1641 changes.push_back( 1642 syncer::SyncChange(FROM_HERE, 1643 SyncChange::ACTION_UPDATE, 1644 syncer::SyncData::CreateRemoteData( 1645 1, 1646 update_entity, 1647 base::Time::Now(), 1648 syncer::AttachmentIdList(), 1649 syncer::AttachmentServiceProxyForTest::Create()))); 1650 manager()->ProcessSyncChanges(FROM_HERE, changes); 1651 1652 // Check that the foreign session was associated and retrieve the data. 1653 std::vector<const SyncedSession*> foreign_sessions; 1654 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1655 ASSERT_EQ(1U, foreign_sessions.size()); 1656 foreign_sessions.clear(); 1657 1658 // Verify the now non-stale session does not get deleted. 1659 manager()->DoGarbageCollection(); 1660 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1661 ASSERT_EQ(1U, foreign_sessions.size()); 1662 std::vector<std::vector<SessionID::id_type> > session_reference; 1663 session_reference.push_back(tab_list1); 1664 helper()->VerifySyncedSession( 1665 tag1, session_reference, *(foreign_sessions[0])); 1666 } 1667 1668 // Test that swapping WebContents for a tab is properly observed and handled 1669 // by the SessionsSyncManager. 1670 TEST_F(SessionsSyncManagerTest, CheckPrerenderedWebContentsSwap) { 1671 AddTab(browser(), GURL("http://foo1")); 1672 NavigateAndCommitActiveTab(GURL("http://foo2")); 1673 1674 syncer::SyncChangeList out; 1675 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); 1676 ASSERT_EQ(4U, out.size()); // Header, tab ADD, tab UPDATE, header UPDATE. 1677 1678 // To simulate WebContents swap during prerendering, create new WebContents 1679 // and swap with old WebContents. 1680 scoped_ptr<content::WebContents> old_web_contents; 1681 old_web_contents.reset(browser()->tab_strip_model()->GetActiveWebContents()); 1682 1683 // Create new WebContents, with the required tab helpers. 1684 WebContents* new_web_contents = WebContents::CreateWithSessionStorage( 1685 WebContents::CreateParams(profile()), 1686 old_web_contents->GetController().GetSessionStorageNamespaceMap()); 1687 SessionTabHelper::CreateForWebContents(new_web_contents); 1688 TabContentsSyncedTabDelegate::CreateForWebContents(new_web_contents); 1689 new_web_contents->GetController() 1690 .CopyStateFrom(old_web_contents->GetController()); 1691 1692 // Swap the WebContents. 1693 int index = browser()->tab_strip_model()->GetIndexOfWebContents( 1694 old_web_contents.get()); 1695 browser()->tab_strip_model()->ReplaceWebContentsAt(index, new_web_contents); 1696 1697 ASSERT_EQ(9U, out.size()); 1698 EXPECT_EQ(SyncChange::ACTION_ADD, out[4].change_type()); 1699 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[5].change_type()); 1700 1701 // Navigate away. 1702 NavigateAndCommitActiveTab(GURL("http://bar2")); 1703 1704 // Delete old WebContents. This should not crash. 1705 old_web_contents.reset(); 1706 1707 // Try more navigations and verify output size. This can also reveal 1708 // bugs (leaks) on memcheck bots if the SessionSyncManager 1709 // didn't properly clean up the tab pool or session tracker. 1710 NavigateAndCommitActiveTab(GURL("http://bar3")); 1711 1712 AddTab(browser(), GURL("http://bar4")); 1713 NavigateAndCommitActiveTab(GURL("http://bar5")); 1714 ASSERT_EQ(19U, out.size()); 1715 } 1716 1717 namespace { 1718 class SessionNotificationObserver : public content::NotificationObserver { 1719 public: 1720 SessionNotificationObserver() : notified_of_update_(false), 1721 notified_of_refresh_(false) { 1722 registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED, 1723 content::NotificationService::AllSources()); 1724 registrar_.Add(this, chrome::NOTIFICATION_SYNC_REFRESH_LOCAL, 1725 content::NotificationService::AllSources()); 1726 } 1727 virtual void Observe(int type, 1728 const content::NotificationSource& source, 1729 const content::NotificationDetails& details) OVERRIDE { 1730 switch (type) { 1731 case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED: 1732 notified_of_update_ = true; 1733 break; 1734 case chrome::NOTIFICATION_SYNC_REFRESH_LOCAL: 1735 notified_of_refresh_ = true; 1736 break; 1737 default: 1738 NOTREACHED(); 1739 break; 1740 } 1741 } 1742 bool notified_of_update() const { return notified_of_update_; } 1743 bool notified_of_refresh() const { return notified_of_refresh_; } 1744 void Reset() { 1745 notified_of_update_ = false; 1746 notified_of_refresh_ = false; 1747 } 1748 private: 1749 content::NotificationRegistrar registrar_; 1750 bool notified_of_update_; 1751 bool notified_of_refresh_; 1752 }; 1753 } // namespace 1754 1755 // Test that NOTIFICATION_FOREIGN_SESSION_UPDATED is sent when processing 1756 // sync changes. 1757 TEST_F(SessionsSyncManagerTest, NotifiedOfUpdates) { 1758 SessionNotificationObserver observer; 1759 ASSERT_FALSE(observer.notified_of_update()); 1760 InitWithNoSyncData(); 1761 1762 SessionID::id_type n[] = {5}; 1763 std::vector<sync_pb::SessionSpecifics> tabs1; 1764 std::vector<SessionID::id_type> tab_list(n, n + arraysize(n)); 1765 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 1766 "tag1", tab_list, &tabs1)); 1767 1768 syncer::SyncChangeList changes; 1769 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD)); 1770 manager()->ProcessSyncChanges(FROM_HERE, changes); 1771 EXPECT_TRUE(observer.notified_of_update()); 1772 1773 changes.clear(); 1774 observer.Reset(); 1775 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes); 1776 manager()->ProcessSyncChanges(FROM_HERE, changes); 1777 EXPECT_TRUE(observer.notified_of_update()); 1778 1779 changes.clear(); 1780 observer.Reset(); 1781 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_DELETE)); 1782 manager()->ProcessSyncChanges(FROM_HERE, changes); 1783 EXPECT_TRUE(observer.notified_of_update()); 1784 } 1785 1786 // Test that NOTIFICATION_FOREIGN_SESSION_UPDATED is sent when handling 1787 // local hide/removal of foreign session. 1788 TEST_F(SessionsSyncManagerTest, NotifiedOfLocalRemovalOfForeignSession) { 1789 InitWithNoSyncData(); 1790 const std::string tag("tag1"); 1791 SessionID::id_type n[] = {5}; 1792 std::vector<sync_pb::SessionSpecifics> tabs1; 1793 std::vector<SessionID::id_type> tab_list(n, n + arraysize(n)); 1794 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 1795 tag, tab_list, &tabs1)); 1796 1797 syncer::SyncChangeList changes; 1798 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD)); 1799 manager()->ProcessSyncChanges(FROM_HERE, changes); 1800 1801 SessionNotificationObserver observer; 1802 ASSERT_FALSE(observer.notified_of_update()); 1803 manager()->DeleteForeignSession(tag); 1804 ASSERT_TRUE(observer.notified_of_update()); 1805 } 1806 1807 #if defined(OS_ANDROID) || defined(OS_IOS) 1808 // Tests that opening the other devices page triggers a session sync refresh. 1809 // This page only exists on mobile platforms today; desktop has a 1810 // search-enhanced NTP without other devices. 1811 TEST_F(SessionsSyncManagerTest, NotifiedOfRefresh) { 1812 SessionNotificationObserver observer; 1813 ASSERT_FALSE(observer.notified_of_refresh()); 1814 InitWithNoSyncData(); 1815 AddTab(browser(), GURL("http://foo1")); 1816 EXPECT_FALSE(observer.notified_of_refresh()); 1817 NavigateAndCommitActiveTab(GURL("chrome://newtab/#open_tabs")); 1818 EXPECT_TRUE(observer.notified_of_refresh()); 1819 } 1820 #endif // defined(OS_ANDROID) || defined(OS_IOS) 1821 1822 } // namespace browser_sync 1823