// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" #include "base/compiler_specific.h" #include "base/files/scoped_temp_dir.h" #include "base/guid.h" #include "base/location.h" #include "base/memory/scoped_ptr.h" #include "base/run_loop.h" #include "base/stl_util.h" #include "base/time/time.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/invalidation/invalidation_service_factory.h" #include "chrome/browser/sessions/session_tab_helper.h" #include "chrome/browser/signin/fake_profile_oauth2_token_service.h" #include "chrome/browser/signin/profile_oauth2_token_service.h" #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" #include "chrome/browser/signin/signin_manager.h" #include "chrome/browser/signin/signin_manager_factory.h" #include "chrome/browser/sync/abstract_profile_sync_service_test.h" #include "chrome/browser/sync/glue/device_info.h" #include "chrome/browser/sync/glue/session_change_processor.h" #include "chrome/browser/sync/glue/session_data_type_controller.h" #include "chrome/browser/sync/glue/session_model_associator.h" #include "chrome/browser/sync/glue/session_sync_test_helper.h" #include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/glue/synced_device_tracker.h" #include "chrome/browser/sync/glue/synced_tab_delegate.h" #include "chrome/browser/sync/glue/tab_node_pool.h" #include "chrome/browser/sync/profile_sync_components_factory_mock.h" #include "chrome/browser/sync/profile_sync_service_factory.h" #include "chrome/browser/sync/profile_sync_test_util.h" #include "chrome/browser/sync/test_profile_sync_service.h" #include "chrome/browser/ui/sync/tab_contents_synced_tab_delegate.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/test/base/browser_with_test_window_test.h" #include "chrome/test/base/testing_profile.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/web_contents.h" #include "content/public/test/test_browser_thread.h" #include "google_apis/gaia/gaia_constants.h" #include "net/url_request/test_url_fetcher_factory.h" #include "sync/internal_api/public/base/model_type.h" #include "sync/internal_api/public/change_record.h" #include "sync/internal_api/public/read_node.h" #include "sync/internal_api/public/read_transaction.h" #include "sync/internal_api/public/test/test_user_share.h" #include "sync/internal_api/public/write_node.h" #include "sync/internal_api/public/write_transaction.h" #include "sync/protocol/session_specifics.pb.h" #include "sync/protocol/sync.pb.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/ui_base_types.h" #include "url/gurl.h" using browser_sync::SessionChangeProcessor; using browser_sync::SessionDataTypeController; using browser_sync::SessionModelAssociator; using browser_sync::SyncBackendHost; using content::BrowserThread; using content::WebContents; using syncer::ChangeRecord; using testing::_; using testing::Return; namespace browser_sync { namespace { class FakeProfileSyncService : public TestProfileSyncService { public: FakeProfileSyncService( ProfileSyncComponentsFactory* factory, Profile* profile, SigninManagerBase* signin, ProfileOAuth2TokenService* oauth2_token_service, ProfileSyncService::StartBehavior behavior) : TestProfileSyncService(factory, profile, signin, oauth2_token_service, behavior) {} virtual ~FakeProfileSyncService() {} virtual scoped_ptr GetLocalDeviceInfo() const OVERRIDE { return scoped_ptr(new DeviceInfo(base::GenerateGUID(), "client_name", std::string(), std::string(), sync_pb::SyncEnums::TYPE_WIN)); } }; bool CompareMemoryToString( const std::string& str, const scoped_refptr& mem) { if (mem->size() != str.size()) return false; for (size_t i = 0; i size(); ++i) { if (str[i] != *(mem->front() + i)) return false; } return true; } } // namespace class ProfileSyncServiceSessionTest : public BrowserWithTestWindowTest, public content::NotificationObserver { public: ProfileSyncServiceSessionTest() : window_bounds_(0, 1, 2, 3), notified_of_refresh_(false), notified_of_update_(false) {} ProfileSyncService* sync_service() { return sync_service_.get(); } protected: virtual TestingProfile* CreateProfile() OVERRIDE { TestingProfile::Builder builder; builder.AddTestingFactory( ProfileOAuth2TokenServiceFactory::GetInstance(), FakeProfileOAuth2TokenService::BuildAutoIssuingTokenService); // Don't want the profile to create a real ProfileSyncService. builder.AddTestingFactory(ProfileSyncServiceFactory::GetInstance(), NULL); scoped_ptr profile(builder.Build()); invalidation::InvalidationServiceFactory::GetInstance()-> SetBuildOnlyFakeInvalidatorsForTest(true); return profile.release(); } virtual void SetUp() { // BrowserWithTestWindowTest implementation. BrowserWithTestWindowTest::SetUp(); ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED, content::NotificationService::AllSources()); registrar_.Add(this, chrome::NOTIFICATION_SYNC_REFRESH_LOCAL, content::NotificationService::AllSources()); } virtual void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) OVERRIDE { switch (type) { case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED: notified_of_update_ = true; break; case chrome::NOTIFICATION_SYNC_REFRESH_LOCAL: notified_of_refresh_ = true; break; default: NOTREACHED(); break; } } virtual void TearDown() { sync_service_->Shutdown(); sync_service_.reset(); // We need to destroy the profile before shutting down the threads, because // some of the ref counted objects in the profile depend on their // destruction on the io thread. DestroyBrowserAndProfile(); ASSERT_FALSE(profile()); // Pump messages posted by the sync core thread (which may end up // posting on the IO thread). base::RunLoop().RunUntilIdle(); BrowserWithTestWindowTest::TearDown(); } bool StartSyncService(const base::Closure& callback, bool will_fail_association) { if (sync_service_) return false; SigninManagerBase* signin = SigninManagerFactory::GetForProfile(profile()); signin->SetAuthenticatedUsername("test_user"); ProfileOAuth2TokenService* oauth2_token_service = ProfileOAuth2TokenServiceFactory::GetForProfile(profile()); ProfileSyncComponentsFactoryMock* factory = new ProfileSyncComponentsFactoryMock(); sync_service_.reset(new FakeProfileSyncService( factory, profile(), signin, oauth2_token_service, ProfileSyncService::AUTO_START)); sync_service_->set_backend_init_callback(callback); // Register the session data type. SessionDataTypeController *dtc = new SessionDataTypeController(factory, profile(), sync_service_.get()); sync_service_->RegisterDataTypeController(dtc); model_associator_ = new SessionModelAssociator(sync_service_.get(), true /* setup_for_test */); change_processor_ = new SessionChangeProcessor( dtc, model_associator_, true /* setup_for_test */); EXPECT_CALL(*factory, CreateSessionSyncComponents(_, _)). WillOnce(Return(ProfileSyncComponentsFactory::SyncComponents( model_associator_, change_processor_))); EXPECT_CALL(*factory, CreateDataTypeManager(_, _, _, _, _, _)). WillOnce(ReturnNewDataTypeManager()); ProfileOAuth2TokenServiceFactory::GetForProfile(profile()) ->UpdateCredentials("test_user", "oauth2_login_token"); sync_service_->Initialize(); base::MessageLoop::current()->Run(); return true; } // Path used in testing. base::ScopedTempDir temp_dir_; SessionModelAssociator* model_associator_; SessionChangeProcessor* change_processor_; SessionID window_id_; scoped_ptr sync_service_; const gfx::Rect window_bounds_; bool notified_of_refresh_; bool notified_of_update_; content::NotificationRegistrar registrar_; net::TestURLFetcherFactory fetcher_factory_; SessionSyncTestHelper helper_; }; class CreateRootHelper { public: explicit CreateRootHelper(ProfileSyncServiceSessionTest* test) : callback_(base::Bind(&CreateRootHelper::CreateRootCallback, base::Unretained(this), test)), success_(false) { } virtual ~CreateRootHelper() {} const base::Closure& callback() const { return callback_; } bool success() { return success_; } private: void CreateRootCallback(ProfileSyncServiceSessionTest* test) { success_ = syncer::TestUserShare::CreateRoot( syncer::SESSIONS, test->sync_service()->GetUserShare()); } base::Closure callback_; bool success_; }; // Test that we can write this machine's session to a node and retrieve it. TEST_F(ProfileSyncServiceSessionTest, WriteSessionToNode) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); // Check that the DataTypeController associated the models. bool has_nodes; ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes)); ASSERT_TRUE(has_nodes); std::string machine_tag = model_associator_->GetCurrentMachineTag(); int64 sync_id = model_associator_->GetSyncIdFromSessionTag(machine_tag); ASSERT_NE(syncer::kInvalidId, sync_id); // Check that we can get the correct session specifics back from the node. syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare()); syncer::ReadNode node(&trans); ASSERT_EQ(syncer::BaseNode::INIT_OK, node.InitByClientTagLookup(syncer::SESSIONS, machine_tag)); const sync_pb::SessionSpecifics& specifics(node.GetSessionSpecifics()); ASSERT_EQ(machine_tag, specifics.session_tag()); ASSERT_TRUE(specifics.has_header()); const sync_pb::SessionHeader& header_s = specifics.header(); ASSERT_TRUE(header_s.has_device_type()); ASSERT_EQ("client_name", header_s.client_name()); ASSERT_EQ(0, header_s.window_size()); } // Crashes sometimes on Windows, particularly XP. // See http://crbug.com/174951 #if defined(OS_WIN) #define MAYBE_WriteFilledSessionToNode DISABLED_WriteFilledSessionToNode #else #define MAYBE_WriteFilledSessionToNode WriteFilledSessionToNode #endif // defined(OS_WIN) // Test that we can fill this machine's session, write it to a node, // and then retrieve it. TEST_F(ProfileSyncServiceSessionTest, MAYBE_WriteFilledSessionToNode) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); // Check that the DataTypeController associated the models. bool has_nodes; ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes)); ASSERT_TRUE(has_nodes); AddTab(browser(), GURL("http://foo/1")); NavigateAndCommitActiveTab(GURL("http://foo/2")); AddTab(browser(), GURL("http://bar/1")); NavigateAndCommitActiveTab(GURL("http://bar/2")); ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes)); ASSERT_TRUE(has_nodes); std::string machine_tag = model_associator_->GetCurrentMachineTag(); int64 sync_id = model_associator_->GetSyncIdFromSessionTag(machine_tag); ASSERT_NE(syncer::kInvalidId, sync_id); // Check that this machine's data is not included in the foreign windows. std::vector foreign_sessions; ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(foreign_sessions.size(), 0U); // Get the tabs for this machine from the node and check that they were // filled. SessionModelAssociator::TabLinksMap tab_map = model_associator_->local_tab_map_; ASSERT_EQ(2U, tab_map.size()); // Tabs are ordered by sessionid in tab_map, so should be able to traverse // the tree based on order of tabs created SessionModelAssociator::TabLinksMap::iterator iter = tab_map.begin(); ASSERT_EQ(2, iter->second->tab()->GetEntryCount()); ASSERT_EQ(GURL("http://foo/1"), iter->second->tab()-> GetEntryAtIndex(0)->GetVirtualURL()); ASSERT_EQ(GURL("http://foo/2"), iter->second->tab()-> GetEntryAtIndex(1)->GetVirtualURL()); iter++; ASSERT_EQ(2, iter->second->tab()->GetEntryCount()); ASSERT_EQ(GURL("http://bar/1"), iter->second->tab()-> GetEntryAtIndex(0)->GetVirtualURL()); ASSERT_EQ(GURL("http://bar/2"), iter->second->tab()-> GetEntryAtIndex(1)->GetVirtualURL()); } // Test that we fail on a failed model association. TEST_F(ProfileSyncServiceSessionTest, FailModelAssociation) { ASSERT_TRUE(StartSyncService(base::Closure(), true)); ASSERT_TRUE(sync_service_->HasUnrecoverableError()); } // Write a foreign session to a node, and then retrieve it. TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNode) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); // Check that the DataTypeController associated the models. bool has_nodes; ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes)); ASSERT_TRUE(has_nodes); // Fill an instance of session specifics with a foreign session's data. std::string tag = "tag1"; SessionID::id_type nums1[] = {5, 10, 13, 17}; std::vector tabs1; std::vector tab_list1(nums1, nums1 + arraysize(nums1)); sync_pb::SessionSpecifics meta(helper_.BuildForeignSession( tag, tab_list1, &tabs1)); // Update associator with the session's meta node containing one window. model_associator_->AssociateForeignSpecifics(meta, base::Time()); // Add tabs for the window. for (std::vector::iterator iter = tabs1.begin(); iter != tabs1.end(); ++iter) { model_associator_->AssociateForeignSpecifics(*iter, base::Time()); } // Check that the foreign session was associated and retrieve the data. std::vector foreign_sessions; ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(1U, foreign_sessions.size()); std::vector > session_reference; session_reference.push_back(tab_list1); helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); } // Write a foreign session with one window to a node. Sync, then add a window. // Sync, then add a third window. Close the two windows. TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeThreeWindows) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); // Build a foreign session with one window and four tabs. std::string tag = "tag1"; SessionID::id_type nums1[] = {5, 10, 13, 17}; std::vector tabs1; std::vector tab_list1(nums1, nums1 + arraysize(nums1)); sync_pb::SessionSpecifics meta(helper_.BuildForeignSession( tag, tab_list1, &tabs1)); // Update associator with the session's meta node containing one window. model_associator_->AssociateForeignSpecifics(meta, base::Time()); // Add tabs for first window. for (std::vector::iterator iter = tabs1.begin(); iter != tabs1.end(); ++iter) { model_associator_->AssociateForeignSpecifics(*iter, base::Time()); } // Verify first window std::vector foreign_sessions; ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); std::vector > session_reference; session_reference.push_back(tab_list1); helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); // Add a second window. SessionID::id_type tab_nums2[] = {7, 15, 18, 20}; std::vector tab_list2( tab_nums2, tab_nums2 + arraysize(tab_nums2)); helper_.AddWindowSpecifics(1, tab_list2, &meta); std::vector tabs2; tabs2.resize(tab_list2.size()); for (size_t i = 0; i < tab_list2.size(); ++i) { helper_.BuildTabSpecifics(tag, 0, tab_list2[i], &tabs2[i]); } // Update associator with the session's meta node containing two windows. model_associator_->AssociateForeignSpecifics(meta, base::Time()); // Add tabs for second window. for (std::vector::iterator iter = tabs2.begin(); iter != tabs2.end(); ++iter) { model_associator_->AssociateForeignSpecifics(*iter, base::Time()); } // Verify the two windows. foreign_sessions.clear(); ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(1U, foreign_sessions.size()); session_reference.push_back(tab_list2); helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); // Add a third window. SessionID::id_type tab_nums3[] = {8, 16, 19, 21}; std::vector tab_list3( tab_nums3, tab_nums3 + arraysize(tab_nums3)); helper_.AddWindowSpecifics(2, tab_list3, &meta); std::vector tabs3; tabs3.resize(tab_list3.size()); for (size_t i = 0; i < tab_list3.size(); ++i) { helper_.BuildTabSpecifics(tag, 0, tab_list3[i], &tabs3[i]); } // Update associator with the session's meta node containing three windows. model_associator_->AssociateForeignSpecifics(meta, base::Time()); // Add tabs for third window. for (std::vector::iterator iter = tabs3.begin(); iter != tabs3.end(); ++iter) { model_associator_->AssociateForeignSpecifics(*iter, base::Time()); } // Verify the three windows foreign_sessions.clear(); ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(1U, foreign_sessions.size()); session_reference.push_back(tab_list3); helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); // Close third window (by clearing and then not adding it back). meta.mutable_header()->clear_window(); helper_.AddWindowSpecifics(0, tab_list1, &meta); helper_.AddWindowSpecifics(1, tab_list2, &meta); // Update associator with just the meta node, now containing only two windows. model_associator_->AssociateForeignSpecifics(meta, base::Time()); // Verify first two windows are still there. foreign_sessions.clear(); ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(1U, foreign_sessions.size()); session_reference.pop_back(); // Pop off the data for the third window. helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); // Close second window (by clearing and then not adding it back). meta.mutable_header()->clear_window(); helper_.AddWindowSpecifics(0, tab_list1, &meta); // Update associator with just the meta node, now containing only one windows. model_associator_->AssociateForeignSpecifics(meta, base::Time()); // Verify first window is still there. foreign_sessions.clear(); ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(1U, foreign_sessions.size()); session_reference.pop_back(); // Pop off the data for the second window. helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); } // Write a foreign session to a node, with the tabs arriving first, and then // retrieve it. TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeTabsFirst) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); // Fill an instance of session specifics with a foreign session's data. std::string tag = "tag1"; SessionID::id_type nums1[] = {5, 10, 13, 17}; std::vector tabs1; std::vector tab_list1 (nums1, nums1 + arraysize(nums1)); sync_pb::SessionSpecifics meta(helper_.BuildForeignSession( tag, tab_list1, &tabs1)); // Add tabs for first window. for (std::vector::iterator iter = tabs1.begin(); iter != tabs1.end(); ++iter) { model_associator_->AssociateForeignSpecifics(*iter, base::Time()); } // Update associator with the session's meta node containing one window. model_associator_->AssociateForeignSpecifics(meta, base::Time()); // Check that the foreign session was associated and retrieve the data. std::vector foreign_sessions; ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(1U, foreign_sessions.size()); std::vector > session_reference; session_reference.push_back(tab_list1); helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); } // Write a foreign session to a node with some tabs that never arrive. TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeMissingTabs) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); // Fill an instance of session specifics with a foreign session's data. std::string tag = "tag1"; SessionID::id_type nums1[] = {5, 10, 13, 17}; std::vector tabs1; std::vector tab_list1 (nums1, nums1 + arraysize(nums1)); sync_pb::SessionSpecifics meta(helper_.BuildForeignSession( tag, tab_list1, &tabs1)); // Add a second window, but this time only create two tab nodes, despite the // window expecting four tabs. SessionID::id_type tab_nums2[] = {7, 15, 18, 20}; std::vector tab_list2( tab_nums2, tab_nums2 + arraysize(tab_nums2)); helper_.AddWindowSpecifics(1, tab_list2, &meta); std::vector tabs2; tabs2.resize(2); for (size_t i = 0; i < 2; ++i) { helper_.BuildTabSpecifics(tag, 0, tab_list2[i], &tabs2[i]); } // Update associator with the session's meta node containing two windows. model_associator_->AssociateForeignSpecifics(meta, base::Time()); // Add tabs for first window. for (std::vector::iterator iter = tabs1.begin(); iter != tabs1.end(); ++iter) { model_associator_->AssociateForeignSpecifics(*iter, base::Time()); } // Add tabs for second window. for (std::vector::iterator iter = tabs2.begin(); iter != tabs2.end(); ++iter) { model_associator_->AssociateForeignSpecifics(*iter, base::Time()); } // Check that the foreign session was associated and retrieve the data. std::vector foreign_sessions; ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(1U, foreign_sessions.size()); ASSERT_EQ(2U, foreign_sessions[0]->windows.size()); ASSERT_EQ(4U, foreign_sessions[0]->windows.find(0)->second->tabs.size()); ASSERT_EQ(4U, foreign_sessions[0]->windows.find(1)->second->tabs.size()); // Close the second window. meta.mutable_header()->clear_window(); helper_.AddWindowSpecifics(0, tab_list1, &meta); // Update associator with the session's meta node containing one window. model_associator_->AssociateForeignSpecifics(meta, base::Time()); // Check that the foreign session was associated and retrieve the data. foreign_sessions.clear(); ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(1U, foreign_sessions.size()); ASSERT_EQ(1U, foreign_sessions[0]->windows.size()); std::vector > session_reference; session_reference.push_back(tab_list1); helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); } // Test the DataTypeController on update. TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionUpdate) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); int64 node_id = model_associator_->GetSyncIdFromSessionTag( model_associator_->GetCurrentMachineTag()); ASSERT_FALSE(notified_of_update_); { syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); change_processor_->ApplyChangesFromSyncModel( &trans, 0, ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList( node_id, ChangeRecord::ACTION_UPDATE)); } ASSERT_TRUE(notified_of_update_); } // Test the DataTypeController on add. TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionAdd) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); int64 node_id = model_associator_->GetSyncIdFromSessionTag( model_associator_->GetCurrentMachineTag()); ASSERT_FALSE(notified_of_update_); { syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); change_processor_->ApplyChangesFromSyncModel( &trans, 0, ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList( node_id, ChangeRecord::ACTION_ADD)); } ASSERT_TRUE(notified_of_update_); } // Test the DataTypeController on delete. TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionDelete) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); int64 node_id = model_associator_->GetSyncIdFromSessionTag( model_associator_->GetCurrentMachineTag()); sync_pb::EntitySpecifics deleted_specifics; deleted_specifics.mutable_session()->set_session_tag("tag"); ASSERT_FALSE(notified_of_update_); { syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); change_processor_->ApplyChangesFromSyncModel( &trans, 0, ProfileSyncServiceTestHelper::MakeSingletonDeletionChangeRecordList( node_id, deleted_specifics)); } ASSERT_TRUE(notified_of_update_); } // Test the TabNodePool when it starts off empty. TEST_F(ProfileSyncServiceSessionTest, TabNodePoolEmpty) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); std::vector node_ids; ASSERT_EQ(0U, model_associator_->local_tab_pool_.Capacity()); ASSERT_TRUE(model_associator_->local_tab_pool_.Empty()); ASSERT_TRUE(model_associator_->local_tab_pool_.Full()); const size_t num_ids = 10; for (size_t i = 0; i < num_ids; ++i) { int id = model_associator_->local_tab_pool_.GetFreeTabNode(); ASSERT_GT(id, TabNodePool::kInvalidTabNodeID); node_ids.push_back(id); // Associate with a tab node. model_associator_->local_tab_pool_.AssociateTabNode(id, i + 1); } ASSERT_EQ(num_ids, model_associator_->local_tab_pool_.Capacity()); ASSERT_TRUE(model_associator_->local_tab_pool_.Empty()); ASSERT_FALSE(model_associator_->local_tab_pool_.Full()); for (size_t i = 0; i < num_ids; ++i) { model_associator_->local_tab_pool_.FreeTabNode(node_ids[i]); } ASSERT_EQ(num_ids, model_associator_->local_tab_pool_.Capacity()); ASSERT_FALSE(model_associator_->local_tab_pool_.Empty()); ASSERT_TRUE(model_associator_->local_tab_pool_.Full()); } // TODO(jhorwich): Re-enable when crbug.com/121487 addressed TEST_F(ProfileSyncServiceSessionTest, TabNodePoolNonEmpty) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); const size_t num_starting_nodes = 3; for (size_t i = 0; i < num_starting_nodes; ++i) { size_t node_id = i + 1; model_associator_->local_tab_pool_.AddTabNode(node_id); model_associator_->local_tab_pool_.AssociateTabNode(node_id, i); model_associator_->local_tab_pool_.FreeTabNode(node_id); } std::vector node_ids; ASSERT_EQ(num_starting_nodes, model_associator_->local_tab_pool_.Capacity()); ASSERT_FALSE(model_associator_->local_tab_pool_.Empty()); ASSERT_TRUE(model_associator_->local_tab_pool_.Full()); const size_t num_ids = 10; for (size_t i = 0; i < num_ids; ++i) { int id = model_associator_->local_tab_pool_.GetFreeTabNode(); ASSERT_GT(id, TabNodePool::kInvalidTabNodeID); node_ids.push_back(id); // Associate with a tab node. model_associator_->local_tab_pool_.AssociateTabNode(id, i + 1); } ASSERT_EQ(num_ids, model_associator_->local_tab_pool_.Capacity()); ASSERT_TRUE(model_associator_->local_tab_pool_.Empty()); ASSERT_FALSE(model_associator_->local_tab_pool_.Full()); for (size_t i = 0; i < num_ids; ++i) { model_associator_->local_tab_pool_.FreeTabNode(node_ids[i]); } ASSERT_EQ(num_ids, model_associator_->local_tab_pool_.Capacity()); ASSERT_FALSE(model_associator_->local_tab_pool_.Empty()); ASSERT_TRUE(model_associator_->local_tab_pool_.Full()); } // Write a foreign session to a node, and then delete it. TEST_F(ProfileSyncServiceSessionTest, DeleteForeignSession) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); // Check that the DataTypeController associated the models. bool has_nodes; ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes)); ASSERT_TRUE(has_nodes); // A foreign session's tag. std::string tag = "tag1"; // Should do nothing if the foreign session doesn't exist. std::vector foreign_sessions; ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_FALSE(notified_of_update_); model_associator_->DeleteForeignSession(tag); ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions)); // Verify that deleteForeignSession did not trigger the // NOTIFICATION_FOREIGN_SESSION_DISABLED notification. ASSERT_FALSE(notified_of_update_); // Fill an instance of session specifics with a foreign session's data. SessionID::id_type nums1[] = {5, 10, 13, 17}; std::vector tabs1; std::vector tab_list1 (nums1, nums1 + arraysize(nums1)); sync_pb::SessionSpecifics meta(helper_.BuildForeignSession( tag, tab_list1, &tabs1)); // Update associator with the session's meta node containing one window. model_associator_->AssociateForeignSpecifics(meta, base::Time()); // Add tabs for the window. for (std::vector::iterator iter = tabs1.begin(); iter != tabs1.end(); ++iter) { model_associator_->AssociateForeignSpecifics(*iter, base::Time()); } // Check that the foreign session was associated and retrieve the data. ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(1U, foreign_sessions.size()); std::vector > session_reference; session_reference.push_back(tab_list1); helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); ASSERT_FALSE(notified_of_update_); // Now delete the foreign session. model_associator_->DeleteForeignSession(tag); ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions)); // Verify that deleteForeignSession triggers the // NOTIFICATION_FOREIGN_SESSION_DISABLED notification. ASSERT_TRUE(notified_of_update_); } // Associate both a non-stale foreign session and a stale foreign session. // Ensure only the stale session gets deleted. TEST_F(ProfileSyncServiceSessionTest, DeleteStaleSessions) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); // Fill two instances of session specifics with a foreign session's data. std::string tag = "tag1"; SessionID::id_type nums1[] = {5, 10, 13, 17}; std::vector tabs1; std::vector tab_list1 (nums1, nums1 + arraysize(nums1)); sync_pb::SessionSpecifics meta(helper_.BuildForeignSession( tag, tab_list1, &tabs1)); std::string tag2 = "tag2"; sync_pb::SessionSpecifics meta2; helper_.BuildSessionSpecifics(tag2, &meta2); SessionID::id_type tab_nums2[] = {8, 15, 18, 20}; std::vector tab_list2( tab_nums2, tab_nums2 + arraysize(tab_nums2)); helper_.AddWindowSpecifics(0, tab_list2, &meta2); std::vector tabs2; tabs2.resize(tab_list2.size()); for (size_t i = 0; i < tab_list2.size(); ++i) { helper_.BuildTabSpecifics(tag2, 0, tab_list2[i], &tabs2[i]); } // Set the modification time for tag1 to be 21 days ago, tag2 to 5 days ago. base::Time tag1_time = base::Time::Now() - base::TimeDelta::FromDays(21); base::Time tag2_time = base::Time::Now() - base::TimeDelta::FromDays(5); // Associate specifics. model_associator_->AssociateForeignSpecifics(meta, tag1_time); for (std::vector::iterator iter = tabs1.begin(); iter != tabs1.end(); ++iter) { model_associator_->AssociateForeignSpecifics(*iter, tag1_time); } model_associator_->AssociateForeignSpecifics(meta2, tag2_time); for (std::vector::iterator iter = tabs2.begin(); iter != tabs2.end(); ++iter) { model_associator_->AssociateForeignSpecifics(*iter, tag2_time); } // Check that the foreign session was associated and retrieve the data. std::vector foreign_sessions; ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(2U, foreign_sessions.size()); // Now delete the stale session and verify the non-stale one is still there. model_associator_->DeleteStaleSessions(); ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(1U, foreign_sessions.size()); std::vector > session_reference; session_reference.push_back(tab_list2); helper_.VerifySyncedSession(tag2, session_reference, *(foreign_sessions[0])); } // Write a stale foreign session to a node. Then update one of its tabs so // the session is no longer stale. Ensure it doesn't get deleted. TEST_F(ProfileSyncServiceSessionTest, StaleSessionRefresh) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); std::string tag = "tag1"; SessionID::id_type nums1[] = {5, 10, 13, 17}; std::vector tabs1; std::vector tab_list1 (nums1, nums1 + arraysize(nums1)); sync_pb::SessionSpecifics meta(helper_.BuildForeignSession( tag, tab_list1, &tabs1)); // Associate. base::Time stale_time = base::Time::Now() - base::TimeDelta::FromDays(21); model_associator_->AssociateForeignSpecifics(meta, stale_time); for (std::vector::iterator iter = tabs1.begin(); iter != tabs1.end(); ++iter) { model_associator_->AssociateForeignSpecifics(*iter, stale_time); } // Associate one of the tabs with a non-stale time. model_associator_->AssociateForeignSpecifics(tabs1[0], base::Time::Now()); // Check that the foreign session was associated and retrieve the data. std::vector foreign_sessions; ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(1U, foreign_sessions.size()); // Verify the now non-stale session does not get deleted. model_associator_->DeleteStaleSessions(); ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(1U, foreign_sessions.size()); std::vector > session_reference; session_reference.push_back(tab_list1); helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); } // Crashes sometimes on Windows, particularly XP. // See http://crbug.com/174951 #if defined(OS_WIN) #define MAYBE_ValidTabs DISABLED_ValidTabs #else #define MAYBE_ValidTabs ValidTabs #endif // defined(OS_WIN) // Test that tabs with nothing but "chrome://*" and "file://*" navigations are // not be synced. TEST_F(ProfileSyncServiceSessionTest, MAYBE_ValidTabs) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); AddTab(browser(), GURL("chrome://bla1/")); NavigateAndCommitActiveTab(GURL("chrome://bla2")); AddTab(browser(), GURL("file://bla3/")); AddTab(browser(), GURL("bla://bla")); // Note: chrome://newtab has special handling which crashes in unit tests. // Get the tabs for this machine. Only the bla:// url should be synced. SessionModelAssociator::TabLinksMap tab_map = model_associator_->local_tab_map_; ASSERT_EQ(1U, tab_map.size()); SessionModelAssociator::TabLinksMap::iterator iter = tab_map.begin(); ASSERT_EQ(1, iter->second->tab()->GetEntryCount()); ASSERT_EQ(GURL("bla://bla"), iter->second->tab()-> GetEntryAtIndex(0)->GetVirtualURL()); } // Verify that AttemptSessionsDataRefresh triggers the // NOTIFICATION_SYNC_REFRESH_LOCAL notification. // TODO(zea): Once we can have unit tests that are able to open to the NTP, // test that the NTP/#opentabs URL triggers a refresh as well (but only when // it is the active tab). TEST_F(ProfileSyncServiceSessionTest, SessionsRefresh) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); // Empty, so returns false. std::vector foreign_sessions; ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_FALSE(notified_of_refresh_); model_associator_->AttemptSessionsDataRefresh(); ASSERT_TRUE(notified_of_refresh_); // Nothing should have changed since we don't have unapplied data. ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions)); } // Ensure model association associates the pre-existing tabs. TEST_F(ProfileSyncServiceSessionTest, ExistingTabs) { AddTab(browser(), GURL("http://foo1")); NavigateAndCommitActiveTab(GURL("http://foo2")); AddTab(browser(), GURL("http://bar1")); NavigateAndCommitActiveTab(GURL("http://bar2")); CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); bool has_nodes; ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes)); ASSERT_TRUE(has_nodes); std::string machine_tag = model_associator_->GetCurrentMachineTag(); int64 sync_id = model_associator_->GetSyncIdFromSessionTag(machine_tag); ASSERT_NE(syncer::kInvalidId, sync_id); // Check that this machine's data is not included in the foreign windows. std::vector foreign_sessions; ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions)); ASSERT_EQ(foreign_sessions.size(), 0U); // Get the tabs for this machine from the node and check that they were // filled. SessionModelAssociator::TabLinksMap tab_map = model_associator_->local_tab_map_; ASSERT_EQ(2U, tab_map.size()); // Tabs are ordered by sessionid in tab_map, so should be able to traverse // the tree based on order of tabs created SessionModelAssociator::TabLinksMap::iterator iter = tab_map.begin(); ASSERT_EQ(2, iter->second->tab()->GetEntryCount()); ASSERT_EQ(GURL("http://foo1"), iter->second->tab()-> GetEntryAtIndex(0)->GetVirtualURL()); ASSERT_EQ(GURL("http://foo2"), iter->second->tab()-> GetEntryAtIndex(1)->GetVirtualURL()); iter++; ASSERT_EQ(2, iter->second->tab()->GetEntryCount()); ASSERT_EQ(GURL("http://bar1"), iter->second->tab()-> GetEntryAtIndex(0)->GetVirtualURL()); ASSERT_EQ(GURL("http://bar2"), iter->second->tab()-> GetEntryAtIndex(1)->GetVirtualURL()); } TEST_F(ProfileSyncServiceSessionTest, MissingHeaderAndTab) { AddTab(browser(), GURL("http://foo1")); NavigateAndCommitActiveTab(GURL("http://foo2")); AddTab(browser(), GURL("http://bar1")); NavigateAndCommitActiveTab(GURL("http://bar2")); CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); syncer::SyncError error; std::string local_tag = model_associator_->GetCurrentMachineTag(); error = model_associator_->DisassociateModels(); ASSERT_FALSE(error.IsSet()); { // Create a sync node with the local tag but neither header nor tab field. syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); syncer::ReadNode root(&trans); root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)); syncer::WriteNode extra_header(&trans); syncer::WriteNode::InitUniqueByCreationResult result = extra_header.InitUniqueByCreation(syncer::SESSIONS, root, "new_tag"); ASSERT_EQ(syncer::WriteNode::INIT_SUCCESS, result); sync_pb::SessionSpecifics specifics; specifics.set_session_tag(local_tag); extra_header.SetSessionSpecifics(specifics); } error = model_associator_->AssociateModels(NULL, NULL); ASSERT_FALSE(error.IsSet()); } TEST_F(ProfileSyncServiceSessionTest, MultipleHeaders) { AddTab(browser(), GURL("http://foo1")); NavigateAndCommitActiveTab(GURL("http://foo2")); AddTab(browser(), GURL("http://bar1")); NavigateAndCommitActiveTab(GURL("http://bar2")); CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); syncer::SyncError error; std::string local_tag = model_associator_->GetCurrentMachineTag(); error = model_associator_->DisassociateModels(); ASSERT_FALSE(error.IsSet()); { // Create another sync node with a header field and the local tag. syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); syncer::ReadNode root(&trans); root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)); syncer::WriteNode extra_header(&trans); syncer::WriteNode::InitUniqueByCreationResult result = extra_header.InitUniqueByCreation(syncer::SESSIONS, root, local_tag + "_"); ASSERT_EQ(syncer::WriteNode::INIT_SUCCESS, result); sync_pb::SessionSpecifics specifics; specifics.set_session_tag(local_tag); specifics.mutable_header(); extra_header.SetSessionSpecifics(specifics); } error = model_associator_->AssociateModels(NULL, NULL); ASSERT_FALSE(error.IsSet()); } TEST_F(ProfileSyncServiceSessionTest, CorruptedForeign) { AddTab(browser(), GURL("http://foo1")); NavigateAndCommitActiveTab(GURL("http://foo2")); AddTab(browser(), GURL("http://bar1")); NavigateAndCommitActiveTab(GURL("http://bar2")); CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); syncer::SyncError error; error = model_associator_->DisassociateModels(); ASSERT_FALSE(error.IsSet()); { // Create another sync node with neither header nor tab field and a foreign // tag. std::string foreign_tag = "foreign_tag"; syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); syncer::ReadNode root(&trans); root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)); syncer::WriteNode extra_header(&trans); syncer::WriteNode::InitUniqueByCreationResult result = extra_header.InitUniqueByCreation(syncer::SESSIONS, root, foreign_tag); ASSERT_EQ(syncer::WriteNode::INIT_SUCCESS, result); sync_pb::SessionSpecifics specifics; specifics.set_session_tag(foreign_tag); extra_header.SetSessionSpecifics(specifics); } error = model_associator_->AssociateModels(NULL, NULL); ASSERT_FALSE(error.IsSet()); } TEST_F(ProfileSyncServiceSessionTest, MissingLocalTabNode) { AddTab(browser(), GURL("http://foo1")); NavigateAndCommitActiveTab(GURL("http://foo2")); AddTab(browser(), GURL("http://bar1")); NavigateAndCommitActiveTab(GURL("http://bar2")); CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); std::string local_tag = model_associator_->GetCurrentMachineTag(); syncer::SyncError error; error = model_associator_->DisassociateModels(); ASSERT_FALSE(error.IsSet()); { // Delete the first sync tab node. std::string tab_tag = TabNodePool::TabIdToTag(local_tag, 1); syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); syncer::ReadNode root(&trans); root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)); syncer::WriteNode tab_node(&trans); ASSERT_EQ(syncer::BaseNode::INIT_OK, tab_node.InitByClientTagLookup(syncer::SESSIONS, tab_tag)); tab_node.Tombstone(); } error = model_associator_->AssociateModels(NULL, NULL); ASSERT_FALSE(error.IsSet()); // Add some more tabs to ensure we don't conflict with the pre-existing tab // node. AddTab(browser(), GURL("http://baz1")); AddTab(browser(), GURL("http://baz2")); } TEST_F(ProfileSyncServiceSessionTest, Favicons) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); // Build a foreign session with one window and one tab. std::string tag = "tag1"; sync_pb::SessionSpecifics meta; helper_.BuildSessionSpecifics(tag, &meta); std::vector tab_list; tab_list.push_back(5); helper_.AddWindowSpecifics(0, tab_list, &meta); sync_pb::SessionSpecifics tab; helper_.BuildTabSpecifics(tag, 0, tab_list[0], &tab); std::string url = tab.tab().navigation(0).virtual_url(); scoped_refptr favicon; // Update associator. model_associator_->AssociateForeignSpecifics(meta, base::Time()); model_associator_->AssociateForeignSpecifics(tab, base::Time()); base::RunLoop().RunUntilIdle(); ASSERT_FALSE(model_associator_->GetSyncedFaviconForPageURL(url, &favicon)); // Now add a favicon. tab.mutable_tab()->set_favicon_source("http://favicon_source.com/png.ico"); tab.mutable_tab()->set_favicon_type(sync_pb::SessionTab::TYPE_WEB_FAVICON); tab.mutable_tab()->set_favicon("data"); model_associator_->AssociateForeignSpecifics(tab, base::Time()); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(model_associator_->GetSyncedFaviconForPageURL(url, &favicon)); ASSERT_TRUE(CompareMemoryToString("data", favicon)); // Simulate navigating away. The associator should not delete the favicon. tab.mutable_tab()->clear_navigation(); tab.mutable_tab()->add_navigation()->set_virtual_url("http://new_url.com"); tab.mutable_tab()->clear_favicon_source(); tab.mutable_tab()->clear_favicon_type(); tab.mutable_tab()->clear_favicon(); model_associator_->AssociateForeignSpecifics(tab, base::Time()); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(model_associator_->GetSyncedFaviconForPageURL(url, &favicon)); } TEST_F(ProfileSyncServiceSessionTest, CorruptedLocalHeader) { AddTab(browser(), GURL("http://foo1")); NavigateAndCommitActiveTab(GURL("http://foo2")); AddTab(browser(), GURL("http://bar1")); NavigateAndCommitActiveTab(GURL("http://bar2")); CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); std::string local_tag = model_associator_->GetCurrentMachineTag(); syncer::SyncError error; error = model_associator_->DisassociateModels(); ASSERT_FALSE(error.IsSet()); { // Load the header node and clear it. syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); syncer::WriteNode header(&trans); ASSERT_EQ(syncer::BaseNode::INIT_OK, header.InitByClientTagLookup(syncer::SESSIONS, local_tag)); sync_pb::SessionSpecifics specifics; header.SetSessionSpecifics(specifics); } // Ensure we associate properly despite the pre-existing node with our local // tag. error = model_associator_->AssociateModels(NULL, NULL); ASSERT_FALSE(error.IsSet()); } TEST_F(ProfileSyncServiceSessionTest, CheckPrerenderedWebContentsSwap) { AddTab(browser(), GURL("http://foo1")); NavigateAndCommitActiveTab(GURL("http://foo2")); CreateRootHelper create_root(this); // Test setup. ASSERT_TRUE(StartSyncService(create_root.callback(), false)); syncer::SyncError error; // Initial association. EXPECT_TRUE(model_associator_->AssociateWindows(true, &error)); ASSERT_FALSE(error.IsSet()); // To simulate WebContents swap during prerendering, create new WebContents // and swap with old WebContents. content::WebContents* old_web_contents = browser()->tab_strip_model()->GetActiveWebContents(); // Create new WebContents, with the required tab helpers. WebContents* new_web_contents = WebContents::CreateWithSessionStorage( WebContents::CreateParams(profile()), old_web_contents->GetController().GetSessionStorageNamespaceMap()); SessionTabHelper::CreateForWebContents(new_web_contents); TabContentsSyncedTabDelegate::CreateForWebContents(new_web_contents); new_web_contents->GetController() .CopyStateFrom(old_web_contents->GetController()); // Swap the WebContents. int index = browser()->tab_strip_model()->GetIndexOfWebContents(old_web_contents); browser()->tab_strip_model()->ReplaceWebContentsAt(index, new_web_contents); EXPECT_TRUE(model_associator_->AssociateWindows(true, &error)); ASSERT_FALSE(error.IsSet()); // Navigate away. NavigateAndCommitActiveTab(GURL("http://bar2")); EXPECT_TRUE(model_associator_->AssociateWindows(true, &error)); ASSERT_FALSE(error.IsSet()); // Delete old WebContents. This should not crash. delete old_web_contents; EXPECT_TRUE(model_associator_->AssociateWindows(true, &error)); ASSERT_FALSE(error.IsSet()); // Try more navigations to make sure everything if fine. NavigateAndCommitActiveTab(GURL("http://bar3")); EXPECT_TRUE(model_associator_->AssociateWindows(true, &error)); ASSERT_FALSE(error.IsSet()); AddTab(browser(), GURL("http://bar4")); EXPECT_TRUE(model_associator_->AssociateWindows(true, &error)); ASSERT_FALSE(error.IsSet()); NavigateAndCommitActiveTab(GURL("http://bar5")); EXPECT_TRUE(model_associator_->AssociateWindows(true, &error)); ASSERT_FALSE(error.IsSet()); } TEST_F(ProfileSyncServiceSessionTest, TabPoolFreeNodeLimits) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); // Allocate TabNodePool::kFreeNodesHighWatermark + 1 nodes and verify that // freeing the last node reduces the free node pool size to // kFreeNodesLowWatermark. SessionID session_id; std::vector used_sync_ids; for (size_t i = 1; i <= TabNodePool::kFreeNodesHighWatermark + 1; ++i) { session_id.set_id(i); int sync_id = model_associator_->local_tab_pool_.GetFreeTabNode(); model_associator_->local_tab_pool_.AssociateTabNode(sync_id, i); used_sync_ids.push_back(sync_id); } // Free all except one node. int last_sync_id = used_sync_ids.back(); used_sync_ids.pop_back(); for (size_t i = 0; i < used_sync_ids.size(); ++i) { model_associator_->local_tab_pool_.FreeTabNode(used_sync_ids[i]); } // Except one node all nodes should be in FreeNode pool. EXPECT_FALSE(model_associator_->local_tab_pool_.Full()); EXPECT_FALSE(model_associator_->local_tab_pool_.Empty()); // Total capacity = 1 Associated Node + kFreeNodesHighWatermark free node. EXPECT_EQ(TabNodePool::kFreeNodesHighWatermark + 1, model_associator_->local_tab_pool_.Capacity()); // Freeing the last sync node should drop the free nodes to // kFreeNodesLowWatermark. model_associator_->local_tab_pool_.FreeTabNode(last_sync_id); EXPECT_FALSE(model_associator_->local_tab_pool_.Empty()); EXPECT_TRUE(model_associator_->local_tab_pool_.Full()); EXPECT_EQ(TabNodePool::kFreeNodesLowWatermark, model_associator_->local_tab_pool_.Capacity()); } TEST_F(ProfileSyncServiceSessionTest, TabNodePoolDeleteUnassociatedNodes) { CreateRootHelper create_root(this); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); std::string local_tag = model_associator_->GetCurrentMachineTag(); syncer::SyncError error; // Create a free node and then dissassociate sessions so that it ends up // unassociated. int tab_node_id = model_associator_->local_tab_pool_.GetFreeTabNode(); // Update the tab_id of the node, so that it is considered a valid // unassociated node otherwise it will be mistaken for a corrupted node and // will be deleted before being added to the tab node pool. { std::string tab_tag = TabNodePool::TabIdToTag(local_tag, tab_node_id); syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); syncer::WriteNode tab_node(&trans); ASSERT_EQ(syncer::BaseNode::INIT_OK, tab_node.InitByClientTagLookup(syncer::SESSIONS, tab_tag)); sync_pb::SessionSpecifics specifics = tab_node.GetSessionSpecifics(); sync_pb::SessionTab* tab = specifics.mutable_tab(); tab->set_tab_id(1); tab_node.SetSessionSpecifics(specifics); } error = model_associator_->DisassociateModels(); ASSERT_FALSE(error.IsSet()); error = model_associator_->AssociateModels(NULL, NULL); ASSERT_FALSE(error.IsSet()); } } // namespace browser_sync