summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/browser_commands_unittest.cc1
-rw-r--r--chrome/browser/dom_ui/foreign_session_handler.cc151
-rw-r--r--chrome/browser/dom_ui/foreign_session_handler.h66
-rw-r--r--chrome/browser/dom_ui/new_tab_ui.cc3
-rw-r--r--chrome/browser/dom_ui/value_helper.cc47
-rw-r--r--chrome/browser/dom_ui/value_helper.h25
-rw-r--r--chrome/browser/sessions/session_restore.cc37
-rw-r--r--chrome/browser/sessions/session_restore.h6
-rw-r--r--chrome/browser/sessions/session_types.cc10
-rw-r--r--chrome/browser/sessions/session_types.h14
-rw-r--r--chrome/browser/sessions/tab_restore_service.h9
-rw-r--r--chrome/browser/sync/abstract_profile_sync_service_test.h28
-rw-r--r--chrome/browser/sync/engine/syncapi.cc24
-rw-r--r--chrome/browser/sync/engine/syncapi.h11
-rw-r--r--chrome/browser/sync/glue/data_type_manager_impl.cc1
-rw-r--r--chrome/browser/sync/glue/session_change_processor.cc132
-rw-r--r--chrome/browser/sync/glue/session_change_processor.h75
-rw-r--r--chrome/browser/sync/glue/session_data_type_controller.cc113
-rw-r--r--chrome/browser/sync/glue/session_data_type_controller.h85
-rw-r--r--chrome/browser/sync/glue/session_model_associator.cc527
-rw-r--r--chrome/browser/sync/glue/session_model_associator.h201
-rw-r--r--chrome/browser/sync/profile_sync_factory.h7
-rw-r--r--chrome/browser/sync/profile_sync_factory_impl.cc24
-rw-r--r--chrome/browser/sync/profile_sync_factory_impl.h4
-rw-r--r--chrome/browser/sync/profile_sync_factory_impl_unittest.cc12
-rw-r--r--chrome/browser/sync/profile_sync_factory_mock.h3
-rw-r--r--chrome/browser/sync/profile_sync_service.cc22
-rw-r--r--chrome/browser/sync/profile_sync_service.h15
-rw-r--r--chrome/browser/sync/profile_sync_service_password_unittest.cc1
-rw-r--r--chrome/browser/sync/profile_sync_service_session_unittest.cc417
-rw-r--r--chrome/browser/sync/profile_sync_service_typed_url_unittest.cc1
-rw-r--r--chrome/browser/sync/sync_setup_flow.cc12
-rw-r--r--chrome/browser/sync/sync_setup_wizard_unittest.cc7
-rw-r--r--chrome/browser/sync/syncable/model_type.cc19
-rw-r--r--chrome/browser/sync/syncable/model_type.h2
-rw-r--r--chrome/browser/views/bookmark_bar_view_unittest.cc4
-rw-r--r--chrome/chrome.gyp2
-rw-r--r--chrome/chrome_browser.gypi10
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/chrome_switches.cc6
-rw-r--r--chrome/common/chrome_switches.h2
-rw-r--r--chrome/common/notification_type.h10
-rw-r--r--chrome/common/pref_names.cc1
-rw-r--r--chrome/common/pref_names.h1
-rw-r--r--chrome/test/browser_with_test_window_test.cc3
-rw-r--r--chrome/test/browser_with_test_window_test.h3
46 files changed, 2112 insertions, 43 deletions
diff --git a/chrome/browser/browser_commands_unittest.cc b/chrome/browser/browser_commands_unittest.cc
index 3ed2adc..1a776b4 100644
--- a/chrome/browser/browser_commands_unittest.cc
+++ b/chrome/browser/browser_commands_unittest.cc
@@ -79,7 +79,6 @@ TEST_F(BrowserCommandsTest, DuplicateTab) {
}
TEST_F(BrowserCommandsTest, BookmarkCurrentPage) {
- ChromeThread ui_loop(ChromeThread::UI, MessageLoop::current());
ChromeThread file_loop(ChromeThread::FILE, MessageLoop::current());
// We use profile() here, since it's a TestingProfile.
profile()->CreateBookmarkModel(true);
diff --git a/chrome/browser/dom_ui/foreign_session_handler.cc b/chrome/browser/dom_ui/foreign_session_handler.cc
new file mode 100644
index 0000000..7e08fae
--- /dev/null
+++ b/chrome/browser/dom_ui/foreign_session_handler.cc
@@ -0,0 +1,151 @@
+// Copyright (c) 2010 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/dom_ui/foreign_session_handler.h"
+
+#include "base/scoped_vector.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/dom_ui/value_helper.h"
+#include "chrome/browser/sessions/session_restore.h"
+#include "chrome/browser/sessions/tab_restore_service.h"
+#include "chrome/browser/sync/engine/syncapi.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/notification_details.h"
+#include "chrome/common/notification_service.h"
+
+namespace browser_sync {
+
+static const int kMaxSessionsToShow = 10;
+
+ForeignSessionHandler::ForeignSessionHandler() {
+ Init();
+}
+
+void ForeignSessionHandler::RegisterMessages() {
+ dom_ui_->RegisterMessageCallback("getForeignSessions",
+ NewCallback(this,
+ &ForeignSessionHandler::HandleGetForeignSessions));
+ dom_ui_->RegisterMessageCallback("reopenForeignSession",
+ NewCallback(this,
+ &ForeignSessionHandler::HandleReopenForeignSession));
+}
+
+void ForeignSessionHandler::Init() {
+ registrar_.Add(this, NotificationType::SYNC_CONFIGURE_DONE,
+ NotificationService::AllSources());
+ registrar_.Add(this, NotificationType::FOREIGN_SESSION_UPDATED,
+ NotificationService::AllSources());
+ registrar_.Add(this, NotificationType::FOREIGN_SESSION_DELETED,
+ NotificationService::AllSources());
+}
+
+void ForeignSessionHandler::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type != NotificationType::SYNC_CONFIGURE_DONE &&
+ type != NotificationType::FOREIGN_SESSION_UPDATED &&
+ type != NotificationType::FOREIGN_SESSION_DELETED) {
+ NOTREACHED();
+ return;
+ }
+ ListValue list_value;
+ HandleGetForeignSessions(&list_value);
+}
+
+SessionModelAssociator* ForeignSessionHandler::GetModelAssociator() {
+ ProfileSyncService* service = dom_ui_->GetProfile()->GetProfileSyncService();
+ if (service == NULL)
+ return NULL;
+ // We only want to set the model associator if there is one, and it is done
+ // syncing sessions.
+ SessionModelAssociator* model_associator = service->
+ GetSessionModelAssociator();
+ if (model_associator == NULL ||
+ !service->ShouldPushChanges()) {
+ return NULL;
+ }
+ return dom_ui_->GetProfile()->GetProfileSyncService()->
+ GetSessionModelAssociator();
+}
+
+void ForeignSessionHandler::HandleGetForeignSessions(const Value* content) {
+ SessionModelAssociator* associator = GetModelAssociator();
+ if (associator)
+ GetForeignSessions(associator);
+}
+
+void ForeignSessionHandler::HandleReopenForeignSession(
+ const Value* content) {
+ // Extract the machine tag and use it to obtain the id for the node we are
+ // looking for. Send it along with a valid associator to OpenForeignSessions.
+ if (content->GetType() == Value::TYPE_LIST) {
+ const ListValue* list_value = static_cast<const ListValue*>(content);
+ Value* list_member;
+ if (list_value->Get(0, &list_member) &&
+ list_member->GetType() == Value::TYPE_STRING) {
+ const StringValue* string_value =
+ static_cast<const StringValue*>(list_member);
+ std::string session_string_value;
+ if (string_value->GetAsString(&session_string_value)) {
+ SessionModelAssociator* associator = GetModelAssociator();
+ if (associator) {
+ int64 id = associator->GetSyncIdFromChromeId(session_string_value);
+ OpenForeignSession(associator, id);
+ }
+ }
+ }
+ }
+}
+
+void ForeignSessionHandler::OpenForeignSession(
+ SessionModelAssociator* associator, int64 id) {
+ // Obtain the session windows for the foreign session.
+ // We don't have a ForeignSessionHandler in off the record mode, so we
+ // expect the ProfileSyncService to exist.
+ sync_api::ReadTransaction trans(dom_ui_->GetProfile()->
+ GetProfileSyncService()->backend()->GetUserShareHandle());
+ ScopedVector<ForeignSession> session;
+ associator->AppendForeignSessionWithID(id, &session.get(), &trans);
+
+ DCHECK(session.size() == 1);
+ std::vector<SessionWindow*> windows = (*session.begin())->windows;
+ SessionRestore::RestoreForeignSessionWindows(dom_ui_->GetProfile(), &windows);
+}
+
+void ForeignSessionHandler::GetForeignSessions(
+ SessionModelAssociator* associator) {
+ ScopedVector<ForeignSession> windows;
+ associator->GetSessionDataFromSyncModel(&windows.get());
+ int added_count = 0;
+ ListValue list_value;
+ for (std::vector<ForeignSession*>::const_iterator i =
+ windows.begin(); i != windows.end() &&
+ added_count < kMaxSessionsToShow; ++i) {
+ ForeignSession* foreign_session = *i;
+ std::vector<TabRestoreService::Entry*> entries;
+ dom_ui_->GetProfile()->GetTabRestoreService()->CreateEntriesFromWindows(
+ &foreign_session->windows, &entries);
+ for (std::vector<TabRestoreService::Entry*>::const_iterator it =
+ entries.begin(); it != entries.end(); ++it) {
+ TabRestoreService::Entry* entry = *it;
+ scoped_ptr<DictionaryValue> value(new DictionaryValue());
+ if (entry->type == TabRestoreService::WINDOW &&
+ ValueHelper::WindowToValue(
+ *static_cast<TabRestoreService::Window*>(entry), value.get())) {
+ // The javascript checks if the session id is a valid session id,
+ // when rendering session information to the new tab page, and it
+ // sends the sessionTag back when we need to restore a session.
+ value->SetString("sessionTag", foreign_session->foreign_tession_tag);
+ value->SetInteger("sessionId", entry->id);
+ list_value.Append(value.release()); // Give ownership to |list_value|.
+ }
+ }
+ added_count++;
+ }
+ dom_ui_->CallJavascriptFunction(L"foreignSessions", list_value);
+}
+
+} // namespace browser_sync
+
diff --git a/chrome/browser/dom_ui/foreign_session_handler.h b/chrome/browser/dom_ui/foreign_session_handler.h
new file mode 100644
index 0000000..220fff3
--- /dev/null
+++ b/chrome/browser/dom_ui/foreign_session_handler.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2010 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_DOM_UI_FOREIGN_SESSION_HANDLER_H_
+#define CHROME_BROWSER_DOM_UI_FOREIGN_SESSION_HANDLER_H_
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "chrome/browser/dom_ui/dom_ui.h"
+#include "chrome/browser/sessions/session_service.h"
+#include "chrome/browser/sync/glue/session_model_associator.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+
+namespace browser_sync {
+
+class ForeignSessionHandler : public DOMMessageHandler,
+ public NotificationObserver {
+ public:
+ // DOMMessageHandler implementation.
+ virtual void RegisterMessages();
+
+ ForeignSessionHandler();
+ virtual ~ForeignSessionHandler() {}
+
+ private:
+ // Used to register ForeignSessionHandler for notifications.
+ void Init();
+
+ // Determines how ForeignSessionHandler will interact with the new tab page.
+ void Observe(NotificationType type, const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // Returns a pointer to the current session model associator or NULL.
+ SessionModelAssociator* GetModelAssociator();
+
+ // Determines whether foreign sessions should be obtained from the sync model.
+ // This is a javascript callback handler, and it is also called when the sync
+ // model has changed and the new tab page needs to reflect the changes.
+ void HandleGetForeignSessions(const Value* content);
+
+ // Helper for reopening a foreign session in a new browser window.
+ void OpenForeignSession(SessionModelAssociator* associator, int64 id);
+
+ // Helper for listing the foreign sessions on the new tab page.
+ void GetForeignSessions(SessionModelAssociator* associator);
+
+ // Determines which session is to be opened, and then calls
+ // OpenForeignSession, to begin the process of opening a new browser window.
+ // This is a javascript callback handler.
+ void HandleReopenForeignSession(const Value* content);
+
+ // The Registrar used to register ForeignSessionHandler for notifications.
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(ForeignSessionHandler);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_DOM_UI_FOREIGN_SESSION_HANDLER_H_
+
diff --git a/chrome/browser/dom_ui/new_tab_ui.cc b/chrome/browser/dom_ui/new_tab_ui.cc
index 7d83e821..6b97778 100644
--- a/chrome/browser/dom_ui/new_tab_ui.cc
+++ b/chrome/browser/dom_ui/new_tab_ui.cc
@@ -21,6 +21,7 @@
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/dom_ui/app_launcher_handler.h"
#include "chrome/browser/dom_ui/dom_ui_theme_source.h"
+#include "chrome/browser/dom_ui/foreign_session_handler.h"
#include "chrome/browser/dom_ui/most_visited_handler.h"
#include "chrome/browser/dom_ui/new_tab_page_sync_handler.h"
#include "chrome/browser/dom_ui/ntp_resource_cache.h"
@@ -465,6 +466,8 @@ NewTabUI::NewTabUI(TabContents* contents)
if (!GetProfile()->IsOffTheRecord()) {
PrefService* pref_service = GetProfile()->GetPrefs();
AddMessageHandler((new ShownSectionsHandler(pref_service))->Attach(this));
+ AddMessageHandler((new browser_sync::ForeignSessionHandler())->
+ Attach(this));
AddMessageHandler((new MostVisitedHandler())->Attach(this));
AddMessageHandler((new RecentlyClosedTabsHandler())->Attach(this));
AddMessageHandler((new MetricsHandler())->Attach(this));
diff --git a/chrome/browser/dom_ui/value_helper.cc b/chrome/browser/dom_ui/value_helper.cc
new file mode 100644
index 0000000..92bee3c
--- /dev/null
+++ b/chrome/browser/dom_ui/value_helper.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2010 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/dom_ui/value_helper.h"
+
+#include "chrome/browser/dom_ui/new_tab_ui.h"
+#include "chrome/common/url_constants.h"
+
+bool ValueHelper::TabToValue(
+ const TabRestoreService::Tab& tab,
+ DictionaryValue* dictionary) {
+ if (tab.navigations.empty())
+ return false;
+
+ const TabNavigation& current_navigation =
+ tab.navigations.at(tab.current_navigation_index);
+ if (current_navigation.virtual_url() == GURL(chrome::kChromeUINewTabURL))
+ return false;
+ NewTabUI::SetURLTitleAndDirection(dictionary, current_navigation.title(),
+ current_navigation.virtual_url());
+ dictionary->SetString("type", "tab");
+ dictionary->SetReal("timestamp", tab.timestamp.ToDoubleT());
+ return true;
+}
+
+bool ValueHelper::WindowToValue(
+ const TabRestoreService::Window& window,
+ DictionaryValue* dictionary) {
+ if (window.tabs.empty()) {
+ NOTREACHED();
+ return false;
+ }
+ scoped_ptr<ListValue> tab_values(new ListValue());
+ for (size_t i = 0; i < window.tabs.size(); ++i) {
+ scoped_ptr<DictionaryValue> tab_value(new DictionaryValue());
+ if (TabToValue(window.tabs[i], tab_value.get()))
+ tab_values->Append(tab_value.release());
+ }
+ if (tab_values->GetSize() == 0)
+ return false;
+ dictionary->SetString("type", "window");
+ dictionary->SetReal("timestamp", window.timestamp.ToDoubleT());
+ dictionary->Set("tabs", tab_values.release());
+ return true;
+}
+
diff --git a/chrome/browser/dom_ui/value_helper.h b/chrome/browser/dom_ui/value_helper.h
new file mode 100644
index 0000000..b6a11777
--- /dev/null
+++ b/chrome/browser/dom_ui/value_helper.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2010 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_DOM_UI_VALUE_HELPER_H_
+#define CHROME_BROWSER_DOM_UI_VALUE_HELPER_H_
+#pragma once
+
+#include "chrome/browser/sessions/tab_restore_service.h"
+
+#include "base/values.h"
+
+// Used to convert TabRestoreService elements to values for JSON processing.
+class ValueHelper {
+ public:
+ static bool TabToValue(const TabRestoreService::Tab& tab,
+ DictionaryValue* dictionary);
+ static bool WindowToValue(const TabRestoreService::Window& window,
+ DictionaryValue* dictionary);
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ValueHelper);
+};
+
+#endif // CHROME_BROWSER_DOM_UI_VALUE_HELPER_H_
+
diff --git a/chrome/browser/sessions/session_restore.cc b/chrome/browser/sessions/session_restore.cc
index a75f7de..9a20e65 100644
--- a/chrome/browser/sessions/session_restore.cc
+++ b/chrome/browser/sessions/session_restore.cc
@@ -298,6 +298,33 @@ class SessionRestoreImpl : public NotificationObserver {
}
}
+ void RestoreForeignSession(std::vector<SessionWindow*>* windows) {
+ tab_loader_.reset(new TabLoader());
+ // Create a browser instance to put the restored tabs in.
+ bool has_tabbed_browser = false;
+ for (std::vector<SessionWindow*>::iterator i = (*windows).begin();
+ i != (*windows).end(); ++i) {
+ Browser* browser = NULL;
+ if (!has_tabbed_browser && (*i)->type == Browser::TYPE_NORMAL)
+ has_tabbed_browser = true;
+ browser = new Browser(static_cast<Browser::Type>((*i)->type),
+ profile_);
+ browser->set_override_bounds((*i)->bounds);
+ browser->set_maximized_state((*i)->is_maximized ?
+ Browser::MAXIMIZED_STATE_MAXIMIZED :
+ Browser::MAXIMIZED_STATE_UNMAXIMIZED);
+ browser->CreateBrowserWindow();
+
+ // Restore and show the browser.
+ const int initial_tab_count = browser->tab_count();
+ RestoreTabsToBrowser(*(*i), browser);
+ ShowBrowser(browser, initial_tab_count,
+ (*i)->selected_tab_index);
+ NotifySessionServiceOfRestoredTabs(browser, initial_tab_count);
+ FinishedTabCreation(true, has_tabbed_browser);
+ }
+ }
+
~SessionRestoreImpl() {
STLDeleteElements(&windows_);
restoring = false;
@@ -614,6 +641,16 @@ void SessionRestore::RestoreSession(Profile* profile,
}
// static
+void SessionRestore::RestoreForeignSessionWindows(Profile* profile,
+ std::vector<SessionWindow*>* windows) {
+ // Create a SessionRestore object to eventually restore the tabs.
+ std::vector<GURL> gurls;
+ SessionRestoreImpl restorer(profile,
+ static_cast<Browser*>(NULL), true, false, true, gurls);
+ restorer.RestoreForeignSession(windows);
+}
+
+// static
void SessionRestore::RestoreSessionSynchronously(
Profile* profile,
const std::vector<GURL>& urls_to_open) {
diff --git a/chrome/browser/sessions/session_restore.h b/chrome/browser/sessions/session_restore.h
index 8ce01d2f..bb08868 100644
--- a/chrome/browser/sessions/session_restore.h
+++ b/chrome/browser/sessions/session_restore.h
@@ -9,6 +9,7 @@
#include <vector>
#include "chrome/browser/history/history.h"
+#include "chrome/browser/sessions/session_types.h"
#include "base/basictypes.h"
class Browser;
@@ -35,6 +36,11 @@ class SessionRestore {
bool always_create_tabbed_browser,
const std::vector<GURL>& urls_to_open);
+ // Specifically used in the restoration of a foreign session. This method
+ // restores the given session windows to a browser.
+ static void RestoreForeignSessionWindows(Profile* profile,
+ std::vector<SessionWindow*>* windows);
+
// Synchronously restores the last session. At least one tabbed browser is
// created, even if there is an error in restoring.
//
diff --git a/chrome/browser/sessions/session_types.cc b/chrome/browser/sessions/session_types.cc
index 6cf193c..cdf77f6 100644
--- a/chrome/browser/sessions/session_types.cc
+++ b/chrome/browser/sessions/session_types.cc
@@ -51,3 +51,13 @@ SessionWindow::SessionWindow()
SessionWindow::~SessionWindow() {
STLDeleteElements(&tabs);
}
+
+// ForeignSession --------------------------------------------------------------
+
+ForeignSession::ForeignSession() : foreign_tession_tag("invalid") {
+}
+
+ForeignSession::~ForeignSession() {
+ STLDeleteElements(&windows);
+}
+
diff --git a/chrome/browser/sessions/session_types.h b/chrome/browser/sessions/session_types.h
index 2ea0757..07dbae4 100644
--- a/chrome/browser/sessions/session_types.h
+++ b/chrome/browser/sessions/session_types.h
@@ -88,7 +88,7 @@ class TabNavigation {
// This is used when determining the selected TabNavigation and only useful
// by BaseSessionService and SessionService.
void set_index(int index) { index_ = index; }
- int index() { return index_; }
+ int index() const { return index_; }
private:
friend class BaseSessionService;
@@ -189,4 +189,16 @@ struct SessionWindow {
DISALLOW_COPY_AND_ASSIGN(SessionWindow);
};
+// Defines a foreign session for session sync. A foreign session is a session
+// on a remote chrome instance.
+struct ForeignSession {
+ ForeignSession();
+ ~ForeignSession();
+
+ // Unique tag for each session.
+ std::string foreign_tession_tag;
+ std::vector<SessionWindow*> windows;
+};
+
#endif // CHROME_BROWSER_SESSIONS_SESSION_TYPES_H_
+
diff --git a/chrome/browser/sessions/tab_restore_service.h b/chrome/browser/sessions/tab_restore_service.h
index 31f22e4..e12b9d1 100644
--- a/chrome/browser/sessions/tab_restore_service.h
+++ b/chrome/browser/sessions/tab_restore_service.h
@@ -175,6 +175,10 @@ class TabRestoreService : public BaseSessionService {
// Max number of entries we'll keep around.
static const size_t kMaxEntries;
+ // Creates and add entries to |entries| for each of the windows in |windows|.
+ void CreateEntriesFromWindows(std::vector<SessionWindow*>* windows,
+ std::vector<Entry*>* entries);
+
protected:
virtual void Save();
@@ -291,11 +295,6 @@ class TabRestoreService : public BaseSessionService {
void OnGotPreviousSession(Handle handle,
std::vector<SessionWindow*>* windows);
- // Creates and add entries to |entries| for each of the windows in |windows|.
- void CreateEntriesFromWindows(
- std::vector<SessionWindow*>* windows,
- std::vector<Entry*>* entries);
-
// Converts a SessionWindow into a Window, returning true on success. We use 0
// as the timestamp here since we do not know when the window/tab was closed.
bool ConvertSessionWindowToWindow(
diff --git a/chrome/browser/sync/abstract_profile_sync_service_test.h b/chrome/browser/sync/abstract_profile_sync_service_test.h
index 24f61df..7f81ca2 100644
--- a/chrome/browser/sync/abstract_profile_sync_service_test.h
+++ b/chrome/browser/sync/abstract_profile_sync_service_test.h
@@ -16,6 +16,7 @@
#include "chrome/browser/sync/glue/autofill_model_associator.h"
#include "chrome/browser/sync/glue/password_model_associator.h"
#include "chrome/browser/sync/glue/preference_model_associator.h"
+#include "chrome/browser/sync/glue/session_model_associator.h"
#include "chrome/browser/sync/glue/typed_url_model_associator.h"
#include "chrome/browser/sync/profile_sync_factory_mock.h"
#include "chrome/browser/sync/protocol/sync.pb.h"
@@ -33,7 +34,6 @@ using sync_api::UserShare;
using syncable::BASE_VERSION;
using syncable::CREATE;
using syncable::DirectoryManager;
-using syncable::ID;
using syncable::IS_DEL;
using syncable::IS_DIR;
using syncable::IS_UNAPPLIED_UPDATE;
@@ -48,13 +48,11 @@ using syncable::UNIQUE_SERVER_TAG;
using syncable::UNITTEST;
using syncable::WriteTransaction;
-class AbstractProfileSyncServiceTest : public testing::Test {
+class ProfileSyncServiceTestHelper {
public:
- AbstractProfileSyncServiceTest()
- : ui_thread_(ChromeThread::UI, &message_loop_) {}
-
- bool CreateRoot(ModelType model_type) {
- UserShare* user_share = service_->backend()->GetUserShareHandle();
+ static bool CreateRoot(ModelType model_type, ProfileSyncService* service,
+ TestIdFactory* ids) {
+ UserShare* user_share = service->backend()->GetUserShareHandle();
DirectoryManager* dir_manager = user_share->dir_manager.get();
ScopedDirLookup dir(dir_manager, user_share->authenticated_name);
@@ -78,6 +76,9 @@ class AbstractProfileSyncServiceTest : public testing::Test {
case syncable::TYPED_URLS:
tag_name = browser_sync::kTypedUrlTag;
break;
+ case syncable::SESSIONS:
+ tag_name = browser_sync::kSessionsTag;
+ break;
default:
return false;
}
@@ -95,13 +96,24 @@ class AbstractProfileSyncServiceTest : public testing::Test {
node.Put(SERVER_VERSION, 20);
node.Put(BASE_VERSION, 20);
node.Put(IS_DEL, false);
- node.Put(ID, ids_.MakeServer(tag_name));
+ node.Put(syncable::ID, ids->MakeServer(tag_name));
sync_pb::EntitySpecifics specifics;
syncable::AddDefaultExtensionValue(model_type, &specifics);
node.Put(SPECIFICS, specifics);
return true;
}
+};
+
+class AbstractProfileSyncServiceTest : public testing::Test {
+ public:
+ AbstractProfileSyncServiceTest()
+ : ui_thread_(ChromeThread::UI, &message_loop_) {}
+
+ bool CreateRoot(ModelType model_type) {
+ return ProfileSyncServiceTestHelper::CreateRoot(model_type,
+ service_.get(), &ids_);
+ }
protected:
diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc
index 82ecfaa..81bdc77 100644
--- a/chrome/browser/sync/engine/syncapi.cc
+++ b/chrome/browser/sync/engine/syncapi.cc
@@ -40,6 +40,7 @@
#include "chrome/browser/sync/protocol/nigori_specifics.pb.h"
#include "chrome/browser/sync/protocol/password_specifics.pb.h"
#include "chrome/browser/sync/protocol/preference_specifics.pb.h"
+#include "chrome/browser/sync/protocol/session_specifics.pb.h"
#include "chrome/browser/sync/protocol/service_constants.h"
#include "chrome/browser/sync/protocol/sync.pb.h"
#include "chrome/browser/sync/protocol/theme_specifics.pb.h"
@@ -299,6 +300,11 @@ const sync_pb::ExtensionSpecifics& BaseNode::GetExtensionSpecifics() const {
return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::extension);
}
+const sync_pb::SessionSpecifics& BaseNode::GetSessionSpecifics() const {
+ DCHECK(GetModelType() == syncable::SESSIONS);
+ return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::session);
+}
+
syncable::ModelType BaseNode::GetModelType() const {
return GetEntry()->GetModelType();
}
@@ -403,6 +409,14 @@ void WriteNode::SetThemeSpecifics(
PutThemeSpecificsAndMarkForSyncing(new_value);
}
+
+void WriteNode::SetSessionSpecifics(
+ const sync_pb::SessionSpecifics& new_value) {
+ DCHECK(GetModelType() == syncable::SESSIONS);
+ PutSessionSpecificsAndMarkForSyncing(new_value);
+}
+
+
void WriteNode::PutPasswordSpecificsAndMarkForSyncing(
const sync_pb::PasswordSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
@@ -457,6 +471,15 @@ void WriteNode::PutExtensionSpecificsAndMarkForSyncing(
PutSpecificsAndMarkForSyncing(entity_specifics);
}
+
+void WriteNode::PutSessionSpecificsAndMarkForSyncing(
+ const sync_pb::SessionSpecifics& new_value) {
+ sync_pb::EntitySpecifics entity_specifics;
+ entity_specifics.MutableExtension(sync_pb::session)->CopyFrom(new_value);
+ PutSpecificsAndMarkForSyncing(entity_specifics);
+}
+
+
void WriteNode::PutSpecificsAndMarkForSyncing(
const sync_pb::EntitySpecifics& specifics) {
// Skip redundant changes.
@@ -1888,7 +1911,6 @@ void SyncManager::SyncInternal::HandleChannelEvent(const SyncerEvent& event) {
// Notifications are sent at the end of every sync cycle, regardless of
// whether we should sync again.
if (event.what_happened == SyncerEvent::SYNC_CYCLE_ENDED) {
-
ModelSafeRoutingInfo enabled_types;
registrar_->GetModelSafeRoutingInfo(&enabled_types);
if (enabled_types.count(syncable::PASSWORDS) > 0) {
diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h
index ec28386..f748106 100644
--- a/chrome/browser/sync/engine/syncapi.h
+++ b/chrome/browser/sync/engine/syncapi.h
@@ -79,6 +79,7 @@ class AutofillSpecifics;
class BookmarkSpecifics;
class EntitySpecifics;
class ExtensionSpecifics;
+class SessionSpecifics;
class NigoriSpecifics;
class PasswordSpecifics;
class PreferenceSpecifics;
@@ -208,6 +209,10 @@ class BaseNode {
// data. Can only be called if GetModelType() == EXTENSIONS.
const sync_pb::ExtensionSpecifics& GetExtensionSpecifics() const;
+ // Getter specific to the SESSIONS datatype. Returns protobuf
+ // data. Can only be called if GetModelType() == SESSIONS.
+ const sync_pb::SessionSpecifics& GetSessionSpecifics() const;
+
// Returns the local external ID associated with the node.
int64 GetExternalId() const;
@@ -353,6 +358,10 @@ class WriteNode : public BaseNode {
// Should only be called if GetModelType() == EXTENSIONS.
void SetExtensionSpecifics(const sync_pb::ExtensionSpecifics& specifics);
+ // Set the session specifics (windows, tabs, navigations etc.).
+ // Should only be called if GetModelType() == SESSIONS.
+ void SetSessionSpecifics(const sync_pb::SessionSpecifics& specifics);
+
// Implementation of BaseNode's abstract virtual accessors.
virtual const syncable::Entry* GetEntry() const;
@@ -390,6 +399,8 @@ class WriteNode : public BaseNode {
const sync_pb::TypedUrlSpecifics& new_value);
void PutExtensionSpecificsAndMarkForSyncing(
const sync_pb::ExtensionSpecifics& new_value);
+ void PutSessionSpecificsAndMarkForSyncing(
+ const sync_pb::SessionSpecifics& new_value);
void PutSpecificsAndMarkForSyncing(
const sync_pb::EntitySpecifics& specifics);
diff --git a/chrome/browser/sync/glue/data_type_manager_impl.cc b/chrome/browser/sync/glue/data_type_manager_impl.cc
index 5da617f..d170ec3 100644
--- a/chrome/browser/sync/glue/data_type_manager_impl.cc
+++ b/chrome/browser/sync/glue/data_type_manager_impl.cc
@@ -26,6 +26,7 @@ static const syncable::ModelType kStartOrder[] = {
syncable::THEMES,
syncable::TYPED_URLS,
syncable::PASSWORDS,
+ syncable::SESSIONS,
};
// Comparator used when sorting data type controllers.
diff --git a/chrome/browser/sync/glue/session_change_processor.cc b/chrome/browser/sync/glue/session_change_processor.cc
new file mode 100644
index 0000000..8ea64b1
--- /dev/null
+++ b/chrome/browser/sync/glue/session_change_processor.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2010 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_change_processor.h"
+
+#include <sstream>
+#include <string>
+
+#include "base/logging.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/sync/engine/syncapi.h"
+#include "chrome/browser/sync/glue/session_model_associator.h"
+#include "chrome/common/notification_details.h"
+#include "chrome/common/notification_source.h"
+
+namespace browser_sync {
+
+SessionChangeProcessor::SessionChangeProcessor(
+ UnrecoverableErrorHandler* error_handler,
+ SessionModelAssociator* session_model_associator)
+ : ChangeProcessor(error_handler),
+ session_model_associator_(session_model_associator),
+ profile_(NULL) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(error_handler);
+ DCHECK(session_model_associator_);
+}
+
+SessionChangeProcessor::~SessionChangeProcessor() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+}
+
+void SessionChangeProcessor::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(running());
+ DCHECK(profile_);
+ switch (type.value) {
+ case NotificationType::SESSION_SERVICE_SAVED: {
+ std::string tag = session_model_associator_->GetCurrentMachineTag();
+ DCHECK_EQ(Source<Profile>(source).ptr(), profile_);
+ LOG(INFO) << "Got change notification of type " << type.value
+ << " for session " << tag;
+ session_model_associator_->UpdateSyncModelDataFromClient();
+ break;
+ }
+ default:
+ LOG(DFATAL) << "Received unexpected notification of type "
+ << type.value;
+ break;
+ }
+}
+
+void SessionChangeProcessor::ApplyChangesFromSyncModel(
+ const sync_api::BaseTransaction* trans,
+ const sync_api::SyncManager::ChangeRecord* changes,
+ int change_count) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ if (!running()) {
+ return;
+ }
+ for (int i = 0; i < change_count; ++i) {
+ const sync_api::SyncManager::ChangeRecord& change = changes[i];
+ switch (change.action) {
+ case sync_api::SyncManager::ChangeRecord::ACTION_ADD:
+ UpdateModel(trans, change, true);
+ break;
+ case sync_api::SyncManager::ChangeRecord::ACTION_UPDATE:
+ UpdateModel(trans, change, true);
+ break;
+ case sync_api::SyncManager::ChangeRecord::ACTION_DELETE:
+ UpdateModel(trans, change, false);
+ break;
+ }
+ }
+}
+
+void SessionChangeProcessor::UpdateModel(const sync_api::BaseTransaction* trans,
+ const sync_api::SyncManager::ChangeRecord& change, bool associate) {
+ sync_api::ReadNode node(trans);
+ if (!node.InitByIdLookup(change.id)) {
+ std::stringstream error;
+ error << "Session node lookup failed for change " << change.id
+ << " of action type " << change.action;
+ error_handler()->OnUnrecoverableError(FROM_HERE, error.str());
+ return;
+ }
+ DCHECK_EQ(node.GetModelType(), syncable::SESSIONS);
+ StopObserving();
+ if (associate) {
+ session_model_associator_->Associate(
+ (const sync_pb::SessionSpecifics *) NULL, node.GetId());
+ } else {
+ session_model_associator_->Disassociate(node.GetId());
+ }
+ StartObserving();
+}
+
+void SessionChangeProcessor::StartImpl(Profile* profile) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(profile);
+ DCHECK(profile_ == NULL);
+ profile_ = profile;
+ StartObserving();
+}
+
+void SessionChangeProcessor::StopImpl() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ StopObserving();
+ profile_ = NULL;
+}
+
+void SessionChangeProcessor::StartObserving() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(profile_);
+ LOG(INFO) << "Observing SESSION_SERVICE_SAVED";
+ notification_registrar_.Add(
+ this, NotificationType::SESSION_SERVICE_SAVED,
+ Source<Profile>(profile_));
+}
+
+void SessionChangeProcessor::StopObserving() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(profile_);
+ LOG(INFO) << "removing observation of all notifications";
+ notification_registrar_.RemoveAll();
+}
+
+} // namespace browser_sync
+
diff --git a/chrome/browser/sync/glue/session_change_processor.h b/chrome/browser/sync/glue/session_change_processor.h
new file mode 100644
index 0000000..549c6df
--- /dev/null
+++ b/chrome/browser/sync/glue/session_change_processor.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2010 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_CHANGE_PROCESSOR_H_
+#define CHROME_BROWSER_SYNC_GLUE_SESSION_CHANGE_PROCESSOR_H_
+#pragma once
+
+#include <vector>
+#include "base/basictypes.h"
+#include "chrome/browser/sessions/session_backend.h"
+#include "chrome/browser/sessions/session_service.h"
+#include "chrome/browser/sync/engine/syncapi.h"
+#include "chrome/browser/sync/glue/change_processor.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_type.h"
+#include "chrome/common/notification_registrar.h"
+
+class NotificationDetails;
+class NotificationSource;
+class Profile;
+
+namespace browser_sync {
+
+class SessionModelAssociator;
+class UnrecoverableErrorHandler;
+
+// This class is responsible for taking changes from the
+// SessionService and applying them to the sync_api 'syncable'
+// model, and vice versa. All operations and use of this class are
+// from the UI thread.
+class SessionChangeProcessor : public ChangeProcessor,
+ public NotificationObserver {
+ public:
+ // Does not take ownership of either argument.
+ SessionChangeProcessor(
+ UnrecoverableErrorHandler* error_handler,
+ SessionModelAssociator* session_model_associator);
+ virtual ~SessionChangeProcessor();
+
+ // NotificationObserver implementation.
+ // BrowserSessionProvider -> sync_api model change application.
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // ChangeProcessor implementation.
+ // sync_api model -> BrowserSessionProvider change application.
+ virtual void ApplyChangesFromSyncModel(
+ const sync_api::BaseTransaction* trans,
+ const sync_api::SyncManager::ChangeRecord* changes,
+ int change_count);
+
+ protected:
+ // ChangeProcessor implementation.
+ virtual void StartImpl(Profile* profile);
+ virtual void StopImpl();
+ private:
+ void StartObserving();
+ void StopObserving();
+ void UpdateModel(const sync_api::BaseTransaction* trans,
+ const sync_api::SyncManager::ChangeRecord& change, bool associate);
+ SessionModelAssociator* session_model_associator_;
+ NotificationRegistrar notification_registrar_;
+
+ // Owner of the SessionService. Non-NULL iff |running()| is true.
+ Profile* profile_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionChangeProcessor);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_SESSION_CHANGE_PROCESSOR_H_
+
diff --git a/chrome/browser/sync/glue/session_data_type_controller.cc b/chrome/browser/sync/glue/session_data_type_controller.cc
new file mode 100644
index 0000000..57537da
--- /dev/null
+++ b/chrome/browser/sync/glue/session_data_type_controller.cc
@@ -0,0 +1,113 @@
+// Copyright (c) 2010 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 "base/histogram.h"
+#include "base/logging.h"
+#include "base/time.h"
+#include "chrome/browser/chrome_thread.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/profile_sync_service.h"
+#include "chrome/browser/sync/profile_sync_factory.h"
+#include "chrome/browser/sync/unrecoverable_error_handler.h"
+
+namespace browser_sync {
+
+SessionDataTypeController::SessionDataTypeController(
+ ProfileSyncFactory* profile_sync_factory,
+ ProfileSyncService* sync_service)
+ : profile_sync_factory_(profile_sync_factory),
+ sync_service_(sync_service),
+ state_(NOT_RUNNING) {
+ DCHECK(profile_sync_factory);
+ DCHECK(sync_service);
+}
+
+SessionDataTypeController::~SessionDataTypeController() {
+}
+
+void SessionDataTypeController::Start(StartCallback* start_callback) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(start_callback);
+ if (state_ != NOT_RUNNING) {
+ start_callback->Run(BUSY);
+ delete start_callback;
+ return;
+ }
+
+ start_callback_.reset(start_callback);
+
+ ProfileSyncFactory::SyncComponents sync_components =
+ profile_sync_factory_->CreateSessionSyncComponents(sync_service_,
+ this);
+ model_associator_.reset(sync_components.model_associator);
+ change_processor_.reset(sync_components.change_processor);
+
+ bool sync_has_nodes = false;
+ if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) {
+ StartFailed(UNRECOVERABLE_ERROR);
+ return;
+ }
+
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ bool success = model_associator_->AssociateModels();
+ UMA_HISTOGRAM_TIMES("Sync.SessionAssociationTime",
+ base::TimeTicks::Now() - start_time);
+ if (!success) {
+ StartFailed(ASSOCIATION_FAILED);
+ return;
+ }
+
+ sync_service_->ActivateDataType(this, change_processor_.get());
+ state_ = RUNNING;
+ FinishStart(!sync_has_nodes ? OK_FIRST_RUN : OK);
+}
+
+void SessionDataTypeController::Stop() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+
+ if (change_processor_ != NULL)
+ sync_service_->DeactivateDataType(this, change_processor_.get());
+
+ if (model_associator_ != NULL)
+ model_associator_->DisassociateModels();
+
+ change_processor_.reset();
+ model_associator_.reset();
+ start_callback_.reset();
+
+ state_ = NOT_RUNNING;
+}
+
+void SessionDataTypeController::OnUnrecoverableError(
+ const tracked_objects::Location& from_here,
+ const std::string& message) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ UMA_HISTOGRAM_COUNTS("Sync.SessionRunFailures", 1);
+ sync_service_->OnUnrecoverableError(from_here, message);
+}
+
+browser_sync::SessionModelAssociator*
+ SessionDataTypeController::GetModelAssociator() {
+ return static_cast<SessionModelAssociator*>(model_associator_.get());
+}
+
+void SessionDataTypeController::FinishStart(StartResult result) {
+ start_callback_->Run(result);
+ start_callback_.reset();
+}
+
+void SessionDataTypeController::StartFailed(StartResult result) {
+ model_associator_.reset();
+ change_processor_.reset();
+ start_callback_->Run(result);
+ start_callback_.reset();
+ UMA_HISTOGRAM_ENUMERATION("Sync.SessionStartFailures",
+ result,
+ MAX_START_RESULT);
+}
+
+} // namespace browser_sync
+
diff --git a/chrome/browser/sync/glue/session_data_type_controller.h b/chrome/browser/sync/glue/session_data_type_controller.h
new file mode 100644
index 0000000..0dc56eb
--- /dev/null
+++ b/chrome/browser/sync/glue/session_data_type_controller.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2010 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_DATA_TYPE_CONTROLLER_H_
+#define CHROME_BROWSER_SYNC_GLUE_SESSION_DATA_TYPE_CONTROLLER_H_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/sync/glue/data_type_controller.h"
+
+class ProfileSyncFactory;
+class ProfileSyncService;
+
+namespace browser_sync {
+
+class AssociatorInterface;
+class ChangeProcessor;
+
+class SessionDataTypeController : public DataTypeController {
+ public:
+ SessionDataTypeController(
+ ProfileSyncFactory* profile_sync_factory,
+ ProfileSyncService* sync_service);
+ virtual ~SessionDataTypeController();
+
+ // DataTypeController implementation.
+ virtual void Start(StartCallback* start_callback);
+
+ virtual void Stop();
+
+ virtual bool enabled() {
+ return true;
+ }
+
+ virtual syncable::ModelType type() {
+ return syncable::SESSIONS;
+ }
+
+ virtual browser_sync::ModelSafeGroup model_safe_group() {
+ return browser_sync::GROUP_UI;
+ }
+
+ virtual const char* name() const {
+ // For logging only.
+ return "session";
+ }
+
+ virtual State state() {
+ return state_;
+ }
+
+ // UnrecoverableErrorHandler interface.
+ virtual void OnUnrecoverableError(
+ const tracked_objects::Location& from_here,
+ const std::string& message);
+
+SessionModelAssociator* GetModelAssociator();
+
+ private:
+ // Helper method to run the stashed start callback with a given result.
+ void FinishStart(StartResult result);
+
+ // Cleans up state and calls callback when start fails.
+ void StartFailed(StartResult result);
+
+ ProfileSyncFactory* profile_sync_factory_;
+ ProfileSyncService* sync_service_;
+
+ State state_;
+
+ scoped_ptr<StartCallback> start_callback_;
+ scoped_ptr<AssociatorInterface> model_associator_;
+ scoped_ptr<ChangeProcessor> change_processor_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionDataTypeController);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_SESSION_DATA_TYPE_CONTROLLER_H_
+
diff --git a/chrome/browser/sync/glue/session_model_associator.cc b/chrome/browser/sync/glue/session_model_associator.cc
new file mode 100644
index 0000000..f6a6d1f
--- /dev/null
+++ b/chrome/browser/sync/glue/session_model_associator.cc
@@ -0,0 +1,527 @@
+// Copyright (c) 2010 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_model_associator.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/sessions/session_id.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/syncable/syncable.h"
+#include "chrome/common/notification_details.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/url_constants.h"
+
+namespace browser_sync {
+
+namespace {
+
+static const char kNoSessionsFolderError[] =
+ "Server did not create the top-level sessions node. We "
+ "might be running against an out-of-date server.";
+
+} // namespace
+
+SessionModelAssociator::SessionModelAssociator(
+ ProfileSyncService* sync_service) : sync_service_(sync_service) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(sync_service_);
+}
+
+SessionModelAssociator::~SessionModelAssociator() {
+ DCHECK(CalledOnValidThread());
+}
+
+// Sends a notification to ForeignSessionHandler to update the UI, because
+// the session corresponding to the id given has changed state.
+void SessionModelAssociator::Associate(
+ const sync_pb::SessionSpecifics* specifics, int64 sync_id) {
+ DCHECK(CalledOnValidThread());
+ NotificationService::current()->Notify(
+ NotificationType::FOREIGN_SESSION_UPDATED,
+ NotificationService::AllSources(),
+ Details<int64>(&sync_id));
+}
+
+bool SessionModelAssociator::AssociateModels() {
+ DCHECK(CalledOnValidThread());
+ UpdateSyncModelDataFromClient();
+ return true;
+}
+
+bool SessionModelAssociator::ChromeModelHasUserCreatedNodes(
+ bool* has_nodes) {
+ DCHECK(CalledOnValidThread());
+ CHECK(has_nodes);
+ // This is wrong, but this function is unused, anyway.
+ *has_nodes = true;
+ return true;
+}
+
+// Sends a notification to ForeignSessionHandler to update the UI, because
+// the session corresponding to the id given has been deleted.
+void SessionModelAssociator::Disassociate(int64 sync_id) {
+ NotificationService::current()->Notify(
+ NotificationType::FOREIGN_SESSION_DELETED,
+ NotificationService::AllSources(),
+ Details<int64>(&sync_id));
+}
+
+const sync_pb::SessionSpecifics* SessionModelAssociator::
+ GetChromeNodeFromSyncId(int64 sync_id) {
+ sync_api::ReadTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode node(&trans);
+ if (!node.InitByIdLookup(sync_id))
+ return NULL;
+ return new sync_pb::SessionSpecifics(node.GetSessionSpecifics());
+}
+
+bool SessionModelAssociator::GetSyncIdForTaggedNode(const std::string* tag,
+ int64* sync_id) {
+ sync_api::ReadTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode node(&trans);
+ if (!node.InitByClientTagLookup(syncable::SESSIONS, *tag))
+ return false;
+ *sync_id = node.GetId();
+ return true;
+}
+
+int64 SessionModelAssociator::GetSyncIdFromChromeId(std::string id) {
+ sync_api::ReadTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode node(&trans);
+ if (!node.InitByClientTagLookup(syncable::SESSIONS, id))
+ return sync_api::kInvalidId;
+ return node.GetId();
+}
+
+bool SessionModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
+ DCHECK(CalledOnValidThread());
+ CHECK(has_nodes);
+ *has_nodes = false;
+ sync_api::ReadTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode root(&trans);
+ if (!root.InitByTagLookup(kSessionsTag)) {
+ LOG(ERROR) << kNoSessionsFolderError;
+ return false;
+ }
+ // The sync model has user created nodes iff the sessions folder has
+ // any children.
+ *has_nodes = root.GetFirstChildId() != sync_api::kInvalidId;
+ return true;
+}
+
+std::string SessionModelAssociator::GetCurrentMachineTag() {
+ if (current_machine_tag_.empty())
+ InitializeCurrentMachineTag();
+ DCHECK(!current_machine_tag_.empty());
+ return current_machine_tag_;
+}
+
+void SessionModelAssociator::AppendForeignSessionFromSpecifics(
+ const sync_pb::SessionSpecifics* specifics,
+ std::vector<ForeignSession*>* session) {
+ ForeignSession* foreign_session = new ForeignSession();
+ foreign_session->foreign_tession_tag = specifics->session_tag();
+ session->insert(session->end(), foreign_session);
+ for (int i = 0; i < specifics->session_window_size(); i++) {
+ const sync_pb::SessionWindow* window = &specifics->session_window(i);
+ SessionWindow* session_window = new SessionWindow();
+ PopulateSessionWindowFromSpecifics(session_window, window);
+ foreign_session->windows.insert(
+ foreign_session->windows.end(), session_window);
+ }
+}
+
+// Fills the given vector with foreign session windows to restore.
+void SessionModelAssociator::AppendForeignSessionWithID(int64 id,
+ std::vector<ForeignSession*>* session, sync_api::BaseTransaction* trans) {
+ if (id == sync_api::kInvalidId)
+ return;
+ sync_api::ReadNode node(trans);
+ if (!node.InitByIdLookup(id))
+ return;
+ const sync_pb::SessionSpecifics* ref = &node.GetSessionSpecifics();
+ AppendForeignSessionFromSpecifics(ref, session);
+}
+
+void SessionModelAssociator::UpdateSyncModelDataFromClient() {
+ DCHECK(CalledOnValidThread());
+ SessionService::SessionCallback* callback =
+ NewCallback(this, &SessionModelAssociator::OnGotSession);
+ // TODO(jerrica): Stop current race condition, possibly make new method in
+ // session service, which only grabs the windows from memory.
+ GetSessionService()->GetCurrentSession(&consumer_, callback);
+}
+
+bool SessionModelAssociator::GetSessionDataFromSyncModel(
+ std::vector<ForeignSession*>* sessions) {
+ std::vector<const sync_pb::SessionSpecifics*> specifics;
+ DCHECK(CalledOnValidThread());
+ sync_api::ReadTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode root(&trans);
+ if (!root.InitByTagLookup(kSessionsTag)) {
+ LOG(ERROR) << kNoSessionsFolderError;
+ return false;
+ }
+ sync_api::ReadNode current_machine(&trans);
+ int64 current_id = (current_machine.InitByClientTagLookup(syncable::SESSIONS,
+ GetCurrentMachineTag())) ? current_machine.GetId() : sync_api::kInvalidId;
+ // Iterate through the nodes and populate the session model.
+ int64 id = root.GetFirstChildId();
+ while (id != sync_api::kInvalidId) {
+ sync_api::ReadNode sync_node(&trans);
+ if (!sync_node.InitByIdLookup(id)) {
+ LOG(ERROR) << "Failed to fetch sync node for id " << id;
+ return false;
+ }
+ if (id != current_id) {
+ specifics.insert(specifics.end(), &sync_node.GetSessionSpecifics());
+ }
+ id = sync_node.GetSuccessorId();
+ }
+ for (std::vector<const sync_pb::SessionSpecifics*>::const_iterator i =
+ specifics.begin(); i != specifics.end(); ++i) {
+ AppendForeignSessionFromSpecifics(*i, sessions);
+ }
+ return true;
+}
+
+SessionService* SessionModelAssociator::GetSessionService() {
+ DCHECK(sync_service_);
+ Profile* profile = sync_service_->profile();
+ DCHECK(profile);
+ SessionService* sessions_service = profile->GetSessionService();
+ DCHECK(sessions_service);
+ return sessions_service;
+}
+
+void SessionModelAssociator::InitializeCurrentMachineTag() {
+ sync_api::WriteTransaction trans(sync_service_->backend()->
+ GetUserShareHandle());
+ syncable::Directory* dir =
+ trans.GetWrappedWriteTrans()->directory();
+ current_machine_tag_ = "session_sync" + dir->cache_guid();
+}
+
+// See PopulateSessionSpecificsTab for use. May add functionality that includes
+// the state later.
+void SessionModelAssociator::PopulateSessionSpecificsNavigation(
+ const TabNavigation* navigation, sync_pb::TabNavigation* tab_navigation) {
+ tab_navigation->set_index(navigation->index());
+ tab_navigation->set_virtual_url(navigation->virtual_url().spec());
+ tab_navigation->set_referrer(navigation->referrer().spec());
+ tab_navigation->set_title(UTF16ToUTF8(navigation->title()));
+ switch (navigation->transition()) {
+ case PageTransition::LINK:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_LINK);
+ break;
+ case PageTransition::TYPED:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_TYPED);
+ break;
+ case PageTransition::AUTO_BOOKMARK:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_AUTO_BOOKMARK);
+ break;
+ case PageTransition::AUTO_SUBFRAME:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_AUTO_SUBFRAME);
+ break;
+ case PageTransition::MANUAL_SUBFRAME:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_MANUAL_SUBFRAME);
+ break;
+ case PageTransition::GENERATED:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_GENERATED);
+ break;
+ case PageTransition::START_PAGE:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_START_PAGE);
+ break;
+ case PageTransition::FORM_SUBMIT:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_FORM_SUBMIT);
+ break;
+ case PageTransition::RELOAD:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_RELOAD);
+ break;
+ case PageTransition::KEYWORD:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_KEYWORD);
+ break;
+ case PageTransition::KEYWORD_GENERATED:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_KEYWORD_GENERATED);
+ break;
+ case PageTransition::CHAIN_START:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_CHAIN_START);
+ break;
+ case PageTransition::CHAIN_END:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_CHAIN_END);
+ break;
+ case PageTransition::CLIENT_REDIRECT:
+ tab_navigation->set_navigation_qualifier(
+ sync_pb::TabNavigation_PageTransitionQualifier_CLIENT_REDIRECT);
+ break;
+ case PageTransition::SERVER_REDIRECT:
+ tab_navigation->set_navigation_qualifier(
+ sync_pb::TabNavigation_PageTransitionQualifier_SERVER_REDIRECT);
+ break;
+ default:
+ tab_navigation->set_page_transition(
+ sync_pb::TabNavigation_PageTransition_TYPED);
+ }
+}
+
+// See PopulateSessionSpecificsWindow for use.
+void SessionModelAssociator::PopulateSessionSpecificsTab(
+ const SessionTab* tab, sync_pb::SessionTab* session_tab) {
+ session_tab->set_tab_visual_index(tab->tab_visual_index);
+ session_tab->set_current_navigation_index(
+ tab->current_navigation_index);
+ session_tab->set_pinned(tab->pinned);
+ session_tab->set_extension_app_id(tab->extension_app_id);
+ for (std::vector<TabNavigation>::const_iterator i3 =
+ tab->navigations.begin(); i3 != tab->navigations.end(); ++i3) {
+ const TabNavigation navigation = *i3;
+ sync_pb::TabNavigation* tab_navigation =
+ session_tab->add_navigation();
+ PopulateSessionSpecificsNavigation(&navigation, tab_navigation);
+ }
+}
+
+// Called when populating session specifics to send to the sync model, called
+// when associating models, or updating the sync model.
+void SessionModelAssociator::PopulateSessionSpecificsWindow(
+ const SessionWindow* window, sync_pb::SessionWindow* session_window) {
+ session_window->set_selected_tab_index(window->selected_tab_index);
+ if (window->type == 1) {
+ session_window->set_browser_type(
+ sync_pb::SessionWindow_BrowserType_TYPE_NORMAL);
+ } else {
+ session_window->set_browser_type(
+ sync_pb::SessionWindow_BrowserType_TYPE_POPUP);
+ }
+ for (std::vector<SessionTab*>::const_iterator i2 = window->tabs.begin();
+ i2 != window->tabs.end(); ++i2) {
+ const SessionTab* tab = *i2;
+ if (tab->navigations.at(tab->current_navigation_index).virtual_url() ==
+ GURL(chrome::kChromeUINewTabURL)) {
+ continue;
+ }
+ sync_pb::SessionTab* session_tab = session_window->add_session_tab();
+ PopulateSessionSpecificsTab(tab, session_tab);
+ }
+}
+
+bool SessionModelAssociator::WindowHasNoTabsToSync(
+ const SessionWindow* window) {
+ int num_populated = 0;
+ for (std::vector<SessionTab*>::const_iterator i = window->tabs.begin();
+ i != window->tabs.end(); ++i) {
+ const SessionTab* tab = *i;
+ if (tab->navigations.at(tab->current_navigation_index).virtual_url() ==
+ GURL(chrome::kChromeUINewTabURL)) {
+ continue;
+ }
+ num_populated++;
+ }
+ if (num_populated == 0)
+ return true;
+ return false;
+}
+
+void SessionModelAssociator::OnGotSession(int handle,
+ std::vector<SessionWindow*>* windows) {
+ sync_pb::SessionSpecifics session;
+ // Set the tag, then iterate through the vector of windows, extracting the
+ // window data, along with the tabs data and tab navigation data to populate
+ // the session specifics.
+ session.set_session_tag(GetCurrentMachineTag());
+ for (std::vector<SessionWindow*>::const_iterator i = windows->begin();
+ i != windows->end(); ++i) {
+ const SessionWindow* window = *i;
+ if (WindowHasNoTabsToSync(window)) {
+ continue;
+ }
+ sync_pb::SessionWindow* session_window = session.add_session_window();
+ PopulateSessionSpecificsWindow(window, session_window);
+ }
+ bool has_nodes = false;
+ if (!SyncModelHasUserCreatedNodes(&has_nodes))
+ return;
+ if (session.session_window_size() == 0 && has_nodes)
+ return;
+ sync_api::WriteTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode root(&trans);
+ if (!root.InitByTagLookup(kSessionsTag)) {
+ LOG(ERROR) << kNoSessionsFolderError;
+ return;
+ }
+ UpdateSyncModel(&session, &trans, &root);
+}
+
+void SessionModelAssociator::AppendSessionTabNavigation(
+ std::vector<TabNavigation>* navigations,
+ const sync_pb::TabNavigation* navigation) {
+ int index = 0;
+ GURL virtual_url;
+ GURL referrer;
+ string16 title;
+ std::string state;
+ PageTransition::Type transition(PageTransition::LINK);
+ if (navigation->has_index())
+ index = navigation->index();
+ if (navigation->has_virtual_url()) {
+ GURL gurl(navigation->virtual_url());
+ virtual_url = gurl;
+ }
+ if (navigation->has_referrer()) {
+ GURL gurl(navigation->referrer());
+ referrer = gurl;
+ }
+ if (navigation->has_title())
+ title = UTF8ToUTF16(navigation->title());
+ if (navigation->has_page_transition() ||
+ navigation->has_navigation_qualifier()) {
+ switch (navigation->page_transition()) {
+ case sync_pb::TabNavigation_PageTransition_LINK:
+ transition = PageTransition::LINK;
+ break;
+ case sync_pb::TabNavigation_PageTransition_TYPED:
+ transition = PageTransition::TYPED;
+ break;
+ case sync_pb::TabNavigation_PageTransition_AUTO_BOOKMARK:
+ transition = PageTransition::AUTO_BOOKMARK;
+ break;
+ case sync_pb::TabNavigation_PageTransition_AUTO_SUBFRAME:
+ transition = PageTransition::AUTO_SUBFRAME;
+ break;
+ case sync_pb::TabNavigation_PageTransition_MANUAL_SUBFRAME:
+ transition = PageTransition::MANUAL_SUBFRAME;
+ break;
+ case sync_pb::TabNavigation_PageTransition_GENERATED:
+ transition = PageTransition::GENERATED;
+ break;
+ case sync_pb::TabNavigation_PageTransition_START_PAGE:
+ transition = PageTransition::START_PAGE;
+ break;
+ case sync_pb::TabNavigation_PageTransition_FORM_SUBMIT:
+ transition = PageTransition::FORM_SUBMIT;
+ break;
+ case sync_pb::TabNavigation_PageTransition_RELOAD:
+ transition = PageTransition::RELOAD;
+ break;
+ case sync_pb::TabNavigation_PageTransition_KEYWORD:
+ transition = PageTransition::KEYWORD;
+ break;
+ case sync_pb::TabNavigation_PageTransition_KEYWORD_GENERATED:
+ transition = PageTransition::KEYWORD_GENERATED;
+ break;
+ case sync_pb::TabNavigation_PageTransition_CHAIN_START:
+ transition = sync_pb::TabNavigation_PageTransition_CHAIN_START;
+ break;
+ case sync_pb::TabNavigation_PageTransition_CHAIN_END:
+ transition = PageTransition::CHAIN_END;
+ break;
+ default:
+ switch (navigation->navigation_qualifier()) {
+ case sync_pb::
+ TabNavigation_PageTransitionQualifier_CLIENT_REDIRECT:
+ transition = PageTransition::CLIENT_REDIRECT;
+ break;
+ case sync_pb::
+ TabNavigation_PageTransitionQualifier_SERVER_REDIRECT:
+ transition = PageTransition::SERVER_REDIRECT;
+ break;
+ default:
+ transition = PageTransition::TYPED;
+ }
+ }
+ }
+ TabNavigation tab_navigation(index, virtual_url, referrer, title, state,
+ transition);
+ navigations->insert(navigations->end(), tab_navigation);
+}
+
+void SessionModelAssociator::PopulateSessionTabFromSpecifics(
+ SessionTab* session_tab,
+ const sync_pb::SessionTab* tab, SessionID id) {
+ session_tab->window_id = id;
+ SessionID tabID;
+ session_tab->tab_id = tabID;
+ if (tab->has_tab_visual_index())
+ session_tab->tab_visual_index = tab->tab_visual_index();
+ if (tab->has_current_navigation_index()) {
+ session_tab->current_navigation_index =
+ tab->current_navigation_index();
+ }
+ if (tab->has_pinned())
+ session_tab->pinned = tab->pinned();
+ if (tab->has_extension_app_id())
+ session_tab->extension_app_id = tab->extension_app_id();
+ for (int i3 = 0; i3 < tab->navigation_size(); i3++) {
+ const sync_pb::TabNavigation* navigation = &tab->navigation(i3);
+ AppendSessionTabNavigation(&session_tab->navigations, navigation);
+ }
+}
+
+void SessionModelAssociator::PopulateSessionWindowFromSpecifics(
+ SessionWindow* session_window, const sync_pb::SessionWindow* window) {
+ SessionID id;
+ session_window->window_id = id;
+ if (window->has_selected_tab_index())
+ session_window->selected_tab_index = window->selected_tab_index();
+ if (window->has_browser_type()) {
+ if (window->browser_type() ==
+ sync_pb::SessionWindow_BrowserType_TYPE_NORMAL) {
+ session_window->type = 1;
+ } else {
+ session_window->type = 2;
+ }
+ }
+ for (int i = 0; i < window->session_tab_size(); i++) {
+ const sync_pb::SessionTab& tab = window->session_tab(i);
+ SessionTab* session_tab = new SessionTab();
+ PopulateSessionTabFromSpecifics(session_tab, &tab, id);
+ session_window->tabs.insert(session_window->tabs.end(), session_tab);
+ }
+}
+
+bool SessionModelAssociator::UpdateSyncModel(
+ sync_pb::SessionSpecifics* session_data,
+ sync_api::WriteTransaction* trans,
+ const sync_api::ReadNode* root) {
+ const std::string id = session_data->session_tag();
+ sync_api::WriteNode write_node(trans);
+ if (!write_node.InitByClientTagLookup(syncable::SESSIONS, id)) {
+ sync_api::WriteNode create_node(trans);
+ if (!create_node.InitUniqueByCreation(syncable::SESSIONS, *root, id)) {
+ LOG(ERROR) << "Could not create node for session " << id;
+ return false;
+ }
+ create_node.SetSessionSpecifics(*session_data);
+ return true;
+ }
+ write_node.SetSessionSpecifics(*session_data);
+ return true;
+}
+
+} // namespace browser_sync
+
diff --git a/chrome/browser/sync/glue/session_model_associator.h b/chrome/browser/sync/glue/session_model_associator.h
new file mode 100644
index 0000000..97ef561
--- /dev/null
+++ b/chrome/browser/sync/glue/session_model_associator.h
@@ -0,0 +1,201 @@
+// Copyright (c) 2010 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_MODEL_ASSOCIATOR_H_
+#define CHROME_BROWSER_SYNC_GLUE_SESSION_MODEL_ASSOCIATOR_H_
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/observer_list.h"
+#include "chrome/browser/sessions/session_service.h"
+#include "chrome/browser/sessions/session_types.h"
+#include "chrome/browser/sync/engine/syncapi.h"
+#include "chrome/browser/sync/glue/model_associator.h"
+#include "chrome/browser/sync/protocol/session_specifics.pb.h"
+#include "chrome/browser/sync/syncable/model_type.h"
+#include "chrome/common/notification_registrar.h"
+
+class Profile;
+class ProfileSyncService;
+
+namespace sync_api {
+class ReadNode;
+class WriteNode;
+class WriteTransaction;
+} // namespace sync_api
+
+namespace sync_pb {
+class SessionSpecifics;
+} // namespace sync_pb
+
+namespace browser_sync {
+
+static const char kSessionsTag[] = "google_chrome_sessions";
+// Contains all logic for associating the Chrome sessions model and
+// the sync sessions model.
+class SessionModelAssociator : public PerDataTypeAssociatorInterface<
+ sync_pb::SessionSpecifics, std::string>, public NonThreadSafe {
+ public:
+
+ // Does not take ownership of sync_service.
+ explicit SessionModelAssociator(ProfileSyncService* sync_service);
+ virtual ~SessionModelAssociator();
+
+
+ // AssociatorInterface and PerDataTypeAssociator Interface implementation.
+ virtual void AbortAssociation() {
+ return;
+ // No implementation needed, this associator runs on the main
+ // thread.
+ }
+
+ // Used to re-associate a foreign session.
+ virtual void Associate(const sync_pb::SessionSpecifics* specifics,
+ int64 sync_id);
+
+ // Updates the sync model with the local client data.
+ virtual bool AssociateModels();
+
+ // The has_nodes out parameter is set to true if the chrome model
+ // has user-created nodes. The method may return false if an error
+ // occurred.
+ virtual bool ChromeModelHasUserCreatedNodes(bool* has_nodes);
+
+ // Will update the new tab page with current foreign sessions excluding the
+ // one being disassociated.
+ virtual void Disassociate(int64 sync_id);
+
+ // TODO(jerrica): Add functionality to stop rendering foreign sessions to the
+ // new tab page.
+ virtual bool DisassociateModels() {
+ // There is no local model stored with which to disassociate.
+ return true;
+ }
+
+ // Returns the chrome session specifics for the given sync id.
+ // Returns NULL if no specifics are found for the given sync id.
+ virtual const sync_pb::SessionSpecifics* GetChromeNodeFromSyncId(
+ int64 sync_id);
+
+ // Returns whether a node with the given permanent tag was found and update
+ // |sync_id| with that node's id.
+ virtual bool GetSyncIdForTaggedNode(const std::string* tag, int64* sync_id);
+
+ // Returns sync id for the given chrome model id.
+ // Returns sync_api::kInvalidId if the sync node is not found for the given
+ // chrome id.
+ virtual int64 GetSyncIdFromChromeId(std::string id);
+
+
+ // Initializes the given sync node from the given chrome node id.
+ // Returns false if no sync node was found for the given chrome node id or
+ // if the initialization of sync node fails.
+ virtual bool InitSyncNodeFromChromeId(std::string id,
+ sync_api::BaseNode* sync_node) {
+ return false;
+ }
+
+ // The has_nodes out parameter is set to true if the sync model has
+ // nodes other than the permanent tagged nodes. The method may
+ // return false if an error occurred.
+ virtual bool SyncModelHasUserCreatedNodes(bool* has_nodes);
+
+ // Returns the tag used to uniquely identify this machine's session in the
+ // sync model.
+ std::string GetCurrentMachineTag();
+
+ // Pulls the current sync model from the server, and returns true upon update
+ // of the client model.
+ bool GetSessionDataFromSyncModel(std::vector<ForeignSession*>* windows);
+
+
+ // Helper method for converting session specifics to windows.
+ void AppendForeignSessionFromSpecifics(
+ const sync_pb::SessionSpecifics* specifics,
+ std::vector<ForeignSession*>* session);
+
+ // Fills the given empty vector with foreign session windows to restore.
+ // If the vector is returned empty, then the session data could not be
+ // converted back into windows.
+ void AppendForeignSessionWithID(int64 id,
+ std::vector<ForeignSession*>* session,
+ sync_api::BaseTransaction* trans);
+
+ // Returns the syncable model type.
+ static syncable::ModelType model_type() { return syncable::SESSIONS; }
+
+ // Updates the server data based upon the current client session. If no node
+ // corresponding to this machine exists in the sync model, one is created.
+ void UpdateSyncModelDataFromClient();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceSessionTest, WriteSessionToNode);
+ FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceSessionTest,
+ WriteForeignSessionToNode);
+
+ // Returns the session service from |sync_service_|.
+ SessionService* GetSessionService();
+
+ // Initializes the tag corresponding to this machine.
+ void InitializeCurrentMachineTag();
+
+ // Populates the navigation portion of the session specifics.
+ void PopulateSessionSpecificsNavigation(const TabNavigation* navigation,
+ sync_pb::TabNavigation* tab_navigation);
+
+ // Populates the tab portion of the session specifics.
+ void PopulateSessionSpecificsTab(const SessionTab* tab,
+ sync_pb::SessionTab* session_tab);
+
+ // Populates the window portion of the session specifics.
+ void PopulateSessionSpecificsWindow(const SessionWindow* window,
+ sync_pb::SessionWindow* session_window);
+
+ // Specifies whether the window has tabs to sync. The new tab page does not
+ // count. If no tabs to sync, it returns true, otherwise false;
+ bool WindowHasNoTabsToSync(const SessionWindow* window);
+
+ // Internal method used in the callback to obtain the current session.
+ // We don't own |windows|.
+ void OnGotSession(int handle, std::vector<SessionWindow*>* windows);
+
+ // Used to populate a session tab from the session specifics tab provided.
+ void AppendSessionTabNavigation(std::vector<TabNavigation>* navigations,
+ const sync_pb::TabNavigation* navigation);
+
+ // Used to populate a session tab from the session specifics tab provided.
+ void PopulateSessionTabFromSpecifics(SessionTab* session_tab,
+ const sync_pb::SessionTab* tab, SessionID id);
+
+ // Used to populate a session window from the session specifics window
+ // provided.
+ void PopulateSessionWindowFromSpecifics(SessionWindow* session_window,
+ const sync_pb::SessionWindow* window);
+
+ // Updates the current session on the server. Creates a node for this machine
+ // if there is not one already.
+ bool UpdateSyncModel(sync_pb::SessionSpecifics* session_data,
+ sync_api::WriteTransaction* trans,
+ const sync_api::ReadNode* root);
+
+ // Stores the machine tag.
+ std::string current_machine_tag_;
+
+ // Weak pointer.
+ ProfileSyncService* sync_service_;
+
+ // Consumer used to obtain the current session.
+ CancelableRequestConsumer consumer_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionModelAssociator);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_SESSION_MODEL_ASSOCIATOR_H_
+
diff --git a/chrome/browser/sync/profile_sync_factory.h b/chrome/browser/sync/profile_sync_factory.h
index dfeea8a..1ca0473 100644
--- a/chrome/browser/sync/profile_sync_factory.h
+++ b/chrome/browser/sync/profile_sync_factory.h
@@ -116,6 +116,13 @@ class ProfileSyncFactory {
ProfileSyncService* profile_sync_service,
history::HistoryBackend* history_backend,
browser_sync::UnrecoverableErrorHandler* error_handler) = 0;
+
+ // Instantiates both a model associator and change processor for the
+ // session data type. The pointers in the return struct are
+ // owned by the caller.
+ virtual SyncComponents CreateSessionSyncComponents(
+ ProfileSyncService* profile_sync_service,
+ browser_sync::UnrecoverableErrorHandler* error_handler) = 0;
};
#endif // CHROME_BROWSER_SYNC_PROFILE_SYNC_FACTORY_H__
diff --git a/chrome/browser/sync/profile_sync_factory_impl.cc b/chrome/browser/sync/profile_sync_factory_impl.cc
index eb3cca8..7ba2c52 100644
--- a/chrome/browser/sync/profile_sync_factory_impl.cc
+++ b/chrome/browser/sync/profile_sync_factory_impl.cc
@@ -24,6 +24,9 @@
#include "chrome/browser/sync/glue/preference_change_processor.h"
#include "chrome/browser/sync/glue/preference_data_type_controller.h"
#include "chrome/browser/sync/glue/preference_model_associator.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/theme_change_processor.h"
#include "chrome/browser/sync/glue/theme_data_type_controller.h"
@@ -55,6 +58,9 @@ using browser_sync::PasswordModelAssociator;
using browser_sync::PreferenceChangeProcessor;
using browser_sync::PreferenceDataTypeController;
using browser_sync::PreferenceModelAssociator;
+using browser_sync::SessionChangeProcessor;
+using browser_sync::SessionDataTypeController;
+using browser_sync::SessionModelAssociator;
using browser_sync::SyncBackendHost;
using browser_sync::ThemeChangeProcessor;
using browser_sync::ThemeDataTypeController;
@@ -129,6 +135,12 @@ ProfileSyncService* ProfileSyncFactoryImpl::CreateProfileSyncService() {
new TypedUrlDataTypeController(this, profile_, pss));
}
+ // Session sync is enabled by default. Register unless explicitly
+ // disabled.
+ if (!command_line_->HasSwitch(switches::kDisableSyncSessions)) {
+ pss->RegisterDataTypeController(
+ new SessionDataTypeController(this, pss));
+ }
return pss;
}
@@ -249,3 +261,15 @@ ProfileSyncFactoryImpl::CreateTypedUrlSyncComponents(
error_handler);
return SyncComponents(model_associator, change_processor);
}
+
+ProfileSyncFactory::SyncComponents
+ProfileSyncFactoryImpl::CreateSessionSyncComponents(
+ ProfileSyncService* profile_sync_service,
+ UnrecoverableErrorHandler* error_handler) {
+ SessionModelAssociator* model_associator =
+ new SessionModelAssociator(profile_sync_service);
+ SessionChangeProcessor* change_processor =
+ new SessionChangeProcessor(error_handler, model_associator);
+ return SyncComponents(model_associator, change_processor);
+}
+
diff --git a/chrome/browser/sync/profile_sync_factory_impl.h b/chrome/browser/sync/profile_sync_factory_impl.h
index 58d39bb..ce535e5 100644
--- a/chrome/browser/sync/profile_sync_factory_impl.h
+++ b/chrome/browser/sync/profile_sync_factory_impl.h
@@ -60,6 +60,10 @@ class ProfileSyncFactoryImpl : public ProfileSyncFactory {
history::HistoryBackend* history_backend,
browser_sync::UnrecoverableErrorHandler* error_handler);
+ virtual SyncComponents CreateSessionSyncComponents(
+ ProfileSyncService* profile_sync_service,
+ browser_sync::UnrecoverableErrorHandler* error_handler);
+
private:
Profile* profile_;
CommandLine* command_line_;
diff --git a/chrome/browser/sync/profile_sync_factory_impl_unittest.cc b/chrome/browser/sync/profile_sync_factory_impl_unittest.cc
index d5d1e55..2cb2c10 100644
--- a/chrome/browser/sync/profile_sync_factory_impl_unittest.cc
+++ b/chrome/browser/sync/profile_sync_factory_impl_unittest.cc
@@ -43,7 +43,7 @@ TEST_F(ProfileSyncFactoryImplTest, CreatePSSDefault) {
DataTypeController::StateMap controller_states;
DataTypeController::StateMap* controller_states_ptr = &controller_states;
pss->GetDataTypeControllerStates(controller_states_ptr);
- EXPECT_EQ(5U, controller_states_ptr->size());
+ EXPECT_EQ(6U, controller_states_ptr->size());
EXPECT_EQ(1U, controller_states_ptr->count(syncable::BOOKMARKS));
EXPECT_EQ(1U, controller_states_ptr->count(syncable::PREFERENCES));
EXPECT_EQ(1U, controller_states_ptr->count(syncable::AUTOFILL));
@@ -58,7 +58,7 @@ TEST_F(ProfileSyncFactoryImplTest, CreatePSSDisableAutofill) {
DataTypeController::StateMap controller_states;
DataTypeController::StateMap* controller_states_ptr = &controller_states;
pss->GetDataTypeControllerStates(controller_states_ptr);
- EXPECT_EQ(4U, controller_states_ptr->size());
+ EXPECT_EQ(5U, controller_states_ptr->size());
EXPECT_EQ(1U, controller_states_ptr->count(syncable::BOOKMARKS));
EXPECT_EQ(1U, controller_states_ptr->count(syncable::PREFERENCES));
EXPECT_EQ(0U, controller_states_ptr->count(syncable::AUTOFILL));
@@ -73,7 +73,7 @@ TEST_F(ProfileSyncFactoryImplTest, CreatePSSDisableBookmarks) {
DataTypeController::StateMap controller_states;
DataTypeController::StateMap* controller_states_ptr = &controller_states;
pss->GetDataTypeControllerStates(controller_states_ptr);
- EXPECT_EQ(4U, controller_states_ptr->size());
+ EXPECT_EQ(5U, controller_states_ptr->size());
EXPECT_EQ(0U, controller_states_ptr->count(syncable::BOOKMARKS));
EXPECT_EQ(1U, controller_states_ptr->count(syncable::PREFERENCES));
EXPECT_EQ(1U, controller_states_ptr->count(syncable::AUTOFILL));
@@ -88,7 +88,7 @@ TEST_F(ProfileSyncFactoryImplTest, CreatePSSDisablePreferences) {
DataTypeController::StateMap controller_states;
DataTypeController::StateMap* controller_states_ptr = &controller_states;
pss->GetDataTypeControllerStates(controller_states_ptr);
- EXPECT_EQ(4U, controller_states_ptr->size());
+ EXPECT_EQ(5U, controller_states_ptr->size());
EXPECT_EQ(1U, controller_states_ptr->count(syncable::BOOKMARKS));
EXPECT_EQ(0U, controller_states_ptr->count(syncable::PREFERENCES));
EXPECT_EQ(1U, controller_states_ptr->count(syncable::AUTOFILL));
@@ -103,7 +103,7 @@ TEST_F(ProfileSyncFactoryImplTest, CreatePSSDisableThemes) {
DataTypeController::StateMap controller_states;
DataTypeController::StateMap* controller_states_ptr = &controller_states;
pss->GetDataTypeControllerStates(controller_states_ptr);
- EXPECT_EQ(4U, controller_states_ptr->size());
+ EXPECT_EQ(5U, controller_states_ptr->size());
EXPECT_EQ(1U, controller_states_ptr->count(syncable::BOOKMARKS));
EXPECT_EQ(1U, controller_states_ptr->count(syncable::PREFERENCES));
EXPECT_EQ(1U, controller_states_ptr->count(syncable::AUTOFILL));
@@ -118,7 +118,7 @@ TEST_F(ProfileSyncFactoryImplTest, CreatePSSDisableExtensions) {
DataTypeController::StateMap controller_states;
DataTypeController::StateMap* controller_states_ptr = &controller_states;
pss->GetDataTypeControllerStates(controller_states_ptr);
- EXPECT_EQ(4U, controller_states_ptr->size());
+ EXPECT_EQ(5U, controller_states_ptr->size());
EXPECT_EQ(1U, controller_states_ptr->count(syncable::BOOKMARKS));
EXPECT_EQ(1U, controller_states_ptr->count(syncable::PREFERENCES));
EXPECT_EQ(1U, controller_states_ptr->count(syncable::AUTOFILL));
diff --git a/chrome/browser/sync/profile_sync_factory_mock.h b/chrome/browser/sync/profile_sync_factory_mock.h
index d0328c0..6c14d1e 100644
--- a/chrome/browser/sync/profile_sync_factory_mock.h
+++ b/chrome/browser/sync/profile_sync_factory_mock.h
@@ -52,6 +52,9 @@ class ProfileSyncFactoryMock : public ProfileSyncFactory {
MOCK_METHOD2(CreatePreferenceSyncComponents,
SyncComponents(ProfileSyncService* profile_sync_service,
browser_sync::UnrecoverableErrorHandler* error_handler));
+ MOCK_METHOD2(CreateSessionSyncComponents,
+ SyncComponents(ProfileSyncService* profile_sync_service,
+ browser_sync::UnrecoverableErrorHandler* error_handler));
MOCK_METHOD2(CreateThemeSyncComponents,
SyncComponents(ProfileSyncService* profile_sync_service,
browser_sync::UnrecoverableErrorHandler* error_handler));
diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc
index f441f4d..92251ce 100644
--- a/chrome/browser/sync/profile_sync_service.cc
+++ b/chrome/browser/sync/profile_sync_service.cc
@@ -29,6 +29,7 @@
#include "chrome/browser/sync/glue/change_processor.h"
#include "chrome/browser/sync/glue/data_type_controller.h"
#include "chrome/browser/sync/glue/data_type_manager.h"
+#include "chrome/browser/sync/glue/session_data_type_controller.h"
#include "chrome/browser/sync/profile_sync_factory.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
#include "chrome/common/chrome_switches.h"
@@ -169,6 +170,19 @@ void ProfileSyncService::RegisterDataTypeController(
data_type_controller;
}
+browser_sync::SessionModelAssociator*
+ ProfileSyncService::GetSessionModelAssociator() {
+ if (data_type_controllers_.find(syncable::SESSIONS) ==
+ data_type_controllers_.end() ||
+ data_type_controllers_.find(syncable::SESSIONS)->second->state() !=
+ DataTypeController::RUNNING) {
+ return NULL;
+ }
+ return static_cast<browser_sync::SessionDataTypeController*>(
+ data_type_controllers_.find(
+ syncable::SESSIONS)->second.get())->GetModelAssociator();
+}
+
void ProfileSyncService::GetDataTypeControllerStates(
browser_sync::DataTypeController::StateMap* state_map) const {
browser_sync::DataTypeController::TypeMap::const_iterator iter
@@ -240,10 +254,9 @@ void ProfileSyncService::RegisterPreferences() {
pref_service->RegisterBooleanPref(prefs::kSyncTypedUrls, enable_by_default);
pref_service->RegisterBooleanPref(prefs::kSyncExtensions, enable_by_default);
pref_service->RegisterBooleanPref(prefs::kSyncApps, enable_by_default);
-
+ pref_service->RegisterBooleanPref(prefs::kSyncSessions, enable_by_default);
pref_service->RegisterBooleanPref(prefs::kKeepEverythingSynced,
enable_by_default);
-
pref_service->RegisterBooleanPref(prefs::kSyncManaged, false);
}
@@ -321,8 +334,7 @@ void ProfileSyncService::StartUp() {
}
void ProfileSyncService::Shutdown(bool sync_disabled) {
-
- // Stop all data type controllers, if needed.
+ // Stop all data type controllers, if needed.
if (data_type_manager_.get() &&
data_type_manager_->state() != DataTypeManager::STOPPED) {
data_type_manager_->Stop();
@@ -409,6 +421,8 @@ const char* ProfileSyncService::GetPrefNameForDataType(
return prefs::kSyncExtensions;
case syncable::APPS:
return prefs::kSyncApps;
+ case syncable::SESSIONS:
+ return prefs::kSyncSessions;
default:
NOTREACHED();
return NULL;
diff --git a/chrome/browser/sync/profile_sync_service.h b/chrome/browser/sync/profile_sync_service.h
index 6566e40..3f5ddaa 100644
--- a/chrome/browser/sync/profile_sync_service.h
+++ b/chrome/browser/sync/profile_sync_service.h
@@ -18,6 +18,7 @@
#include "chrome/browser/pref_member.h"
#include "chrome/browser/sync/glue/data_type_controller.h"
#include "chrome/browser/sync/glue/data_type_manager.h"
+#include "chrome/browser/sync/glue/session_model_associator.h"
#include "chrome/browser/sync/glue/sync_backend_host.h"
#include "chrome/browser/sync/notification_method.h"
#include "chrome/browser/sync/profile_sync_service_observer.h"
@@ -127,6 +128,11 @@ class ProfileSyncService : public browser_sync::SyncFrontend,
void RegisterDataTypeController(
browser_sync::DataTypeController* data_type_controller);
+ // Returns the session model associator associated with this type, but only if
+ // the associator is running. If it is doing anything else, it will return
+ // null.
+ browser_sync::SessionModelAssociator* GetSessionModelAssociator();
+
// Fills state_map with a map of current data types that are possible to
// sync, as well as their states.
void GetDataTypeControllerStates(
@@ -295,6 +301,10 @@ class ProfileSyncService : public browser_sync::SyncFrontend,
// NotificationService when the outcome is known.
virtual void SetPassphrase(const std::string& passphrase);
+ // Returns whether processing changes is allowed. Check this before doing
+ // any model-modifying operations.
+ bool ShouldPushChanges();
+
protected:
// Used by ProfileSyncServiceMock only.
//
@@ -306,10 +316,6 @@ class ProfileSyncService : public browser_sync::SyncFrontend,
// Helper to install and configure a data type manager.
void ConfigureDataTypeManager();
- // Returns whether processing changes is allowed. Check this before doing
- // any model-modifying operations.
- bool ShouldPushChanges();
-
// Starts up the backend sync components.
void StartUp();
// Shuts down the backend sync components.
@@ -344,6 +350,7 @@ class ProfileSyncService : public browser_sync::SyncFrontend,
private:
friend class ProfileSyncServiceTest;
friend class ProfileSyncServicePreferenceTest;
+ friend class ProfileSyncServiceSessionTest;
friend class ProfileSyncServiceTestHarness;
FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceTest, InitialState);
FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceTest,
diff --git a/chrome/browser/sync/profile_sync_service_password_unittest.cc b/chrome/browser/sync/profile_sync_service_password_unittest.cc
index 337375a2..c989822 100644
--- a/chrome/browser/sync/profile_sync_service_password_unittest.cc
+++ b/chrome/browser/sync/profile_sync_service_password_unittest.cc
@@ -44,7 +44,6 @@ using sync_api::UserShare;
using syncable::BASE_VERSION;
using syncable::CREATE;
using syncable::DirectoryManager;
-using syncable::ID;
using syncable::IS_DEL;
using syncable::IS_DIR;
using syncable::IS_UNAPPLIED_UPDATE;
diff --git a/chrome/browser/sync/profile_sync_service_session_unittest.cc b/chrome/browser/sync/profile_sync_service_session_unittest.cc
new file mode 100644
index 0000000..5f4fc10
--- /dev/null
+++ b/chrome/browser/sync/profile_sync_service_session_unittest.cc
@@ -0,0 +1,417 @@
+// Copyright (c) 2010 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/message_loop.h"
+#include "base/scoped_ptr.h"
+#include "base/scoped_temp_dir.h"
+#include "base/stl_util-inl.h"
+#include "base/task.h"
+#include "chrome/browser/sessions/session_service.h"
+#include "chrome/browser/sessions/session_service_test_helper.h"
+#include "chrome/browser/sync/abstract_profile_sync_service_test.h"
+#include "chrome/browser/sync/engine/syncapi.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/profile_sync_test_util.h"
+#include "chrome/browser/sync/profile_sync_factory_mock.h"
+#include "chrome/browser/sync/protocol/session_specifics.pb.h"
+#include "chrome/browser/sync/protocol/sync.pb.h"
+#include "chrome/browser/sync/syncable/directory_manager.h"
+#include "chrome/browser/sync/syncable/model_type.h"
+#include "chrome/browser/sync/syncable/syncable.h"
+#include "chrome/browser/sync/test_profile_sync_service.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/browser_with_test_window_test.h"
+#include "chrome/test/file_test_utils.h"
+#include "chrome/test/profile_mock.h"
+#include "chrome/test/testing_profile.h"
+#include "chrome/test/sync/engine/test_id_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using browser_sync::SessionChangeProcessor;
+using browser_sync::SessionDataTypeController;
+using browser_sync::SessionModelAssociator;
+using browser_sync::SyncBackendHost;
+using sync_api::SyncManager;
+using testing::_;
+using testing::Return;
+using browser_sync::TestIdFactory;
+
+namespace browser_sync {
+
+class ProfileSyncServiceSessionTest
+ : public BrowserWithTestWindowTest,
+ public NotificationObserver {
+ public:
+ ProfileSyncServiceSessionTest()
+ : window_bounds_(0, 1, 2, 3),
+ notified_of_update_(false),
+ notification_sync_id_(0) {}
+
+ ProfileSyncService* sync_service() { return sync_service_.get(); }
+
+ TestIdFactory* ids() { return &ids_; }
+
+ protected:
+ SessionService* service() { return helper_.service(); }
+
+ virtual void SetUp() {
+ BrowserWithTestWindowTest::SetUp();
+ profile()->set_has_history_service(true);
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ SessionService* session_service = new SessionService(temp_dir_.path());
+ helper_.set_service(session_service);
+ service()->SetWindowType(window_id_, Browser::TYPE_NORMAL);
+ service()->SetWindowBounds(window_id_, window_bounds_, false);
+ registrar_.Add(this, NotificationType::FOREIGN_SESSION_UPDATED,
+ NotificationService::AllSources());
+ registrar_.Add(this, NotificationType::FOREIGN_SESSION_DELETED,
+ NotificationService::AllSources());
+ }
+
+ void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type.value) {
+ case NotificationType::FOREIGN_SESSION_UPDATED: {
+ notified_of_update_ = true;
+ notification_sync_id_ = *Details<int64>(details).ptr();
+ break;
+ }
+ case NotificationType::FOREIGN_SESSION_DELETED: {
+ notified_of_update_ = true;
+ notification_sync_id_ = -1;
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ virtual void TearDown() {
+ helper_.set_service(NULL);
+ profile()->set_session_service(NULL);
+ sync_service_.reset();
+ }
+
+ bool StartSyncService(Task* task, bool will_fail_association) {
+ if (sync_service_.get())
+ return false;
+
+ sync_service_.reset(new TestProfileSyncService(
+ &factory_, profile(), false, false, task));
+ profile()->set_session_service(helper_.service());
+
+ // Register the session data type.
+ model_associator_ =
+ new SessionModelAssociator(sync_service_.get());
+ change_processor_ = new SessionChangeProcessor(
+ sync_service_.get(), model_associator_);
+ EXPECT_CALL(factory_, CreateSessionSyncComponents(_, _)).
+ WillOnce(Return(ProfileSyncFactory::SyncComponents(
+ model_associator_, change_processor_)));
+ EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).
+ WillOnce(ReturnNewDataTypeManager());
+ sync_service_->set_num_expected_resumes(will_fail_association ? 0 : 1);
+ sync_service_->RegisterDataTypeController(
+ new SessionDataTypeController(&factory_, sync_service_.get()));
+ sync_service_->Initialize();
+ MessageLoop::current()->Run();
+ return true;
+ }
+
+ SyncBackendHost* backend() { return sync_service_->backend(); }
+
+ // Path used in testing.
+ ScopedTempDir temp_dir_;
+ SessionServiceTestHelper helper_;
+ SessionModelAssociator* model_associator_;
+ SessionChangeProcessor* change_processor_;
+ SessionID window_id_;
+ ProfileSyncFactoryMock factory_;
+ scoped_ptr<TestProfileSyncService> sync_service_;
+ TestIdFactory ids_;
+ const gfx::Rect window_bounds_;
+ bool notified_of_update_;
+ int64 notification_sync_id_;
+ NotificationRegistrar registrar_;
+};
+
+class CreateRootTask : public Task {
+ public:
+ explicit CreateRootTask(ProfileSyncServiceSessionTest* test)
+ : test_(test), success_(false) {
+ }
+
+ virtual ~CreateRootTask() {}
+ virtual void Run() {
+ success_ = ProfileSyncServiceTestHelper::CreateRoot(syncable::SESSIONS,
+ test_->sync_service(), test_->ids());
+ }
+
+ bool success() { return success_; }
+
+ private:
+ ProfileSyncServiceSessionTest* test_;
+ bool success_;
+};
+
+// Test that we can write this machine's session to a node and retrieve it.
+TEST_F(ProfileSyncServiceSessionTest, WriteSessionToNode) {
+ CreateRootTask task(this);
+ ASSERT_TRUE(StartSyncService(&task, false));
+ ASSERT_TRUE(task.success());
+ ASSERT_EQ(model_associator_->GetSessionService(), helper_.service());
+
+ // Check that the DataTypeController associated the models.
+ bool has_nodes;
+ ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes));
+ ASSERT_TRUE(has_nodes);
+ ASSERT_TRUE(model_associator_->ChromeModelHasUserCreatedNodes(&has_nodes));
+ ASSERT_TRUE(has_nodes);
+ std::string machine_tag = model_associator_->GetCurrentMachineTag();
+ int64 sync_id;
+ ASSERT_TRUE(model_associator_->GetSyncIdForTaggedNode(&machine_tag,
+ &sync_id));
+ ASSERT_EQ(model_associator_->GetSyncIdFromChromeId(machine_tag), sync_id);
+ scoped_ptr<const sync_pb::SessionSpecifics> sync_specifics(
+ model_associator_->GetChromeNodeFromSyncId(sync_id));
+ ASSERT_TRUE(sync_specifics != NULL);
+
+ // Check that we can get the correct session specifics back from the node.
+ sync_api::ReadTransaction trans(sync_service_->
+ backend()->GetUserShareHandle());
+ sync_api::ReadNode node(&trans);
+ ASSERT_TRUE(node.InitByClientTagLookup(syncable::SESSIONS,
+ machine_tag));
+ const sync_pb::SessionSpecifics& specifics(node.GetSessionSpecifics());
+ ASSERT_EQ(sync_specifics->session_tag(), specifics.session_tag());
+ ASSERT_EQ(machine_tag, specifics.session_tag());
+}
+
+// Test that we can fill this machine's session, write it to a node,
+// and then retrieve it.
+TEST_F(ProfileSyncServiceSessionTest, WriteFilledSessionToNode) {
+ CreateRootTask task(this);
+ ASSERT_TRUE(StartSyncService(&task, false));
+ ASSERT_TRUE(task.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"));
+
+ // Report a saved session, thus causing the ChangeProcessor to write to a
+ // node.
+ NotificationService::current()->Notify(
+ NotificationType::SESSION_SERVICE_SAVED,
+ Source<Profile>(profile()),
+ NotificationService::NoDetails());
+ std::string machine_tag = model_associator_->GetCurrentMachineTag();
+ int64 sync_id;
+ ASSERT_TRUE(model_associator_->GetSyncIdForTaggedNode(&machine_tag,
+ &sync_id));
+ ASSERT_EQ(model_associator_->GetSyncIdFromChromeId(machine_tag), sync_id);
+ scoped_ptr<const sync_pb::SessionSpecifics> sync_specifics(
+ model_associator_->GetChromeNodeFromSyncId(sync_id));
+ ASSERT_TRUE(sync_specifics != NULL);
+
+ // Check that this machine's data is not included in the foreign windows.
+ std::vector<ForeignSession*> foreign_sessions;
+ model_associator_->GetSessionDataFromSyncModel(&foreign_sessions);
+ ASSERT_EQ(foreign_sessions.size(), 0U);
+
+ // Get the windows for this machine from the node and check that they were
+ // filled.
+ sync_api::ReadTransaction trans(sync_service_->
+ backend()->GetUserShareHandle());
+ sync_api::ReadNode node(&trans);
+ ASSERT_TRUE(node.InitByClientTagLookup(syncable::SESSIONS,
+ machine_tag));
+ model_associator_->AppendForeignSessionWithID(sync_id, &foreign_sessions,
+ &trans);
+ ASSERT_EQ(foreign_sessions.size(), 1U);
+ ASSERT_EQ(1U, foreign_sessions[0]->windows.size());
+ ASSERT_EQ(2U, foreign_sessions[0]->windows[0]->tabs.size());
+ ASSERT_EQ(2U, foreign_sessions[0]->windows[0]->tabs[0]->navigations.size());
+ ASSERT_EQ(GURL("http://bar/1"),
+ foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].virtual_url());
+ ASSERT_EQ(GURL("http://bar/2"),
+ foreign_sessions[0]->windows[0]->tabs[0]->navigations[1].virtual_url());
+ ASSERT_EQ(2U, foreign_sessions[0]->windows[0]->tabs[1]->navigations.size());
+ ASSERT_EQ(GURL("http://foo/1"),
+ foreign_sessions[0]->windows[0]->tabs[1]->navigations[0].virtual_url());
+ ASSERT_EQ(GURL("http://foo/2"),
+ foreign_sessions[0]->windows[0]->tabs[1]->navigations[1].virtual_url());
+ const sync_pb::SessionSpecifics& specifics(node.GetSessionSpecifics());
+ ASSERT_EQ(sync_specifics->session_tag(), specifics.session_tag());
+ ASSERT_EQ(machine_tag, specifics.session_tag());
+}
+
+// Test that we fail on a failed model association.
+TEST_F(ProfileSyncServiceSessionTest, FailModelAssociation) {
+ ASSERT_TRUE(StartSyncService(NULL, true));
+ ASSERT_TRUE(sync_service_->unrecoverable_error_detected());
+}
+
+// Write a foreign session to a node, and then retrieve it.
+TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNode) {
+ CreateRootTask task(this);
+ ASSERT_TRUE(StartSyncService(&task, false));
+ ASSERT_TRUE(task.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.
+ sync_pb::SessionSpecifics specifics;
+ std::string machine_tag = "session_sync123";
+ specifics.set_session_tag(machine_tag);
+ sync_pb::SessionWindow* window = specifics.add_session_window();
+ window->set_selected_tab_index(1);
+ window->set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_NORMAL);
+ sync_pb::SessionTab* tab = window->add_session_tab();
+ tab->set_tab_visual_index(13);
+ tab->set_current_navigation_index(3);
+ tab->set_pinned(true);
+ tab->set_extension_app_id("app_id");
+ sync_pb::TabNavigation* navigation = tab->add_navigation();
+ navigation->set_index(12);
+ navigation->set_virtual_url("http://foo/1");
+ navigation->set_referrer("referrer");
+ navigation->set_title("title");
+ navigation->set_page_transition(sync_pb::TabNavigation_PageTransition_TYPED);
+
+ // Update the server with the session specifics.
+ {
+ sync_api::WriteTransaction trans(sync_service_->
+ backend()->GetUserShareHandle());
+ sync_api::ReadNode root(&trans);
+ ASSERT_TRUE(root.InitByTagLookup(kSessionsTag));
+ model_associator_->UpdateSyncModel(&specifics, &trans, &root);
+ }
+
+ // Check that the foreign session was written to a node and retrieve the data.
+ int64 sync_id;
+ ASSERT_TRUE(model_associator_->GetSyncIdForTaggedNode(&machine_tag,
+ &sync_id));
+ ASSERT_EQ(model_associator_->GetSyncIdFromChromeId(machine_tag), sync_id);
+ scoped_ptr<const sync_pb::SessionSpecifics> sync_specifics(
+ model_associator_->GetChromeNodeFromSyncId(sync_id));
+ ASSERT_TRUE(sync_specifics != NULL);
+ std::vector<ForeignSession*> foreign_sessions;
+ model_associator_->GetSessionDataFromSyncModel(&foreign_sessions);
+ ASSERT_EQ(foreign_sessions.size(), 1U);
+ ASSERT_EQ(1U, foreign_sessions[0]->windows.size());
+ ASSERT_EQ(1U, foreign_sessions[0]->windows[0]->tabs.size());
+ ASSERT_EQ(1U, foreign_sessions[0]->windows[0]->tabs[0]->navigations.size());
+ ASSERT_EQ(foreign_sessions[0]->foreign_tession_tag, machine_tag);
+ ASSERT_EQ(1, foreign_sessions[0]->windows[0]->selected_tab_index);
+ ASSERT_EQ(1, foreign_sessions[0]->windows[0]->type);
+ ASSERT_EQ(13, foreign_sessions[0]->windows[0]->tabs[0]->tab_visual_index);
+ ASSERT_EQ(3,
+ foreign_sessions[0]->windows[0]->tabs[0]->current_navigation_index);
+ ASSERT_TRUE(foreign_sessions[0]->windows[0]->tabs[0]->pinned);
+ ASSERT_EQ("app_id",
+ foreign_sessions[0]->windows[0]->tabs[0]->extension_app_id);
+ ASSERT_EQ(12,
+ foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].index());
+ ASSERT_EQ(GURL("referrer"),
+ foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].referrer());
+ ASSERT_EQ(string16(ASCIIToUTF16("title")),
+ foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].title());
+ ASSERT_EQ(PageTransition::TYPED,
+ foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].transition());
+ ASSERT_EQ(GURL("http://foo/1"),
+ foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].virtual_url());
+ sync_api::WriteTransaction trans(sync_service_->
+ backend()->GetUserShareHandle());
+ sync_api::ReadNode node(&trans);
+ ASSERT_TRUE(node.InitByClientTagLookup(syncable::SESSIONS,
+ machine_tag));
+ const sync_pb::SessionSpecifics& specifics_(node.GetSessionSpecifics());
+ ASSERT_EQ(sync_specifics->session_tag(), specifics_.session_tag());
+ ASSERT_EQ(machine_tag, specifics_.session_tag());
+}
+
+// Test the DataTypeController on update.
+TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionUpdate) {
+ CreateRootTask task(this);
+ ASSERT_TRUE(StartSyncService(&task, false));
+ ASSERT_TRUE(task.success());
+ int64 node_id = model_associator_->GetSyncIdFromChromeId(
+ model_associator_->GetCurrentMachineTag());
+ scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
+ record->action = SyncManager::ChangeRecord::ACTION_UPDATE;
+ record->id = node_id;
+ ASSERT_EQ(notification_sync_id_, 0);
+ ASSERT_FALSE(notified_of_update_);
+ {
+ sync_api::WriteTransaction trans(backend()->GetUserShareHandle());
+ change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
+ }
+ ASSERT_EQ(notification_sync_id_, node_id);
+ ASSERT_TRUE(notified_of_update_);
+}
+
+// Test the DataTypeController on add.
+TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionAdd) {
+ CreateRootTask task(this);
+ ASSERT_TRUE(StartSyncService(&task, false));
+ ASSERT_TRUE(task.success());
+
+ int64 node_id = model_associator_->GetSyncIdFromChromeId(
+ model_associator_->GetCurrentMachineTag());
+ scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
+ record->action = SyncManager::ChangeRecord::ACTION_ADD;
+ record->id = node_id;
+ ASSERT_EQ(notification_sync_id_, 0);
+ ASSERT_FALSE(notified_of_update_);
+ {
+ sync_api::WriteTransaction trans(backend()->GetUserShareHandle());
+ change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
+ }
+ ASSERT_EQ(notification_sync_id_, node_id);
+ ASSERT_TRUE(notified_of_update_);
+}
+
+// Test the DataTypeController on delete.
+TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionDelete) {
+ CreateRootTask task(this);
+ ASSERT_TRUE(StartSyncService(&task, false));
+ ASSERT_TRUE(task.success());
+
+ int64 node_id = model_associator_->GetSyncIdFromChromeId(
+ model_associator_->GetCurrentMachineTag());
+ scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
+ record->action = SyncManager::ChangeRecord::ACTION_DELETE;
+ record->id = node_id;
+ ASSERT_EQ(notification_sync_id_, 0);
+ ASSERT_FALSE(notified_of_update_);
+ {
+ sync_api::WriteTransaction trans(backend()->GetUserShareHandle());
+ change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
+ }
+ ASSERT_EQ(notification_sync_id_, -1);
+ ASSERT_TRUE(notified_of_update_);
+}
+
+} // namespace browser_sync
+
diff --git a/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc b/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc
index 31c25b4..331f466 100644
--- a/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc
+++ b/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc
@@ -51,7 +51,6 @@ using sync_api::UserShare;
using syncable::BASE_VERSION;
using syncable::CREATE;
using syncable::DirectoryManager;
-using syncable::ID;
using syncable::IS_DEL;
using syncable::IS_DIR;
using syncable::IS_UNAPPLIED_UPDATE;
diff --git a/chrome/browser/sync/sync_setup_flow.cc b/chrome/browser/sync/sync_setup_flow.cc
index b320247..b8fc556 100644
--- a/chrome/browser/sync/sync_setup_flow.cc
+++ b/chrome/browser/sync/sync_setup_flow.cc
@@ -106,6 +106,12 @@ static bool GetDataTypeChoiceData(const std::string& json,
if (sync_extensions)
data_types->insert(syncable::EXTENSIONS);
+ bool sync_sessions;
+ if (!result->GetBoolean("syncSessions", &sync_sessions))
+ return false;
+ if (sync_sessions)
+ data_types->insert(syncable::SESSIONS);
+
bool sync_typed_urls;
if (!result->GetBoolean("syncTypedUrls", &sync_typed_urls))
return false;
@@ -188,7 +194,6 @@ void FlowHandler::ShowGaiaSuccessAndSettingUp() {
// Called by SyncSetupFlow::Advance.
void FlowHandler::ShowChooseDataTypes(const DictionaryValue& args) {
-
// If you're starting the wizard at the Choose Data Types screen (i.e. from
// "Customize Sync"), this will be redundant. However, if you're coming from
// another wizard state, this will make sure Choose Data Types is on top.
@@ -361,7 +366,8 @@ void SyncSetupFlow::GetArgsForChooseDataTypes(ProfileSyncService* service,
registered_types.count(syncable::TYPED_URLS) > 0);
args->SetBoolean("appsRegistered",
registered_types.count(syncable::APPS) > 0);
-
+ args->SetBoolean("sessionsRegistered",
+ registered_types.count(syncable::SESSIONS) > 0);
args->SetBoolean("syncBookmarks",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncBookmarks));
args->SetBoolean("syncPreferences",
@@ -374,6 +380,8 @@ void SyncSetupFlow::GetArgsForChooseDataTypes(ProfileSyncService* service,
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncAutofill));
args->SetBoolean("syncExtensions",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncExtensions));
+ args->SetBoolean("syncSessions",
+ service->profile()->GetPrefs()->GetBoolean(prefs::kSyncSessions));
args->SetBoolean("syncTypedUrls",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncTypedUrls));
args->SetBoolean("syncApps",
diff --git a/chrome/browser/sync/sync_setup_wizard_unittest.cc b/chrome/browser/sync/sync_setup_wizard_unittest.cc
index cc6ffb0..cbced31 100644
--- a/chrome/browser/sync/sync_setup_wizard_unittest.cc
+++ b/chrome/browser/sync/sync_setup_wizard_unittest.cc
@@ -164,8 +164,7 @@ class TestBrowserWindowForWizardTest : public TestBrowserWindow {
class SyncSetupWizardTest : public BrowserWithTestWindowTest {
public:
SyncSetupWizardTest()
- : ui_thread_(ChromeThread::UI, MessageLoop::current()),
- file_thread_(ChromeThread::FILE, MessageLoop::current()),
+ : file_thread_(ChromeThread::FILE, MessageLoop::current()),
test_window_(NULL),
wizard_(NULL) { }
virtual ~SyncSetupWizardTest() { }
@@ -190,7 +189,6 @@ class SyncSetupWizardTest : public BrowserWithTestWindowTest {
wizard_.reset();
}
- ChromeThread ui_thread_;
ChromeThread file_thread_;
TestBrowserWindowForWizardTest* test_window_;
scoped_ptr<SyncSetupWizard> wizard_;
@@ -306,7 +304,8 @@ TEST_F(SyncSetupWizardTest, ChooseDataTypesSetsPrefs) {
data_type_choices += "\"syncBookmarks\":true,\"syncPreferences\":true,";
data_type_choices += "\"syncThemes\":false,\"syncPasswords\":false,";
data_type_choices += "\"syncAutofill\":false,\"syncExtensions\":false,";
- data_type_choices += "\"syncTypedUrls\":true,\"syncApps\":true}";
+ data_type_choices += "\"syncTypedUrls\":true,\"syncApps\":true,";
+ data_type_choices += "\"syncSessions\":false}";
data_type_choices_value.Append(new StringValue(data_type_choices));
// Simulate the user choosing data types; bookmarks, prefs, typed
diff --git a/chrome/browser/sync/syncable/model_type.cc b/chrome/browser/sync/syncable/model_type.cc
index bb1de93..5a06be8 100644
--- a/chrome/browser/sync/syncable/model_type.cc
+++ b/chrome/browser/sync/syncable/model_type.cc
@@ -12,6 +12,7 @@
#include "chrome/browser/sync/protocol/nigori_specifics.pb.h"
#include "chrome/browser/sync/protocol/password_specifics.pb.h"
#include "chrome/browser/sync/protocol/preference_specifics.pb.h"
+#include "chrome/browser/sync/protocol/session_specifics.pb.h"
#include "chrome/browser/sync/protocol/sync.pb.h"
#include "chrome/browser/sync/protocol/theme_specifics.pb.h"
#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
@@ -45,6 +46,9 @@ void AddDefaultExtensionValue(syncable::ModelType datatype,
case NIGORI:
specifics->MutableExtension(sync_pb::nigori);
break;
+ case SESSIONS:
+ specifics->MutableExtension(sync_pb::session);
+ break;
case APPS:
specifics->MutableExtension(sync_pb::app);
break;
@@ -112,6 +116,9 @@ ModelType GetModelTypeFromSpecifics(const sync_pb::EntitySpecifics& specifics) {
if (specifics.HasExtension(sync_pb::app))
return APPS;
+ if (specifics.HasExtension(sync_pb::session))
+ return SESSIONS;
+
return UNSPECIFIED;
}
@@ -133,6 +140,8 @@ std::string ModelTypeToString(ModelType model_type) {
return "Extensions";
case NIGORI:
return "Encryption keys";
+ case SESSIONS:
+ return "Sessions";
case APPS:
return "Apps";
default:
@@ -153,6 +162,7 @@ const char kTypedUrlNotificationType[] = "TYPED_URL";
const char kExtensionNotificationType[] = "EXTENSION";
const char kNigoriNotificationType[] = "NIGORI";
const char kAppNotificationType[] = "APP";
+const char kSessionNotificationType[] = "SESSION";
// TODO(akalin): This is a hack to make new sync data types work with
// server-issued notifications. Remove this when it's not needed
// anymore.
@@ -189,6 +199,9 @@ bool RealModelTypeToNotificationType(ModelType model_type,
case APPS:
*notification_type = kAppNotificationType;
return true;
+ case SESSIONS:
+ *notification_type = kSessionNotificationType;
+ return true;
// TODO(akalin): This is a hack to make new sync data types work with
// server-issued notifications. Remove this when it's not needed
// anymore.
@@ -231,7 +244,11 @@ bool NotificationTypeToRealModelType(const std::string& notification_type,
} else if (notification_type == kAppNotificationType) {
*model_type = APPS;
return true;
- } else if (notification_type == kUnknownNotificationType) {
+ } else if (notification_type == kSessionNotificationType) {
+ *model_type = SESSIONS;
+ return true;
+ }
+ else if (notification_type == kUnknownNotificationType) {
// TODO(akalin): This is a hack to make new sync data types work with
// server-issued notifications. Remove this when it's not needed
// anymore.
diff --git a/chrome/browser/sync/syncable/model_type.h b/chrome/browser/sync/syncable/model_type.h
index 4295d58..077d243 100644
--- a/chrome/browser/sync/syncable/model_type.h
+++ b/chrome/browser/sync/syncable/model_type.h
@@ -58,6 +58,8 @@ enum ModelType {
EXTENSIONS,
// An object represeting a set of Nigori keys.
NIGORI,
+ // An object representing a browser session.
+ SESSIONS,
// An app folder or an app object.
APPS,
diff --git a/chrome/browser/views/bookmark_bar_view_unittest.cc b/chrome/browser/views/bookmark_bar_view_unittest.cc
index 61249b6..29be678 100644
--- a/chrome/browser/views/bookmark_bar_view_unittest.cc
+++ b/chrome/browser/views/bookmark_bar_view_unittest.cc
@@ -11,11 +11,9 @@
class BookmarkBarViewTest : public BrowserWithTestWindowTest {
public:
BookmarkBarViewTest()
- : ui_thread_(ChromeThread::UI, message_loop()),
- file_thread_(ChromeThread::FILE, message_loop()) {}
+ : file_thread_(ChromeThread::FILE, message_loop()) {}
private:
- ChromeThread ui_thread_;
ChromeThread file_thread_;
DISALLOW_COPY_AND_ASSIGN(BookmarkBarViewTest);
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 81b723e..aa110da 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -867,6 +867,8 @@
'<(protoc_out_dir)/chrome/browser/sync/protocol/password_specifics.pb.h',
'<(protoc_out_dir)/chrome/browser/sync/protocol/preference_specifics.pb.cc',
'<(protoc_out_dir)/chrome/browser/sync/protocol/preference_specifics.pb.h',
+ '<(protoc_out_dir)/chrome/browser/sync/protocol/session_specifics.pb.cc',
+ '<(protoc_out_dir)/chrome/browser/sync/protocol/session_specifics.pb.h',
'<(protoc_out_dir)/chrome/browser/sync/protocol/theme_specifics.pb.cc',
'<(protoc_out_dir)/chrome/browser/sync/protocol/theme_specifics.pb.h',
'<(protoc_out_dir)/chrome/browser/sync/protocol/typed_url_specifics.pb.cc',
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 4400764..b21dc00 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1190,6 +1190,8 @@
'browser/dom_ui/fileicon_source.h',
'browser/dom_ui/filebrowse_ui.cc',
'browser/dom_ui/filebrowse_ui.h',
+ 'browser/dom_ui/foreign_session_handler.cc',
+ 'browser/dom_ui/foreign_session_handler.h',
'browser/dom_ui/history_ui.cc',
'browser/dom_ui/history_ui.h',
'browser/dom_ui/history2_ui.cc',
@@ -1230,6 +1232,8 @@
'browser/dom_ui/stop_syncing_handler.h',
'browser/dom_ui/tips_handler.cc',
'browser/dom_ui/tips_handler.h',
+ 'browser/dom_ui/value_helper.cc',
+ 'browser/dom_ui/value_helper.h',
'browser/download/download_exe.cc',
'browser/download/download_file.cc',
'browser/download/download_file.h',
@@ -2472,6 +2476,12 @@
'browser/sync/glue/preference_data_type_controller.h',
'browser/sync/glue/preference_model_associator.cc',
'browser/sync/glue/preference_model_associator.h',
+ 'browser/sync/glue/session_change_processor.cc',
+ 'browser/sync/glue/session_change_processor.h',
+ 'browser/sync/glue/session_data_type_controller.cc',
+ 'browser/sync/glue/session_data_type_controller.h',
+ 'browser/sync/glue/session_model_associator.cc',
+ 'browser/sync/glue/session_model_associator.h',
'browser/sync/glue/sync_backend_host.cc',
'browser/sync/glue/sync_backend_host.h',
'browser/sync/glue/synchronized_preferences.h',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 28ac169..4a5473f 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1126,6 +1126,7 @@
'browser/sync/profile_sync_service_mock.h',
'browser/sync/profile_sync_service_password_unittest.cc',
'browser/sync/profile_sync_service_preference_unittest.cc',
+ 'browser/sync/profile_sync_service_session_unittest.cc',
'browser/sync/profile_sync_service_startup_unittest.cc',
'browser/sync/profile_sync_service_typed_url_unittest.cc',
'browser/sync/profile_sync_service_unittest.cc',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index b99a8cb..630c4b7 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -243,6 +243,9 @@ const char kDisableSyncPasswords[] = "disable-sync-passwords";
// Disable syncing of preferences.
const char kDisableSyncPreferences[] = "disable-sync-preferences";
+// Disable syncing of sessions.
+const char kDisableSyncSessions[] = "disable-sync-sessions";
+
// Disable syncing of themes.
const char kDisableSyncThemes[] = "disable-sync-themes";
@@ -436,6 +439,9 @@ const char kEnableSyncPasswords[] = "enable-sync-passwords";
// Enable syncing browser preferences.
const char kEnableSyncPreferences[] = "enable-sync-preferences";
+// Enable syncing browser sessions.
+const char kEnableSyncSessions[] = "enable-sync-sessions";
+
// Enable syncing browser themes.
const char kEnableSyncThemes[] = "enable-sync-themes";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 1c6c116d0..9591d5c 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -87,6 +87,7 @@ extern const char kDisableSyncPasswords[];
extern const char kDisableSyncPreferences[];
extern const char kDisableSyncThemes[];
extern const char kDisableSyncTypedUrls[];
+extern const char kDisableSyncSessions[];
extern const char kDisableTabCloseableStateWatcher[];
extern const char kDisableWebResources[];
extern const char kDisableWebSecurity[];
@@ -140,6 +141,7 @@ extern const char kEnableSyncBookmarks[];
extern const char kEnableSyncExtensions[];
extern const char kEnableSyncPasswords[];
extern const char kEnableSyncPreferences[];
+extern const char kEnableSyncSessions[];
extern const char kEnableSyncThemes[];
extern const char kEnableSyncTypedUrls[];
extern const char kEnableTabbedOptions[];
diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h
index 0a0c58f..48871b4 100644
--- a/chrome/common/notification_type.h
+++ b/chrome/common/notification_type.h
@@ -1017,6 +1017,16 @@ class NotificationType {
// operations.
SESSION_SERVICE_SAVED,
+ // A foreign session has been updated. If a new tab page is open, the
+ // foreign session handler needs to update the new tab page's foreign
+ // session data.
+ FOREIGN_SESSION_UPDATED,
+
+ // A foreign session has been deleted. If a new tab page is open, the
+ // foreign session handler needs to update the new tab page's foreign
+ // session data.
+ FOREIGN_SESSION_DELETED,
+
// The syncer requires a passphrase to decrypt sensitive updates. This
// notification is sent when the first sensitive data type is setup by the
// user as well as anytime any the passphrase is changed in another synced
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 01f3a6a2..40183d1 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -883,6 +883,7 @@ const char kSyncAutofill[] = "sync.autofill";
const char kSyncThemes[] = "sync.themes";
const char kSyncTypedUrls[] = "sync.typed_urls";
const char kSyncExtensions[] = "sync.extensions";
+const char kSyncSessions[] = "sync.sessions";
// Boolean used by enterprise configuration management in order to lock down
// sync.
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 21d44db..f27005a 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -318,6 +318,7 @@ extern const char kNTPPrefVersion[];
extern const char kDevToolsOpenDocked[];
extern const char kDevToolsSplitLocation[];
+extern const char kSyncSessions[];
extern const char kSyncLastSyncedTime[];
extern const char kSyncHasSetupCompleted[];
diff --git a/chrome/test/browser_with_test_window_test.cc b/chrome/test/browser_with_test_window_test.cc
index 30ec42b..bf6f308 100644
--- a/chrome/test/browser_with_test_window_test.cc
+++ b/chrome/test/browser_with_test_window_test.cc
@@ -14,7 +14,8 @@
#include "chrome/test/testing_profile.h"
BrowserWithTestWindowTest::BrowserWithTestWindowTest()
- : rph_factory_(),
+ : ui_thread_(ChromeThread::UI, message_loop()),
+ rph_factory_(),
rvh_factory_(&rph_factory_) {
#if defined(OS_WIN)
OleInitialize(NULL);
diff --git a/chrome/test/browser_with_test_window_test.h b/chrome/test/browser_with_test_window_test.h
index ae85634..c57a2f9 100644
--- a/chrome/test/browser_with_test_window_test.h
+++ b/chrome/test/browser_with_test_window_test.h
@@ -7,6 +7,7 @@
#pragma once
#include "base/message_loop.h"
+#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/renderer_host/test/test_render_view_host.h"
#include "chrome/test/test_browser_window.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -86,8 +87,10 @@ class BrowserWithTestWindowTest : public testing::Test {
void NavigateAndCommitActiveTab(const GURL& url);
private:
+
// We need to create a MessageLoop, otherwise a bunch of things fails.
MessageLoopForUI ui_loop_;
+ ChromeThread ui_thread_;
scoped_ptr<TestingProfile> profile_;
scoped_ptr<TestBrowserWindow> window_;