Bug 1921090 - Fix fenix lint failure CLOSED TREE
[gecko.git] / widget / cocoa / nsAppShell.mm
blob64ee1e259154a929267743a42668cadc00a79473
1 /* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8  * Runs the main native Cocoa run loop, interrupting it as needed to process
9  * Gecko events.
10  */
12 #import <Cocoa/Cocoa.h>
14 #include <dlfcn.h>
16 #include "mozilla/AvailableMemoryWatcher.h"
17 #include "CustomCocoaEvents.h"
18 #include "nsAppShell.h"
19 #include "gfxPlatform.h"
20 #include "nsCOMPtr.h"
21 #include "nsIFile.h"
22 #include "nsDirectoryServiceDefs.h"
23 #include "nsString.h"
24 #include "nsIRollupListener.h"
25 #include "nsIWidget.h"
26 #include "nsMemoryPressure.h"
27 #include "nsThreadUtils.h"
28 #include "nsServiceManagerUtils.h"
29 #include "nsObjCExceptions.h"
30 #include "nsCocoaUtils.h"
31 #include "nsCocoaFeatures.h"
32 #include "nsChildView.h"
33 #include "nsToolkit.h"
34 #include "TextInputHandler.h"
35 #include "mozilla/BackgroundHangMonitor.h"
36 #include "ScreenHelperCocoa.h"
37 #include "mozilla/Hal.h"
38 #include "mozilla/ProfilerLabels.h"
39 #include "mozilla/ProfilerThreadSleep.h"
40 #include "mozilla/widget/ScreenManager.h"
41 #include "HeadlessScreenHelper.h"
42 #include "MOZMenuOpeningCoordinator.h"
43 #include "pratom.h"
44 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
45 #  include "nsSandboxViolationSink.h"
46 #endif
48 #include <IOKit/pwr_mgt/IOPMLib.h>
49 #include "nsIDOMWakeLockListener.h"
50 #include "nsIPowerManagerService.h"
52 #include "nsIObserverService.h"
53 #include "mozilla/Services.h"
54 #include "mozilla/StaticPrefs_widget.h"
56 using namespace mozilla;
57 using namespace mozilla::widget;
59 #define WAKE_LOCK_LOG(...) \
60   MOZ_LOG(gMacWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
61 static mozilla::LazyLogModule gMacWakeLockLog("MacWakeLock");
63 // A wake lock listener that disables screen saver when requested by
64 // Gecko. For example when we're playing video in a foreground tab we
65 // don't want the screen saver to turn on.
67 class MacWakeLockListener final : public nsIDOMMozWakeLockListener {
68  public:
69   NS_DECL_ISUPPORTS;
71  private:
72   ~MacWakeLockListener() {}
74   IOPMAssertionID mAssertionNoDisplaySleepID = kIOPMNullAssertionID;
75   IOPMAssertionID mAssertionNoIdleSleepID = kIOPMNullAssertionID;
77   NS_IMETHOD Callback(const nsAString& aTopic,
78                       const nsAString& aState) override {
79     if (!aTopic.EqualsASCII("screen") && !aTopic.EqualsASCII("audio-playing") &&
80         !aTopic.EqualsASCII("video-playing")) {
81       return NS_OK;
82     }
84     // we should still hold the lock for background audio.
85     if (aTopic.EqualsASCII("audio-playing") &&
86         aState.EqualsASCII("locked-background")) {
87       WAKE_LOCK_LOG("keep audio playing even in background");
88       return NS_OK;
89     }
91     bool shouldKeepDisplayOn =
92         aTopic.EqualsASCII("screen") || aTopic.EqualsASCII("video-playing");
93     CFStringRef assertionType = shouldKeepDisplayOn
94                                     ? kIOPMAssertionTypeNoDisplaySleep
95                                     : kIOPMAssertionTypeNoIdleSleep;
96     IOPMAssertionID& assertionId = shouldKeepDisplayOn
97                                        ? mAssertionNoDisplaySleepID
98                                        : mAssertionNoIdleSleepID;
99     WAKE_LOCK_LOG("topic=%s, state=%s, shouldKeepDisplayOn=%d",
100                   NS_ConvertUTF16toUTF8(aTopic).get(),
101                   NS_ConvertUTF16toUTF8(aState).get(), shouldKeepDisplayOn);
103     // Note the wake lock code ensures that we're not sent duplicate
104     // "locked-foreground" notifications when multiple wake locks are held.
105     if (aState.EqualsASCII("locked-foreground")) {
106       if (assertionId != kIOPMNullAssertionID) {
107         WAKE_LOCK_LOG("already has a lock");
108         return NS_OK;
109       }
110       // Prevent screen saver.
111       CFStringRef cf_topic = ::CFStringCreateWithCharacters(
112           kCFAllocatorDefault, reinterpret_cast<const UniChar*>(aTopic.Data()),
113           aTopic.Length());
114       IOReturn success = ::IOPMAssertionCreateWithName(
115           assertionType, kIOPMAssertionLevelOn, cf_topic, &assertionId);
116       CFRelease(cf_topic);
117       if (success != kIOReturnSuccess) {
118         WAKE_LOCK_LOG("failed to disable screensaver");
119       }
120       WAKE_LOCK_LOG("create screensaver");
121     } else {
122       // Re-enable screen saver.
123       if (assertionId != kIOPMNullAssertionID) {
124         IOReturn result = ::IOPMAssertionRelease(assertionId);
125         if (result != kIOReturnSuccess) {
126           WAKE_LOCK_LOG("failed to release screensaver");
127         }
128         WAKE_LOCK_LOG("Release screensaver");
129         assertionId = kIOPMNullAssertionID;
130       }
131     }
132     return NS_OK;
133   }
134 };  // MacWakeLockListener
136 // defined in nsCocoaWindow.mm
137 extern int32_t gXULModalLevel;
139 static bool gAppShellMethodsSwizzled = false;
141 void OnUncaughtException(NSException* aException) {
142   nsObjCExceptionLog(aException);
143   MOZ_CRASH(
144       "Uncaught Objective C exception from NSSetUncaughtExceptionHandler");
147 @implementation GeckoNSApplication
149 // Load is called very early during startup, when the Objective C runtime loads
150 // this class.
151 + (void)load {
152   NSSetUncaughtExceptionHandler(OnUncaughtException);
155 // This method is called from NSDefaultTopLevelErrorHandler, which is invoked
156 // when an Objective C exception propagates up into the native event loop. It is
157 // possible that it is also called in other cases.
158 - (void)reportException:(NSException*)aException {
159   if (ShouldIgnoreObjCException(aException)) {
160     return;
161   }
163   nsObjCExceptionLog(aException);
165 #ifdef NIGHTLY_BUILD
166   MOZ_CRASH("Uncaught Objective C exception from -[GeckoNSApplication "
167             "reportException:]");
168 #endif
171 - (void)run {
172   _didLaunch = YES;
173   [super run];
176 - (void)sendEvent:(NSEvent*)anEvent {
177   mozilla::BackgroundHangMonitor().NotifyActivity();
178   [super sendEvent:anEvent];
181 - (NSEvent*)nextEventMatchingMask:(NSEventMask)mask
182                         untilDate:(NSDate*)expiration
183                            inMode:(NSString*)mode
184                           dequeue:(BOOL)flag {
185   MOZ_ASSERT([NSApp didLaunch]);
186   if (expiration) {
187     mozilla::BackgroundHangMonitor().NotifyWait();
188   }
189   NSEvent* nextEvent = [super nextEventMatchingMask:mask
190                                           untilDate:expiration
191                                              inMode:mode
192                                             dequeue:flag];
193   if (expiration) {
194     mozilla::BackgroundHangMonitor().NotifyActivity();
195   }
196   return nextEvent;
199 @end
201 // AppShellDelegate
203 // Cocoa bridge class.  An object of this class is registered to receive
204 // notifications.
206 @interface AppShellDelegate : NSObject {
207  @private
208   nsAppShell* mAppShell;
211 - (id)initWithAppShell:(nsAppShell*)aAppShell;
212 - (void)applicationWillTerminate:(NSNotification*)aNotification;
213 @end
215 // nsAppShell implementation
217 NS_IMETHODIMP
218 nsAppShell::ResumeNative(void) {
219   nsresult retval = nsBaseAppShell::ResumeNative();
220   if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) &&
221       mSkippedNativeCallback) {
222     mSkippedNativeCallback = false;
223     ScheduleNativeEventCallback();
224   }
225   return retval;
228 nsAppShell::nsAppShell()
229     : mAutoreleasePools(nullptr),
230       mDelegate(nullptr),
231       mCFRunLoop(NULL),
232       mCFRunLoopSource(NULL),
233       mRunningEventLoop(false),
234       mStarted(false),
235       mTerminated(false),
236       mSkippedNativeCallback(false),
237       mNativeEventCallbackDepth(0),
238       mNativeEventScheduledDepth(0) {
239   // A Cocoa event loop is running here if (and only if) we've been embedded
240   // by a Cocoa app.
241   mRunningCocoaEmbedded = [NSApp isRunning] ? true : false;
244 nsAppShell::~nsAppShell() {
245   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
247   hal::Shutdown();
249   if (mMemoryPressureSource) {
250     dispatch_release(mMemoryPressureSource);
251     mMemoryPressureSource = nullptr;
252   }
254   if (mCFRunLoop) {
255     if (mCFRunLoopSource) {
256       ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
257                               kCFRunLoopCommonModes);
258       ::CFRelease(mCFRunLoopSource);
259     }
260     if (mCFRunLoopObserver) {
261       ::CFRunLoopRemoveObserver(mCFRunLoop, mCFRunLoopObserver,
262                                 kCFRunLoopCommonModes);
263       ::CFRelease(mCFRunLoopObserver);
264     }
265     ::CFRelease(mCFRunLoop);
266   }
268   if (mAutoreleasePools) {
269     NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
270                  "nsAppShell destroyed without popping all autorelease pools");
271     ::CFRelease(mAutoreleasePools);
272   }
274   [mDelegate release];
276   NS_OBJC_END_TRY_IGNORE_BLOCK
279 NS_IMPL_ISUPPORTS(MacWakeLockListener, nsIDOMMozWakeLockListener)
280 mozilla::StaticRefPtr<MacWakeLockListener> sWakeLockListener;
282 static void AddScreenWakeLockListener() {
283   nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
284       do_GetService(POWERMANAGERSERVICE_CONTRACTID);
285   if (sPowerManagerService) {
286     sWakeLockListener = new MacWakeLockListener();
287     sPowerManagerService->AddWakeLockListener(sWakeLockListener);
288   } else {
289     NS_WARNING(
290         "Failed to retrieve PowerManagerService, wakelocks will be broken!");
291   }
294 static void RemoveScreenWakeLockListener() {
295   nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
296       do_GetService(POWERMANAGERSERVICE_CONTRACTID);
297   if (sPowerManagerService) {
298     sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
299     sPowerManagerService = nullptr;
300     sWakeLockListener = nullptr;
301   }
304 void RunLoopObserverCallback(CFRunLoopObserverRef aObserver,
305                              CFRunLoopActivity aActivity, void* aInfo) {
306   static_cast<nsAppShell*>(aInfo)->OnRunLoopActivityChanged(aActivity);
309 void nsAppShell::OnRunLoopActivityChanged(CFRunLoopActivity aActivity) {
310   if (aActivity == kCFRunLoopBeforeWaiting) {
311     mozilla::BackgroundHangMonitor().NotifyWait();
312   }
314   // When the event loop is in its waiting state, we would like the profiler to
315   // know that the thread is idle. The usual way to notify the profiler of
316   // idleness would be to place a profiler label frame with the IDLE category on
317   // the stack, for the duration of the function that does the waiting. However,
318   // since macOS uses an event loop model where "the event loop calls you", we
319   // do not control the function that does the waiting; the waiting happens
320   // inside CFRunLoop code. Instead, the run loop notifies us when it enters and
321   // exits the waiting state, by calling this function. So we do not have a
322   // function under our control that stays on the stack for the duration of the
323   // wait. So, rather than putting an AutoProfilerLabel on the stack, we will
324   // manually push and pop the label frame here. The location in the stack where
325   // this label frame is inserted is somewhat arbitrary. In practice, the label
326   // frame will be at the very tip of the stack, looking like it's "inside" the
327   // mach_msg_trap wait function.
328   if (aActivity == kCFRunLoopBeforeWaiting) {
329     using ThreadRegistration = mozilla::profiler::ThreadRegistration;
330     ThreadRegistration::WithOnThreadRef(
331         [&](ThreadRegistration::OnThreadRef aOnThreadRef) {
332           ProfilingStack& profilingStack =
333               aOnThreadRef.UnlockedConstReaderAndAtomicRWRef()
334                   .ProfilingStackRef();
335           mProfilingStackWhileWaiting = &profilingStack;
336           uint8_t variableOnStack = 0;
337           profilingStack.pushLabelFrame("Native event loop idle", nullptr,
338                                         &variableOnStack,
339                                         JS::ProfilingCategoryPair::IDLE, 0);
340           profiler_thread_sleep();
341         });
342   } else {
343     if (mProfilingStackWhileWaiting) {
344       mProfilingStackWhileWaiting->pop();
345       mProfilingStackWhileWaiting = nullptr;
346       profiler_thread_wake();
347     }
348   }
351 // Init
353 // Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
354 // interrupt the main native run loop.
356 // public
357 nsresult nsAppShell::Init() {
358   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
360   // No event loop is running yet (unless an embedding app that uses
361   // NSApplicationMain() is running).
362   NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
364   char* mozAppNoDock = PR_GetEnv("MOZ_APP_NO_DOCK");
365   if (mozAppNoDock && strcmp(mozAppNoDock, "") != 0) {
366     [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
367   }
369   // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
370   // by |this|.  CFArray is used instead of NSArray because NSArray wants to
371   // retain each object you add to it, and you can't retain an
372   // NSAutoreleasePool.
373   mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
374   NS_ENSURE_STATE(mAutoreleasePools);
376   bool isNSApplicationProcessType =
377       (XRE_GetProcessType() != GeckoProcessType_RDD) &&
378       (XRE_GetProcessType() != GeckoProcessType_Socket);
380   if (isNSApplicationProcessType) {
381     // This call initializes NSApplication unless:
382     // 1) we're using xre -- NSApp's already been initialized by
383     //    MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
384     // 2) an embedding app that uses NSApplicationMain() is running -- NSApp's
385     //    already been initialized and its main run loop is already running.
386     [[NSBundle mainBundle] loadNibNamed:@"res/MainMenu"
387                                   owner:[GeckoNSApplication sharedApplication]
388                         topLevelObjects:nil];
389   }
391   mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
392   NS_ENSURE_STATE(mDelegate);
394   // Add a CFRunLoopSource to the main native run loop.  The source is
395   // responsible for interrupting the run loop when Gecko events are ready.
397   mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
398   NS_ENSURE_STATE(mCFRunLoop);
399   ::CFRetain(mCFRunLoop);
401   CFRunLoopSourceContext context;
402   bzero(&context, sizeof(context));
403   // context.version = 0;
404   context.info = this;
405   context.perform = ProcessGeckoEvents;
407   mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
408   NS_ENSURE_STATE(mCFRunLoopSource);
410   ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
412   // Add a CFRunLoopObserver so that the profiler can be notified when we enter
413   // and exit the waiting state.
414   CFRunLoopObserverContext observerContext;
415   PodZero(&observerContext);
416   observerContext.info = this;
418   mCFRunLoopObserver = ::CFRunLoopObserverCreate(
419       kCFAllocatorDefault,
420       kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting | kCFRunLoopExit, true,
421       0, RunLoopObserverCallback, &observerContext);
422   NS_ENSURE_STATE(mCFRunLoopObserver);
424   ::CFRunLoopAddObserver(mCFRunLoop, mCFRunLoopObserver, kCFRunLoopCommonModes);
426   hal::Init();
428   if (XRE_IsParentProcess()) {
429     ScreenManager& screenManager = ScreenManager::GetSingleton();
431     if (gfxPlatform::IsHeadless()) {
432       screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
433     } else {
434       screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperCocoa>());
435     }
437     InitMemoryPressureObserver();
438   }
440   nsresult rv = nsBaseAppShell::Init();
442   if (isNSApplicationProcessType && !gAppShellMethodsSwizzled) {
443     // We should only replace the original terminate: method if we're not
444     // running in a Cocoa embedder. See bug 604901.
445     if (!mRunningCocoaEmbedded) {
446       nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
447                                 @selector(nsAppShell_NSApplication_terminate:));
448     }
449     gAppShellMethodsSwizzled = true;
450   }
452 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
453   if (Preferences::GetBool("security.sandbox.mac.track.violations", false)) {
454     nsSandboxViolationSink::Start();
455   }
456 #endif
458   [localPool release];
460   return rv;
462   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
465 // ProcessGeckoEvents
467 // The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
468 // signalled from ScheduleNativeEventCallback.
470 // Arrange for Gecko events to be processed on demand (in response to a call
471 // to ScheduleNativeEventCallback(), if processing of Gecko events via "native
472 // methods" hasn't been suspended).  This happens in NativeEventCallback().
474 // protected static
475 void nsAppShell::ProcessGeckoEvents(void* aInfo) {
476   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
477   AUTO_PROFILER_LABEL("nsAppShell::ProcessGeckoEvents", OTHER);
479   nsAppShell* self = static_cast<nsAppShell*>(aInfo);
481   if (self->mRunningEventLoop) {
482     self->mRunningEventLoop = false;
484     // The run loop may be sleeping -- [NSRunLoop runMode:...]
485     // won't return until it's given a reason to wake up.  Awaken it by
486     // posting a bogus event.  There's no need to make the event
487     // presentable.
488     //
489     // But _don't_ set windowNumber to '-1' -- that can lead to nasty
490     // weirdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
491     // these fake events, because the -1 has gotten changed into the number
492     // of an actual NSWindow object, and that NSWindow object has just been
493     // destroyed).  Setting windowNumber to '0' seems to work fine -- this
494     // seems to prevent the OS from ever trying to associate our bogus event
495     // with a particular NSWindow object.
496     [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
497                                         location:NSMakePoint(0, 0)
498                                    modifierFlags:0
499                                        timestamp:0
500                                     windowNumber:0
501                                          context:NULL
502                                          subtype:kEventSubtypeNone
503                                            data1:0
504                                            data2:0]
505              atStart:NO];
506     // Previously we used to send this second event regardless of
507     // self->mRunningEventLoop. However, that was removed in bug 1690687 for
508     // performance reasons. It is still needed for the mRunningEventLoop case
509     // otherwise we'll get in a cycle of sending postEvent followed by the
510     // DummyEvent inserted by nsBaseAppShell::OnProcessNextEvent. This second
511     // event will cause the second call to AcquireFirstMatchingEventInQueue in
512     // nsAppShell::ProcessNextNativeEvent to return true. Which makes
513     // nsBaseAppShell::OnProcessNextEvent call
514     // nsAppShell::ProcessNextNativeEvent again during which it will loop until
515     // it sleeps because ProcessGeckoEvents() won't be called for the
516     // DummyEvent.
517     //
518     // This is not a good approach and we should fix things up so that only
519     // one postEvent is needed.
520     [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
521                                         location:NSMakePoint(0, 0)
522                                    modifierFlags:0
523                                        timestamp:0
524                                     windowNumber:0
525                                          context:NULL
526                                          subtype:kEventSubtypeNone
527                                            data1:0
528                                            data2:0]
529              atStart:NO];
530   }
532   if (self->mSuspendNativeCount <= 0) {
533     ++self->mNativeEventCallbackDepth;
534     self->NativeEventCallback();
535     --self->mNativeEventCallbackDepth;
536   } else {
537     self->mSkippedNativeCallback = true;
538   }
540   if (self->mTerminated) {
541     // Still needed to avoid crashes on quit in most Mochitests.
542     [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
543                                         location:NSMakePoint(0, 0)
544                                    modifierFlags:0
545                                        timestamp:0
546                                     windowNumber:0
547                                          context:NULL
548                                          subtype:kEventSubtypeNone
549                                            data1:0
550                                            data2:0]
551              atStart:NO];
552   }
553   // Normally every call to ScheduleNativeEventCallback() results in
554   // exactly one call to ProcessGeckoEvents().  So each Release() here
555   // normally balances exactly one AddRef() in ScheduleNativeEventCallback().
556   // But if Exit() is called just after ScheduleNativeEventCallback(), the
557   // corresponding call to ProcessGeckoEvents() will never happen.  We check
558   // for this possibility in two different places -- here and in Exit()
559   // itself.  If we find here that Exit() has been called (that mTerminated
560   // is true), it's because we've been called recursively, that Exit() was
561   // called from self->NativeEventCallback() above, and that we're unwinding
562   // the recursion.  In this case we'll never be called again, and we balance
563   // here any extra calls to ScheduleNativeEventCallback().
564   //
565   // When ProcessGeckoEvents() is called recursively, it's because of a
566   // call to ScheduleNativeEventCallback() from NativeEventCallback().  We
567   // balance the "extra" AddRefs here (rather than always in Exit()) in order
568   // to ensure that 'self' stays alive until the end of this method.  We also
569   // make sure not to finish the balancing until all the recursion has been
570   // unwound.
571   if (self->mTerminated) {
572     int32_t releaseCount = 0;
573     if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) {
574       releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth,
575                                    self->mNativeEventCallbackDepth);
576     }
577     while (releaseCount-- > self->mNativeEventCallbackDepth) self->Release();
578   } else {
579     // As best we can tell, every call to ProcessGeckoEvents() is triggered
580     // by a call to ScheduleNativeEventCallback().  But we've seen a few
581     // (non-reproducible) cases of double-frees that *might* have been caused
582     // by spontaneous calls (from the OS) to ProcessGeckoEvents().  So we
583     // deal with that possibility here.
584     if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) {
585       PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0);
586       NS_WARNING("Spontaneous call to ProcessGeckoEvents()!");
587     } else {
588       self->Release();
589     }
590   }
592   NS_OBJC_END_TRY_IGNORE_BLOCK;
595 // WillTerminate
597 // Called by the AppShellDelegate when an NSApplicationWillTerminate
598 // notification is posted.  After this method is called, native events should
599 // no longer be processed.  The NSApplicationWillTerminate notification is
600 // only posted when [NSApp terminate:] is called, which doesn't happen on a
601 // "normal" application quit.
603 // public
604 void nsAppShell::WillTerminate() {
605   if (mTerminated) return;
607   // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
608   // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
609   NS_ProcessPendingEvents(NS_GetCurrentThread());
611   mTerminated = true;
614 // ScheduleNativeEventCallback
616 // Called (possibly on a non-main thread) when Gecko has an event that
617 // needs to be processed.  The Gecko event needs to be processed on the
618 // main thread, so the native run loop must be interrupted.
620 // In nsBaseAppShell.cpp, the mNativeEventPending variable is used to
621 // ensure that ScheduleNativeEventCallback() is called no more than once
622 // per call to NativeEventCallback().  ProcessGeckoEvents() can skip its
623 // call to NativeEventCallback() if processing of Gecko events by native
624 // means is suspended (using nsIAppShell::SuspendNative()), which will
625 // suspend calls from nsBaseAppShell::OnDispatchedEvent() to
626 // ScheduleNativeEventCallback().  But when Gecko event processing by
627 // native means is resumed (in ResumeNative()), an extra call is made to
628 // ScheduleNativeEventCallback() (from ResumeNative()).  This triggers
629 // another call to ProcessGeckoEvents(), which calls NativeEventCallback(),
630 // and nsBaseAppShell::OnDispatchedEvent() resumes calling
631 // ScheduleNativeEventCallback().
633 // protected virtual
634 void nsAppShell::ScheduleNativeEventCallback() {
635   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
637   if (mTerminated) return;
639   // Each AddRef() here is normally balanced by exactly one Release() in
640   // ProcessGeckoEvents().  But there are exceptions, for which see
641   // ProcessGeckoEvents() and Exit().
642   NS_ADDREF_THIS();
643   PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth);
645   // This will invoke ProcessGeckoEvents on the main thread.
646   ::CFRunLoopSourceSignal(mCFRunLoopSource);
647   ::CFRunLoopWakeUp(mCFRunLoop);
649   NS_OBJC_END_TRY_IGNORE_BLOCK;
652 // Undocumented Cocoa Event Manager function, present in the same form since
653 // at least OS X 10.6.
654 extern "C" EventAttributes GetEventAttributes(EventRef inEvent);
656 // ProcessNextNativeEvent
658 // If aMayWait is false, process a single native event.  If it is true, run
659 // the native run loop until stopped by ProcessGeckoEvents.
661 // Returns true if more events are waiting in the native event queue.
663 // protected virtual
664 bool nsAppShell::ProcessNextNativeEvent(bool aMayWait) {
665   bool moreEvents = false;
667   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
669   bool eventProcessed = false;
670   NSString* currentMode = nil;
672   if (mTerminated) return false;
674   // Do not call -[NSApplication nextEventMatchingMask:...] when we're trying to
675   // close a native menu. Doing so could confuse the NSMenu's closing mechanism.
676   // Instead, we try to unwind the stack as quickly as possible and return to
677   // the parent event loop. At that point, native events will be processed.
678   if (MOZMenuOpeningCoordinator.needToUnwindForMenuClosing) {
679     return false;
680   }
682   bool wasRunningEventLoop = mRunningEventLoop;
683   mRunningEventLoop = aMayWait;
684   NSDate* waitUntil = nil;
685   if (aMayWait) waitUntil = [NSDate distantFuture];
687   NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
689   EventQueueRef currentEventQueue = GetCurrentEventQueue();
691   if (aMayWait) {
692     mozilla::BackgroundHangMonitor().NotifyWait();
693   }
695   // Only call -[NSApp sendEvent:] (and indirectly send user-input events to
696   // Gecko) if aMayWait is true.  Tbis ensures most calls to -[NSApp
697   // sendEvent:] happen under nsAppShell::Run(), at the lowest level of
698   // recursion -- thereby making it less likely Gecko will process user-input
699   // events in the wrong order or skip some of them.  It also avoids eating
700   // too much CPU in nsBaseAppShell::OnProcessNextEvent() (which calls
701   // us) -- thereby avoiding the starvation of nsIRunnable events in
702   // nsThread::ProcessNextEvent().  For more information see bug 996848.
703   do {
704     // No autorelease pool is provided here, because OnProcessNextEvent
705     // and AfterProcessNextEvent are responsible for maintaining it.
706     NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
707                  "No autorelease pool for native event");
709     if (aMayWait && [[GeckoNSApplication sharedApplication] didLaunch]) {
710       currentMode = [currentRunLoop currentMode];
711       if (!currentMode) currentMode = NSDefaultRunLoopMode;
712       NSEvent* nextEvent = [NSApp nextEventMatchingMask:NSEventMaskAny
713                                               untilDate:waitUntil
714                                                  inMode:currentMode
715                                                 dequeue:YES];
716       if (nextEvent) {
717         mozilla::BackgroundHangMonitor().NotifyActivity();
718         [NSApp sendEvent:nextEvent];
719         eventProcessed = true;
720       }
721     } else {
722       // In at least 10.15, AcquireFirstMatchingEventInQueue will move 1
723       // CGEvent from the CGEvent queue into the Carbon event queue.
724       // Unfortunately, once an event has been moved to the Carbon event queue
725       // it's no longer a candidate for coalescing. This means that even if we
726       // don't remove the event from the queue, just calling
727       // AcquireFirstMatchingEventInQueue can cause behaviour change. Prior to
728       // bug 1690687 landing, the event that we got from
729       // AcquireFirstMatchingEventInQueue was often our own ApplicationDefined
730       // event. However, once we stopped posting that event on every Gecko
731       // event we're much more likely to get a CGEvent. When we have a high
732       // amount of load on the main thread, we end up alternating between Gecko
733       // events and native events.  Without CGEvent coalescing, the native
734       // event events can accumulate in the Carbon event queue which will
735       // manifest as laggy scrolling.
736 #if 1
737       eventProcessed = false;
738       break;
739 #else
740       // AcquireFirstMatchingEventInQueue() doesn't spin the (native) event
741       // loop, though it does queue up any newly available events from the
742       // window server.
743       EventRef currentEvent = AcquireFirstMatchingEventInQueue(
744           currentEventQueue, 0, NULL, kEventQueueOptionsNone);
745       if (!currentEvent) {
746         continue;
747       }
748       EventAttributes attrs = GetEventAttributes(currentEvent);
749       UInt32 eventKind = GetEventKind(currentEvent);
750       UInt32 eventClass = GetEventClass(currentEvent);
751       bool osCocoaEvent =
752           ((eventClass == 'appl') || (eventClass == kEventClassAppleEvent) ||
753            ((eventClass == 'cgs ') &&
754             (eventKind != NSEventTypeApplicationDefined)));
755       // If attrs is kEventAttributeUserEvent or kEventAttributeMonitored
756       // (i.e. a user input event), we shouldn't process it here while
757       // aMayWait is false.  Likewise if currentEvent will eventually be
758       // turned into an OS-defined Cocoa event, or otherwise needs AppKit
759       // processing.  Doing otherwise risks doing too much work here, and
760       // preventing the event from being properly processed by the AppKit
761       // framework.
762       if ((attrs != kEventAttributeNone) || osCocoaEvent) {
763         // Since we can't process the next event here (while aMayWait is false),
764         // we want moreEvents to be false on return.
765         eventProcessed = false;
766         // This call to ReleaseEvent() matches a call to RetainEvent() in
767         // AcquireFirstMatchingEventInQueue() above.
768         ReleaseEvent(currentEvent);
769         break;
770       }
771       // This call to RetainEvent() matches a call to ReleaseEvent() in
772       // RemoveEventFromQueue() below.
773       RetainEvent(currentEvent);
774       RemoveEventFromQueue(currentEventQueue, currentEvent);
775       EventTargetRef eventDispatcherTarget = GetEventDispatcherTarget();
776       SendEventToEventTarget(currentEvent, eventDispatcherTarget);
777       // This call to ReleaseEvent() matches a call to RetainEvent() in
778       // AcquireFirstMatchingEventInQueue() above.
779       ReleaseEvent(currentEvent);
780       eventProcessed = true;
781 #endif
782     }
783   } while (mRunningEventLoop);
785   if (eventProcessed) {
786     moreEvents =
787         (AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
788                                           kEventQueueOptionsNone) != NULL);
789   }
791   mRunningEventLoop = wasRunningEventLoop;
793   NS_OBJC_END_TRY_IGNORE_BLOCK;
795   if (!moreEvents) {
796     nsChildView::UpdateCurrentInputEventCount();
797   }
799   return moreEvents;
802 // Attempt to work around bug 1801419 by loading and initializing the
803 // SidecarCore private framework as the app shell starts up. This normally
804 // happens on demand, the first time any Cmd-key combination is pressed, and
805 // sometimes triggers crashes, caused by an Apple bug. We hope that doing it
806 // now, and somewhat more simply, will avoid the crashes. They happen
807 // (intermittently) when SidecarCore code tries to access C strings in special
808 // sections of its own __TEXT segment, and triggers fatal page faults (which
809 // is Apple's bug). Many of the C strings are part of the Objective-C class
810 // hierarchy (class names and so forth). We hope that adding them to this
811 // hierarchy will "pin" them in place -- so they'll rarely, if ever, be paged
812 // out again. Bug 1801419's crashes happen much more often on macOS 13
813 // (Ventura) than on other versions of macOS. So we only use this hack on
814 // macOS 13 and up.
815 static void PinSidecarCoreTextCStringSections() {
816   if (!dlopen(
817           "/System/Library/PrivateFrameworks/SidecarCore.framework/SidecarCore",
818           RTLD_LAZY)) {
819     return;
820   }
822   // Explicitly run the most basic part of the initialization code that
823   // normally runs automatically on the first Cmd-key combination.
824   Class displayManagerClass = NSClassFromString(@"SidecarDisplayManager");
825   if ([displayManagerClass respondsToSelector:@selector(sharedManager)]) {
826     id sharedManager =
827         [displayManagerClass performSelector:@selector(sharedManager)];
828     if ([sharedManager respondsToSelector:@selector(devices)]) {
829       [sharedManager performSelector:@selector(devices)];
830     }
831   }
834 // Run
836 // Overrides the base class's Run() method to call [NSApp run] (which spins
837 // the native run loop until the application quits).  Since (unlike the base
838 // class's Run() method) we don't process any Gecko events here, they need
839 // to be processed elsewhere (in NativeEventCallback(), called from
840 // ProcessGeckoEvents()).
842 // Camino called [NSApp run] on its own (via NSApplicationMain()), and so
843 // didn't call nsAppShell::Run().
845 // public
846 NS_IMETHODIMP
847 nsAppShell::Run(void) {
848   NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
849   if (mStarted || mTerminated) return NS_OK;
851   mStarted = true;
853   if (XRE_IsParentProcess()) {
854     if (nsCocoaFeatures::OnVenturaOrLater()) {
855       PinSidecarCoreTextCStringSections();
856     }
857     AddScreenWakeLockListener();
858   }
860   // We use the native Gecko event loop in content processes.
861   nsresult rv = NS_OK;
862   if (XRE_UseNativeEventProcessing()) {
863     NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
864     [NSApp run];
865     NS_OBJC_END_TRY_IGNORE_BLOCK;
866   } else {
867     rv = nsBaseAppShell::Run();
868   }
870   if (XRE_IsParentProcess()) {
871     RemoveScreenWakeLockListener();
872   }
874   return rv;
877 NS_IMETHODIMP
878 nsAppShell::Exit(void) {
879   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
881   // This method is currently called more than once -- from (according to
882   // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
883   // XPCOM shutdown notification that nsBaseAppShell has registered to
884   // receive.  So we need to ensure that multiple calls won't break anything.
885   if (mTerminated) {
886     return NS_OK;
887   }
889   mTerminated = true;
891 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
892   nsSandboxViolationSink::Stop();
893 #endif
895   // Quoting from Apple's doc on the [NSApplication stop:] method (from their
896   // doc on the NSApplication class):  "If this method is invoked during a
897   // modal event loop, it will break that loop but not the main event loop."
898   // nsAppShell::Exit() shouldn't be called from a modal event loop.  So if
899   // it is we complain about it (to users of debug builds) and call [NSApp
900   // stop:] one extra time.  (I'm not sure if modal event loops can be nested
901   // -- Apple's docs don't say one way or the other.  But the return value
902   // of [NSApp _isRunningModal] doesn't change immediately after a call to
903   // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
904   // will do the job.)
905   BOOL cocoaModal = [NSApp _isRunningModal];
906   NS_ASSERTION(!cocoaModal,
907                "Don't call nsAppShell::Exit() from a modal event loop!");
908   if (cocoaModal) [NSApp stop:nullptr];
909   [NSApp stop:nullptr];
911   // A call to Exit() just after a call to ScheduleNativeEventCallback()
912   // prevents the (normally) matching call to ProcessGeckoEvents() from
913   // happening.  If we've been called from ProcessGeckoEvents() (as usually
914   // happens), we take care of it there.  But if we have an unbalanced call
915   // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
916   // stack, we need to take care of the problem here.
917   if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
918     int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
919     while (releaseCount-- > 0) NS_RELEASE_THIS();
920   }
922   return nsBaseAppShell::Exit();
924   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
927 // OnProcessNextEvent
929 // This nsIThreadObserver method is called prior to processing an event.
930 // Set up an autorelease pool that will service any autoreleased Cocoa
931 // objects during this event.  This includes native events processed by
932 // ProcessNextNativeEvent.  The autorelease pool will be popped by
933 // AfterProcessNextEvent, it is important for these two methods to be
934 // tightly coupled.
936 // public
937 NS_IMETHODIMP
938 nsAppShell::OnProcessNextEvent(nsIThreadInternal* aThread, bool aMayWait) {
939   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
941   NS_ASSERTION(mAutoreleasePools,
942                "No stack on which to store autorelease pool");
944   NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
945   ::CFArrayAppendValue(mAutoreleasePools, pool);
947   return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait);
949   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
952 // AfterProcessNextEvent
954 // This nsIThreadObserver method is called after event processing is complete.
955 // The Cocoa implementation cleans up the autorelease pool create by the
956 // previous OnProcessNextEvent call.
958 // public
959 NS_IMETHODIMP
960 nsAppShell::AfterProcessNextEvent(nsIThreadInternal* aThread,
961                                   bool aEventWasProcessed) {
962   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
964   CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
966   NS_ASSERTION(mAutoreleasePools && count,
967                "Processed an event, but there's no autorelease pool?");
969   const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*>(
970       ::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
971   ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
972   [pool release];
974   return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed);
976   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
979 void nsAppShell::InitMemoryPressureObserver() {
980   // Testing shows that sometimes the memory pressure event is not fired for
981   // over a minute after the memory pressure change is reflected in sysctl
982   // values. Hence this may need to be augmented with polling of the memory
983   // pressure sysctls for lower latency reactions to OS memory pressure. This
984   // was also observed when using DISPATCH_QUEUE_PRIORITY_HIGH.
985   mMemoryPressureSource = dispatch_source_create(
986       DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0,
987       DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN |
988           DISPATCH_MEMORYPRESSURE_CRITICAL,
989       dispatch_get_main_queue());
991   dispatch_source_set_event_handler(mMemoryPressureSource, ^{
992     dispatch_source_memorypressure_flags_t pressureLevel =
993         dispatch_source_get_data(mMemoryPressureSource);
994     nsAppShell::OnMemoryPressureChanged(pressureLevel);
995   });
997   dispatch_resume(mMemoryPressureSource);
999   // Initialize the memory watcher.
1000   RefPtr<mozilla::nsAvailableMemoryWatcherBase> watcher(
1001       nsAvailableMemoryWatcherBase::GetSingleton());
1004 void nsAppShell::OnMemoryPressureChanged(
1005     dispatch_source_memorypressure_flags_t aPressureLevel) {
1006   // The memory pressure dispatch source is created (above) with
1007   // dispatch_get_main_queue() which always fires on the main thread.
1008   MOZ_ASSERT(NS_IsMainThread());
1010   MacMemoryPressureLevel geckoPressureLevel;
1011   switch (aPressureLevel) {
1012     case DISPATCH_MEMORYPRESSURE_NORMAL:
1013       geckoPressureLevel = MacMemoryPressureLevel::Value::eNormal;
1014       break;
1015     case DISPATCH_MEMORYPRESSURE_WARN:
1016       geckoPressureLevel = MacMemoryPressureLevel::Value::eWarning;
1017       break;
1018     case DISPATCH_MEMORYPRESSURE_CRITICAL:
1019       geckoPressureLevel = MacMemoryPressureLevel::Value::eCritical;
1020       break;
1021     default:
1022       geckoPressureLevel = MacMemoryPressureLevel::Value::eUnexpected;
1023   }
1025   RefPtr<mozilla::nsAvailableMemoryWatcherBase> watcher(
1026       nsAvailableMemoryWatcherBase::GetSingleton());
1027   watcher->OnMemoryPressureChanged(geckoPressureLevel);
1030 // AppShellDelegate implementation
1032 @implementation AppShellDelegate
1033 // initWithAppShell:
1035 // Constructs the AppShellDelegate object
1036 - (id)initWithAppShell:(nsAppShell*)aAppShell {
1037   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1039   if ((self = [self init])) {
1040     mAppShell = aAppShell;
1042     [[NSNotificationCenter defaultCenter]
1043         addObserver:self
1044            selector:@selector(applicationWillTerminate:)
1045                name:NSApplicationWillTerminateNotification
1046              object:NSApp];
1047     [[NSNotificationCenter defaultCenter]
1048         addObserver:self
1049            selector:@selector(applicationDidBecomeActive:)
1050                name:NSApplicationDidBecomeActiveNotification
1051              object:NSApp];
1052     [[NSNotificationCenter defaultCenter]
1053         addObserver:self
1054            selector:@selector(timezoneChanged:)
1055                name:NSSystemTimeZoneDidChangeNotification
1056              object:nil];
1057   }
1059   return self;
1061   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
1064 - (void)dealloc {
1065   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1067   [[NSNotificationCenter defaultCenter] removeObserver:self];
1068   [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
1069   [super dealloc];
1071   NS_OBJC_END_TRY_IGNORE_BLOCK;
1074 // applicationWillTerminate:
1076 // Notify the nsAppShell that native event processing should be discontinued.
1077 - (void)applicationWillTerminate:(NSNotification*)aNotification {
1078   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1080   mAppShell->WillTerminate();
1082   NS_OBJC_END_TRY_IGNORE_BLOCK;
1085 // applicationDidBecomeActive
1087 // Make sure TextInputHandler::sLastModifierState is updated when we become
1088 // active (since we won't have received [ChildView flagsChanged:] messages
1089 // while inactive).
1090 - (void)applicationDidBecomeActive:(NSNotification*)aNotification {
1091   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1093   // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
1094   // to worry about getting an NSInternalInconsistencyException here.
1095   NSEvent* currentEvent = [NSApp currentEvent];
1096   if (currentEvent) {
1097     TextInputHandler::sLastModifierState =
1098         [currentEvent modifierFlags] &
1099         NSEventModifierFlagDeviceIndependentFlagsMask;
1100   }
1102   nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
1103   if (observerService) {
1104     observerService->NotifyObservers(
1105         nullptr, NS_WIDGET_MAC_APP_ACTIVATE_OBSERVER_TOPIC, nullptr);
1106   }
1108   NS_OBJC_END_TRY_IGNORE_BLOCK;
1111 - (void)timezoneChanged:(NSNotification*)aNotification {
1112   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1114   nsBaseAppShell::OnSystemTimezoneChange();
1116   NS_OBJC_END_TRY_IGNORE_BLOCK;
1119 @end
1121 // We hook terminate: in order to make OS-initiated termination work nicely
1122 // with Gecko's shutdown sequence.  (Two ways to trigger OS-initiated
1123 // termination:  1) Quit from the Dock menu; 2) Log out from (or shut down)
1124 // your computer while the browser is active.)
1125 @interface NSApplication (MethodSwizzling)
1126 - (void)nsAppShell_NSApplication_terminate:(id)sender;
1127 @end
1129 @implementation NSApplication (MethodSwizzling)
1131 // Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
1132 // has returned NSTerminateNow.  This method "subclasses" and replaces the
1133 // OS's original implementation.  The only thing the orginal method does which
1134 // we need is that it posts NSApplicationWillTerminateNotification.  Everything
1135 // else is unneeded (because it's handled elsewhere), or actively interferes
1136 // with Gecko's shutdown sequence.  For example the original terminate: method
1137 // causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
1138 // above), which means that nothing runs after the call to nsAppStartup::Run()
1139 // in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
1140 // and NS_ShutdownXPCOM() never get called.
1141 - (void)nsAppShell_NSApplication_terminate:(id)sender {
1142   [[NSNotificationCenter defaultCenter]
1143       postNotificationName:NSApplicationWillTerminateNotification
1144                     object:NSApp];
1147 @end