diff options
author | akalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-01 07:42:12 +0000 |
---|---|---|
committer | akalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-01 07:42:12 +0000 |
commit | f92351d68c2089e7e36c00a4789ad3a199921ebc (patch) | |
tree | 49f4e1438481c33f37338178ca0515b597173b2d | |
parent | 14c5c9c368d6f38c09b7cf3d170b2b55caf93ffd (diff) | |
download | chromium_src-f92351d68c2089e7e36c00a4789ad3a199921ebc.zip chromium_src-f92351d68c2089e7e36c00a4789ad3a199921ebc.tar.gz chromium_src-f92351d68c2089e7e36c00a4789ad3a199921ebc.tar.bz2 |
[Sync] Add JS scriptability to syncapi
Add plumbing for ProfileSyncService, SyncBackendHost, and SyncManager to
be able to listen for messages and emit events to chrome://sync-internals.
Add initial version of notification-related messages and events and hook
it up to chrome://sync-internals.
Added rudimentary display for notifications to chrome://sync-internals.
Changed DOMUI class slightly to be more testable.
BUG=69500
TEST=
Review URL: http://codereview.chromium.org/6379010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@73263 0039d316-1c4b-4281-b951-d872f2087c98
36 files changed, 1875 insertions, 130 deletions
diff --git a/chrome/browser/dom_ui/dom_ui.cc b/chrome/browser/dom_ui/dom_ui.cc index a96521e..98d4bf2 100644 --- a/chrome/browser/dom_ui/dom_ui.cc +++ b/chrome/browser/dom_ui/dom_ui.cc @@ -134,18 +134,16 @@ Profile* DOMUI::GetProfile() const { return tab_contents()->profile(); } -// DOMUI, protected: ---------------------------------------------------------- - -void DOMUI::AddMessageHandler(DOMMessageHandler* handler) { - handlers_.push_back(handler); -} - RenderViewHost* DOMUI::GetRenderViewHost() const { DCHECK(tab_contents()); return tab_contents()->render_view_host(); } -// DOMUI, private: ------------------------------------------------------------ +// DOMUI, protected: ---------------------------------------------------------- + +void DOMUI::AddMessageHandler(DOMMessageHandler* handler) { + handlers_.push_back(handler); +} void DOMUI::ExecuteJavascript(const std::wstring& javascript) { GetRenderViewHost()->ExecuteJavascriptInWebFrame(std::wstring(), javascript); diff --git a/chrome/browser/dom_ui/dom_ui.h b/chrome/browser/dom_ui/dom_ui.h index f2463ab..67932ef 100644 --- a/chrome/browser/dom_ui/dom_ui.h +++ b/chrome/browser/dom_ui/dom_ui.h @@ -141,6 +141,10 @@ class DOMUI { protected: void AddMessageHandler(DOMMessageHandler* handler); + // Execute a string of raw Javascript on the page. Overridable for + // testing purposes. + virtual void ExecuteJavascript(const std::wstring& javascript); + // Options that may be overridden by individual DOM UI implementations. The // bool options default to false. See the public getters for more information. bool hide_favicon_; @@ -159,9 +163,6 @@ class DOMUI { TabContents* tab_contents_; private: - // Execute a string of raw Javascript on the page. - void ExecuteJavascript(const std::wstring& javascript); - // A map of message name -> message handling callback. typedef std::map<std::string, MessageCallback*> MessageCallbackMap; MessageCallbackMap message_callbacks_; diff --git a/chrome/browser/dom_ui/sync_internals_message_handler.cc b/chrome/browser/dom_ui/sync_internals_message_handler.cc deleted file mode 100644 index 5ef0084..0000000 --- a/chrome/browser/dom_ui/sync_internals_message_handler.cc +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/dom_ui/sync_internals_message_handler.h" - -#include "base/callback.h" -#include "base/logging.h" -#include "base/values.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/sync/profile_sync_service.h" -#include "chrome/browser/sync/sync_ui_util.h" - -SyncInternalsMessageHandler::SyncInternalsMessageHandler(Profile* profile) - : profile_(profile) { - DCHECK(profile_); - ProfileSyncService* service = profile_->GetProfileSyncService(); - if (service) { - service->AddObserver(this); - } - // TODO(akalin): Listen for when the service gets created/destroyed. -} - -SyncInternalsMessageHandler::~SyncInternalsMessageHandler() { - ProfileSyncService* service = profile_->GetProfileSyncService(); - if (service) { - service->RemoveObserver(this); - } -} - -void SyncInternalsMessageHandler::OnStateChanged() { - dom_ui_->CallJavascriptFunction(L"onSyncServiceStateChanged"); -} - -void SyncInternalsMessageHandler::RegisterMessages() { - dom_ui_->RegisterMessageCallback( - "getAboutInfo", - NewCallback( - this, &SyncInternalsMessageHandler::HandleGetAboutInfo)); -} - -void SyncInternalsMessageHandler::HandleGetAboutInfo(const ListValue* args) { - ProfileSyncService* service = profile_->GetProfileSyncService(); - DictionaryValue about_info; - sync_ui_util::ConstructAboutInformation(service, &about_info); - - dom_ui_->CallJavascriptFunction(L"onGetAboutInfoFinished", about_info); -} diff --git a/chrome/browser/dom_ui/sync_internals_message_handler.h b/chrome/browser/dom_ui/sync_internals_message_handler.h deleted file mode 100644 index 341750e..0000000 --- a/chrome/browser/dom_ui/sync_internals_message_handler.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_DOM_UI_SYNC_INTERNALS_MESSAGE_HANDLER_H_ -#define CHROME_BROWSER_DOM_UI_SYNC_INTERNALS_MESSAGE_HANDLER_H_ -#pragma once - -#include "base/basictypes.h" -#include "chrome/browser/dom_ui/dom_ui.h" -#include "chrome/browser/sync/profile_sync_service_observer.h" - -class Profile; - -class SyncInternalsMessageHandler : public DOMMessageHandler, - public ProfileSyncServiceObserver { - public: - // Does not take ownership of |profile|, which must outlive this - // object. - explicit SyncInternalsMessageHandler(Profile* profile); - virtual ~SyncInternalsMessageHandler(); - - // ProfileSyncServiceObserver implementation. - virtual void OnStateChanged(); - - protected: - // DOMMessageHandler implementation. - virtual void RegisterMessages(); - - private: - // Callback handlers. - void HandleGetAboutInfo(const ListValue* args); - - Profile* profile_; - - DISALLOW_COPY_AND_ASSIGN(SyncInternalsMessageHandler); -}; - -#endif // CHROME_BROWSER_DOM_UI_SYNC_INTERNALS_MESSAGE_HANDLER_H_ diff --git a/chrome/browser/dom_ui/sync_internals_ui.cc b/chrome/browser/dom_ui/sync_internals_ui.cc index 838f5d6..0af9f0c 100644 --- a/chrome/browser/dom_ui/sync_internals_ui.cc +++ b/chrome/browser/dom_ui/sync_internals_ui.cc @@ -4,22 +4,33 @@ #include "chrome/browser/dom_ui/sync_internals_ui.h" +#include <string> + +#include "base/logging.h" #include "base/ref_counted.h" #include "base/task.h" #include "base/tracked_objects.h" +#include "base/values.h" #include "chrome/browser/browser_thread.h" #include "chrome/browser/dom_ui/chrome_url_data_manager.h" #include "chrome/browser/dom_ui/sync_internals_html_source.h" -#include "chrome/browser/dom_ui/sync_internals_message_handler.h" -#include "chrome/browser/tab_contents/tab_contents.h" - -SyncInternalsUI::SyncInternalsUI(TabContents* contents) : DOMUI(contents) { - SyncInternalsMessageHandler* message_handler = - new SyncInternalsMessageHandler(contents->profile()); - message_handler->Attach(this); - AddMessageHandler(message_handler); +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sync/js_arg_list.h" +#include "chrome/browser/sync/js_frontend.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/sync_ui_util.h" +#include "chrome/common/render_messages_params.h" - BrowserThread::PostTask( +SyncInternalsUI::SyncInternalsUI(TabContents* contents) + : DOMUI(contents) { + browser_sync::JsFrontend* backend = GetJsFrontend(); + if (backend) { + backend->AddHandler(this); + } + // If this PostTask() call fails, it's most likely because this is + // being run from a unit test. The created objects will be cleaned + // up, anyway. + (void)BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, NewRunnableMethod( ChromeURLDataManager::GetInstance(), @@ -27,4 +38,50 @@ SyncInternalsUI::SyncInternalsUI(TabContents* contents) : DOMUI(contents) { make_scoped_refptr(new SyncInternalsHTMLSource()))); } -SyncInternalsUI::~SyncInternalsUI() {} +SyncInternalsUI::~SyncInternalsUI() { + browser_sync::JsFrontend* backend = GetJsFrontend(); + if (backend) { + backend->RemoveHandler(this); + } +} + +void SyncInternalsUI::ProcessDOMUIMessage( + const ViewHostMsg_DomMessage_Params& params) { + const std::string& name = params.name; + browser_sync::JsArgList args(params.arguments); + VLOG(1) << "Received message: " << name << " with args " + << args.ToString(); + // We handle this case directly because it needs to work even if + // the sync service doesn't exist. + if (name == "getAboutInfo") { + ListValue args; + DictionaryValue* about_info = new DictionaryValue(); + args.Append(about_info); + ProfileSyncService* service = GetProfile()->GetProfileSyncService(); + sync_ui_util::ConstructAboutInformation(service, about_info); + HandleJsEvent("onGetAboutInfoFinished", + browser_sync::JsArgList(args)); + } else { + browser_sync::JsFrontend* backend = GetJsFrontend(); + if (backend) { + backend->ProcessMessage(name, args, this); + } else { + LOG(WARNING) << "No sync service; dropping message " << name + << " with args " << args.ToString(); + } + } +} + +void SyncInternalsUI::HandleJsEvent(const std::string& name, + const browser_sync::JsArgList& args) { + VLOG(1) << "Handling event: " << name << " with args " << args.ToString(); + std::vector<const Value*> arg_list(args.Get().begin(), args.Get().end()); + CallJavascriptFunction(UTF8ToWide(name), arg_list); +} + +browser_sync::JsFrontend* SyncInternalsUI::GetJsFrontend() { + // If this returns NULL that means that sync is disabled for + // whatever reason. + ProfileSyncService* sync_service = GetProfile()->GetProfileSyncService(); + return sync_service ? sync_service->GetJsFrontend() : NULL; +} diff --git a/chrome/browser/dom_ui/sync_internals_ui.h b/chrome/browser/dom_ui/sync_internals_ui.h index d17f34a..1bc12a4 100644 --- a/chrome/browser/dom_ui/sync_internals_ui.h +++ b/chrome/browser/dom_ui/sync_internals_ui.h @@ -6,15 +6,47 @@ #define CHROME_BROWSER_DOM_UI_SYNC_INTERNALS_UI_H_ #pragma once +#include <string> + #include "base/basictypes.h" #include "chrome/browser/dom_ui/dom_ui.h" +#include "chrome/browser/sync/js_event_handler.h" + +namespace browser_sync { +class JsFrontend; +} // namespace browser_sync -class SyncInternalsUI : public DOMUI { +// The implementation for the chrome://sync-internals page. +class SyncInternalsUI : public DOMUI, public browser_sync::JsEventHandler { public: explicit SyncInternalsUI(TabContents* contents); virtual ~SyncInternalsUI(); + // DOMUI implementation. + // + // The following messages are processed: + // + // getAboutInfo(): + // Immediately fires a onGetAboutInfoFinished() event with a + // dictionary of sync-related stats and info. + // + // All other messages are routed to the sync service if it exists, + // and dropped otherwise. + // + // TODO(akalin): Add a simple isSyncEnabled() message and make + // getAboutInfo() be handled by the sync service. + virtual void ProcessDOMUIMessage( + const ViewHostMsg_DomMessage_Params& params); + + // browser_sync::JsEventHandler implementation. + virtual void HandleJsEvent(const std::string& name, + const browser_sync::JsArgList& args); + private: + // Returns the sync service's JsFrontend object, or NULL if the sync + // service does not exist. + browser_sync::JsFrontend* GetJsFrontend(); + DISALLOW_COPY_AND_ASSIGN(SyncInternalsUI); }; diff --git a/chrome/browser/dom_ui/sync_internals_ui_unittest.cc b/chrome/browser/dom_ui/sync_internals_ui_unittest.cc new file mode 100644 index 0000000..c36bfef --- /dev/null +++ b/chrome/browser/dom_ui/sync_internals_ui_unittest.cc @@ -0,0 +1,217 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/dom_ui/sync_internals_ui.h" + +#include <cstddef> +#include <string> + +#include "base/message_loop.h" +#include "base/values.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/sync/js_arg_list.h" +#include "chrome/browser/sync/js_test_util.h" +#include "chrome/browser/sync/profile_sync_service_mock.h" +#include "chrome/browser/tab_contents/test_tab_contents.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/test/profile_mock.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using browser_sync::HasArgsAsList; +using browser_sync::JsArgList; +using testing::NiceMock; +using testing::Return; +using testing::StrictMock; + +// Subclass of SyncInternalsUI to mock out ExecuteJavascript. +class TestSyncInternalsUI : public SyncInternalsUI { + public: + explicit TestSyncInternalsUI(TabContents* contents) + : SyncInternalsUI(contents) {} + virtual ~TestSyncInternalsUI() {} + + MOCK_METHOD1(ExecuteJavascript, void(const std::wstring&)); +}; + +class SyncInternalsUITest : public testing::Test { + protected: + // We allocate memory for |sync_internals_ui_| but we don't + // construct it. This is because we want to set mock expectations + // with its address before we construct it, and its constructor + // calls into our mocks. + SyncInternalsUITest() + : ui_thread_(BrowserThread::UI, MessageLoopForUI::current()), + test_tab_contents_(&profile_mock_, NULL), + test_sync_internals_ui_buf_( + operator new(sizeof(TestSyncInternalsUI))), + test_sync_internals_ui_constructor_called_(false) {} + + virtual ~SyncInternalsUITest() { + if (test_sync_internals_ui_constructor_called_) { + GetTestSyncInternalsUI()->~TestSyncInternalsUI(); + } + operator delete(test_sync_internals_ui_buf_); + } + + // Set up boilerplate expectations for calls done during + // SyncInternalUI's construction/destruction. + void ExpectSetupTeardownCalls() { + EXPECT_CALL(profile_mock_, GetProfileSyncService()) + .WillRepeatedly(Return(&profile_sync_service_mock_)); + + EXPECT_CALL(profile_sync_service_mock_, GetJsFrontend()) + .WillRepeatedly(Return(&mock_js_backend_)); + + // Called by sync_ui_util::ConstructAboutInformation(). + EXPECT_CALL(profile_sync_service_mock_, HasSyncSetupCompleted()) + .WillRepeatedly(Return(false)); + + // Called by SyncInternalsUI's constructor. + EXPECT_CALL(mock_js_backend_, + AddHandler(GetTestSyncInternalsUIAddress())); + + // Called by SyncInternalUI's destructor. + EXPECT_CALL(mock_js_backend_, + RemoveHandler(GetTestSyncInternalsUIAddress())); + } + + // Like ExpectSetupTeardownCalls() but with a NULL + // ProfileSyncService. + void ExpectSetupTeardownCallsNullService() { + EXPECT_CALL(profile_mock_, GetProfileSyncService()) + .WillRepeatedly(Return(static_cast<ProfileSyncService*>(NULL))); + } + + void ConstructTestSyncInternalsUI() { + if (test_sync_internals_ui_constructor_called_) { + ADD_FAILURE() << "ConstructTestSyncInternalsUI() should be called " + << "at most once per test"; + return; + } + new(test_sync_internals_ui_buf_) TestSyncInternalsUI(&test_tab_contents_); + test_sync_internals_ui_constructor_called_ = true; + } + + TestSyncInternalsUI* GetTestSyncInternalsUI() { + if (!test_sync_internals_ui_constructor_called_) { + ADD_FAILURE() << "ConstructTestSyncInternalsUI() should be called " + << "before GetTestSyncInternalsUI()"; + return NULL; + } + return GetTestSyncInternalsUIAddress(); + } + + // Used for passing into EXPECT_CALL(). + TestSyncInternalsUI* GetTestSyncInternalsUIAddress() { + return static_cast<TestSyncInternalsUI*>(test_sync_internals_ui_buf_); + } + + NiceMock<ProfileMock> profile_mock_; + StrictMock<ProfileSyncServiceMock> profile_sync_service_mock_; + StrictMock<browser_sync::MockJsFrontend> mock_js_backend_; + + private: + // Needed by |ui_thread_|. + MessageLoopForUI ui_loop_; + // Needed by |test_tab_contents_|. + BrowserThread ui_thread_; + TestTabContents test_tab_contents_; + void* test_sync_internals_ui_buf_; + bool test_sync_internals_ui_constructor_called_; +}; + +TEST_F(SyncInternalsUITest, HandleJsEvent) { + ExpectSetupTeardownCalls(); + + ConstructTestSyncInternalsUI(); + + EXPECT_CALL(*GetTestSyncInternalsUI(), + ExecuteJavascript(std::wstring(L"testMessage(5,true);"))); + + ListValue args; + args.Append(Value::CreateIntegerValue(5)); + args.Append(Value::CreateBooleanValue(true)); + GetTestSyncInternalsUI()->HandleJsEvent("testMessage", JsArgList(args)); +} + +TEST_F(SyncInternalsUITest, HandleJsEventNullService) { + ExpectSetupTeardownCallsNullService(); + + ConstructTestSyncInternalsUI(); + + EXPECT_CALL(*GetTestSyncInternalsUI(), + ExecuteJavascript(std::wstring(L"testMessage(5,true);"))); + + ListValue args; + args.Append(Value::CreateIntegerValue(5)); + args.Append(Value::CreateBooleanValue(true)); + GetTestSyncInternalsUI()->HandleJsEvent("testMessage", JsArgList(args)); +} + +TEST_F(SyncInternalsUITest, ProcessDOMUIMessageBasic) { + ExpectSetupTeardownCalls(); + + ViewHostMsg_DomMessage_Params params; + params.name = "testName"; + params.arguments.Append(Value::CreateIntegerValue(10)); + + EXPECT_CALL(mock_js_backend_, + ProcessMessage(params.name, HasArgsAsList(params.arguments), + GetTestSyncInternalsUIAddress())); + + ConstructTestSyncInternalsUI(); + + GetTestSyncInternalsUI()->ProcessDOMUIMessage(params); +} + +TEST_F(SyncInternalsUITest, ProcessDOMUIMessageBasicNullService) { + ExpectSetupTeardownCallsNullService(); + + ConstructTestSyncInternalsUI(); + + ViewHostMsg_DomMessage_Params params; + params.name = "testName"; + params.arguments.Append(Value::CreateIntegerValue(5)); + + // Should drop the message. + GetTestSyncInternalsUI()->ProcessDOMUIMessage(params); +} + +namespace { +const wchar_t kAboutInfoCall[] = + L"onGetAboutInfoFinished({\"summary\":\"SYNC DISABLED\"});"; +} // namespace + +TEST_F(SyncInternalsUITest, ProcessDOMUIMessageGetAboutInfo) { + ExpectSetupTeardownCalls(); + + ViewHostMsg_DomMessage_Params params; + params.name = "getAboutInfo"; + + ConstructTestSyncInternalsUI(); + + EXPECT_CALL(*GetTestSyncInternalsUI(), + ExecuteJavascript(std::wstring(kAboutInfoCall))); + + GetTestSyncInternalsUI()->ProcessDOMUIMessage(params); +} + +TEST_F(SyncInternalsUITest, ProcessDOMUIMessageGetAboutInfoNullService) { + ExpectSetupTeardownCallsNullService(); + + ViewHostMsg_DomMessage_Params params; + params.name = "getAboutInfo"; + + ConstructTestSyncInternalsUI(); + + EXPECT_CALL(*GetTestSyncInternalsUI(), + ExecuteJavascript(std::wstring(kAboutInfoCall))); + + GetTestSyncInternalsUI()->ProcessDOMUIMessage(params); +} + +} // namespace diff --git a/chrome/browser/resources/sync_internals/sync_index.html b/chrome/browser/resources/sync_internals/sync_index.html index 26ed19e..51e397d 100644 --- a/chrome/browser/resources/sync_internals/sync_index.html +++ b/chrome/browser/resources/sync_internals/sync_index.html @@ -7,6 +7,7 @@ chrome/test/functional/special_tabs.py. --> <script> function onLoad() { chrome.send('getAboutInfo'); + chrome.send('getNotificationState'); } function onGetAboutInfoFinished(aboutInfo) { @@ -14,9 +15,42 @@ function onGetAboutInfoFinished(aboutInfo) { jstProcess(new JsEvalContext(aboutInfo), aboutInfoDiv); } +function onGetNotificationStateFinished(notificationsEnabled) { + onSyncNotificationStateChange(notificationsEnabled); +} + function onSyncServiceStateChanged() { chrome.send('getAboutInfo'); } + +function onSyncNotificationStateChange(notificationsEnabled) { + var notificationsEnabledInfo = + document.getElementById('notificationsEnabledInfo'); + jstProcess( + new JsEvalContext({ 'notificationsEnabled': notificationsEnabled }), + notificationsEnabledInfo); +} + +var notificationCounts = {}; + +function onSyncIncomingNotification(changedTypes) { + for (var i = 0; i < changedTypes.length; ++i) { + var changedType = changedTypes[i]; + notificationCounts[changedType] = notificationCounts[changedType] || 0; + ++notificationCounts[changedType]; + } + + var infos = []; + for (var k in notificationCounts) { + var info = { 'modelType': k, 'notificationCount': notificationCounts[k] }; + infos.push(info); + } + + var notificationCountsInfo = + document.getElementById('notificationCountsInfo'); + jstProcess(new JsEvalContext({ 'notificationCounts': infos }), + notificationCountsInfo); +} </script> <style type="text/css"> @@ -223,5 +257,22 @@ table.list#details .name { </div> </div> +<div class="desc"><h2> Notifications </h2></div> +<p id='notificationsEnabledInfo'> + Enabled: <span jscontent='notificationsEnabled'></span> +</p> + +<table class='list' id='notificationCountsInfo'> + <tr jsselect='notificationCounts'> + <td class='name'> + <div jscontent='modelType'></div> + </td> + <td class='number'> + <div jscontent='notificationCount'></div> + </td> + </tr> +</table> +</td> + </body> </html> diff --git a/chrome/browser/sync/README.js b/chrome/browser/sync/README.js new file mode 100644 index 0000000..d902b71 --- /dev/null +++ b/chrome/browser/sync/README.js @@ -0,0 +1,54 @@ +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 asynchronous messages to the +sync backend and the sync backend asynchronously raises events to +chrome://sync-internals, either when replying to messages or when +something interesting happens. + +Both messages and events have a name and a list of arguments, the +latter of which is represented by a JsArgList (js_arg_list.h) object, +which is basically a wrapper around an immutable ListValue. + +Message/event flow +------------------ + +chrome://sync-internals is represented by SyncInternalsUI +(chrome/browser/dom_ui/sync_internals_ui.h). SyncInternalsUI +interacts with the sync service via a JsFrontend (js_frontend.h) +object, which has a ProcessMessage() method. The JsFrontend can +handle some messages itself, but it can also delegate the rest to a +JsBackend instance (js_backend.h), which also has a ProcessMessage() +method. A JsBackend can in turn handle some messages itself and +delegate to other JsBackend instances. + +Essentially, there is a tree with a JsFrontend as the root and +JsBackend as non-root internal nodes and leaf nodes (although +currently, the tree is more like a simple list). The sets of messages +handled by the JsBackends and the JsFrontend are disjoint, which means +that at most one node handles a given message type. Also, the +JsBackends may live on different threads, but JsArgList is thread-safe +so that's okay. + +SyncInternalsUI is a JsEventHandler (js_event_handler.h), which means +that it has a HandleJsEvent() method, but JsBackends cannot easily +access those objects. Instead, each JsBackend keeps track of its +parent router, which is a JsEventRouter object (js_event_router.h). +Basically, a JsEventRouter is another JsBackend object or a JsFrontend +object. So an event travels up through the JsEventRouter until it +reaches the JsFrontend, which knows about the existing JsEventHandlers +(via AddHandler()/RemoveHandler()) and so can delegate to the right +one. + +A diagram of the flow of a message and its reply: + +msg(args) -> F -> B -> B -> B + | | | + H <- R <- R <- R <- reply-event(args) + +F = JsFrontend, B = JsBackend, R = JsEventRouter, H = JsEventHandler + +Non-reply events are percolated up similarly. diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc index a275ae9..f989da1 100644 --- a/chrome/browser/sync/engine/syncapi.cc +++ b/chrome/browser/sync/engine/syncapi.cc @@ -19,6 +19,7 @@ #include "base/synchronization/lock.h" #include "base/task.h" #include "base/utf_string_conversions.h" +#include "base/values.h" #include "chrome/browser/browser_thread.h" #include "chrome/browser/sync/sync_constants.h" #include "chrome/browser/sync/engine/all_status.h" @@ -28,6 +29,9 @@ #include "chrome/browser/sync/engine/net/syncapi_server_connection_manager.h" #include "chrome/browser/sync/engine/syncer.h" #include "chrome/browser/sync/engine/syncer_thread.h" +#include "chrome/browser/sync/js_arg_list.h" +#include "chrome/browser/sync/js_backend.h" +#include "chrome/browser/sync/js_event_router.h" #include "chrome/browser/sync/notifier/server_notifier_thread.h" #include "chrome/browser/sync/notifier/state_writer.h" #include "chrome/browser/sync/protocol/app_specifics.pb.h" @@ -957,6 +961,7 @@ class SyncManager::SyncInternal public TalkMediator::Delegate, public sync_notifier::StateWriter, public browser_sync::ChannelEventHandler<syncable::DirectoryChangeEvent>, + public browser_sync::JsBackend, public SyncEngineEventListener { static const int kDefaultNudgeDelayMilliseconds; static const int kPreferencesNudgeDelayMilliseconds; @@ -964,6 +969,7 @@ class SyncManager::SyncInternal explicit SyncInternal(SyncManager* sync_manager) : core_message_loop_(NULL), observer_(NULL), + parent_router_(NULL), sync_manager_(sync_manager), registrar_(NULL), notification_pending_(false), @@ -1150,6 +1156,15 @@ class SyncManager::SyncInternal // SyncEngineEventListener implementation. virtual void OnSyncEngineEvent(const SyncEngineEvent& event); + + // browser_sync::JsBackend implementation. + virtual void SetParentJsEventRouter(browser_sync::JsEventRouter* router); + virtual void RemoveParentJsEventRouter(); + virtual const browser_sync::JsEventRouter* GetParentJsEventRouter() const; + virtual void ProcessMessage(const std::string& name, + const browser_sync::JsArgList& args, + const browser_sync::JsEventHandler* sender); + private: // Helper to handle the details of initializing the TalkMediator. // Must be called only after OpenDirectory() is called. @@ -1259,6 +1274,8 @@ class SyncManager::SyncInternal // WARNING: This can be NULL! SyncManager::Observer* observer_; + browser_sync::JsEventRouter* parent_router_; + // The ServerConnectionManager used to abstract communication between the // client (the Syncer) and the sync server. scoped_ptr<SyncAPIServerConnectionManager> connection_manager_; @@ -1782,6 +1799,10 @@ void SyncManager::RemoveObserver() { data_->set_observer(NULL); } +browser_sync::JsBackend* SyncManager::GetJsBackend() { + return data_; +} + void SyncManager::Shutdown() { data_->Shutdown(); } @@ -2154,6 +2175,42 @@ void SyncManager::SyncInternal::OnSyncEngineEvent( } } +void SyncManager::SyncInternal::SetParentJsEventRouter( + browser_sync::JsEventRouter* router) { + DCHECK(router); + parent_router_ = router; +} + +void SyncManager::SyncInternal::RemoveParentJsEventRouter() { + parent_router_ = NULL; +} + +const browser_sync::JsEventRouter* + SyncManager::SyncInternal::GetParentJsEventRouter() const { + return parent_router_; +} + +void SyncManager::SyncInternal::ProcessMessage( + const std::string& name, const browser_sync::JsArgList& args, + const browser_sync::JsEventHandler* sender) { + if (name == "getNotificationState") { + if (parent_router_) { + bool notifications_enabled = allstatus_.status().notifications_enabled; + ListValue return_args; + return_args.Append(Value::CreateBooleanValue(notifications_enabled)); + parent_router_->RouteJsEvent( + "onGetNotificationStateFinished", + browser_sync::JsArgList(return_args), sender); + } else { + VLOG(1) << "No parent router; not replying to message " << name + << " with args " << args.ToString(); + } + } else { + VLOG(1) << "Dropping unknown message " << name + << " with args " << args.ToString(); + } +} + void SyncManager::SyncInternal::OnNotificationStateChange( bool notifications_enabled) { VLOG(1) << "P2P: Notifications enabled = " @@ -2162,6 +2219,13 @@ void SyncManager::SyncInternal::OnNotificationStateChange( if (syncer_thread()) { syncer_thread()->SetNotificationsEnabled(notifications_enabled); } + if (parent_router_) { + ListValue args; + args.Append(Value::CreateBooleanValue(notifications_enabled)); + // TODO(akalin): Tidy up grammar in event names. + parent_router_->RouteJsEvent("onSyncNotificationStateChange", + browser_sync::JsArgList(args), NULL); + } if ((notifier_options_.notification_method != notifier::NOTIFICATION_SERVER) && notifications_enabled) { // Nudge the syncer thread when notifications are enabled, in case there is @@ -2248,6 +2312,21 @@ void SyncManager::SyncInternal::OnIncomingNotification( } else { LOG(WARNING) << "Sync received notification without any type information."; } + + if (parent_router_) { + ListValue args; + ListValue* changed_types = new ListValue(); + args.Append(changed_types); + for (browser_sync::sessions::TypePayloadMap::const_iterator + it = model_types_with_payloads.begin(); + it != model_types_with_payloads.end(); ++it) { + const std::string& model_type_str = + syncable::ModelTypeToString(it->first); + changed_types->Append(Value::CreateStringValue(model_type_str)); + } + parent_router_->RouteJsEvent("onSyncIncomingNotification", + browser_sync::JsArgList(args), NULL); + } } void SyncManager::SyncInternal::OnOutgoingNotification() { @@ -2321,4 +2400,17 @@ bool SyncManager::HasUnsyncedItems() const { return (trans.GetWrappedTrans()->directory()->unsynced_entity_count() != 0); } +void SyncManager::TriggerOnNotificationStateChangeForTest( + bool notifications_enabled) { + data_->OnNotificationStateChange(notifications_enabled); +} + +void SyncManager::TriggerOnIncomingNotificationForTest( + const syncable::ModelTypeBitSet& model_types) { + IncomingNotificationData notification_data; + notification_data.service_url = model_types.to_string(); + // Here we rely on the default notification method being SERVER. + data_->OnIncomingNotification(notification_data); +} + } // namespace sync_api diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h index 601e58e..e215bda 100644 --- a/chrome/browser/sync/engine/syncapi.h +++ b/chrome/browser/sync/engine/syncapi.h @@ -56,6 +56,7 @@ class FilePath; namespace browser_sync { +class JsBackend; class ModelSafeWorkerRegistrar; namespace sessions { @@ -899,6 +900,28 @@ class SyncManager { // destroyed so the SyncManager doesn't potentially dereference garbage. void RemoveObserver(); + // Returns a pointer to the JsBackend (which is owned by the sync + // manager). Never returns NULL. The following events are sent by + // the returned backend: + // + // onSyncNotificationStateChange(boolean notificationsEnabled): + // Sent when notifications are enabled or disabled. + // + // onSyncIncomingNotification(array changedTypes): + // Sent when an incoming notification arrives. |changedTypes| + // contains a list of sync types (strings) which have changed. + // + // The following messages are processed by the returned backend: + // + // getNotificationState(): + // If there is a parent router, sends the + // onGetNotificationStateFinished(boolean notifications_enabled) + // event to |sender| via the parent router with whether or not + // notifications are enabled. + // + // All other messages are dropped. + browser_sync::JsBackend* GetJsBackend(); + // Status-related getters. Typically GetStatusSummary will suffice, but // GetDetailedSyncStatus can be useful for gathering debug-level details of // the internals of the sync engine. @@ -925,6 +948,14 @@ class SyncManager { // any remaining unsynced items. bool HasUnsyncedItems() const; + // Functions used for testing. + + void TriggerOnNotificationStateChangeForTest( + bool notifications_enabled); + + void TriggerOnIncomingNotificationForTest( + const syncable::ModelTypeBitSet& model_types); + private: // An opaque pointer to the nested private class. SyncInternal* data_; diff --git a/chrome/browser/sync/engine/syncapi_unittest.cc b/chrome/browser/sync/engine/syncapi_unittest.cc index b83c501..3856ef8 100644 --- a/chrome/browser/sync/engine/syncapi_unittest.cc +++ b/chrome/browser/sync/engine/syncapi_unittest.cc @@ -6,17 +6,31 @@ // functionality is provided by the Syncable layer, which has its own // unit tests. We'll test SyncApi specific things in this harness. +#include "base/message_loop.h" #include "base/scoped_ptr.h" #include "base/scoped_temp_dir.h" +#include "base/values.h" +#include "chrome/browser/browser_thread.h" #include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/js_arg_list.h" +#include "chrome/browser/sync/js_backend.h" +#include "chrome/browser/sync/js_event_handler.h" +#include "chrome/browser/sync/js_event_router.h" +#include "chrome/browser/sync/js_test_util.h" #include "chrome/browser/sync/protocol/password_specifics.pb.h" #include "chrome/browser/sync/syncable/directory_manager.h" #include "chrome/browser/sync/syncable/syncable.h" #include "chrome/test/sync/engine/test_directory_setter_upper.h" - +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +using browser_sync::HasArgsAsList; using browser_sync::KeyParams; +using browser_sync::JsArgList; +using browser_sync::MockJsEventHandler; +using browser_sync::MockJsEventRouter; +using testing::_; +using testing::StrictMock; namespace sync_api { @@ -264,4 +278,153 @@ TEST_F(SyncApiTest, WriteAndReadPassword) { } } +namespace { + +class SyncManagerTest : public testing::Test { + protected: + SyncManagerTest() : ui_thread_(BrowserThread::UI, &ui_loop_) {} + + private: + // Needed by |ui_thread_|. + MessageLoopForUI ui_loop_; + // Needed by |sync_manager_|. + BrowserThread ui_thread_; + + protected: + SyncManager sync_manager_; +}; + +TEST_F(SyncManagerTest, ParentJsEventRouter) { + StrictMock<MockJsEventRouter> event_router; + browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend(); + EXPECT_EQ(NULL, js_backend->GetParentJsEventRouter()); + js_backend->SetParentJsEventRouter(&event_router); + EXPECT_EQ(&event_router, js_backend->GetParentJsEventRouter()); + js_backend->RemoveParentJsEventRouter(); + EXPECT_EQ(NULL, js_backend->GetParentJsEventRouter()); +} + +TEST_F(SyncManagerTest, ProcessMessage) { + const JsArgList kNoArgs; + + browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend(); + + // Messages sent without any parent router should be dropped. + { + StrictMock<MockJsEventHandler> event_handler; + js_backend->ProcessMessage("unknownMessage", + kNoArgs, &event_handler); + js_backend->ProcessMessage("getNotificationState", + kNoArgs, &event_handler); + } + + { + StrictMock<MockJsEventHandler> event_handler; + StrictMock<MockJsEventRouter> event_router; + + ListValue false_args; + false_args.Append(Value::CreateBooleanValue(false)); + + EXPECT_CALL(event_router, + RouteJsEvent("onGetNotificationStateFinished", + HasArgsAsList(false_args), + &event_handler)).Times(1); + + js_backend->SetParentJsEventRouter(&event_router); + + // This message should be dropped. + js_backend->ProcessMessage("unknownMessage", + kNoArgs, &event_handler); + + // This should trigger the reply. + js_backend->ProcessMessage("getNotificationState", + kNoArgs, &event_handler); + + js_backend->RemoveParentJsEventRouter(); + } + + // Messages sent after a parent router has been removed should be + // dropped. + { + StrictMock<MockJsEventHandler> event_handler; + js_backend->ProcessMessage("unknownMessage", + kNoArgs, &event_handler); + js_backend->ProcessMessage("getNotificationState", + kNoArgs, &event_handler); + } +} + +TEST_F(SyncManagerTest, OnNotificationStateChange) { + StrictMock<MockJsEventRouter> event_router; + + ListValue true_args; + true_args.Append(Value::CreateBooleanValue(true)); + ListValue false_args; + false_args.Append(Value::CreateBooleanValue(false)); + + EXPECT_CALL(event_router, + RouteJsEvent("onSyncNotificationStateChange", + HasArgsAsList(true_args), NULL)).Times(1); + EXPECT_CALL(event_router, + RouteJsEvent("onSyncNotificationStateChange", + HasArgsAsList(false_args), NULL)).Times(1); + + browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend(); + + sync_manager_.TriggerOnNotificationStateChangeForTest(true); + sync_manager_.TriggerOnNotificationStateChangeForTest(false); + + js_backend->SetParentJsEventRouter(&event_router); + sync_manager_.TriggerOnNotificationStateChangeForTest(true); + sync_manager_.TriggerOnNotificationStateChangeForTest(false); + js_backend->RemoveParentJsEventRouter(); + + sync_manager_.TriggerOnNotificationStateChangeForTest(true); + sync_manager_.TriggerOnNotificationStateChangeForTest(false); +} + +TEST_F(SyncManagerTest, OnIncomingNotification) { + StrictMock<MockJsEventRouter> event_router; + + const syncable::ModelTypeBitSet empty_model_types; + syncable::ModelTypeBitSet model_types; + model_types.set(syncable::BOOKMARKS); + model_types.set(syncable::THEMES); + + // Build expected_args to have a single argument with the string + // equivalents of model_types. + ListValue expected_args; + { + ListValue* model_type_list = new ListValue(); + expected_args.Append(model_type_list); + for (int i = syncable::FIRST_REAL_MODEL_TYPE; + i < syncable::MODEL_TYPE_COUNT; ++i) { + if (model_types[i]) { + model_type_list->Append( + Value::CreateStringValue( + syncable::ModelTypeToString( + syncable::ModelTypeFromInt(i)))); + } + } + } + + EXPECT_CALL(event_router, + RouteJsEvent("onSyncIncomingNotification", + HasArgsAsList(expected_args), NULL)).Times(1); + + browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend(); + + sync_manager_.TriggerOnIncomingNotificationForTest(empty_model_types); + sync_manager_.TriggerOnIncomingNotificationForTest(model_types); + + js_backend->SetParentJsEventRouter(&event_router); + sync_manager_.TriggerOnIncomingNotificationForTest(model_types); + js_backend->RemoveParentJsEventRouter(); + + sync_manager_.TriggerOnIncomingNotificationForTest(empty_model_types); + sync_manager_.TriggerOnIncomingNotificationForTest(model_types); +} + +} // namespace + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/sync_backend_host.cc b/chrome/browser/sync/glue/sync_backend_host.cc index 9b154fd..9230c2c 100644 --- a/chrome/browser/sync/glue/sync_backend_host.cc +++ b/chrome/browser/sync/glue/sync_backend_host.cc @@ -25,6 +25,7 @@ #include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/glue/http_bridge.h" #include "chrome/browser/sync/glue/password_model_worker.h" +#include "chrome/browser/sync/js_arg_list.h" #include "chrome/browser/sync/sessions/session_state.h" // TODO(tim): Remove this! We should have a syncapi pass-thru instead. #include "chrome/browser/sync/syncable/directory_manager.h" // Cryptographer. @@ -179,6 +180,15 @@ bool SyncBackendHost::IsCryptographerReady() const { GetUserShare()->dir_manager->cryptographer()->is_ready(); } +JsBackend* SyncBackendHost::GetJsBackend() { + if (syncapi_initialized_) { + return core_.get(); + } else { + NOTREACHED(); + return NULL; + } +} + sync_api::HttpPostProviderFactory* SyncBackendHost::MakeHttpBridgeFactory( URLRequestContextGetter* getter) { return new HttpBridgeFactory(getter); @@ -560,7 +570,8 @@ bool SyncBackendHost::HasUnsyncedItems() const { SyncBackendHost::Core::Core(SyncBackendHost* backend) : host_(backend), - syncapi_(new sync_api::SyncManager()) { + syncapi_(new sync_api::SyncManager()), + parent_router_(NULL) { } // Helper to construct a user agent string (ASCII) suitable for use by @@ -656,6 +667,7 @@ void SyncBackendHost::Core::DoShutdown(bool sync_disabled) { save_changes_timer_.Stop(); syncapi_->Shutdown(); // Stops the SyncerThread. syncapi_->RemoveObserver(); + DisconnectChildJsEventRouter(); host_->ui_worker()->OnSyncerShutdownComplete(); if (sync_disabled) @@ -808,7 +820,6 @@ bool SyncBackendHost::Core::IsCurrentThreadSafeForModel( return worker->CurrentThreadIsWorkThread(); } - void SyncBackendHost::Core::OnAuthError(const AuthError& auth_error) { // Post to our core loop so we can modify state. Could be on another thread. host_->frontend_loop_->PostTask(FROM_HERE, @@ -860,6 +871,14 @@ void SyncBackendHost::Core::OnClearServerDataFailed() { &Core::HandleClearServerDataFailedOnFrontendLoop)); } +void SyncBackendHost::Core::RouteJsEvent( + const std::string& name, const JsArgList& args, + const JsEventHandler* target) { + host_->frontend_loop_->PostTask( + FROM_HERE, NewRunnableMethod( + this, &Core::RouteJsEventOnFrontendLoop, name, args, target)); +} + void SyncBackendHost::Core::HandleStopSyncingPermanentlyOnFrontendLoop() { if (!host_ || !host_->frontend_) return; @@ -889,6 +908,17 @@ void SyncBackendHost::Core::HandleAuthErrorEventOnFrontendLoop( host_->frontend_->OnAuthError(); } +void SyncBackendHost::Core::RouteJsEventOnFrontendLoop( + const std::string& name, const JsArgList& args, + const JsEventHandler* target) { + if (!host_ || !parent_router_) + return; + + DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); + + parent_router_->RouteJsEvent(name, args, target); +} + void SyncBackendHost::Core::StartSavingChanges() { save_changes_timer_.Start( base::TimeDelta::FromSeconds(kSaveChangesIntervalSeconds), @@ -922,4 +952,62 @@ void SyncBackendHost::Core::DeleteSyncDataFolder() { } } +void SyncBackendHost::Core::SetParentJsEventRouter(JsEventRouter* router) { + DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); + DCHECK(router); + parent_router_ = router; + MessageLoop* core_message_loop = host_->core_thread_.message_loop(); + CHECK(core_message_loop); + core_message_loop->PostTask( + FROM_HERE, + NewRunnableMethod(this, + &SyncBackendHost::Core::ConnectChildJsEventRouter)); +} + +void SyncBackendHost::Core::RemoveParentJsEventRouter() { + DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); + parent_router_ = NULL; + MessageLoop* core_message_loop = host_->core_thread_.message_loop(); + CHECK(core_message_loop); + core_message_loop->PostTask( + FROM_HERE, + NewRunnableMethod(this, + &SyncBackendHost::Core::DisconnectChildJsEventRouter)); +} + +const JsEventRouter* SyncBackendHost::Core::GetParentJsEventRouter() const { + DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); + return parent_router_; +} + +void SyncBackendHost::Core::ProcessMessage( + const std::string& name, const JsArgList& args, + const JsEventHandler* sender) { + DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); + MessageLoop* core_message_loop = host_->core_thread_.message_loop(); + CHECK(core_message_loop); + core_message_loop->PostTask( + FROM_HERE, + NewRunnableMethod(this, + &SyncBackendHost::Core::DoProcessMessage, + name, args, sender)); +} + +void SyncBackendHost::Core::ConnectChildJsEventRouter() { + DCHECK_EQ(MessageLoop::current(), host_->core_thread_.message_loop()); + syncapi_->GetJsBackend()->SetParentJsEventRouter(this); +} + +void SyncBackendHost::Core::DisconnectChildJsEventRouter() { + DCHECK_EQ(MessageLoop::current(), host_->core_thread_.message_loop()); + syncapi_->GetJsBackend()->RemoveParentJsEventRouter(); +} + +void SyncBackendHost::Core::DoProcessMessage( + const std::string& name, const JsArgList& args, + const JsEventHandler* sender) { + DCHECK_EQ(MessageLoop::current(), host_->core_thread_.message_loop()); + syncapi_->GetJsBackend()->ProcessMessage(name, args, sender); +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/sync_backend_host.h b/chrome/browser/sync/glue/sync_backend_host.h index 01954a5..f7576493 100644 --- a/chrome/browser/sync/glue/sync_backend_host.h +++ b/chrome/browser/sync/glue/sync_backend_host.h @@ -19,8 +19,10 @@ #include "base/utf_string_conversions.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/engine/model_safe_worker.h" +#include "chrome/browser/sync/js_backend.h" #include "chrome/browser/sync/glue/data_type_controller.h" #include "chrome/browser/sync/glue/ui_model_worker.h" +#include "chrome/browser/sync/js_event_router.h" #include "chrome/browser/sync/syncable/model_type.h" #include "chrome/common/net/gaia/google_service_auth_error.h" #include "chrome/common/net/url_request_context_getter.h" @@ -42,6 +44,7 @@ struct SyncSessionSnapshot; class ChangeProcessor; class DataTypeController; +class JsArgList; // SyncFrontend is the interface used by SyncBackendHost to communicate with // the entity that created it and, presumably, is interested in sync-related @@ -110,7 +113,7 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // For testing. // TODO(skrul): Extract an interface so this is not needed. SyncBackendHost(); - ~SyncBackendHost(); + virtual ~SyncBackendHost(); // Called on |frontend_loop_| to kick off asynchronous initialization. // As a fallback when no cached auth information is available, try to @@ -231,10 +234,21 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // using a token previously received. bool IsCryptographerReady() const; + // Returns a pointer to the JsBackend (which is owned by the + // service). Must be called only after the sync backend has been + // initialized, and never returns NULL if you do so. Overrideable + // for testing purposes. + virtual JsBackend* GetJsBackend(); + + // TODO(akalin): Write unit tests for the JsBackend, finding a way + // to make this class testable in general. + protected: // The real guts of SyncBackendHost, to keep the public client API clean. class Core : public base::RefCountedThreadSafe<SyncBackendHost::Core>, - public sync_api::SyncManager::Observer { + public sync_api::SyncManager::Observer, + public JsBackend, + public JsEventRouter { public: explicit Core(SyncBackendHost* backend); @@ -260,6 +274,18 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { virtual void OnClearServerDataFailed(); virtual void OnClearServerDataSucceeded(); + // JsBackend implementation. + virtual void SetParentJsEventRouter(JsEventRouter* router); + virtual void RemoveParentJsEventRouter(); + virtual const JsEventRouter* GetParentJsEventRouter() const; + virtual void ProcessMessage(const std::string& name, const JsArgList& args, + const JsEventHandler* sender); + + // JsEventRouter implementation. + virtual void RouteJsEvent(const std::string& event_name, + const JsArgList& args, + const JsEventHandler* dst); + struct DoInitializeOptions { DoInitializeOptions( const GURL& service_url, @@ -332,6 +358,14 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // sync databases), as well as shutdown when you're no longer syncing. void DeleteSyncDataFolder(); + void ConnectChildJsEventRouter(); + + void DisconnectChildJsEventRouter(); + + void DoProcessMessage( + const std::string& name, const JsArgList& args, + const JsEventHandler* sender); + #if defined(UNIT_TEST) // Special form of initialization that does not try and authenticate the // last known user (since it will fail in test mode) and does some extra @@ -354,7 +388,7 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { friend class base::RefCountedThreadSafe<SyncBackendHost::Core>; friend class SyncBackendHostForProfileSyncTest; - ~Core(); + virtual ~Core(); // Return change processor for a particular model (return NULL on failure). ChangeProcessor* GetProcessor(syncable::ModelType modeltype); @@ -413,12 +447,13 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // frontend thread components. void HandleInitalizationCompletedOnFrontendLoop(); + void RouteJsEventOnFrontendLoop( + const std::string& name, const JsArgList& args, + const JsEventHandler* dst); + // Return true if a model lives on the current thread. bool IsCurrentThreadSafeForModel(syncable::ModelType model_type); - // True if credentials are ready for sync use. - bool CredentialsAvailable(); - // Our parent SyncBackendHost SyncBackendHost* host_; @@ -428,6 +463,8 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // The top-level syncapi entry point. scoped_ptr<sync_api::SyncManager> syncapi_; + JsEventRouter* parent_router_; + DISALLOW_COPY_AND_ASSIGN(Core); }; diff --git a/chrome/browser/sync/js_arg_list.cc b/chrome/browser/sync/js_arg_list.cc new file mode 100644 index 0000000..538b07c --- /dev/null +++ b/chrome/browser/sync/js_arg_list.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2011 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/js_arg_list.h" + +#include "base/json/json_writer.h" +#include "base/scoped_ptr.h" + +namespace browser_sync { + +JsArgList::JsArgList() : args_(new SharedListValue()) {} + +JsArgList::JsArgList(const ListValue& args) + : args_(new SharedListValue(args)) {} + +JsArgList::JsArgList(const std::vector<const Value*>& args) + : args_(new SharedListValue(args)) {} + +const ListValue& JsArgList::Get() const { + return args_->Get(); +} + +std::string JsArgList::ToString() const { + std::string str; + base::JSONWriter::Write(&Get(), false, &str); + return str; +} + +JsArgList::SharedListValue::SharedListValue() {} + +JsArgList::SharedListValue::SharedListValue(const ListValue& list_value) { + // Go through contortions to copy the list since ListValues are not + // copyable. + scoped_ptr<ListValue> list_value_copy(list_value.DeepCopy()); + list_value_.Swap(list_value_copy.get()); +} + +JsArgList::SharedListValue::SharedListValue( + const std::vector<const Value*>& value_list) { + for (std::vector<const Value*>::const_iterator it = value_list.begin(); + it != value_list.end(); ++it) { + list_value_.Append((*it)->DeepCopy()); + } +} + +const ListValue& JsArgList::SharedListValue::Get() const { + return list_value_; +} + +JsArgList::SharedListValue::~SharedListValue() {} + +} // namespace browser_sync diff --git a/chrome/browser/sync/js_arg_list.h b/chrome/browser/sync/js_arg_list.h new file mode 100644 index 0000000..80ea55e --- /dev/null +++ b/chrome/browser/sync/js_arg_list.h @@ -0,0 +1,54 @@ +// Copyright (c) 2011 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_JS_ARG_LIST_H_ +#define CHROME_BROWSER_SYNC_JS_ARG_LIST_H_ +#pragma once + +// See README.js for design comments. + +#include <string> +#include <vector> + +#include "base/ref_counted.h" +#include "base/values.h" + +namespace browser_sync { + +// A thread-safe ref-counted wrapper around an immutable ListValue. +// Used for passing around argument lists to different threads. +class JsArgList { + public: + JsArgList(); + explicit JsArgList(const ListValue& args); + explicit JsArgList(const std::vector<const Value*>& args); + + const ListValue& Get() const; + + std::string ToString() const; + + private: + class SharedListValue : public base::RefCountedThreadSafe<SharedListValue> { + public: + SharedListValue(); + explicit SharedListValue(const ListValue& list_value); + explicit SharedListValue(const std::vector<const Value*>& value_list); + + const ListValue& Get() const; + + private: + virtual ~SharedListValue(); + friend class base::RefCountedThreadSafe<SharedListValue>; + + ListValue list_value_; + }; + + scoped_refptr<const SharedListValue> args_; + + // Copy constructor and assignment operator welcome. +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_JS_ARG_LIST_H_ diff --git a/chrome/browser/sync/js_arg_list_unittest.cc b/chrome/browser/sync/js_arg_list_unittest.cc new file mode 100644 index 0000000..961a46b --- /dev/null +++ b/chrome/browser/sync/js_arg_list_unittest.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2011 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/js_arg_list.h" + +#include "base/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()); +} + +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()); + + JsArgList arg_list(*list); + + // Make sure arg_list takes a deep copy. + scoped_ptr<ListValue> list_copy(list->DeepCopy()); + list.reset(); + EXPECT_TRUE(arg_list.Get().Equals(list_copy.get())); +} + +TEST_F(JsArgListTest, FromVector) { + FundamentalValue bool_value(false); + FundamentalValue int_value(5); + DictionaryValue dict; + dict.SetString("foo", "bar"); + dict.Set("baz", new ListValue()); + + std::vector<const Value*> vec; + vec.push_back(&bool_value); + vec.push_back(&int_value); + vec.push_back(&dict); + + JsArgList arg_list(vec); + + ListValue list; + list.Append(bool_value.DeepCopy()); + list.Append(int_value.DeepCopy()); + list.Append(dict.DeepCopy()); + + // Make sure arg_list takes a deep copy. + vec.clear(); + dict.SetString("baz", "foo"); + EXPECT_TRUE(arg_list.Get().Equals(&list)); +} + +} // namespace +} // namespace browser_sync diff --git a/chrome/browser/sync/js_backend.h b/chrome/browser/sync/js_backend.h new file mode 100644 index 0000000..6d5ec92 --- /dev/null +++ b/chrome/browser/sync/js_backend.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 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_JS_BACKEND_H_ +#define CHROME_BROWSER_SYNC_JS_BACKEND_H_ +#pragma once + +// See README.js for design comments. + +#include <string> + +namespace browser_sync { + +class JsArgList; +class JsEventHandler; +class JsEventRouter; + +class JsBackend { + public: + // Sets the JS event router to which all backend events will be + // sent. + virtual void SetParentJsEventRouter(JsEventRouter* router) = 0; + + // Removes any existing JS event router. + virtual void RemoveParentJsEventRouter() = 0; + + // Gets the crurent JS event router, or NULL if there is none. Used + // for testing. + virtual const JsEventRouter* GetParentJsEventRouter() const = 0; + + // Processes the given message. All reply events are sent to the + // parent JS event router (if set). + virtual void ProcessMessage( + const std::string& name, const JsArgList& args, + const JsEventHandler* sender) = 0; + + protected: + virtual ~JsBackend() {} +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_JS_BACKEND_H_ diff --git a/chrome/browser/sync/js_event_handler.h b/chrome/browser/sync/js_event_handler.h new file mode 100644 index 0000000..0e8b301 --- /dev/null +++ b/chrome/browser/sync/js_event_handler.h @@ -0,0 +1,30 @@ +// Copyright (c) 2011 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_JS_EVENT_HANDLER_H_ +#define CHROME_BROWSER_SYNC_JS_EVENT_HANDLER_H_ +#pragma once + +// See README.js for design comments. + +#include <string> + +namespace browser_sync { + +class JsArgList; + +// An interface for objects that handle Javascript events (e.g., +// WebUIs). +class JsEventHandler { + public: + virtual void HandleJsEvent( + const std::string& name, const JsArgList& args) = 0; + + protected: + virtual ~JsEventHandler() {} +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_JS_EVENT_HANDLER_H_ diff --git a/chrome/browser/sync/js_event_handler_list.cc b/chrome/browser/sync/js_event_handler_list.cc new file mode 100644 index 0000000..abc619385 --- /dev/null +++ b/chrome/browser/sync/js_event_handler_list.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2011 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/js_event_handler_list.h" + +#include <cstddef> + +#include "base/logging.h" +#include "chrome/browser/sync/js_backend.h" +#include "chrome/browser/sync/js_event_handler.h" + +namespace browser_sync { + +JsEventHandlerList::PendingMessage::PendingMessage( + const std::string& name, const JsArgList& args, + const JsEventHandler* sender) + : name(name), args(args), sender(sender) {} + +JsEventHandlerList::JsEventHandlerList() : backend_(NULL) {} + +JsEventHandlerList::~JsEventHandlerList() { + RemoveBackend(); +} + +// We connect to the backend only when necessary, i.e. when there is +// at least one handler. + +void JsEventHandlerList::AddHandler(JsEventHandler* handler) { + handlers_.AddObserver(handler); + if (backend_) { + backend_->SetParentJsEventRouter(this); + } +} + +void JsEventHandlerList::RemoveHandler(JsEventHandler* handler) { + handlers_.RemoveObserver(handler); + if (backend_ && handlers_.size() == 0) { + backend_->RemoveParentJsEventRouter(); + } +} + +void JsEventHandlerList::SetBackend(JsBackend* backend) { + DCHECK(!backend_); + DCHECK(backend); + backend_ = backend; + + if (handlers_.size() > 0) { + backend_->SetParentJsEventRouter(this); + + // Process any queued messages. + PendingMessageList pending_messages; + pending_messages_.swap(pending_messages); + for (PendingMessageList::const_iterator it = pending_messages.begin(); + it != pending_messages.end(); ++it) { + backend_->ProcessMessage(it->name, it->args, it->sender); + } + } +} + +void JsEventHandlerList::RemoveBackend() { + if (backend_) { + backend_->RemoveParentJsEventRouter(); + backend_ = NULL; + } +} + +void JsEventHandlerList::ProcessMessage( + const std::string& name, const JsArgList& args, + const JsEventHandler* sender) { + if (backend_) { + backend_->ProcessMessage(name, args, sender); + } else { + pending_messages_.push_back(PendingMessage(name, args, sender)); + } +} + +void JsEventHandlerList::RouteJsEvent(const std::string& name, + const JsArgList& args, + const JsEventHandler* target) { + if (target) { + JsEventHandler* non_const_target(const_cast<JsEventHandler*>(target)); + if (handlers_.HasObserver(non_const_target)) { + non_const_target->HandleJsEvent(name, args); + } else { + VLOG(1) << "Unknown target; dropping event " << name + << " with args " << args.ToString(); + } + } else { + FOR_EACH_OBSERVER(JsEventHandler, handlers_, HandleJsEvent(name, args)); + } +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/js_event_handler_list.h b/chrome/browser/sync/js_event_handler_list.h new file mode 100644 index 0000000..2d5cf9e --- /dev/null +++ b/chrome/browser/sync/js_event_handler_list.h @@ -0,0 +1,76 @@ +// Copyright (c) 2011 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_JS_EVENT_HANDLER_LIST_H_ +#define CHROME_BROWSER_SYNC_JS_EVENT_HANDLER_LIST_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/observer_list.h" +#include "chrome/browser/sync/js_arg_list.h" +#include "chrome/browser/sync/js_event_router.h" +#include "chrome/browser/sync/js_frontend.h" + +namespace browser_sync { + +class JsBackend; +class JsEventHandler; + +// A beefed-up ObserverList<JsEventHandler> that transparently handles +// the communication between the handlers and a backend. +class JsEventHandlerList : public JsFrontend, public JsEventRouter { + public: + JsEventHandlerList(); + + virtual ~JsEventHandlerList(); + + // Sets the backend to route all messages to. Should be called only + // if a backend has not already been set. + void SetBackend(JsBackend* backend); + + // Removes any existing backend. + void RemoveBackend(); + + // JsFrontend implementation. Routes messages to any attached + // backend; if there is none, queues up the message for processing + // when the next backend is attached. + virtual void AddHandler(JsEventHandler* handler); + virtual void RemoveHandler(JsEventHandler* handler); + virtual void ProcessMessage( + const std::string& name, const JsArgList& args, + const JsEventHandler* sender); + + // JsEventRouter implementation. Routes the event to the + // appropriate handler(s). + virtual void RouteJsEvent(const std::string& name, + const JsArgList& args, + const JsEventHandler* target); + + private: + // A struct used to hold the arguments to ProcessMessage() for + // future invocation. + struct PendingMessage { + std::string name; + JsArgList args; + const JsEventHandler* sender; + + PendingMessage(const std::string& name, const JsArgList& args, + const JsEventHandler* sender); + }; + + typedef std::vector<PendingMessage> PendingMessageList; + + JsBackend* backend_; + ObserverList<JsEventHandler> handlers_; + PendingMessageList pending_messages_; + + DISALLOW_COPY_AND_ASSIGN(JsEventHandlerList); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_JS_EVENT_HANDLER_LIST_H_ diff --git a/chrome/browser/sync/js_event_handler_list_unittest.cc b/chrome/browser/sync/js_event_handler_list_unittest.cc new file mode 100644 index 0000000..449e70f --- /dev/null +++ b/chrome/browser/sync/js_event_handler_list_unittest.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2011 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/js_event_handler_list.h" + +#include "base/values.h" +#include "chrome/browser/sync/js_arg_list.h" +#include "chrome/browser/sync/js_test_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace browser_sync { +namespace { + +using ::testing::StrictMock; + +class JsEventHandlerListTest : public testing::Test {}; + +TEST_F(JsEventHandlerListTest, Basic) { + // |backend| must outlive |event_handlers|. + StrictMock<MockJsBackend> backend; + + JsEventHandlerList event_handlers; + + ListValue arg_list1, arg_list2; + arg_list1.Append(Value::CreateBooleanValue(false)); + arg_list2.Append(Value::CreateIntegerValue(5)); + JsArgList args1(arg_list1), args2(arg_list2); + + StrictMock<MockJsEventHandler> handler1, handler2; + + // Once from each call to AddHandler(). + EXPECT_CALL(backend, SetParentJsEventRouter(&event_handlers)).Times(2); + // Once from the second RemoveHandler(), once from the destructor. + EXPECT_CALL(backend, RemoveParentJsEventRouter()).Times(2); + EXPECT_CALL(backend, ProcessMessage("test1", HasArgs(args2), &handler1)); + EXPECT_CALL(backend, ProcessMessage("test2", HasArgs(args1), &handler2)); + + EXPECT_CALL(handler1, HandleJsEvent("reply1", HasArgs(args2))); + EXPECT_CALL(handler1, HandleJsEvent("allreply", HasArgs(args1))); + + EXPECT_CALL(handler2, HandleJsEvent("reply2", HasArgs(args1))); + EXPECT_CALL(handler2, HandleJsEvent("allreply", HasArgs(args1))); + EXPECT_CALL(handler2, HandleJsEvent("anotherreply2", HasArgs(args2))); + EXPECT_CALL(handler2, HandleJsEvent("anotherallreply", HasArgs(args2))); + + event_handlers.SetBackend(&backend); + + event_handlers.AddHandler(&handler1); + event_handlers.AddHandler(&handler2); + + event_handlers.ProcessMessage("test1", args2, &handler1); + + event_handlers.RouteJsEvent("reply2", args1, &handler2); + event_handlers.RouteJsEvent("reply1", args2, &handler1); + event_handlers.RouteJsEvent("allreply", args1, NULL); + + event_handlers.RemoveHandler(&handler1); + + event_handlers.ProcessMessage("test2", args1, &handler2); + + event_handlers.RouteJsEvent("droppedreply1", args1, &handler1); + event_handlers.RouteJsEvent("anotherreply2", args2, &handler2); + event_handlers.RouteJsEvent("anotherallreply", args2, NULL); + + event_handlers.RemoveHandler(&handler2); + + event_handlers.RouteJsEvent("anotherdroppedreply1", args1, &handler1); + event_handlers.RouteJsEvent("anotheranotherreply2", args2, &handler2); + event_handlers.RouteJsEvent("droppedallreply", args2, NULL); + + // Let destructor of |event_handlers| call RemoveBackend(). +} + +TEST_F(JsEventHandlerListTest, QueuedMessages) { + // |backend| must outlive |event_handlers|. + StrictMock<MockJsBackend> backend; + + JsEventHandlerList event_handlers; + + ListValue arg_list1, arg_list2; + arg_list1.Append(Value::CreateBooleanValue(false)); + arg_list2.Append(Value::CreateIntegerValue(5)); + JsArgList args1(arg_list1), args2(arg_list2); + + StrictMock<MockJsEventHandler> handler1, handler2; + + // Once from the call to SetBackend(). + EXPECT_CALL(backend, SetParentJsEventRouter(&event_handlers)); + // Once from the first call to RemoveBackend(). + EXPECT_CALL(backend, RemoveParentJsEventRouter()); + // Once from the call to SetBackend(). + EXPECT_CALL(backend, ProcessMessage("test1", HasArgs(args2), &handler1)); + // Once from the call to SetBackend(). + EXPECT_CALL(backend, ProcessMessage("test2", HasArgs(args1), &handler2)); + + event_handlers.AddHandler(&handler1); + event_handlers.AddHandler(&handler2); + + // Should queue messages. + event_handlers.ProcessMessage("test1", args2, &handler1); + event_handlers.ProcessMessage("test2", args1, &handler2); + + // Should call the queued messages. + event_handlers.SetBackend(&backend); + + event_handlers.RemoveBackend(); + // Should do nothing. + event_handlers.RemoveBackend(); +} + +} // namespace +} // namespace browser_sync diff --git a/chrome/browser/sync/js_event_router.h b/chrome/browser/sync/js_event_router.h new file mode 100644 index 0000000..be783f4 --- /dev/null +++ b/chrome/browser/sync/js_event_router.h @@ -0,0 +1,37 @@ +// Copyright (c) 2011 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_JS_EVENT_ROUTER_H_ +#define CHROME_BROWSER_SYNC_JS_EVENT_ROUTER_H_ +#pragma once + +// See README.js for design comments. + +#include <string> + +namespace browser_sync { + +class JsArgList; +class JsEventHandler; + +// An interface for objects that don't directly handle Javascript +// events but can pass them to JsEventHandlers or route them to other +// JsEventRouters. +class JsEventRouter { + public: + // If |target| is NULL that means the event is intended for every + // handler. Otherwise the event is meant for the given target only. + // |target| is const because it shouldn't be used except by the + // router that directly knows about it (which can then safely cast + // away the constness). + virtual void RouteJsEvent(const std::string& name, const JsArgList& args, + const JsEventHandler* target) = 0; + + protected: + virtual ~JsEventRouter() {} +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_JS_EVENT_ROUTER_H_ diff --git a/chrome/browser/sync/js_frontend.h b/chrome/browser/sync/js_frontend.h new file mode 100644 index 0000000..e57cacd --- /dev/null +++ b/chrome/browser/sync/js_frontend.h @@ -0,0 +1,43 @@ +// Copyright (c) 2011 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_JS_FRONTEND_H_ +#define CHROME_BROWSER_SYNC_JS_FRONTEND_H_ +#pragma once + +// See README.js for design comments. + +#include <string> + +namespace browser_sync { + +class JsArgList; +class JsEventHandler; + +// An interface for objects that interact directly with +// JsEventHandlers. +class JsFrontend { + public: + // Adds a handler which will start receiving JS events (not + // immediately, so this can be called in the handler's constructor). + // A handler must be added at most once. + virtual void AddHandler(JsEventHandler* handler) = 0; + + // Removes the given handler if it has been added. It will + // immediately stop receiving any JS events. + virtual void RemoveHandler(JsEventHandler* handler) = 0; + + // Sends a JS message. The reply (if any) will be sent to |sender| + // if it has been added. + virtual void ProcessMessage( + const std::string& name, const JsArgList& args, + const JsEventHandler* sender) = 0; + + protected: + virtual ~JsFrontend() {} +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_JS_FRONTEND_H_ diff --git a/chrome/browser/sync/js_test_util.cc b/chrome/browser/sync/js_test_util.cc new file mode 100644 index 0000000..3c736c7 --- /dev/null +++ b/chrome/browser/sync/js_test_util.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2011 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/js_test_util.h" + +#include "base/basictypes.h" +#include "chrome/browser/sync/js_arg_list.h" + +namespace browser_sync { + +void PrintTo(const JsArgList& args, ::std::ostream* os) { + *os << args.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); +}; + +} // 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) { + return HasArgs(JsArgList(expected_args)); +} + +} // namespace browser_sync + diff --git a/chrome/browser/sync/js_test_util.h b/chrome/browser/sync/js_test_util.h new file mode 100644 index 0000000..71badb3 --- /dev/null +++ b/chrome/browser/sync/js_test_util.h @@ -0,0 +1,70 @@ +// Copyright (c) 2011 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_JS_TEST_UTIL_H_ +#define CHROME_BROWSER_SYNC_JS_TEST_UTIL_H_ +#pragma once + +#include <ostream> +#include <string> + +#include "chrome/browser/sync/js_backend.h" +#include "chrome/browser/sync/js_frontend.h" +#include "chrome/browser/sync/js_event_handler.h" +#include "chrome/browser/sync/js_event_router.h" +#include "testing/gmock/include/gmock/gmock.h" + +class ListValue; + +namespace browser_sync { + +class JsArgList; + +// Defined for googletest. Equivalent to "*os << args.ToString()". +void PrintTo(const JsArgList& args, ::std::ostream* os); + +// A matcher for gmock. Use like: +// +// EXPECT_CALL(mock, ProcessMessage("foo", HasArgs(args))).Times(1); +::testing::Matcher<const JsArgList&> HasArgs(const JsArgList& expected_args); + +// Like HasArgs() but takes a ListValue instead. +::testing::Matcher<const JsArgList&> HasArgsAsList( + const ListValue& expected_args); + +// Mocks. + +class MockJsBackend : public JsBackend { + public: + MOCK_METHOD1(SetParentJsEventRouter, void(JsEventRouter*)); + MOCK_METHOD0(RemoveParentJsEventRouter, void()); + MOCK_CONST_METHOD0(GetParentJsEventRouter, const JsEventRouter*()); + MOCK_METHOD3(ProcessMessage, void(const ::std::string&, const JsArgList&, + const JsEventHandler*)); +}; + +class MockJsFrontend : public JsFrontend { + public: + MOCK_METHOD1(AddHandler, void(JsEventHandler*)); + MOCK_METHOD1(RemoveHandler, void(JsEventHandler*)); + MOCK_METHOD3(ProcessMessage, + void(const ::std::string&, const JsArgList&, + const JsEventHandler*)); +}; + +class MockJsEventHandler : public JsEventHandler { + public: + MOCK_METHOD2(HandleJsEvent, void(const ::std::string&, const JsArgList&)); +}; + +class MockJsEventRouter : public JsEventRouter { + public: + MOCK_METHOD3(RouteJsEvent, + void(const ::std::string&, const JsArgList&, + const JsEventHandler*)); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_JS_TEST_UTIL_H_ diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc index 4ea3c79..bb25585 100644 --- a/chrome/browser/sync/profile_sync_service.cc +++ b/chrome/browser/sync/profile_sync_service.cc @@ -30,9 +30,9 @@ #include "chrome/browser/sync/glue/data_type_controller.h" #include "chrome/browser/sync/glue/data_type_manager.h" #include "chrome/browser/sync/glue/session_data_type_controller.h" +#include "chrome/browser/sync/js_arg_list.h" #include "chrome/browser/sync/profile_sync_factory.h" #include "chrome/browser/sync/signin_manager.h" -#include "chrome/browser/sync/sync_ui_util.h" #include "chrome/browser/sync/token_migrator.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/net/gaia/gaia_constants.h" @@ -78,9 +78,9 @@ ProfileSyncService::ProfileSyncService(ProfileSyncFactory* factory, sync_service_url_(kDevServerUrl), backend_initialized_(false), is_auth_in_progress_(false), - ALLOW_THIS_IN_INITIALIZER_LIST(wizard_(this)), + wizard_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), unrecoverable_error_detected_(false), - ALLOW_THIS_IN_INITIALIZER_LIST(scoped_runnable_method_factory_(this)), + scoped_runnable_method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), token_migrator_(NULL), clear_server_data_state_(CLEAR_NOT_STARTED) { DCHECK(factory); @@ -475,6 +475,8 @@ void ProfileSyncService::Shutdown(bool sync_disabled) { data_type_manager_.reset(); } + js_event_handlers_.RemoveBackend(); + // Move aside the backend so nobody else tries to use it while we are // shutting it down. scoped_ptr<SyncBackendHost> doomed_backend(backend_.release()); @@ -538,6 +540,8 @@ void ProfileSyncService::UpdateLastSyncedTime() { void ProfileSyncService::NotifyObservers() { FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged()); + js_event_handlers_.RouteJsEvent( + "onSyncServiceStateChanged", browser_sync::JsArgList(), NULL); } // static @@ -602,6 +606,8 @@ void ProfileSyncService::OnUnrecoverableError( void ProfileSyncService::OnBackendInitialized() { backend_initialized_ = true; + js_event_handlers_.SetBackend(backend_->GetJsBackend()); + // The very first time the backend initializes is effectively the first time // we can say we successfully "synced". last_synced_time_ will only be null // in this case, because the pref wasn't restored on StartUp. @@ -1282,6 +1288,10 @@ bool ProfileSyncService::HasObserver(Observer* observer) const { return observers_.HasObserver(observer); } +browser_sync::JsFrontend* ProfileSyncService::GetJsFrontend() { + return &js_event_handlers_; +} + void ProfileSyncService::SyncEvent(SyncEventCodes code) { UMA_HISTOGRAM_ENUMERATION("Sync.EventCodes", code, MAX_SYNC_EVENT_CODE); } diff --git a/chrome/browser/sync/profile_sync_service.h b/chrome/browser/sync/profile_sync_service.h index 90acae7..ec9ce6b 100644 --- a/chrome/browser/sync/profile_sync_service.h +++ b/chrome/browser/sync/profile_sync_service.h @@ -21,6 +21,7 @@ #include "chrome/browser/sync/glue/data_type_manager.h" #include "chrome/browser/sync/glue/session_model_associator.h" #include "chrome/browser/sync/glue/sync_backend_host.h" +#include "chrome/browser/sync/js_event_handler_list.h" #include "chrome/browser/sync/profile_sync_service_observer.h" #include "chrome/browser/sync/signin_manager.h" #include "chrome/browser/sync/sync_setup_wizard.h" @@ -40,6 +41,10 @@ class ProfileSyncFactory; class TabContents; class TokenMigrator; +namespace browser_sync { +class JsFrontend; +} // namespace browser_sync + // ProfileSyncService is the layer between browser subsystems like bookmarks, // and the sync backend. Each subsystem is logically thought of as being // a sync datatype. @@ -306,6 +311,11 @@ class ProfileSyncService : public browser_sync::SyncFrontend, // Returns true if |observer| has already been added as an observer. bool HasObserver(Observer* observer) const; + // Returns a pointer to the service's JsFrontend (which is owned by + // the service). Never returns NULL. Overrideable for testing + // purposes. + virtual browser_sync::JsFrontend* GetJsFrontend(); + // Record stats on various events. static void SyncEvent(SyncEventCodes code); @@ -581,6 +591,8 @@ class ProfileSyncService : public browser_sync::SyncFrontend, ObserverList<Observer> observers_; + browser_sync::JsEventHandlerList js_event_handlers_; + NotificationRegistrar registrar_; ScopedRunnableMethodFactory<ProfileSyncService> diff --git a/chrome/browser/sync/profile_sync_service_mock.h b/chrome/browser/sync/profile_sync_service_mock.h index 32bff66..4e460f2 100644 --- a/chrome/browser/sync/profile_sync_service_mock.h +++ b/chrome/browser/sync/profile_sync_service_mock.h @@ -43,6 +43,7 @@ class ProfileSyncServiceMock : public ProfileSyncService { MOCK_METHOD0(InitializeBackend, void()); MOCK_METHOD1(AddObserver, void(Observer*)); MOCK_METHOD1(RemoveObserver, void(Observer*)); + MOCK_METHOD0(GetJsFrontend, browser_sync::JsFrontend*()); MOCK_CONST_METHOD0(HasSyncSetupCompleted, bool()); MOCK_METHOD1(ChangePreferredDataTypes, diff --git a/chrome/browser/sync/profile_sync_service_unittest.cc b/chrome/browser/sync/profile_sync_service_unittest.cc index 15b93aa..ce2134e 100644 --- a/chrome/browser/sync/profile_sync_service_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_unittest.cc @@ -13,6 +13,7 @@ #include "base/string_util.h" #include "base/string16.h" #include "base/utf_string_conversions.h" +#include "base/values.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/browser_thread.h" #include "chrome/browser/net/gaia/token_service.h" @@ -27,6 +28,8 @@ #include "chrome/browser/sync/glue/model_associator.h" #include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/glue/sync_backend_host_mock.h" +#include "chrome/browser/sync/js_arg_list.h" +#include "chrome/browser/sync/js_test_util.h" #include "chrome/browser/sync/profile_sync_factory.h" #include "chrome/browser/sync/profile_sync_factory_mock.h" #include "chrome/browser/sync/test_profile_sync_service.h" @@ -43,12 +46,16 @@ using browser_sync::BookmarkChangeProcessor; using browser_sync::BookmarkModelAssociator; using browser_sync::ChangeProcessor; using browser_sync::DataTypeController; +using browser_sync::HasArgs; +using browser_sync::JsArgList; +using browser_sync::MockJsEventHandler; using browser_sync::ModelAssociator; using browser_sync::SyncBackendHost; using browser_sync::SyncBackendHostMock; using browser_sync::UnrecoverableErrorHandler; using testing::_; using testing::Return; +using testing::StrictMock; using testing::WithArg; using testing::Invoke; @@ -310,14 +317,16 @@ class ProfileSyncServiceTest : public testing::Test { } void StartSyncService() { - StartSyncServiceAndSetInitialSyncEnded(true); + StartSyncServiceAndSetInitialSyncEnded(true, true); } - void StartSyncServiceAndSetInitialSyncEnded(bool set_initial_sync_ended) { + void StartSyncServiceAndSetInitialSyncEnded( + bool set_initial_sync_ended, + bool issue_auth_token) { if (!service_.get()) { // Set bootstrap to true and it will provide a logged in user for test service_.reset(new TestProfileSyncService(&factory_, profile_.get(), - "test", false, NULL)); + "test", true, NULL)); if (!set_initial_sync_ended) service_->dont_set_initial_sync_ended_on_init(); @@ -337,10 +346,11 @@ class ProfileSyncServiceTest : public testing::Test { profile_.get(), service_.get())); - profile_->GetTokenService()->IssueAuthTokenForTest( - GaiaConstants::kSyncService, "token"); + if (issue_auth_token) { + profile_->GetTokenService()->IssueAuthTokenForTest( + GaiaConstants::kSyncService, "token"); + } service_->Initialize(); - MessageLoop::current()->Run(); } } @@ -947,6 +957,170 @@ TEST_F(ProfileSyncServiceTest, MergeDuplicates) { ExpectModelMatch(); } +TEST_F(ProfileSyncServiceTest, JsFrontendHandlersBasic) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSyncService(); + + StrictMock<MockJsEventHandler> event_handler; + + browser_sync::SyncBackendHostForProfileSyncTest* test_backend = + service_->GetBackendForTest(); + + EXPECT_TRUE(service_->sync_initialized()); + ASSERT_TRUE(test_backend != NULL); + ASSERT_TRUE(test_backend->GetJsBackend() != NULL); + EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter()); + + browser_sync::JsFrontend* js_backend = service_->GetJsFrontend(); + js_backend->AddHandler(&event_handler); + ASSERT_TRUE(test_backend->GetJsBackend() != NULL); + EXPECT_TRUE(test_backend->GetJsBackend()->GetParentJsEventRouter() != NULL); + + js_backend->RemoveHandler(&event_handler); + EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter()); +} + +TEST_F(ProfileSyncServiceTest, + JsFrontendHandlersDelayedBackendInitialization) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSyncServiceAndSetInitialSyncEnded(true, false); + + StrictMock<MockJsEventHandler> event_handler; + EXPECT_CALL(event_handler, + HandleJsEvent("onSyncServiceStateChanged", + HasArgs(JsArgList()))).Times(3); + + EXPECT_EQ(NULL, service_->GetBackendForTest()); + EXPECT_FALSE(service_->sync_initialized()); + + browser_sync::JsFrontend* js_backend = service_->GetJsFrontend(); + js_backend->AddHandler(&event_handler); + // Since we're doing synchronous initialization, backend should be + // initialized by this call. + profile_->GetTokenService()->IssueAuthTokenForTest( + GaiaConstants::kSyncService, "token"); + + browser_sync::SyncBackendHostForProfileSyncTest* test_backend = + service_->GetBackendForTest(); + + EXPECT_TRUE(service_->sync_initialized()); + ASSERT_TRUE(test_backend != NULL); + ASSERT_TRUE(test_backend->GetJsBackend() != NULL); + EXPECT_TRUE(test_backend->GetJsBackend()->GetParentJsEventRouter() != NULL); + + js_backend->RemoveHandler(&event_handler); + EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter()); +} + +TEST_F(ProfileSyncServiceTest, JsFrontendProcessMessageBasic) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSyncService(); + + StrictMock<MockJsEventHandler> event_handler; + + ListValue arg_list1; + arg_list1.Append(Value::CreateBooleanValue(true)); + arg_list1.Append(Value::CreateIntegerValue(5)); + JsArgList args1(arg_list1); + EXPECT_CALL(event_handler, HandleJsEvent("testMessage1", HasArgs(args1))); + + ListValue arg_list2; + arg_list2.Append(Value::CreateStringValue("test")); + arg_list2.Append(arg_list1.DeepCopy()); + JsArgList args2(arg_list2); + EXPECT_CALL(event_handler, + HandleJsEvent("delayTestMessage2", HasArgs(args2))); + + ListValue arg_list3; + arg_list3.Append(arg_list1.DeepCopy()); + arg_list3.Append(arg_list2.DeepCopy()); + JsArgList args3(arg_list3); + + browser_sync::JsFrontend* js_backend = service_->GetJsFrontend(); + + // Never replied to. + js_backend->ProcessMessage("notRepliedTo", args3, &event_handler); + + // Replied to later. + js_backend->ProcessMessage("delayTestMessage2", args2, &event_handler); + + js_backend->AddHandler(&event_handler); + + // Replied to immediately. + js_backend->ProcessMessage("testMessage1", args1, &event_handler); + + // Fires off reply for delayTestMessage2. + message_loop_.RunAllPending(); + + // Never replied to. + js_backend->ProcessMessage("delayNotRepliedTo", args3, &event_handler); + + js_backend->RemoveHandler(&event_handler); + + message_loop_.RunAllPending(); + + // Never replied to. + js_backend->ProcessMessage("notRepliedTo", args3, &event_handler); +} + +TEST_F(ProfileSyncServiceTest, + JsFrontendProcessMessageBasicDelayedBackendInitialization) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSyncServiceAndSetInitialSyncEnded(true, false); + + StrictMock<MockJsEventHandler> event_handler; + + ListValue arg_list1; + arg_list1.Append(Value::CreateBooleanValue(true)); + arg_list1.Append(Value::CreateIntegerValue(5)); + JsArgList args1(arg_list1); + EXPECT_CALL(event_handler, HandleJsEvent("testMessage1", HasArgs(args1))); + + ListValue arg_list2; + arg_list2.Append(Value::CreateStringValue("test")); + arg_list2.Append(arg_list1.DeepCopy()); + JsArgList args2(arg_list2); + EXPECT_CALL(event_handler, HandleJsEvent("testMessage2", HasArgs(args2))); + + ListValue arg_list3; + arg_list3.Append(arg_list1.DeepCopy()); + arg_list3.Append(arg_list2.DeepCopy()); + JsArgList args3(arg_list3); + EXPECT_CALL(event_handler, + HandleJsEvent("delayTestMessage3", HasArgs(args3))); + + const JsArgList kNoArgs; + + EXPECT_CALL(event_handler, HandleJsEvent("onSyncServiceStateChanged", + HasArgs(kNoArgs))).Times(3); + + browser_sync::JsFrontend* js_backend = service_->GetJsFrontend(); + + // We expect a reply for this message, even though its sent before + // |event_handler| is added as a handler. + js_backend->ProcessMessage("testMessage1", args1, &event_handler); + + js_backend->AddHandler(&event_handler); + + js_backend->ProcessMessage("testMessage2", args2, &event_handler); + js_backend->ProcessMessage("delayTestMessage3", args3, &event_handler); + + // Fires testMessage1 and testMessage2. + profile_->GetTokenService()->IssueAuthTokenForTest( + GaiaConstants::kSyncService, "token"); + + // Fires delayTestMessage3. + message_loop_.RunAllPending(); + + js_backend->ProcessMessage("delayNotRepliedTo", kNoArgs, &event_handler); + + js_backend->RemoveHandler(&event_handler); + + message_loop_.RunAllPending(); + + js_backend->ProcessMessage("notRepliedTo", kNoArgs, &event_handler); +} + struct TestData { const wchar_t* title; const char* url; @@ -1401,7 +1575,7 @@ TEST_F(ProfileSyncServiceTestWithData, RecoverAfterDeletingSyncDataDirectory) { // Restart the sync service. Don't fake out setting initial sync ended; lets // make sure the system does in fact nudge and wait for this to happen. - StartSyncServiceAndSetInitialSyncEnded(false); + StartSyncServiceAndSetInitialSyncEnded(false, true); // Make sure we're back in sync. In real life, the user would need // to reauthenticate before this happens, but in the test, authentication diff --git a/chrome/browser/sync/test_profile_sync_service.cc b/chrome/browser/sync/test_profile_sync_service.cc index 0b9ec18..e536282 100644 --- a/chrome/browser/sync/test_profile_sync_service.cc +++ b/chrome/browser/sync/test_profile_sync_service.cc @@ -111,6 +111,40 @@ void SyncBackendHostForProfileSyncTest::InitCore( } } +JsBackend* SyncBackendHostForProfileSyncTest::GetJsBackend() { + // Return a non-NULL result only when the overridden function does. + if (SyncBackendHost::GetJsBackend()) { + return this; + } else { + NOTREACHED(); + return NULL; + } +} + +void SyncBackendHostForProfileSyncTest::SetParentJsEventRouter( + JsEventRouter* router) { + core_->SetParentJsEventRouter(router); +} + +void SyncBackendHostForProfileSyncTest::RemoveParentJsEventRouter() { + core_->RemoveParentJsEventRouter(); +} + +const JsEventRouter* + SyncBackendHostForProfileSyncTest::GetParentJsEventRouter() const { + return core_->GetParentJsEventRouter(); +} + +void SyncBackendHostForProfileSyncTest::ProcessMessage( + const std::string& name, const JsArgList& args, + const JsEventHandler* sender) { + if (name.find("delay") != name.npos) { + core_->RouteJsEvent(name, args, sender); + } else { + core_->RouteJsEventOnFrontendLoop(name, args, sender); + } +} + void SyncBackendHostForProfileSyncTest:: SetDefaultExpectationsForWorkerCreation(ProfileMock* profile) { EXPECT_CALL(*profile, GetPasswordStore(testing::_)). diff --git a/chrome/browser/sync/test_profile_sync_service.h b/chrome/browser/sync/test_profile_sync_service.h index 452b1ed..377b05c 100644 --- a/chrome/browser/sync/test_profile_sync_service.h +++ b/chrome/browser/sync/test_profile_sync_service.h @@ -9,6 +9,7 @@ #include <string> #include "chrome/browser/sync/glue/data_type_manager_impl.h" +#include "chrome/browser/sync/js_backend.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/test/profile_mock.h" #include "chrome/test/sync/engine/test_id_factory.h" @@ -28,7 +29,8 @@ namespace browser_sync { // running in these tests, and allows tests to provide a task on construction // to set up initial nodes to mock out an actual server initial sync // download. -class SyncBackendHostForProfileSyncTest : public SyncBackendHost { +class SyncBackendHostForProfileSyncTest + : public SyncBackendHost, public JsBackend { public: // |synchronous_init| causes initialization to block until the syncapi has // completed setting itself up and called us back. @@ -56,6 +58,18 @@ class SyncBackendHostForProfileSyncTest : public SyncBackendHost { virtual void InitCore(const Core::DoInitializeOptions& options); + virtual JsBackend* GetJsBackend(); + + // JsBackend implementation. + virtual void SetParentJsEventRouter(JsEventRouter* router); + virtual void RemoveParentJsEventRouter(); + virtual const JsEventRouter* GetParentJsEventRouter() const; + // Fires an event identical to the message unless the message has + // "delay" as a prefix, in which case a task to fire the identical + // event is posted instead. + virtual void ProcessMessage(const std::string& name, const JsArgList& args, + const JsEventHandler* sender); + static void SetDefaultExpectationsForWorkerCreation(ProfileMock* profile); static void SetHistoryServiceExpectations(ProfileMock* profile); diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 7b7eeed..0f1db96 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -956,6 +956,14 @@ 'browser/sync/engine/update_applicator.h', 'browser/sync/engine/verify_updates_command.cc', 'browser/sync/engine/verify_updates_command.h', + 'browser/sync/js_arg_list.cc', + 'browser/sync/js_arg_list.h', + 'browser/sync/js_backend.h', + 'browser/sync/js_event_handler.h', + 'browser/sync/js_event_handler_list.cc', + 'browser/sync/js_event_handler_list.h', + 'browser/sync/js_event_router.h', + 'browser/sync/js_frontend.h', 'browser/sync/protocol/service_constants.h', 'browser/sync/sessions/ordered_commit_set.cc', 'browser/sync/sessions/ordered_commit_set.h', diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index e0059c1..7c33c93 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -979,8 +979,6 @@ 'browser/dom_ui/slideshow_ui.h', 'browser/dom_ui/sync_internals_html_source.cc', 'browser/dom_ui/sync_internals_html_source.h', - 'browser/dom_ui/sync_internals_message_handler.cc', - 'browser/dom_ui/sync_internals_message_handler.h', 'browser/dom_ui/sync_internals_ui.cc', 'browser/dom_ui/sync_internals_ui.h', 'browser/dom_ui/textfields_ui.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 7c719ef..cdd303c 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -216,6 +216,22 @@ ], }, { + 'target_name': 'test_support_sync', + 'type': '<(library)', + 'dependencies': [ + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + 'sync', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'browser/sync/js_test_util.cc', + 'browser/sync/js_test_util.h', + ], + }, + { 'target_name': 'test_support_unit', 'type': '<(library)', 'dependencies': [ @@ -988,6 +1004,7 @@ 'renderer', 'service', 'test_support_common', + 'test_support_sync', 'test_support_unit', 'utility', '../app/app.gyp:app_base', @@ -1157,6 +1174,7 @@ 'browser/dom_ui/html_dialog_tab_contents_delegate_unittest.cc', 'browser/dom_ui/options/language_options_handler_unittest.cc', 'browser/dom_ui/shown_sections_handler_unittest.cc', + 'browser/dom_ui/sync_internals_ui_unittest.cc', 'browser/download/base_file_unittest.cc', 'browser/download/download_manager_unittest.cc', 'browser/download/download_request_infobar_delegate_unittest.cc', @@ -2724,6 +2742,8 @@ 'browser/sync/engine/syncproto_unittest.cc', 'browser/sync/engine/syncapi_mock.h', 'browser/sync/engine/verify_updates_command_unittest.cc', + 'browser/sync/js_arg_list_unittest.cc', + 'browser/sync/js_event_handler_list_unittest.cc', 'browser/sync/notifier/cache_invalidation_packet_handler_unittest.cc', 'browser/sync/notifier/chrome_invalidation_client_unittest.cc', 'browser/sync/notifier/chrome_system_resources_unittest.cc', @@ -2781,6 +2801,7 @@ 'profile_import', 'syncapi', 'sync_notifier', + 'test_support_sync', 'test_support_unit', ], 'conditions': [ diff --git a/chrome/test/profile_mock.h b/chrome/test/profile_mock.h index 73770c8..d44a83e 100644 --- a/chrome/test/profile_mock.h +++ b/chrome/test/profile_mock.h @@ -22,6 +22,7 @@ class ProfileMock : public TestingProfile { MOCK_METHOD0(GetPersonalDataManager, PersonalDataManager*()); MOCK_METHOD1(GetPasswordStore, PasswordStore* (ServiceAccessType access)); MOCK_METHOD0(GetTokenService, TokenService*()); + MOCK_METHOD0(GetProfileSyncService, ProfileSyncService*()); }; #endif // CHROME_TEST_PROFILE_MOCK_H__ |