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