blob: 979ed40474ea8f0890f615838fc77e84e1eb4a97 [file] [log] [blame]
// 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 <map>
#include <string>
#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/strings/utf_string_conversions.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/signin_manager.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/signin/token_service_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/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,
bool synchronous_backend_initialization)
: TestProfileSyncService(factory,
profile,
signin,
oauth2_token_service,
behavior,
synchronous_backend_initialization) {}
virtual ~FakeProfileSyncService() {}
virtual scoped_ptr<DeviceInfo> GetLocalDeviceInfo() const OVERRIDE {
return scoped_ptr<DeviceInfo>(new DeviceInfo(base::GenerateGUID(),
"client_name",
std::string(),
std::string(),
sync_pb::SyncEnums::TYPE_WIN));
}
};
void BuildSessionSpecifics(const std::string& tag,
sync_pb::SessionSpecifics* meta) {
meta->set_session_tag(tag);
sync_pb::SessionHeader* header = meta->mutable_header();
header->set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_LINUX);
header->set_client_name("name");
}
void AddWindowSpecifics(int window_id,
const std::vector<int>& tab_list,
sync_pb::SessionSpecifics* meta) {
sync_pb::SessionHeader* header = meta->mutable_header();
sync_pb::SessionWindow* window = header->add_window();
window->set_window_id(window_id);
window->set_selected_tab_index(0);
window->set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_TABBED);
for (std::vector<int>::const_iterator iter = tab_list.begin();
iter != tab_list.end(); ++iter) {
window->add_tab(*iter);
}
}
// Verifies number of windows, number of tabs, and basic fields.
void VerifySyncedSession(
const std::string& tag,
const std::vector<std::vector<SessionID::id_type> >& windows,
const SyncedSession& session) {
ASSERT_EQ(tag, session.session_tag);
ASSERT_EQ(SyncedSession::TYPE_LINUX, session.device_type);
ASSERT_EQ("name", session.session_name);
ASSERT_EQ(windows.size(), session.windows.size());
// We assume the window id's are in increasing order.
int i = 0;
for (std::vector<std::vector<int> >::const_iterator win_iter =
windows.begin();
win_iter != windows.end(); ++win_iter, ++i) {
SessionWindow* win_ptr;
SyncedSession::SyncedWindowMap::const_iterator map_iter =
session.windows.find(i);
if (map_iter != session.windows.end())
win_ptr = map_iter->second;
else
FAIL();
ASSERT_EQ(win_iter->size(), win_ptr->tabs.size());
ASSERT_EQ(0, win_ptr->selected_tab_index);
ASSERT_EQ(1, win_ptr->type);
int j = 0;
for (std::vector<int>::const_iterator tab_iter = (*win_iter).begin();
tab_iter != (*win_iter).end(); ++tab_iter, ++j) {
SessionTab* tab = win_ptr->tabs[j];
ASSERT_EQ(*tab_iter, tab->tab_id.id());
ASSERT_EQ(1U, tab->navigations.size());
ASSERT_EQ(1, tab->tab_visual_index);
ASSERT_EQ(0, tab->current_navigation_index);
ASSERT_TRUE(tab->pinned);
ASSERT_EQ("app_id", tab->extension_app_id);
ASSERT_EQ(1U, tab->navigations.size());
ASSERT_EQ(tab->navigations[0].virtual_url(), GURL("http://foo/1"));
ASSERT_EQ(tab->navigations[0].referrer().url, GURL("referrer"));
ASSERT_EQ(tab->navigations[0].title(), string16(ASCIIToUTF16("title")));
ASSERT_EQ(tab->navigations[0].transition_type(),
content::PAGE_TRANSITION_TYPED);
}
}
}
bool CompareMemoryToString(
const std::string& str,
const scoped_refptr<base::RefCountedMemory>& mem) {
if (mem->size() != str.size())
return false;
for (size_t i = 0; i <mem->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_update_(false),
notified_of_refresh_(false),
max_tab_node_id_(0) {}
ProfileSyncService* sync_service() { return sync_service_.get(); }
void BuildTabSpecifics(const std::string& tag, int window_id, int tab_id,
sync_pb::SessionSpecifics* tab_base) {
tab_base->set_session_tag(tag);
tab_base->set_tab_node_id(++max_tab_node_id_);
sync_pb::SessionTab* tab = tab_base->mutable_tab();
tab->set_tab_id(tab_id);
tab->set_tab_visual_index(1);
tab->set_current_navigation_index(0);
tab->set_pinned(true);
tab->set_extension_app_id("app_id");
sync_pb::TabNavigation* navigation = tab->add_navigation();
navigation->set_virtual_url("http://foo/1");
navigation->set_referrer("referrer");
navigation->set_title("title");
navigation->set_page_transition(sync_pb::SyncEnums_PageTransition_TYPED);
}
protected:
virtual TestingProfile* CreateProfile() OVERRIDE {
TestingProfile::Builder builder;
builder.AddTestingFactory(ProfileOAuth2TokenServiceFactory::GetInstance(),
FakeOAuth2TokenService::BuildTokenService);
// Don't want the profile to create a real ProfileSyncService.
builder.AddTestingFactory(ProfileSyncServiceFactory::GetInstance(), NULL);
scoped_ptr<TestingProfile> 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() {
max_tab_node_id_ = 0;
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,
false));
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");
TokenServiceFactory::GetForProfile(profile())
->IssueAuthTokenForTest(GaiaConstants::kSyncService, "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<TestProfileSyncService> sync_service_;
const gfx::Rect window_bounds_;
bool notified_of_update_;
bool notified_of_refresh_;
content::NotificationRegistrar registrar_;
net::TestURLFetcherFactory fetcher_factory_;
int max_tab_node_id_;
};
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<const SyncedSession*> 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";
sync_pb::SessionSpecifics meta;
BuildSessionSpecifics(tag, &meta);
SessionID::id_type tab_nums1[] = {5, 10, 13, 17};
std::vector<SessionID::id_type> tab_list1(
tab_nums1, tab_nums1 + arraysize(tab_nums1));
AddWindowSpecifics(0, tab_list1, &meta);
std::vector<sync_pb::SessionSpecifics> tabs1;
tabs1.resize(tab_list1.size());
for (size_t i = 0; i < tab_list1.size(); ++i) {
BuildTabSpecifics(tag, 0, tab_list1[i], &tabs1[i]);
}
// 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<sync_pb::SessionSpecifics>::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<const SyncedSession*> foreign_sessions;
ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
ASSERT_EQ(1U, foreign_sessions.size());
std::vector<std::vector<SessionID::id_type> > session_reference;
session_reference.push_back(tab_list1);
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";
sync_pb::SessionSpecifics meta;
BuildSessionSpecifics(tag, &meta);
SessionID::id_type tab_nums1[] = {5, 10, 13, 17};
std::vector<SessionID::id_type> tab_list1(
tab_nums1, tab_nums1 + arraysize(tab_nums1));
AddWindowSpecifics(0, tab_list1, &meta);
std::vector<sync_pb::SessionSpecifics> tabs1;
tabs1.resize(tab_list1.size());
for (size_t i = 0; i < tab_list1.size(); ++i) {
BuildTabSpecifics(tag, 0, tab_list1[i], &tabs1[i]);
}
// 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<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin();
iter != tabs1.end(); ++iter) {
model_associator_->AssociateForeignSpecifics(*iter, base::Time());
}
// Verify first window
std::vector<const SyncedSession*> foreign_sessions;
ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
std::vector<std::vector<SessionID::id_type> > session_reference;
session_reference.push_back(tab_list1);
VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
// Add a second window.
SessionID::id_type tab_nums2[] = {7, 15, 18, 20};
std::vector<SessionID::id_type> tab_list2(
tab_nums2, tab_nums2 + arraysize(tab_nums2));
AddWindowSpecifics(1, tab_list2, &meta);
std::vector<sync_pb::SessionSpecifics> tabs2;
tabs2.resize(tab_list2.size());
for (size_t i = 0; i < tab_list2.size(); ++i) {
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<sync_pb::SessionSpecifics>::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);
VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
// Add a third window.
SessionID::id_type tab_nums3[] = {8, 16, 19, 21};
std::vector<SessionID::id_type> tab_list3(
tab_nums3, tab_nums3 + arraysize(tab_nums3));
AddWindowSpecifics(2, tab_list3, &meta);
std::vector<sync_pb::SessionSpecifics> tabs3;
tabs3.resize(tab_list3.size());
for (size_t i = 0; i < tab_list3.size(); ++i) {
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<sync_pb::SessionSpecifics>::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);
VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
// Close third window (by clearing and then not adding it back).
meta.mutable_header()->clear_window();
AddWindowSpecifics(0, tab_list1, &meta);
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.
VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
// Close second window (by clearing and then not adding it back).
meta.mutable_header()->clear_window();
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.
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";
sync_pb::SessionSpecifics meta;
BuildSessionSpecifics(tag, &meta);
SessionID::id_type tab_nums1[] = {5, 10, 13, 17};
std::vector<SessionID::id_type> tab_list1(
tab_nums1, tab_nums1 + arraysize(tab_nums1));
AddWindowSpecifics(0, tab_list1, &meta);
std::vector<sync_pb::SessionSpecifics> tabs1;
tabs1.resize(tab_list1.size());
for (size_t i = 0; i < tab_list1.size(); ++i) {
BuildTabSpecifics(tag, 0, tab_list1[i], &tabs1[i]);
}
// Add tabs for first window.
for (std::vector<sync_pb::SessionSpecifics>::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<const SyncedSession*> foreign_sessions;
ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
ASSERT_EQ(1U, foreign_sessions.size());
std::vector<std::vector<SessionID::id_type> > session_reference;
session_reference.push_back(tab_list1);
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";
sync_pb::SessionSpecifics meta;
BuildSessionSpecifics(tag, &meta);
SessionID::id_type tab_nums1[] = {5, 10, 13, 17};
std::vector<SessionID::id_type> tab_list1(
tab_nums1, tab_nums1 + arraysize(tab_nums1));
AddWindowSpecifics(0, tab_list1, &meta);
std::vector<sync_pb::SessionSpecifics> tabs1;
tabs1.resize(tab_list1.size()); // First window has all the tabs
for (size_t i = 0; i < tab_list1.size(); ++i) {
BuildTabSpecifics(tag, 0, tab_list1[i], &tabs1[i]);
}
// 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<SessionID::id_type> tab_list2(
tab_nums2, tab_nums2 + arraysize(tab_nums2));
AddWindowSpecifics(1, tab_list2, &meta);
std::vector<sync_pb::SessionSpecifics> tabs2;
tabs2.resize(2);
for (size_t i = 0; i < 2; ++i) {
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<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin();
iter != tabs1.end(); ++iter) {
model_associator_->AssociateForeignSpecifics(*iter, base::Time());
}
// Add tabs for second window.
for (std::vector<sync_pb::SessionSpecifics>::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<const SyncedSession*> 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();
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<std::vector<SessionID::id_type> > session_reference;
session_reference.push_back(tab_list1);
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<int> 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<int> 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<const SyncedSession*> foreign_sessions;
ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions));
model_associator_->DeleteForeignSession(tag);
ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions));
// Fill an instance of session specifics with a foreign session's data.
sync_pb::SessionSpecifics meta;
BuildSessionSpecifics(tag, &meta);
SessionID::id_type tab_nums1[] = {5, 10, 13, 17};
std::vector<SessionID::id_type> tab_list1(
tab_nums1, tab_nums1 + arraysize(tab_nums1));
AddWindowSpecifics(0, tab_list1, &meta);
std::vector<sync_pb::SessionSpecifics> tabs1;
tabs1.resize(tab_list1.size());
for (size_t i = 0; i < tab_list1.size(); ++i) {
BuildTabSpecifics(tag, 0, tab_list1[i], &tabs1[i]);
}
// 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<sync_pb::SessionSpecifics>::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<std::vector<SessionID::id_type> > session_reference;
session_reference.push_back(tab_list1);
VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
// Now delete the foreign session.
model_associator_->DeleteForeignSession(tag);
ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions));
}
// 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";
sync_pb::SessionSpecifics meta;
BuildSessionSpecifics(tag, &meta);
SessionID::id_type tab_nums1[] = {5, 10, 13, 17};
std::vector<SessionID::id_type> tab_list1(
tab_nums1, tab_nums1 + arraysize(tab_nums1));
AddWindowSpecifics(0, tab_list1, &meta);
std::vector<sync_pb::SessionSpecifics> tabs1;
tabs1.resize(tab_list1.size());
for (size_t i = 0; i < tab_list1.size(); ++i) {
BuildTabSpecifics(tag, 0, tab_list1[i], &tabs1[i]);
}
std::string tag2 = "tag2";
sync_pb::SessionSpecifics meta2;
BuildSessionSpecifics(tag2, &meta2);
SessionID::id_type tab_nums2[] = {8, 15, 18, 20};
std::vector<SessionID::id_type> tab_list2(
tab_nums2, tab_nums2 + arraysize(tab_nums2));
AddWindowSpecifics(0, tab_list2, &meta2);
std::vector<sync_pb::SessionSpecifics> tabs2;
tabs2.resize(tab_list2.size());
for (size_t i = 0; i < tab_list2.size(); ++i) {
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<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin();
iter != tabs1.end(); ++iter) {
model_associator_->AssociateForeignSpecifics(*iter, tag1_time);
}
model_associator_->AssociateForeignSpecifics(meta2, tag2_time);
for (std::vector<sync_pb::SessionSpecifics>::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<const SyncedSession*> 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<std::vector<SessionID::id_type> > session_reference;
session_reference.push_back(tab_list2);
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";
sync_pb::SessionSpecifics meta;
BuildSessionSpecifics(tag, &meta);
SessionID::id_type tab_nums1[] = {5, 10, 13, 17};
std::vector<SessionID::id_type> tab_list1(
tab_nums1, tab_nums1 + arraysize(tab_nums1));
AddWindowSpecifics(0, tab_list1, &meta);
std::vector<sync_pb::SessionSpecifics> tabs1;
tabs1.resize(tab_list1.size());
for (size_t i = 0; i < tab_list1.size(); ++i) {
BuildTabSpecifics(tag, 0, tab_list1[i], &tabs1[i]);
}
// Associate.
base::Time stale_time = base::Time::Now() - base::TimeDelta::FromDays(21);
model_associator_->AssociateForeignSpecifics(meta, stale_time);
for (std::vector<sync_pb::SessionSpecifics>::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<const SyncedSession*> 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<std::vector<SessionID::id_type> > session_reference;
session_reference.push_back(tab_list1);
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<const SyncedSession*> 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<const SyncedSession*> 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;
BuildSessionSpecifics(tag, &meta);
std::vector<SessionID::id_type> tab_list;
tab_list.push_back(5);
AddWindowSpecifics(0, tab_list, &meta);
sync_pb::SessionSpecifics tab;
BuildTabSpecifics(tag, 0, tab_list[0], &tab);
std::string url = tab.tab().navigation(0).virtual_url();
scoped_refptr<base::RefCountedMemory> 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<int> 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