michael@0: // Copyright (c) 2008 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "base/message_pump_mac.h" michael@0: michael@0: #import michael@0: #import michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: michael@0: #import "base/chrome_application_mac.h" michael@0: #include "base/logging.h" michael@0: #include "base/time.h" michael@0: michael@0: namespace { michael@0: michael@0: void NoOp(void* info) { michael@0: } michael@0: michael@0: const CFTimeInterval kCFTimeIntervalMax = michael@0: std::numeric_limits::max(); michael@0: michael@0: } // namespace michael@0: michael@0: namespace base { michael@0: michael@0: // A scoper for autorelease pools created from message pump run loops. michael@0: // Avoids dirtying up the ScopedNSAutoreleasePool interface for the rare michael@0: // case where an autorelease pool needs to be passed in. michael@0: class MessagePumpScopedAutoreleasePool { michael@0: public: michael@0: explicit MessagePumpScopedAutoreleasePool(MessagePumpCFRunLoopBase* pump) : michael@0: pool_(pump->CreateAutoreleasePool()) { michael@0: } michael@0: ~MessagePumpScopedAutoreleasePool() { michael@0: [pool_ drain]; michael@0: } michael@0: michael@0: private: michael@0: NSAutoreleasePool* pool_; michael@0: DISALLOW_COPY_AND_ASSIGN(MessagePumpScopedAutoreleasePool); michael@0: }; michael@0: michael@0: // Must be called on the run loop thread. michael@0: MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() michael@0: : delegate_(NULL), michael@0: delayed_work_fire_time_(kCFTimeIntervalMax), michael@0: nesting_level_(0), michael@0: run_nesting_level_(0), michael@0: deepest_nesting_level_(0), michael@0: delegateless_work_(false), michael@0: delegateless_delayed_work_(false), michael@0: delegateless_idle_work_(false) { michael@0: run_loop_ = CFRunLoopGetCurrent(); michael@0: CFRetain(run_loop_); michael@0: michael@0: // Set a repeating timer with a preposterous firing time and interval. The michael@0: // timer will effectively never fire as-is. The firing time will be adjusted michael@0: // as needed when ScheduleDelayedWork is called. michael@0: CFRunLoopTimerContext timer_context = CFRunLoopTimerContext(); michael@0: timer_context.info = this; michael@0: delayed_work_timer_ = CFRunLoopTimerCreate(NULL, // allocator michael@0: kCFTimeIntervalMax, // fire time michael@0: kCFTimeIntervalMax, // interval michael@0: 0, // flags michael@0: 0, // priority michael@0: RunDelayedWorkTimer, michael@0: &timer_context); michael@0: CFRunLoopAddTimer(run_loop_, delayed_work_timer_, kCFRunLoopCommonModes); michael@0: michael@0: CFRunLoopSourceContext source_context = CFRunLoopSourceContext(); michael@0: source_context.info = this; michael@0: source_context.perform = RunWorkSource; michael@0: work_source_ = CFRunLoopSourceCreate(NULL, // allocator michael@0: 1, // priority michael@0: &source_context); michael@0: CFRunLoopAddSource(run_loop_, work_source_, kCFRunLoopCommonModes); michael@0: michael@0: source_context.perform = RunDelayedWorkSource; michael@0: delayed_work_source_ = CFRunLoopSourceCreate(NULL, // allocator michael@0: 2, // priority michael@0: &source_context); michael@0: CFRunLoopAddSource(run_loop_, delayed_work_source_, kCFRunLoopCommonModes); michael@0: michael@0: source_context.perform = RunIdleWorkSource; michael@0: idle_work_source_ = CFRunLoopSourceCreate(NULL, // allocator michael@0: 3, // priority michael@0: &source_context); michael@0: CFRunLoopAddSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes); michael@0: michael@0: source_context.perform = RunNestingDeferredWorkSource; michael@0: nesting_deferred_work_source_ = CFRunLoopSourceCreate(NULL, // allocator michael@0: 0, // priority michael@0: &source_context); michael@0: CFRunLoopAddSource(run_loop_, nesting_deferred_work_source_, michael@0: kCFRunLoopCommonModes); michael@0: michael@0: CFRunLoopObserverContext observer_context = CFRunLoopObserverContext(); michael@0: observer_context.info = this; michael@0: pre_wait_observer_ = CFRunLoopObserverCreate(NULL, // allocator michael@0: kCFRunLoopBeforeWaiting, michael@0: true, // repeat michael@0: 0, // priority michael@0: PreWaitObserver, michael@0: &observer_context); michael@0: CFRunLoopAddObserver(run_loop_, pre_wait_observer_, kCFRunLoopCommonModes); michael@0: michael@0: pre_source_observer_ = CFRunLoopObserverCreate(NULL, // allocator michael@0: kCFRunLoopBeforeSources, michael@0: true, // repeat michael@0: 0, // priority michael@0: PreSourceObserver, michael@0: &observer_context); michael@0: CFRunLoopAddObserver(run_loop_, pre_source_observer_, kCFRunLoopCommonModes); michael@0: michael@0: enter_exit_observer_ = CFRunLoopObserverCreate(NULL, // allocator michael@0: kCFRunLoopEntry | michael@0: kCFRunLoopExit, michael@0: true, // repeat michael@0: 0, // priority michael@0: EnterExitObserver, michael@0: &observer_context); michael@0: CFRunLoopAddObserver(run_loop_, enter_exit_observer_, kCFRunLoopCommonModes); michael@0: michael@0: root_power_domain_ = IORegisterForSystemPower(this, michael@0: &power_notification_port_, michael@0: PowerStateNotification, michael@0: &power_notification_object_); michael@0: if (root_power_domain_ != MACH_PORT_NULL) { michael@0: CFRunLoopAddSource( michael@0: run_loop_, michael@0: IONotificationPortGetRunLoopSource(power_notification_port_), michael@0: kCFRunLoopCommonModes); michael@0: } michael@0: } michael@0: michael@0: // Ideally called on the run loop thread. If other run loops were running michael@0: // lower on the run loop thread's stack when this object was created, the michael@0: // same number of run loops must be running when this object is destroyed. michael@0: MessagePumpCFRunLoopBase::~MessagePumpCFRunLoopBase() { michael@0: if (root_power_domain_ != MACH_PORT_NULL) { michael@0: CFRunLoopRemoveSource( michael@0: run_loop_, michael@0: IONotificationPortGetRunLoopSource(power_notification_port_), michael@0: kCFRunLoopCommonModes); michael@0: IODeregisterForSystemPower(&power_notification_object_); michael@0: IOServiceClose(root_power_domain_); michael@0: IONotificationPortDestroy(power_notification_port_); michael@0: } michael@0: michael@0: CFRunLoopRemoveObserver(run_loop_, enter_exit_observer_, michael@0: kCFRunLoopCommonModes); michael@0: CFRelease(enter_exit_observer_); michael@0: michael@0: CFRunLoopRemoveObserver(run_loop_, pre_source_observer_, michael@0: kCFRunLoopCommonModes); michael@0: CFRelease(pre_source_observer_); michael@0: michael@0: CFRunLoopRemoveObserver(run_loop_, pre_wait_observer_, michael@0: kCFRunLoopCommonModes); michael@0: CFRelease(pre_wait_observer_); michael@0: michael@0: CFRunLoopRemoveSource(run_loop_, nesting_deferred_work_source_, michael@0: kCFRunLoopCommonModes); michael@0: CFRelease(nesting_deferred_work_source_); michael@0: michael@0: CFRunLoopRemoveSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes); michael@0: CFRelease(idle_work_source_); michael@0: michael@0: CFRunLoopRemoveSource(run_loop_, delayed_work_source_, kCFRunLoopCommonModes); michael@0: CFRelease(delayed_work_source_); michael@0: michael@0: CFRunLoopRemoveSource(run_loop_, work_source_, kCFRunLoopCommonModes); michael@0: CFRelease(work_source_); michael@0: michael@0: CFRunLoopRemoveTimer(run_loop_, delayed_work_timer_, kCFRunLoopCommonModes); michael@0: CFRelease(delayed_work_timer_); michael@0: michael@0: CFRelease(run_loop_); michael@0: } michael@0: michael@0: // Must be called on the run loop thread. michael@0: void MessagePumpCFRunLoopBase::Run(Delegate* delegate) { michael@0: // nesting_level_ will be incremented in EnterExitRunLoop, so set michael@0: // run_nesting_level_ accordingly. michael@0: int last_run_nesting_level = run_nesting_level_; michael@0: run_nesting_level_ = nesting_level_ + 1; michael@0: michael@0: Delegate* last_delegate = delegate_; michael@0: delegate_ = delegate; michael@0: michael@0: if (delegate) { michael@0: // If any work showed up but could not be dispatched for want of a michael@0: // delegate, set it up for dispatch again now that a delegate is michael@0: // available. michael@0: if (delegateless_work_) { michael@0: CFRunLoopSourceSignal(work_source_); michael@0: delegateless_work_ = false; michael@0: } michael@0: if (delegateless_delayed_work_) { michael@0: CFRunLoopSourceSignal(delayed_work_source_); michael@0: delegateless_delayed_work_ = false; michael@0: } michael@0: if (delegateless_idle_work_) { michael@0: CFRunLoopSourceSignal(idle_work_source_); michael@0: delegateless_idle_work_ = false; michael@0: } michael@0: } michael@0: michael@0: DoRun(delegate); michael@0: michael@0: // Restore the previous state of the object. michael@0: delegate_ = last_delegate; michael@0: run_nesting_level_ = last_run_nesting_level; michael@0: } michael@0: michael@0: // May be called on any thread. michael@0: void MessagePumpCFRunLoopBase::ScheduleWork() { michael@0: CFRunLoopSourceSignal(work_source_); michael@0: CFRunLoopWakeUp(run_loop_); michael@0: } michael@0: michael@0: // Must be called on the run loop thread. michael@0: void MessagePumpCFRunLoopBase::ScheduleDelayedWork( michael@0: const TimeTicks& delayed_work_time) { michael@0: TimeDelta delta = delayed_work_time - TimeTicks::Now(); michael@0: delayed_work_fire_time_ = CFAbsoluteTimeGetCurrent() + delta.InSecondsF(); michael@0: CFRunLoopTimerSetNextFireDate(delayed_work_timer_, delayed_work_fire_time_); michael@0: } michael@0: michael@0: // Called from the run loop. michael@0: // static michael@0: void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer, michael@0: void* info) { michael@0: MessagePumpCFRunLoopBase* self = static_cast(info); michael@0: michael@0: // The timer won't fire again until it's reset. michael@0: self->delayed_work_fire_time_ = kCFTimeIntervalMax; michael@0: michael@0: // CFRunLoopTimers fire outside of the priority scheme for CFRunLoopSources. michael@0: // In order to establish the proper priority where delegate_->DoDelayedWork michael@0: // can only be called if delegate_->DoWork returns false, the timer used michael@0: // to schedule delayed work must signal a CFRunLoopSource set at a lower michael@0: // priority than the one used for delegate_->DoWork. michael@0: CFRunLoopSourceSignal(self->delayed_work_source_); michael@0: } michael@0: michael@0: // Called from the run loop. michael@0: // static michael@0: void MessagePumpCFRunLoopBase::RunWorkSource(void* info) { michael@0: MessagePumpCFRunLoopBase* self = static_cast(info); michael@0: self->RunWork(); michael@0: } michael@0: michael@0: // Called by MessagePumpCFRunLoopBase::RunWorkSource. michael@0: bool MessagePumpCFRunLoopBase::RunWork() { michael@0: if (!delegate_) { michael@0: // This point can be reached with a NULL delegate_ if Run is not on the michael@0: // stack but foreign code is spinning the CFRunLoop. Arrange to come back michael@0: // here when a delegate is available. michael@0: delegateless_work_ = true; michael@0: return false; michael@0: } michael@0: michael@0: // The NSApplication-based run loop only drains the autorelease pool at each michael@0: // UI event (NSEvent). The autorelease pool is not drained for each michael@0: // CFRunLoopSource target that's run. Use a local pool for any autoreleased michael@0: // objects if the app is not currently handling a UI event to ensure they're michael@0: // released promptly even in the absence of UI events. michael@0: MessagePumpScopedAutoreleasePool autorelease_pool(this); michael@0: michael@0: // Call DoWork once, and if something was done, arrange to come back here michael@0: // again as long as the loop is still running. michael@0: bool did_work = delegate_->DoWork(); michael@0: if (did_work) { michael@0: CFRunLoopSourceSignal(work_source_); michael@0: } michael@0: michael@0: return did_work; michael@0: } michael@0: michael@0: // Called from the run loop. michael@0: // static michael@0: void MessagePumpCFRunLoopBase::RunDelayedWorkSource(void* info) { michael@0: MessagePumpCFRunLoopBase* self = static_cast(info); michael@0: self->RunDelayedWork(); michael@0: } michael@0: michael@0: // Called by MessagePumpCFRunLoopBase::RunDelayedWorkSource. michael@0: bool MessagePumpCFRunLoopBase::RunDelayedWork() { michael@0: if (!delegate_) { michael@0: // This point can be reached with a NULL delegate_ if Run is not on the michael@0: // stack but foreign code is spinning the CFRunLoop. Arrange to come back michael@0: // here when a delegate is available. michael@0: delegateless_delayed_work_ = true; michael@0: return false; michael@0: } michael@0: michael@0: // The NSApplication-based run loop only drains the autorelease pool at each michael@0: // UI event (NSEvent). The autorelease pool is not drained for each michael@0: // CFRunLoopSource target that's run. Use a local pool for any autoreleased michael@0: // objects if the app is not currently handling a UI event to ensure they're michael@0: // released promptly even in the absence of UI events. michael@0: MessagePumpScopedAutoreleasePool autorelease_pool(this); michael@0: michael@0: TimeTicks next_time; michael@0: delegate_->DoDelayedWork(&next_time); michael@0: michael@0: bool more_work = !next_time.is_null(); michael@0: if (more_work) { michael@0: TimeDelta delay = next_time - TimeTicks::Now(); michael@0: if (delay > TimeDelta()) { michael@0: // There's more delayed work to be done in the future. michael@0: ScheduleDelayedWork(next_time); michael@0: } else { michael@0: // There's more delayed work to be done, and its time is in the past. michael@0: // Arrange to come back here directly as long as the loop is still michael@0: // running. michael@0: CFRunLoopSourceSignal(delayed_work_source_); michael@0: } michael@0: } michael@0: michael@0: return more_work; michael@0: } michael@0: michael@0: // Called from the run loop. michael@0: // static michael@0: void MessagePumpCFRunLoopBase::RunIdleWorkSource(void* info) { michael@0: MessagePumpCFRunLoopBase* self = static_cast(info); michael@0: self->RunIdleWork(); michael@0: } michael@0: michael@0: // Called by MessagePumpCFRunLoopBase::RunIdleWorkSource. michael@0: bool MessagePumpCFRunLoopBase::RunIdleWork() { michael@0: if (!delegate_) { michael@0: // This point can be reached with a NULL delegate_ if Run is not on the michael@0: // stack but foreign code is spinning the CFRunLoop. Arrange to come back michael@0: // here when a delegate is available. michael@0: delegateless_idle_work_ = true; michael@0: return false; michael@0: } michael@0: michael@0: // The NSApplication-based run loop only drains the autorelease pool at each michael@0: // UI event (NSEvent). The autorelease pool is not drained for each michael@0: // CFRunLoopSource target that's run. Use a local pool for any autoreleased michael@0: // objects if the app is not currently handling a UI event to ensure they're michael@0: // released promptly even in the absence of UI events. michael@0: MessagePumpScopedAutoreleasePool autorelease_pool(this); michael@0: michael@0: // Call DoIdleWork once, and if something was done, arrange to come back here michael@0: // again as long as the loop is still running. michael@0: bool did_work = delegate_->DoIdleWork(); michael@0: if (did_work) { michael@0: CFRunLoopSourceSignal(idle_work_source_); michael@0: } michael@0: michael@0: return did_work; michael@0: } michael@0: michael@0: // Called from the run loop. michael@0: // static michael@0: void MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource(void* info) { michael@0: MessagePumpCFRunLoopBase* self = static_cast(info); michael@0: self->RunNestingDeferredWork(); michael@0: } michael@0: michael@0: // Called by MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource. michael@0: bool MessagePumpCFRunLoopBase::RunNestingDeferredWork() { michael@0: if (!delegate_) { michael@0: // This point can be reached with a NULL delegate_ if Run is not on the michael@0: // stack but foreign code is spinning the CFRunLoop. There's no sense in michael@0: // attempting to do any work or signalling the work sources because michael@0: // without a delegate, work is not possible. michael@0: return false; michael@0: } michael@0: michael@0: // Immediately try work in priority order. michael@0: if (!RunWork()) { michael@0: if (!RunDelayedWork()) { michael@0: if (!RunIdleWork()) { michael@0: return false; michael@0: } michael@0: } else { michael@0: // There was no work, and delayed work was done. Arrange for the loop michael@0: // to try non-nestable idle work on a subsequent pass. michael@0: CFRunLoopSourceSignal(idle_work_source_); michael@0: } michael@0: } else { michael@0: // Work was done. Arrange for the loop to try non-nestable delayed and michael@0: // idle work on a subsequent pass. michael@0: CFRunLoopSourceSignal(delayed_work_source_); michael@0: CFRunLoopSourceSignal(idle_work_source_); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Called before the run loop goes to sleep or exits, or processes sources. michael@0: void MessagePumpCFRunLoopBase::MaybeScheduleNestingDeferredWork() { michael@0: // deepest_nesting_level_ is set as run loops are entered. If the deepest michael@0: // level encountered is deeper than the current level, a nested loop michael@0: // (relative to the current level) ran since the last time nesting-deferred michael@0: // work was scheduled. When that situation is encountered, schedule michael@0: // nesting-deferred work in case any work was deferred because nested work michael@0: // was disallowed. michael@0: if (deepest_nesting_level_ > nesting_level_) { michael@0: deepest_nesting_level_ = nesting_level_; michael@0: CFRunLoopSourceSignal(nesting_deferred_work_source_); michael@0: } michael@0: } michael@0: michael@0: // Called from the run loop. michael@0: // static michael@0: void MessagePumpCFRunLoopBase::PreWaitObserver(CFRunLoopObserverRef observer, michael@0: CFRunLoopActivity activity, michael@0: void* info) { michael@0: MessagePumpCFRunLoopBase* self = static_cast(info); michael@0: michael@0: // Attempt to do some idle work before going to sleep. michael@0: self->RunIdleWork(); michael@0: michael@0: // The run loop is about to go to sleep. If any of the work done since it michael@0: // started or woke up resulted in a nested run loop running, michael@0: // nesting-deferred work may have accumulated. Schedule it for processing michael@0: // if appropriate. michael@0: self->MaybeScheduleNestingDeferredWork(); michael@0: } michael@0: michael@0: // Called from the run loop. michael@0: // static michael@0: void MessagePumpCFRunLoopBase::PreSourceObserver(CFRunLoopObserverRef observer, michael@0: CFRunLoopActivity activity, michael@0: void* info) { michael@0: MessagePumpCFRunLoopBase* self = static_cast(info); michael@0: michael@0: // The run loop has reached the top of the loop and is about to begin michael@0: // processing sources. If the last iteration of the loop at this nesting michael@0: // level did not sleep or exit, nesting-deferred work may have accumulated michael@0: // if a nested loop ran. Schedule nesting-deferred work for processing if michael@0: // appropriate. michael@0: self->MaybeScheduleNestingDeferredWork(); michael@0: } michael@0: michael@0: // Called from the run loop. michael@0: // static michael@0: void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, michael@0: CFRunLoopActivity activity, michael@0: void* info) { michael@0: MessagePumpCFRunLoopBase* self = static_cast(info); michael@0: michael@0: switch (activity) { michael@0: case kCFRunLoopEntry: michael@0: ++self->nesting_level_; michael@0: if (self->nesting_level_ > self->deepest_nesting_level_) { michael@0: self->deepest_nesting_level_ = self->nesting_level_; michael@0: } michael@0: break; michael@0: michael@0: case kCFRunLoopExit: michael@0: // Not all run loops go to sleep. If a run loop is stopped before it michael@0: // goes to sleep due to a CFRunLoopStop call, or if the timeout passed michael@0: // to CFRunLoopRunInMode expires, the run loop may proceed directly from michael@0: // handling sources to exiting without any sleep. This most commonly michael@0: // occurs when CFRunLoopRunInMode is passed a timeout of 0, causing it michael@0: // to make a single pass through the loop and exit without sleep. Some michael@0: // native loops use CFRunLoop in this way. Because PreWaitObserver will michael@0: // not be called in these case, MaybeScheduleNestingDeferredWork needs michael@0: // to be called here, as the run loop exits. michael@0: // michael@0: // MaybeScheduleNestingDeferredWork consults self->nesting_level_ michael@0: // to determine whether to schedule nesting-deferred work. It expects michael@0: // the nesting level to be set to the depth of the loop that is going michael@0: // to sleep or exiting. It must be called before decrementing the michael@0: // value so that the value still corresponds to the level of the exiting michael@0: // loop. michael@0: self->MaybeScheduleNestingDeferredWork(); michael@0: --self->nesting_level_; michael@0: break; michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: self->EnterExitRunLoop(activity); michael@0: } michael@0: michael@0: // Called from the run loop. michael@0: // static michael@0: void MessagePumpCFRunLoopBase::PowerStateNotification(void* info, michael@0: io_service_t service, michael@0: uint32_t message_type, michael@0: void* message_argument) { michael@0: // CFRunLoopTimer (NSTimer) is scheduled in terms of CFAbsoluteTime, which michael@0: // measures the number of seconds since 2001-01-01 00:00:00.0 Z. It is michael@0: // implemented in terms of kernel ticks, as in mach_absolute_time. While an michael@0: // offset and scale factor can be applied to convert between the two time michael@0: // bases at any time after boot, the kernel clock stops while the system is michael@0: // asleep, altering the offset. (The offset will also change when the michael@0: // real-time clock is adjusted.) CFRunLoopTimers are not readjusted to take michael@0: // this into account when the system wakes up, so any timers that were michael@0: // pending while the system was asleep will be delayed by the sleep michael@0: // duration. michael@0: // michael@0: // The MessagePump interface assumes that scheduled delayed work will be michael@0: // performed at the time ScheduleDelayedWork was asked to perform it. The michael@0: // delay caused by the CFRunLoopTimer not firing at the appropriate time michael@0: // results in a stall of queued delayed work when the system wakes up. michael@0: // With this limitation, scheduled work would not be performed until michael@0: // (system wake time + scheduled work time - system sleep time), while it michael@0: // would be expected to be performed at (scheduled work time). michael@0: // michael@0: // To work around this problem, when the system wakes up from sleep, if a michael@0: // delayed work timer is pending, it is rescheduled to fire at the original michael@0: // time that it was scheduled to fire. michael@0: // michael@0: // This mechanism is not resilient if the real-time clock does not maintain michael@0: // stable time while the system is sleeping, but it matches the behavior of michael@0: // the various other MessagePump implementations, and MessageLoop seems to michael@0: // be limited in the same way. michael@0: // michael@0: // References michael@0: // - Chris Kane, "NSTimer and deep sleep," cocoa-dev@lists.apple.com, michael@0: // http://lists.apple.com/archives/Cocoa-dev/2002/May/msg01547.html michael@0: // - Apple Technical Q&A QA1340, "Registering and unregistering for sleep michael@0: // and wake notifications," michael@0: // http://developer.apple.com/mac/library/qa/qa2004/qa1340.html michael@0: // - Core Foundation source code, CF-550/CFRunLoop.c and CF-550/CFDate.c, michael@0: // http://www.opensource.apple.com/ michael@0: michael@0: MessagePumpCFRunLoopBase* self = static_cast(info); michael@0: michael@0: switch (message_type) { michael@0: case kIOMessageSystemWillPowerOn: michael@0: if (self->delayed_work_fire_time_ != kCFTimeIntervalMax) { michael@0: CFRunLoopTimerSetNextFireDate(self->delayed_work_timer_, michael@0: self->delayed_work_fire_time_); michael@0: } michael@0: break; michael@0: michael@0: case kIOMessageSystemWillSleep: michael@0: case kIOMessageCanSystemSleep: michael@0: // The system will wait for 30 seconds before entering sleep if neither michael@0: // IOAllowPowerChange nor IOCancelPowerChange are called. That would be michael@0: // pretty antisocial. michael@0: IOAllowPowerChange(self->root_power_domain_, michael@0: reinterpret_cast(message_argument)); michael@0: break; michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Called by MessagePumpCFRunLoopBase::EnterExitRunLoop. The default michael@0: // implementation is a no-op. michael@0: void MessagePumpCFRunLoopBase::EnterExitRunLoop(CFRunLoopActivity activity) { michael@0: } michael@0: michael@0: // Base version returns a standard NSAutoreleasePool. michael@0: NSAutoreleasePool* MessagePumpCFRunLoopBase::CreateAutoreleasePool() { michael@0: return [[NSAutoreleasePool alloc] init]; michael@0: } michael@0: michael@0: MessagePumpCFRunLoop::MessagePumpCFRunLoop() michael@0: : quit_pending_(false) { michael@0: } michael@0: michael@0: // Called by MessagePumpCFRunLoopBase::DoRun. If other CFRunLoopRun loops were michael@0: // running lower on the run loop thread's stack when this object was created, michael@0: // the same number of CFRunLoopRun loops must be running for the outermost call michael@0: // to Run. Run/DoRun are reentrant after that point. michael@0: void MessagePumpCFRunLoop::DoRun(Delegate* delegate) { michael@0: // This is completely identical to calling CFRunLoopRun(), except autorelease michael@0: // pool management is introduced. michael@0: int result; michael@0: do { michael@0: MessagePumpScopedAutoreleasePool autorelease_pool(this); michael@0: result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, michael@0: kCFTimeIntervalMax, michael@0: false); michael@0: } while (result != kCFRunLoopRunStopped && result != kCFRunLoopRunFinished); michael@0: } michael@0: michael@0: // Must be called on the run loop thread. michael@0: void MessagePumpCFRunLoop::Quit() { michael@0: // Stop the innermost run loop managed by this MessagePumpCFRunLoop object. michael@0: if (nesting_level() == run_nesting_level()) { michael@0: // This object is running the innermost loop, just stop it. michael@0: CFRunLoopStop(run_loop()); michael@0: } else { michael@0: // There's another loop running inside the loop managed by this object. michael@0: // In other words, someone else called CFRunLoopRunInMode on the same michael@0: // thread, deeper on the stack than the deepest Run call. Don't preempt michael@0: // other run loops, just mark this object to quit the innermost Run as michael@0: // soon as the other inner loops not managed by Run are done. michael@0: quit_pending_ = true; michael@0: } michael@0: } michael@0: michael@0: // Called by MessagePumpCFRunLoopBase::EnterExitObserver. michael@0: void MessagePumpCFRunLoop::EnterExitRunLoop(CFRunLoopActivity activity) { michael@0: if (activity == kCFRunLoopExit && michael@0: nesting_level() == run_nesting_level() && michael@0: quit_pending_) { michael@0: // Quit was called while loops other than those managed by this object michael@0: // were running further inside a run loop managed by this object. Now michael@0: // that all unmanaged inner run loops are gone, stop the loop running michael@0: // just inside Run. michael@0: CFRunLoopStop(run_loop()); michael@0: quit_pending_ = false; michael@0: } michael@0: } michael@0: michael@0: MessagePumpNSRunLoop::MessagePumpNSRunLoop() michael@0: : keep_running_(true) { michael@0: CFRunLoopSourceContext source_context = CFRunLoopSourceContext(); michael@0: source_context.perform = NoOp; michael@0: quit_source_ = CFRunLoopSourceCreate(NULL, // allocator michael@0: 0, // priority michael@0: &source_context); michael@0: CFRunLoopAddSource(run_loop(), quit_source_, kCFRunLoopCommonModes); michael@0: } michael@0: michael@0: MessagePumpNSRunLoop::~MessagePumpNSRunLoop() { michael@0: CFRunLoopRemoveSource(run_loop(), quit_source_, kCFRunLoopCommonModes); michael@0: CFRelease(quit_source_); michael@0: } michael@0: michael@0: void MessagePumpNSRunLoop::DoRun(Delegate* delegate) { michael@0: while (keep_running_) { michael@0: // NSRunLoop manages autorelease pools itself. michael@0: [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode michael@0: beforeDate:[NSDate distantFuture]]; michael@0: } michael@0: michael@0: keep_running_ = true; michael@0: } michael@0: michael@0: void MessagePumpNSRunLoop::Quit() { michael@0: keep_running_ = false; michael@0: CFRunLoopSourceSignal(quit_source_); michael@0: CFRunLoopWakeUp(run_loop()); michael@0: } michael@0: michael@0: MessagePumpNSApplication::MessagePumpNSApplication() michael@0: : keep_running_(true), michael@0: running_own_loop_(false) { michael@0: } michael@0: michael@0: void MessagePumpNSApplication::DoRun(Delegate* delegate) { michael@0: bool last_running_own_loop_ = running_own_loop_; michael@0: michael@0: // TODO(dmaclach): Get rid of this gratuitous sharedApplication. michael@0: // Tests should be setting up their applications on their own. michael@0: [CrApplication sharedApplication]; michael@0: michael@0: if (![NSApp isRunning]) { michael@0: running_own_loop_ = false; michael@0: // NSApplication manages autorelease pools itself when run this way. michael@0: [NSApp run]; michael@0: } else { michael@0: running_own_loop_ = true; michael@0: NSDate* distant_future = [NSDate distantFuture]; michael@0: while (keep_running_) { michael@0: MessagePumpScopedAutoreleasePool autorelease_pool(this); michael@0: NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask michael@0: untilDate:distant_future michael@0: inMode:NSDefaultRunLoopMode michael@0: dequeue:YES]; michael@0: if (event) { michael@0: [NSApp sendEvent:event]; michael@0: } michael@0: } michael@0: keep_running_ = true; michael@0: } michael@0: michael@0: running_own_loop_ = last_running_own_loop_; michael@0: } michael@0: michael@0: void MessagePumpNSApplication::Quit() { michael@0: if (!running_own_loop_) { michael@0: [NSApp stop:nil]; michael@0: } else { michael@0: keep_running_ = false; michael@0: } michael@0: michael@0: // Send a fake event to wake the loop up. michael@0: [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined michael@0: location:NSMakePoint(0, 0) michael@0: modifierFlags:0 michael@0: timestamp:0 michael@0: windowNumber:0 michael@0: context:NULL michael@0: subtype:0 michael@0: data1:0 michael@0: data2:0] michael@0: atStart:NO]; michael@0: } michael@0: michael@0: // Prevents an autorelease pool from being created if the app is in the midst of michael@0: // handling a UI event because various parts of AppKit depend on objects that michael@0: // are created while handling a UI event to be autoreleased in the event loop. michael@0: // An example of this is NSWindowController. When a window with a window michael@0: // controller is closed it goes through a stack like this: michael@0: // (Several stack frames elided for clarity) michael@0: // michael@0: // #0 [NSWindowController autorelease] michael@0: // #1 DoAClose michael@0: // #2 MessagePumpCFRunLoopBase::DoWork() michael@0: // #3 [NSRunLoop run] michael@0: // #4 [NSButton performClick:] michael@0: // #5 [NSWindow sendEvent:] michael@0: // #6 [NSApp sendEvent:] michael@0: // #7 [NSApp run] michael@0: // michael@0: // -performClick: spins a nested run loop. If the pool created in DoWork was a michael@0: // standard NSAutoreleasePool, it would release the objects that were michael@0: // autoreleased into it once DoWork released it. This would cause the window michael@0: // controller, which autoreleased itself in frame #0, to release itself, and michael@0: // possibly free itself. Unfortunately this window controller controls the michael@0: // window in frame #5. When the stack is unwound to frame #5, the window would michael@0: // no longer exists and crashes may occur. Apple gets around this by never michael@0: // releasing the pool it creates in frame #4, and letting frame #7 clean it up michael@0: // when it cleans up the pool that wraps frame #7. When an autorelease pool is michael@0: // released it releases all other pools that were created after it on the michael@0: // autorelease pool stack. michael@0: // michael@0: // CrApplication is responsible for setting handlingSendEvent to true just michael@0: // before it sends the event throught the event handling mechanism, and michael@0: // returning it to its previous value once the event has been sent. michael@0: NSAutoreleasePool* MessagePumpNSApplication::CreateAutoreleasePool() { michael@0: NSAutoreleasePool* pool = nil; michael@0: DCHECK([NSApp isKindOfClass:[CrApplication class]]); michael@0: if (![static_cast(NSApp) isHandlingSendEvent]) { michael@0: pool = MessagePumpCFRunLoopBase::CreateAutoreleasePool(); michael@0: } michael@0: return pool; michael@0: } michael@0: michael@0: // static michael@0: MessagePump* MessagePumpMac::Create() { michael@0: if ([NSThread isMainThread]) { michael@0: return new MessagePumpNSApplication; michael@0: } michael@0: michael@0: return new MessagePumpNSRunLoop; michael@0: } michael@0: michael@0: } // namespace base