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