summaryrefslogtreecommitdiffstats
path: root/sync/js
diff options
context:
space:
mode:
authorakalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-15 09:35:42 +0000
committerakalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-15 09:35:42 +0000
commitc1c32c85357f14756247b04b8b5ae41b05bf2e16 (patch)
tree58f25f64e1fa592e8daf276ef69901cd2218f929 /sync/js
parent63ee33bde2ec8471a70f0f0ec6a1962dd07fc8ab (diff)
downloadchromium_src-c1c32c85357f14756247b04b8b5ae41b05bf2e16.zip
chromium_src-c1c32c85357f14756247b04b8b5ae41b05bf2e16.tar.gz
chromium_src-c1c32c85357f14756247b04b8b5ae41b05bf2e16.tar.bz2
[Sync] Move 'sync' target to sync/
Also move related test files. Move WriteNode::UpdateEntryWithEncryption to nigori_util.h. Clean up defines and dependencies. In particular, get rid of SYNC_ENGINE_VERSION_STRING and hard-code the string in the single place it's used. Rename data_encryption.* to data_encryption_win.* and add a pragma for crypt32.lib. Clean up exit-time constructor warnings in sync{able,er}_unittest.cc. Remove some unused files. BUG=117585 TEST= TBR=jhawkins@chromium.org Review URL: https://chromiumcodereview.appspot.com/9699057 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@126872 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sync/js')
-rw-r--r--sync/js/DEPS3
-rw-r--r--sync/js/README.js48
-rw-r--r--sync/js/js_arg_list.cc27
-rw-r--r--sync/js/js_arg_list.h44
-rw-r--r--sync/js/js_arg_list_unittest.cc40
-rw-r--r--sync/js/js_backend.h41
-rw-r--r--sync/js/js_controller.h50
-rw-r--r--sync/js/js_event_details.cc27
-rw-r--r--sync/js/js_event_details.h45
-rw-r--r--sync/js/js_event_details_unittest.cc36
-rw-r--r--sync/js/js_event_handler.h30
-rw-r--r--sync/js/js_reply_handler.h30
-rw-r--r--sync/js/js_test_util.cc137
-rw-r--r--sync/js/js_test_util.h109
-rw-r--r--sync/js/sync_js_controller.cc83
-rw-r--r--sync/js/sync_js_controller.h81
-rw-r--r--sync/js/sync_js_controller_unittest.cc126
17 files changed, 957 insertions, 0 deletions
diff --git a/sync/js/DEPS b/sync/js/DEPS
new file mode 100644
index 0000000..82d1d29
--- /dev/null
+++ b/sync/js/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+sync/util",
+]
diff --git a/sync/js/README.js b/sync/js/README.js
new file mode 100644
index 0000000..0fbfa66
--- /dev/null
+++ b/sync/js/README.js
@@ -0,0 +1,48 @@
+Overview of chrome://sync-internals
+-----------------------------------
+
+This note explains how chrome://sync-internals (also known as
+about:sync) interacts with the sync service/backend.
+
+Basically, chrome://sync-internals sends messages to the sync backend
+and the sync backend sends the reply asynchronously. The sync backend
+also asynchronously raises events which chrome://sync-internals listen
+to.
+
+A message and its reply has a name and a list of arguments, which is
+basically a wrapper around an immutable ListValue.
+
+An event has a name and a details object, which is represented by a
+JsEventDetails (js_event_details.h) object, which is basically a
+wrapper around an immutable DictionaryValue.
+
+Message/event flow
+------------------
+
+chrome://sync-internals is represented by SyncInternalsUI
+(chrome/browser/ui/webui/sync_internals_ui.h). SyncInternalsUI
+interacts with the sync service via a JsController (js_controller.h)
+object, which has a ProcessJsMessage() method that just delegates to
+an underlying JsBackend instance (js_backend.h). The SyncInternalsUI
+object also registers itself (as a JsEventHandler
+[js_event_handler.h]) to the JsController object, and any events
+raised by the JsBackend are propagated to the JsController and then to
+the registered JsEventHandlers.
+
+The ProcessJsMessage() takes a WeakHandle (weak_handle.h) to a
+JsReplyHandler (js_reply_handler.h), which the backend uses to send
+replies safely across threads. SyncInternalsUI implements
+JsReplyHandler, so it simply passes itself as the reply handler when
+it calls ProcessJsMessage() on the JsController.
+
+The following objects live on the UI thread:
+
+- SyncInternalsUI (implements JsEventHandler, JsReplyHandler)
+- SyncJsController (implements JsController, JsEventHandler)
+
+The following objects live on the sync thread:
+
+- SyncManager::SyncInternal (implements JsBackend)
+
+Of course, none of these objects need to know where the other objects
+live, since they interact via WeakHandles.
diff --git a/sync/js/js_arg_list.cc b/sync/js/js_arg_list.cc
new file mode 100644
index 0000000..d8ab8e2
--- /dev/null
+++ b/sync/js/js_arg_list.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sync/js/js_arg_list.h"
+
+#include "base/json/json_writer.h"
+
+namespace browser_sync {
+
+JsArgList::JsArgList() {}
+
+JsArgList::JsArgList(ListValue* args) : args_(args) {}
+
+JsArgList::~JsArgList() {}
+
+const ListValue& JsArgList::Get() const {
+ return args_.Get();
+}
+
+std::string JsArgList::ToString() const {
+ std::string str;
+ base::JSONWriter::Write(&Get(), false, &str);
+ return str;
+}
+
+} // namespace browser_sync
diff --git a/sync/js/js_arg_list.h b/sync/js/js_arg_list.h
new file mode 100644
index 0000000..aab49d9
--- /dev/null
+++ b/sync/js/js_arg_list.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SYNC_JS_JS_ARG_LIST_H_
+#define SYNC_JS_JS_ARG_LIST_H_
+#pragma once
+
+// See README.js for design comments.
+
+#include <string>
+
+#include "base/values.h"
+#include "sync/util/immutable.h"
+
+namespace browser_sync {
+
+// A thin wrapper around Immutable<ListValue>. Used for passing
+// around argument lists to different threads.
+class JsArgList {
+ public:
+ // Uses an empty argument list.
+ JsArgList();
+
+ // Takes over the data in |args|, leaving |args| empty.
+ explicit JsArgList(ListValue* args);
+
+ ~JsArgList();
+
+ const ListValue& Get() const;
+
+ std::string ToString() const;
+
+ // Copy constructor and assignment operator welcome.
+
+ private:
+ typedef Immutable<ListValue, HasSwapMemFnByPtr<ListValue> >
+ ImmutableListValue;
+ ImmutableListValue args_;
+};
+
+} // namespace browser_sync
+
+#endif // SYNC_JS_JS_ARG_LIST_H_
diff --git a/sync/js/js_arg_list_unittest.cc b/sync/js/js_arg_list_unittest.cc
new file mode 100644
index 0000000..3f00e4b
--- /dev/null
+++ b/sync/js/js_arg_list_unittest.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sync/js/js_arg_list.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace browser_sync {
+namespace {
+
+class JsArgListTest : public testing::Test {};
+
+TEST_F(JsArgListTest, EmptyList) {
+ JsArgList arg_list;
+ EXPECT_TRUE(arg_list.Get().empty());
+ EXPECT_EQ("[]", arg_list.ToString());
+}
+
+TEST_F(JsArgListTest, FromList) {
+ scoped_ptr<ListValue> list(new ListValue());
+ list->Append(Value::CreateBooleanValue(false));
+ list->Append(Value::CreateIntegerValue(5));
+ DictionaryValue* dict = new DictionaryValue();
+ list->Append(dict);
+ dict->SetString("foo", "bar");
+ dict->Set("baz", new ListValue());
+
+ scoped_ptr<ListValue> list_copy(list->DeepCopy());
+
+ JsArgList arg_list(list.get());
+
+ // |arg_list| should take over |list|'s data.
+ EXPECT_TRUE(list->empty());
+ EXPECT_TRUE(arg_list.Get().Equals(list_copy.get()));
+}
+
+} // namespace
+} // namespace browser_sync
diff --git a/sync/js/js_backend.h b/sync/js/js_backend.h
new file mode 100644
index 0000000..3c7f89a
--- /dev/null
+++ b/sync/js/js_backend.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SYNC_JS_JS_BACKEND_H_
+#define SYNC_JS_JS_BACKEND_H_
+#pragma once
+
+// See README.js for design comments.
+
+#include <string>
+
+namespace browser_sync {
+
+class JsArgList;
+class JsEventHandler;
+class JsReplyHandler;
+template <typename T> class WeakHandle;
+
+// Interface representing the backend of chrome://sync-internals. A
+// JsBackend can handle messages and can emit events to a
+// JsEventHandler.
+class JsBackend {
+ public:
+ // Starts emitting events to the given handler, if initialized.
+ virtual void SetJsEventHandler(
+ const WeakHandle<JsEventHandler>& event_handler) = 0;
+
+ // Processes the given message and replies via the given handler, if
+ // initialized.
+ virtual void ProcessJsMessage(
+ const std::string& name, const JsArgList& args,
+ const WeakHandle<JsReplyHandler>& reply_handler) = 0;
+
+ protected:
+ virtual ~JsBackend() {}
+};
+
+} // namespace browser_sync
+
+#endif // SYNC_JS_JS_BACKEND_H_
diff --git a/sync/js/js_controller.h b/sync/js/js_controller.h
new file mode 100644
index 0000000..d0deb20
--- /dev/null
+++ b/sync/js/js_controller.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SYNC_JS_JS_CONTROLLER_H_
+#define SYNC_JS_JS_CONTROLLER_H_
+#pragma once
+
+// See README.js for design comments.
+
+#include <string>
+
+namespace browser_sync {
+
+class JsArgList;
+class JsEventHandler;
+class JsReplyHandler;
+template <typename T> class WeakHandle;
+
+// An interface for objects that JsEventHandlers directly interact
+// with. JsEventHandlers can add themselves to receive events and
+// also send messages which will eventually reach the backend.
+class JsController {
+ public:
+ // Adds an event handler which will start receiving JS events (not
+ // immediately, so this can be called in the handler's constructor).
+ // Multiple event handlers are supported, but each event handler
+ // must be added at most once.
+ //
+ // Ideally, we'd take WeakPtrs, but we need the raw pointer values
+ // to be able to look them up for removal.
+ virtual void AddJsEventHandler(JsEventHandler* event_handler) = 0;
+
+ // Removes the given event handler if it has been added. It will
+ // immediately stop receiving any JS events.
+ virtual void RemoveJsEventHandler(JsEventHandler* event_handler) = 0;
+
+ // Processes a JS message. The reply (if any) will be sent to
+ // |reply_handler| if it is initialized.
+ virtual void ProcessJsMessage(
+ const std::string& name, const JsArgList& args,
+ const WeakHandle<JsReplyHandler>& reply_handler) = 0;
+
+ protected:
+ virtual ~JsController() {}
+};
+
+} // namespace browser_sync
+
+#endif // SYNC_JS_JS_CONTROLLER_H_
diff --git a/sync/js/js_event_details.cc b/sync/js/js_event_details.cc
new file mode 100644
index 0000000..693ba4c
--- /dev/null
+++ b/sync/js/js_event_details.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sync/js/js_event_details.h"
+
+#include "base/json/json_writer.h"
+
+namespace browser_sync {
+
+JsEventDetails::JsEventDetails() {}
+
+JsEventDetails::JsEventDetails(DictionaryValue* details) : details_(details) {}
+
+JsEventDetails::~JsEventDetails() {}
+
+const DictionaryValue& JsEventDetails::Get() const {
+ return details_.Get();
+}
+
+std::string JsEventDetails::ToString() const {
+ std::string str;
+ base::JSONWriter::Write(&Get(), false, &str);
+ return str;
+}
+
+} // namespace browser_sync
diff --git a/sync/js/js_event_details.h b/sync/js/js_event_details.h
new file mode 100644
index 0000000..df59851
--- /dev/null
+++ b/sync/js/js_event_details.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SYNC_JS_JS_EVENT_DETAILS_H_
+#define SYNC_JS_JS_EVENT_DETAILS_H_
+#pragma once
+
+// See README.js for design comments.
+
+#include <string>
+
+#include "base/values.h"
+#include "sync/util/immutable.h"
+
+namespace browser_sync {
+
+// A thin wrapper around Immutable<DictionaryValue>. Used for passing
+// around event details to different threads.
+class JsEventDetails {
+ public:
+ // Uses an empty dictionary.
+ JsEventDetails();
+
+ // Takes over the data in |details|, leaving |details| empty.
+ explicit JsEventDetails(DictionaryValue* details);
+
+ ~JsEventDetails();
+
+ const DictionaryValue& Get() const;
+
+ std::string ToString() const;
+
+ // Copy constructor and assignment operator welcome.
+
+ private:
+ typedef Immutable<DictionaryValue, HasSwapMemFnByPtr<DictionaryValue> >
+ ImmutableDictionaryValue;
+
+ ImmutableDictionaryValue details_;
+};
+
+} // namespace browser_sync
+
+#endif // SYNC_JS_JS_EVENT_DETAILS_H_
diff --git a/sync/js/js_event_details_unittest.cc b/sync/js/js_event_details_unittest.cc
new file mode 100644
index 0000000..01cea19
--- /dev/null
+++ b/sync/js/js_event_details_unittest.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sync/js/js_event_details.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace browser_sync {
+namespace {
+
+class JsEventDetailsTest : public testing::Test {};
+
+TEST_F(JsEventDetailsTest, EmptyList) {
+ JsEventDetails details;
+ EXPECT_TRUE(details.Get().empty());
+ EXPECT_EQ("{}", details.ToString());
+}
+
+TEST_F(JsEventDetailsTest, FromDictionary) {
+ DictionaryValue dict;
+ dict.SetString("foo", "bar");
+ dict.Set("baz", new ListValue());
+
+ scoped_ptr<DictionaryValue> dict_copy(dict.DeepCopy());
+
+ JsEventDetails details(&dict);
+
+ // |details| should take over |dict|'s data.
+ EXPECT_TRUE(dict.empty());
+ EXPECT_TRUE(details.Get().Equals(dict_copy.get()));
+}
+
+} // namespace
+} // namespace browser_sync
diff --git a/sync/js/js_event_handler.h b/sync/js/js_event_handler.h
new file mode 100644
index 0000000..ce15903
--- /dev/null
+++ b/sync/js/js_event_handler.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SYNC_JS_JS_EVENT_HANDLER_H_
+#define SYNC_JS_JS_EVENT_HANDLER_H_
+#pragma once
+
+// See README.js for design comments.
+
+#include <string>
+
+namespace browser_sync {
+
+class JsEventDetails;
+
+// An interface for objects that handle Javascript events (e.g.,
+// WebUIs).
+class JsEventHandler {
+ public:
+ virtual void HandleJsEvent(
+ const std::string& name, const JsEventDetails& details) = 0;
+
+ protected:
+ virtual ~JsEventHandler() {}
+};
+
+} // namespace browser_sync
+
+#endif // SYNC_JS_JS_EVENT_HANDLER_H_
diff --git a/sync/js/js_reply_handler.h b/sync/js/js_reply_handler.h
new file mode 100644
index 0000000..3b10309
--- /dev/null
+++ b/sync/js/js_reply_handler.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SYNC_JS_JS_REPLY_HANDLER_H_
+#define SYNC_JS_JS_REPLY_HANDLER_H_
+#pragma once
+
+// See README.js for design comments.
+
+#include <string>
+
+namespace browser_sync {
+
+class JsArgList;
+
+// An interface for objects that handle Javascript message replies
+// (e.g., WebUIs).
+class JsReplyHandler {
+ public:
+ virtual void HandleJsReply(
+ const std::string& name, const JsArgList& args) = 0;
+
+ protected:
+ virtual ~JsReplyHandler() {}
+};
+
+} // namespace browser_sync
+
+#endif // SYNC_JS_JS_REPLY_HANDLER_H_
diff --git a/sync/js/js_test_util.cc b/sync/js/js_test_util.cc
new file mode 100644
index 0000000..257a947
--- /dev/null
+++ b/sync/js/js_test_util.cc
@@ -0,0 +1,137 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sync/js/js_test_util.h"
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "sync/js/js_arg_list.h"
+#include "sync/js/js_event_details.h"
+
+namespace browser_sync {
+
+void PrintTo(const JsArgList& args, ::std::ostream* os) {
+ *os << args.ToString();
+}
+
+void PrintTo(const JsEventDetails& details, ::std::ostream* os) {
+ *os << details.ToString();
+}
+
+namespace {
+
+// Matcher implementation for HasArgs().
+class HasArgsMatcher
+ : public ::testing::MatcherInterface<const JsArgList&> {
+ public:
+ explicit HasArgsMatcher(const JsArgList& expected_args)
+ : expected_args_(expected_args) {}
+
+ virtual ~HasArgsMatcher() {}
+
+ virtual bool MatchAndExplain(
+ const JsArgList& args,
+ ::testing::MatchResultListener* listener) const {
+ // No need to annotate listener since we already define PrintTo().
+ return args.Get().Equals(&expected_args_.Get());
+ }
+
+ virtual void DescribeTo(::std::ostream* os) const {
+ *os << "has args " << expected_args_.ToString();
+ }
+
+ virtual void DescribeNegationTo(::std::ostream* os) const {
+ *os << "doesn't have args " << expected_args_.ToString();
+ }
+
+ private:
+ const JsArgList expected_args_;
+
+ DISALLOW_COPY_AND_ASSIGN(HasArgsMatcher);
+};
+
+// Matcher implementation for HasDetails().
+class HasDetailsMatcher
+ : public ::testing::MatcherInterface<const JsEventDetails&> {
+ public:
+ explicit HasDetailsMatcher(const JsEventDetails& expected_details)
+ : expected_details_(expected_details) {}
+
+ virtual ~HasDetailsMatcher() {}
+
+ virtual bool MatchAndExplain(
+ const JsEventDetails& details,
+ ::testing::MatchResultListener* listener) const {
+ // No need to annotate listener since we already define PrintTo().
+ return details.Get().Equals(&expected_details_.Get());
+ }
+
+ virtual void DescribeTo(::std::ostream* os) const {
+ *os << "has details " << expected_details_.ToString();
+ }
+
+ virtual void DescribeNegationTo(::std::ostream* os) const {
+ *os << "doesn't have details " << expected_details_.ToString();
+ }
+
+ private:
+ const JsEventDetails expected_details_;
+
+ DISALLOW_COPY_AND_ASSIGN(HasDetailsMatcher);
+};
+
+} // namespace
+
+::testing::Matcher<const JsArgList&> HasArgs(const JsArgList& expected_args) {
+ return ::testing::MakeMatcher(new HasArgsMatcher(expected_args));
+}
+
+::testing::Matcher<const JsArgList&> HasArgsAsList(
+ const ListValue& expected_args) {
+ scoped_ptr<ListValue> expected_args_copy(expected_args.DeepCopy());
+ return HasArgs(JsArgList(expected_args_copy.get()));
+}
+
+::testing::Matcher<const JsEventDetails&> HasDetails(
+ const JsEventDetails& expected_details) {
+ return ::testing::MakeMatcher(new HasDetailsMatcher(expected_details));
+}
+
+::testing::Matcher<const JsEventDetails&> HasDetailsAsDictionary(
+ const DictionaryValue& expected_details) {
+ scoped_ptr<DictionaryValue> expected_details_copy(
+ expected_details.DeepCopy());
+ return HasDetails(JsEventDetails(expected_details_copy.get()));
+}
+
+MockJsBackend::MockJsBackend() {}
+
+MockJsBackend::~MockJsBackend() {}
+
+WeakHandle<JsBackend> MockJsBackend::AsWeakHandle() {
+ return MakeWeakHandle(AsWeakPtr());
+}
+
+MockJsController::MockJsController() {}
+
+MockJsController::~MockJsController() {}
+
+MockJsEventHandler::MockJsEventHandler() {}
+
+WeakHandle<JsEventHandler> MockJsEventHandler::AsWeakHandle() {
+ return MakeWeakHandle(AsWeakPtr());
+}
+
+MockJsEventHandler::~MockJsEventHandler() {}
+
+MockJsReplyHandler::MockJsReplyHandler() {}
+
+MockJsReplyHandler::~MockJsReplyHandler() {}
+
+WeakHandle<JsReplyHandler> MockJsReplyHandler::AsWeakHandle() {
+ return MakeWeakHandle(AsWeakPtr());
+}
+
+} // namespace browser_sync
+
diff --git a/sync/js/js_test_util.h b/sync/js/js_test_util.h
new file mode 100644
index 0000000..ef6bec7
--- /dev/null
+++ b/sync/js/js_test_util.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SYNC_JS_JS_TEST_UTIL_H_
+#define SYNC_JS_JS_TEST_UTIL_H_
+#pragma once
+
+#include <ostream>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "sync/js/js_backend.h"
+#include "sync/js/js_controller.h"
+#include "sync/js/js_event_handler.h"
+#include "sync/js/js_reply_handler.h"
+#include "sync/util/weak_handle.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+namespace browser_sync {
+
+class JsArgList;
+class JsEventDetails;
+
+// Defined for googletest. Equivalent to "*os << args.ToString()".
+void PrintTo(const JsArgList& args, ::std::ostream* os);
+void PrintTo(const JsEventDetails& details, ::std::ostream* os);
+
+// A gmock matcher for JsArgList. Use like:
+//
+// EXPECT_CALL(mock, HandleJsReply("foo", HasArgs(expected_args)));
+::testing::Matcher<const JsArgList&> HasArgs(const JsArgList& expected_args);
+
+// Like HasArgs() but takes a ListValue instead.
+::testing::Matcher<const JsArgList&> HasArgsAsList(
+ const base::ListValue& expected_args);
+
+// A gmock matcher for JsEventDetails. Use like:
+//
+// EXPECT_CALL(mock, HandleJsEvent("foo", HasArgs(expected_details)));
+::testing::Matcher<const JsEventDetails&> HasDetails(
+ const JsEventDetails& expected_details);
+
+// Like HasDetails() but takes a DictionaryValue instead.
+::testing::Matcher<const JsEventDetails&> HasDetailsAsDictionary(
+ const base::DictionaryValue& expected_details);
+
+// Mocks.
+
+class MockJsBackend : public JsBackend,
+ public base::SupportsWeakPtr<MockJsBackend> {
+ public:
+ MockJsBackend();
+ virtual ~MockJsBackend();
+
+ WeakHandle<JsBackend> AsWeakHandle();
+
+ MOCK_METHOD1(SetJsEventHandler, void(const WeakHandle<JsEventHandler>&));
+ MOCK_METHOD3(ProcessJsMessage, void(const ::std::string&, const JsArgList&,
+ const WeakHandle<JsReplyHandler>&));
+};
+
+class MockJsController : public JsController,
+ public base::SupportsWeakPtr<MockJsController> {
+ public:
+ MockJsController();
+ virtual ~MockJsController();
+
+ MOCK_METHOD1(AddJsEventHandler, void(JsEventHandler*));
+ MOCK_METHOD1(RemoveJsEventHandler, void(JsEventHandler*));
+ MOCK_METHOD3(ProcessJsMessage,
+ void(const ::std::string&, const JsArgList&,
+ const WeakHandle<JsReplyHandler>&));
+};
+
+class MockJsEventHandler
+ : public JsEventHandler,
+ public base::SupportsWeakPtr<MockJsEventHandler> {
+ public:
+ MockJsEventHandler();
+ virtual ~MockJsEventHandler();
+
+ WeakHandle<JsEventHandler> AsWeakHandle();
+
+ MOCK_METHOD2(HandleJsEvent,
+ void(const ::std::string&, const JsEventDetails&));
+};
+
+class MockJsReplyHandler
+ : public JsReplyHandler,
+ public base::SupportsWeakPtr<MockJsReplyHandler> {
+ public:
+ MockJsReplyHandler();
+ virtual ~MockJsReplyHandler();
+
+ WeakHandle<JsReplyHandler> AsWeakHandle();
+
+ MOCK_METHOD2(HandleJsReply,
+ void(const ::std::string&, const JsArgList&));
+};
+
+} // namespace browser_sync
+
+#endif // SYNC_JS_JS_TEST_UTIL_H_
diff --git a/sync/js/sync_js_controller.cc b/sync/js/sync_js_controller.cc
new file mode 100644
index 0000000..0448486
--- /dev/null
+++ b/sync/js/sync_js_controller.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sync/js/sync_js_controller.h"
+
+#include "base/location.h"
+#include "sync/js/js_backend.h"
+#include "sync/js/js_event_details.h"
+
+namespace browser_sync {
+
+SyncJsController::PendingJsMessage::PendingJsMessage(
+ const std::string& name, const JsArgList& args,
+ const WeakHandle<JsReplyHandler>& reply_handler)
+ : name(name), args(args), reply_handler(reply_handler) {}
+
+SyncJsController::PendingJsMessage::~PendingJsMessage() {}
+
+SyncJsController::SyncJsController() {}
+
+SyncJsController::~SyncJsController() {
+ AttachJsBackend(WeakHandle<JsBackend>());
+}
+
+void SyncJsController::AddJsEventHandler(JsEventHandler* event_handler) {
+ js_event_handlers_.AddObserver(event_handler);
+ UpdateBackendEventHandler();
+}
+
+void SyncJsController::RemoveJsEventHandler(JsEventHandler* event_handler) {
+ js_event_handlers_.RemoveObserver(event_handler);
+ UpdateBackendEventHandler();
+}
+
+void SyncJsController::AttachJsBackend(
+ const WeakHandle<JsBackend>& js_backend) {
+ js_backend_ = js_backend;
+ UpdateBackendEventHandler();
+
+ if (js_backend_.IsInitialized()) {
+ // Process any queued messages.
+ for (PendingJsMessageList::const_iterator it =
+ pending_js_messages_.begin();
+ it != pending_js_messages_.end(); ++it) {
+ js_backend_.Call(FROM_HERE, &JsBackend::ProcessJsMessage,
+ it->name, it->args, it->reply_handler);
+ }
+ }
+}
+
+void SyncJsController::ProcessJsMessage(
+ const std::string& name, const JsArgList& args,
+ const WeakHandle<JsReplyHandler>& reply_handler) {
+ if (js_backend_.IsInitialized()) {
+ js_backend_.Call(FROM_HERE, &JsBackend::ProcessJsMessage,
+ name, args, reply_handler);
+ } else {
+ pending_js_messages_.push_back(
+ PendingJsMessage(name, args, reply_handler));
+ }
+}
+
+void SyncJsController::HandleJsEvent(const std::string& name,
+ const JsEventDetails& details) {
+ FOR_EACH_OBSERVER(JsEventHandler, js_event_handlers_,
+ HandleJsEvent(name, details));
+}
+
+void SyncJsController::UpdateBackendEventHandler() {
+ if (js_backend_.IsInitialized()) {
+ // To avoid making the backend send useless events, we clear the
+ // event handler we pass to it if we don't have any event
+ // handlers.
+ WeakHandle<JsEventHandler> backend_event_handler =
+ (js_event_handlers_.size() > 0) ?
+ MakeWeakHandle(AsWeakPtr()) : WeakHandle<SyncJsController>();
+ js_backend_.Call(FROM_HERE, &JsBackend::SetJsEventHandler,
+ backend_event_handler);
+ }
+}
+
+} // namespace browser_sync
diff --git a/sync/js/sync_js_controller.h b/sync/js/sync_js_controller.h
new file mode 100644
index 0000000..6e8f100
--- /dev/null
+++ b/sync/js/sync_js_controller.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SYNC_JS_SYNC_JS_CONTROLLER_H_
+#define SYNC_JS_SYNC_JS_CONTROLLER_H_
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "sync/js/js_arg_list.h"
+#include "sync/js/js_controller.h"
+#include "sync/js/js_event_handler.h"
+#include "sync/util/weak_handle.h"
+
+namespace browser_sync {
+
+class JsBackend;
+
+// A class that mediates between the sync JsEventHandlers and the sync
+// JsBackend.
+class SyncJsController
+ : public JsController, public JsEventHandler,
+ public base::SupportsWeakPtr<SyncJsController> {
+ public:
+ SyncJsController();
+
+ virtual ~SyncJsController();
+
+ // Sets the backend to route all messages to (if initialized).
+ // Sends any queued-up messages if |backend| is initialized.
+ void AttachJsBackend(const WeakHandle<JsBackend>& js_backend);
+
+ // JsController implementation.
+ virtual void AddJsEventHandler(JsEventHandler* event_handler) OVERRIDE;
+ virtual void RemoveJsEventHandler(JsEventHandler* event_handler) OVERRIDE;
+ // Queues up any messages that are sent when there is no attached
+ // initialized backend.
+ virtual void ProcessJsMessage(
+ const std::string& name, const JsArgList& args,
+ const WeakHandle<JsReplyHandler>& reply_handler) OVERRIDE;
+
+ // JsEventHandler implementation.
+ virtual void HandleJsEvent(const std::string& name,
+ const JsEventDetails& details) OVERRIDE;
+
+ private:
+ // A struct used to hold the arguments to ProcessJsMessage() for
+ // future invocation.
+ struct PendingJsMessage {
+ std::string name;
+ JsArgList args;
+ WeakHandle<JsReplyHandler> reply_handler;
+
+ PendingJsMessage(const std::string& name, const JsArgList& args,
+ const WeakHandle<JsReplyHandler>& reply_handler);
+
+ ~PendingJsMessage();
+ };
+
+ typedef std::vector<PendingJsMessage> PendingJsMessageList;
+
+ // Sets |js_backend_|'s event handler depending on how many
+ // underlying event handlers we have.
+ void UpdateBackendEventHandler();
+
+ WeakHandle<JsBackend> js_backend_;
+ ObserverList<JsEventHandler> js_event_handlers_;
+ PendingJsMessageList pending_js_messages_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncJsController);
+};
+
+} // namespace browser_sync
+
+#endif // SYNC_JS_SYNC_JS_CONTROLLER_H_
diff --git a/sync/js/sync_js_controller_unittest.cc b/sync/js/sync_js_controller_unittest.cc
new file mode 100644
index 0000000..10a982d
--- /dev/null
+++ b/sync/js/sync_js_controller_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sync/js/sync_js_controller.h"
+
+#include "base/message_loop.h"
+#include "base/values.h"
+#include "sync/js/js_arg_list.h"
+#include "sync/js/js_event_details.h"
+#include "sync/js/js_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace browser_sync {
+namespace {
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Mock;
+using ::testing::StrictMock;
+
+class SyncJsControllerTest : public testing::Test {
+ protected:
+ void PumpLoop() {
+ message_loop_.RunAllPending();
+ }
+
+ private:
+ MessageLoop message_loop_;
+};
+
+TEST_F(SyncJsControllerTest, Messages) {
+ InSequence dummy;
+ // |mock_backend| needs to outlive |sync_js_controller|.
+ StrictMock<MockJsBackend> mock_backend;
+ SyncJsController sync_js_controller;
+
+ ListValue arg_list1, arg_list2;
+ arg_list1.Append(Value::CreateBooleanValue(false));
+ arg_list2.Append(Value::CreateIntegerValue(5));
+ JsArgList args1(&arg_list1), args2(&arg_list2);
+
+ // TODO(akalin): Write matchers for WeakHandle and use them here
+ // instead of _.
+ EXPECT_CALL(mock_backend, SetJsEventHandler(_));
+ EXPECT_CALL(mock_backend, ProcessJsMessage("test1", HasArgs(args2), _));
+ EXPECT_CALL(mock_backend, ProcessJsMessage("test2", HasArgs(args1), _));
+
+ sync_js_controller.AttachJsBackend(mock_backend.AsWeakHandle());
+ sync_js_controller.ProcessJsMessage("test1", args2,
+ WeakHandle<JsReplyHandler>());
+ sync_js_controller.ProcessJsMessage("test2", args1,
+ WeakHandle<JsReplyHandler>());
+ PumpLoop();
+
+ // Let destructor of |sync_js_controller| call RemoveBackend().
+}
+
+TEST_F(SyncJsControllerTest, QueuedMessages) {
+ // |mock_backend| needs to outlive |sync_js_controller|.
+ StrictMock<MockJsBackend> mock_backend;
+ SyncJsController sync_js_controller;
+
+ ListValue arg_list1, arg_list2;
+ arg_list1.Append(Value::CreateBooleanValue(false));
+ arg_list2.Append(Value::CreateIntegerValue(5));
+ JsArgList args1(&arg_list1), args2(&arg_list2);
+
+ // Should queue messages.
+ sync_js_controller.ProcessJsMessage("test1", args2,
+ WeakHandle<JsReplyHandler>());
+ sync_js_controller.ProcessJsMessage("test2", args1,
+ WeakHandle<JsReplyHandler>());
+
+ Mock::VerifyAndClearExpectations(&mock_backend);
+
+ // TODO(akalin): Write matchers for WeakHandle and use them here
+ // instead of _.
+ EXPECT_CALL(mock_backend, SetJsEventHandler(_));
+ EXPECT_CALL(mock_backend, ProcessJsMessage("test1", HasArgs(args2), _));
+ EXPECT_CALL(mock_backend, ProcessJsMessage("test2", HasArgs(args1), _));
+
+ // Should call the queued messages.
+ sync_js_controller.AttachJsBackend(mock_backend.AsWeakHandle());
+ PumpLoop();
+
+ // Should do nothing.
+ sync_js_controller.AttachJsBackend(WeakHandle<JsBackend>());
+ PumpLoop();
+
+ // Should also do nothing.
+ sync_js_controller.AttachJsBackend(WeakHandle<JsBackend>());
+ PumpLoop();
+}
+
+TEST_F(SyncJsControllerTest, Events) {
+ InSequence dummy;
+ SyncJsController sync_js_controller;
+
+ DictionaryValue details_dict1, details_dict2;
+ details_dict1.SetString("foo", "bar");
+ details_dict2.SetInteger("baz", 5);
+ JsEventDetails details1(&details_dict1), details2(&details_dict2);
+
+ StrictMock<MockJsEventHandler> event_handler1, event_handler2;
+ EXPECT_CALL(event_handler1, HandleJsEvent("event", HasDetails(details1)));
+ EXPECT_CALL(event_handler2, HandleJsEvent("event", HasDetails(details1)));
+ EXPECT_CALL(event_handler1,
+ HandleJsEvent("anotherevent", HasDetails(details2)));
+ EXPECT_CALL(event_handler2,
+ HandleJsEvent("anotherevent", HasDetails(details2)));
+
+ sync_js_controller.AddJsEventHandler(&event_handler1);
+ sync_js_controller.AddJsEventHandler(&event_handler2);
+ sync_js_controller.HandleJsEvent("event", details1);
+ sync_js_controller.HandleJsEvent("anotherevent", details2);
+ sync_js_controller.RemoveJsEventHandler(&event_handler1);
+ sync_js_controller.RemoveJsEventHandler(&event_handler2);
+ sync_js_controller.HandleJsEvent("droppedevent", details2);
+
+ PumpLoop();
+}
+
+} // namespace
+} // namespace browser_sync