1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
|
// 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 <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#include <IOKit/IOMessage.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <limits>
#import "base/chrome_application_mac.h"
#include "base/logging.h"
#include "base/time.h"
namespace {
void NoOp(void* info) {
}
const CFTimeInterval kCFTimeIntervalMax =
std::numeric_limits<CFTimeInterval>::max();
} // namespace
namespace base {
// A scoper for autorelease pools created from message pump run loops.
// Avoids dirtying up the ScopedNSAutoreleasePool interface for the rare
// case where an autorelease pool needs to be passed in.
class MessagePumpScopedAutoreleasePool {
public:
explicit MessagePumpScopedAutoreleasePool(MessagePumpCFRunLoopBase* pump) :
pool_(pump->CreateAutoreleasePool()) {
}
~MessagePumpScopedAutoreleasePool() {
[pool_ drain];
}
private:
NSAutoreleasePool* pool_;
DISALLOW_COPY_AND_ASSIGN(MessagePumpScopedAutoreleasePool);
};
// Must be called on the run loop thread.
MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase()
: delegate_(NULL),
delayed_work_fire_time_(kCFTimeIntervalMax),
nesting_level_(0),
run_nesting_level_(0),
deepest_nesting_level_(0),
delegateless_work_(false),
delegateless_delayed_work_(false),
delegateless_idle_work_(false) {
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
kCFTimeIntervalMax, // fire time
kCFTimeIntervalMax, // interval
0, // flags
0, // priority
RunDelayedWorkTimer,
&timer_context);
CFRunLoopAddTimer(run_loop_, delayed_work_timer_, kCFRunLoopCommonModes);
CFRunLoopSourceContext source_context = CFRunLoopSourceContext();
source_context.info = this;
source_context.perform = RunWorkSource;
work_source_ = CFRunLoopSourceCreate(NULL, // allocator
1, // priority
&source_context);
CFRunLoopAddSource(run_loop_, work_source_, kCFRunLoopCommonModes);
source_context.perform = RunDelayedWorkSource;
delayed_work_source_ = CFRunLoopSourceCreate(NULL, // allocator
2, // priority
&source_context);
CFRunLoopAddSource(run_loop_, delayed_work_source_, kCFRunLoopCommonModes);
source_context.perform = RunIdleWorkSource;
idle_work_source_ = CFRunLoopSourceCreate(NULL, // allocator
3, // priority
&source_context);
CFRunLoopAddSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes);
source_context.perform = RunNestingDeferredWorkSource;
nesting_deferred_work_source_ = CFRunLoopSourceCreate(NULL, // allocator
0, // priority
&source_context);
CFRunLoopAddSource(run_loop_, nesting_deferred_work_source_,
kCFRunLoopCommonModes);
CFRunLoopObserverContext observer_context = CFRunLoopObserverContext();
observer_context.info = this;
pre_wait_observer_ = CFRunLoopObserverCreate(NULL, // allocator
kCFRunLoopBeforeWaiting,
true, // repeat
0, // priority
PreWaitObserver,
&observer_context);
CFRunLoopAddObserver(run_loop_, pre_wait_observer_, kCFRunLoopCommonModes);
pre_source_observer_ = CFRunLoopObserverCreate(NULL, // allocator
kCFRunLoopBeforeSources,
true, // repeat
0, // priority
PreSourceObserver,
&observer_context);
CFRunLoopAddObserver(run_loop_, pre_source_observer_, kCFRunLoopCommonModes);
enter_exit_observer_ = CFRunLoopObserverCreate(NULL, // allocator
kCFRunLoopEntry |
kCFRunLoopExit,
true, // repeat
0, // priority
EnterExitObserver,
&observer_context);
CFRunLoopAddObserver(run_loop_, enter_exit_observer_, kCFRunLoopCommonModes);
root_power_domain_ = IORegisterForSystemPower(this,
&power_notification_port_,
PowerStateNotification,
&power_notification_object_);
if (root_power_domain_ != MACH_PORT_NULL) {
CFRunLoopAddSource(
run_loop_,
IONotificationPortGetRunLoopSource(power_notification_port_),
kCFRunLoopCommonModes);
}
}
// Ideally called on the run loop thread. If other run loops were running
// lower on the run loop thread's stack when this object was created, the
// same number of run loops must be running when this object is destroyed.
MessagePumpCFRunLoopBase::~MessagePumpCFRunLoopBase() {
if (root_power_domain_ != MACH_PORT_NULL) {
CFRunLoopRemoveSource(
run_loop_,
IONotificationPortGetRunLoopSource(power_notification_port_),
kCFRunLoopCommonModes);
IODeregisterForSystemPower(&power_notification_object_);
IOServiceClose(root_power_domain_);
IONotificationPortDestroy(power_notification_port_);
}
CFRunLoopRemoveObserver(run_loop_, enter_exit_observer_,
kCFRunLoopCommonModes);
CFRelease(enter_exit_observer_);
CFRunLoopRemoveObserver(run_loop_, pre_source_observer_,
kCFRunLoopCommonModes);
CFRelease(pre_source_observer_);
CFRunLoopRemoveObserver(run_loop_, pre_wait_observer_,
kCFRunLoopCommonModes);
CFRelease(pre_wait_observer_);
CFRunLoopRemoveSource(run_loop_, nesting_deferred_work_source_,
kCFRunLoopCommonModes);
CFRelease(nesting_deferred_work_source_);
CFRunLoopRemoveSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes);
CFRelease(idle_work_source_);
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) {
// nesting_level_ will be incremented in EnterExitRunLoop, so set
// run_nesting_level_ accordingly.
int last_run_nesting_level = run_nesting_level_;
run_nesting_level_ = nesting_level_ + 1;
Delegate* last_delegate = delegate_;
delegate_ = delegate;
if (delegate) {
// If any work showed up but could not be dispatched for want of a
// delegate, set it up for dispatch again now that a delegate is
// available.
if (delegateless_work_) {
CFRunLoopSourceSignal(work_source_);
delegateless_work_ = false;
}
if (delegateless_delayed_work_) {
CFRunLoopSourceSignal(delayed_work_source_);
delegateless_delayed_work_ = false;
}
if (delegateless_idle_work_) {
CFRunLoopSourceSignal(idle_work_source_);
delegateless_idle_work_ = false;
}
}
DoRun(delegate);
// Restore the previous state of the object.
delegate_ = last_delegate;
run_nesting_level_ = last_run_nesting_level;
}
// 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 TimeTicks& delayed_work_time) {
TimeDelta delta = delayed_work_time - TimeTicks::Now();
delayed_work_fire_time_ = CFAbsoluteTimeGetCurrent() + delta.InSecondsF();
CFRunLoopTimerSetNextFireDate(delayed_work_timer_, delayed_work_fire_time_);
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer,
void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
// The timer won't fire again until it's reset.
self->delayed_work_fire_time_ = kCFTimeIntervalMax;
// 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::RunWorkSource(void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
self->RunWork();
}
// Called by MessagePumpCFRunLoopBase::RunWorkSource.
bool MessagePumpCFRunLoopBase::RunWork() {
if (!delegate_) {
// This point can be reached with a NULL delegate_ if Run is not on the
// stack but foreign code is spinning the CFRunLoop. Arrange to come back
// here when a delegate is available.
delegateless_work_ = true;
return false;
}
// The NSApplication-based run loop only drains the autorelease pool at each
// UI event (NSEvent). The autorelease pool is not drained for each
// CFRunLoopSource target that's run. Use a local pool for any autoreleased
// objects if the app is not currently handling a UI event to ensure they're
// released promptly even in the absence of UI events.
MessagePumpScopedAutoreleasePool autorelease_pool(this);
// Call DoWork once, and if something was done, arrange to come back here
// again as long as the loop is still running.
bool did_work = delegate_->DoWork();
if (did_work) {
CFRunLoopSourceSignal(work_source_);
}
return did_work;
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::RunDelayedWorkSource(void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
self->RunDelayedWork();
}
// Called by MessagePumpCFRunLoopBase::RunDelayedWorkSource.
bool MessagePumpCFRunLoopBase::RunDelayedWork() {
if (!delegate_) {
// This point can be reached with a NULL delegate_ if Run is not on the
// stack but foreign code is spinning the CFRunLoop. Arrange to come back
// here when a delegate is available.
delegateless_delayed_work_ = true;
return false;
}
// The NSApplication-based run loop only drains the autorelease pool at each
// UI event (NSEvent). The autorelease pool is not drained for each
// CFRunLoopSource target that's run. Use a local pool for any autoreleased
// objects if the app is not currently handling a UI event to ensure they're
// released promptly even in the absence of UI events.
MessagePumpScopedAutoreleasePool autorelease_pool(this);
TimeTicks next_time;
delegate_->DoDelayedWork(&next_time);
bool more_work = !next_time.is_null();
if (more_work) {
TimeDelta delay = next_time - TimeTicks::Now();
if (delay > TimeDelta()) {
// There's more delayed work to be done in the future.
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(delayed_work_source_);
}
}
return more_work;
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::RunIdleWorkSource(void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
self->RunIdleWork();
}
// Called by MessagePumpCFRunLoopBase::RunIdleWorkSource.
bool MessagePumpCFRunLoopBase::RunIdleWork() {
if (!delegate_) {
// This point can be reached with a NULL delegate_ if Run is not on the
// stack but foreign code is spinning the CFRunLoop. Arrange to come back
// here when a delegate is available.
delegateless_idle_work_ = true;
return false;
}
// The NSApplication-based run loop only drains the autorelease pool at each
// UI event (NSEvent). The autorelease pool is not drained for each
// CFRunLoopSource target that's run. Use a local pool for any autoreleased
// objects if the app is not currently handling a UI event to ensure they're
// released promptly even in the absence of UI events.
MessagePumpScopedAutoreleasePool autorelease_pool(this);
// Call DoIdleWork once, and if something was done, arrange to come back here
// again as long as the loop is still running.
bool did_work = delegate_->DoIdleWork();
if (did_work) {
CFRunLoopSourceSignal(idle_work_source_);
}
return did_work;
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource(void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
self->RunNestingDeferredWork();
}
// Called by MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource.
bool MessagePumpCFRunLoopBase::RunNestingDeferredWork() {
if (!delegate_) {
// This point can be reached with a NULL delegate_ if Run is not on the
// stack but foreign code is spinning the CFRunLoop. There's no sense in
// attempting to do any work or signalling the work sources because
// without a delegate, work is not possible.
return false;
}
// Immediately try work in priority order.
if (!RunWork()) {
if (!RunDelayedWork()) {
if (!RunIdleWork()) {
return false;
}
} else {
// There was no work, and delayed work was done. Arrange for the loop
// to try non-nestable idle work on a subsequent pass.
CFRunLoopSourceSignal(idle_work_source_);
}
} else {
// Work was done. Arrange for the loop to try non-nestable delayed and
// idle work on a subsequent pass.
CFRunLoopSourceSignal(delayed_work_source_);
CFRunLoopSourceSignal(idle_work_source_);
}
return true;
}
// Called before the run loop goes to sleep or exits, or processes sources.
void MessagePumpCFRunLoopBase::MaybeScheduleNestingDeferredWork() {
// deepest_nesting_level_ is set as run loops are entered. If the deepest
// level encountered is deeper than the current level, a nested loop
// (relative to the current level) ran since the last time nesting-deferred
// work was scheduled. When that situation is encountered, schedule
// nesting-deferred work in case any work was deferred because nested work
// was disallowed.
if (deepest_nesting_level_ > nesting_level_) {
deepest_nesting_level_ = nesting_level_;
CFRunLoopSourceSignal(nesting_deferred_work_source_);
}
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::PreWaitObserver(CFRunLoopObserverRef observer,
CFRunLoopActivity activity,
void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
// Attempt to do some idle work before going to sleep.
self->RunIdleWork();
// The run loop is about to go to sleep. If any of the work done since it
// started or woke up resulted in a nested run loop running,
// nesting-deferred work may have accumulated. Schedule it for processing
// if appropriate.
self->MaybeScheduleNestingDeferredWork();
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::PreSourceObserver(CFRunLoopObserverRef observer,
CFRunLoopActivity activity,
void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
// The run loop has reached the top of the loop and is about to begin
// processing sources. If the last iteration of the loop at this nesting
// level did not sleep or exit, nesting-deferred work may have accumulated
// if a nested loop ran. Schedule nesting-deferred work for processing if
// appropriate.
self->MaybeScheduleNestingDeferredWork();
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer,
CFRunLoopActivity activity,
void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
switch (activity) {
case kCFRunLoopEntry:
++self->nesting_level_;
if (self->nesting_level_ > self->deepest_nesting_level_) {
self->deepest_nesting_level_ = self->nesting_level_;
}
break;
case kCFRunLoopExit:
// Not all run loops go to sleep. If a run loop is stopped before it
// goes to sleep due to a CFRunLoopStop call, or if the timeout passed
// to CFRunLoopRunInMode expires, the run loop may proceed directly from
// handling sources to exiting without any sleep. This most commonly
// occurs when CFRunLoopRunInMode is passed a timeout of 0, causing it
// to make a single pass through the loop and exit without sleep. Some
// native loops use CFRunLoop in this way. Because PreWaitObserver will
// not be called in these case, MaybeScheduleNestingDeferredWork needs
// to be called here, as the run loop exits.
//
// MaybeScheduleNestingDeferredWork consults self->nesting_level_
// to determine whether to schedule nesting-deferred work. It expects
// the nesting level to be set to the depth of the loop that is going
// to sleep or exiting. It must be called before decrementing the
// value so that the value still corresponds to the level of the exiting
// loop.
self->MaybeScheduleNestingDeferredWork();
--self->nesting_level_;
break;
default:
break;
}
self->EnterExitRunLoop(activity);
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::PowerStateNotification(void* info,
io_service_t service,
uint32_t message_type,
void* message_argument) {
// CFRunLoopTimer (NSTimer) is scheduled in terms of CFAbsoluteTime, which
// measures the number of seconds since 2001-01-01 00:00:00.0 Z. It is
// implemented in terms of kernel ticks, as in mach_absolute_time. While an
// offset and scale factor can be applied to convert between the two time
// bases at any time after boot, the kernel clock stops while the system is
// asleep, altering the offset. (The offset will also change when the
// real-time clock is adjusted.) CFRunLoopTimers are not readjusted to take
// this into account when the system wakes up, so any timers that were
// pending while the system was asleep will be delayed by the sleep
// duration.
//
// The MessagePump interface assumes that scheduled delayed work will be
// performed at the time ScheduleDelayedWork was asked to perform it. The
// delay caused by the CFRunLoopTimer not firing at the appropriate time
// results in a stall of queued delayed work when the system wakes up.
// With this limitation, scheduled work would not be performed until
// (system wake time + scheduled work time - system sleep time), while it
// would be expected to be performed at (scheduled work time).
//
// To work around this problem, when the system wakes up from sleep, if a
// delayed work timer is pending, it is rescheduled to fire at the original
// time that it was scheduled to fire.
//
// This mechanism is not resilient if the real-time clock does not maintain
// stable time while the system is sleeping, but it matches the behavior of
// the various other MessagePump implementations, and MessageLoop seems to
// be limited in the same way.
//
// References
// - Chris Kane, "NSTimer and deep sleep," cocoa-dev@lists.apple.com,
// http://lists.apple.com/archives/Cocoa-dev/2002/May/msg01547.html
// - Apple Technical Q&A QA1340, "Registering and unregistering for sleep
// and wake notifications,"
// http://developer.apple.com/mac/library/qa/qa2004/qa1340.html
// - Core Foundation source code, CF-550/CFRunLoop.c and CF-550/CFDate.c,
// http://www.opensource.apple.com/
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
switch (message_type) {
case kIOMessageSystemWillPowerOn:
if (self->delayed_work_fire_time_ != kCFTimeIntervalMax) {
CFRunLoopTimerSetNextFireDate(self->delayed_work_timer_,
self->delayed_work_fire_time_);
}
break;
case kIOMessageSystemWillSleep:
case kIOMessageCanSystemSleep:
// The system will wait for 30 seconds before entering sleep if neither
// IOAllowPowerChange nor IOCancelPowerChange are called. That would be
// pretty antisocial.
IOAllowPowerChange(self->root_power_domain_,
reinterpret_cast<long>(message_argument));
break;
default:
break;
}
}
// Called by MessagePumpCFRunLoopBase::EnterExitRunLoop. The default
// implementation is a no-op.
void MessagePumpCFRunLoopBase::EnterExitRunLoop(CFRunLoopActivity activity) {
}
// Base version returns a standard NSAutoreleasePool.
NSAutoreleasePool* MessagePumpCFRunLoopBase::CreateAutoreleasePool() {
return [[NSAutoreleasePool alloc] init];
}
MessagePumpCFRunLoop::MessagePumpCFRunLoop()
: quit_pending_(false) {
}
// Called by MessagePumpCFRunLoopBase::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) {
// This is completely identical to calling CFRunLoopRun(), except autorelease
// pool management is introduced.
int result;
do {
MessagePumpScopedAutoreleasePool autorelease_pool(this);
result = CFRunLoopRunInMode(kCFRunLoopDefaultMode,
kCFTimeIntervalMax,
false);
} while (result != kCFRunLoopRunStopped && result != kCFRunLoopRunFinished);
}
// Must be called on the run loop thread.
void MessagePumpCFRunLoop::Quit() {
// Stop the innermost run loop managed by this MessagePumpCFRunLoop object.
if (nesting_level() == run_nesting_level()) {
// 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 CFRunLoopRunInMode on the same
// thread, deeper on the stack than the deepest Run call. Don't preempt
// other run loops, just mark this object to quit the innermost Run as
// soon as the other inner loops not managed by Run are done.
quit_pending_ = true;
}
}
// Called by MessagePumpCFRunLoopBase::EnterExitObserver.
void MessagePumpCFRunLoop::EnterExitRunLoop(CFRunLoopActivity activity) {
if (activity == kCFRunLoopExit &&
nesting_level() == run_nesting_level() &&
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(run_loop());
quit_pending_ = false;
}
}
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_;
// TODO(dmaclach): Get rid of this gratuitous sharedApplication.
// Tests should be setting up their applications on their own.
[CrApplication sharedApplication];
if (![NSApp isRunning]) {
running_own_loop_ = false;
// NSApplication manages autorelease pools itself when run this way.
[NSApp run];
} else {
running_own_loop_ = true;
NSDate* distant_future = [NSDate distantFuture];
while (keep_running_) {
MessagePumpScopedAutoreleasePool autorelease_pool(this);
NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
untilDate:distant_future
inMode:NSDefaultRunLoopMode
dequeue:YES];
if (event) {
[NSApp sendEvent:event];
}
}
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];
}
// Prevents an autorelease pool from being created if the app is in the midst of
// handling a UI event because various parts of AppKit depend on objects that
// are created while handling a UI event to be autoreleased in the event loop.
// An example of this is NSWindowController. When a window with a window
// controller is closed it goes through a stack like this:
// (Several stack frames elided for clarity)
//
// #0 [NSWindowController autorelease]
// #1 DoAClose
// #2 MessagePumpCFRunLoopBase::DoWork()
// #3 [NSRunLoop run]
// #4 [NSButton performClick:]
// #5 [NSWindow sendEvent:]
// #6 [NSApp sendEvent:]
// #7 [NSApp run]
//
// -performClick: spins a nested run loop. If the pool created in DoWork was a
// standard NSAutoreleasePool, it would release the objects that were
// autoreleased into it once DoWork released it. This would cause the window
// controller, which autoreleased itself in frame #0, to release itself, and
// possibly free itself. Unfortunately this window controller controls the
// window in frame #5. When the stack is unwound to frame #5, the window would
// no longer exists and crashes may occur. Apple gets around this by never
// releasing the pool it creates in frame #4, and letting frame #7 clean it up
// when it cleans up the pool that wraps frame #7. When an autorelease pool is
// released it releases all other pools that were created after it on the
// autorelease pool stack.
//
// CrApplication is responsible for setting handlingSendEvent to true just
// before it sends the event throught the event handling mechanism, and
// returning it to its previous value once the event has been sent.
NSAutoreleasePool* MessagePumpNSApplication::CreateAutoreleasePool() {
NSAutoreleasePool* pool = nil;
DCHECK([NSApp isKindOfClass:[CrApplication class]]);
if (![static_cast<CrApplication*>(NSApp) isHandlingSendEvent]) {
pool = MessagePumpCFRunLoopBase::CreateAutoreleasePool();
}
return pool;
}
// static
MessagePump* MessagePumpMac::Create() {
if ([NSThread isMainThread]) {
return new MessagePumpNSApplication;
}
return new MessagePumpNSRunLoop;
}
} // namespace base
|