1// Copyright 2012 The Chromium Authors 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "base/message_loop/message_pump_apple.h" 6 7#import <Foundation/Foundation.h> 8 9#include <atomic> 10#include <limits> 11#include <memory> 12#include <optional> 13 14#include "base/apple/call_with_eh_frame.h" 15#include "base/apple/scoped_cftyperef.h" 16#include "base/apple/scoped_nsautorelease_pool.h" 17#include "base/auto_reset.h" 18#include "base/check_op.h" 19#include "base/feature_list.h" 20#include "base/memory/raw_ptr.h" 21#include "base/memory/scoped_policy.h" 22#include "base/memory/stack_allocated.h" 23#include "base/metrics/histogram_samples.h" 24#include "base/notreached.h" 25#include "base/run_loop.h" 26#include "base/task/task_features.h" 27#include "base/threading/platform_thread.h" 28#include "base/time/time.h" 29#include "build/build_config.h" 30 31#if !BUILDFLAG(IS_IOS) 32#import <AppKit/AppKit.h> 33#endif // !BUILDFLAG(IS_IOS) 34 35namespace base { 36 37namespace { 38 39// Caches the state of the "TimerSlackMac" feature for efficiency. 40std::atomic_bool g_timer_slack = false; 41 42// Mask that determines which modes to use. 43enum { kCommonModeMask = 0b0000'0001, kAllModesMask = 0b0000'0111 }; 44 45// Modes to use for MessagePumpNSApplication that are considered "safe". 46// Currently just the common mode. Ideally, messages would be pumped in all 47// modes, but that interacts badly with app modal dialogs (e.g. NSAlert). 48enum { kNSApplicationModalSafeModeMask = 0b0000'0001 }; 49 50void NoOp(void* info) {} 51 52constexpr CFTimeInterval kCFTimeIntervalMax = 53 std::numeric_limits<CFTimeInterval>::max(); 54 55#if !BUILDFLAG(IS_IOS) 56// Set to true if message_pump_apple::Create() is called before NSApp is 57// initialized. Only accessed from the main thread. 58bool g_not_using_cr_app = false; 59 60// The MessagePump controlling [NSApp run]. 61MessagePumpNSApplication* g_app_pump; 62#endif // !BUILDFLAG(IS_IOS) 63 64} // namespace 65 66// A scoper for an optional autorelease pool. 67class OptionalAutoreleasePool { 68 STACK_ALLOCATED(); 69 70 public: 71 explicit OptionalAutoreleasePool(MessagePumpCFRunLoopBase* pump) { 72 if (pump->ShouldCreateAutoreleasePool()) { 73 pool_.emplace(); 74 } 75 } 76 77 OptionalAutoreleasePool(const OptionalAutoreleasePool&) = delete; 78 OptionalAutoreleasePool& operator=(const OptionalAutoreleasePool&) = delete; 79 80 private: 81 std::optional<base::apple::ScopedNSAutoreleasePool> pool_; 82}; 83 84class MessagePumpCFRunLoopBase::ScopedModeEnabler { 85 public: 86 ScopedModeEnabler(MessagePumpCFRunLoopBase* owner, int mode_index) 87 : owner_(owner), mode_index_(mode_index) { 88 CFRunLoopRef loop = owner_->run_loop_.get(); 89 CFRunLoopAddTimer(loop, owner_->delayed_work_timer_.get(), mode()); 90 CFRunLoopAddSource(loop, owner_->work_source_.get(), mode()); 91 CFRunLoopAddSource(loop, owner_->nesting_deferred_work_source_.get(), 92 mode()); 93 CFRunLoopAddObserver(loop, owner_->pre_wait_observer_.get(), mode()); 94 CFRunLoopAddObserver(loop, owner_->after_wait_observer_.get(), mode()); 95 CFRunLoopAddObserver(loop, owner_->pre_source_observer_.get(), mode()); 96 CFRunLoopAddObserver(loop, owner_->enter_exit_observer_.get(), mode()); 97 } 98 99 ScopedModeEnabler(const ScopedModeEnabler&) = delete; 100 ScopedModeEnabler& operator=(const ScopedModeEnabler&) = delete; 101 102 ~ScopedModeEnabler() { 103 CFRunLoopRef loop = owner_->run_loop_.get(); 104 CFRunLoopRemoveObserver(loop, owner_->enter_exit_observer_.get(), mode()); 105 CFRunLoopRemoveObserver(loop, owner_->pre_source_observer_.get(), mode()); 106 CFRunLoopRemoveObserver(loop, owner_->pre_wait_observer_.get(), mode()); 107 CFRunLoopRemoveObserver(loop, owner_->after_wait_observer_.get(), mode()); 108 CFRunLoopRemoveSource(loop, owner_->nesting_deferred_work_source_.get(), 109 mode()); 110 CFRunLoopRemoveSource(loop, owner_->work_source_.get(), mode()); 111 CFRunLoopRemoveTimer(loop, owner_->delayed_work_timer_.get(), mode()); 112 } 113 114 // This function knows about the AppKit RunLoop modes observed to potentially 115 // run tasks posted to Chrome's main thread task runner. Some are internal to 116 // AppKit but must be observed to keep Chrome's UI responsive. Others that may 117 // be interesting, but are not watched: 118 // - com.apple.hitoolbox.windows.transitionmode 119 // - com.apple.hitoolbox.windows.flushmode 120 const CFStringRef& mode() const { 121 static const CFStringRef modes[] = { 122 // The standard Core Foundation "common modes" constant. Must always be 123 // first in this list to match the value of kCommonModeMask. 124 kCFRunLoopCommonModes, 125 126 // Process work when NSMenus are fading out. 127 CFSTR("com.apple.hitoolbox.windows.windowfadingmode"), 128 129 // Process work when AppKit is highlighting an item on the main menubar. 130 CFSTR("NSUnhighlightMenuRunLoopMode"), 131 }; 132 static_assert(std::size(modes) == kNumModes, "mode size mismatch"); 133 static_assert((1 << kNumModes) - 1 == kAllModesMask, 134 "kAllModesMask not large enough"); 135 136 return modes[mode_index_]; 137 } 138 139 private: 140 const raw_ptr<MessagePumpCFRunLoopBase> owner_; // Weak. Owns this. 141 const int mode_index_; 142}; 143 144// Must be called on the run loop thread. 145void MessagePumpCFRunLoopBase::Run(Delegate* delegate) { 146 AutoReset<bool> auto_reset_keep_running(&keep_running_, true); 147 // nesting_level_ will be incremented in EnterExitRunLoop, so set 148 // run_nesting_level_ accordingly. 149 int last_run_nesting_level = run_nesting_level_; 150 run_nesting_level_ = nesting_level_ + 1; 151 152 Delegate* last_delegate = delegate_; 153 SetDelegate(delegate); 154 155 ScheduleWork(); 156 DoRun(delegate); 157 158 // Restore the previous state of the object. 159 SetDelegate(last_delegate); 160 run_nesting_level_ = last_run_nesting_level; 161} 162 163void MessagePumpCFRunLoopBase::Quit() { 164 if (DoQuit()) { 165 OnDidQuit(); 166 } 167} 168 169void MessagePumpCFRunLoopBase::OnDidQuit() { 170 keep_running_ = false; 171} 172 173// May be called on any thread. 174void MessagePumpCFRunLoopBase::ScheduleWork() { 175 CFRunLoopSourceSignal(work_source_.get()); 176 CFRunLoopWakeUp(run_loop_.get()); 177} 178 179// Must be called on the run loop thread. 180void MessagePumpCFRunLoopBase::ScheduleDelayedWork( 181 const Delegate::NextWorkInfo& next_work_info) { 182 DCHECK(!next_work_info.is_immediate()); 183 184 // The tolerance needs to be set before the fire date or it may be ignored. 185 if (g_timer_slack.load(std::memory_order_relaxed) && 186 !next_work_info.delayed_run_time.is_max() && 187 delayed_work_leeway_ != next_work_info.leeway) { 188 if (!next_work_info.leeway.is_zero()) { 189 // Specify slack based on |next_work_info|. 190 CFRunLoopTimerSetTolerance(delayed_work_timer_.get(), 191 next_work_info.leeway.InSecondsF()); 192 } else { 193 CFRunLoopTimerSetTolerance(delayed_work_timer_.get(), 0); 194 } 195 delayed_work_leeway_ = next_work_info.leeway; 196 } 197 198 // No-op if the delayed run time hasn't changed. 199 if (next_work_info.delayed_run_time != delayed_work_scheduled_at_) { 200 if (next_work_info.delayed_run_time.is_max()) { 201 CFRunLoopTimerSetNextFireDate(delayed_work_timer_.get(), 202 kCFTimeIntervalMax); 203 } else { 204 const double delay_seconds = 205 next_work_info.remaining_delay().InSecondsF(); 206 CFRunLoopTimerSetNextFireDate(delayed_work_timer_.get(), 207 CFAbsoluteTimeGetCurrent() + delay_seconds); 208 } 209 210 delayed_work_scheduled_at_ = next_work_info.delayed_run_time; 211 } 212} 213 214TimeTicks MessagePumpCFRunLoopBase::AdjustDelayedRunTime( 215 TimeTicks earliest_time, 216 TimeTicks run_time, 217 TimeTicks latest_time) { 218 if (g_timer_slack.load(std::memory_order_relaxed)) { 219 return earliest_time; 220 } 221 return MessagePump::AdjustDelayedRunTime(earliest_time, run_time, 222 latest_time); 223} 224 225#if BUILDFLAG(IS_IOS) 226void MessagePumpCFRunLoopBase::Attach(Delegate* delegate) {} 227 228void MessagePumpCFRunLoopBase::Detach() {} 229#endif // BUILDFLAG(IS_IOS) 230 231// Must be called on the run loop thread. 232MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase(int initial_mode_mask) { 233 run_loop_.reset(CFRunLoopGetCurrent(), base::scoped_policy::RETAIN); 234 235 // Set a repeating timer with a preposterous firing time and interval. The 236 // timer will effectively never fire as-is. The firing time will be adjusted 237 // as needed when ScheduleDelayedWork is called. 238 CFRunLoopTimerContext timer_context = {0}; 239 timer_context.info = this; 240 delayed_work_timer_.reset( 241 CFRunLoopTimerCreate(/*allocator=*/nullptr, 242 /*fireDate=*/kCFTimeIntervalMax, 243 /*interval=*/kCFTimeIntervalMax, 244 /*flags=*/0, 245 /*order=*/0, 246 /*callout=*/RunDelayedWorkTimer, 247 /*context=*/&timer_context)); 248 249 CFRunLoopSourceContext source_context = {0}; 250 source_context.info = this; 251 source_context.perform = RunWorkSource; 252 work_source_.reset(CFRunLoopSourceCreate(/*allocator=*/nullptr, 253 /*order=*/1, 254 /*context=*/&source_context)); 255 source_context.perform = RunNestingDeferredWorkSource; 256 nesting_deferred_work_source_.reset( 257 CFRunLoopSourceCreate(/*allocator=*/nullptr, 258 /*order=*/0, 259 /*context=*/&source_context)); 260 261 CFRunLoopObserverContext observer_context = {0}; 262 observer_context.info = this; 263 pre_wait_observer_.reset( 264 CFRunLoopObserverCreate(/*allocator=*/nullptr, 265 /*activities=*/kCFRunLoopBeforeWaiting, 266 /*repeats=*/true, 267 /*order=*/0, 268 /*callout=*/PreWaitObserver, 269 /*context=*/&observer_context)); 270 after_wait_observer_.reset(CFRunLoopObserverCreate( 271 /*allocator=*/nullptr, 272 /*activities=*/kCFRunLoopAfterWaiting, 273 /*repeats=*/true, 274 /*order=*/0, 275 /*callout=*/AfterWaitObserver, 276 /*context=*/&observer_context)); 277 pre_source_observer_.reset( 278 CFRunLoopObserverCreate(/*allocator=*/nullptr, 279 /*activities=*/kCFRunLoopBeforeSources, 280 /*repeats=*/true, 281 /*order=*/0, 282 /*callout=*/PreSourceObserver, 283 /*context=*/&observer_context)); 284 enter_exit_observer_.reset( 285 CFRunLoopObserverCreate(/*allocator=*/nullptr, 286 /*activities=*/kCFRunLoopEntry | kCFRunLoopExit, 287 /*repeats=*/true, 288 /*order=*/0, 289 /*callout=*/EnterExitObserver, 290 /*context=*/&observer_context)); 291 SetModeMask(initial_mode_mask); 292} 293 294// Ideally called on the run loop thread. If other run loops were running 295// lower on the run loop thread's stack when this object was created, the 296// same number of run loops must be running when this object is destroyed. 297MessagePumpCFRunLoopBase::~MessagePumpCFRunLoopBase() { 298 SetModeMask(0); 299} 300 301// static 302void MessagePumpCFRunLoopBase::InitializeFeatures() { 303 g_timer_slack.store(FeatureList::IsEnabled(kTimerSlackMac), 304 std::memory_order_relaxed); 305} 306 307#if BUILDFLAG(IS_IOS) 308void MessagePumpCFRunLoopBase::OnAttach() { 309 CHECK_EQ(nesting_level_, 0); 310 // On iOS: the MessagePump is attached while it's already running. 311 nesting_level_ = 1; 312 313 // There could be some native work done after attaching to the loop and before 314 // |work_source_| is invoked. 315 PushWorkItemScope(); 316} 317 318void MessagePumpCFRunLoopBase::OnDetach() { 319 // This function is called on shutdown. This can happen at either 320 // `nesting_level` >=1 or 0: 321 // `nesting_level_ == 0`: When this is detached as part of tear down outside 322 // of a run loop (e.g. ~TaskEnvironment). `nesting_level_ >= 1`: When this 323 // is detached as part of a native shutdown notification ran from the 324 // message pump itself. Nesting levels higher than 1 can happen in 325 // legitimate nesting situations like the browser being dismissed while 326 // displaying a long press context menu (CRWContextMenuController). 327 CHECK_GE(nesting_level_, 0); 328} 329#endif // BUILDFLAG(IS_IOS) 330 331void MessagePumpCFRunLoopBase::SetDelegate(Delegate* delegate) { 332 delegate_ = delegate; 333 334 if (delegate) { 335 // If any work showed up but could not be dispatched for want of a 336 // delegate, set it up for dispatch again now that a delegate is 337 // available. 338 if (delegateless_work_) { 339 CFRunLoopSourceSignal(work_source_.get()); 340 delegateless_work_ = false; 341 } 342 } 343} 344 345// Base version creates an autorelease pool. 346bool MessagePumpCFRunLoopBase::ShouldCreateAutoreleasePool() { 347 return true; 348} 349 350void MessagePumpCFRunLoopBase::SetModeMask(int mode_mask) { 351 for (size_t i = 0; i < kNumModes; ++i) { 352 bool enable = mode_mask & (0x1 << i); 353 if (enable == !enabled_modes_[i]) { 354 enabled_modes_[i] = 355 enable ? std::make_unique<ScopedModeEnabler>(this, i) : nullptr; 356 } 357 } 358} 359 360int MessagePumpCFRunLoopBase::GetModeMask() const { 361 int mask = 0; 362 for (size_t i = 0; i < kNumModes; ++i) { 363 mask |= enabled_modes_[i] ? (0x1 << i) : 0; 364 } 365 return mask; 366} 367 368void MessagePumpCFRunLoopBase::PopWorkItemScope() { 369 // A WorkItemScope should never have been pushed unless the loop was entered. 370 DCHECK_NE(nesting_level_, 0); 371 // If no WorkItemScope was pushed it cannot be popped. 372 DCHECK_GT(stack_.size(), 0u); 373 374 stack_.pop(); 375} 376 377void MessagePumpCFRunLoopBase::PushWorkItemScope() { 378 // A WorkItemScope should never be pushed unless the loop was entered. 379 DCHECK_NE(nesting_level_, 0); 380 381 // See RunWork() comments on why the size of |stack| is never bigger than 382 // |nesting_level_| even in nested loops. 383 DCHECK_LT(stack_.size(), static_cast<size_t>(nesting_level_)); 384 385 if (delegate_) { 386 stack_.push(delegate_->BeginWorkItem()); 387 } else { 388 stack_.push(std::nullopt); 389 } 390} 391 392// Called from the run loop. 393// static 394void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer, 395 void* info) { 396 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); 397 // The timer fired, assume we have work and let RunWork() figure out what to 398 // do and what to schedule after. 399 base::apple::CallWithEHFrame(^{ 400 // It would be incorrect to expect that `self->delayed_work_scheduled_at_` 401 // is smaller than or equal to `TimeTicks::Now()` because the fire date of a 402 // CFRunLoopTimer can be adjusted slightly. 403 // https://developer.apple.com/documentation/corefoundation/1543570-cfrunlooptimercreate?language=objc 404 DCHECK(!self->delayed_work_scheduled_at_.is_max()); 405 406 self->delayed_work_scheduled_at_ = base::TimeTicks::Max(); 407 self->RunWork(); 408 }); 409} 410 411// Called from the run loop. 412// static 413void MessagePumpCFRunLoopBase::RunWorkSource(void* info) { 414 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); 415 base::apple::CallWithEHFrame(^{ 416 self->RunWork(); 417 }); 418} 419 420// Called by MessagePumpCFRunLoopBase::RunWorkSource and RunDelayedWorkTimer. 421bool MessagePumpCFRunLoopBase::RunWork() { 422 if (!delegate_) { 423 // This point can be reached with a nullptr |delegate_| if Run is not on the 424 // stack but foreign code is spinning the CFRunLoop. Arrange to come back 425 // here when a delegate is available. 426 delegateless_work_ = true; 427 return false; 428 } 429 if (!keep_running()) { 430 return false; 431 } 432 433 // The NSApplication-based run loop only drains the autorelease pool at each 434 // UI event (NSEvent). The autorelease pool is not drained for each 435 // CFRunLoopSource target that's run. Use a local pool for any autoreleased 436 // objects if the app is not currently handling a UI event to ensure they're 437 // released promptly even in the absence of UI events. 438 OptionalAutoreleasePool autorelease_pool(this); 439 440 // Pop the current work item scope as it captures any native work happening 441 // *between* DoWork()'s. This DoWork() happens in sequence to that native 442 // work, not nested within it. 443 PopWorkItemScope(); 444 Delegate::NextWorkInfo next_work_info = delegate_->DoWork(); 445 // DoWork() (and its own work item coverage) is over so push a new scope to 446 // cover any native work that could possibly happen before the next RunWork(). 447 PushWorkItemScope(); 448 449 if (next_work_info.is_immediate()) { 450 CFRunLoopSourceSignal(work_source_.get()); 451 return true; 452 } else { 453 // This adjusts the next delayed wake up time (potentially cancels an 454 // already scheduled wake up if there is no delayed work). 455 ScheduleDelayedWork(next_work_info); 456 return false; 457 } 458} 459 460void MessagePumpCFRunLoopBase::RunIdleWork() { 461 if (!delegate_) { 462 // This point can be reached with a nullptr delegate_ if Run is not on the 463 // stack but foreign code is spinning the CFRunLoop. 464 return; 465 } 466 if (!keep_running()) { 467 return; 468 } 469 // The NSApplication-based run loop only drains the autorelease pool at each 470 // UI event (NSEvent). The autorelease pool is not drained for each 471 // CFRunLoopSource target that's run. Use a local pool for any autoreleased 472 // objects if the app is not currently handling a UI event to ensure they're 473 // released promptly even in the absence of UI events. 474 OptionalAutoreleasePool autorelease_pool(this); 475 bool did_work = delegate_->DoIdleWork(); 476 if (did_work) { 477 CFRunLoopSourceSignal(work_source_.get()); 478 } 479} 480 481// Called from the run loop. 482// static 483void MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource(void* info) { 484 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); 485 base::apple::CallWithEHFrame(^{ 486 self->RunNestingDeferredWork(); 487 }); 488} 489 490// Called by MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource. 491void MessagePumpCFRunLoopBase::RunNestingDeferredWork() { 492 if (!delegate_) { 493 // This point can be reached with a nullptr |delegate_| if Run is not on the 494 // stack but foreign code is spinning the CFRunLoop. There's no sense in 495 // attempting to do any work or signalling the work sources because 496 // without a delegate, work is not possible. 497 return; 498 } 499 500 // Attempt to do work, if there's any more work to do this call will re-signal 501 // |work_source_| and keep things going; otherwise, PreWaitObserver will be 502 // invoked by the native pump to declare us idle. 503 RunWork(); 504} 505 506void MessagePumpCFRunLoopBase::BeforeWait() { 507 if (!delegate_) { 508 // This point can be reached with a nullptr |delegate_| if Run is not on the 509 // stack but foreign code is spinning the CFRunLoop. 510 return; 511 } 512 delegate_->BeforeWait(); 513} 514 515// Called before the run loop goes to sleep or exits, or processes sources. 516void MessagePumpCFRunLoopBase::MaybeScheduleNestingDeferredWork() { 517 // deepest_nesting_level_ is set as run loops are entered. If the deepest 518 // level encountered is deeper than the current level, a nested loop 519 // (relative to the current level) ran since the last time nesting-deferred 520 // work was scheduled. When that situation is encountered, schedule 521 // nesting-deferred work in case any work was deferred because nested work 522 // was disallowed. 523 if (deepest_nesting_level_ > nesting_level_) { 524 deepest_nesting_level_ = nesting_level_; 525 CFRunLoopSourceSignal(nesting_deferred_work_source_.get()); 526 } 527} 528 529// Called from the run loop. 530// static 531void MessagePumpCFRunLoopBase::PreWaitObserver(CFRunLoopObserverRef observer, 532 CFRunLoopActivity activity, 533 void* info) { 534 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); 535 base::apple::CallWithEHFrame(^{ 536 // Current work item tracking needs to go away since execution will stop. 537 // Matches the PushWorkItemScope() in AfterWaitObserver() (with an arbitrary 538 // amount of matching Pop/Push in between when running work items). 539 self->PopWorkItemScope(); 540 541 // Attempt to do some idle work before going to sleep. 542 self->RunIdleWork(); 543 544 // The run loop is about to go to sleep. If any of the work done since it 545 // started or woke up resulted in a nested run loop running, 546 // nesting-deferred work may have accumulated. Schedule it for processing 547 // if appropriate. 548 self->MaybeScheduleNestingDeferredWork(); 549 550 // Notify the delegate that the loop is about to sleep. 551 self->BeforeWait(); 552 }); 553} 554 555// Called from the run loop. 556// static 557void MessagePumpCFRunLoopBase::AfterWaitObserver(CFRunLoopObserverRef observer, 558 CFRunLoopActivity activity, 559 void* info) { 560 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); 561 base::apple::CallWithEHFrame(^{ 562 // Emerging from sleep, any work happening after this (outside of a 563 // RunWork()) should be considered native work. Matching PopWorkItemScope() 564 // is in BeforeWait(). 565 self->PushWorkItemScope(); 566 }); 567} 568 569// Called from the run loop. 570// static 571void MessagePumpCFRunLoopBase::PreSourceObserver(CFRunLoopObserverRef observer, 572 CFRunLoopActivity activity, 573 void* info) { 574 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); 575 576 // The run loop has reached the top of the loop and is about to begin 577 // processing sources. If the last iteration of the loop at this nesting 578 // level did not sleep or exit, nesting-deferred work may have accumulated 579 // if a nested loop ran. Schedule nesting-deferred work for processing if 580 // appropriate. 581 base::apple::CallWithEHFrame(^{ 582 self->MaybeScheduleNestingDeferredWork(); 583 }); 584} 585 586// Called from the run loop. 587// static 588void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, 589 CFRunLoopActivity activity, 590 void* info) { 591 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); 592 593 switch (activity) { 594 case kCFRunLoopEntry: 595 ++self->nesting_level_; 596 597 // There could be some native work done after entering the loop and before 598 // the next observer. 599 self->PushWorkItemScope(); 600 if (self->nesting_level_ > self->deepest_nesting_level_) { 601 self->deepest_nesting_level_ = self->nesting_level_; 602 } 603 break; 604 605 case kCFRunLoopExit: 606 // Not all run loops go to sleep. If a run loop is stopped before it 607 // goes to sleep due to a CFRunLoopStop call, or if the timeout passed 608 // to CFRunLoopRunInMode expires, the run loop may proceed directly from 609 // handling sources to exiting without any sleep. This most commonly 610 // occurs when CFRunLoopRunInMode is passed a timeout of 0, causing it 611 // to make a single pass through the loop and exit without sleep. Some 612 // native loops use CFRunLoop in this way. Because PreWaitObserver will 613 // not be called in these case, MaybeScheduleNestingDeferredWork needs 614 // to be called here, as the run loop exits. 615 // 616 // MaybeScheduleNestingDeferredWork consults self->nesting_level_ 617 // to determine whether to schedule nesting-deferred work. It expects 618 // the nesting level to be set to the depth of the loop that is going 619 // to sleep or exiting. It must be called before decrementing the 620 // value so that the value still corresponds to the level of the exiting 621 // loop. 622 base::apple::CallWithEHFrame(^{ 623 self->MaybeScheduleNestingDeferredWork(); 624 }); 625 626 // Current work item tracking needs to go away since execution will stop. 627 self->PopWorkItemScope(); 628 629 --self->nesting_level_; 630 break; 631 632 default: 633 break; 634 } 635 636 base::apple::CallWithEHFrame(^{ 637 self->EnterExitRunLoop(activity); 638 }); 639} 640 641// Called by MessagePumpCFRunLoopBase::EnterExitRunLoop. The default 642// implementation is a no-op. 643void MessagePumpCFRunLoopBase::EnterExitRunLoop(CFRunLoopActivity activity) {} 644 645MessagePumpCFRunLoop::MessagePumpCFRunLoop() 646 : MessagePumpCFRunLoopBase(kCommonModeMask), quit_pending_(false) {} 647 648MessagePumpCFRunLoop::~MessagePumpCFRunLoop() = default; 649 650// Called by MessagePumpCFRunLoopBase::DoRun. If other CFRunLoopRun loops were 651// running lower on the run loop thread's stack when this object was created, 652// the same number of CFRunLoopRun loops must be running for the outermost call 653// to Run. Run/DoRun are reentrant after that point. 654void MessagePumpCFRunLoop::DoRun(Delegate* delegate) { 655 // This is completely identical to calling CFRunLoopRun(), except autorelease 656 // pool management is introduced. 657 int result; 658 do { 659 OptionalAutoreleasePool autorelease_pool(this); 660 result = 661 CFRunLoopRunInMode(kCFRunLoopDefaultMode, kCFTimeIntervalMax, false); 662 } while (result != kCFRunLoopRunStopped && result != kCFRunLoopRunFinished); 663} 664 665// Must be called on the run loop thread. 666bool MessagePumpCFRunLoop::DoQuit() { 667 // Stop the innermost run loop managed by this MessagePumpCFRunLoop object. 668 if (nesting_level() == run_nesting_level()) { 669 // This object is running the innermost loop, just stop it. 670 CFRunLoopStop(run_loop()); 671 return true; 672 } else { 673 // There's another loop running inside the loop managed by this object. 674 // In other words, someone else called CFRunLoopRunInMode on the same 675 // thread, deeper on the stack than the deepest Run call. Don't preempt 676 // other run loops, just mark this object to quit the innermost Run as 677 // soon as the other inner loops not managed by Run are done. 678 quit_pending_ = true; 679 return false; 680 } 681} 682 683// Called by MessagePumpCFRunLoopBase::EnterExitObserver. 684void MessagePumpCFRunLoop::EnterExitRunLoop(CFRunLoopActivity activity) { 685 if (activity == kCFRunLoopExit && nesting_level() == run_nesting_level() && 686 quit_pending_) { 687 // Quit was called while loops other than those managed by this object 688 // were running further inside a run loop managed by this object. Now 689 // that all unmanaged inner run loops are gone, stop the loop running 690 // just inside Run. 691 CFRunLoopStop(run_loop()); 692 quit_pending_ = false; 693 OnDidQuit(); 694 } 695} 696 697MessagePumpNSRunLoop::MessagePumpNSRunLoop() 698 : MessagePumpCFRunLoopBase(kCommonModeMask) { 699 CFRunLoopSourceContext source_context = {0}; 700 source_context.perform = NoOp; 701 quit_source_.reset(CFRunLoopSourceCreate(/*allocator=*/nullptr, 702 /*order=*/0, 703 /*context=*/&source_context)); 704 CFRunLoopAddSource(run_loop(), quit_source_.get(), kCFRunLoopCommonModes); 705} 706 707MessagePumpNSRunLoop::~MessagePumpNSRunLoop() { 708 CFRunLoopRemoveSource(run_loop(), quit_source_.get(), kCFRunLoopCommonModes); 709} 710 711void MessagePumpNSRunLoop::DoRun(Delegate* delegate) { 712 while (keep_running()) { 713 // NSRunLoop manages autorelease pools itself. 714 [NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode 715 beforeDate:NSDate.distantFuture]; 716 } 717} 718 719bool MessagePumpNSRunLoop::DoQuit() { 720 CFRunLoopSourceSignal(quit_source_.get()); 721 CFRunLoopWakeUp(run_loop()); 722 return true; 723} 724 725#if BUILDFLAG(IS_IOS) 726MessagePumpUIApplication::MessagePumpUIApplication() 727 : MessagePumpCFRunLoopBase(kCommonModeMask) {} 728 729MessagePumpUIApplication::~MessagePumpUIApplication() = default; 730 731void MessagePumpUIApplication::DoRun(Delegate* delegate) { 732 NOTREACHED(); 733} 734 735bool MessagePumpUIApplication::DoQuit() { 736 NOTREACHED(); 737 return false; 738} 739 740void MessagePumpUIApplication::Attach(Delegate* delegate) { 741 DCHECK(!run_loop_); 742 run_loop_.emplace(); 743 744 CHECK(run_loop_->BeforeRun()); 745 SetDelegate(delegate); 746 747 OnAttach(); 748} 749 750void MessagePumpUIApplication::Detach() { 751 DCHECK(run_loop_); 752 run_loop_->AfterRun(); 753 SetDelegate(nullptr); 754 run_loop_.reset(); 755 756 OnDetach(); 757} 758 759#else 760 761ScopedPumpMessagesInPrivateModes::ScopedPumpMessagesInPrivateModes() { 762 DCHECK(g_app_pump); 763 DCHECK_EQ(kNSApplicationModalSafeModeMask, g_app_pump->GetModeMask()); 764 // Pumping events in private runloop modes is known to interact badly with 765 // app modal windows like NSAlert. 766 if (NSApp.modalWindow) { 767 return; 768 } 769 g_app_pump->SetModeMask(kAllModesMask); 770} 771 772ScopedPumpMessagesInPrivateModes::~ScopedPumpMessagesInPrivateModes() { 773 DCHECK(g_app_pump); 774 g_app_pump->SetModeMask(kNSApplicationModalSafeModeMask); 775} 776 777int ScopedPumpMessagesInPrivateModes::GetModeMaskForTest() { 778 return g_app_pump ? g_app_pump->GetModeMask() : -1; 779} 780 781MessagePumpNSApplication::MessagePumpNSApplication() 782 : MessagePumpCFRunLoopBase(kNSApplicationModalSafeModeMask) { 783 DCHECK_EQ(nullptr, g_app_pump); 784 g_app_pump = this; 785} 786 787MessagePumpNSApplication::~MessagePumpNSApplication() { 788 DCHECK_EQ(this, g_app_pump); 789 g_app_pump = nullptr; 790} 791 792void MessagePumpNSApplication::DoRun(Delegate* delegate) { 793 bool last_running_own_loop_ = running_own_loop_; 794 795 // NSApp must be initialized by calling: 796 // [{some class which implements CrAppProtocol} sharedApplication] 797 // Most likely candidates are CrApplication or BrowserCrApplication. 798 // These can be initialized from C++ code by calling 799 // RegisterCrApp() or RegisterBrowserCrApp(). 800 CHECK(NSApp); 801 802 if (!NSApp.running) { 803 running_own_loop_ = false; 804 // NSApplication manages autorelease pools itself when run this way. 805 [NSApp run]; 806 } else { 807 running_own_loop_ = true; 808 while (keep_running()) { 809 OptionalAutoreleasePool autorelease_pool(this); 810 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny 811 untilDate:NSDate.distantFuture 812 inMode:NSDefaultRunLoopMode 813 dequeue:YES]; 814 if (event) { 815 [NSApp sendEvent:event]; 816 } 817 } 818 } 819 820 running_own_loop_ = last_running_own_loop_; 821} 822 823bool MessagePumpNSApplication::DoQuit() { 824 // If the app is displaying a modal window in a native run loop, we can only 825 // quit our run loop after the window is closed. Otherwise the [NSApplication 826 // stop] below will apply to the modal window run loop instead. To work around 827 // this, the quit is applied when we re-enter our own run loop after the 828 // window is gone (see MessagePumpNSApplication::EnterExitRunLoop). 829 if (nesting_level() > run_nesting_level() && NSApp.modalWindow != nil) { 830 quit_pending_ = true; 831 return false; 832 } 833 834 if (!running_own_loop_) { 835 [NSApp stop:nil]; 836 } 837 838 // Send a fake event to wake the loop up. 839 [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined 840 location:NSZeroPoint 841 modifierFlags:0 842 timestamp:0 843 windowNumber:0 844 context:nil 845 subtype:0 846 data1:0 847 data2:0] 848 atStart:NO]; 849 return true; 850} 851 852void MessagePumpNSApplication::EnterExitRunLoop(CFRunLoopActivity activity) { 853 // If we previously tried quitting while a modal window was active, check if 854 // the window is gone now and we're no longer nested in a system run loop. 855 if (activity == kCFRunLoopEntry && quit_pending_ && 856 nesting_level() <= run_nesting_level() && NSApp.modalWindow == nil) { 857 quit_pending_ = false; 858 if (DoQuit()) { 859 OnDidQuit(); 860 } 861 } 862} 863 864MessagePumpCrApplication::MessagePumpCrApplication() = default; 865 866MessagePumpCrApplication::~MessagePumpCrApplication() = default; 867 868// Prevents an autorelease pool from being created if the app is in the midst of 869// handling a UI event because various parts of AppKit depend on objects that 870// are created while handling a UI event to be autoreleased in the event loop. 871// An example of this is NSWindowController. When a window with a window 872// controller is closed it goes through a stack like this: 873// (Several stack frames elided for clarity) 874// 875// #0 [NSWindowController autorelease] 876// #1 DoAClose 877// #2 MessagePumpCFRunLoopBase::DoWork() 878// #3 [NSRunLoop run] 879// #4 [NSButton performClick:] 880// #5 [NSWindow sendEvent:] 881// #6 [NSApp sendEvent:] 882// #7 [NSApp run] 883// 884// -performClick: spins a nested run loop. If the pool created in DoWork was a 885// standard NSAutoreleasePool, it would release the objects that were 886// autoreleased into it once DoWork released it. This would cause the window 887// controller, which autoreleased itself in frame #0, to release itself, and 888// possibly free itself. Unfortunately this window controller controls the 889// window in frame #5. When the stack is unwound to frame #5, the window would 890// no longer exists and crashes may occur. Apple gets around this by never 891// releasing the pool it creates in frame #4, and letting frame #7 clean it up 892// when it cleans up the pool that wraps frame #7. When an autorelease pool is 893// released it releases all other pools that were created after it on the 894// autorelease pool stack. 895// 896// CrApplication is responsible for setting handlingSendEvent to true just 897// before it sends the event through the event handling mechanism, and 898// returning it to its previous value once the event has been sent. 899bool MessagePumpCrApplication::ShouldCreateAutoreleasePool() { 900 if (message_pump_apple::IsHandlingSendEvent()) { 901 return false; 902 } 903 return MessagePumpNSApplication::ShouldCreateAutoreleasePool(); 904} 905 906#endif // BUILDFLAG(IS_IOS) 907 908namespace message_pump_apple { 909 910std::unique_ptr<MessagePump> Create() { 911 if (NSThread.isMainThread) { 912#if BUILDFLAG(IS_IOS) 913 return std::make_unique<MessagePumpUIApplication>(); 914#else 915 if ([NSApp conformsToProtocol:@protocol(CrAppProtocol)]) { 916 return std::make_unique<MessagePumpCrApplication>(); 917 } 918 919 // The main-thread MessagePump implementations REQUIRE an NSApp. 920 // Executables which have specific requirements for their 921 // NSApplication subclass should initialize appropriately before 922 // creating an event loop. 923 [NSApplication sharedApplication]; 924 g_not_using_cr_app = true; 925 return std::make_unique<MessagePumpNSApplication>(); 926#endif 927 } 928 929 return std::make_unique<MessagePumpNSRunLoop>(); 930} 931 932#if !BUILDFLAG(IS_IOS) 933 934bool UsingCrApp() { 935 DCHECK(NSThread.isMainThread); 936 937 // If NSApp is still not initialized, then the subclass used cannot 938 // be determined. 939 DCHECK(NSApp); 940 941 // The pump was created using MessagePumpNSApplication. 942 if (g_not_using_cr_app) { 943 return false; 944 } 945 946 return [NSApp conformsToProtocol:@protocol(CrAppProtocol)]; 947} 948 949bool IsHandlingSendEvent() { 950 DCHECK([NSApp conformsToProtocol:@protocol(CrAppProtocol)]); 951 NSObject<CrAppProtocol>* app = static_cast<NSObject<CrAppProtocol>*>(NSApp); 952 return [app isHandlingSendEvent]; 953} 954 955#endif // !BUILDFLAG(IS_IOS) 956 957} // namespace message_pump_apple 958 959} // namespace base 960