diff options
-rw-r--r-- | chrome/chrome_common.gypi | 5 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | chrome/common/multi_process_notification.cc | 27 | ||||
-rw-r--r-- | chrome/common/multi_process_notification.h | 87 | ||||
-rw-r--r-- | chrome/common/multi_process_notification_linux.cc | 58 | ||||
-rw-r--r-- | chrome/common/multi_process_notification_mac.mm | 156 | ||||
-rw-r--r-- | chrome/common/multi_process_notification_unittest.cc | 222 | ||||
-rw-r--r-- | chrome/common/multi_process_notification_win.cc | 58 |
8 files changed, 614 insertions, 0 deletions
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi index a7cd981..7eed4a6 100644 --- a/chrome/chrome_common.gypi +++ b/chrome/chrome_common.gypi @@ -112,6 +112,11 @@ 'common/multi_process_lock_linux.cc', 'common/multi_process_lock_mac.cc', 'common/multi_process_lock_win.cc', + 'common/multi_process_notification.h', + 'common/multi_process_notification.cc', + 'common/multi_process_notification_linux.cc', + 'common/multi_process_notification_mac.mm', + 'common/multi_process_notification_win.cc', 'common/nacl_cmd_line.cc', 'common/nacl_cmd_line.h', 'common/nacl_messages.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 415ab2f5..9e78410 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1646,6 +1646,7 @@ 'common/json_value_serializer_unittest.cc', 'common/mru_cache_unittest.cc', 'common/multi_process_lock_unittest.cc', + 'common/multi_process_notification_unittest.cc', 'common/net/gaia/gaia_auth_fetcher_unittest.cc', 'common/net/gaia/gaia_auth_fetcher_unittest.h', 'common/net/gaia/gaia_authenticator_unittest.cc', diff --git a/chrome/common/multi_process_notification.cc b/chrome/common/multi_process_notification.cc new file mode 100644 index 0000000..8eae607 --- /dev/null +++ b/chrome/common/multi_process_notification.cc @@ -0,0 +1,27 @@ +// 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/common/multi_process_notification.h" + +#include "base/task.h" + +namespace multi_process_notification { + +PerformTaskOnNotification::PerformTaskOnNotification(Task* task) : task_(task) { +} + +PerformTaskOnNotification::~PerformTaskOnNotification() { +} + +void PerformTaskOnNotification::OnNotificationReceived( + const std::string& name, Domain domain) { + task_->Run(); + task_.reset(); +} + +bool PerformTaskOnNotification::WasNotificationReceived() { + return task_.get() == NULL; +} + +} // namespace multi_process_notification diff --git a/chrome/common/multi_process_notification.h b/chrome/common/multi_process_notification.h new file mode 100644 index 0000000..91381a6 --- /dev/null +++ b/chrome/common/multi_process_notification.h @@ -0,0 +1,87 @@ +// 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_COMMON_MULTI_PROCESS_NOTIFICATION_H_ +#define CHROME_COMMON_MULTI_PROCESS_NOTIFICATION_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/scoped_ptr.h" + +class Task; +class MessageLoop; + +// Platform abstraction for a notification that can be sent between processes. +// Notifications are strings. The string will be prefixed accordingly per +// platform (so on Mac OS X a "Happy" notification will become +// "org.chromium.Happy"). +namespace multi_process_notification { + +class ListenerImpl; + +enum Domain { + // Notifications intended to be received by processes running with the + // same uid and same profile. + ProfileDomain, + // Notifications intended to be received by processes running with the + // same uid. + UserDomain, + // Notifications intended to be received by processes running on the + // same system. + SystemDomain +}; + +// Posts a notification |name| to |domain|. +// Returns true if the notification was posted. +bool Post(const std::string& name, Domain domain); + +// A notification listener. Will listen for a given notification and +// call the delegate. Note that the delegate is not owned by the listener. +class Listener { + public: + class Delegate { + public: + virtual ~Delegate() { } + virtual void OnNotificationReceived(const std::string& name, + Domain domain) = 0; + }; + + Listener(const std::string& name, Domain domain, Delegate* delegate); + + // A destructor is required for scoped_ptr to compile. + ~Listener(); + + bool Start(); + + private: + scoped_ptr<ListenerImpl> impl_; + + DISALLOW_COPY_AND_ASSIGN(Listener); +}; + +// A delegate implementation that performs a task when a notification is +// received. Note that it does not check the notification, and will fire +// for any notification it receives. It will only fire the task on the +// first notification received. +class PerformTaskOnNotification : public Listener::Delegate { + public: + explicit PerformTaskOnNotification(Task* task); + virtual ~PerformTaskOnNotification(); + + virtual void OnNotificationReceived(const std::string& name, + Domain domain) OVERRIDE; + bool WasNotificationReceived(); + + private: + scoped_ptr<Task> task_; + + DISALLOW_COPY_AND_ASSIGN(PerformTaskOnNotification); +}; + +} // namespace multi_process_notification + +#endif // CHROME_COMMON_MULTI_PROCESS_NOTIFICATION_H_ diff --git a/chrome/common/multi_process_notification_linux.cc b/chrome/common/multi_process_notification_linux.cc new file mode 100644 index 0000000..8f8370a --- /dev/null +++ b/chrome/common/multi_process_notification_linux.cc @@ -0,0 +1,58 @@ +// 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/common/multi_process_notification.h" + +#include "base/logging.h" + +namespace multi_process_notification { + +bool Post(const std::string& name, Domain domain) { + // TODO(dmaclach): Implement + NOTIMPLEMENTED(); + return false; +} + +class ListenerImpl { + public: + ListenerImpl(const std::string& name, + Domain domain, + Listener::Delegate* delegate); + + bool Start(); + + private: + std::string name_; + Domain domain_; + Listener::Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(ListenerImpl); +}; + +ListenerImpl::ListenerImpl(const std::string& name, + Domain domain, + Listener::Delegate* delegate) + : name_(name), domain_(domain), delegate_(delegate) { +} + +bool ListenerImpl::Start() { + // TODO(dmaclach): Implement + NOTIMPLEMENTED(); + return false; +} + +Listener::Listener(const std::string& name, + Domain domain, + Listener::Delegate* delegate) + : impl_(new ListenerImpl(name, domain, delegate)) { +} + +Listener::~Listener() { +} + +bool Listener::Start() { + return impl_->Start(); +} + +} // namespace multi_process_notification diff --git a/chrome/common/multi_process_notification_mac.mm b/chrome/common/multi_process_notification_mac.mm new file mode 100644 index 0000000..acbe89a --- /dev/null +++ b/chrome/common/multi_process_notification_mac.mm @@ -0,0 +1,156 @@ +// 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/common/multi_process_notification.h" + +#import <Foundation/Foundation.h> +#include <notify.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/eintr_wrapper.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/message_loop.h" +#include "base/message_pump_libevent.h" +#include "base/path_service.h" +#include "base/stringprintf.h" +#include "base/sys_string_conversions.h" +#include "base/task.h" +#include "chrome/common/chrome_paths.h" + +namespace { + +std::string AddPrefixToNotification(const std::string& name, + multi_process_notification::Domain domain) { + // The ordering of the components in the string returned by this function + // is important. Read "NAMESPACE CONVENTIONS" in 'man 3 notify' for details. + base::mac::ScopedNSAutoreleasePool pool; + NSBundle *bundle = base::mac::MainAppBundle(); + NSString *ns_bundle_id = [bundle bundleIdentifier]; + std::string bundle_id = base::SysNSStringToUTF8(ns_bundle_id); + std::string domain_string; + switch (domain) { + case multi_process_notification::ProfileDomain: { + FilePath user_data_dir; + if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) { + NOTREACHED(); + } + domain_string = StringPrintf("user.uid.%u.%s.", + getuid(), user_data_dir.value().c_str()); + break; + } + + case multi_process_notification::UserDomain: + domain_string = StringPrintf("user.uid.%u.", getuid()); + break; + + case multi_process_notification::SystemDomain: + break; + } + return domain_string + bundle_id + "." + name; +} + + +} // namespace + +namespace multi_process_notification { + +bool Post(const std::string& name, Domain domain) { + std::string notification = AddPrefixToNotification(name, domain); + uint32_t status = notify_post(notification.c_str()); + DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK)); + return status == NOTIFY_STATUS_OK; +} + + +class ListenerImpl : public base::MessagePumpLibevent::Watcher { + public: + ListenerImpl(const std::string& name, + Domain domain, + Listener::Delegate* delegate); + virtual ~ListenerImpl(); + + bool Start(); + + virtual void OnFileCanReadWithoutBlocking(int fd); + virtual void OnFileCanWriteWithoutBlocking(int fd); + + private: + std::string name_; + Domain domain_; + Listener::Delegate* delegate_; + int fd_; + int token_; + base::MessagePumpLibevent::FileDescriptorWatcher watcher_; + + DISALLOW_COPY_AND_ASSIGN(ListenerImpl); +}; + +ListenerImpl::ListenerImpl(const std::string& name, + Domain domain, + Listener::Delegate* delegate) + : name_(name), domain_(domain), delegate_(delegate), fd_(-1), token_(-1) { +} + +ListenerImpl::~ListenerImpl() { + if (token_ != -1) { + uint32_t status = notify_cancel(token_); + DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK)); + token_ = -1; + } +} + +bool ListenerImpl::Start() { + DCHECK_EQ(fd_, -1); + DCHECK_EQ(token_, -1); + std::string notification = AddPrefixToNotification(name_, domain_); + + uint32_t status = notify_register_file_descriptor(notification.c_str(), &fd_, + 0, &token_); + if (status != NOTIFY_STATUS_OK) { + LOG(ERROR) << "Unable to notify_register_file_descriptor for '" + << notification << "' Status: " << status; + return false; + } + + MessageLoopForIO *io_loop = MessageLoopForIO::current(); + return io_loop->WatchFileDescriptor(fd_, true, MessageLoopForIO::WATCH_READ, + &watcher_, this); +} + +void ListenerImpl::OnFileCanReadWithoutBlocking(int fd) { + DCHECK_EQ(fd, fd_); + int token = 0; + if (HANDLE_EINTR(read(fd_, &token, sizeof(token))) >= 0) { + // Have to swap to native endianness <http://openradar.appspot.com/8821081>. + token = static_cast<int>(ntohl(token)); + if (token == token_) { + delegate_->OnNotificationReceived(name_, domain_); + } else { + LOG(WARNING) << "Unexpected value " << token; + } + } +} + +void ListenerImpl::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED(); +} + +Listener::Listener(const std::string& name, + Domain domain, + Listener::Delegate* delegate) + : impl_(new ListenerImpl(name, domain, delegate)) { +} + +Listener::~Listener() { +} + +bool Listener::Start() { + return impl_->Start(); +} + +} // namespace multi_process_notification diff --git a/chrome/common/multi_process_notification_unittest.cc b/chrome/common/multi_process_notification_unittest.cc new file mode 100644 index 0000000..f25f441 --- /dev/null +++ b/chrome/common/multi_process_notification_unittest.cc @@ -0,0 +1,222 @@ +// 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/common/multi_process_notification.h" + +#include "base/basictypes.h" +#include "base/environment.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "base/time.h" +#include "testing/multiprocess_func_list.h" + +#if defined(OS_MACOSX) +// TODO(dmaclach): Remove defined(OS_MACOSX) once +// MultiProcessNotification is implemented on Win/Linux. + +namespace { + +const char kStartedNotificationName[] = "MultiProcessTestStartedNotification"; +const char kQuitNotificationName[] = "MultiProcessTestQuitNotification"; + +void SpinRunLoop(int milliseconds) { + MessageLoopForIO *loop = MessageLoopForIO::current(); + + // Post a quit task so that this loop eventually ends and we don't hang + // in the case of a bad test. Usually, the run loop will quit sooner than + // that because all tests use a MultiProcessNotificationTestQuit which quits + // the current run loop when it gets a notification. + loop->PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask(), milliseconds); + loop->Run(); +} + +int MultiProcessNotificationMain(multi_process_notification::Domain domain) { + MessageLoop io_loop(MessageLoop::TYPE_IO); + multi_process_notification::PerformTaskOnNotification quitter( + new MessageLoop::QuitTask()); + multi_process_notification::Listener listener( + kQuitNotificationName, domain, &quitter); + EXPECT_TRUE(listener.Start()); + EXPECT_TRUE(multi_process_notification::Post(kStartedNotificationName, + domain)); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + EXPECT_TRUE(quitter.WasNotificationReceived()); + return 0; +} + +} // namespace + +class MultiProcessNotificationTest : public base::MultiProcessTest { + public: + MultiProcessNotificationTest(); + + void PostNotificationTest(multi_process_notification::Domain domain); + void CrossPostNotificationTest(multi_process_notification::Domain domain); + + private: + MessageLoop io_loop_; +}; + +MultiProcessNotificationTest::MultiProcessNotificationTest() + : io_loop_(MessageLoop::TYPE_IO) { +} + +void MultiProcessNotificationTest::PostNotificationTest( + multi_process_notification::Domain domain) { + multi_process_notification::PerformTaskOnNotification process_started( + new MessageLoop::QuitTask()); + multi_process_notification::Listener listener(kStartedNotificationName, + domain, + &process_started); + ASSERT_TRUE(listener.Start()); + std::string process_name; + switch (domain) { + case multi_process_notification::ProfileDomain: + process_name = "MultiProcessProfileNotificationMain"; + break; + + case multi_process_notification::UserDomain: + process_name = "MultiProcessUserNotificationMain"; + break; + + case multi_process_notification::SystemDomain: + process_name = "MultiProcessSystemNotificationMain"; + break; + } + base::ProcessHandle handle = SpawnChild(process_name, false); + ASSERT_TRUE(handle); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(process_started.WasNotificationReceived()); + ASSERT_TRUE(multi_process_notification::Post(kQuitNotificationName, domain)); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCodeWithTimeout( + handle, &exit_code, TestTimeouts::action_max_timeout_ms())); +} + +void MultiProcessNotificationTest::CrossPostNotificationTest( + multi_process_notification::Domain domain) { + // Check to make sure notifications sent to user domain aren't picked up + // by system domain listeners and vice versa. + std::string local_notification("QuitLocalNotification"); + std::string final_notification("FinalQuitLocalNotification"); + multi_process_notification::PerformTaskOnNotification profile_quitter( + new MessageLoop::QuitTask()); + multi_process_notification::PerformTaskOnNotification user_quitter( + new MessageLoop::QuitTask()); + multi_process_notification::PerformTaskOnNotification system_quitter( + new MessageLoop::QuitTask()); + multi_process_notification::PerformTaskOnNotification final_quitter( + new MessageLoop::QuitTask()); + multi_process_notification::Listener profile_listener( + local_notification, multi_process_notification::ProfileDomain, + &profile_quitter); + multi_process_notification::Listener user_listener( + local_notification, multi_process_notification::UserDomain, + &user_quitter); + multi_process_notification::Listener system_listener( + local_notification, multi_process_notification::SystemDomain, + &system_quitter); + multi_process_notification::Listener final_listener( + final_notification, multi_process_notification::UserDomain, + &final_quitter); + + ASSERT_TRUE(profile_listener.Start()); + ASSERT_TRUE(user_listener.Start()); + ASSERT_TRUE(system_listener.Start()); + ASSERT_TRUE(multi_process_notification::Post(local_notification, domain)); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + + // Now send out a final_notification to queue up a notification + // after the local_notification and make sure that all listeners have had a + // chance to process local_notification before we check to see if they + // were called. + ASSERT_TRUE(final_listener.Start()); + ASSERT_TRUE(multi_process_notification::Post( + final_notification, multi_process_notification::UserDomain)); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + ASSERT_TRUE(final_quitter.WasNotificationReceived()); + switch (domain) { + case multi_process_notification::ProfileDomain: + ASSERT_TRUE(profile_quitter.WasNotificationReceived()); + ASSERT_FALSE(user_quitter.WasNotificationReceived()); + ASSERT_FALSE(system_quitter.WasNotificationReceived()); + break; + + case multi_process_notification::UserDomain: + ASSERT_FALSE(profile_quitter.WasNotificationReceived()); + ASSERT_TRUE(user_quitter.WasNotificationReceived()); + ASSERT_FALSE(system_quitter.WasNotificationReceived()); + break; + + case multi_process_notification::SystemDomain: + ASSERT_FALSE(profile_quitter.WasNotificationReceived()); + ASSERT_FALSE(user_quitter.WasNotificationReceived()); + ASSERT_TRUE(system_quitter.WasNotificationReceived()); + break; + } +} + +TEST_F(MultiProcessNotificationTest, BasicCreationTest) { + multi_process_notification::Listener local_listener( + "BasicCreationTest", multi_process_notification::UserDomain, NULL); + ASSERT_TRUE(local_listener.Start()); + multi_process_notification::Listener system_listener( + "BasicCreationTest", multi_process_notification::SystemDomain, NULL); + ASSERT_TRUE(system_listener.Start()); +} + +TEST_F(MultiProcessNotificationTest, PostInProcessNotification) { + std::string local_notification("QuitLocalNotification"); + multi_process_notification::PerformTaskOnNotification quitter( + new MessageLoop::QuitTask()); + multi_process_notification::Listener listener( + local_notification, multi_process_notification::UserDomain, &quitter); + + ASSERT_TRUE(listener.Start()); + ASSERT_TRUE(multi_process_notification::Post( + local_notification, multi_process_notification::UserDomain)); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_TRUE(quitter.WasNotificationReceived()); +} + +TEST_F(MultiProcessNotificationTest, PostProfileNotification) { + PostNotificationTest(multi_process_notification::ProfileDomain); +} + +TEST_F(MultiProcessNotificationTest, PostUserNotification) { + PostNotificationTest(multi_process_notification::UserDomain); +} + +TEST_F(MultiProcessNotificationTest, PostSystemNotification) { + PostNotificationTest(multi_process_notification::SystemDomain); +} + +TEST_F(MultiProcessNotificationTest, ProfileCrossDomainPosting) { + CrossPostNotificationTest(multi_process_notification::ProfileDomain); +} + +TEST_F(MultiProcessNotificationTest, UserCrossDomainPosting) { + CrossPostNotificationTest(multi_process_notification::UserDomain); +} + +TEST_F(MultiProcessNotificationTest, SystemCrossDomainPosting) { + CrossPostNotificationTest(multi_process_notification::SystemDomain); +} + +MULTIPROCESS_TEST_MAIN(MultiProcessProfileNotificationMain) { + return MultiProcessNotificationMain( + multi_process_notification::ProfileDomain); +} + +MULTIPROCESS_TEST_MAIN(MultiProcessUserNotificationMain) { + return MultiProcessNotificationMain(multi_process_notification::UserDomain); +} + +MULTIPROCESS_TEST_MAIN(MultiProcessSystemNotificationMain) { + return MultiProcessNotificationMain(multi_process_notification::SystemDomain); +} + +#endif // defined(OS_MACOSX) diff --git a/chrome/common/multi_process_notification_win.cc b/chrome/common/multi_process_notification_win.cc new file mode 100644 index 0000000..8f8370a --- /dev/null +++ b/chrome/common/multi_process_notification_win.cc @@ -0,0 +1,58 @@ +// 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/common/multi_process_notification.h" + +#include "base/logging.h" + +namespace multi_process_notification { + +bool Post(const std::string& name, Domain domain) { + // TODO(dmaclach): Implement + NOTIMPLEMENTED(); + return false; +} + +class ListenerImpl { + public: + ListenerImpl(const std::string& name, + Domain domain, + Listener::Delegate* delegate); + + bool Start(); + + private: + std::string name_; + Domain domain_; + Listener::Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(ListenerImpl); +}; + +ListenerImpl::ListenerImpl(const std::string& name, + Domain domain, + Listener::Delegate* delegate) + : name_(name), domain_(domain), delegate_(delegate) { +} + +bool ListenerImpl::Start() { + // TODO(dmaclach): Implement + NOTIMPLEMENTED(); + return false; +} + +Listener::Listener(const std::string& name, + Domain domain, + Listener::Delegate* delegate) + : impl_(new ListenerImpl(name, domain, delegate)) { +} + +Listener::~Listener() { +} + +bool Listener::Start() { + return impl_->Start(); +} + +} // namespace multi_process_notification |