From 96c9ea12f042a0c12f4de097c86e3be27a458bd2 Mon Sep 17 00:00:00 2001 From: "mark@chromium.org" Date: Tue, 23 Sep 2008 21:08:28 +0000 Subject: Mac-specific CFRunLoop-based MessagePump implementation Review URL: http://codereview.chromium.org/444 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@2521 0039d316-1c4b-4281-b951-d872f2087c98 --- base/message_pump_mac.mm | 363 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 base/message_pump_mac.mm (limited to 'base/message_pump_mac.mm') diff --git a/base/message_pump_mac.mm b/base/message_pump_mac.mm new file mode 100644 index 0000000..495f828 --- /dev/null +++ b/base/message_pump_mac.mm @@ -0,0 +1,363 @@ +// Copyright (c) 2008 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 "base/message_pump_mac.h" + +#import +#import +#include + +namespace { + +void NoOp(void* info) { +} + +} // namespace + +namespace base { + +// Must be called on the run loop thread. +MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() + : delegate_(NULL) { + run_loop_ = CFRunLoopGetCurrent(); + CFRetain(run_loop_); + + // Set a repeating timer with a preposterous firing time and interval. The + // timer will effectively never fire as-is. The firing time will be adjusted + // as needed when ScheduleDelayedWork is called. + CFRunLoopTimerContext timer_context = CFRunLoopTimerContext(); + timer_context.info = this; + delayed_work_timer_ = CFRunLoopTimerCreate(NULL, // allocator + DBL_MAX, // fire time + DBL_MAX, // interval + 0, // flags (ignored) + 0, // priority (ignored) + RunDelayedWorkTimer, + &timer_context); + CFRunLoopAddTimer(run_loop_, delayed_work_timer_, kCFRunLoopCommonModes); + + CFRunLoopSourceContext source_context = CFRunLoopSourceContext(); + source_context.info = this; + source_context.perform = RunWork; + work_source_ = CFRunLoopSourceCreate(NULL, // allocator + 0, // priority + &source_context); + CFRunLoopAddSource(run_loop_, work_source_, kCFRunLoopCommonModes); + + source_context.perform = RunDelayedWork; + delayed_work_source_ = CFRunLoopSourceCreate(NULL, // allocator + 1, // priority + &source_context); + CFRunLoopAddSource(run_loop_, delayed_work_source_, kCFRunLoopCommonModes); + + CFRunLoopObserverContext observer_context = CFRunLoopObserverContext(); + observer_context.info = this; + idle_work_observer_ = CFRunLoopObserverCreate(NULL, // allocator + kCFRunLoopBeforeWaiting, + true, // repeat + 0, // priority + RunIdleWork, + &observer_context); + CFRunLoopAddObserver(run_loop_, idle_work_observer_, kCFRunLoopCommonModes); +} + +// Ideally called on the run loop thread. +MessagePumpCFRunLoopBase::~MessagePumpCFRunLoopBase() { + CFRunLoopRemoveObserver(run_loop_, idle_work_observer_, + kCFRunLoopCommonModes); + CFRelease(idle_work_observer_); + + CFRunLoopRemoveSource(run_loop_, delayed_work_source_, kCFRunLoopCommonModes); + CFRelease(delayed_work_source_); + + CFRunLoopRemoveSource(run_loop_, work_source_, kCFRunLoopCommonModes); + CFRelease(work_source_); + + CFRunLoopRemoveTimer(run_loop_, delayed_work_timer_, kCFRunLoopCommonModes); + CFRelease(delayed_work_timer_); + + CFRelease(run_loop_); +} + +// Must be called on the run loop thread. +void MessagePumpCFRunLoopBase::Run(Delegate* delegate) { + Delegate* last_delegate = delegate_; + delegate_ = delegate; + + DoRun(delegate); + + delegate_ = last_delegate; +} + +// May be called on any thread. +void MessagePumpCFRunLoopBase::ScheduleWork() { + CFRunLoopSourceSignal(work_source_); + CFRunLoopWakeUp(run_loop_); +} + +// Must be called on the run loop thread. +void MessagePumpCFRunLoopBase::ScheduleDelayedWork( + const Time& delayed_work_time) { + Time::Exploded exploded; + delayed_work_time.UTCExplode(&exploded); + double seconds = exploded.second + + (static_cast((delayed_work_time.ToInternalValue()) % + Time::kMicrosecondsPerSecond) / + Time::kMicrosecondsPerSecond); + CFGregorianDate gregorian = { + exploded.year, + exploded.month, + exploded.day_of_month, + exploded.hour, + exploded.minute, + seconds + }; + CFAbsoluteTime fire_time = CFGregorianDateGetAbsoluteTime(gregorian, NULL); + + CFRunLoopTimerSetNextFireDate(delayed_work_timer_, fire_time); +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer, + void* info) { + MessagePumpCFRunLoop* self = static_cast(info); + + // CFRunLoopTimers fire outside of the priority scheme for CFRunLoopSources. + // In order to establish the proper priority where delegate_->DoDelayedWork + // can only be called if delegate_->DoWork returns false, the timer used + // to schedule delayed work must signal a CFRunLoopSource set at a lower + // priority than the one used for delegate_->DoWork. + CFRunLoopSourceSignal(self->delayed_work_source_); +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::RunWork(void* info) { + MessagePumpCFRunLoop* self = static_cast(info); + + // Call DoWork once, and if something was done, arrange to come back here + // again as long as the loop is still running. + if (self->delegate_->DoWork()) { + CFRunLoopSourceSignal(self->work_source_); + } +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::RunDelayedWork(void* info) { + MessagePumpCFRunLoop* self = static_cast(info); + + Time next_time; + self->delegate_->DoDelayedWork(&next_time); + if (!next_time.is_null()) { + TimeDelta delay = next_time - Time::Now(); + if (delay > TimeDelta()) { + // There's more delayed work to be done in the future. + self->ScheduleDelayedWork(next_time); + } else { + // There's more delayed work to be done, and its time is in the past. + // Arrange to come back here directly as long as the loop is still + // running. + CFRunLoopSourceSignal(self->delayed_work_source_); + } + } +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::RunIdleWork(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void* info) { + MessagePumpCFRunLoop* self = static_cast(info); + + if (self->delegate_->DoIdleWork()) { + // If idle work was done, don't let the loop go to sleep. More idle work + // might be waiting. + CFRunLoopWakeUp(self->run_loop_); + } +} + +// Must be called on the run loop thread. +MessagePumpCFRunLoop::MessagePumpCFRunLoop() + : nesting_level_(0), + innermost_quittable_(0), + quit_pending_(false) { + CFRunLoopObserverContext observer_context = CFRunLoopObserverContext(); + observer_context.info = this; + enter_exit_observer_ = CFRunLoopObserverCreate(NULL, // allocator + kCFRunLoopEntry | + kCFRunLoopExit, + true, // repeat + 0, // priority + EnterExitRunLoop, + &observer_context); + CFRunLoopAddObserver(run_loop_, enter_exit_observer_, kCFRunLoopCommonModes); +} + +// Ideally called on the run loop thread. If other CFRunLoopRun loops were +// running lower on the run loop thread's stack when this object was created, +// the same number of CFRunLoopRun loops must be running when this object is +// destroyed. +MessagePumpCFRunLoop::~MessagePumpCFRunLoop() { + CFRunLoopRemoveObserver(run_loop_, enter_exit_observer_, + kCFRunLoopCommonModes); + CFRelease(enter_exit_observer_); +} + +// Called by CFRunLoopBase::DoRun. If other CFRunLoopRun loops were running +// lower on the run loop thread's stack when this object was created, the same +// number of CFRunLoopRun loops must be running for the outermost call to Run. +// Run/DoRun are reentrant after that point. +void MessagePumpCFRunLoop::DoRun(Delegate* delegate) { + // nesting_level_ will be incremented in EnterExitRunLoop, so set + // innermost_quittable_ accordingly. + int last_innermost_quittable = innermost_quittable_; + innermost_quittable_ = nesting_level_ + 1; + + CFRunLoopRun(); + + // Restore the previous state of the object. + innermost_quittable_ = last_innermost_quittable; +} + +// Must be called on the run loop thread. +void MessagePumpCFRunLoop::Quit() { + // Stop the innermost run loop managed by this MessagePumpCFRunLoop object. + if (nesting_level_ == innermost_quittable_) { + // This object is running the innermost loop, just stop it. + CFRunLoopStop(run_loop_); + } else { + // There's another loop running inside the loop managed by this object. + // In other words, someone else called CFRunLoopRun on the same thread, + // higher on the stack than our highest Run call. Don't preempt other + // run loops, just mark the object to quit our innermost run loop as soon + // as the other inner loops we don't manage are done. + quit_pending_ = true; + } +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoop::EnterExitRunLoop(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void* info) { + MessagePumpCFRunLoop* self = static_cast(info); + + switch (activity) { + case kCFRunLoopEntry: + // If the run loop was entered by a call to Run, this will properly + // balance the decrement done in Run before entering the loop. + ++self->nesting_level_; + break; + case kCFRunLoopExit: + if (--self->nesting_level_ == self->innermost_quittable_ && + self->quit_pending_) { + // Quit was called while loops other than those managed by this object + // were running further inside a run loop managed by this object. Now + // that all unmanaged inner run loops are gone, stop the loop running + // just inside Run. + CFRunLoopStop(self->run_loop_); + self->quit_pending_ = false; + } + break; + default: + break; + } +} + +MessagePumpNSRunLoop::MessagePumpNSRunLoop() + : keep_running_(true) { + CFRunLoopSourceContext source_context = CFRunLoopSourceContext(); + source_context.perform = NoOp; + quit_source_ = CFRunLoopSourceCreate(NULL, // allocator + 0, // priority + &source_context); + CFRunLoopAddSource(run_loop_, quit_source_, kCFRunLoopCommonModes); +} + +MessagePumpNSRunLoop::~MessagePumpNSRunLoop() { + CFRunLoopRemoveSource(run_loop_, quit_source_, kCFRunLoopCommonModes); + CFRelease(quit_source_); +} + +void MessagePumpNSRunLoop::DoRun(Delegate* delegate) { + while (keep_running_) { + // NSRunLoop manages autorelease pools itself. + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:[NSDate distantFuture]]; + } + + keep_running_ = true; +} + +void MessagePumpNSRunLoop::Quit() { + keep_running_ = false; + CFRunLoopSourceSignal(quit_source_); + CFRunLoopWakeUp(run_loop_); +} + +MessagePumpNSApplication::MessagePumpNSApplication() + : keep_running_(true), + running_own_loop_(false) { +} + +void MessagePumpNSApplication::DoRun(Delegate* delegate) { + bool last_running_own_loop_ = running_own_loop_; + + [NSApplication sharedApplication]; + + if (![NSApp isRunning]) { + running_own_loop_ = false; + // NSApplication manages autorelease pools itself when run this way. + [NSApp run]; + } else { + running_own_loop_ = true; + while (keep_running_) { + NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init]; + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) { + [NSApp sendEvent:event]; + } + [autorelease_pool drain]; + } + keep_running_ = true; + } + + running_own_loop_ = last_running_own_loop_; +} + +void MessagePumpNSApplication::Quit() { + if (!running_own_loop_) { + [NSApp stop:nil]; + } else { + keep_running_ = false; + } + + // Send a fake event to wake the loop up. + [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined + location:NSMakePoint(0,0) + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:NULL + subtype:0 + data1:0 + data2:0] + atStart:NO]; +} + +// static +MessagePump* MessagePumpMac::Create() { + if ([NSThread isMainThread]) { + return new MessagePumpNSApplication; + } + + return new MessagePumpNSRunLoop; +} + +} // namespace base -- cgit v1.1