Bug 1700051: part 28) Refactor `WordSplitState<T>::GetDOMWordSeparatorOffset` to...
[gecko.git] / widget / cocoa / nsAppShell.mm
blob90f479e03b7238a10294a8c2feed29a3d2c31660
1 /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /*
7  * Runs the main native Cocoa run loop, interrupting it as needed to process
8  * Gecko events.
9  */
11 #import <Cocoa/Cocoa.h>
13 #include "CustomCocoaEvents.h"
14 #include "mozilla/WidgetTraceEvent.h"
15 #include "nsAppShell.h"
16 #include "gfxPlatform.h"
17 #include "nsCOMPtr.h"
18 #include "nsIFile.h"
19 #include "nsDirectoryServiceDefs.h"
20 #include "nsString.h"
21 #include "nsIRollupListener.h"
22 #include "nsIWidget.h"
23 #include "nsThreadUtils.h"
24 #include "nsServiceManagerUtils.h"
25 #include "nsObjCExceptions.h"
26 #include "nsCocoaUtils.h"
27 #include "nsChildView.h"
28 #include "nsToolkit.h"
29 #include "TextInputHandler.h"
30 #include "mozilla/BackgroundHangMonitor.h"
31 #include "GeckoProfiler.h"
32 #include "ScreenHelperCocoa.h"
33 #include "mozilla/Hal.h"
34 #include "mozilla/widget/ScreenManager.h"
35 #include "HeadlessScreenHelper.h"
36 #include "pratom.h"
37 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
38 #  include "nsSandboxViolationSink.h"
39 #endif
41 #include <IOKit/pwr_mgt/IOPMLib.h>
42 #include "nsIDOMWakeLockListener.h"
43 #include "nsIPowerManagerService.h"
45 #include "nsIObserverService.h"
46 #include "mozilla/Services.h"
47 #include "mozilla/StaticPrefs_widget.h"
49 using namespace mozilla;
50 using namespace mozilla::widget;
52 #define WAKE_LOCK_LOG(...) MOZ_LOG(gMacWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
53 static mozilla::LazyLogModule gMacWakeLockLog("MacWakeLock");
55 // A wake lock listener that disables screen saver when requested by
56 // Gecko. For example when we're playing video in a foreground tab we
57 // don't want the screen saver to turn on.
59 class MacWakeLockListener final : public nsIDOMMozWakeLockListener {
60  public:
61   NS_DECL_ISUPPORTS;
63  private:
64   ~MacWakeLockListener() {}
66   IOPMAssertionID mAssertionNoDisplaySleepID = kIOPMNullAssertionID;
67   IOPMAssertionID mAssertionNoIdleSleepID = kIOPMNullAssertionID;
69   NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) override {
70     if (!aTopic.EqualsASCII("screen") && !aTopic.EqualsASCII("audio-playing") &&
71         !aTopic.EqualsASCII("video-playing")) {
72       return NS_OK;
73     }
75     // we should still hold the lock for background audio.
76     if (aTopic.EqualsASCII("audio-playing") && aState.EqualsASCII("locked-background")) {
77       WAKE_LOCK_LOG("keep audio playing even in background");
78       return NS_OK;
79     }
81     bool shouldKeepDisplayOn = aTopic.EqualsASCII("screen") || aTopic.EqualsASCII("video-playing");
82     CFStringRef assertionType =
83         shouldKeepDisplayOn ? kIOPMAssertionTypeNoDisplaySleep : kIOPMAssertionTypeNoIdleSleep;
84     IOPMAssertionID& assertionId =
85         shouldKeepDisplayOn ? mAssertionNoDisplaySleepID : mAssertionNoIdleSleepID;
86     WAKE_LOCK_LOG("topic=%s, state=%s, shouldKeepDisplayOn=%d", NS_ConvertUTF16toUTF8(aTopic).get(),
87                   NS_ConvertUTF16toUTF8(aState).get(), shouldKeepDisplayOn);
89     // Note the wake lock code ensures that we're not sent duplicate
90     // "locked-foreground" notifications when multiple wake locks are held.
91     if (aState.EqualsASCII("locked-foreground")) {
92       if (assertionId != kIOPMNullAssertionID) {
93         WAKE_LOCK_LOG("already has a lock");
94         return NS_OK;
95       }
96       // Prevent screen saver.
97       CFStringRef cf_topic = ::CFStringCreateWithCharacters(
98           kCFAllocatorDefault, reinterpret_cast<const UniChar*>(aTopic.Data()), aTopic.Length());
99       IOReturn success = ::IOPMAssertionCreateWithName(assertionType, kIOPMAssertionLevelOn,
100                                                        cf_topic, &assertionId);
101       CFRelease(cf_topic);
102       if (success != kIOReturnSuccess) {
103         WAKE_LOCK_LOG("failed to disable screensaver");
104       }
105       WAKE_LOCK_LOG("create screensaver");
106     } else {
107       // Re-enable screen saver.
108       if (assertionId != kIOPMNullAssertionID) {
109         IOReturn result = ::IOPMAssertionRelease(assertionId);
110         if (result != kIOReturnSuccess) {
111           WAKE_LOCK_LOG("failed to release screensaver");
112         }
113         WAKE_LOCK_LOG("Release screensaver");
114         assertionId = kIOPMNullAssertionID;
115       }
116     }
117     return NS_OK;
118   }
119 };  // MacWakeLockListener
121 // defined in nsCocoaWindow.mm
122 extern int32_t gXULModalLevel;
124 static bool gAppShellMethodsSwizzled = false;
126 void OnUncaughtException(NSException* aException) {
127   nsObjCExceptionLog(aException);
128   MOZ_CRASH("Uncaught Objective C exception from NSSetUncaughtExceptionHandler");
131 @implementation GeckoNSApplication
133 // Load is called very early during startup, when the Objective C runtime loads this class.
134 + (void)load {
135   NSSetUncaughtExceptionHandler(OnUncaughtException);
138 // This method is called from NSDefaultTopLevelErrorHandler, which is invoked when an Objective C
139 // exception propagates up into the native event loop. It is possible that it is also called in
140 // other cases.
141 - (void)reportException:(NSException*)aException {
142   if (ShouldIgnoreObjCException(aException)) {
143     return;
144   }
146   nsObjCExceptionLog(aException);
148 #ifdef NIGHTLY_BUILD
149   MOZ_CRASH("Uncaught Objective C exception from -[GeckoNSApplication reportException:]");
150 #endif
153 - (void)sendEvent:(NSEvent*)anEvent {
154   mozilla::BackgroundHangMonitor().NotifyActivity();
156   if ([anEvent type] == NSEventTypeApplicationDefined && [anEvent subtype] == kEventSubtypeTrace) {
157     mozilla::SignalTracerThread();
158     return;
159   }
160   [super sendEvent:anEvent];
163 - (NSEvent*)nextEventMatchingMask:(NSEventMask)mask
164                         untilDate:(NSDate*)expiration
165                            inMode:(NSString*)mode
166                           dequeue:(BOOL)flag {
167   if (expiration) {
168     mozilla::BackgroundHangMonitor().NotifyWait();
169   }
170   NSEvent* nextEvent = [super nextEventMatchingMask:mask
171                                           untilDate:expiration
172                                              inMode:mode
173                                             dequeue:flag];
174   if (expiration) {
175     mozilla::BackgroundHangMonitor().NotifyActivity();
176   }
177   return nextEvent;
180 @end
182 // AppShellDelegate
184 // Cocoa bridge class.  An object of this class is registered to receive
185 // notifications.
187 @interface AppShellDelegate : NSObject {
188  @private
189   nsAppShell* mAppShell;
192 - (id)initWithAppShell:(nsAppShell*)aAppShell;
193 - (void)applicationWillTerminate:(NSNotification*)aNotification;
194 @end
196 // nsAppShell implementation
198 NS_IMETHODIMP
199 nsAppShell::ResumeNative(void) {
200   nsresult retval = nsBaseAppShell::ResumeNative();
201   if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) && mSkippedNativeCallback) {
202     mSkippedNativeCallback = false;
203     ScheduleNativeEventCallback();
204   }
205   return retval;
208 nsAppShell::nsAppShell()
209     : mAutoreleasePools(nullptr),
210       mDelegate(nullptr),
211       mCFRunLoop(NULL),
212       mCFRunLoopSource(NULL),
213       mRunningEventLoop(false),
214       mStarted(false),
215       mTerminated(false),
216       mSkippedNativeCallback(false),
217       mNativeEventCallbackDepth(0),
218       mNativeEventScheduledDepth(0) {
219   // A Cocoa event loop is running here if (and only if) we've been embedded
220   // by a Cocoa app.
221   mRunningCocoaEmbedded = [NSApp isRunning] ? true : false;
224 nsAppShell::~nsAppShell() {
225   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
227   hal::Shutdown();
229   if (mCFRunLoop) {
230     if (mCFRunLoopSource) {
231       ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
232       ::CFRelease(mCFRunLoopSource);
233     }
234     if (mCFRunLoopObserver) {
235       ::CFRunLoopRemoveObserver(mCFRunLoop, mCFRunLoopObserver, kCFRunLoopCommonModes);
236       ::CFRelease(mCFRunLoopObserver);
237     }
238     ::CFRelease(mCFRunLoop);
239   }
241   if (mAutoreleasePools) {
242     NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
243                  "nsAppShell destroyed without popping all autorelease pools");
244     ::CFRelease(mAutoreleasePools);
245   }
247   [mDelegate release];
249   NS_OBJC_END_TRY_IGNORE_BLOCK
252 NS_IMPL_ISUPPORTS(MacWakeLockListener, nsIDOMMozWakeLockListener)
253 mozilla::StaticRefPtr<MacWakeLockListener> sWakeLockListener;
255 static void AddScreenWakeLockListener() {
256   nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
257       do_GetService(POWERMANAGERSERVICE_CONTRACTID);
258   if (sPowerManagerService) {
259     sWakeLockListener = new MacWakeLockListener();
260     sPowerManagerService->AddWakeLockListener(sWakeLockListener);
261   } else {
262     NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
263   }
266 static void RemoveScreenWakeLockListener() {
267   nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
268       do_GetService(POWERMANAGERSERVICE_CONTRACTID);
269   if (sPowerManagerService) {
270     sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
271     sPowerManagerService = nullptr;
272     sWakeLockListener = nullptr;
273   }
276 void RunLoopObserverCallback(CFRunLoopObserverRef aObserver, CFRunLoopActivity aActivity,
277                              void* aInfo) {
278   static_cast<nsAppShell*>(aInfo)->OnRunLoopActivityChanged(aActivity);
281 void nsAppShell::OnRunLoopActivityChanged(CFRunLoopActivity aActivity) {
282 #ifdef MOZ_GECKO_PROFILER
283   // When the event loop is in its waiting state, we would like the profiler to know that the thread
284   // is idle. The usual way to notify the profiler of idleness would be to place a profiler label
285   // frame with the IDLE category on the stack, for the duration of the function that does the
286   // waiting. However, since macOS uses an event loop model where "the event loop calls you", we do
287   // not control the function that does the waiting; the waiting happens inside CFRunLoop code.
288   // Instead, the run loop notifies us when it enters and exits the waiting state, by calling this
289   // function.
290   // So we do not have a function under our control that stays on the stack for the duration of the
291   // wait. So, rather than putting an AutoProfilerLabel on the stack, we will manually push and pop
292   // the label frame here.
293   // The location in the stack where this label frame is inserted is somewhat arbitrary. In
294   // practice, the label frame will be at the very tip of the stack, looking like it's "inside" the
295   // mach_msg_trap wait function.
296   if (aActivity == kCFRunLoopBeforeWaiting) {
297     if (ProfilingStackOwner* profilingStackOwner =
298             AutoProfilerLabel::ProfilingStackOwnerTLS::Get()) {
299       mProfilingStackOwnerWhileWaiting = profilingStackOwner;
300       uint8_t variableOnStack = 0;
301       mProfilingStackOwnerWhileWaiting->ProfilingStack().pushLabelFrame(
302           "Native event loop idle", nullptr, &variableOnStack, JS::ProfilingCategoryPair::IDLE, 0);
303     }
304   } else {
305     if (mProfilingStackOwnerWhileWaiting) {
306       mProfilingStackOwnerWhileWaiting->ProfilingStack().pop();
307       mProfilingStackOwnerWhileWaiting = nullptr;
308     }
309   }
310 #endif
313 // Init
315 // Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
316 // interrupt the main native run loop.
318 // public
319 nsresult nsAppShell::Init() {
320   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
322   // No event loop is running yet (unless an embedding app that uses
323   // NSApplicationMain() is running).
324   NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
326   // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
327   // by |this|.  CFArray is used instead of NSArray because NSArray wants to
328   // retain each object you add to it, and you can't retain an
329   // NSAutoreleasePool.
330   mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
331   NS_ENSURE_STATE(mAutoreleasePools);
333   bool isNSApplicationProcessType = (XRE_GetProcessType() != GeckoProcessType_RDD) &&
334                                     (XRE_GetProcessType() != GeckoProcessType_Socket);
336   if (isNSApplicationProcessType) {
337     // This call initializes NSApplication unless:
338     // 1) we're using xre -- NSApp's already been initialized by
339     //    MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
340     // 2) an embedding app that uses NSApplicationMain() is running -- NSApp's
341     //    already been initialized and its main run loop is already running.
342     [[NSBundle mainBundle] loadNibNamed:@"res/MainMenu"
343                                   owner:[GeckoNSApplication sharedApplication]
344                         topLevelObjects:nil];
345   }
347   mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
348   NS_ENSURE_STATE(mDelegate);
350   // Add a CFRunLoopSource to the main native run loop.  The source is
351   // responsible for interrupting the run loop when Gecko events are ready.
353   mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
354   NS_ENSURE_STATE(mCFRunLoop);
355   ::CFRetain(mCFRunLoop);
357   CFRunLoopSourceContext context;
358   bzero(&context, sizeof(context));
359   // context.version = 0;
360   context.info = this;
361   context.perform = ProcessGeckoEvents;
363   mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
364   NS_ENSURE_STATE(mCFRunLoopSource);
366   ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
368   // Add a CFRunLoopObserver so that the profiler can be notified when we enter and exit the waiting
369   // state.
370   CFRunLoopObserverContext observerContext;
371   PodZero(&observerContext);
372   observerContext.info = this;
374   mCFRunLoopObserver = ::CFRunLoopObserverCreate(
375       kCFAllocatorDefault, kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting | kCFRunLoopExit, true,
376       0, RunLoopObserverCallback, &observerContext);
377   NS_ENSURE_STATE(mCFRunLoopObserver);
379   ::CFRunLoopAddObserver(mCFRunLoop, mCFRunLoopObserver, kCFRunLoopCommonModes);
381   hal::Init();
383   if (XRE_IsParentProcess()) {
384     ScreenManager& screenManager = ScreenManager::GetSingleton();
386     if (gfxPlatform::IsHeadless()) {
387       screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
388     } else {
389       screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperCocoa>());
390     }
391   }
393   nsresult rv = nsBaseAppShell::Init();
395   if (isNSApplicationProcessType && !gAppShellMethodsSwizzled) {
396     // We should only replace the original terminate: method if we're not
397     // running in a Cocoa embedder. See bug 604901.
398     if (!mRunningCocoaEmbedded) {
399       nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
400                                 @selector(nsAppShell_NSApplication_terminate:));
401     }
402     gAppShellMethodsSwizzled = true;
403   }
405 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
406   if (Preferences::GetBool("security.sandbox.mac.track.violations", false)) {
407     nsSandboxViolationSink::Start();
408   }
409 #endif
411   [localPool release];
413   return rv;
415   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
418 // ProcessGeckoEvents
420 // The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
421 // signalled from ScheduleNativeEventCallback.
423 // Arrange for Gecko events to be processed on demand (in response to a call
424 // to ScheduleNativeEventCallback(), if processing of Gecko events via "native
425 // methods" hasn't been suspended).  This happens in NativeEventCallback().
427 // protected static
428 void nsAppShell::ProcessGeckoEvents(void* aInfo) {
429   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
430   AUTO_PROFILER_LABEL("nsAppShell::ProcessGeckoEvents", OTHER);
432   nsAppShell* self = static_cast<nsAppShell*>(aInfo);
434   if (self->mRunningEventLoop) {
435     self->mRunningEventLoop = false;
437     // The run loop may be sleeping -- [NSRunLoop runMode:...]
438     // won't return until it's given a reason to wake up.  Awaken it by
439     // posting a bogus event.  There's no need to make the event
440     // presentable.
441     //
442     // But _don't_ set windowNumber to '-1' -- that can lead to nasty
443     // weirdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
444     // these fake events, because the -1 has gotten changed into the number
445     // of an actual NSWindow object, and that NSWindow object has just been
446     // destroyed).  Setting windowNumber to '0' seems to work fine -- this
447     // seems to prevent the OS from ever trying to associate our bogus event
448     // with a particular NSWindow object.
449     [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
450                                         location:NSMakePoint(0, 0)
451                                    modifierFlags:0
452                                        timestamp:0
453                                     windowNumber:0
454                                          context:NULL
455                                          subtype:kEventSubtypeNone
456                                            data1:0
457                                            data2:0]
458              atStart:NO];
459   }
461   if (self->mSuspendNativeCount <= 0) {
462     ++self->mNativeEventCallbackDepth;
463     self->NativeEventCallback();
464     --self->mNativeEventCallbackDepth;
465   } else {
466     self->mSkippedNativeCallback = true;
467   }
469   // Still needed to avoid crashes on quit in most Mochitests.
470   [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
471                                       location:NSMakePoint(0, 0)
472                                  modifierFlags:0
473                                      timestamp:0
474                                   windowNumber:0
475                                        context:NULL
476                                        subtype:kEventSubtypeNone
477                                          data1:0
478                                          data2:0]
479            atStart:NO];
481   // Normally every call to ScheduleNativeEventCallback() results in
482   // exactly one call to ProcessGeckoEvents().  So each Release() here
483   // normally balances exactly one AddRef() in ScheduleNativeEventCallback().
484   // But if Exit() is called just after ScheduleNativeEventCallback(), the
485   // corresponding call to ProcessGeckoEvents() will never happen.  We check
486   // for this possibility in two different places -- here and in Exit()
487   // itself.  If we find here that Exit() has been called (that mTerminated
488   // is true), it's because we've been called recursively, that Exit() was
489   // called from self->NativeEventCallback() above, and that we're unwinding
490   // the recursion.  In this case we'll never be called again, and we balance
491   // here any extra calls to ScheduleNativeEventCallback().
492   //
493   // When ProcessGeckoEvents() is called recursively, it's because of a
494   // call to ScheduleNativeEventCallback() from NativeEventCallback().  We
495   // balance the "extra" AddRefs here (rather than always in Exit()) in order
496   // to ensure that 'self' stays alive until the end of this method.  We also
497   // make sure not to finish the balancing until all the recursion has been
498   // unwound.
499   if (self->mTerminated) {
500     int32_t releaseCount = 0;
501     if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) {
502       releaseCount =
503           PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, self->mNativeEventCallbackDepth);
504     }
505     while (releaseCount-- > self->mNativeEventCallbackDepth) self->Release();
506   } else {
507     // As best we can tell, every call to ProcessGeckoEvents() is triggered
508     // by a call to ScheduleNativeEventCallback().  But we've seen a few
509     // (non-reproducible) cases of double-frees that *might* have been caused
510     // by spontaneous calls (from the OS) to ProcessGeckoEvents().  So we
511     // deal with that possibility here.
512     if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) {
513       PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0);
514       NS_WARNING("Spontaneous call to ProcessGeckoEvents()!");
515     } else {
516       self->Release();
517     }
518   }
520   NS_OBJC_END_TRY_IGNORE_BLOCK;
523 // WillTerminate
525 // Called by the AppShellDelegate when an NSApplicationWillTerminate
526 // notification is posted.  After this method is called, native events should
527 // no longer be processed.  The NSApplicationWillTerminate notification is
528 // only posted when [NSApp terminate:] is called, which doesn't happen on a
529 // "normal" application quit.
531 // public
532 void nsAppShell::WillTerminate() {
533   if (mTerminated) return;
535   // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
536   // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
537   NS_ProcessPendingEvents(NS_GetCurrentThread());
539   mTerminated = true;
542 // ScheduleNativeEventCallback
544 // Called (possibly on a non-main thread) when Gecko has an event that
545 // needs to be processed.  The Gecko event needs to be processed on the
546 // main thread, so the native run loop must be interrupted.
548 // In nsBaseAppShell.cpp, the mNativeEventPending variable is used to
549 // ensure that ScheduleNativeEventCallback() is called no more than once
550 // per call to NativeEventCallback().  ProcessGeckoEvents() can skip its
551 // call to NativeEventCallback() if processing of Gecko events by native
552 // means is suspended (using nsIAppShell::SuspendNative()), which will
553 // suspend calls from nsBaseAppShell::OnDispatchedEvent() to
554 // ScheduleNativeEventCallback().  But when Gecko event processing by
555 // native means is resumed (in ResumeNative()), an extra call is made to
556 // ScheduleNativeEventCallback() (from ResumeNative()).  This triggers
557 // another call to ProcessGeckoEvents(), which calls NativeEventCallback(),
558 // and nsBaseAppShell::OnDispatchedEvent() resumes calling
559 // ScheduleNativeEventCallback().
561 // protected virtual
562 void nsAppShell::ScheduleNativeEventCallback() {
563   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
565   if (mTerminated) return;
567   // Each AddRef() here is normally balanced by exactly one Release() in
568   // ProcessGeckoEvents().  But there are exceptions, for which see
569   // ProcessGeckoEvents() and Exit().
570   NS_ADDREF_THIS();
571   PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth);
573   // This will invoke ProcessGeckoEvents on the main thread.
574   ::CFRunLoopSourceSignal(mCFRunLoopSource);
575   ::CFRunLoopWakeUp(mCFRunLoop);
577   NS_OBJC_END_TRY_IGNORE_BLOCK;
580 // Undocumented Cocoa Event Manager function, present in the same form since
581 // at least OS X 10.6.
582 extern "C" EventAttributes GetEventAttributes(EventRef inEvent);
584 // ProcessNextNativeEvent
586 // If aMayWait is false, process a single native event.  If it is true, run
587 // the native run loop until stopped by ProcessGeckoEvents.
589 // Returns true if more events are waiting in the native event queue.
591 // protected virtual
592 bool nsAppShell::ProcessNextNativeEvent(bool aMayWait) {
593   bool moreEvents = false;
595   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
597   bool eventProcessed = false;
598   NSString* currentMode = nil;
600   if (mTerminated) return false;
602   bool wasRunningEventLoop = mRunningEventLoop;
603   mRunningEventLoop = aMayWait;
604   NSDate* waitUntil = nil;
605   if (aMayWait) waitUntil = [NSDate distantFuture];
607   NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
609   EventQueueRef currentEventQueue = GetCurrentEventQueue();
610   EventTargetRef eventDispatcherTarget = GetEventDispatcherTarget();
612   if (aMayWait) {
613     mozilla::BackgroundHangMonitor().NotifyWait();
614   }
616   // Only call -[NSApp sendEvent:] (and indirectly send user-input events to
617   // Gecko) if aMayWait is true.  Tbis ensures most calls to -[NSApp
618   // sendEvent:] happen under nsAppShell::Run(), at the lowest level of
619   // recursion -- thereby making it less likely Gecko will process user-input
620   // events in the wrong order or skip some of them.  It also avoids eating
621   // too much CPU in nsBaseAppShell::OnProcessNextEvent() (which calls
622   // us) -- thereby avoiding the starvation of nsIRunnable events in
623   // nsThread::ProcessNextEvent().  For more information see bug 996848.
624   do {
625     // No autorelease pool is provided here, because OnProcessNextEvent
626     // and AfterProcessNextEvent are responsible for maintaining it.
627     NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
628                  "No autorelease pool for native event");
630     if (aMayWait) {
631       currentMode = [currentRunLoop currentMode];
632       if (!currentMode) currentMode = NSDefaultRunLoopMode;
633       NSEvent* nextEvent = [NSApp nextEventMatchingMask:NSEventMaskAny
634                                               untilDate:waitUntil
635                                                  inMode:currentMode
636                                                 dequeue:YES];
637       if (nextEvent) {
638         mozilla::BackgroundHangMonitor().NotifyActivity();
639         [NSApp sendEvent:nextEvent];
640         eventProcessed = true;
641       }
642     } else {
643       // AcquireFirstMatchingEventInQueue() doesn't spin the (native) event
644       // loop, though it does queue up any newly available events from the
645       // window server.
646       EventRef currentEvent =
647           AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL, kEventQueueOptionsNone);
648       if (!currentEvent) {
649         continue;
650       }
651       EventAttributes attrs = GetEventAttributes(currentEvent);
652       UInt32 eventKind = GetEventKind(currentEvent);
653       UInt32 eventClass = GetEventClass(currentEvent);
654       bool osCocoaEvent =
655           ((eventClass == 'appl') || (eventClass == kEventClassAppleEvent) ||
656            ((eventClass == 'cgs ') && (eventKind != NSEventTypeApplicationDefined)));
657       // If attrs is kEventAttributeUserEvent or kEventAttributeMonitored
658       // (i.e. a user input event), we shouldn't process it here while
659       // aMayWait is false.  Likewise if currentEvent will eventually be
660       // turned into an OS-defined Cocoa event, or otherwise needs AppKit
661       // processing.  Doing otherwise risks doing too much work here, and
662       // preventing the event from being properly processed by the AppKit
663       // framework.
664       if ((attrs != kEventAttributeNone) || osCocoaEvent) {
665         // Since we can't process the next event here (while aMayWait is false),
666         // we want moreEvents to be false on return.
667         eventProcessed = false;
668         // This call to ReleaseEvent() matches a call to RetainEvent() in
669         // AcquireFirstMatchingEventInQueue() above.
670         ReleaseEvent(currentEvent);
671         break;
672       }
673       // This call to RetainEvent() matches a call to ReleaseEvent() in
674       // RemoveEventFromQueue() below.
675       RetainEvent(currentEvent);
676       RemoveEventFromQueue(currentEventQueue, currentEvent);
677       SendEventToEventTarget(currentEvent, eventDispatcherTarget);
678       // This call to ReleaseEvent() matches a call to RetainEvent() in
679       // AcquireFirstMatchingEventInQueue() above.
680       ReleaseEvent(currentEvent);
681       eventProcessed = true;
682     }
683   } while (mRunningEventLoop);
685   if (eventProcessed) {
686     moreEvents = (AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
687                                                    kEventQueueOptionsNone) != NULL);
688   }
690   mRunningEventLoop = wasRunningEventLoop;
692   NS_OBJC_END_TRY_IGNORE_BLOCK;
694   if (!moreEvents) {
695     nsChildView::UpdateCurrentInputEventCount();
696   }
698   return moreEvents;
701 // Run
703 // Overrides the base class's Run() method to call [NSApp run] (which spins
704 // the native run loop until the application quits).  Since (unlike the base
705 // class's Run() method) we don't process any Gecko events here, they need
706 // to be processed elsewhere (in NativeEventCallback(), called from
707 // ProcessGeckoEvents()).
709 // Camino called [NSApp run] on its own (via NSApplicationMain()), and so
710 // didn't call nsAppShell::Run().
712 // public
713 NS_IMETHODIMP
714 nsAppShell::Run(void) {
715   NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
716   if (mStarted || mTerminated) return NS_OK;
718   mStarted = true;
720   if (XRE_IsParentProcess()) {
721     AddScreenWakeLockListener();
722   }
724   // We use the native Gecko event loop in content processes.
725   nsresult rv = NS_OK;
726   if (XRE_UseNativeEventProcessing()) {
727     NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
728     [NSApp run];
729     NS_OBJC_END_TRY_IGNORE_BLOCK;
730   } else {
731     rv = nsBaseAppShell::Run();
732   }
734   if (XRE_IsParentProcess()) {
735     RemoveScreenWakeLockListener();
736   }
738   return rv;
741 NS_IMETHODIMP
742 nsAppShell::Exit(void) {
743   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
745   // This method is currently called more than once -- from (according to
746   // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
747   // XPCOM shutdown notification that nsBaseAppShell has registered to
748   // receive.  So we need to ensure that multiple calls won't break anything.
749   // But we should also complain about it (since it isn't quite kosher).
750   if (mTerminated) {
751     NS_WARNING("nsAppShell::Exit() called redundantly");
752     return NS_OK;
753   }
755   mTerminated = true;
757 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
758   nsSandboxViolationSink::Stop();
759 #endif
761   // Quoting from Apple's doc on the [NSApplication stop:] method (from their
762   // doc on the NSApplication class):  "If this method is invoked during a
763   // modal event loop, it will break that loop but not the main event loop."
764   // nsAppShell::Exit() shouldn't be called from a modal event loop.  So if
765   // it is we complain about it (to users of debug builds) and call [NSApp
766   // stop:] one extra time.  (I'm not sure if modal event loops can be nested
767   // -- Apple's docs don't say one way or the other.  But the return value
768   // of [NSApp _isRunningModal] doesn't change immediately after a call to
769   // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
770   // will do the job.)
771   BOOL cocoaModal = [NSApp _isRunningModal];
772   NS_ASSERTION(!cocoaModal, "Don't call nsAppShell::Exit() from a modal event loop!");
773   if (cocoaModal) [NSApp stop:nullptr];
774   [NSApp stop:nullptr];
776   // A call to Exit() just after a call to ScheduleNativeEventCallback()
777   // prevents the (normally) matching call to ProcessGeckoEvents() from
778   // happening.  If we've been called from ProcessGeckoEvents() (as usually
779   // happens), we take care of it there.  But if we have an unbalanced call
780   // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
781   // stack, we need to take care of the problem here.
782   if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
783     int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
784     while (releaseCount-- > 0) NS_RELEASE_THIS();
785   }
787   return nsBaseAppShell::Exit();
789   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
792 // OnProcessNextEvent
794 // This nsIThreadObserver method is called prior to processing an event.
795 // Set up an autorelease pool that will service any autoreleased Cocoa
796 // objects during this event.  This includes native events processed by
797 // ProcessNextNativeEvent.  The autorelease pool will be popped by
798 // AfterProcessNextEvent, it is important for these two methods to be
799 // tightly coupled.
801 // public
802 NS_IMETHODIMP
803 nsAppShell::OnProcessNextEvent(nsIThreadInternal* aThread, bool aMayWait) {
804   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
806   NS_ASSERTION(mAutoreleasePools, "No stack on which to store autorelease pool");
808   NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
809   ::CFArrayAppendValue(mAutoreleasePools, pool);
811   return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait);
813   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
816 // AfterProcessNextEvent
818 // This nsIThreadObserver method is called after event processing is complete.
819 // The Cocoa implementation cleans up the autorelease pool create by the
820 // previous OnProcessNextEvent call.
822 // public
823 NS_IMETHODIMP
824 nsAppShell::AfterProcessNextEvent(nsIThreadInternal* aThread, bool aEventWasProcessed) {
825   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
827   CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
829   NS_ASSERTION(mAutoreleasePools && count, "Processed an event, but there's no autorelease pool?");
831   const NSAutoreleasePool* pool =
832       static_cast<const NSAutoreleasePool*>(::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
833   ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
834   [pool release];
836   return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed);
838   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
841 // AppShellDelegate implementation
843 @implementation AppShellDelegate
844 // initWithAppShell:
846 // Constructs the AppShellDelegate object
847 - (id)initWithAppShell:(nsAppShell*)aAppShell {
848   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
850   if ((self = [self init])) {
851     mAppShell = aAppShell;
853     [[NSNotificationCenter defaultCenter] addObserver:self
854                                              selector:@selector(applicationWillTerminate:)
855                                                  name:NSApplicationWillTerminateNotification
856                                                object:NSApp];
857     [[NSNotificationCenter defaultCenter] addObserver:self
858                                              selector:@selector(applicationDidBecomeActive:)
859                                                  name:NSApplicationDidBecomeActiveNotification
860                                                object:NSApp];
861   }
863   return self;
865   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
868 - (void)dealloc {
869   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
871   [[NSNotificationCenter defaultCenter] removeObserver:self];
872   [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
873   [super dealloc];
875   NS_OBJC_END_TRY_IGNORE_BLOCK;
878 // applicationWillTerminate:
880 // Notify the nsAppShell that native event processing should be discontinued.
881 - (void)applicationWillTerminate:(NSNotification*)aNotification {
882   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
884   mAppShell->WillTerminate();
886   NS_OBJC_END_TRY_IGNORE_BLOCK;
889 // applicationDidBecomeActive
891 // Make sure TextInputHandler::sLastModifierState is updated when we become
892 // active (since we won't have received [ChildView flagsChanged:] messages
893 // while inactive).
894 - (void)applicationDidBecomeActive:(NSNotification*)aNotification {
895   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
897   // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
898   // to worry about getting an NSInternalInconsistencyException here.
899   NSEvent* currentEvent = [NSApp currentEvent];
900   if (currentEvent) {
901     TextInputHandler::sLastModifierState =
902         [currentEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
903   }
905   nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
906   if (observerService) {
907     observerService->NotifyObservers(nullptr, NS_WIDGET_MAC_APP_ACTIVATE_OBSERVER_TOPIC, nullptr);
908   }
910   NS_OBJC_END_TRY_IGNORE_BLOCK;
913 @end
915 // We hook terminate: in order to make OS-initiated termination work nicely
916 // with Gecko's shutdown sequence.  (Two ways to trigger OS-initiated
917 // termination:  1) Quit from the Dock menu; 2) Log out from (or shut down)
918 // your computer while the browser is active.)
919 @interface NSApplication (MethodSwizzling)
920 - (void)nsAppShell_NSApplication_terminate:(id)sender;
921 @end
923 @implementation NSApplication (MethodSwizzling)
925 // Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
926 // has returned NSTerminateNow.  This method "subclasses" and replaces the
927 // OS's original implementation.  The only thing the orginal method does which
928 // we need is that it posts NSApplicationWillTerminateNotification.  Everything
929 // else is unneeded (because it's handled elsewhere), or actively interferes
930 // with Gecko's shutdown sequence.  For example the original terminate: method
931 // causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
932 // above), which means that nothing runs after the call to nsAppStartup::Run()
933 // in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
934 // and NS_ShutdownXPCOM() never get called.
935 - (void)nsAppShell_NSApplication_terminate:(id)sender {
936   [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification
937                                                       object:NSApp];
940 @end