summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/history/history.cc7
-rw-r--r--chrome/browser/history/history.h30
-rw-r--r--chrome/browser/history/history_backend.cc31
-rw-r--r--chrome/browser/history/history_backend.h18
-rw-r--r--chrome/browser/history/url_database.cc14
-rw-r--r--chrome/browser/history/url_database.h4
-rw-r--r--chrome/browser/sync/engine/model_safe_worker.h2
-rw-r--r--chrome/browser/sync/engine/syncapi.cc19
-rw-r--r--chrome/browser/sync/engine/syncapi.h11
-rw-r--r--chrome/browser/sync/glue/autofill_model_associator.cc2
-rw-r--r--chrome/browser/sync/glue/data_type_manager_impl.cc3
-rw-r--r--chrome/browser/sync/glue/history_model_worker.cc53
-rw-r--r--chrome/browser/sync/glue/history_model_worker.h47
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.cc9
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.h6
-rw-r--r--chrome/browser/sync/glue/typed_url_change_processor.cc239
-rw-r--r--chrome/browser/sync/glue/typed_url_change_processor.h95
-rw-r--r--chrome/browser/sync/glue/typed_url_data_type_controller.cc209
-rw-r--r--chrome/browser/sync/glue/typed_url_data_type_controller.h113
-rw-r--r--chrome/browser/sync/glue/typed_url_model_associator.cc338
-rw-r--r--chrome/browser/sync/glue/typed_url_model_associator.h145
-rw-r--r--chrome/browser/sync/glue/typed_url_model_associator_unittest.cc120
-rw-r--r--chrome/browser/sync/profile_sync_factory.h12
-rw-r--r--chrome/browser/sync/profile_sync_factory_impl.cc31
-rw-r--r--chrome/browser/sync/profile_sync_factory_impl.h5
-rw-r--r--chrome/browser/sync/profile_sync_factory_mock.h5
-rw-r--r--chrome/browser/sync/profile_sync_service.cc3
-rw-r--r--chrome/browser/sync/profile_sync_service_typed_url_unittest.cc498
-rw-r--r--chrome/browser/sync/protocol/typed_url_specifics.proto27
-rw-r--r--chrome/browser/sync/syncable/model_type.cc7
-rw-r--r--chrome/browser/sync/syncable/model_type.h1
-rw-r--r--chrome/browser/sync/syncable/syncable.cc5
-rw-r--r--chrome/chrome.gyp3
-rwxr-xr-xchrome/chrome_browser.gypi8
-rw-r--r--chrome/chrome_tests.gypi2
-rw-r--r--chrome/common/chrome_switches.cc6
-rw-r--r--chrome/common/chrome_switches.h2
-rw-r--r--chrome/test/profile_mock.h2
38 files changed, 2109 insertions, 23 deletions
diff --git a/chrome/browser/history/history.cc b/chrome/browser/history/history.cc
index b1b9d09..fe85e5f 100644
--- a/chrome/browser/history/history.cc
+++ b/chrome/browser/history/history.cc
@@ -86,6 +86,13 @@ class HistoryService::BackendDelegate : public HistoryBackend::Delegate {
virtual void BroadcastNotifications(NotificationType type,
history::HistoryDetails* details) {
+ // Send the notification on the history thread.
+ if (NotificationService::current()) {
+ Details<history::HistoryDetails> det(details);
+ NotificationService::current()->Notify(type,
+ NotificationService::AllSources(),
+ det);
+ }
// Send the notification to the history service on the main thread.
message_loop_->PostTask(FROM_HERE, NewRunnableMethod(history_service_.get(),
&HistoryService::BroadcastNotifications, type, details));
diff --git a/chrome/browser/history/history.h b/chrome/browser/history/history.h
index 8f7c68c..2326f8b 100644
--- a/chrome/browser/history/history.h
+++ b/chrome/browser/history/history.h
@@ -41,6 +41,11 @@ class Thread;
class Time;
}
+namespace browser_sync {
+class HistoryModelWorker;
+class TypedUrlDataTypeController;
+}
+
namespace history {
class InMemoryHistoryBackend;
@@ -492,8 +497,8 @@ class HistoryService : public CancelableRequestProvider,
// Schedules a HistoryDBTask for running on the history backend thread. See
// HistoryDBTask for details on what this does.
- Handle ScheduleDBTask(HistoryDBTask* task,
- CancelableRequestConsumerBase* consumer);
+ virtual Handle ScheduleDBTask(HistoryDBTask* task,
+ CancelableRequestConsumerBase* consumer);
// Testing -------------------------------------------------------------------
@@ -525,6 +530,17 @@ class HistoryService : public CancelableRequestProvider,
// The same as AddPageWithDetails() but takes a vector.
void AddPagesWithDetails(const std::vector<history::URLRow>& info);
+ protected:
+ ~HistoryService();
+
+ // These are not currently used, hopefully we can do something in the future
+ // to ensure that the most important things happen first.
+ enum SchedulePriority {
+ PRIORITY_UI, // The highest priority (must respond to UI events).
+ PRIORITY_NORMAL, // Normal stuff like adding a page.
+ PRIORITY_LOW, // Low priority things like indexing or expiration.
+ };
+
private:
class BackendDelegate;
friend class base::RefCountedThreadSafe<HistoryService>;
@@ -541,16 +557,6 @@ class HistoryService : public CancelableRequestProvider,
friend class FavIconRequest;
friend class TestingProfile;
- ~HistoryService();
-
- // These are not currently used, hopefully we can do something in the future
- // to ensure that the most important things happen first.
- enum SchedulePriority {
- PRIORITY_UI, // The highest priority (must respond to UI events).
- PRIORITY_NORMAL, // Normal stuff like adding a page.
- PRIORITY_LOW, // Low priority things like indexing or expiration.
- };
-
// Implementation of NotificationObserver.
virtual void Observe(NotificationType type,
const NotificationSource& source,
diff --git a/chrome/browser/history/history_backend.cc b/chrome/browser/history/history_backend.cc
index 420095e..58fa489 100644
--- a/chrome/browser/history/history_backend.cc
+++ b/chrome/browser/history/history_backend.cc
@@ -689,7 +689,6 @@ std::pair<URLID, VisitID> HistoryBackend::AddPageVisit(
return std::make_pair(url_id, visit_id);
}
-// Note: this method is only for testing purposes.
void HistoryBackend::AddPagesWithDetails(const std::vector<URLRow>& urls) {
if (!db_.get())
return;
@@ -850,6 +849,24 @@ void HistoryBackend::IterateURLs(HistoryService::URLEnumerator* iterator) {
iterator->OnComplete(false); // Failure.
}
+bool HistoryBackend::GetAllTypedURLs(std::vector<history::URLRow>* urls) {
+ if (db_.get())
+ return db_->GetAllTypedUrls(urls);
+ return false;
+}
+
+bool HistoryBackend::UpdateURL(const URLID id, const history::URLRow& url) {
+ if (db_.get())
+ return db_->UpdateURLRow(id, url);
+ return false;
+}
+
+bool HistoryBackend::GetURL(const GURL& url, history::URLRow* url_row) {
+ if (db_.get())
+ return db_->GetRowForURL(url, url_row) != 0;
+ return false;
+}
+
void HistoryBackend::QueryURL(scoped_refptr<QueryURLRequest> request,
const GURL& url,
bool want_visits) {
@@ -1730,6 +1747,18 @@ void HistoryBackend::ReleaseDBTasks() {
//
////////////////////////////////////////////////////////////////////////////////
+void HistoryBackend::DeleteURLs(const std::vector<GURL>& urls) {
+ for (std::vector<GURL>::const_iterator url = urls.begin(); url != urls.end();
+ ++url) {
+ expirer_.DeleteURL(*url);
+ }
+
+ db_->GetStartDate(&first_recorded_time_);
+ // Force a commit, if the user is deleting something for privacy reasons, we
+ // want to get it on disk ASAP.
+ Commit();
+}
+
void HistoryBackend::DeleteURL(const GURL& url) {
expirer_.DeleteURL(url);
diff --git a/chrome/browser/history/history_backend.h b/chrome/browser/history/history_backend.h
index cfd1637..7d230c6 100644
--- a/chrome/browser/history/history_backend.h
+++ b/chrome/browser/history/history_backend.h
@@ -112,8 +112,7 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
// Navigation ----------------------------------------------------------------
void AddPage(scoped_refptr<HistoryAddPageArgs> request);
- void SetPageTitle(const GURL& url, const std::wstring& title);
- void AddPageWithDetails(const URLRow& info);
+ virtual void SetPageTitle(const GURL& url, const std::wstring& title);
// Indexing ------------------------------------------------------------------
@@ -239,9 +238,17 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
void ProcessDBTask(scoped_refptr<HistoryDBTaskRequest> request);
+ virtual bool GetAllTypedURLs(std::vector<history::URLRow>* urls);
+
+ virtual bool UpdateURL(const URLID id, const history::URLRow& url);
+
+ virtual bool GetURL(const GURL& url, history::URLRow* url_row);
+
// Deleting ------------------------------------------------------------------
- void DeleteURL(const GURL& url);
+ virtual void DeleteURLs(const std::vector<GURL>& urls);
+
+ virtual void DeleteURL(const GURL& url);
// Calls ExpireHistoryBackend::ExpireHistoryBetween and commits the change.
void ExpireHistoryBetween(scoped_refptr<ExpireHistoryRequest> request,
@@ -272,6 +279,9 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
ExpireHistoryBackend* expire_backend() { return &expirer_; }
#endif
+ protected:
+ virtual ~HistoryBackend();
+
private:
friend class base::RefCountedThreadSafe<HistoryBackend>;
friend class CommitLaterTask; // The commit task needs to call Commit().
@@ -282,8 +292,6 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
FRIEND_TEST(HistoryBackendTest, StripUsernamePasswordTest);
friend class ::TestingProfile;
- ~HistoryBackend();
-
// Computes the name of the specified database on disk.
FilePath GetThumbnailFileName() const;
FilePath GetArchivedFileName() const;
diff --git a/chrome/browser/history/url_database.cc b/chrome/browser/history/url_database.cc
index c5c6199..e1c8106 100644
--- a/chrome/browser/history/url_database.cc
+++ b/chrome/browser/history/url_database.cc
@@ -79,6 +79,20 @@ bool URLDatabase::GetURLRow(URLID url_id, URLRow* info) {
return false;
}
+bool URLDatabase::GetAllTypedUrls(std::vector<history::URLRow>* urls) {
+ sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE typed_count > 0"));
+ if (!statement)
+ return false;
+
+ while (statement.Step()) {
+ URLRow info;
+ FillURLRow(statement, &info);
+ urls->push_back(info);
+ }
+ return true;
+}
+
URLID URLDatabase::GetRowForURL(const GURL& url, history::URLRow* info) {
sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
"SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE url=?"));
diff --git a/chrome/browser/history/url_database.h b/chrome/browser/history/url_database.h
index 6e2eca1..9fb67b4 100644
--- a/chrome/browser/history/url_database.h
+++ b/chrome/browser/history/url_database.h
@@ -54,6 +54,10 @@ class URLDatabase {
// success and false otherwise.
bool GetURLRow(URLID url_id, URLRow* info);
+ // Looks up all urls that were typed in manually. Fills info with the data.
+ // Returns true on success and false otherwise.
+ bool GetAllTypedUrls(std::vector<history::URLRow>* urls);
+
// Looks up the given URL and if it exists, fills the given pointers with the
// associated info and returns the ID of that URL. If the info pointer is
// NULL, no information about the URL will be filled in, only the ID will be
diff --git a/chrome/browser/sync/engine/model_safe_worker.h b/chrome/browser/sync/engine/model_safe_worker.h
index 5e5b70d..df2edae 100644
--- a/chrome/browser/sync/engine/model_safe_worker.h
+++ b/chrome/browser/sync/engine/model_safe_worker.h
@@ -21,6 +21,8 @@ enum ModelSafeGroup {
// native model.
GROUP_UI, // Models that live on UI thread and are being synced.
GROUP_DB, // Models that live on DB thread and are being synced.
+ GROUP_HISTORY, // Models that live on history thread and are being
+ // synced.
MODEL_SAFE_GROUP_COUNT,
};
diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc
index 484e6c3..433475c 100644
--- a/chrome/browser/sync/engine/syncapi.cc
+++ b/chrome/browser/sync/engine/syncapi.cc
@@ -52,6 +52,7 @@
#include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
#include "chrome/browser/sync/protocol/preference_specifics.pb.h"
#include "chrome/browser/sync/protocol/service_constants.h"
+#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
#include "chrome/browser/sync/sessions/sync_session_context.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
#include "chrome/browser/sync/syncable/syncable.h"
@@ -482,6 +483,11 @@ const sync_pb::PreferenceSpecifics& BaseNode::GetPreferenceSpecifics() const {
return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::preference);
}
+const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const {
+ DCHECK(GetModelType() == syncable::TYPED_URLS);
+ return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::typed_url);
+}
+
syncable::ModelType BaseNode::GetModelType() const {
return GetEntry()->GetModelType();
}
@@ -554,6 +560,19 @@ void WriteNode::PutPreferenceSpecificsAndMarkForSyncing(
PutSpecificsAndMarkForSyncing(entity_specifics);
}
+void WriteNode::SetTypedUrlSpecifics(
+ const sync_pb::TypedUrlSpecifics& new_value) {
+ DCHECK(GetModelType() == syncable::TYPED_URLS);
+ PutTypedUrlSpecificsAndMarkForSyncing(new_value);
+}
+
+void WriteNode::PutTypedUrlSpecificsAndMarkForSyncing(
+ const sync_pb::TypedUrlSpecifics& new_value) {
+ sync_pb::EntitySpecifics entity_specifics;
+ entity_specifics.MutableExtension(sync_pb::typed_url)->CopyFrom(new_value);
+ PutSpecificsAndMarkForSyncing(entity_specifics);
+}
+
void WriteNode::PutSpecificsAndMarkForSyncing(
const sync_pb::EntitySpecifics& specifics) {
// Skip redundant changes.
diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h
index 459ff40..ca1cc01 100644
--- a/chrome/browser/sync/engine/syncapi.h
+++ b/chrome/browser/sync/engine/syncapi.h
@@ -72,6 +72,7 @@ class AutofillSpecifics;
class BookmarkSpecifics;
class EntitySpecifics;
class PreferenceSpecifics;
+class TypedUrlSpecifics;
}
namespace sync_api {
@@ -170,6 +171,10 @@ class BaseNode {
// data. Can only be called if GetModelType() == PREFERENCE.
const sync_pb::PreferenceSpecifics& GetPreferenceSpecifics() const;
+ // Getter specific to the TYPED_URLS datatype. Returns protobuf
+ // data. Can only be called if GetModelType() == TYPED_URLS.
+ const sync_pb::TypedUrlSpecifics& GetTypedUrlSpecifics() const;
+
// Returns the local external ID associated with the node.
int64 GetExternalId() const;
@@ -277,6 +282,10 @@ class WriteNode : public BaseNode {
// Should only be called if GetModelType() == PREFERENCE.
void SetPreferenceSpecifics(const sync_pb::PreferenceSpecifics& specifics);
+ // Set the typed_url specifics (url, title, typed_count, etc).
+ // Should only be called if GetModelType() == TYPED_URLS.
+ void SetTypedUrlSpecifics(const sync_pb::TypedUrlSpecifics& specifics);
+
// Implementation of BaseNode's abstract virtual accessors.
virtual const syncable::Entry* GetEntry() const;
@@ -302,6 +311,8 @@ class WriteNode : public BaseNode {
const sync_pb::BookmarkSpecifics& new_value);
void PutPreferenceSpecificsAndMarkForSyncing(
const sync_pb::PreferenceSpecifics& new_value);
+ void PutTypedUrlSpecificsAndMarkForSyncing(
+ const sync_pb::TypedUrlSpecifics& new_value);
void PutSpecificsAndMarkForSyncing(
const sync_pb::EntitySpecifics& specifics);
diff --git a/chrome/browser/sync/glue/autofill_model_associator.cc b/chrome/browser/sync/glue/autofill_model_associator.cc
index ccda440..57d1bae 100644
--- a/chrome/browser/sync/glue/autofill_model_associator.cc
+++ b/chrome/browser/sync/glue/autofill_model_associator.cc
@@ -108,7 +108,7 @@ bool AutofillModelAssociator::AssociateModels() {
sync_api::ReadNode sync_child_node(&trans);
if (!sync_child_node.InitByIdLookup(sync_child_id)) {
LOG(ERROR) << "Failed to fetch child node.";
- sync_service_->OnUnrecoverableError();
+ error_handler_->OnUnrecoverableError();
return false;
}
const sync_pb::AutofillSpecifics& autofill(
diff --git a/chrome/browser/sync/glue/data_type_manager_impl.cc b/chrome/browser/sync/glue/data_type_manager_impl.cc
index aba040c..12cd4d2 100644
--- a/chrome/browser/sync/glue/data_type_manager_impl.cc
+++ b/chrome/browser/sync/glue/data_type_manager_impl.cc
@@ -18,7 +18,8 @@ namespace {
static const syncable::ModelType kStartOrder[] = {
syncable::BOOKMARKS,
syncable::PREFERENCES,
- syncable::AUTOFILL
+ syncable::AUTOFILL,
+ syncable::TYPED_URLS,
};
} // namespace
diff --git a/chrome/browser/sync/glue/history_model_worker.cc b/chrome/browser/sync/glue/history_model_worker.cc
new file mode 100644
index 0000000..cab697c
--- /dev/null
+++ b/chrome/browser/sync/glue/history_model_worker.cc
@@ -0,0 +1,53 @@
+// 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/history_model_worker.h"
+
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/ref_counted.h"
+#include "base/task.h"
+#include "base/waitable_event.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/history/history.h"
+#include "chrome/browser/sync/util/closure.h"
+
+using base::WaitableEvent;
+
+namespace browser_sync {
+
+class WorkerTask : public HistoryDBTask {
+ public:
+ WorkerTask(Closure* work, WaitableEvent* done)
+ : work_(work), done_(done) {}
+
+ virtual bool RunOnDBThread(history::HistoryBackend* backend,
+ history::HistoryDatabase* db) {
+ work_->Run();
+ done_->Signal();
+ return true;
+ }
+
+ // Since the DoWorkAndWaitUntilDone() is syncronous, we don't need to run any
+ // code asynchronously on the main thread after completion.
+ virtual void DoneRunOnMainThread() {}
+
+ protected:
+ Closure* work_;
+ WaitableEvent* done_;
+};
+
+
+HistoryModelWorker::HistoryModelWorker(HistoryService* history_service)
+ : history_service_(history_service) {
+}
+
+void HistoryModelWorker::DoWorkAndWaitUntilDone(Closure* work) {
+ WaitableEvent done(false, false);
+ scoped_refptr<WorkerTask> task = new WorkerTask(work, &done);
+ history_service_->ScheduleDBTask(task.get(), this);
+ done.Wait();
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/history_model_worker.h b/chrome/browser/sync/glue/history_model_worker.h
new file mode 100644
index 0000000..d9fa5ed
--- /dev/null
+++ b/chrome/browser/sync/glue/history_model_worker.h
@@ -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.
+
+#ifndef CHROME_BROWSER_SYNC_GLUE_HISTORY_MODEL_WORKER_H_
+#define CHROME_BROWSER_SYNC_GLUE_HISTORY_MODEL_WORKER_H_
+
+#include "chrome/browser/sync/engine/model_safe_worker.h"
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "chrome/browser/cancelable_request.h"
+#include "chrome/browser/sync/util/closure.h"
+
+class HistoryService;
+
+namespace base {
+class WaitableEvent;
+}
+
+namespace browser_sync {
+
+// A ModelSafeWorker for history models that accepts requests
+// from the syncapi that need to be fulfilled on the history thread.
+class HistoryModelWorker : public browser_sync::ModelSafeWorker,
+ public CancelableRequestConsumerBase {
+ public:
+ explicit HistoryModelWorker(HistoryService* history_service);
+
+ // ModelSafeWorker implementation. Called on syncapi SyncerThread.
+ void DoWorkAndWaitUntilDone(Closure* work);
+ virtual ModelSafeGroup GetModelSafeGroup() { return GROUP_HISTORY; }
+
+ // CancelableRequestConsumerBase implementation.
+ virtual void OnRequestAdded(CancelableRequestProvider* provider,
+ CancelableRequestProvider::Handle handle) {}
+
+ virtual void OnRequestRemoved(CancelableRequestProvider* provider,
+ CancelableRequestProvider::Handle handle) {}
+ private:
+ scoped_refptr<HistoryService> history_service_;
+ DISALLOW_COPY_AND_ASSIGN(HistoryModelWorker);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_HISTORY_MODEL_WORKER_H_
diff --git a/chrome/browser/sync/glue/sync_backend_host.cc b/chrome/browser/sync/glue/sync_backend_host.cc
index 113a4b1..51a8f86 100644
--- a/chrome/browser/sync/glue/sync_backend_host.cc
+++ b/chrome/browser/sync/glue/sync_backend_host.cc
@@ -7,8 +7,10 @@
#include "base/file_version_info.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/profile.h"
#include "chrome/browser/sync/glue/change_processor.h"
#include "chrome/browser/sync/glue/database_model_worker.h"
+#include "chrome/browser/sync/glue/history_model_worker.h"
#include "chrome/browser/sync/glue/sync_backend_host.h"
#include "chrome/browser/sync/glue/http_bridge.h"
#include "chrome/common/notification_service.h"
@@ -29,10 +31,12 @@ namespace browser_sync {
SyncBackendHost::SyncBackendHost(
SyncFrontend* frontend,
+ Profile* profile,
const FilePath& profile_path,
const DataTypeController::TypeMap& data_type_controllers)
: core_thread_("Chrome_SyncCoreThread"),
frontend_loop_(MessageLoop::current()),
+ profile_(profile),
frontend_(frontend),
sync_data_folder_path_(profile_path.Append(kSyncDataFolderName)),
data_type_controllers_(data_type_controllers),
@@ -71,6 +75,9 @@ void SyncBackendHost::Initialize(
// when a new type is synced as the worker may already exist and you just
// need to update routing_info_.
registrar_.workers[GROUP_DB] = new DatabaseModelWorker();
+ registrar_.workers[GROUP_HISTORY] =
+ new HistoryModelWorker(
+ profile_->GetHistoryService(Profile::IMPLICIT_ACCESS));
registrar_.workers[GROUP_UI] = new UIModelWorker(frontend_loop_);
registrar_.workers[GROUP_PASSIVE] = new ModelSafeWorker();
@@ -135,9 +142,11 @@ void SyncBackendHost::Shutdown(bool sync_disabled) {
registrar_.routing_info.clear();
registrar_.workers[GROUP_DB] = NULL;
+ registrar_.workers[GROUP_HISTORY] = NULL;
registrar_.workers[GROUP_UI] = NULL;
registrar_.workers[GROUP_PASSIVE] = NULL;
registrar_.workers.erase(GROUP_DB);
+ registrar_.workers.erase(GROUP_HISTORY);
registrar_.workers.erase(GROUP_UI);
registrar_.workers.erase(GROUP_PASSIVE);
frontend_ = NULL;
diff --git a/chrome/browser/sync/glue/sync_backend_host.h b/chrome/browser/sync/glue/sync_backend_host.h
index 405fe35..8edc7ba 100644
--- a/chrome/browser/sync/glue/sync_backend_host.h
+++ b/chrome/browser/sync/glue/sync_backend_host.h
@@ -25,6 +25,8 @@
#include "chrome/browser/sync/syncable/model_type.h"
#include "googleurl/src/gurl.h"
+class Profile;
+
namespace browser_sync {
class ChangeProcessor;
@@ -74,6 +76,7 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
// and communicates to via the SyncFrontend interface (on the same thread
// it used to call the constructor).
SyncBackendHost(SyncFrontend* frontend,
+ Profile* profile,
const FilePath& profile_path,
const DataTypeController::TypeMap& data_type_controllers);
// For testing.
@@ -160,6 +163,7 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
registrar_.routing_info[syncable::BOOKMARKS] = GROUP_PASSIVE;
registrar_.routing_info[syncable::PREFERENCES] = GROUP_PASSIVE;
registrar_.routing_info[syncable::AUTOFILL] = GROUP_PASSIVE;
+ registrar_.routing_info[syncable::TYPED_URLS] = GROUP_PASSIVE;
core_thread_.message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(core_.get(),
@@ -356,6 +360,8 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
// to safely talk back to the SyncFrontend.
MessageLoop* const frontend_loop_;
+ Profile* profile_;
+
// This is state required to implement ModelSafeWorkerRegistrar.
struct {
// We maintain ownership of all workers. In some cases, we need to ensure
diff --git a/chrome/browser/sync/glue/typed_url_change_processor.cc b/chrome/browser/sync/glue/typed_url_change_processor.cc
new file mode 100644
index 0000000..709c5e6
--- /dev/null
+++ b/chrome/browser/sync/glue/typed_url_change_processor.cc
@@ -0,0 +1,239 @@
+// 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/typed_url_change_processor.h"
+
+#include "base/string_util.h"
+#include "chrome/browser/history/history_backend.h"
+#include "chrome/browser/history/history_notifications.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/sync/glue/typed_url_model_associator.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/notification_type.h"
+
+namespace browser_sync {
+
+TypedUrlChangeProcessor::TypedUrlChangeProcessor(
+ TypedUrlModelAssociator* model_associator,
+ history::HistoryBackend* history_backend,
+ UnrecoverableErrorHandler* error_handler)
+ : ChangeProcessor(error_handler),
+ model_associator_(model_associator),
+ history_backend_(history_backend),
+ observing_(false),
+ expected_loop_(MessageLoop::current()) {
+ DCHECK(model_associator);
+ DCHECK(history_backend);
+ DCHECK(error_handler);
+ DCHECK(!ChromeThread::CurrentlyOn(ChromeThread::UI));
+ // When running in unit tests, there is already a NotificationService object.
+ // Since only one can exist at a time per thread, check first.
+ if (!NotificationService::current())
+ notification_service_.reset(new NotificationService);
+ StartObserving();
+}
+
+TypedUrlChangeProcessor::~TypedUrlChangeProcessor() {
+ DCHECK(expected_loop_ == MessageLoop::current());
+}
+
+void TypedUrlChangeProcessor::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ if (!observing_)
+ return;
+
+ LOG(INFO) << "Observed typed_url change.";
+ DCHECK(running());
+ DCHECK(NotificationType::HISTORY_TYPED_URLS_MODIFIED == type ||
+ NotificationType::HISTORY_URLS_DELETED == type);
+ if (type == NotificationType::HISTORY_TYPED_URLS_MODIFIED) {
+ HandleURLsModified(Details<history::URLsModifiedDetails>(details).ptr());
+ } else if (type == NotificationType::HISTORY_URLS_DELETED) {
+ HandleURLsDeleted(Details<history::URLsDeletedDetails>(details).ptr());
+ }
+}
+
+void TypedUrlChangeProcessor::HandleURLsModified(
+ history::URLsModifiedDetails* details) {
+ sync_api::WriteTransaction trans(share_handle());
+
+ sync_api::ReadNode typed_url_root(&trans);
+ if (!typed_url_root.InitByTagLookup(kTypedUrlTag)) {
+ error_handler()->OnUnrecoverableError();
+ LOG(ERROR) << "Server did not create the top-level typed_url node. We "
+ << "might be running against an out-of-date server.";
+ return;
+ }
+
+ for (std::vector<history::URLRow>::iterator url =
+ details->changed_urls.begin(); url != details->changed_urls.end();
+ ++url) {
+ std::string tag = url->url().spec();
+
+ sync_api::WriteNode update_node(&trans);
+ if (update_node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) {
+ model_associator_->WriteToSyncNode(*url, &update_node);
+ } else {
+ sync_api::WriteNode create_node(&trans);
+ if (!create_node.InitUniqueByCreation(syncable::TYPED_URLS,
+ typed_url_root, tag)) {
+ LOG(ERROR) << "Failed to create typed_url sync node.";
+ error_handler()->OnUnrecoverableError();
+ return;
+ }
+
+ create_node.SetTitle(UTF8ToWide(tag));
+ model_associator_->WriteToSyncNode(*url, &create_node);
+
+ model_associator_->Associate(&tag, create_node.GetId());
+ }
+ }
+}
+
+void TypedUrlChangeProcessor::HandleURLsDeleted(
+ history::URLsDeletedDetails* details) {
+ sync_api::WriteTransaction trans(share_handle());
+
+ if (details->all_history) {
+ if (!model_associator_->DeleteAllNodes(&trans)) {
+ error_handler()->OnUnrecoverableError();
+ return;
+ }
+ } else {
+ for (std::set<GURL>::iterator url = details->urls.begin();
+ url != details->urls.end(); ++url) {
+ sync_api::WriteNode sync_node(&trans);
+ int64 sync_id =
+ model_associator_->GetSyncIdFromChromeId(url->spec());
+ if (sync_api::kInvalidId == sync_id) {
+ LOG(ERROR) << "Unexpected notification for: " <<
+ url->spec();
+ error_handler()->OnUnrecoverableError();
+ return;
+ } else {
+ if (!sync_node.InitByIdLookup(sync_id)) {
+ LOG(ERROR) << "Typed url node lookup failed.";
+ error_handler()->OnUnrecoverableError();
+ return;
+ }
+ model_associator_->Disassociate(sync_node.GetId());
+ sync_node.Remove();
+ }
+ }
+ }
+}
+
+void TypedUrlChangeProcessor::ApplyChangesFromSyncModel(
+ const sync_api::BaseTransaction* trans,
+ const sync_api::SyncManager::ChangeRecord* changes,
+ int change_count) {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ if (!running())
+ return;
+ StopObserving();
+
+ sync_api::ReadNode typed_url_root(trans);
+ if (!typed_url_root.InitByTagLookup(kTypedUrlTag)) {
+ LOG(ERROR) << "TypedUrl root node lookup failed.";
+ error_handler()->OnUnrecoverableError();
+ return;
+ }
+
+ TypedUrlModelAssociator::TypedUrlTitleVector titles;
+ TypedUrlModelAssociator::TypedUrlVector new_urls;
+ TypedUrlModelAssociator::TypedUrlUpdateVector updated_urls;
+
+ for (int i = 0; i < change_count; ++i) {
+
+ sync_api::ReadNode sync_node(trans);
+ if (!sync_node.InitByIdLookup(changes[i].id)) {
+ LOG(ERROR) << "TypedUrl node lookup failed.";
+ error_handler()->OnUnrecoverableError();
+ return;
+ }
+
+ // Check that the changed node is a child of the typed_urls folder.
+ DCHECK(typed_url_root.GetId() == sync_node.GetParentId());
+ DCHECK(syncable::TYPED_URLS == sync_node.GetModelType());
+
+ const sync_pb::TypedUrlSpecifics& typed_url(
+ sync_node.GetTypedUrlSpecifics());
+ if (sync_api::SyncManager::ChangeRecord::ACTION_ADD == changes[i].action) {
+ history::URLRow new_url(GURL(typed_url.url()));
+ new_url.set_title(UTF8ToWide(typed_url.title()));
+ new_url.set_visit_count(typed_url.visit_count());
+ new_url.set_typed_count(typed_url.typed_count());
+ new_url.set_last_visit(
+ base::Time::FromInternalValue(typed_url.last_visit()));
+ new_url.set_hidden(typed_url.hidden());
+
+ new_urls.push_back(new_url);
+ } else if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE ==
+ changes[i].action) {
+ history_backend_->DeleteURL(GURL(typed_url.url()));
+ } else {
+ history::URLRow old_url;
+ if (!history_backend_->GetURL(GURL(typed_url.url()), &old_url)) {
+ LOG(ERROR) << "TypedUrl db lookup failed.";
+ error_handler()->OnUnrecoverableError();
+ return;
+ }
+
+ history::URLRow new_url(GURL(typed_url.url()));
+ new_url.set_title(UTF8ToWide(typed_url.title()));
+ new_url.set_visit_count(typed_url.visit_count());
+ new_url.set_typed_count(typed_url.typed_count());
+ new_url.set_last_visit(
+ base::Time::FromInternalValue(typed_url.last_visit()));
+ new_url.set_hidden(typed_url.hidden());
+
+ updated_urls.push_back(
+ std::pair<history::URLID, history::URLRow>(old_url.id(), new_url));
+
+ if (old_url.title().compare(new_url.title()) != 0) {
+ titles.push_back(std::pair<GURL, std::wstring>(new_url.url(),
+ new_url.title()));
+ }
+ }
+ }
+ model_associator_->WriteToHistoryBackend(&titles, &new_urls, &updated_urls);
+
+ StartObserving();
+}
+
+void TypedUrlChangeProcessor::StartImpl(Profile* profile) {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ observing_ = true;
+}
+
+void TypedUrlChangeProcessor::StopImpl() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ observing_ = false;
+}
+
+
+void TypedUrlChangeProcessor::StartObserving() {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ notification_registrar_.Add(this,
+ NotificationType::HISTORY_TYPED_URLS_MODIFIED,
+ NotificationService::AllSources());
+ notification_registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED,
+ NotificationService::AllSources());
+}
+
+void TypedUrlChangeProcessor::StopObserving() {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ notification_registrar_.Remove(this,
+ NotificationType::HISTORY_TYPED_URLS_MODIFIED,
+ NotificationService::AllSources());
+ notification_registrar_.Remove(this,
+ NotificationType::HISTORY_URLS_DELETED,
+ NotificationService::AllSources());
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/typed_url_change_processor.h b/chrome/browser/sync/glue/typed_url_change_processor.h
new file mode 100644
index 0000000..920c62e
--- /dev/null
+++ b/chrome/browser/sync/glue/typed_url_change_processor.h
@@ -0,0 +1,95 @@
+// 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_TYPED_URL_CHANGE_PROCESSOR_H_
+#define CHROME_BROWSER_SYNC_GLUE_TYPED_URL_CHANGE_PROCESSOR_H_
+
+#include "chrome/browser/sync/glue/change_processor.h"
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "chrome/browser/sync/glue/sync_backend_host.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/notification_type.h"
+
+class MessageLoop;
+class NotificationService;
+
+namespace history {
+class HistoryBackend;
+struct URLsDeletedDetails;
+struct URLsModifiedDetails;
+class URLRow;
+};
+
+namespace browser_sync {
+
+class TypedUrlModelAssociator;
+class UnrecoverableErrorHandler;
+
+// This class is responsible for taking changes from the history backend 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 TypedUrlChangeProcessor : public ChangeProcessor,
+ public NotificationObserver {
+ public:
+ TypedUrlChangeProcessor(TypedUrlModelAssociator* model_associator,
+ history::HistoryBackend* history_backend,
+ UnrecoverableErrorHandler* error_handler);
+ virtual ~TypedUrlChangeProcessor();
+
+ // NotificationObserver implementation.
+ // History -> sync_api model change application.
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // sync_api model -> WebDataService change application.
+ virtual void ApplyChangesFromSyncModel(
+ const sync_api::BaseTransaction* trans,
+ const sync_api::SyncManager::ChangeRecord* changes,
+ int change_count);
+
+ protected:
+ virtual void StartImpl(Profile* profile);
+ virtual void StopImpl();
+
+ private:
+ bool WriteTypedUrl(sync_api::WriteNode* node,
+ const string16& name,
+ const string16& value,
+ std::vector<base::Time>& timestamps);
+
+ void StartObserving();
+ void StopObserving();
+
+ void HandleURLsModified(history::URLsModifiedDetails* details);
+ void HandleURLsDeleted(history::URLsDeletedDetails* details);
+
+ // The two models should be associated according to this ModelAssociator.
+ TypedUrlModelAssociator* model_associator_;
+
+ // The model we are processing changes from. This is owned by the
+ // WebDataService which is kept alive by our data type controller
+ // holding a reference.
+ history::HistoryBackend* history_backend_;
+
+ NotificationRegistrar notification_registrar_;
+
+ bool observing_;
+
+ MessageLoop* expected_loop_;
+
+ scoped_ptr<NotificationService> notification_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(TypedUrlChangeProcessor);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_TYPED_URL_CHANGE_PROCESSOR_H_
diff --git a/chrome/browser/sync/glue/typed_url_data_type_controller.cc b/chrome/browser/sync/glue/typed_url_data_type_controller.cc
new file mode 100644
index 0000000..831f2ae
--- /dev/null
+++ b/chrome/browser/sync/glue/typed_url_data_type_controller.cc
@@ -0,0 +1,209 @@
+// 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/task.h"
+#include "base/time.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/history/history.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/sync/glue/typed_url_change_processor.h"
+#include "chrome/browser/sync/glue/typed_url_data_type_controller.h"
+#include "chrome/browser/sync/glue/typed_url_model_associator.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/profile_sync_factory.h"
+#include "chrome/browser/webdata/web_data_service.h"
+#include "chrome/common/notification_service.h"
+
+namespace browser_sync {
+
+class ControlTask : public HistoryDBTask {
+ public:
+ ControlTask(TypedUrlDataTypeController* controller, bool start)
+ : controller_(controller), start_(start) {}
+
+ virtual bool RunOnDBThread(history::HistoryBackend* backend,
+ history::HistoryDatabase* db) {
+ if (start_) {
+ controller_->StartImpl(backend);
+ } else {
+ controller_->StopImpl();
+ }
+
+ // Release the reference to the controller. This ensures that
+ // the controller isn't held past its lifetime in unit tests.
+ controller_ = NULL;
+ return true;
+ }
+
+ virtual void DoneRunOnMainThread() {}
+
+ protected:
+ scoped_refptr<TypedUrlDataTypeController> controller_;
+ bool start_;
+};
+
+TypedUrlDataTypeController::TypedUrlDataTypeController(
+ ProfileSyncFactory* profile_sync_factory,
+ Profile* profile,
+ ProfileSyncService* sync_service)
+ : profile_sync_factory_(profile_sync_factory),
+ profile_(profile),
+ sync_service_(sync_service),
+ state_(NOT_RUNNING),
+ merge_allowed_(false) {
+ DCHECK(profile_sync_factory);
+ DCHECK(profile);
+ DCHECK(sync_service);
+}
+
+TypedUrlDataTypeController::~TypedUrlDataTypeController() {
+}
+
+void TypedUrlDataTypeController::Start(bool merge_allowed,
+ StartCallback* start_callback) {
+ LOG(INFO) << "Starting typed_url data controller.";
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(start_callback);
+ if (state_ != NOT_RUNNING || start_callback_.get()) {
+ start_callback->Run(BUSY);
+ delete start_callback;
+ return;
+ }
+
+ start_callback_.reset(start_callback);
+ merge_allowed_ = merge_allowed;
+
+ HistoryService* history = profile_->GetHistoryServiceWithoutCreating();
+ if (history) {
+ set_state(ASSOCIATING);
+ history_service_ = history;
+ history_service_->ScheduleDBTask(new ControlTask(this, true), this);
+ } else {
+ set_state(MODEL_STARTING);
+ notification_registrar_.Add(this, NotificationType::HISTORY_LOADED,
+ NotificationService::AllSources());
+ }
+}
+
+void TypedUrlDataTypeController::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ LOG(INFO) << "History loaded observed.";
+ notification_registrar_.Remove(this,
+ NotificationType::HISTORY_LOADED,
+ NotificationService::AllSources());
+
+ history_service_ = profile_->GetHistoryServiceWithoutCreating();
+ DCHECK(history_service_.get());
+ history_service_->ScheduleDBTask(new ControlTask(this, true), this);
+}
+
+void TypedUrlDataTypeController::Stop() {
+ LOG(INFO) << "Stopping typed_url data type controller.";
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+
+ if (change_processor_ != NULL)
+ sync_service_->DeactivateDataType(this, change_processor_.get());
+
+ if (model_associator_ != NULL)
+ model_associator_->DisassociateModels();
+
+ set_state(NOT_RUNNING);
+ DCHECK(history_service_.get());
+ history_service_->ScheduleDBTask(new ControlTask(this, false), this);
+}
+
+void TypedUrlDataTypeController::StartImpl(history::HistoryBackend* backend) {
+ LOG(INFO) << "TypedUrl data type controller StartImpl called.";
+ // No additional services need to be started before we can proceed
+ // with model association.
+ ProfileSyncFactory::SyncComponents sync_components =
+ profile_sync_factory_->CreateTypedUrlSyncComponents(
+ sync_service_,
+ backend,
+ this);
+ model_associator_.reset(sync_components.model_associator);
+ change_processor_.reset(sync_components.change_processor);
+
+ bool chrome_has_nodes = false;
+ if (!model_associator_->ChromeModelHasUserCreatedNodes(&chrome_has_nodes)) {
+ StartFailed(UNRECOVERABLE_ERROR);
+ return;
+ }
+ bool sync_has_nodes = false;
+ if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) {
+ StartFailed(UNRECOVERABLE_ERROR);
+ return;
+ }
+
+ if (chrome_has_nodes && sync_has_nodes && !merge_allowed_) {
+ StartFailed(NEEDS_MERGE);
+ return;
+ }
+
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ bool merge_success = model_associator_->AssociateModels();
+ UMA_HISTOGRAM_TIMES("Sync.TypedUrlAssociationTime",
+ base::TimeTicks::Now() - start_time);
+ if (!merge_success) {
+ StartFailed(NEEDS_MERGE);
+ return;
+ }
+
+ sync_service_->ActivateDataType(this, change_processor_.get());
+ StartDone(!sync_has_nodes ? OK_FIRST_RUN : OK, RUNNING);
+}
+
+void TypedUrlDataTypeController::StartDone(
+ DataTypeController::StartResult result,
+ DataTypeController::State new_state) {
+ LOG(INFO) << "TypedUrl data type controller StartDone called.";
+ ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(
+ this,
+ &TypedUrlDataTypeController::StartDoneImpl,
+ result,
+ new_state));
+}
+
+void TypedUrlDataTypeController::StartDoneImpl(
+ DataTypeController::StartResult result,
+ DataTypeController::State new_state) {
+ LOG(INFO) << "TypedUrl data type controller StartDoneImpl called.";
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ set_state(new_state);
+ start_callback_->Run(result);
+ start_callback_.reset();
+}
+
+void TypedUrlDataTypeController::StopImpl() {
+ LOG(INFO) << "TypedUrl data type controller StopImpl called.";
+
+ change_processor_.reset();
+ model_associator_.reset();
+
+ state_ = NOT_RUNNING;
+}
+
+void TypedUrlDataTypeController::StartFailed(StartResult result) {
+ change_processor_.reset();
+ model_associator_.reset();
+ StartDone(result, NOT_RUNNING);
+}
+
+void TypedUrlDataTypeController::OnUnrecoverableError() {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(this,
+ &TypedUrlDataTypeController::OnUnrecoverableErrorImpl));
+}
+
+void TypedUrlDataTypeController::OnUnrecoverableErrorImpl() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ sync_service_->OnUnrecoverableError();
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/typed_url_data_type_controller.h b/chrome/browser/sync/glue/typed_url_data_type_controller.h
new file mode 100644
index 0000000..9897f71
--- /dev/null
+++ b/chrome/browser/sync/glue/typed_url_data_type_controller.h
@@ -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.
+
+#ifndef CHROME_BROWSER_SYNC_GLUE_TYPED_URL_DATA_TYPE_CONTROLLER_H__
+#define CHROME_BROWSER_SYNC_GLUE_TYPED_URL_DATA_TYPE_CONTROLLER_H__
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/cancelable_request.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/glue/data_type_controller.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/notification_type.h"
+
+class NotificationSource;
+class NotificationDetails;
+class Profile;
+class ProfileSyncFactory;
+class ProfileSyncService;
+
+namespace browser_sync {
+
+class AssociatorInterface;
+class ChangeProcessor;
+class ControlTask;
+
+// A class that manages the startup and shutdown of typed_url sync.
+class TypedUrlDataTypeController : public DataTypeController,
+ public NotificationObserver,
+ public CancelableRequestConsumerBase {
+ public:
+ TypedUrlDataTypeController(
+ ProfileSyncFactory* profile_sync_factory,
+ Profile* profile,
+ ProfileSyncService* sync_service);
+ virtual ~TypedUrlDataTypeController();
+
+ // DataTypeController implementation
+ virtual void Start(bool merge_allowed, StartCallback* start_callback);
+
+ virtual void Stop();
+
+ virtual bool enabled() {
+ return true;
+ }
+
+ virtual syncable::ModelType type() {
+ return syncable::TYPED_URLS;
+ }
+
+ virtual browser_sync::ModelSafeGroup model_safe_group() {
+ return browser_sync::GROUP_HISTORY;
+ }
+
+ virtual const char* name() const {
+ // For logging only.
+ return "typed_url";
+ }
+
+ virtual State state() {
+ return state_;
+ }
+
+ // UnrecoverableHandler implementation
+ virtual void OnUnrecoverableError();
+
+ // NotificationObserver implementation.
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // CancelableRequestConsumerBase implementation.
+ virtual void OnRequestAdded(CancelableRequestProvider* provider,
+ CancelableRequestProvider::Handle handle) {}
+
+ virtual void OnRequestRemoved(CancelableRequestProvider* provider,
+ CancelableRequestProvider::Handle handle) {}
+ private:
+ friend class ControlTask;
+ void StartImpl(history::HistoryBackend* backend);
+ void StartDone(StartResult result, State state);
+ void StartDoneImpl(StartResult result, State state);
+ void StopImpl();
+ void StartFailed(StartResult result);
+ void OnUnrecoverableErrorImpl();
+
+ void set_state(State state) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ state_ = state;
+ }
+
+ ProfileSyncFactory* profile_sync_factory_;
+ Profile* profile_;
+ ProfileSyncService* sync_service_;
+ State state_;
+
+ scoped_ptr<AssociatorInterface> model_associator_;
+ scoped_ptr<ChangeProcessor> change_processor_;
+ scoped_ptr<StartCallback> start_callback_;
+ scoped_refptr<HistoryService> history_service_;
+
+ NotificationRegistrar notification_registrar_;
+
+ bool merge_allowed_;
+
+ DISALLOW_COPY_AND_ASSIGN(TypedUrlDataTypeController);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_TYPED_URL_DATA_TYPE_CONTROLLER_H__
diff --git a/chrome/browser/sync/glue/typed_url_model_associator.cc b/chrome/browser/sync/glue/typed_url_model_associator.cc
new file mode 100644
index 0000000..080a598
--- /dev/null
+++ b/chrome/browser/sync/glue/typed_url_model_associator.cc
@@ -0,0 +1,338 @@
+// 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/typed_url_model_associator.h"
+
+#include <set>
+
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/history/history_backend.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/sync/engine/syncapi.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
+
+namespace browser_sync {
+
+const char kTypedUrlTag[] = "google_chrome_typed_urls";
+
+TypedUrlModelAssociator::TypedUrlModelAssociator(
+ ProfileSyncService* sync_service,
+ history::HistoryBackend* history_backend,
+ UnrecoverableErrorHandler* error_handler)
+ : sync_service_(sync_service),
+ history_backend_(history_backend),
+ error_handler_(error_handler),
+ typed_url_node_id_(sync_api::kInvalidId),
+ expected_loop_(MessageLoop::current()) {
+ DCHECK(sync_service_);
+ DCHECK(history_backend_);
+ DCHECK(error_handler_);
+ DCHECK(!ChromeThread::CurrentlyOn(ChromeThread::UI));
+}
+
+bool TypedUrlModelAssociator::AssociateModels() {
+ LOG(INFO) << "Associating TypedUrl Models";
+ DCHECK(expected_loop_ == MessageLoop::current());
+
+ std::vector<history::URLRow> typed_urls;
+ if (!history_backend_->GetAllTypedURLs(&typed_urls)) {
+ LOG(ERROR) << "Could not get the typed_url entries.";
+ return false;
+ }
+
+ sync_api::WriteTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode typed_url_root(&trans);
+ if (!typed_url_root.InitByTagLookup(kTypedUrlTag)) {
+ LOG(ERROR) << "Server did not create the top-level typed_url node. We "
+ << "might be running against an out-of-date server.";
+ return false;
+ }
+
+ std::set<std::string> current_urls;
+ TypedUrlTitleVector titles;
+ TypedUrlVector new_urls;
+ TypedUrlUpdateVector updated_urls;
+
+ for (std::vector<history::URLRow>::iterator ix = typed_urls.begin();
+ ix != typed_urls.end(); ++ix) {
+ std::string tag = ix->url().spec();
+
+ sync_api::ReadNode node(&trans);
+ if (node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) {
+ const sync_pb::TypedUrlSpecifics& typed_url(node.GetTypedUrlSpecifics());
+ DCHECK_EQ(tag, typed_url.url());
+
+ history::URLRow new_url(ix->url());
+
+ int difference = MergeUrls(typed_url, *ix, &new_url);
+ if (difference & DIFF_NODE_CHANGED) {
+ sync_api::WriteNode write_node(&trans);
+ if (!write_node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) {
+ LOG(ERROR) << "Failed to edit typed_url sync node.";
+ return false;
+ }
+ WriteToSyncNode(new_url, &write_node);
+ }
+ if (difference & DIFF_TITLE_CHANGED) {
+ titles.push_back(std::pair<GURL, std::wstring>(new_url.url(),
+ new_url.title()));
+ }
+ if (difference & DIFF_ROW_CHANGED) {
+ updated_urls.push_back(
+ std::pair<history::URLID, history::URLRow>(ix->id(), new_url));
+ }
+
+ Associate(&tag, node.GetId());
+ } else {
+ sync_api::WriteNode node(&trans);
+ if (!node.InitUniqueByCreation(syncable::TYPED_URLS,
+ typed_url_root, tag)) {
+ LOG(ERROR) << "Failed to create typed_url sync node.";
+ return false;
+ }
+
+ node.SetTitle(UTF8ToWide(tag));
+ WriteToSyncNode(*ix, &node);
+
+ Associate(&tag, node.GetId());
+ }
+
+ current_urls.insert(tag);
+ }
+
+ int64 sync_child_id = typed_url_root.GetFirstChildId();
+ while (sync_child_id != sync_api::kInvalidId) {
+ sync_api::ReadNode sync_child_node(&trans);
+ if (!sync_child_node.InitByIdLookup(sync_child_id)) {
+ LOG(ERROR) << "Failed to fetch child node.";
+ return false;
+ }
+ const sync_pb::TypedUrlSpecifics& typed_url(
+ sync_child_node.GetTypedUrlSpecifics());
+
+ if (current_urls.find(typed_url.url()) == current_urls.end()) {
+ history::URLRow new_url(GURL(typed_url.url()));
+
+ new_url.set_title(UTF8ToWide(typed_url.title()));
+ new_url.set_visit_count(typed_url.visit_count());
+ new_url.set_typed_count(typed_url.typed_count());
+ new_url.set_last_visit(
+ base::Time::FromInternalValue(typed_url.last_visit()));
+ new_url.set_hidden(typed_url.hidden());
+
+ Associate(&typed_url.url(), sync_child_node.GetId());
+ new_urls.push_back(new_url);
+ }
+
+ sync_child_id = sync_child_node.GetSuccessorId();
+ }
+
+ WriteToHistoryBackend(&titles, &new_urls, &updated_urls);
+
+ return true;
+}
+
+bool TypedUrlModelAssociator::DeleteAllNodes(
+ sync_api::WriteTransaction* trans) {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ for (TypedUrlToSyncIdMap::iterator node_id = id_map_.begin();
+ node_id != id_map_.end(); ++node_id) {
+ sync_api::WriteNode sync_node(trans);
+ if (!sync_node.InitByIdLookup(node_id->second)) {
+ LOG(ERROR) << "Typed url node lookup failed.";
+ return false;
+ }
+ sync_node.Remove();
+ }
+
+ id_map_.clear();
+ id_map_inverse_.clear();
+ return true;
+}
+
+bool TypedUrlModelAssociator::DisassociateModels() {
+ id_map_.clear();
+ id_map_inverse_.clear();
+ return true;
+}
+
+bool TypedUrlModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
+ DCHECK(has_nodes);
+ *has_nodes = false;
+ int64 typed_url_sync_id;
+ if (!GetSyncIdForTaggedNode(kTypedUrlTag, &typed_url_sync_id)) {
+ LOG(ERROR) << "Server did not create the top-level typed_url node. We "
+ << "might be running against an out-of-date server.";
+ return false;
+ }
+ sync_api::ReadTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+
+ sync_api::ReadNode typed_url_node(&trans);
+ if (!typed_url_node.InitByIdLookup(typed_url_sync_id)) {
+ LOG(ERROR) << "Server did not create the top-level typed_url node. We "
+ << "might be running against an out-of-date server.";
+ return false;
+ }
+
+ // The sync model has user created nodes if the typed_url folder has any
+ // children.
+ *has_nodes = sync_api::kInvalidId != typed_url_node.GetFirstChildId();
+ return true;
+}
+
+bool TypedUrlModelAssociator::ChromeModelHasUserCreatedNodes(bool* has_nodes) {
+ DCHECK(has_nodes);
+ // Assume the typed_url model always have user-created nodes.
+ *has_nodes = true;
+ return true;
+}
+
+int64 TypedUrlModelAssociator::GetSyncIdFromChromeId(
+ const std::string typed_url) {
+ TypedUrlToSyncIdMap::const_iterator iter = id_map_.find(typed_url);
+ return iter == id_map_.end() ? sync_api::kInvalidId : iter->second;
+}
+
+void TypedUrlModelAssociator::Associate(
+ const std::string* typed_url, int64 sync_id) {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ DCHECK_NE(sync_api::kInvalidId, sync_id);
+ DCHECK(id_map_.find(*typed_url) == id_map_.end());
+ DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end());
+ id_map_[*typed_url] = sync_id;
+ id_map_inverse_[sync_id] = *typed_url;
+}
+
+void TypedUrlModelAssociator::Disassociate(int64 sync_id) {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ SyncIdToTypedUrlMap::iterator iter = id_map_inverse_.find(sync_id);
+ if (iter == id_map_inverse_.end())
+ return;
+ CHECK(id_map_.erase(iter->second));
+ id_map_inverse_.erase(iter);
+}
+
+bool TypedUrlModelAssociator::GetSyncIdForTaggedNode(const std::string& tag,
+ int64* sync_id) {
+ sync_api::ReadTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode sync_node(&trans);
+ if (!sync_node.InitByTagLookup(tag.c_str()))
+ return false;
+ *sync_id = sync_node.GetId();
+ return true;
+}
+
+void TypedUrlModelAssociator::WriteToHistoryBackend(
+ const TypedUrlTitleVector* titles,
+ const TypedUrlVector* new_urls,
+ const TypedUrlUpdateVector* updated_urls) {
+ if (titles) {
+ for (TypedUrlTitleVector::const_iterator title = titles->begin();
+ title != titles->end(); ++title) {
+ history_backend_->SetPageTitle(title->first, title->second);
+ }
+ }
+ if (new_urls) {
+ history_backend_->AddPagesWithDetails(*new_urls);
+ }
+ if (updated_urls) {
+ for (TypedUrlUpdateVector::const_iterator url = updated_urls->begin();
+ url != updated_urls->end(); ++url) {
+ history_backend_->UpdateURL(url->first, url->second);
+ }
+ }
+}
+
+// static
+int TypedUrlModelAssociator::MergeUrls(
+ const sync_pb::TypedUrlSpecifics& typed_url,
+ const history::URLRow& url,
+ history::URLRow* new_url) {
+ DCHECK(new_url);
+ DCHECK(!typed_url.url().compare(url.url().spec()));
+ DCHECK(!typed_url.url().compare(new_url->url().spec()));
+
+ // Convert these values only once.
+ std::wstring typed_title(UTF8ToWide(typed_url.title()));
+ base::Time typed_visit =
+ base::Time::FromInternalValue(typed_url.last_visit());
+
+ // This is a bitfield represting what we'll need to update with the output
+ // value.
+ int different = DIFF_NONE;
+
+ // Check if the non-incremented values changed.
+ if ((typed_title.compare(url.title()) != 0) ||
+ (typed_visit != url.last_visit()) ||
+ (typed_url.hidden() != url.hidden())) {
+
+ // Use the values from the most recent visit.
+ if (typed_visit >= url.last_visit()) {
+ new_url->set_title(typed_title);
+ new_url->set_last_visit(typed_visit);
+ new_url->set_hidden(typed_url.hidden());
+ different |= DIFF_ROW_CHANGED;
+
+ // If we're changing the local title, note this.
+ if (new_url->title().compare(url.title()) != 0) {
+ different |= DIFF_TITLE_CHANGED;
+ }
+ } else {
+ new_url->set_title(url.title());
+ new_url->set_last_visit(url.last_visit());
+ new_url->set_hidden(url.hidden());
+ different |= DIFF_NODE_CHANGED;
+ }
+ } else {
+ // No difference.
+ new_url->set_title(url.title());
+ new_url->set_last_visit(url.last_visit());
+ new_url->set_hidden(url.hidden());
+ }
+
+ // For visit count, we just select the maximum value.
+ if (typed_url.visit_count() > url.visit_count()) {
+ new_url->set_visit_count(typed_url.visit_count());
+ different |= DIFF_ROW_CHANGED;
+ } else if (typed_url.visit_count() < url.visit_count()) {
+ new_url->set_visit_count(url.visit_count());
+ different |= DIFF_NODE_CHANGED;
+ } else {
+ new_url->set_visit_count(typed_url.visit_count());
+ }
+
+ // For typed count, we just select the maximum value.
+ if (typed_url.typed_count() > url.typed_count()) {
+ new_url->set_typed_count(typed_url.typed_count());
+ different |= DIFF_ROW_CHANGED;
+ } else if (typed_url.typed_count() < url.typed_count()) {
+ new_url->set_typed_count(url.visit_count());
+ different |= DIFF_NODE_CHANGED;
+ } else {
+ // No difference.
+ new_url->set_typed_count(typed_url.typed_count());
+ }
+
+ return different;
+}
+
+// static
+void TypedUrlModelAssociator::WriteToSyncNode(const history::URLRow& url,
+ sync_api::WriteNode* node) {
+ sync_pb::TypedUrlSpecifics typed_url;
+ typed_url.set_url(url.url().spec());
+ typed_url.set_title(WideToUTF8(url.title()));
+ typed_url.set_visit_count(url.visit_count());
+ typed_url.set_typed_count(url.typed_count());
+ typed_url.set_last_visit(url.last_visit().ToInternalValue());
+ typed_url.set_hidden(url.hidden());
+
+ node->SetTypedUrlSpecifics(typed_url);
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/typed_url_model_associator.h b/chrome/browser/sync/glue/typed_url_model_associator.h
new file mode 100644
index 0000000..b3087c6
--- /dev/null
+++ b/chrome/browser/sync/glue/typed_url_model_associator.h
@@ -0,0 +1,145 @@
+// 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_TYPED_URL_MODEL_ASSOCIATOR_H_
+#define CHROME_BROWSER_SYNC_GLUE_TYPED_URL_MODEL_ASSOCIATOR_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "base/task.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/sync/glue/model_associator.h"
+#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
+
+class GURL;
+class MessageLoop;
+class ProfileSyncService;
+
+namespace history {
+class HistoryBackend;
+class URLRow;
+};
+
+namespace sync_api {
+class WriteNode;
+class WriteTransaction;
+};
+
+namespace browser_sync {
+
+class TypedUrlChangeProcessor;
+class UnrecoverableErrorHandler;
+
+extern const char kTypedUrlTag[];
+
+// Contains all model association related logic:
+// * Algorithm to associate typed_url model and sync model.
+// * Persisting model associations and loading them back.
+// We do not check if we have local data before this run; we always
+// merge and sync.
+class TypedUrlModelAssociator
+ : public PerDataTypeAssociatorInterface<std::string, std::string> {
+ public:
+ typedef std::vector<std::pair<GURL, std::wstring> > TypedUrlTitleVector;
+ typedef std::vector<history::URLRow> TypedUrlVector;
+ typedef std::vector<std::pair<history::URLID, history::URLRow> >
+ TypedUrlUpdateVector;
+
+ static syncable::ModelType model_type() { return syncable::TYPED_URLS; }
+ TypedUrlModelAssociator(ProfileSyncService* sync_service,
+ history::HistoryBackend* history_backend,
+ UnrecoverableErrorHandler* error_handler);
+ virtual ~TypedUrlModelAssociator() { }
+
+ // PerDataTypeAssociatorInterface implementation.
+ //
+ // Iterates through the sync model looking for matched pairs of items.
+ virtual bool AssociateModels();
+
+ // Delete all typed url nodes.
+ bool DeleteAllNodes(sync_api::WriteTransaction* trans);
+
+ // Clears all associations.
+ virtual bool DisassociateModels();
+
+ // The has_nodes out param is true if the sync model has nodes other
+ // than the permanent tagged nodes.
+ virtual bool SyncModelHasUserCreatedNodes(bool* has_nodes);
+
+ // The has_nodes out param is true if the autofill model has any
+ // user-defined typed_url entries.
+ virtual bool ChromeModelHasUserCreatedNodes(bool* has_nodes);
+
+ // Not implemented.
+ virtual const std::string* GetChromeNodeFromSyncId(int64 sync_id) {
+ return NULL;
+ }
+
+ // Not implemented.
+ virtual bool InitSyncNodeFromChromeId(std::string node_id,
+ sync_api::BaseNode* sync_node) {
+ return false;
+ }
+
+ // Returns the sync id for the given typed_url name, or sync_api::kInvalidId
+ // if the typed_url name is not associated to any sync id.
+ virtual int64 GetSyncIdFromChromeId(std::string node_id);
+
+ // Associates the given typed_url name with the given sync id.
+ virtual void Associate(const std::string* node, int64 sync_id);
+
+ // Remove the association that corresponds to the given sync id.
+ virtual void Disassociate(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);
+
+ void WriteToHistoryBackend(const TypedUrlTitleVector* titles,
+ const TypedUrlVector* new_urls,
+ const TypedUrlUpdateVector* updated_urls);
+
+ enum {
+ DIFF_NONE = 0x0000,
+ DIFF_NODE_CHANGED = 0x0001,
+ DIFF_TITLE_CHANGED = 0x0002,
+ DIFF_ROW_CHANGED = 0x0004,
+ };
+
+ static int MergeUrls(const sync_pb::TypedUrlSpecifics& typed_url,
+ const history::URLRow& url,
+ history::URLRow* new_url);
+ static void WriteToSyncNode(const history::URLRow& url,
+ sync_api::WriteNode* node);
+
+ protected:
+ // Returns sync service instance.
+ ProfileSyncService* sync_service() { return sync_service_; }
+
+ private:
+ typedef std::map<std::string, int64> TypedUrlToSyncIdMap;
+ typedef std::map<int64, std::string> SyncIdToTypedUrlMap;
+
+ ProfileSyncService* sync_service_;
+ history::HistoryBackend* history_backend_;
+ UnrecoverableErrorHandler* error_handler_;
+ int64 typed_url_node_id_;
+
+ MessageLoop* expected_loop_;
+
+ TypedUrlToSyncIdMap id_map_;
+ SyncIdToTypedUrlMap id_map_inverse_;
+
+ DISALLOW_COPY_AND_ASSIGN(TypedUrlModelAssociator);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_TYPED_URL_MODEL_ASSOCIATOR_H_
diff --git a/chrome/browser/sync/glue/typed_url_model_associator_unittest.cc b/chrome/browser/sync/glue/typed_url_model_associator_unittest.cc
new file mode 100644
index 0000000..e45ed06
--- /dev/null
+++ b/chrome/browser/sync/glue/typed_url_model_associator_unittest.cc
@@ -0,0 +1,120 @@
+// 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/string_piece.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/sync/glue/typed_url_model_associator.h"
+#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using browser_sync::TypedUrlModelAssociator;
+
+class TypedUrlModelAssociatorTest : public testing::Test {
+ public:
+ static history::URLRow MakeTypedUrlRow(const char* url,
+ const char* title,
+ int visit_count,
+ int typed_count,
+ int64 last_visit,
+ bool hidden) {
+ GURL gurl(url);
+ history::URLRow history_url(gurl);
+ history_url.set_title(UTF8ToWide(title));
+ history_url.set_visit_count(visit_count);
+ history_url.set_typed_count(typed_count);
+ history_url.set_last_visit(
+ base::Time::FromInternalValue(last_visit));
+ history_url.set_hidden(hidden);
+ return history_url;
+ }
+
+ static sync_pb::TypedUrlSpecifics MakeTypedUrlSpecifics(const char* url,
+ const char* title,
+ int visit_count,
+ int typed_count,
+ int64 last_visit,
+ bool hidden) {
+ sync_pb::TypedUrlSpecifics typed_url;
+ typed_url.set_url(url);
+ typed_url.set_title(title);
+ typed_url.set_visit_count(visit_count);
+ typed_url.set_typed_count(typed_count);
+ typed_url.set_last_visit(last_visit);
+ typed_url.set_hidden(hidden);
+ return typed_url;
+ }
+
+ static bool URLsEqual(history::URLRow& lhs, history::URLRow& rhs) {
+ return (lhs.url().spec().compare(rhs.url().spec()) == 0) &&
+ (lhs.title().compare(rhs.title()) == 0) &&
+ (lhs.visit_count() == rhs.visit_count()) &&
+ (lhs.typed_count() == rhs.typed_count()) &&
+ (lhs.last_visit() == rhs.last_visit()) &&
+ (lhs.hidden() == rhs.hidden());
+ }
+};
+
+TEST_F(TypedUrlModelAssociatorTest, MergeUrls) {
+ history::URLRow row1(MakeTypedUrlRow("http://pie.com/", "pie",
+ 1, 2, 3, false));
+ sync_pb::TypedUrlSpecifics specs1(MakeTypedUrlSpecifics("http://pie.com/",
+ "pie",
+ 1, 2, 3, false));
+ history::URLRow new_row1(GURL("http://pie.com/"));
+ EXPECT_EQ(TypedUrlModelAssociator::DIFF_NONE,
+ TypedUrlModelAssociator::MergeUrls(specs1, row1, &new_row1));
+
+ history::URLRow row2(MakeTypedUrlRow("http://pie.com/", "pie",
+ 1, 2, 3, false));
+ sync_pb::TypedUrlSpecifics specs2(MakeTypedUrlSpecifics("http://pie.com/",
+ "pie",
+ 1, 2, 4, true));
+ history::URLRow expected2(MakeTypedUrlRow("http://pie.com/", "pie",
+ 1, 2, 4, true));
+ history::URLRow new_row2(GURL("http://pie.com/"));
+ EXPECT_EQ(TypedUrlModelAssociator::DIFF_ROW_CHANGED,
+ TypedUrlModelAssociator::MergeUrls(specs2, row2, &new_row2));
+ EXPECT_TRUE(URLsEqual(new_row2, expected2));
+
+ history::URLRow row3(MakeTypedUrlRow("http://pie.com/", "pie",
+ 1, 2, 3, false));
+ sync_pb::TypedUrlSpecifics specs3(MakeTypedUrlSpecifics("http://pie.com/",
+ "pie2",
+ 1, 2, 4, true));
+ history::URLRow expected3(MakeTypedUrlRow("http://pie.com/", "pie2",
+ 1, 2, 4, true));
+ history::URLRow new_row3(GURL("http://pie.com/"));
+ EXPECT_EQ(TypedUrlModelAssociator::DIFF_ROW_CHANGED |
+ TypedUrlModelAssociator::DIFF_TITLE_CHANGED,
+ TypedUrlModelAssociator::MergeUrls(specs3, row3, &new_row3));
+ EXPECT_TRUE(URLsEqual(new_row3, expected3));
+
+ history::URLRow row4(MakeTypedUrlRow("http://pie.com/", "pie",
+ 1, 2, 4, false));
+ sync_pb::TypedUrlSpecifics specs4(MakeTypedUrlSpecifics("http://pie.com/",
+ "pie2",
+ 1, 2, 3, true));
+ history::URLRow expected4(MakeTypedUrlRow("http://pie.com/", "pie",
+ 1, 2, 4, false));
+ history::URLRow new_row4(GURL("http://pie.com/"));
+ EXPECT_EQ(TypedUrlModelAssociator::DIFF_NODE_CHANGED,
+ TypedUrlModelAssociator::MergeUrls(specs4, row4, &new_row4));
+ EXPECT_TRUE(URLsEqual(new_row4, expected4));
+
+ history::URLRow row5(MakeTypedUrlRow("http://pie.com/", "pie",
+ 2, 1, 3, false));
+ sync_pb::TypedUrlSpecifics specs5(MakeTypedUrlSpecifics("http://pie.com/",
+ "pie",
+ 1, 2, 3, false));
+ history::URLRow expected5(MakeTypedUrlRow("http://pie.com/", "pie",
+ 2, 2, 3, false));
+ history::URLRow new_row5(GURL("http://pie.com/"));
+ EXPECT_EQ(TypedUrlModelAssociator::DIFF_ROW_CHANGED |
+ TypedUrlModelAssociator::DIFF_NODE_CHANGED,
+ TypedUrlModelAssociator::MergeUrls(specs5, row5, &new_row5));
+ EXPECT_TRUE(URLsEqual(new_row5, expected5));
+
+}
diff --git a/chrome/browser/sync/profile_sync_factory.h b/chrome/browser/sync/profile_sync_factory.h
index ae61f05..ef813d4 100644
--- a/chrome/browser/sync/profile_sync_factory.h
+++ b/chrome/browser/sync/profile_sync_factory.h
@@ -21,6 +21,10 @@ class SyncBackendHost;
class UnrecoverableErrorHandler;
}
+namespace history {
+class HistoryBackend;
+};
+
// Factory class for all profile sync related classes.
class ProfileSyncFactory {
public:
@@ -71,6 +75,14 @@ class ProfileSyncFactory {
virtual SyncComponents CreatePreferenceSyncComponents(
ProfileSyncService* profile_sync_service,
browser_sync::UnrecoverableErrorHandler* error_handler) = 0;
+
+ // Instantiates both a model associator and change processor for the
+ // typed_url data type. The pointers in the return struct are owned
+ // by the caller.
+ virtual SyncComponents CreateTypedUrlSyncComponents(
+ ProfileSyncService* profile_sync_service,
+ history::HistoryBackend* history_backend,
+ 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 a687e3b..89df00ba 100644
--- a/chrome/browser/sync/profile_sync_factory_impl.cc
+++ b/chrome/browser/sync/profile_sync_factory_impl.cc
@@ -16,6 +16,9 @@
#include "chrome/browser/sync/glue/preference_data_type_controller.h"
#include "chrome/browser/sync/glue/preference_model_associator.h"
#include "chrome/browser/sync/glue/sync_backend_host.h"
+#include "chrome/browser/sync/glue/typed_url_change_processor.h"
+#include "chrome/browser/sync/glue/typed_url_data_type_controller.h"
+#include "chrome/browser/sync/glue/typed_url_model_associator.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/profile_sync_factory_impl.h"
#include "chrome/browser/webdata/web_data_service.h"
@@ -26,16 +29,17 @@ using browser_sync::AutofillDataTypeController;
using browser_sync::AutofillModelAssociator;
using browser_sync::BookmarkChangeProcessor;
using browser_sync::BookmarkDataTypeController;
-using browser_sync::BookmarkChangeProcessor;
using browser_sync::BookmarkModelAssociator;
using browser_sync::DataTypeController;
using browser_sync::DataTypeManager;
using browser_sync::DataTypeManagerImpl;
-using browser_sync::PreferenceDataTypeController;
using browser_sync::PreferenceChangeProcessor;
using browser_sync::PreferenceDataTypeController;
using browser_sync::PreferenceModelAssociator;
using browser_sync::SyncBackendHost;
+using browser_sync::TypedUrlChangeProcessor;
+using browser_sync::TypedUrlDataTypeController;
+using browser_sync::TypedUrlModelAssociator;
using browser_sync::UnrecoverableErrorHandler;
ProfileSyncFactoryImpl::ProfileSyncFactoryImpl(
@@ -71,6 +75,13 @@ ProfileSyncService* ProfileSyncFactoryImpl::CreateProfileSyncService() {
new PreferenceDataTypeController(this, pss));
}
+ // TypedUrl sync is disabled by default. Register only if
+ // explicitly enabled.
+ if (command_line_->HasSwitch(switches::kEnableSyncTypedUrls)) {
+ pss->RegisterDataTypeController(
+ new TypedUrlDataTypeController(this, profile_, pss));
+ }
+
return pss;
}
@@ -121,3 +132,19 @@ ProfileSyncFactoryImpl::CreatePreferenceSyncComponents(
error_handler);
return SyncComponents(model_associator, change_processor);
}
+
+ProfileSyncFactory::SyncComponents
+ProfileSyncFactoryImpl::CreateTypedUrlSyncComponents(
+ ProfileSyncService* profile_sync_service,
+ history::HistoryBackend* history_backend,
+ browser_sync::UnrecoverableErrorHandler* error_handler) {
+ TypedUrlModelAssociator* model_associator =
+ new TypedUrlModelAssociator(profile_sync_service,
+ history_backend,
+ error_handler);
+ TypedUrlChangeProcessor* change_processor =
+ new TypedUrlChangeProcessor(model_associator,
+ history_backend,
+ error_handler);
+ 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 2989c53..effec7e 100644
--- a/chrome/browser/sync/profile_sync_factory_impl.h
+++ b/chrome/browser/sync/profile_sync_factory_impl.h
@@ -36,6 +36,11 @@ class ProfileSyncFactoryImpl : public ProfileSyncFactory {
ProfileSyncService* profile_sync_service,
browser_sync::UnrecoverableErrorHandler* error_handler);
+ virtual SyncComponents CreateTypedUrlSyncComponents(
+ ProfileSyncService* profile_sync_service,
+ history::HistoryBackend* history_backend,
+ browser_sync::UnrecoverableErrorHandler* error_handler);
+
private:
Profile* profile_;
CommandLine* command_line_;
diff --git a/chrome/browser/sync/profile_sync_factory_mock.h b/chrome/browser/sync/profile_sync_factory_mock.h
index d635b6e..ba4d078 100644
--- a/chrome/browser/sync/profile_sync_factory_mock.h
+++ b/chrome/browser/sync/profile_sync_factory_mock.h
@@ -39,6 +39,11 @@ class ProfileSyncFactoryMock : public ProfileSyncFactory {
MOCK_METHOD2(CreatePreferenceSyncComponents,
SyncComponents(ProfileSyncService* profile_sync_service,
browser_sync::UnrecoverableErrorHandler* error_handler));
+ MOCK_METHOD3(CreateTypedUrlSyncComponents,
+ SyncComponents(
+ ProfileSyncService* profile_sync_service,
+ history::HistoryBackend* history_backend,
+ browser_sync::UnrecoverableErrorHandler* error_handler));
private:
SyncComponents MakeBookmarkSyncComponents();
diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc
index 256f443..bb184cd 100644
--- a/chrome/browser/sync/profile_sync_service.cc
+++ b/chrome/browser/sync/profile_sync_service.cc
@@ -184,7 +184,8 @@ void ProfileSyncService::StartUp() {
profile_->GetPrefs()->GetInt64(prefs::kSyncLastSyncedTime));
backend_.reset(
- new SyncBackendHost(this, profile_->GetPath(), data_type_controllers_));
+ new SyncBackendHost(this, profile_, profile_->GetPath(),
+ data_type_controllers_));
// Initialize the backend. Every time we start up a new SyncBackendHost,
// we'll want to start from a fresh SyncDB, so delete any old one that might
diff --git a/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc b/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc
new file mode 100644
index 0000000..6c18b59
--- /dev/null
+++ b/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc
@@ -0,0 +1,498 @@
+// 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 <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "base/ref_counted.h"
+#include "base/string16.h"
+#include "base/thread.h"
+#include "base/time.h"
+#include "base/waitable_event.h"
+#include "chrome/browser/history/history_backend.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/sync/engine/syncapi.h"
+#include "chrome/browser/sync/glue/sync_backend_host.h"
+#include "chrome/browser/sync/glue/sync_backend_host_mock.h"
+#include "chrome/browser/sync/glue/typed_url_change_processor.h"
+#include "chrome/browser/sync/glue/typed_url_data_type_controller.h"
+#include "chrome/browser/sync/glue/typed_url_model_associator.h"
+#include "chrome/browser/sync/profile_sync_factory.h"
+#include "chrome/browser/sync/profile_sync_factory_mock.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/profile_sync_test_util.h"
+#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
+#include "chrome/browser/sync/syncable/directory_manager.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/test/profile_mock.h"
+#include "chrome/test/sync/engine/test_id_factory.h"
+#include "chrome/test/testing_profile.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using base::Time;
+using base::Thread;
+using base::WaitableEvent;
+using browser_sync::SyncBackendHost;
+using browser_sync::SyncBackendHostMock;
+using browser_sync::TestIdFactory;
+using browser_sync::TypedUrlChangeProcessor;
+using browser_sync::TypedUrlDataTypeController;
+using browser_sync::TypedUrlModelAssociator;
+using browser_sync::UnrecoverableErrorHandler;
+using history::HistoryBackend;
+using history::URLID;
+using history::URLRow;
+using sync_api::SyncManager;
+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;
+using syncable::IS_UNSYNCED;
+using syncable::MutableEntry;
+using syncable::SERVER_IS_DIR;
+using syncable::SERVER_VERSION;
+using syncable::SPECIFICS;
+using syncable::ScopedDirLookup;
+using syncable::UNIQUE_SERVER_TAG;
+using syncable::UNITTEST;
+using syncable::WriteTransaction;
+using testing::_;
+using testing::DoAll;
+using testing::DoDefault;
+using testing::Invoke;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::WithArgs;
+
+class TestingProfileSyncService : public ProfileSyncService {
+ public:
+ explicit TestingProfileSyncService(ProfileSyncFactory* factory,
+ Profile* profile,
+ bool bootstrap_sync_authentication)
+ : ProfileSyncService(factory, profile, bootstrap_sync_authentication) {
+ RegisterPreferences();
+ SetSyncSetupCompleted();
+ }
+ virtual ~TestingProfileSyncService() {
+ }
+
+ virtual void InitializeBackend(bool delete_sync_data_folder) {
+ browser_sync::TestHttpBridgeFactory* factory =
+ new browser_sync::TestHttpBridgeFactory();
+ browser_sync::TestHttpBridgeFactory* factory2 =
+ new browser_sync::TestHttpBridgeFactory();
+ backend()->InitializeForTestMode(L"testuser", factory, factory2,
+ delete_sync_data_folder, browser_sync::kDefaultNotificationMethod);
+ }
+
+ private:
+ // When testing under ChromiumOS, this method must not return an empty
+ // value value in order for the profile sync service to start.
+ virtual std::string GetLsidForAuthBootstraping() {
+ return "foo";
+ }
+};
+
+class HistoryBackendMock : public HistoryBackend {
+ public:
+ HistoryBackendMock() : HistoryBackend(FilePath(), NULL, NULL) {}
+ MOCK_METHOD1(GetAllTypedURLs, bool(std::vector<history::URLRow>* entries));
+ MOCK_METHOD2(UpdateURL, bool(history::URLID id, history::URLRow& url));
+ MOCK_METHOD2(SetPageTitle, void(const GURL& url, const std::wstring& title));
+ MOCK_METHOD2(GetURL, bool(const GURL& url_id, history::URLRow* url_row));
+ MOCK_METHOD1(DeleteURL, void(const GURL& url));
+};
+
+class HistoryThreadNotificationService :
+ public base::RefCountedThreadSafe<HistoryThreadNotificationService> {
+ public:
+ explicit HistoryThreadNotificationService(Thread* history_thread)
+ : done_event_(false, false),
+ history_thread_(history_thread) {}
+
+ void Init() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ history_thread_->message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &HistoryThreadNotificationService::InitTask));
+ done_event_.Wait();
+ }
+
+ void TearDown() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ history_thread_->message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,
+ &HistoryThreadNotificationService::TearDownTask));
+ done_event_.Wait();
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<HistoryThreadNotificationService>;
+
+ void InitTask() {
+ service_.reset(new NotificationService());
+ done_event_.Signal();
+ }
+
+ void TearDownTask() {
+ service_.reset(NULL);
+ done_event_.Signal();
+ }
+
+ WaitableEvent done_event_;
+ Thread* history_thread_;
+ scoped_ptr<NotificationService> service_;
+};
+
+class HistoryServiceMock : public HistoryService {
+ public:
+ HistoryServiceMock() {}
+ MOCK_METHOD2(ScheduleDBTask, Handle(HistoryDBTask*,
+ CancelableRequestConsumerBase*));
+};
+
+class RunOnDBThreadTask : public Task {
+ public:
+ RunOnDBThreadTask(HistoryBackend* backend, HistoryDBTask* task)
+ : backend_(backend), task_(task) {}
+ virtual void Run() {
+ task_->RunOnDBThread(backend_, NULL);
+ }
+ private:
+ HistoryBackend* backend_;
+ HistoryDBTask* task_;
+};
+
+ACTION_P2(RunTaskOnDBThread, thread, backend) {
+ thread->message_loop()->PostTask(
+ FROM_HERE,
+ new RunOnDBThreadTask(backend, arg0));
+ return 0;
+}
+
+ACTION(QuitUIMessageLoop) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ MessageLoop::current()->Quit();
+}
+
+ACTION_P3(MakeTypedUrlSyncComponents, service, hb, dtc) {
+ TypedUrlModelAssociator* model_associator =
+ new TypedUrlModelAssociator(service, hb, dtc);
+ TypedUrlChangeProcessor* change_processor =
+ new TypedUrlChangeProcessor(model_associator, hb, service);
+ return ProfileSyncFactory::SyncComponents(model_associator, change_processor);
+}
+
+class ProfileSyncServiceTypedUrlTest : public testing::Test {
+ protected:
+ ProfileSyncServiceTypedUrlTest()
+ : ui_thread_(ChromeThread::UI, &message_loop_),
+ history_thread_("history"),
+ done_event_(false, false) {
+ }
+
+ virtual void SetUp() {
+ history_backend_ = new HistoryBackendMock();
+ history_service_ = new HistoryServiceMock();
+ EXPECT_CALL((*history_service_.get()), ScheduleDBTask(_, _))
+ .WillRepeatedly(RunTaskOnDBThread(&history_thread_,
+ history_backend_));
+ history_thread_.Start();
+
+ notification_service_ =
+ new HistoryThreadNotificationService(&history_thread_);
+ notification_service_->Init();
+ }
+
+ virtual void TearDown() {
+ history_backend_ = NULL;
+ history_service_ = NULL;
+ service_.reset();
+ notification_service_->TearDown();
+ history_thread_.Stop();
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void StartSyncService(Task* task) {
+ if (!service_.get()) {
+ service_.reset(
+ new TestingProfileSyncService(&factory_, &profile_, false));
+ service_->AddObserver(&observer_);
+ TypedUrlDataTypeController* data_type_controller =
+ new TypedUrlDataTypeController(&factory_,
+ &profile_,
+ service_.get());
+
+ EXPECT_CALL(factory_, CreateTypedUrlSyncComponents(_, _, _)).
+ WillOnce(MakeTypedUrlSyncComponents(service_.get(),
+ history_backend_.get(),
+ data_type_controller));
+ EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).
+ WillOnce(MakeDataTypeManager(&backend_mock_));
+
+ EXPECT_CALL(profile_, GetHistoryServiceWithoutCreating()).
+ WillRepeatedly(Return(history_service_.get()));
+
+ EXPECT_CALL(profile_, GetHistoryService(_)).
+ WillRepeatedly(Return(history_service_.get()));
+
+ // State changes once for the backend init and once for startup done.
+ EXPECT_CALL(observer_, OnStateChanged()).
+ WillOnce(InvokeTask(task)).
+ WillOnce(QuitUIMessageLoop());
+ service_->RegisterDataTypeController(data_type_controller);
+ service_->Initialize();
+ MessageLoop::current()->Run();
+ }
+ }
+
+ void CreateTypedUrlRoot() {
+ UserShare* user_share = service_->backend()->GetUserShareHandle();
+ DirectoryManager* dir_manager = user_share->dir_manager.get();
+
+ ScopedDirLookup dir(dir_manager, user_share->authenticated_name);
+ if (!dir.good())
+ return;
+
+ WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__);
+ MutableEntry node(&wtrans,
+ CREATE,
+ wtrans.root_id(),
+ browser_sync::kTypedUrlTag);
+ node.Put(UNIQUE_SERVER_TAG, browser_sync::kTypedUrlTag);
+ node.Put(IS_DIR, true);
+ node.Put(SERVER_IS_DIR, false);
+ node.Put(IS_UNSYNCED, false);
+ node.Put(IS_UNAPPLIED_UPDATE, false);
+ node.Put(SERVER_VERSION, 20);
+ node.Put(BASE_VERSION, 20);
+ node.Put(IS_DEL, false);
+ node.Put(ID, ids_.MakeServer(browser_sync::kTypedUrlTag));
+ sync_pb::EntitySpecifics specifics;
+ specifics.MutableExtension(sync_pb::typed_url);
+ node.Put(SPECIFICS, specifics);
+ }
+
+ void AddTypedUrlSyncNode(const history::URLRow& url) {
+ sync_api::WriteTransaction trans(
+ service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode typed_url_root(&trans);
+ ASSERT_TRUE(typed_url_root.InitByTagLookup(browser_sync::kTypedUrlTag));
+
+ sync_api::WriteNode node(&trans);
+ std::string tag = url.url().spec();
+ ASSERT_TRUE(node.InitUniqueByCreation(syncable::TYPED_URLS,
+ typed_url_root,
+ tag));
+ TypedUrlModelAssociator::WriteToSyncNode(url, &node);
+ }
+
+ void GetTypedUrlsFromSyncDB(std::vector<history::URLRow>* urls) {
+ sync_api::ReadTransaction trans(service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode typed_url_root(&trans);
+ if (!typed_url_root.InitByTagLookup(browser_sync::kTypedUrlTag))
+ return;
+
+ int64 child_id = typed_url_root.GetFirstChildId();
+ while (child_id != sync_api::kInvalidId) {
+ sync_api::ReadNode child_node(&trans);
+ if (!child_node.InitByIdLookup(child_id))
+ return;
+
+ const sync_pb::TypedUrlSpecifics& typed_url(
+ child_node.GetTypedUrlSpecifics());
+ history::URLRow new_url(GURL(typed_url.url()));
+
+ new_url.set_title(UTF8ToWide(typed_url.title()));
+ new_url.set_visit_count(typed_url.visit_count());
+ new_url.set_typed_count(typed_url.typed_count());
+ new_url.set_last_visit(
+ base::Time::FromInternalValue(typed_url.last_visit()));
+ new_url.set_hidden(typed_url.hidden());
+
+ urls->push_back(new_url);
+ child_id = child_node.GetSuccessorId();
+ }
+ }
+
+ void SetIdleChangeProcessorExpectations() {
+ EXPECT_CALL((*history_backend_.get()), SetPageTitle(_, _)).Times(0);
+ EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).Times(0);
+ EXPECT_CALL((*history_backend_.get()), GetURL(_, _)).Times(0);
+ EXPECT_CALL((*history_backend_.get()), DeleteURL(_)).Times(0);
+ }
+
+ static bool URLsEqual(history::URLRow& lhs, history::URLRow& rhs) {
+ return (lhs.url().spec().compare(rhs.url().spec()) == 0) &&
+ (lhs.title().compare(rhs.title()) == 0) &&
+ (lhs.visit_count() == rhs.visit_count()) &&
+ (lhs.typed_count() == rhs.typed_count()) &&
+ (lhs.last_visit() == rhs.last_visit()) &&
+ (lhs.hidden() == rhs.hidden());
+ }
+
+ static history::URLRow MakeTypedUrlEntry(const char* url,
+ const char* title,
+ int visit_count,
+ int typed_count,
+ int64 last_visit,
+ bool hidden) {
+ GURL gurl(url);
+ URLRow history_url(gurl);
+ history_url.set_title(UTF8ToWide(title));
+ history_url.set_visit_count(visit_count);
+ history_url.set_typed_count(typed_count);
+ history_url.set_last_visit(
+ base::Time::FromInternalValue(last_visit));
+ history_url.set_hidden(hidden);
+ return history_url;
+ }
+
+ friend class AddTypedUrlEntriesTask;
+ friend class CreateTypedUrlRootTask;
+
+ MessageLoopForUI message_loop_;
+ ChromeThread ui_thread_;
+ Thread history_thread_;
+ WaitableEvent done_event_;
+ scoped_refptr<HistoryThreadNotificationService> notification_service_;
+
+ scoped_ptr<TestingProfileSyncService> service_;
+ ProfileMock profile_;
+ ProfileSyncFactoryMock factory_;
+ SyncBackendHostMock backend_mock_;
+ ProfileSyncServiceObserverMock observer_;
+ scoped_refptr<HistoryBackendMock> history_backend_;
+ scoped_refptr<HistoryServiceMock> history_service_;
+
+ TestIdFactory ids_;
+};
+
+class CreateTypedUrlRootTask : public Task {
+ public:
+ explicit CreateTypedUrlRootTask(ProfileSyncServiceTypedUrlTest* test)
+ : test_(test) {
+ }
+
+ virtual void Run() {
+ test_->CreateTypedUrlRoot();
+ }
+
+ private:
+ ProfileSyncServiceTypedUrlTest* test_;
+};
+
+class AddTypedUrlEntriesTask : public Task {
+ public:
+ AddTypedUrlEntriesTask(ProfileSyncServiceTypedUrlTest* test,
+ const std::vector<history::URLRow>& entries)
+ : test_(test), entries_(entries) {
+ }
+
+ virtual void Run() {
+ test_->CreateTypedUrlRoot();
+ for (size_t i = 0; i < entries_.size(); ++i) {
+ test_->AddTypedUrlSyncNode(entries_[i]);
+ }
+ }
+
+ private:
+ ProfileSyncServiceTypedUrlTest* test_;
+ const std::vector<history::URLRow>& entries_;
+};
+
+TEST_F(ProfileSyncServiceTypedUrlTest, EmptyNativeEmptySync) {
+ EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
+ WillOnce(Return(true));
+ SetIdleChangeProcessorExpectations();
+ CreateTypedUrlRootTask task(this);
+ StartSyncService(&task);
+ std::vector<history::URLRow> sync_entries;
+ GetTypedUrlsFromSyncDB(&sync_entries);
+ EXPECT_EQ(0U, sync_entries.size());
+}
+
+TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeEmptySync) {
+ std::vector<history::URLRow> entries;
+ entries.push_back(MakeTypedUrlEntry("http://foo.com", "bar",
+ 1, 2, 15, false));
+ EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
+ WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true)));
+ SetIdleChangeProcessorExpectations();
+ CreateTypedUrlRootTask task(this);
+ StartSyncService(&task);
+ std::vector<history::URLRow> sync_entries;
+ GetTypedUrlsFromSyncDB(&sync_entries);
+ ASSERT_EQ(1U, entries.size());
+ EXPECT_TRUE(URLsEqual(entries[0], sync_entries[0]));
+}
+
+TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeHasSyncNoMerge) {
+ history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "entry",
+ 1, 2, 15, false));
+ history::URLRow sync_entry(MakeTypedUrlEntry("http://sync.com", "entry",
+ 2, 3, 16, false));
+
+ std::vector<history::URLRow> native_entries;
+ native_entries.push_back(native_entry);
+ EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
+ WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true)));
+
+ std::vector<history::URLRow> sync_entries;
+ sync_entries.push_back(sync_entry);
+ AddTypedUrlEntriesTask task(this, sync_entries);
+
+ EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).
+ WillRepeatedly(Return(true));
+ StartSyncService(&task);
+
+ std::map<std::string, history::URLRow> expected;
+ expected[native_entry.url().spec()] = native_entry;
+ expected[sync_entry.url().spec()] = sync_entry;
+
+ std::vector<history::URLRow> new_sync_entries;
+ GetTypedUrlsFromSyncDB(&new_sync_entries);
+
+ EXPECT_TRUE(new_sync_entries.size() == expected.size());
+ for (std::vector<history::URLRow>::iterator entry = new_sync_entries.begin();
+ entry != new_sync_entries.end(); ++entry) {
+ EXPECT_TRUE(URLsEqual(expected[entry->url().spec()], *entry));
+ }
+}
+
+TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeHasSyncMerge) {
+ history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "entry",
+ 1, 2, 15, false));
+ history::URLRow sync_entry(MakeTypedUrlEntry("http://native.com", "name",
+ 2, 1, 17, false));
+ history::URLRow merged_entry(MakeTypedUrlEntry("http://native.com", "name",
+ 2, 1, 17, false));
+
+ std::vector<history::URLRow> native_entries;
+ native_entries.push_back(native_entry);
+ EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
+ WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true)));
+
+ std::vector<history::URLRow> sync_entries;
+ sync_entries.push_back(sync_entry);
+ AddTypedUrlEntriesTask task(this, sync_entries);
+
+ EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).
+ WillRepeatedly(Return(true));
+ EXPECT_CALL((*history_backend_.get()), SetPageTitle(_, _)).
+ WillRepeatedly(Return());
+ StartSyncService(&task);
+
+ std::vector<history::URLRow> new_sync_entries;
+ GetTypedUrlsFromSyncDB(&new_sync_entries);
+ ASSERT_EQ(1U, new_sync_entries.size());
+ EXPECT_TRUE(URLsEqual(merged_entry, new_sync_entries[0]));
+}
diff --git a/chrome/browser/sync/protocol/typed_url_specifics.proto b/chrome/browser/sync/protocol/typed_url_specifics.proto
new file mode 100644
index 0000000..a6bd309
--- /dev/null
+++ b/chrome/browser/sync/protocol/typed_url_specifics.proto
@@ -0,0 +1,27 @@
+// 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.
+//
+// Sync protocol datatype extension for typed urls.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package sync_pb;
+
+import "sync.proto";
+
+// Properties of typed_url sync objects.
+message TypedUrlSpecifics {
+ optional string url = 1;
+ optional string title = 2;
+ optional int32 visit_count = 3;
+ optional int32 typed_count = 4;
+ optional int64 last_visit = 5;
+ optional bool hidden = 6;
+}
+
+extend EntitySpecifics {
+ optional TypedUrlSpecifics typed_url = 40781;
+}
diff --git a/chrome/browser/sync/syncable/model_type.cc b/chrome/browser/sync/syncable/model_type.cc
index 10ff451..63a8d65 100644
--- a/chrome/browser/sync/syncable/model_type.cc
+++ b/chrome/browser/sync/syncable/model_type.cc
@@ -8,6 +8,7 @@
#include "chrome/browser/sync/protocol/autofill_specifics.pb.h"
#include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
#include "chrome/browser/sync/protocol/preference_specifics.pb.h"
+#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
#include "chrome/browser/sync/protocol/sync.pb.h"
namespace syncable {
@@ -24,6 +25,9 @@ void AddDefaultExtensionValue(syncable::ModelType datatype,
case AUTOFILL:
specifics->MutableExtension(sync_pb::autofill);
break;
+ case TYPED_URLS:
+ specifics->MutableExtension(sync_pb::typed_url);
+ break;
default:
NOTREACHED() << "No known extension for model type.";
}
@@ -49,6 +53,9 @@ ModelType GetModelType(const sync_pb::SyncEntity& sync_pb_entity) {
if (sync_entity.specifics().HasExtension(sync_pb::autofill))
return syncable::AUTOFILL;
+ if (sync_entity.specifics().HasExtension(sync_pb::typed_url))
+ return syncable::TYPED_URLS;
+
// Loose check for server-created top-level folders that aren't
// bound to a particular model type.
if (!sync_entity.server_defined_unique_tag().empty() &&
diff --git a/chrome/browser/sync/syncable/model_type.h b/chrome/browser/sync/syncable/model_type.h
index e5dd648..7fc5655 100644
--- a/chrome/browser/sync/syncable/model_type.h
+++ b/chrome/browser/sync/syncable/model_type.h
@@ -28,6 +28,7 @@ enum ModelType {
BOOKMARKS, // A bookmark folder or a bookmark URL object.
PREFERENCES, // A preference folder or a preference object.
AUTOFILL, // An autofill folder or an autofill object.
+ TYPED_URLS, // A typed_url folder or a typed_url object.
MODEL_TYPE_COUNT,
};
diff --git a/chrome/browser/sync/syncable/syncable.cc b/chrome/browser/sync/syncable/syncable.cc
index 6a8e628..880074b 100644
--- a/chrome/browser/sync/syncable/syncable.cc
+++ b/chrome/browser/sync/syncable/syncable.cc
@@ -39,6 +39,7 @@
#include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
#include "chrome/browser/sync/protocol/preference_specifics.pb.h"
#include "chrome/browser/sync/protocol/service_constants.h"
+#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
#include "chrome/browser/sync/syncable/directory_backing_store.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
#include "chrome/browser/sync/syncable/syncable-inl.h"
@@ -1063,6 +1064,8 @@ syncable::ModelType Entry::GetServerModelType() const {
return PREFERENCES;
if (Get(SERVER_SPECIFICS).HasExtension(sync_pb::autofill))
return AUTOFILL;
+ if (Get(SERVER_SPECIFICS).HasExtension(sync_pb::typed_url))
+ return TYPED_URLS;
if (IsRoot())
return TOP_LEVEL_FOLDER;
// Loose check for server-created top-level folders that aren't
@@ -1090,6 +1093,8 @@ syncable::ModelType Entry::GetModelType() const {
return PREFERENCES;
if (Get(SPECIFICS).HasExtension(sync_pb::autofill))
return AUTOFILL;
+ if (Get(SPECIFICS).HasExtension(sync_pb::typed_url))
+ return TYPED_URLS;
if (IsRoot())
return TOP_LEVEL_FOLDER;
// Loose check for server-created top-level folders that aren't
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index b80b36d..124ccb5 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -700,6 +700,7 @@
'browser/sync/protocol/autofill_specifics.proto',
'browser/sync/protocol/bookmark_specifics.proto',
'browser/sync/protocol/preference_specifics.proto',
+ 'browser/sync/protocol/typed_url_specifics.proto',
],
'rules': [
{
@@ -849,6 +850,8 @@
'<(protoc_out_dir)/chrome/browser/sync/protocol/bookmark_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/typed_url_specifics.pb.cc',
+ '<(protoc_out_dir)/chrome/browser/sync/protocol/typed_url_specifics.pb.h',
'browser/sync/engine/all_status.cc',
'browser/sync/engine/all_status.h',
'browser/sync/engine/apply_updates_command.cc',
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 9ce744d..6cbd8b1 100755
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1897,6 +1897,8 @@
'browser/sync/glue/data_type_manager_impl.h',
'browser/sync/glue/database_model_worker.cc',
'browser/sync/glue/database_model_worker.h',
+ 'browser/sync/glue/history_model_worker.cc',
+ 'browser/sync/glue/history_model_worker.h',
'browser/sync/glue/http_bridge.cc',
'browser/sync/glue/http_bridge.h',
'browser/sync/glue/model_associator.h',
@@ -1908,6 +1910,12 @@
'browser/sync/glue/preference_model_associator.h',
'browser/sync/glue/sync_backend_host.cc',
'browser/sync/glue/sync_backend_host.h',
+ 'browser/sync/glue/typed_url_change_processor.cc',
+ 'browser/sync/glue/typed_url_change_processor.h',
+ 'browser/sync/glue/typed_url_data_type_controller.cc',
+ 'browser/sync/glue/typed_url_data_type_controller.h',
+ 'browser/sync/glue/typed_url_model_associator.cc',
+ 'browser/sync/glue/typed_url_model_associator.h',
'browser/sync/glue/ui_model_worker.cc',
'browser/sync/glue/ui_model_worker.h',
'browser/sync/notification_method.h',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 368cb2a..ad74905 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -867,6 +867,7 @@
'browser/sync/glue/http_bridge_unittest.cc',
'browser/sync/glue/preference_data_type_controller_unittest.cc',
'browser/sync/glue/sync_backend_host_mock.h',
+ 'browser/sync/glue/typed_url_model_associator_unittest.cc',
'browser/sync/glue/ui_model_worker_unittest.cc',
'browser/sync/profile_sync_service_mock.h',
'browser/sync/profile_sync_service_startup_unittest.cc',
@@ -876,6 +877,7 @@
'browser/sync/profile_sync_factory_mock.h',
'browser/sync/profile_sync_service_autofill_unittest.cc',
'browser/sync/profile_sync_service_preference_unittest.cc',
+ 'browser/sync/profile_sync_service_typed_url_unittest.cc',
'browser/sync/profile_sync_test_util.h',
'browser/sync/sync_setup_wizard_unittest.cc',
'browser/sync/sync_ui_util_mac_unittest.mm',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index e3bc887..56528c6 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -174,6 +174,9 @@ const char kDisableSyncBookmarks[] = "disable-sync-bookmarks";
// Disable syncing of preferences.
const char kDisableSyncPreferences[] = "disable-sync-preferences";
+// Disable syncing of typed urls.
+const char kDisableSyncTypedUrls[] = "disable-sync-typed-urls";
+
// Enables the backend service for web resources, used in the new tab page for
// loading tips and recommendations from a JSON feed.
const char kDisableWebResources[] = "disable-web-resources";
@@ -291,6 +294,9 @@ const char kEnableSyncBookmarks[] = "enable-sync-bookmarks";
// Enable syncing browser preferences.
const char kEnableSyncPreferences[] = "enable-sync-preferences";
+// Enable syncing browser typed urls.
+const char kEnableSyncTypedUrls[] = "enable-sync-typed-urls";
+
// Enable the tabbed bookmark manager
const char kEnableTabbedBookmarkManager[] = "enable-tabbed-bookmark-manager";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index eb3b06e..134df40 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -65,6 +65,7 @@ extern const char kDisableSync[];
extern const char kDisableSyncAutofill[];
extern const char kDisableSyncBookmarks[];
extern const char kDisableSyncPreferences[];
+extern const char kDisableSyncTypedUrls[];
extern const char kDisableWebResources[];
extern const char kDisableWebSecurity[];
extern const char kDisableWebSockets[];
@@ -99,6 +100,7 @@ extern const char kEnableSync[];
extern const char kEnableSyncAutofill[];
extern const char kEnableSyncBookmarks[];
extern const char kEnableSyncPreferences[];
+extern const char kEnableSyncTypedUrls[];
extern const char kEnableTabbedBookmarkManager[];
extern const char kEnableUserDataDirProfiles[];
extern const char kEnableUserStyleSheet[];
diff --git a/chrome/test/profile_mock.h b/chrome/test/profile_mock.h
index c85a1ca..388d282 100644
--- a/chrome/test/profile_mock.h
+++ b/chrome/test/profile_mock.h
@@ -13,6 +13,8 @@ class ProfileMock : public TestingProfile {
public:
MOCK_METHOD0(GetBookmarkModel, BookmarkModel*());
MOCK_METHOD1(GetWebDataService, WebDataService*(ServiceAccessType access));
+ MOCK_METHOD1(GetHistoryService, HistoryService*(ServiceAccessType access));
+ MOCK_METHOD0(GetHistoryServiceWithoutCreating, HistoryService*());
};
#endif // CHROME_TEST_PROFILE_MOCK_H__