xref: /aosp_15_r20/external/cronet/base/message_loop/message_pump_apple.mm (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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