diff options
author | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-27 17:56:42 +0000 |
---|---|---|
committer | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-27 17:56:42 +0000 |
commit | f545687686a2bcb95fb83a11440f1c80c5a704eb (patch) | |
tree | a0b54b7d5588ba2c1585e4ff87b8cfacce4290f0 | |
parent | acae39d810773951c6a8bfed2d464248d554015a (diff) | |
download | chromium_src-f545687686a2bcb95fb83a11440f1c80c5a704eb.zip chromium_src-f545687686a2bcb95fb83a11440f1c80c5a704eb.tar.gz chromium_src-f545687686a2bcb95fb83a11440f1c80c5a704eb.tar.bz2 |
sync: beginnings of a sync/api implementation of sessions datatype
Based off of https://codereview.chromium.org/23018004/
BUG=80194, 98892
R=shashishekhar@chromium.org, zea@chromium.org
Review URL: https://codereview.chromium.org/18600013
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@225731 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/sync/glue/session_sync_test_helper.cc | 135 | ||||
-rw-r--r-- | chrome/browser/sync/glue/session_sync_test_helper.h | 56 | ||||
-rw-r--r-- | chrome/browser/sync/profile_sync_service_session_unittest.cc | 244 | ||||
-rw-r--r-- | chrome/browser/sync/sessions2/sessions_sync_manager.cc | 430 | ||||
-rw-r--r-- | chrome/browser/sync/sessions2/sessions_sync_manager.h | 237 | ||||
-rw-r--r-- | chrome/browser/sync/sessions2/sessions_sync_manager_unittest.cc | 369 | ||||
-rw-r--r-- | chrome/browser/sync/sessions2/tab_node_pool2.cc | 6 | ||||
-rw-r--r-- | chrome/browser/sync/sessions2/tab_node_pool2.h | 4 | ||||
-rw-r--r-- | chrome/browser/sync/sessions2/tab_node_pool2_unittest.cc | 112 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests_unit.gypi | 3 |
11 files changed, 1367 insertions, 231 deletions
diff --git a/chrome/browser/sync/glue/session_sync_test_helper.cc b/chrome/browser/sync/glue/session_sync_test_helper.cc new file mode 100644 index 0000000..0fd71f6 --- /dev/null +++ b/chrome/browser/sync/glue/session_sync_test_helper.cc @@ -0,0 +1,135 @@ +// Copyright 2013 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 "chrome/browser/sync/glue/session_sync_test_helper.h" + +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/sync/glue/synced_session.h" +#include "sync/protocol/session_specifics.pb.h" +#include "sync/protocol/sync_enums.pb.h" +#include "testing/gtest/include/gtest/gtest.h" + + +namespace browser_sync { + +static const char* kClientName = "name"; +static const char* kAppId = "app_id"; +static const char* kVirtualUrl = "http://foo/1"; +static const char* kReferrer = "referrer"; +static const char* kTitle = "title"; + +// static +void SessionSyncTestHelper::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(kClientName); +} + +// static +void SessionSyncTestHelper::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); + } +} + +// static +void SessionSyncTestHelper::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(kClientName, 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(kAppId, tab->extension_app_id); + ASSERT_EQ(1U, tab->navigations.size()); + ASSERT_EQ(tab->navigations[0].virtual_url(), GURL(kVirtualUrl)); + ASSERT_EQ(tab->navigations[0].referrer().url, GURL(kReferrer)); + ASSERT_EQ(tab->navigations[0].title(), string16(ASCIIToUTF16(kTitle))); + ASSERT_EQ(tab->navigations[0].transition_type(), + content::PAGE_TRANSITION_TYPED); + } + } +} + +void SessionSyncTestHelper::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(kAppId); + sync_pb::TabNavigation* navigation = tab->add_navigation(); + navigation->set_virtual_url(kVirtualUrl); + navigation->set_referrer(kReferrer); + navigation->set_title(kTitle); + navigation->set_page_transition(sync_pb::SyncEnums_PageTransition_TYPED); +} + +void SessionSyncTestHelper::Reset() { + max_tab_node_id_ = 0; +} + +sync_pb::SessionSpecifics SessionSyncTestHelper::BuildForeignSession( + const std::string& tag, + const std::vector<SessionID::id_type>& tab_list, + std::vector<sync_pb::SessionSpecifics>* tabs) { + sync_pb::SessionSpecifics meta; + BuildSessionSpecifics(tag, &meta); + AddWindowSpecifics(0, tab_list, &meta); + std::vector<sync_pb::SessionSpecifics> tabs1; + tabs1.resize(tab_list.size()); + for (size_t i = 0; i < tab_list.size(); ++i) { + BuildTabSpecifics(tag, 0, tab_list[i], &tabs1[i]); + } + + if (tabs) + tabs->swap(tabs1); + return meta; +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/session_sync_test_helper.h b/chrome/browser/sync/glue/session_sync_test_helper.h new file mode 100644 index 0000000..ea576e5 --- /dev/null +++ b/chrome/browser/sync/glue/session_sync_test_helper.h @@ -0,0 +1,56 @@ +// Copyright 2013 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. + +#ifndef CHROME_BROWSER_SYNC_GLUE_SESSION_SYNC_TEST_HELPER_H_ +#define CHROME_BROWSER_SYNC_GLUE_SESSION_SYNC_TEST_HELPER_H_ + +#include <string> +#include <vector> + +#include "chrome/browser/sessions/session_id.h" + +namespace sync_pb { +class SessionSpecifics; +} + +namespace browser_sync { + +struct SyncedSession; + +class SessionSyncTestHelper { + public: + SessionSyncTestHelper() : max_tab_node_id_(0) {} + + static void BuildSessionSpecifics(const std::string& tag, + sync_pb::SessionSpecifics* meta); + + static void AddWindowSpecifics(int window_id, + const std::vector<int>& tab_list, + sync_pb::SessionSpecifics* meta); + + static void VerifySyncedSession( + const std::string& tag, + const std::vector<std::vector<SessionID::id_type> >& windows, + const SyncedSession& session); + + void BuildTabSpecifics(const std::string& tag, + int window_id, + int tab_id, + sync_pb::SessionSpecifics* tab_base); + + sync_pb::SessionSpecifics BuildForeignSession( + const std::string& tag, + const std::vector<SessionID::id_type>& tab_list, + std::vector<sync_pb::SessionSpecifics>* tabs); + + void Reset(); + + private: + int max_tab_node_id_; + DISALLOW_COPY_AND_ASSIGN(SessionSyncTestHelper); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_SESSION_SYNC_TEST_HELPER_H_ diff --git a/chrome/browser/sync/profile_sync_service_session_unittest.cc b/chrome/browser/sync/profile_sync_service_session_unittest.cc index 979ed404..e9857cc 100644 --- a/chrome/browser/sync/profile_sync_service_session_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_session_unittest.cc @@ -15,7 +15,6 @@ #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" @@ -28,6 +27,7 @@ #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" @@ -103,73 +103,6 @@ class FakeProfileSyncService : public TestProfileSyncService { } }; -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) { @@ -191,27 +124,9 @@ class ProfileSyncServiceSessionTest ProfileSyncServiceSessionTest() : window_bounds_(0, 1, 2, 3), notified_of_update_(false), - notified_of_refresh_(false), - max_tab_node_id_(0) {} + notified_of_refresh_(false) {} 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; @@ -252,7 +167,6 @@ class ProfileSyncServiceSessionTest } virtual void TearDown() { - max_tab_node_id_ = 0; sync_service_->Shutdown(); sync_service_.reset(); @@ -326,7 +240,7 @@ class ProfileSyncServiceSessionTest bool notified_of_refresh_; content::NotificationRegistrar registrar_; net::TestURLFetcherFactory fetcher_factory_; - int max_tab_node_id_; + SessionSyncTestHelper helper_; }; class CreateRootHelper { @@ -455,17 +369,11 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNode) { // 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); + SessionID::id_type nums1[] = {5, 10, 13, 17}; 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::vector<SessionID::id_type> 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()); @@ -481,7 +389,7 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNode) { 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])); + helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); } // Write a foreign session with one window to a node. Sync, then add a window. @@ -493,17 +401,11 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeThreeWindows) { // 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); + SessionID::id_type nums1[] = {5, 10, 13, 17}; 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::vector<SessionID::id_type> 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. @@ -517,17 +419,17 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeThreeWindows) { 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])); + helper_.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); + helper_.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]); + 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()); @@ -542,17 +444,17 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeThreeWindows) { 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])); + helper_.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); + helper_.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]); + 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()); @@ -567,12 +469,12 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeThreeWindows) { 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])); + helper_.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); + 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()); @@ -581,11 +483,11 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeThreeWindows) { 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])); + helper_.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); + helper_.AddWindowSpecifics(0, tab_list1, &meta); // Update associator with just the meta node, now containing only one windows. model_associator_->AssociateForeignSpecifics(meta, base::Time()); @@ -594,7 +496,7 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeThreeWindows) { 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])); + helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); } // Write a foreign session to a node, with the tabs arriving first, and then @@ -606,17 +508,11 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeTabsFirst) { // 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); + SessionID::id_type nums1[] = {5, 10, 13, 17}; 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::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1)); + sync_pb::SessionSpecifics meta(helper_.BuildForeignSession( + tag, tab_list1, &tabs1)); // Add tabs for first window. for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin(); @@ -632,7 +528,7 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeTabsFirst) { 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])); + helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); } // Write a foreign session to a node with some tabs that never arrive. @@ -643,27 +539,21 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeMissingTabs) { // 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); + SessionID::id_type nums1[] = {5, 10, 13, 17}; 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]); - } + std::vector<SessionID::id_type> 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<SessionID::id_type> tab_list2( tab_nums2, tab_nums2 + arraysize(tab_nums2)); - AddWindowSpecifics(1, tab_list2, &meta); + helper_.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]); + helper_.BuildTabSpecifics(tag, 0, tab_list2[i], &tabs2[i]); } // Update associator with the session's meta node containing two windows. @@ -689,7 +579,7 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeMissingTabs) { // Close the second window. meta.mutable_header()->clear_window(); - AddWindowSpecifics(0, tab_list1, &meta); + helper_.AddWindowSpecifics(0, tab_list1, &meta); // Update associator with the session's meta node containing one window. model_associator_->AssociateForeignSpecifics(meta, base::Time()); @@ -701,7 +591,7 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeMissingTabs) { 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])); + helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); } // Test the DataTypeController on update. @@ -848,17 +738,11 @@ TEST_F(ProfileSyncServiceSessionTest, DeleteForeignSession) { 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); + SessionID::id_type nums1[] = {5, 10, 13, 17}; 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::vector<SessionID::id_type> 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()); @@ -873,7 +757,7 @@ TEST_F(ProfileSyncServiceSessionTest, DeleteForeignSession) { 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])); + helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); // Now delete the foreign session. model_associator_->DeleteForeignSession(tag); @@ -889,28 +773,22 @@ TEST_F(ProfileSyncServiceSessionTest, DeleteStaleSessions) { // 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); + SessionID::id_type nums1[] = {5, 10, 13, 17}; 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::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1)); + sync_pb::SessionSpecifics meta(helper_.BuildForeignSession( + tag, tab_list1, &tabs1)); std::string tag2 = "tag2"; sync_pb::SessionSpecifics meta2; - BuildSessionSpecifics(tag2, &meta2); + helper_.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); + helper_.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]); + 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. @@ -940,7 +818,7 @@ TEST_F(ProfileSyncServiceSessionTest, DeleteStaleSessions) { 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])); + helper_.VerifySyncedSession(tag2, session_reference, *(foreign_sessions[0])); } // Write a stale foreign session to a node. Then update one of its tabs so @@ -951,17 +829,11 @@ TEST_F(ProfileSyncServiceSessionTest, StaleSessionRefresh) { 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); + SessionID::id_type nums1[] = {5, 10, 13, 17}; 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::vector<SessionID::id_type> 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); @@ -985,7 +857,7 @@ TEST_F(ProfileSyncServiceSessionTest, StaleSessionRefresh) { 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])); + helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); } // Crashes sometimes on Windows, particularly XP. @@ -1217,12 +1089,12 @@ TEST_F(ProfileSyncServiceSessionTest, Favicons) { // Build a foreign session with one window and one tab. std::string tag = "tag1"; sync_pb::SessionSpecifics meta; - BuildSessionSpecifics(tag, &meta); + helper_.BuildSessionSpecifics(tag, &meta); std::vector<SessionID::id_type> tab_list; tab_list.push_back(5); - AddWindowSpecifics(0, tab_list, &meta); + helper_.AddWindowSpecifics(0, tab_list, &meta); sync_pb::SessionSpecifics tab; - BuildTabSpecifics(tag, 0, tab_list[0], &tab); + helper_.BuildTabSpecifics(tag, 0, tab_list[0], &tab); std::string url = tab.tab().navigation(0).virtual_url(); scoped_refptr<base::RefCountedMemory> favicon; diff --git a/chrome/browser/sync/sessions2/sessions_sync_manager.cc b/chrome/browser/sync/sessions2/sessions_sync_manager.cc new file mode 100644 index 0000000..2a49054 --- /dev/null +++ b/chrome/browser/sync/sessions2/sessions_sync_manager.cc @@ -0,0 +1,430 @@ +// Copyright 2013 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 "chrome/browser/sync/sessions2/sessions_sync_manager.h" + +#include "chrome/browser/sync/glue/synced_tab_delegate.h" +#include "sync/api/sync_error.h" +#include "sync/api/sync_error_factory.h" +#include "sync/api/sync_merge_result.h" +#include "sync/api/time.h" + +using syncer::SyncChange; +using syncer::SyncData; + +namespace browser_sync { + +// Maximum number of favicons to sync. +// TODO(zea): pull this from the server. +static const int kMaxSyncFavicons = 200; + +static const int kInvalidTabNodeId = -1; + +SessionsSyncManager::SessionsSyncManager( + Profile* profile, + scoped_ptr<SyncPrefs> sync_prefs, + SyncInternalApiDelegate* delegate) + : favicon_cache_(profile, kMaxSyncFavicons), + delegate_(delegate), + local_session_header_node_id_(kInvalidTabNodeId) { + sync_prefs_ = sync_prefs.Pass(); +} + +SessionsSyncManager::~SessionsSyncManager() { +} + +// Returns the GUID-based string that should be used for +// |SessionsSyncManager::current_machine_tag_|. +static std::string BuildMachineTag(const std::string& cache_guid) { + std::string machine_tag = "session_sync"; + machine_tag.append(cache_guid); + return machine_tag; +} + +syncer::SyncMergeResult SessionsSyncManager::MergeDataAndStartSyncing( + syncer::ModelType type, + const syncer::SyncDataList& initial_sync_data, + scoped_ptr<syncer::SyncChangeProcessor> sync_processor, + scoped_ptr<syncer::SyncErrorFactory> error_handler) { + syncer::SyncMergeResult merge_result(type); + DCHECK(session_tracker_.Empty()); + DCHECK_EQ(0U, local_tab_pool_.Capacity()); + + error_handler_ = error_handler.Pass(); + sync_processor_ = sync_processor.Pass(); + + local_session_header_node_id_ = kInvalidTabNodeId; + scoped_ptr<DeviceInfo> local_device_info(delegate_->GetLocalDeviceInfo()); + syncer::SyncChangeList new_changes; + + // Make sure we have a machine tag. We do this now (versus earlier) as it's + // a conveniently safe time to assert sync is ready and the cache_guid is + // initialized. + if (current_machine_tag_.empty()) + InitializeCurrentMachineTag(); + if (local_device_info) { + current_session_name_ = local_device_info->client_name(); + } else { + merge_result.set_error(error_handler_->CreateAndUploadError( + FROM_HERE, + "Failed to get device info for machine tag.")); + return merge_result; + } + session_tracker_.SetLocalSessionTag(current_machine_tag_); + + // First, we iterate over sync data to update our session_tracker_. + if (!InitFromSyncModel(initial_sync_data, &new_changes)) { + // The sync db didn't have a header node for us. Create one. + sync_pb::EntitySpecifics specifics; + sync_pb::SessionSpecifics* base_specifics = specifics.mutable_session(); + base_specifics->set_session_tag(current_machine_tag()); + sync_pb::SessionHeader* header_s = base_specifics->mutable_header(); + header_s->set_client_name(current_session_name_); + header_s->set_device_type(DeviceInfo::GetLocalDeviceType()); + syncer::SyncData data = syncer::SyncData::CreateLocalData( + current_machine_tag(), current_session_name_, specifics); + new_changes.push_back(syncer::SyncChange( + FROM_HERE, syncer::SyncChange::ACTION_ADD, data)); + } + +#if defined(OS_ANDROID) + std::string sync_machine_tag(BuildMachineTag(delegate_->GetCacheGuid())); + if (current_machine_tag_.compare(sync_machine_tag) != 0) + DeleteForeignSession(sync_machine_tag, &new_changes); +#endif + + // Check if anything has changed on the local client side. + AssociateWindows(RELOAD_TABS, &new_changes); + + merge_result.set_error( + sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes)); + return merge_result; +} + +void SessionsSyncManager::AssociateWindows( + ReloadTabsOption option, + syncer::SyncChangeList* change_output) { + NOTIMPLEMENTED() << "TODO(tim)"; +} + +void SessionsSyncManager::AssociateTab(SyncedTabDelegate* const tab, + syncer::SyncChangeList* change_output) { + NOTIMPLEMENTED() << "TODO(tim)"; +} + +void SessionsSyncManager::OnLocalTabModified( + const SyncedTabDelegate& modified_tab, syncer::SyncError* error) { + NOTIMPLEMENTED() << "TODO(tim): SessionModelAssociator::Observe equivalent."; +} + +void SessionsSyncManager::OnBrowserOpened() { + NOTIMPLEMENTED() << "TODO(tim): SessionModelAssociator::Observe equivalent."; +} + +bool SessionsSyncManager::ShouldSyncTab(const SyncedTabDelegate& tab) const { + NOTIMPLEMENTED() << "TODO(tim)"; + return true; +} + +void SessionsSyncManager::ForwardRelevantFaviconUpdatesToFaviconCache( + const std::set<GURL>& updated_favicon_page_urls) { + NOTIMPLEMENTED() << + "TODO(tim): SessionModelAssociator::FaviconsUpdated equivalent."; +} + +void SessionsSyncManager::StopSyncing(syncer::ModelType type) { + NOTIMPLEMENTED(); +} + +syncer::SyncDataList SessionsSyncManager::GetAllSyncData( + syncer::ModelType type) const { + NOTIMPLEMENTED(); + return syncer::SyncDataList(); +} + +syncer::SyncError SessionsSyncManager::ProcessSyncChanges( + const tracked_objects::Location& from_here, + const syncer::SyncChangeList& change_list) { + NOTIMPLEMENTED(); + return syncer::SyncError(); +} + +syncer::SyncChange SessionsSyncManager::TombstoneTab( + const sync_pb::SessionSpecifics& tab) { + if (!tab.has_tab_node_id()) { + LOG(WARNING) << "Old sessions node without tab node id; can't tombstone."; + return syncer::SyncChange(); + } else { + return syncer::SyncChange( + FROM_HERE, + SyncChange::ACTION_DELETE, + SyncData::CreateLocalDelete( + TabNodePool2::TabIdToTag(current_machine_tag(), + tab.tab_node_id()), + syncer::SESSIONS)); + } +} + +bool SessionsSyncManager::GetAllForeignSessions( + std::vector<const SyncedSession*>* sessions) { + return session_tracker_.LookupAllForeignSessions(sessions); +} + +bool SessionsSyncManager::InitFromSyncModel( + const syncer::SyncDataList& sync_data, + syncer::SyncChangeList* new_changes) { + bool found_current_header = false; + for (syncer::SyncDataList::const_iterator it = sync_data.begin(); + it != sync_data.end(); + ++it) { + const syncer::SyncData& data = *it; + DCHECK(data.GetSpecifics().has_session()); + const sync_pb::SessionSpecifics& specifics = data.GetSpecifics().session(); + if (specifics.session_tag().empty() || + (specifics.has_tab() && (!specifics.has_tab_node_id() || + !specifics.tab().has_tab_id()))) { + syncer::SyncChange tombstone(TombstoneTab(specifics)); + if (tombstone.IsValid()) + new_changes->push_back(tombstone); + } else if (specifics.session_tag() != current_machine_tag()) { + UpdateTrackerWithForeignSession(specifics, data.GetRemoteModifiedTime()); + } else { + // This is previously stored local session information. + if (specifics.has_header() && !found_current_header) { + // This is our previous header node, reuse it. + found_current_header = true; + if (specifics.header().has_client_name()) + current_session_name_ = specifics.header().client_name(); + } else { + if (specifics.has_header() || !specifics.has_tab()) { + LOG(WARNING) << "Found more than one session header node with local " + << "tag."; + syncer::SyncChange tombstone(TombstoneTab(specifics)); + if (tombstone.IsValid()) + new_changes->push_back(tombstone); + } else { + // This is a valid old tab node, add it to the pool so it can be + // reused for reassociation. + local_tab_pool_.AddTabNode(specifics.tab_node_id()); + } + } + } + } + return found_current_header; +} + +void SessionsSyncManager::UpdateTrackerWithForeignSession( + const sync_pb::SessionSpecifics& specifics, + const base::Time& modification_time) { + std::string foreign_session_tag = specifics.session_tag(); + DCHECK_NE(foreign_session_tag, current_machine_tag()); + + SyncedSession* foreign_session = + session_tracker_.GetSession(foreign_session_tag); + if (specifics.has_header()) { + // Read in the header data for this foreign session. + // Header data contains window information and ordered tab id's for each + // window. + + // Load (or create) the SyncedSession object for this client. + const sync_pb::SessionHeader& header = specifics.header(); + PopulateSessionHeaderFromSpecifics(header, + modification_time, + foreign_session); + + // Reset the tab/window tracking for this session (must do this before + // we start calling PutWindowInSession and PutTabInWindow so that all + // unused tabs/windows get cleared by the CleanupSession(...) call). + session_tracker_.ResetSessionTracking(foreign_session_tag); + + // Process all the windows and their tab information. + int num_windows = header.window_size(); + DVLOG(1) << "Associating " << foreign_session_tag << " with " + << num_windows << " windows."; + + for (int i = 0; i < num_windows; ++i) { + const sync_pb::SessionWindow& window_s = header.window(i); + SessionID::id_type window_id = window_s.window_id(); + session_tracker_.PutWindowInSession(foreign_session_tag, + window_id); + BuildSyncedSessionFromSpecifics(foreign_session_tag, + window_s, + modification_time, + foreign_session->windows[window_id]); + } + // Delete any closed windows and unused tabs as necessary. + session_tracker_.CleanupSession(foreign_session_tag); + } else if (specifics.has_tab()) { + const sync_pb::SessionTab& tab_s = specifics.tab(); + SessionID::id_type tab_id = tab_s.tab_id(); + SessionTab* tab = + session_tracker_.GetTab(foreign_session_tag, + tab_id, + specifics.tab_node_id()); + + // Update SessionTab based on protobuf. + tab->SetFromSyncData(tab_s, modification_time); + + // If a favicon or favicon urls are present, load the URLs and visit + // times into the in-memory favicon cache. + RefreshFaviconVisitTimesFromForeignTab(tab_s, modification_time); + + // Update the last modified time. + if (foreign_session->modified_time < modification_time) + foreign_session->modified_time = modification_time; + } else { + LOG(WARNING) << "Ignoring foreign session node with missing header/tab " + << "fields and tag " << foreign_session_tag << "."; + } +} + +void SessionsSyncManager::InitializeCurrentMachineTag() { + DCHECK(current_machine_tag_.empty()); + std::string persisted_guid; + persisted_guid = sync_prefs_->GetSyncSessionsGUID(); + if (!persisted_guid.empty()) { + current_machine_tag_ = persisted_guid; + DVLOG(1) << "Restoring persisted session sync guid: " << persisted_guid; + } else { + current_machine_tag_ = BuildMachineTag(delegate_->GetCacheGuid()); + DVLOG(1) << "Creating session sync guid: " << current_machine_tag_; + sync_prefs_->SetSyncSessionsGUID(current_machine_tag_); + } + + local_tab_pool_.SetMachineTag(current_machine_tag_); +} + +// static +void SessionsSyncManager::PopulateSessionHeaderFromSpecifics( + const sync_pb::SessionHeader& header_specifics, + base::Time mtime, + SyncedSession* session_header) const { + if (header_specifics.has_client_name()) + session_header->session_name = header_specifics.client_name(); + if (header_specifics.has_device_type()) { + switch (header_specifics.device_type()) { + case sync_pb::SyncEnums_DeviceType_TYPE_WIN: + session_header->device_type = SyncedSession::TYPE_WIN; + break; + case sync_pb::SyncEnums_DeviceType_TYPE_MAC: + session_header->device_type = SyncedSession::TYPE_MACOSX; + break; + case sync_pb::SyncEnums_DeviceType_TYPE_LINUX: + session_header->device_type = SyncedSession::TYPE_LINUX; + break; + case sync_pb::SyncEnums_DeviceType_TYPE_CROS: + session_header->device_type = SyncedSession::TYPE_CHROMEOS; + break; + case sync_pb::SyncEnums_DeviceType_TYPE_PHONE: + session_header->device_type = SyncedSession::TYPE_PHONE; + break; + case sync_pb::SyncEnums_DeviceType_TYPE_TABLET: + session_header->device_type = SyncedSession::TYPE_TABLET; + break; + case sync_pb::SyncEnums_DeviceType_TYPE_OTHER: + // Intentionally fall-through + default: + session_header->device_type = SyncedSession::TYPE_OTHER; + break; + } + } + session_header->modified_time = mtime; +} + +// static +void SessionsSyncManager::BuildSyncedSessionFromSpecifics( + const std::string& session_tag, + const sync_pb::SessionWindow& specifics, + base::Time mtime, + SessionWindow* session_window) { + if (specifics.has_window_id()) + session_window->window_id.set_id(specifics.window_id()); + if (specifics.has_selected_tab_index()) + session_window->selected_tab_index = specifics.selected_tab_index(); + if (specifics.has_browser_type()) { + if (specifics.browser_type() == + sync_pb::SessionWindow_BrowserType_TYPE_TABBED) { + session_window->type = 1; + } else { + session_window->type = 2; + } + } + session_window->timestamp = mtime; + session_window->tabs.resize(specifics.tab_size(), NULL); + for (int i = 0; i < specifics.tab_size(); i++) { + SessionID::id_type tab_id = specifics.tab(i); + session_tracker_.PutTabInWindow(session_tag, + session_window->window_id.id(), + tab_id, + i); + } +} + +void SessionsSyncManager::RefreshFaviconVisitTimesFromForeignTab( + const sync_pb::SessionTab& tab, const base::Time& modification_time) { + // First go through and iterate over all the navigations, checking if any + // have valid favicon urls. + for (int i = 0; i < tab.navigation_size(); ++i) { + if (!tab.navigation(i).favicon_url().empty()) { + const std::string& page_url = tab.navigation(i).virtual_url(); + const std::string& favicon_url = tab.navigation(i).favicon_url(); + favicon_cache_.OnReceivedSyncFavicon(GURL(page_url), + GURL(favicon_url), + std::string(), + syncer::TimeToProtoTime( + modification_time)); + } + } +} + +bool SessionsSyncManager::GetSyncedFaviconForPageURL( + const std::string& page_url, + scoped_refptr<base::RefCountedMemory>* favicon_png) const { + return favicon_cache_.GetSyncedFaviconForPageURL(GURL(page_url), favicon_png); +} + +void SessionsSyncManager::DeleteForeignSession( + const std::string& tag, syncer::SyncChangeList* change_output) { + if (tag == current_machine_tag()) { + LOG(ERROR) << "Attempting to delete local session. This is not currently " + << "supported."; + return; + } + + std::set<int> tab_node_ids_to_delete; + session_tracker_.LookupTabNodeIds(tag, &tab_node_ids_to_delete); + if (!DisassociateForeignSession(tag)) { + // We don't have any data for this session, our work here is done! + return; + } + + // Prepare deletes for the meta-node as well as individual tab nodes. + change_output->push_back(syncer::SyncChange( + FROM_HERE, + SyncChange::ACTION_DELETE, + SyncData::CreateLocalDelete(tag, syncer::SESSIONS))); + + for (std::set<int>::const_iterator it = tab_node_ids_to_delete.begin(); + it != tab_node_ids_to_delete.end(); + ++it) { + change_output->push_back(syncer::SyncChange( + FROM_HERE, + SyncChange::ACTION_DELETE, + SyncData::CreateLocalDelete(TabNodePool2::TabIdToTag(tag, *it), + syncer::SESSIONS))); + } +} + +bool SessionsSyncManager::DisassociateForeignSession( + const std::string& foreign_session_tag) { + if (foreign_session_tag == current_machine_tag()) { + DVLOG(1) << "Local session deleted! Doing nothing until a navigation is " + << "triggered."; + return false; + } + DVLOG(1) << "Disassociating session " << foreign_session_tag; + return session_tracker_.DeleteSession(foreign_session_tag); +} + +}; // namespace browser_sync diff --git a/chrome/browser/sync/sessions2/sessions_sync_manager.h b/chrome/browser/sync/sessions2/sessions_sync_manager.h new file mode 100644 index 0000000..174ede6 --- /dev/null +++ b/chrome/browser/sync/sessions2/sessions_sync_manager.h @@ -0,0 +1,237 @@ +// Copyright 2013 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. + +#ifndef CHROME_BROWSER_SYNC_SESSIONS2_SESSIONS_SYNC_MANAGER_H_ +#define CHROME_BROWSER_SYNC_SESSIONS2_SESSIONS_SYNC_MANAGER_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_vector.h" +#include "base/time/time.h" +#include "chrome/browser/sessions/session_id.h" +#include "chrome/browser/sessions/session_types.h" +#include "chrome/browser/sync/glue/device_info.h" +#include "chrome/browser/sync/glue/favicon_cache.h" +#include "chrome/browser/sync/glue/synced_session.h" +#include "chrome/browser/sync/glue/synced_session_tracker.h" +#include "chrome/browser/sync/sessions2/tab_node_pool2.h" +#include "chrome/browser/sync/sync_prefs.h" +#include "sync/api/syncable_service.h" + +class Profile; + +namespace syncer { +class SyncErrorFactory; +} + +namespace sync_pb { +class SessionHeader; +class SessionSpecifics; +class SessionTab; +class SessionWindow; +class TabNavigation; +} // namespace sync_pb + +namespace browser_sync { + +class DataTypeErrorHandler; +class SyncedTabDelegate; + +// Contains all logic for associating the Chrome sessions model and +// the sync sessions model. +class SessionsSyncManager : public syncer::SyncableService { + public: + // Isolates SessionsSyncManager from having to depend on sync internals. + class SyncInternalApiDelegate { + public: + // Returns sync's representation of the local device info. + // Return value is an empty scoped_ptr if the device info is unavailable. + virtual scoped_ptr<DeviceInfo> GetLocalDeviceInfo() const = 0; + + // Used for creation of the machine tag for this local session. + virtual std::string GetCacheGuid() const = 0; + }; + + SessionsSyncManager(Profile* profile, + scoped_ptr<SyncPrefs> sync_prefs, + SyncInternalApiDelegate* delegate); + virtual ~SessionsSyncManager(); + + // A local navigation event took place that affects the synced session + // for this instance of Chrome. + void OnLocalTabModified(const SyncedTabDelegate& modified_tab, + syncer::SyncError* error); + + // When a Browser window is opened, we want to know so we can make sure our + // bookkeeping of open windows / sessions on this device is up-to-date. + void OnBrowserOpened(); + + // A local navigation occurred that triggered updates to favicon data for + // each URL in |updated_page_urls|. This is routed through Sessions Sync so + // that we can filter (exclude) favicon updates for pages that aren't + // currently part of the set of local open tabs, and pass relevant updates + // on to FaviconCache for out-of-band favicon syncing. + void ForwardRelevantFaviconUpdatesToFaviconCache( + const std::set<GURL>& updated_favicon_page_urls); + + // Returns the tag used to uniquely identify this machine's session in the + // sync model. + const std::string& current_machine_tag() const { + DCHECK(!current_machine_tag_.empty()); + return current_machine_tag_; + } + + // Builds a list of all foreign sessions. Caller does NOT own SyncedSession + // objects. + // Returns true if foreign sessions were found, false otherwise. + bool GetAllForeignSessions(std::vector<const SyncedSession*>* sessions); + + // If a valid favicon for the page at |url| is found, fills |favicon_png| with + // the png-encoded image and returns true. Else, returns false. + bool GetSyncedFaviconForPageURL( + const std::string& page_url, + scoped_refptr<base::RefCountedMemory>* favicon_png) const; + + // syncer::SyncableService implementation. + virtual syncer::SyncMergeResult MergeDataAndStartSyncing( + syncer::ModelType type, + const syncer::SyncDataList& initial_sync_data, + scoped_ptr<syncer::SyncChangeProcessor> sync_processor, + scoped_ptr<syncer::SyncErrorFactory> error_handler) OVERRIDE; + virtual void StopSyncing(syncer::ModelType type) OVERRIDE; + virtual syncer::SyncDataList GetAllSyncData( + syncer::ModelType type) const OVERRIDE; + virtual syncer::SyncError ProcessSyncChanges( + const tracked_objects::Location& from_here, + const syncer::SyncChangeList& change_list) OVERRIDE; + + private: + FRIEND_TEST_ALL_PREFIXES(SessionsSyncManagerTest, DeleteForeignSession); + FRIEND_TEST_ALL_PREFIXES(SessionsSyncManagerTest, + WriteForeignSessionToNodeTabsFirst); + FRIEND_TEST_ALL_PREFIXES(SessionsSyncManagerTest, + WriteForeignSessionToNodeMissingTabs); + FRIEND_TEST_ALL_PREFIXES(SessionsSyncManagerTest, Favicons); + FRIEND_TEST_ALL_PREFIXES(SessionsSyncManagerTest, + SaveUnassociatedNodesForReassociation); + FRIEND_TEST_ALL_PREFIXES(SessionsSyncManagerTest, MergeDeletesCorruptNode); + + void InitializeCurrentMachineTag(); + + // Load and add window or tab data for a foreign session to our internal + // tracking. + void UpdateTrackerWithForeignSession( + const sync_pb::SessionSpecifics& specifics, + const base::Time& modification_time); + + // Returns true if |sync_data| contained a header node for the current + // machine, false otherwise. + bool InitFromSyncModel(const syncer::SyncDataList& sync_data, + syncer::SyncChangeList* new_changes); + + // Helper to construct a deletion SyncChange for a *tab node*. + // Caller should check IsValid() on the returned change, as it's possible + // this node could not be deleted. + syncer::SyncChange TombstoneTab(const sync_pb::SessionSpecifics& tab); + + // Helper method to load the favicon data from the tab specifics. If the + // favicon is valid, stores the favicon data into the favicon cache. + void RefreshFaviconVisitTimesFromForeignTab( + const sync_pb::SessionTab& tab, const base::Time& modification_time); + + // Removes a foreign session from our internal bookkeeping. + // Returns true if the session was found and deleted, false if no data was + // found for that session. This will *NOT* trigger sync deletions. See + // DeleteForeignSession below. + bool DisassociateForeignSession(const std::string& foreign_session_tag); + + // Delete a foreign session and all its sync data. + // |change_output| *must* be provided as a link to the SyncChange pipeline + // that exists in the caller's context. This function will append necessary + // changes for processing later. + void DeleteForeignSession(const std::string& tag, + syncer::SyncChangeList* change_output); + + // Used to populate a session header from the session specifics header + // provided. + void PopulateSessionHeaderFromSpecifics( + const sync_pb::SessionHeader& header_specifics, + base::Time mtime, + SyncedSession* session_header) const; + + // Builds |session_window| from the session specifics window + // provided and updates the SessionTracker with foreign session data created. + void BuildSyncedSessionFromSpecifics( + const std::string& session_tag, + const sync_pb::SessionWindow& specifics, + base::Time mtime, + SessionWindow* session_window); + + // Resync local window information. Updates the local sessions header node + // with the status of open windows and the order of tabs they contain. Should + // only be called for changes that affect a window, not a change within a + // single tab. + // + // RELOAD_TABS will additionally cause a resync of all tabs (same as calling + // AssociateTabs with a vector of all tabs). + // + // Returns: false if the local session's sync nodes were deleted and + // reassociation is necessary, true otherwise. + // + // |change_output| *must* be provided as a link to the SyncChange pipeline + // that exists in the caller's context. This function will append necessary + // changes for processing later. + enum ReloadTabsOption { + RELOAD_TABS, + DONT_RELOAD_TABS + }; + void AssociateWindows(ReloadTabsOption option, + syncer::SyncChangeList* change_output); + + // Loads and reassociates the local tabs referenced in |tabs|. + // |change_output| *must* be provided as a link to the SyncChange pipeline + // that exists in the caller's context. This function will append necessary + // changes for processing later. + void AssociateTab(SyncedTabDelegate* const tab, + syncer::SyncChangeList* change_output); + + // Control which local tabs we're interested in syncing. + // Ensures the profile matches sync's profile and that the tab has valid + // entries. + bool ShouldSyncTab(const SyncedTabDelegate& tab) const; + + SyncedSessionTracker session_tracker_; + FaviconCache favicon_cache_; + + // Pool of used/available sync nodes associated with local tabs. + TabNodePool2 local_tab_pool_; + + scoped_ptr<SyncPrefs> sync_prefs_; + + scoped_ptr<syncer::SyncErrorFactory> error_handler_; + scoped_ptr<syncer::SyncChangeProcessor> sync_processor_; + + const SyncInternalApiDelegate* const delegate_; + + // Unique client tag. + std::string current_machine_tag_; + + // User-visible machine name. + std::string current_session_name_; + + // SyncID for the sync node containing all the window information for this + // client. + int local_session_header_node_id_; + + DISALLOW_COPY_AND_ASSIGN(SessionsSyncManager); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_SESSIONS2_SESSIONS_SYNC_MANAGER_H_ diff --git a/chrome/browser/sync/sessions2/sessions_sync_manager_unittest.cc b/chrome/browser/sync/sessions2/sessions_sync_manager_unittest.cc new file mode 100644 index 0000000..ac57edf --- /dev/null +++ b/chrome/browser/sync/sessions2/sessions_sync_manager_unittest.cc @@ -0,0 +1,369 @@ +// Copyright 2013 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 "chrome/browser/sync/sessions2/sessions_sync_manager.h" + +#include "base/run_loop.h" +#include "chrome/browser/sync/glue/device_info.h" +#include "chrome/browser/sync/glue/session_sync_test_helper.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "sync/api/sync_error_factory_mock.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using syncer::SyncData; + +namespace browser_sync { + +namespace { + +class TestSyncProcessorStub : public syncer::SyncChangeProcessor { + public: + explicit TestSyncProcessorStub(syncer::SyncChangeList* output) + : output_(output) {} + virtual syncer::SyncError ProcessSyncChanges( + const tracked_objects::Location& from_here, + const syncer::SyncChangeList& change_list) OVERRIDE { + if (output_) + output_->assign(change_list.begin(), change_list.end()); + return syncer::SyncError(); + } + + virtual syncer::SyncDataList GetAllSyncData(syncer::ModelType type) + const OVERRIDE { + return syncer::SyncDataList(); + } + private: + syncer::SyncChangeList* output_; +}; + +} // namespace + +class SessionsSyncManagerTest + : public testing::Test, + public SessionsSyncManager::SyncInternalApiDelegate { + public: + SessionsSyncManagerTest() {} + + virtual void SetUp() OVERRIDE { + profile_.reset(new TestingProfile()); + manager_.reset(new SessionsSyncManager( + profile_.get(), + scoped_ptr<SyncPrefs>(new SyncPrefs(profile_->GetPrefs())), + this)); + } + + virtual void TearDown() OVERRIDE { + helper()->Reset(); + manager_.reset(); + profile_.reset(); + } + + virtual scoped_ptr<DeviceInfo> GetLocalDeviceInfo() const OVERRIDE { + return scoped_ptr<DeviceInfo>( + new DeviceInfo(GetCacheGuid(), + "Wayne Gretzky's Hacking Box", + "Chromium 10k", + "Chrome 10k", + sync_pb::SyncEnums_DeviceType_TYPE_LINUX)); + } + + virtual std::string GetCacheGuid() const OVERRIDE { + return "cache_guid"; + } + + SessionsSyncManager* manager() { return manager_.get(); } + SessionSyncTestHelper* helper() { return &helper_; } + + void InitWithSyncDataTakeOutput(const syncer::SyncDataList& initial_data, + syncer::SyncChangeList* output) { + syncer::SyncMergeResult result = manager_->MergeDataAndStartSyncing( + syncer::SESSIONS, initial_data, + scoped_ptr<syncer::SyncChangeProcessor>( + new TestSyncProcessorStub(output)), + scoped_ptr<syncer::SyncErrorFactory>( + new syncer::SyncErrorFactoryMock())); + EXPECT_FALSE(result.error().IsSet()); + } + + void InitWithNoSyncData() { + InitWithSyncDataTakeOutput(syncer::SyncDataList(), NULL); + } + + syncer::SyncChangeList* FilterOutLocalHeaderCreation( + syncer::SyncChangeList* list) { + syncer::SyncChangeList::iterator it = list->begin(); + bool found = false; + for (; it != list->end(); ++it) { + if (it->sync_data().GetTag() == manager_->current_machine_tag()) { + EXPECT_EQ(syncer::SyncChange::ACTION_ADD, it->change_type()); + list->erase(it); + found = true; + break; + } + } + EXPECT_TRUE(found); + return list; + } + + private: + content::TestBrowserThreadBundle thread_bundle_; + scoped_ptr<Profile> profile_; + scoped_ptr<SessionsSyncManager> manager_; + SessionSyncTestHelper helper_; +}; + +TEST_F(SessionsSyncManagerTest, MergeLocalSession) { + syncer::SyncChangeList out; + InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); + EXPECT_FALSE(manager()->current_machine_tag().empty()); + + EXPECT_EQ(1U, out.size()); + EXPECT_TRUE(out[0].IsValid()); + EXPECT_EQ(syncer::SyncChange::ACTION_ADD, out[0].change_type()); + const SyncData data(out[0].sync_data()); + EXPECT_EQ(manager()->current_machine_tag(), data.GetTag()); + const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session()); + EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag()); + EXPECT_TRUE(specifics.has_header()); + const sync_pb::SessionHeader& header_s = specifics.header(); + EXPECT_TRUE(header_s.has_device_type()); + EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name()); + EXPECT_EQ(0, header_s.window_size()); + + // Now take that header node and feed it in as input. + SyncData d(SyncData::CreateRemoteData(1, data.GetSpecifics(), base::Time())); + syncer::SyncDataList in(&d, &d + 1); + out.clear(); + TearDown(); + SetUp(); + InitWithSyncDataTakeOutput(in, &out); + EXPECT_TRUE(out.empty()); +} + +TEST_F(SessionsSyncManagerTest, MergeWithInitialForeignSession) { + std::string tag = "tag1"; + + SessionID::id_type n1[] = {5, 10, 13, 17}; + std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1)); + std::vector<sync_pb::SessionSpecifics> tabs1; + sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( + tag, tab_list1, &tabs1)); + // Add a second window. + SessionID::id_type n2[] = {7, 15, 18, 20}; + std::vector<SessionID::id_type> tab_list2(n2, n2 + arraysize(n2)); + helper()->AddWindowSpecifics(1, tab_list2, &meta); + + // Set up initial data. + syncer::SyncDataList initial_data; + sync_pb::EntitySpecifics entity; + entity.mutable_session()->CopyFrom(meta); + initial_data.push_back(SyncData::CreateRemoteData(1, entity, base::Time())); + for (size_t i = 0; i < tabs1.size(); i++) { + sync_pb::EntitySpecifics entity; + entity.mutable_session()->CopyFrom(tabs1[i]); + initial_data.push_back(SyncData::CreateRemoteData( + i + 2, entity, base::Time())); + } + for (size_t i = 0; i < tab_list2.size(); ++i) { + sync_pb::EntitySpecifics entity; + helper()->BuildTabSpecifics(tag, 0, tab_list2[i], + entity.mutable_session()); + initial_data.push_back( + SyncData::CreateRemoteData(i + 10, entity, base::Time())); + } + + syncer::SyncChangeList output; + InitWithSyncDataTakeOutput(initial_data, &output); + EXPECT_TRUE(FilterOutLocalHeaderCreation(&output)->empty()); + + std::vector<const SyncedSession*> foreign_sessions; + ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); + std::vector<std::vector<SessionID::id_type> > session_reference; + session_reference.push_back(tab_list1); + session_reference.push_back(tab_list2); + helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); +} + +TEST_F(SessionsSyncManagerTest, DeleteForeignSession) { + InitWithNoSyncData(); + std::string tag = "tag1"; + syncer::SyncChangeList changes; + + std::vector<const SyncedSession*> foreign_sessions; + ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions)); + manager()->DeleteForeignSession(tag, &changes); + ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions)); + EXPECT_TRUE(changes.empty()); + + // Fill an instance of session specifics with a foreign session's data. + std::vector<sync_pb::SessionSpecifics> tabs; + SessionID::id_type n1[] = {5, 10, 13, 17}; + std::vector<SessionID::id_type> tab_nums1(n1, n1 + arraysize(n1)); + sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( + tag, tab_nums1, &tabs)); + + // Update associator with the session's meta node, window, and tabs. + manager()->UpdateTrackerWithForeignSession(meta, base::Time()); + for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs.begin(); + iter != tabs.end(); ++iter) { + manager()->UpdateTrackerWithForeignSession(*iter, base::Time()); + } + ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); + ASSERT_EQ(1U, foreign_sessions.size()); + + // Now delete the foreign session. + manager()->DeleteForeignSession(tag, &changes); + EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions)); + + EXPECT_EQ(5U, changes.size()); + std::set<std::string> expected_tags(&tag, &tag + 1); + for (int i = 0; i < 5; i++) + expected_tags.insert(TabNodePool2::TabIdToTag(tag, i)); + + for (int i = 0; i < 5; i++) { + SCOPED_TRACE(changes[i].ToString()); + EXPECT_TRUE(changes[i].IsValid()); + EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, changes[i].change_type()); + EXPECT_TRUE(changes[i].sync_data().IsValid()); + EXPECT_EQ(1U, expected_tags.erase(changes[i].sync_data().GetTag())); + } +} + +// Write a foreign session to a node, with the tabs arriving first, and then +// retrieve it. +TEST_F(SessionsSyncManagerTest, WriteForeignSessionToNodeTabsFirst) { + InitWithNoSyncData(); + + // 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<sync_pb::SessionSpecifics> tabs1; + std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1)); + sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( + tag, tab_list1, &tabs1)); + + // Add tabs for first window. + for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin(); + iter != tabs1.end(); ++iter) { + manager()->UpdateTrackerWithForeignSession(*iter, base::Time()); + } + // Update associator with the session's meta node containing one window. + manager()->UpdateTrackerWithForeignSession(meta, base::Time()); + + // Check that the foreign session was associated and retrieve the data. + std::vector<const SyncedSession*> foreign_sessions; + ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); + ASSERT_EQ(1U, foreign_sessions.size()); + std::vector<std::vector<SessionID::id_type> > 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(SessionsSyncManagerTest, WriteForeignSessionToNodeMissingTabs) { + InitWithNoSyncData(); + + // 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<sync_pb::SessionSpecifics> tabs1; + std::vector<SessionID::id_type> 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<SessionID::id_type> tab_list2( + tab_nums2, tab_nums2 + arraysize(tab_nums2)); + helper()->AddWindowSpecifics(1, tab_list2, &meta); + std::vector<sync_pb::SessionSpecifics> 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. + manager()->UpdateTrackerWithForeignSession(meta, base::Time()); + // Add tabs for first window. + for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin(); + iter != tabs1.end(); ++iter) { + manager()->UpdateTrackerWithForeignSession(*iter, base::Time()); + } + // Add tabs for second window. + for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs2.begin(); + iter != tabs2.end(); ++iter) { + manager()->UpdateTrackerWithForeignSession(*iter, base::Time()); + } + + // Check that the foreign session was associated and retrieve the data. + std::vector<const SyncedSession*> foreign_sessions; + ASSERT_TRUE(manager()->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. + manager()->UpdateTrackerWithForeignSession(meta, base::Time()); + + // Check that the foreign session was associated and retrieve the data. + foreign_sessions.clear(); + ASSERT_TRUE(manager()->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); + helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0])); +} + +// TODO(shashishekhar): "Move this to TabNodePool unittests." +TEST_F(SessionsSyncManagerTest, SaveUnassociatedNodesForReassociation) { + syncer::SyncChangeList changes; + InitWithNoSyncData(); + + std::string local_tag = manager()->current_machine_tag(); + // Create a free node and then dissassociate sessions so that it ends up + // unassociated. + manager()->local_tab_pool_.GetFreeTabNode(&changes); + + // 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. + sync_pb::EntitySpecifics entity(changes[0].sync_data().GetSpecifics()); + entity.mutable_session()->mutable_tab()->set_tab_id(1); + SyncData d(SyncData::CreateRemoteData(1, entity, base::Time())); + syncer::SyncDataList in(&d, &d + 1); + changes.clear(); + TearDown(); + SetUp(); + InitWithSyncDataTakeOutput(in, &changes); + EXPECT_TRUE(FilterOutLocalHeaderCreation(&changes)->empty()); +} + +TEST_F(SessionsSyncManagerTest, MergeDeletesCorruptNode) { + syncer::SyncChangeList changes; + InitWithNoSyncData(); + + std::string local_tag = manager()->current_machine_tag(); + int tab_node_id = manager()->local_tab_pool_.GetFreeTabNode(&changes); + SyncData d(SyncData::CreateRemoteData( + 1, changes[0].sync_data().GetSpecifics(), base::Time())); + syncer::SyncDataList in(&d, &d + 1); + changes.clear(); + TearDown(); + SetUp(); + InitWithSyncDataTakeOutput(in, &changes); + EXPECT_EQ(1U, FilterOutLocalHeaderCreation(&changes)->size()); + EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, changes[0].change_type()); + EXPECT_EQ(TabNodePool2::TabIdToTag(local_tag, tab_node_id), + changes[0].sync_data().GetTag()); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/sessions2/tab_node_pool2.cc b/chrome/browser/sync/sessions2/tab_node_pool2.cc index 70bc225..b0c4dc4b 100644 --- a/chrome/browser/sync/sessions2/tab_node_pool2.cc +++ b/chrome/browser/sync/sessions2/tab_node_pool2.cc @@ -33,10 +33,8 @@ std::string TabNodePool2::TabIdToTag( return base::StringPrintf("%s %d", machine_tag.c_str(), tab_node_id); } -void TabNodePool2::AddTabNode(int tab_node_id, - const SessionID& tab_id) { +void TabNodePool2::AddTabNode(int tab_node_id) { DCHECK_GT(tab_node_id, kInvalidTabNodeID); - DCHECK_GT(tab_id.id(), kInvalidTabID); DCHECK(nodeid_tabid_map_.find(tab_node_id) == nodeid_tabid_map_.end()); unassociated_nodes_.insert(tab_node_id); if (max_used_tab_node_id_ < tab_node_id) @@ -156,7 +154,7 @@ SessionID::id_type TabNodePool2::GetTabIdFromTabNodeId( return kInvalidTabID; } -void TabNodePool2::FreeUnassociatedTabNodes( +void TabNodePool2::DeleteUnassociatedTabNodes( syncer::SyncChangeList* append_changes) { for (std::set<int>::iterator it = unassociated_nodes_.begin(); it != unassociated_nodes_.end();) { diff --git a/chrome/browser/sync/sessions2/tab_node_pool2.h b/chrome/browser/sync/sessions2/tab_node_pool2.h index db3c8b5..700dc10 100644 --- a/chrome/browser/sync/sessions2/tab_node_pool2.h +++ b/chrome/browser/sync/sessions2/tab_node_pool2.h @@ -90,7 +90,7 @@ class TabNodePool2 { // Note: this should only be called when we discover tab sync nodes from // previous sessions, not for freeing tab nodes we created through // GetFreeTabNode (use FreeTabNode below for that). - void AddTabNode(int tab_node_id, const SessionID& tab_id); + void AddTabNode(int tab_node_id); // Returns the tab_id for |tab_node_id| if it is associated else returns // kInvalidTabID. @@ -107,7 +107,7 @@ class TabNodePool2 { // |change_output| *must* be provided. It is the TabNodePool's link to // the SyncChange pipeline that exists in the caller's context. // See FreeTabNode for more detail. - void FreeUnassociatedTabNodes(syncer::SyncChangeList* change_output); + void DeleteUnassociatedTabNodes(syncer::SyncChangeList* change_output); // Clear tab pool. void Clear(); diff --git a/chrome/browser/sync/sessions2/tab_node_pool2_unittest.cc b/chrome/browser/sync/sessions2/tab_node_pool2_unittest.cc index 14942e2..d0a9d63 100644 --- a/chrome/browser/sync/sessions2/tab_node_pool2_unittest.cc +++ b/chrome/browser/sync/sessions2/tab_node_pool2_unittest.cc @@ -4,6 +4,7 @@ #include "chrome/browser/sync/sessions2/tab_node_pool2.h" +#include "base/logging.h" #include "sync/api/sync_change.h" #include "sync/protocol/session_specifics.pb.h" #include "sync/protocol/sync.pb.h" @@ -17,65 +18,68 @@ class SyncTabNodePool2Test : public testing::Test { int GetMaxUsedTabNodeId() const { return pool_.max_used_tab_node_id_; } + void AddFreeTabNodes(size_t size, const int node_ids[]); + TabNodePool2 pool_; }; +void SyncTabNodePool2Test::AddFreeTabNodes( + size_t size, const int node_ids[]) { + for (size_t i = 0; i < size; ++i) { + pool_.free_nodes_pool_.insert(node_ids[i]); + } +} + namespace { TEST_F(SyncTabNodePool2Test, TabNodeIdIncreases) { syncer::SyncChangeList changes; // max_used_tab_node_ always increases. - SessionID session_id; - session_id.set_id(1); - pool_.AddTabNode(10, session_id); + pool_.AddTabNode(10); EXPECT_EQ(10, GetMaxUsedTabNodeId()); - session_id.set_id(2); - pool_.AddTabNode(1, session_id); + pool_.AddTabNode(5); EXPECT_EQ(10, GetMaxUsedTabNodeId()); - session_id.set_id(3); - pool_.AddTabNode(1000, session_id); + pool_.AddTabNode(1000); EXPECT_EQ(1000, GetMaxUsedTabNodeId()); - pool_.ReassociateTabNode(1000, 500); - + pool_.ReassociateTabNode(1000, 1); + pool_.ReassociateTabNode(5, 2); + pool_.ReassociateTabNode(10, 3); // Freeing a tab node does not change max_used_tab_node_id_. pool_.FreeTabNode(1000, &changes); EXPECT_TRUE(changes.empty()); - pool_.FreeUnassociatedTabNodes(&changes); + pool_.FreeTabNode(5, &changes); + EXPECT_TRUE(changes.empty()); + pool_.FreeTabNode(10, &changes); EXPECT_TRUE(changes.empty()); - EXPECT_EQ(1000, GetMaxUsedTabNodeId()); for (int i = 0; i < 3; ++i) { pool_.AssociateTabNode(pool_.GetFreeTabNode(&changes), i + 1); EXPECT_EQ(1000, GetMaxUsedTabNodeId()); } - // We Free() nodes above, so GetFreeTabNode should not need to create. EXPECT_TRUE(changes.empty()); - EXPECT_EQ(1000, GetMaxUsedTabNodeId()); + EXPECT_TRUE(pool_.Empty()); } TEST_F(SyncTabNodePool2Test, OldTabNodesAddAndRemove) { syncer::SyncChangeList changes; // VerifyOldTabNodes are added. - // tab_node_id = 1, tab_id = 1 - SessionID session_id; - session_id.set_id(1); - pool_.AddTabNode(1, session_id); - // tab_node_id = 2, tab_id = 2 - session_id.set_id(2); - pool_.AddTabNode(2, session_id); + pool_.AddTabNode(1); + pool_.AddTabNode(2); EXPECT_EQ(2u, pool_.Capacity()); EXPECT_TRUE(pool_.Empty()); EXPECT_TRUE(pool_.IsUnassociatedTabNode(1)); + EXPECT_TRUE(pool_.IsUnassociatedTabNode(2)); pool_.ReassociateTabNode(1, 2); EXPECT_TRUE(pool_.Empty()); - // Check FreeUnassociatedTabNodes returns the node to free node pool_. - pool_.FreeUnassociatedTabNodes(&changes); + pool_.AssociateTabNode(2, 3); + EXPECT_FALSE(pool_.IsUnassociatedTabNode(1)); + EXPECT_FALSE(pool_.IsUnassociatedTabNode(2)); + pool_.FreeTabNode(2, &changes); EXPECT_TRUE(changes.empty()); // 2 should be returned to free node pool_. EXPECT_EQ(2u, pool_.Capacity()); // Should be able to free 1. pool_.FreeTabNode(1, &changes); - EXPECT_TRUE(changes.empty()); EXPECT_FALSE(pool_.Empty()); EXPECT_TRUE(pool_.Full()); EXPECT_EQ(1, pool_.GetFreeTabNode(&changes)); @@ -86,24 +90,24 @@ TEST_F(SyncTabNodePool2Test, OldTabNodesAddAndRemove) { pool_.AssociateTabNode(2, 1); EXPECT_TRUE(pool_.Empty()); EXPECT_FALSE(pool_.Full()); + EXPECT_FALSE(pool_.Full()); } TEST_F(SyncTabNodePool2Test, OldTabNodesReassociation) { // VerifyOldTabNodes are reassociated correctly. - SessionID session_id; - session_id.set_id(1); - pool_.AddTabNode(4, session_id); - session_id.set_id(2); - pool_.AddTabNode(5, session_id); - session_id.set_id(3); - pool_.AddTabNode(6, session_id); + pool_.AddTabNode(4); + pool_.AddTabNode(5); + pool_.AddTabNode(6); EXPECT_EQ(3u, pool_.Capacity()); EXPECT_TRUE(pool_.Empty()); EXPECT_TRUE(pool_.IsUnassociatedTabNode(4)); pool_.ReassociateTabNode(4, 5); + pool_.AssociateTabNode(5, 6); + pool_.AssociateTabNode(6, 7); // Free 5 and 6. syncer::SyncChangeList changes; - pool_.FreeUnassociatedTabNodes(&changes); + pool_.FreeTabNode(5, &changes); + pool_.FreeTabNode(6, &changes); EXPECT_TRUE(changes.empty()); // 5 and 6 nodes should not be unassociated. EXPECT_FALSE(pool_.IsUnassociatedTabNode(5)); @@ -138,14 +142,8 @@ TEST_F(SyncTabNodePool2Test, Init) { TEST_F(SyncTabNodePool2Test, AddGet) { syncer::SyncChangeList changes; - SessionID session_id; - session_id.set_id(1); - pool_.AddTabNode(5, session_id); - session_id.set_id(2); - pool_.AddTabNode(10, session_id); - pool_.FreeUnassociatedTabNodes(&changes); - EXPECT_FALSE(pool_.Empty()); - EXPECT_TRUE(pool_.Full()); + int free_nodes[] = {5, 10}; + AddFreeTabNodes(2, free_nodes); EXPECT_EQ(2U, pool_.Capacity()); EXPECT_EQ(5, pool_.GetFreeTabNode(&changes)); @@ -223,6 +221,42 @@ TEST_F(SyncTabNodePool2Test, GetFreeTabNodeCreate) { EXPECT_EQ(0, specifics.tab_node_id()); } +TEST_F(SyncTabNodePool2Test, TabPoolFreeNodeLimits) { + // Allocate TabNodePool::kFreeNodesHighWatermark + 1 nodes and verify that + // freeing the last node reduces the free node pool size to + // kFreeNodesLowWatermark. + syncer::SyncChangeList changes; + SessionID session_id; + std::vector<int> used_sync_ids; + for (size_t i = 1; i <= TabNodePool2::kFreeNodesHighWatermark + 1; ++i) { + session_id.set_id(i); + int sync_id = pool_.GetFreeTabNode(&changes); + 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) { + pool_.FreeTabNode(used_sync_ids[i], &changes); + } + + // Except one node all nodes should be in FreeNode pool. + EXPECT_FALSE(pool_.Full()); + EXPECT_FALSE(pool_.Empty()); + // Total capacity = 1 Associated Node + kFreeNodesHighWatermark free node. + EXPECT_EQ(TabNodePool2::kFreeNodesHighWatermark + 1, pool_.Capacity()); + + // Freeing the last sync node should drop the free nodes to + // kFreeNodesLowWatermark. + pool_.FreeTabNode(last_sync_id, &changes); + EXPECT_FALSE(pool_.Empty()); + EXPECT_TRUE(pool_.Full()); + EXPECT_EQ(TabNodePool2::kFreeNodesLowWatermark, pool_.Capacity()); +} + } // namespace } // namespace browser_sync diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 2e7657f..28c49e7 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -2248,6 +2248,8 @@ 'browser/sync/profile_sync_service_observer.h', 'browser/sync/retry_verifier.cc', 'browser/sync/retry_verifier.h', + 'browser/sync/sessions2/sessions_sync_manager.cc', + 'browser/sync/sessions2/sessions_sync_manager.h', 'browser/sync/sessions2/tab_node_pool2.cc', 'browser/sync/sessions2/tab_node_pool2.h', 'browser/sync/sync_global_error.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 5fa3472..5bae30f 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1278,6 +1278,8 @@ 'browser/sync/glue/non_ui_data_type_controller_unittest.cc', 'browser/sync/glue/search_engine_data_type_controller_unittest.cc', 'browser/sync/glue/session_model_associator_unittest.cc', + 'browser/sync/glue/session_sync_test_helper.cc', + 'browser/sync/glue/session_sync_test_helper.h', 'browser/sync/glue/shared_change_processor_mock.cc', 'browser/sync/glue/shared_change_processor_mock.h', 'browser/sync/glue/shared_change_processor_unittest.cc', @@ -1302,6 +1304,7 @@ 'browser/sync/profile_sync_service_unittest.cc', 'browser/sync/profile_sync_test_util.cc', 'browser/sync/profile_sync_test_util.h', + 'browser/sync/sessions2/sessions_sync_manager_unittest.cc', 'browser/sync/sessions2/tab_node_pool2_unittest.cc', 'browser/sync/sync_global_error_unittest.cc', 'browser/sync/sync_prefs_unittest.cc', |