• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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