diff options
Diffstat (limited to 'sync/js')
-rw-r--r-- | sync/js/DEPS | 3 | ||||
-rw-r--r-- | sync/js/README.js | 48 | ||||
-rw-r--r-- | sync/js/js_arg_list.cc | 27 | ||||
-rw-r--r-- | sync/js/js_arg_list.h | 44 | ||||
-rw-r--r-- | sync/js/js_arg_list_unittest.cc | 40 | ||||
-rw-r--r-- | sync/js/js_backend.h | 41 | ||||
-rw-r--r-- | sync/js/js_controller.h | 50 | ||||
-rw-r--r-- | sync/js/js_event_details.cc | 27 | ||||
-rw-r--r-- | sync/js/js_event_details.h | 45 | ||||
-rw-r--r-- | sync/js/js_event_details_unittest.cc | 36 | ||||
-rw-r--r-- | sync/js/js_event_handler.h | 30 | ||||
-rw-r--r-- | sync/js/js_reply_handler.h | 30 | ||||
-rw-r--r-- | sync/js/js_test_util.cc | 137 | ||||
-rw-r--r-- | sync/js/js_test_util.h | 109 | ||||
-rw-r--r-- | sync/js/sync_js_controller.cc | 83 | ||||
-rw-r--r-- | sync/js/sync_js_controller.h | 81 | ||||
-rw-r--r-- | sync/js/sync_js_controller_unittest.cc | 126 |
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 |