diff options
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_; |