summaryrefslogtreecommitdiffstats
path: root/chrome/common/deprecated
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2010-08-06 12:13:06 +0100
committerBen Murdoch <benm@google.com>2010-08-18 15:49:13 +0100
commit06741cbc25cd4227a9fba40dfd0273bfcc1a587a (patch)
treeca6f21dec86a8c4f6d3c50e78628c0cf31da0353 /chrome/common/deprecated
parentaa0bf16ed53445f227734aee4274c7aef056f032 (diff)
downloadexternal_chromium-06741cbc25cd4227a9fba40dfd0273bfcc1a587a.zip
external_chromium-06741cbc25cd4227a9fba40dfd0273bfcc1a587a.tar.gz
external_chromium-06741cbc25cd4227a9fba40dfd0273bfcc1a587a.tar.bz2
Add chrome/common @ 52593
Needed by autofill Change-Id: Ibfea9ab92382af0bd0cfc6e94d21e4baa4b9d896
Diffstat (limited to 'chrome/common/deprecated')
-rw-r--r--chrome/common/deprecated/event_sys-inl.h341
-rw-r--r--chrome/common/deprecated/event_sys.h45
-rw-r--r--chrome/common/deprecated/event_sys_unittest.cc271
3 files changed, 657 insertions, 0 deletions
diff --git a/chrome/common/deprecated/event_sys-inl.h b/chrome/common/deprecated/event_sys-inl.h
new file mode 100644
index 0000000..62fa350
--- /dev/null
+++ b/chrome/common/deprecated/event_sys-inl.h
@@ -0,0 +1,341 @@
+// Copyright (c) 2009 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_DEPRECATED_EVENT_SYS_INL_H_
+#define CHROME_COMMON_DEPRECATED_EVENT_SYS_INL_H_
+
+#include <map>
+
+#include "base/atomicops.h"
+#include "base/basictypes.h"
+#include "base/condition_variable.h"
+#include "base/lock.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/port.h"
+#include "chrome/common/deprecated/event_sys.h"
+
+// How to use Channels:
+
+// 0. Assume Bob is the name of the class from which you want to broadcast
+// events.
+// 1. Choose an EventType. This could be an Enum or something more complicated.
+// 2. Create an EventTraits class for your EventType. It must have
+// two members:
+//
+// typedef x EventType;
+// static bool IsChannelShutdownEvent(const EventType& event);
+//
+// 3. Add an EventChannel<MyEventTraits>* instance and event_channel() const;
+// accessor to Bob.
+// Delete the channel ordinarily in Bob's destructor, or whenever you want.
+// 4. To broadcast events, call bob->event_channel()->NotifyListeners(event).
+// 5. Only call NotifyListeners from a single thread at a time.
+
+// How to use Listeners/Hookups:
+
+// 0. Assume you want a class called Lisa to listen to events from Bob.
+// 1. Create an event handler method in Lisa. Its single argument should be of
+// your event type.
+// 2. Add a EventListenerHookup* hookup_ member to Lisa.
+// 3. Initialize the hookup by calling:
+//
+// hookup_ = NewEventListenerHookup(bob->event_channel(),
+// this,
+// &Lisa::HandleEvent);
+//
+// 4. Delete hookup_ in Lisa's destructor, or anytime sooner to stop receiving
+// events.
+
+// An Event Channel is a source, or broadcaster of events. Listeners subscribe
+// by calling the AddListener() method. The owner of the channel calls the
+// NotifyListeners() method.
+//
+// Don't inherit from this class. Just make an event_channel member and an
+// event_channel() accessor.
+
+// No reason why CallbackWaiters has to be templatized.
+class CallbackWaiters {
+ public:
+ CallbackWaiters() : waiter_count_(0),
+ callback_done_(false),
+ condvar_(&mutex_) {
+ }
+ ~CallbackWaiters() {
+ DCHECK_EQ(0, waiter_count_);
+ }
+ void WaitForCallbackToComplete(Lock* listeners_mutex) {
+ {
+ AutoLock lock(mutex_);
+ waiter_count_ += 1;
+ listeners_mutex->Release();
+ while (!callback_done_)
+ condvar_.Wait();
+ waiter_count_ -= 1;
+ if (0 != waiter_count_)
+ return;
+ }
+ delete this;
+ }
+
+ void Signal() {
+ AutoLock lock(mutex_);
+ callback_done_ = true;
+ condvar_.Broadcast();
+ }
+
+ protected:
+ int waiter_count_;
+ bool callback_done_;
+ Lock mutex_;
+ ConditionVariable condvar_;
+};
+
+template <typename EventTraitsType, typename NotifyLock,
+ typename ScopedNotifyLocker>
+class EventChannel {
+ public:
+ typedef EventTraitsType EventTraits;
+ typedef typename EventTraits::EventType EventType;
+ typedef EventListener<EventType> Listener;
+
+ protected:
+ typedef std::map<Listener*, bool> Listeners;
+
+ public:
+ // The shutdown event gets send in the EventChannel's destructor.
+ explicit EventChannel(const EventType& shutdown_event)
+ : current_listener_callback_(NULL),
+ callback_waiters_(NULL),
+ shutdown_event_(shutdown_event) {
+ }
+
+ ~EventChannel() {
+ // Tell all the listeners that the channel is being deleted.
+ NotifyListeners(shutdown_event_);
+
+ // Make sure all the listeners have been disconnected. Otherwise, they
+ // will try to call RemoveListener() at a later date.
+#if defined(DEBUG)
+ AutoLock lock(listeners_mutex_);
+ for (typename Listeners::iterator i = listeners_.begin();
+ i != listeners_.end(); ++i) {
+ DCHECK(i->second) << "Listener not disconnected";
+ }
+#endif
+ }
+
+ // Never call this twice for the same listener.
+ //
+ // Thread safe.
+ void AddListener(Listener* listener) {
+ AutoLock lock(listeners_mutex_);
+ typename Listeners::iterator found = listeners_.find(listener);
+ if (found == listeners_.end()) {
+ listeners_.insert(std::make_pair(listener,
+ false)); // Not dead yet.
+ } else {
+ DCHECK(found->second) << "Attempted to add the same listener twice.";
+ found->second = false; // Not dead yet.
+ }
+ }
+
+ // If listener's callback is currently executing, this method waits until the
+ // callback completes before returning.
+ //
+ // Thread safe.
+ void RemoveListener(Listener* listener) {
+ bool wait = false;
+ listeners_mutex_.Acquire();
+ typename Listeners::iterator found = listeners_.find(listener);
+ if (found != listeners_.end()) {
+ found->second = true; // Mark as dead.
+ wait = (found->first == current_listener_callback_ &&
+ (MessageLoop::current() != current_listener_callback_message_loop_));
+ }
+ if (!wait) {
+ listeners_mutex_.Release();
+ return;
+ }
+ if (NULL == callback_waiters_)
+ callback_waiters_ = new CallbackWaiters;
+ callback_waiters_->WaitForCallbackToComplete(&listeners_mutex_);
+ }
+
+ // Blocks until all listeners have been notified.
+ //
+ // NOT thread safe. Must only be called by one thread at a time.
+ void NotifyListeners(const EventType& event) {
+ ScopedNotifyLocker lock_notify(notify_lock_);
+ listeners_mutex_.Acquire();
+ DCHECK(NULL == current_listener_callback_);
+ current_listener_callback_message_loop_ = MessageLoop::current();
+ typename Listeners::iterator i = listeners_.begin();
+ while (i != listeners_.end()) {
+ if (i->second) { // Clean out dead listeners
+ listeners_.erase(i++);
+ continue;
+ }
+ current_listener_callback_ = i->first;
+ listeners_mutex_.Release();
+
+ i->first->HandleEvent(event);
+
+ listeners_mutex_.Acquire();
+ current_listener_callback_ = NULL;
+ if (NULL != callback_waiters_) {
+ callback_waiters_->Signal();
+ callback_waiters_ = NULL;
+ }
+
+ ++i;
+ }
+ listeners_mutex_.Release();
+ }
+
+ // A map iterator remains valid until the element it points to gets removed
+ // from the map, so a map is perfect for our needs.
+ //
+ // Map value is a bool, true means the Listener is dead.
+ Listeners listeners_;
+ // NULL means no callback is currently being called.
+ Listener* current_listener_callback_;
+ // Only valid when current_listener is not NULL.
+ // The thread on which the callback is executing.
+ MessageLoop* current_listener_callback_message_loop_;
+ // Win32 Event that is usually NULL. Only created when another thread calls
+ // Remove while in callback. Owned and closed by the thread calling Remove().
+ CallbackWaiters* callback_waiters_;
+
+ Lock listeners_mutex_; // Protects all members above.
+ const EventType shutdown_event_;
+ NotifyLock notify_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventChannel);
+};
+
+// An EventListenerHookup hooks up a method in your class to an EventChannel.
+// Deleting the hookup disconnects from the EventChannel.
+//
+// Contains complexity of inheriting from Listener class and managing lifetimes.
+//
+// Create using NewEventListenerHookup() to avoid explicit template arguments.
+class EventListenerHookup {
+ public:
+ virtual ~EventListenerHookup() { }
+};
+
+template <typename EventChannel, typename EventTraits,
+ class Derived>
+class EventListenerHookupImpl : public EventListenerHookup,
+public EventListener<typename EventTraits::EventType> {
+ public:
+ explicit EventListenerHookupImpl(EventChannel* channel)
+ : channel_(channel), deleted_(NULL) {
+ channel->AddListener(this);
+ connected_ = true;
+ }
+
+ ~EventListenerHookupImpl() {
+ if (NULL != deleted_)
+ *deleted_ = true;
+ if (connected_)
+ channel_->RemoveListener(this);
+ }
+
+ typedef typename EventTraits::EventType EventType;
+ virtual void HandleEvent(const EventType& event) {
+ DCHECK(connected_);
+ bool deleted = false;
+ deleted_ = &deleted;
+ static_cast<Derived*>(this)->Callback(event);
+ if (deleted) // The callback (legally) deleted this.
+ return; // The only safe thing to do.
+ deleted_ = NULL;
+ if (EventTraits::IsChannelShutdownEvent(event)) {
+ channel_->RemoveListener(this);
+ connected_ = false;
+ }
+ }
+
+ protected:
+ EventChannel* const channel_;
+ bool connected_;
+ bool* deleted_; // Allows the handler to delete the hookup.
+};
+
+// SimpleHookup just passes the event to the callback message.
+template <typename EventChannel, typename EventTraits,
+ typename CallbackObject, typename CallbackMethod>
+class SimpleHookup
+ : public EventListenerHookupImpl<EventChannel, EventTraits,
+ SimpleHookup<EventChannel,
+ EventTraits,
+ CallbackObject,
+ CallbackMethod> > {
+ public:
+ SimpleHookup(EventChannel* channel, CallbackObject* cbobject,
+ CallbackMethod cbmethod)
+ : EventListenerHookupImpl<EventChannel, EventTraits,
+ SimpleHookup>(channel), cbobject_(cbobject),
+ cbmethod_(cbmethod) { }
+
+ typedef typename EventTraits::EventType EventType;
+ void Callback(const EventType& event) {
+ (cbobject_->*cbmethod_)(event);
+ }
+ CallbackObject* const cbobject_;
+ CallbackMethod const cbmethod_;
+};
+
+// ArgHookup also passes an additional arg to the callback method.
+template <typename EventChannel, typename EventTraits,
+ typename CallbackObject, typename CallbackMethod,
+ typename CallbackArg0>
+class ArgHookup
+ : public EventListenerHookupImpl<EventChannel, EventTraits,
+ ArgHookup<EventChannel, EventTraits,
+ CallbackObject,
+ CallbackMethod,
+ CallbackArg0> > {
+ public:
+ ArgHookup(EventChannel* channel, CallbackObject* cbobject,
+ CallbackMethod cbmethod, CallbackArg0 arg0)
+ : EventListenerHookupImpl<EventChannel, EventTraits,
+ ArgHookup>(channel), cbobject_(cbobject),
+ cbmethod_(cbmethod), arg0_(arg0) { }
+
+ typedef typename EventTraits::EventType EventType;
+ void Callback(const EventType& event) {
+ (cbobject_->*cbmethod_)(arg0_, event);
+ }
+ CallbackObject* const cbobject_;
+ CallbackMethod const cbmethod_;
+ CallbackArg0 const arg0_;
+};
+
+
+template <typename EventChannel, typename CallbackObject,
+ typename CallbackMethod>
+EventListenerHookup* NewEventListenerHookup(EventChannel* channel,
+ CallbackObject* cbobject,
+ CallbackMethod cbmethod) {
+ return new SimpleHookup<EventChannel,
+ typename EventChannel::EventTraits,
+ CallbackObject, CallbackMethod>(channel, cbobject, cbmethod);
+}
+
+template <typename EventChannel, typename CallbackObject,
+ typename CallbackMethod, typename CallbackArg0>
+EventListenerHookup* NewEventListenerHookup(EventChannel* channel,
+ CallbackObject* cbobject,
+ CallbackMethod cbmethod,
+ CallbackArg0 arg0) {
+ return new ArgHookup<EventChannel,
+ typename EventChannel::EventTraits,
+ CallbackObject, CallbackMethod, CallbackArg0>(channel, cbobject,
+ cbmethod, arg0);
+}
+
+#endif // CHROME_COMMON_DEPRECATED_EVENT_SYS_INL_H_
diff --git a/chrome/common/deprecated/event_sys.h b/chrome/common/deprecated/event_sys.h
new file mode 100644
index 0000000..bec8144
--- /dev/null
+++ b/chrome/common/deprecated/event_sys.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2009 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_DEPRECATED_EVENT_SYS_H_
+#define CHROME_COMMON_DEPRECATED_EVENT_SYS_H_
+
+// TODO: This class should be removed or moved to Notifier code.
+// See Bug 42450 (http://code.google.com/p/chromium/issues/detail?id=42450).
+
+class AutoLock;
+class Lock;
+
+// An abstract base class for listening to events.
+//
+// Don't inherit from this class yourself. Using NewEventListenerHookup() is
+// much easier.
+template <typename EventType>
+class EventListener {
+ public:
+ virtual void HandleEvent(const EventType& event) = 0;
+};
+
+// See the -inl.h for details about the following.
+
+template <typename EventTraits, typename NotifyLock = Lock,
+ typename ScopedNotifyLocker = AutoLock>
+class EventChannel;
+
+class EventListenerHookup;
+
+template <typename EventChannel, typename CallbackObject,
+ typename CallbackMethod>
+EventListenerHookup* NewEventListenerHookup(EventChannel* channel,
+ CallbackObject* cbobject,
+ CallbackMethod cbmethod);
+
+template <typename EventChannel, typename CallbackObject,
+ typename CallbackMethod, typename CallbackArg0>
+EventListenerHookup* NewEventListenerHookup(EventChannel* channel,
+ CallbackObject* cbobject,
+ CallbackMethod cbmethod,
+ CallbackArg0 arg0);
+
+#endif // CHROME_COMMON_DEPRECATED_EVENT_SYS_H_
diff --git a/chrome/common/deprecated/event_sys_unittest.cc b/chrome/common/deprecated/event_sys_unittest.cc
new file mode 100644
index 0000000..c70d3f7
--- /dev/null
+++ b/chrome/common/deprecated/event_sys_unittest.cc
@@ -0,0 +1,271 @@
+// Copyright (c) 2009 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 <iosfwd>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/platform_thread.h"
+#include "base/port.h"
+#include "build/build_config.h"
+#include "chrome/common/deprecated/event_sys-inl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::endl;
+using std::ostream;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+namespace {
+
+class Pair;
+
+struct TestEvent {
+ Pair* source;
+ enum {
+ A_CHANGED, B_CHANGED, PAIR_BEING_DELETED,
+ } what_happened;
+ int old_value;
+};
+
+struct TestEventTraits {
+ typedef TestEvent EventType;
+ static bool IsChannelShutdownEvent(const TestEvent& event) {
+ return TestEvent::PAIR_BEING_DELETED == event.what_happened;
+ }
+};
+
+class Pair {
+ public:
+ typedef EventChannel<TestEventTraits> Channel;
+ explicit Pair(const string& name) : name_(name), a_(0), b_(0) {
+ TestEvent shutdown = { this, TestEvent::PAIR_BEING_DELETED, 0 };
+ event_channel_ = new Channel(shutdown);
+ }
+ ~Pair() {
+ delete event_channel_;
+ }
+ void set_a(int n) {
+ TestEvent event = { this, TestEvent::A_CHANGED, a_ };
+ a_ = n;
+ event_channel_->NotifyListeners(event);
+ }
+ void set_b(int n) {
+ TestEvent event = { this, TestEvent::B_CHANGED, b_ };
+ b_ = n;
+ event_channel_->NotifyListeners(event);
+ }
+ int a() const { return a_; }
+ int b() const { return b_; }
+ const string& name() { return name_; }
+ Channel* event_channel() const { return event_channel_; }
+
+ protected:
+ const string name_;
+ int a_;
+ int b_;
+ Channel* event_channel_;
+};
+
+class EventLogger {
+ public:
+ explicit EventLogger(ostream& out) : out_(out) { }
+ ~EventLogger() {
+ for (Hookups::iterator i = hookups_.begin(); i != hookups_.end(); ++i)
+ delete *i;
+ }
+
+ void Hookup(const string name, Pair::Channel* channel) {
+ hookups_.push_back(NewEventListenerHookup(channel, this,
+ &EventLogger::HandlePairEvent,
+ name));
+ }
+
+ void HandlePairEvent(const string& name, const TestEvent& event) {
+ const char* what_changed = NULL;
+ int new_value = 0;
+ Hookups::iterator dead;
+ switch (event.what_happened) {
+ case TestEvent::A_CHANGED:
+ what_changed = "A";
+ new_value = event.source->a();
+ break;
+ case TestEvent::B_CHANGED:
+ what_changed = "B";
+ new_value = event.source->b();
+ break;
+ case TestEvent::PAIR_BEING_DELETED:
+ out_ << name << " heard " << event.source->name() << " being deleted."
+ << endl;
+ return;
+ default:
+ FAIL() << "Bad event.what_happened: " << event.what_happened;
+ break;
+ }
+ out_ << name << " heard " << event.source->name() << "'s " << what_changed
+ << " change from "
+ << event.old_value << " to " << new_value << endl;
+ }
+
+ typedef vector<EventListenerHookup*> Hookups;
+ Hookups hookups_;
+ ostream& out_;
+};
+
+const char golden_result[] = "Larry heard Sally's B change from 0 to 2\n"
+"Larry heard Sally's A change from 1 to 3\n"
+"Lewis heard Sam's B change from 0 to 5\n"
+"Larry heard Sally's A change from 3 to 6\n"
+"Larry heard Sally being deleted.\n";
+
+TEST(EventSys, Basic) {
+ Pair sally("Sally"), sam("Sam");
+ sally.set_a(1);
+ stringstream log;
+ EventLogger logger(log);
+ logger.Hookup("Larry", sally.event_channel());
+ sally.set_b(2);
+ sally.set_a(3);
+ sam.set_a(4);
+ logger.Hookup("Lewis", sam.event_channel());
+ sam.set_b(5);
+ sally.set_a(6);
+ // Test that disconnect within callback doesn't deadlock.
+ TestEvent event = {&sally, TestEvent::PAIR_BEING_DELETED, 0 };
+ sally.event_channel()->NotifyListeners(event);
+ sally.set_a(7);
+ ASSERT_EQ(log.str(), golden_result);
+}
+
+
+// This goes pretty far beyond the normal use pattern, so don't use
+// ThreadTester as an example of what to do.
+class ThreadTester : public EventListener<TestEvent>,
+ public PlatformThread::Delegate {
+ public:
+ explicit ThreadTester(Pair* pair)
+ : pair_(pair), remove_event_(&remove_event_mutex_),
+ remove_event_bool_(false), completed_(false) {
+ pair_->event_channel()->AddListener(this);
+ }
+ ~ThreadTester() {
+ pair_->event_channel()->RemoveListener(this);
+ for (size_t i = 0; i < threads_.size(); i++) {
+ PlatformThread::Join(threads_[i].thread);
+ }
+ }
+
+ struct ThreadInfo {
+ PlatformThreadHandle thread;
+ };
+
+ struct ThreadArgs {
+ ConditionVariable* thread_running_cond;
+ Lock* thread_running_mutex;
+ bool thread_running;
+ };
+
+ void Go() {
+ Lock thread_running_mutex;
+ ConditionVariable thread_running_cond(&thread_running_mutex);
+ ThreadArgs args;
+ ThreadInfo info;
+ args.thread_running_cond = &(thread_running_cond);
+ args.thread_running_mutex = &(thread_running_mutex);
+ args.thread_running = false;
+ args_ = args;
+ ASSERT_TRUE(PlatformThread::Create(0, this, &info.thread));
+ thread_running_mutex.Acquire();
+ while ((args_.thread_running) == false) {
+ thread_running_cond.Wait();
+ }
+ thread_running_mutex.Release();
+ threads_.push_back(info);
+ }
+
+ // PlatformThread::Delegate methods.
+ virtual void ThreadMain() {
+ // Make sure each thread gets a current MessageLoop in TLS.
+ // This test should use chrome threads for testing, but I'll leave it like
+ // this for the moment since it requires a big chunk of rewriting and I
+ // want the test passing while I checkpoint my CL. Technically speaking,
+ // there should be no functional difference.
+ MessageLoop message_loop;
+ args_.thread_running_mutex->Acquire();
+ args_.thread_running = true;
+ args_.thread_running_cond->Signal();
+ args_.thread_running_mutex->Release();
+
+ remove_event_mutex_.Acquire();
+ while (remove_event_bool_ == false) {
+ remove_event_.Wait();
+ }
+ remove_event_mutex_.Release();
+
+ // Normally, you'd just delete the hookup. This is very bad style, but
+ // necessary for the test.
+ pair_->event_channel()->RemoveListener(this);
+
+ completed_mutex_.Acquire();
+ completed_ = true;
+ completed_mutex_.Release();
+ }
+
+ void HandleEvent(const TestEvent& event) {
+ remove_event_mutex_.Acquire();
+ remove_event_bool_ = true;
+ remove_event_.Broadcast();
+ remove_event_mutex_.Release();
+
+ PlatformThread::YieldCurrentThread();
+
+ completed_mutex_.Acquire();
+ if (completed_)
+ FAIL() << "A test thread exited too early.";
+ completed_mutex_.Release();
+ }
+
+ Pair* pair_;
+ ConditionVariable remove_event_;
+ Lock remove_event_mutex_;
+ bool remove_event_bool_;
+ Lock completed_mutex_;
+ bool completed_;
+ vector<ThreadInfo> threads_;
+ ThreadArgs args_;
+};
+
+TEST(EventSys, Multithreaded) {
+ Pair sally("Sally");
+ ThreadTester a(&sally);
+ for (int i = 0; i < 3; ++i)
+ a.Go();
+ sally.set_b(99);
+}
+
+class HookupDeleter {
+ public:
+ void HandleEvent(const TestEvent& event) {
+ delete hookup_;
+ hookup_ = NULL;
+ }
+ EventListenerHookup* hookup_;
+};
+
+TEST(EventSys, InHandlerDeletion) {
+ Pair sally("Sally");
+ HookupDeleter deleter;
+ deleter.hookup_ = NewEventListenerHookup(sally.event_channel(),
+ &deleter,
+ &HookupDeleter::HandleEvent);
+ sally.set_a(1);
+ ASSERT_TRUE(NULL == deleter.hookup_);
+}
+
+} // namespace