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/. */
7 * Runs the main native Cocoa run loop, interrupting it as needed to process
11 #import <Cocoa/Cocoa.h>
13 #include "CustomCocoaEvents.h"
14 #include "mozilla/WidgetTraceEvent.h"
15 #include "nsAppShell.h"
18 #include "nsDirectoryServiceDefs.h"
20 #include "nsIRollupListener.h"
21 #include "nsIWidget.h"
22 #include "nsThreadUtils.h"
23 #include "nsIWindowMediator.h"
24 #include "nsServiceManagerUtils.h"
25 #include "nsIInterfaceRequestor.h"
26 #include "nsIWebBrowserChrome.h"
27 #include "nsObjCExceptions.h"
28 #include "nsCocoaFeatures.h"
29 #include "nsCocoaUtils.h"
30 #include "nsChildView.h"
31 #include "nsToolkit.h"
32 #include "TextInputHandler.h"
33 #include "mozilla/HangMonitor.h"
34 #include "GeckoProfiler.h"
37 #include <IOKit/pwr_mgt/IOPMLib.h>
38 #include "nsIDOMWakeLockListener.h"
39 #include "nsIPowerManagerService.h"
41 using namespace mozilla::widget;
43 // A wake lock listener that disables screen saver when requested by
44 // Gecko. For example when we're playing video in a foreground tab we
45 // don't want the screen saver to turn on.
47 class MacWakeLockListener MOZ_FINAL : public nsIDOMMozWakeLockListener {
52 ~MacWakeLockListener() {}
54 IOPMAssertionID mAssertionID = kIOPMNullAssertionID;
56 NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) {
57 if (!aTopic.EqualsASCII("screen")) {
60 // Note the wake lock code ensures that we're not sent duplicate
61 // "locked-foreground" notifications when multiple wake locks are held.
62 if (aState.EqualsASCII("locked-foreground")) {
63 // Prevent screen saver.
64 CFStringRef cf_topic =
65 ::CFStringCreateWithCharacters(kCFAllocatorDefault,
66 reinterpret_cast<const UniChar*>
70 ::IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
71 kIOPMAssertionLevelOn,
75 if (success != kIOReturnSuccess) {
76 NS_WARNING("failed to disable screensaver");
79 // Re-enable screen saver.
80 NS_WARNING("Releasing screensaver");
81 if (mAssertionID != kIOPMNullAssertionID) {
82 IOReturn result = ::IOPMAssertionRelease(mAssertionID);
83 if (result != kIOReturnSuccess) {
84 NS_WARNING("failed to release screensaver");
92 // defined in nsCocoaWindow.mm
93 extern int32_t gXULModalLevel;
95 static bool gAppShellMethodsSwizzled = false;
97 @implementation GeckoNSApplication
99 - (void)sendEvent:(NSEvent *)anEvent
101 mozilla::HangMonitor::NotifyActivity();
102 if ([anEvent type] == NSApplicationDefined &&
103 [anEvent subtype] == kEventSubtypeTrace) {
104 mozilla::SignalTracerThread();
107 [super sendEvent:anEvent];
110 - (NSEvent*)nextEventMatchingMask:(NSUInteger)mask
111 untilDate:(NSDate*)expiration
112 inMode:(NSString*)mode
116 mozilla::HangMonitor::Suspend();
118 return [super nextEventMatchingMask:mask
119 untilDate:expiration inMode:mode dequeue:flag];
127 // Cocoa bridge class. An object of this class is registered to receive
130 @interface AppShellDelegate : NSObject
133 nsAppShell* mAppShell;
136 - (id)initWithAppShell:(nsAppShell*)aAppShell;
137 - (void)applicationWillTerminate:(NSNotification*)aNotification;
138 - (void)beginMenuTracking:(NSNotification*)aNotification;
141 // nsAppShell implementation
144 nsAppShell::ResumeNative(void)
146 nsresult retval = nsBaseAppShell::ResumeNative();
147 if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) &&
148 mSkippedNativeCallback)
150 mSkippedNativeCallback = false;
151 ScheduleNativeEventCallback();
156 nsAppShell::nsAppShell()
157 : mAutoreleasePools(nullptr)
160 , mCFRunLoopSource(NULL)
161 , mRunningEventLoop(false)
164 , mSkippedNativeCallback(false)
165 , mNativeEventCallbackDepth(0)
166 , mNativeEventScheduledDepth(0)
168 // A Cocoa event loop is running here if (and only if) we've been embedded
169 // by a Cocoa app (like Camino).
170 mRunningCocoaEmbedded = [NSApp isRunning] ? true : false;
173 nsAppShell::~nsAppShell()
175 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
178 if (mCFRunLoopSource) {
179 ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
180 kCFRunLoopCommonModes);
181 ::CFRelease(mCFRunLoopSource);
183 ::CFRelease(mCFRunLoop);
186 if (mAutoreleasePools) {
187 NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
188 "nsAppShell destroyed without popping all autorelease pools");
189 ::CFRelease(mAutoreleasePools);
194 NS_OBJC_END_TRY_ABORT_BLOCK
197 NS_IMPL_ISUPPORTS(MacWakeLockListener, nsIDOMMozWakeLockListener)
198 mozilla::StaticRefPtr<MacWakeLockListener> sWakeLockListener;
201 AddScreenWakeLockListener()
203 nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(
204 POWERMANAGERSERVICE_CONTRACTID);
205 if (sPowerManagerService) {
206 sWakeLockListener = new MacWakeLockListener();
207 sPowerManagerService->AddWakeLockListener(sWakeLockListener);
209 NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
214 RemoveScreenWakeLockListener()
216 nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(
217 POWERMANAGERSERVICE_CONTRACTID);
218 if (sPowerManagerService) {
219 sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
220 sPowerManagerService = nullptr;
221 sWakeLockListener = nullptr;
225 // An undocumented CoreGraphics framework method, present in the same form
226 // since at least OS X 10.5.
227 extern "C" CGError CGSSetDebugOptions(int options);
231 // Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
232 // interrupt the main native run loop.
238 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
240 // No event loop is running yet (unless Camino is running, or another
241 // embedding app that uses NSApplicationMain()).
242 NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
244 // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
245 // by |this|. CFArray is used instead of NSArray because NSArray wants to
246 // retain each object you add to it, and you can't retain an
247 // NSAutoreleasePool.
248 mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
249 NS_ENSURE_STATE(mAutoreleasePools);
251 // Get the path of the nib file, which lives in the GRE location
252 nsCOMPtr<nsIFile> nibFile;
253 nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(nibFile));
254 NS_ENSURE_SUCCESS(rv, rv);
256 nibFile->AppendNative(NS_LITERAL_CSTRING("res"));
257 nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib"));
259 nsAutoCString nibPath;
260 rv = nibFile->GetNativePath(nibPath);
261 NS_ENSURE_SUCCESS(rv, rv);
263 // This call initializes NSApplication unless:
264 // 1) we're using xre -- NSApp's already been initialized by
265 // MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
266 // 2) Camino is running (or another embedding app that uses
267 // NSApplicationMain()) -- NSApp's already been initialized and
268 // its main run loop is already running.
269 [NSBundle loadNibFile:
270 [NSString stringWithUTF8String:(const char*)nibPath.get()]
272 [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication]
274 withZone:NSDefaultMallocZone()];
276 mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
277 NS_ENSURE_STATE(mDelegate);
279 // Add a CFRunLoopSource to the main native run loop. The source is
280 // responsible for interrupting the run loop when Gecko events are ready.
282 mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
283 NS_ENSURE_STATE(mCFRunLoop);
284 ::CFRetain(mCFRunLoop);
286 CFRunLoopSourceContext context;
287 bzero(&context, sizeof(context));
288 // context.version = 0;
290 context.perform = ProcessGeckoEvents;
292 mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
293 NS_ENSURE_STATE(mCFRunLoopSource);
295 ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
297 rv = nsBaseAppShell::Init();
300 TextInputHandler::InstallPluginKeyEventsHandler();
303 if (!gAppShellMethodsSwizzled) {
304 // We should only replace the original terminate: method if we're not
305 // running in a Cocoa embedder (like Camino). See bug 604901.
306 if (!mRunningCocoaEmbedded) {
307 nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
308 @selector(nsAppShell_NSApplication_terminate:));
310 gAppShellMethodsSwizzled = true;
313 if (nsCocoaFeatures::OnYosemiteOrLater()) {
314 // Explicitly turn off CGEvent logging. This works around bug 1092855.
315 // If there are already CGEvents in the log, turning off logging also
316 // causes those events to be written to disk. But at this point no
317 // CGEvents have yet been processed. CGEvents are events (usually
318 // input events) pulled from the WindowServer. An option of 0x80000008
319 // turns on CGEvent logging.
320 CGSSetDebugOptions(0x80000007);
327 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
330 // ProcessGeckoEvents
332 // The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
333 // signalled from ScheduleNativeEventCallback.
335 // Arrange for Gecko events to be processed on demand (in response to a call
336 // to ScheduleNativeEventCallback(), if processing of Gecko events via "native
337 // methods" hasn't been suspended). This happens in NativeEventCallback().
341 nsAppShell::ProcessGeckoEvents(void* aInfo)
343 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
344 PROFILER_LABEL("Events", "ProcessGeckoEvents",
345 js::ProfileEntry::Category::EVENTS);
347 nsAppShell* self = static_cast<nsAppShell*> (aInfo);
349 if (self->mRunningEventLoop) {
350 self->mRunningEventLoop = false;
352 // The run loop may be sleeping -- [NSRunLoop runMode:...]
353 // won't return until it's given a reason to wake up. Awaken it by
354 // posting a bogus event. There's no need to make the event
357 // But _don't_ set windowNumber to '-1' -- that can lead to nasty
358 // wierdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
359 // these fake events, because the -1 has gotten changed into the number
360 // of an actual NSWindow object, and that NSWindow object has just been
361 // destroyed). Setting windowNumber to '0' seems to work fine -- this
362 // seems to prevent the OS from ever trying to associate our bogus event
363 // with a particular NSWindow object.
364 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
365 location:NSMakePoint(0,0)
370 subtype:kEventSubtypeNone
376 if (self->mSuspendNativeCount <= 0) {
377 ++self->mNativeEventCallbackDepth;
378 self->NativeEventCallback();
379 --self->mNativeEventCallbackDepth;
381 self->mSkippedNativeCallback = true;
384 // Still needed to avoid crashes on quit in most Mochitests.
385 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
386 location:NSMakePoint(0,0)
391 subtype:kEventSubtypeNone
396 // Normally every call to ScheduleNativeEventCallback() results in
397 // exactly one call to ProcessGeckoEvents(). So each Release() here
398 // normally balances exactly one AddRef() in ScheduleNativeEventCallback().
399 // But if Exit() is called just after ScheduleNativeEventCallback(), the
400 // corresponding call to ProcessGeckoEvents() will never happen. We check
401 // for this possibility in two different places -- here and in Exit()
402 // itself. If we find here that Exit() has been called (that mTerminated
403 // is true), it's because we've been called recursively, that Exit() was
404 // called from self->NativeEventCallback() above, and that we're unwinding
405 // the recursion. In this case we'll never be called again, and we balance
406 // here any extra calls to ScheduleNativeEventCallback().
408 // When ProcessGeckoEvents() is called recursively, it's because of a
409 // call to ScheduleNativeEventCallback() from NativeEventCallback(). We
410 // balance the "extra" AddRefs here (rather than always in Exit()) in order
411 // to ensure that 'self' stays alive until the end of this method. We also
412 // make sure not to finish the balancing until all the recursion has been
414 if (self->mTerminated) {
415 int32_t releaseCount = 0;
416 if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) {
417 releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth,
418 self->mNativeEventCallbackDepth);
420 while (releaseCount-- > self->mNativeEventCallbackDepth)
423 // As best we can tell, every call to ProcessGeckoEvents() is triggered
424 // by a call to ScheduleNativeEventCallback(). But we've seen a few
425 // (non-reproducible) cases of double-frees that *might* have been caused
426 // by spontaneous calls (from the OS) to ProcessGeckoEvents(). So we
427 // deal with that possibility here.
428 if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) {
429 PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0);
430 NS_WARNING("Spontaneous call to ProcessGeckoEvents()!");
436 NS_OBJC_END_TRY_ABORT_BLOCK;
441 // Called by the AppShellDelegate when an NSApplicationWillTerminate
442 // notification is posted. After this method is called, native events should
443 // no longer be processed. The NSApplicationWillTerminate notification is
444 // only posted when [NSApp terminate:] is called, which doesn't happen on a
445 // "normal" application quit.
449 nsAppShell::WillTerminate()
454 // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
455 // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
456 NS_ProcessPendingEvents(NS_GetCurrentThread());
461 // ScheduleNativeEventCallback
463 // Called (possibly on a non-main thread) when Gecko has an event that
464 // needs to be processed. The Gecko event needs to be processed on the
465 // main thread, so the native run loop must be interrupted.
467 // In nsBaseAppShell.cpp, the mNativeEventPending variable is used to
468 // ensure that ScheduleNativeEventCallback() is called no more than once
469 // per call to NativeEventCallback(). ProcessGeckoEvents() can skip its
470 // call to NativeEventCallback() if processing of Gecko events by native
471 // means is suspended (using nsIAppShell::SuspendNative()), which will
472 // suspend calls from nsBaseAppShell::OnDispatchedEvent() to
473 // ScheduleNativeEventCallback(). But when Gecko event processing by
474 // native means is resumed (in ResumeNative()), an extra call is made to
475 // ScheduleNativeEventCallback() (from ResumeNative()). This triggers
476 // another call to ProcessGeckoEvents(), which calls NativeEventCallback(),
477 // and nsBaseAppShell::OnDispatchedEvent() resumes calling
478 // ScheduleNativeEventCallback().
482 nsAppShell::ScheduleNativeEventCallback()
484 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
489 // Each AddRef() here is normally balanced by exactly one Release() in
490 // ProcessGeckoEvents(). But there are exceptions, for which see
491 // ProcessGeckoEvents() and Exit().
493 PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth);
495 // This will invoke ProcessGeckoEvents on the main thread.
496 ::CFRunLoopSourceSignal(mCFRunLoopSource);
497 ::CFRunLoopWakeUp(mCFRunLoop);
499 NS_OBJC_END_TRY_ABORT_BLOCK;
502 // Undocumented Cocoa Event Manager function, present in the same form since
503 // at least OS X 10.6.
504 extern "C" EventAttributes GetEventAttributes(EventRef inEvent);
506 // ProcessNextNativeEvent
508 // If aMayWait is false, process a single native event. If it is true, run
509 // the native run loop until stopped by ProcessGeckoEvents.
511 // Returns true if more events are waiting in the native event queue.
515 nsAppShell::ProcessNextNativeEvent(bool aMayWait)
517 bool moreEvents = false;
519 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
521 bool eventProcessed = false;
522 NSString* currentMode = nil;
527 bool wasRunningEventLoop = mRunningEventLoop;
528 mRunningEventLoop = aMayWait;
529 NSDate* waitUntil = nil;
531 waitUntil = [NSDate distantFuture];
533 NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
535 EventQueueRef currentEventQueue = GetCurrentEventQueue();
536 EventTargetRef eventDispatcherTarget = GetEventDispatcherTarget();
539 mozilla::HangMonitor::Suspend();
542 // Only call -[NSApp sendEvent:] (and indirectly send user-input events to
543 // Gecko) if aMayWait is true. Tbis ensures most calls to -[NSApp
544 // sendEvent:] happen under nsAppShell::Run(), at the lowest level of
545 // recursion -- thereby making it less likely Gecko will process user-input
546 // events in the wrong order or skip some of them. It also avoids eating
547 // too much CPU in nsBaseAppShell::OnProcessNextEvent() (which calls
548 // us) -- thereby avoiding the starvation of nsIRunnable events in
549 // nsThread::ProcessNextEvent(). For more information see bug 996848.
551 // No autorelease pool is provided here, because OnProcessNextEvent
552 // and AfterProcessNextEvent are responsible for maintaining it.
553 NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
554 "No autorelease pool for native event");
557 currentMode = [currentRunLoop currentMode];
559 currentMode = NSDefaultRunLoopMode;
560 NSEvent *nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
565 mozilla::HangMonitor::NotifyActivity();
566 [NSApp sendEvent:nextEvent];
567 eventProcessed = true;
570 // AcquireFirstMatchingEventInQueue() doesn't spin the (native) event
571 // loop, though it does queue up any newly available events from the
573 EventRef currentEvent = AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
574 kEventQueueOptionsNone);
578 EventAttributes attrs = GetEventAttributes(currentEvent);
579 UInt32 eventKind = GetEventKind(currentEvent);
580 UInt32 eventClass = GetEventClass(currentEvent);
582 ((eventClass == 'appl') || (eventClass == kEventClassAppleEvent) ||
583 ((eventClass == 'cgs ') && (eventKind != NSApplicationDefined)));
584 // If attrs is kEventAttributeUserEvent or kEventAttributeMonitored
585 // (i.e. a user input event), we shouldn't process it here while
586 // aMayWait is false. Likewise if currentEvent will eventually be
587 // turned into an OS-defined Cocoa event, or otherwise needs AppKit
588 // processing. Doing otherwise risks doing too much work here, and
589 // preventing the event from being properly processed by the AppKit
591 if ((attrs != kEventAttributeNone) || osCocoaEvent) {
592 // Since we can't process the next event here (while aMayWait is false),
593 // we want moreEvents to be false on return.
594 eventProcessed = false;
595 // This call to ReleaseEvent() matches a call to RetainEvent() in
596 // AcquireFirstMatchingEventInQueue() above.
597 ReleaseEvent(currentEvent);
600 // This call to RetainEvent() matches a call to ReleaseEvent() in
601 // RemoveEventFromQueue() below.
602 RetainEvent(currentEvent);
603 RemoveEventFromQueue(currentEventQueue, currentEvent);
604 SendEventToEventTarget(currentEvent, eventDispatcherTarget);
605 // This call to ReleaseEvent() matches a call to RetainEvent() in
606 // AcquireFirstMatchingEventInQueue() above.
607 ReleaseEvent(currentEvent);
608 eventProcessed = true;
610 } while (mRunningEventLoop);
612 if (eventProcessed) {
614 (AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
615 kEventQueueOptionsNone) != NULL);
618 mRunningEventLoop = wasRunningEventLoop;
620 NS_OBJC_END_TRY_ABORT_BLOCK;
623 nsChildView::UpdateCurrentInputEventCount();
631 // Overrides the base class's Run() method to call [NSApp run] (which spins
632 // the native run loop until the application quits). Since (unlike the base
633 // class's Run() method) we don't process any Gecko events here, they need
634 // to be processed elsewhere (in NativeEventCallback(), called from
635 // ProcessGeckoEvents()).
637 // Camino calls [NSApp run] on its own (via NSApplicationMain()), and so
638 // doesn't call nsAppShell::Run().
642 nsAppShell::Run(void)
644 NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
645 if (mStarted || mTerminated)
650 AddScreenWakeLockListener();
652 NS_OBJC_TRY_ABORT([NSApp run]);
654 RemoveScreenWakeLockListener();
660 nsAppShell::Exit(void)
662 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
664 // This method is currently called more than once -- from (according to
665 // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
666 // XPCOM shutdown notification that nsBaseAppShell has registered to
667 // receive. So we need to ensure that multiple calls won't break anything.
668 // But we should also complain about it (since it isn't quite kosher).
670 NS_WARNING("nsAppShell::Exit() called redundantly");
677 TextInputHandler::RemovePluginKeyEventsHandler();
680 // Quoting from Apple's doc on the [NSApplication stop:] method (from their
681 // doc on the NSApplication class): "If this method is invoked during a
682 // modal event loop, it will break that loop but not the main event loop."
683 // nsAppShell::Exit() shouldn't be called from a modal event loop. So if
684 // it is we complain about it (to users of debug builds) and call [NSApp
685 // stop:] one extra time. (I'm not sure if modal event loops can be nested
686 // -- Apple's docs don't say one way or the other. But the return value
687 // of [NSApp _isRunningModal] doesn't change immediately after a call to
688 // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
690 BOOL cocoaModal = [NSApp _isRunningModal];
691 NS_ASSERTION(!cocoaModal,
692 "Don't call nsAppShell::Exit() from a modal event loop!");
694 [NSApp stop:nullptr];
695 [NSApp stop:nullptr];
697 // A call to Exit() just after a call to ScheduleNativeEventCallback()
698 // prevents the (normally) matching call to ProcessGeckoEvents() from
699 // happening. If we've been called from ProcessGeckoEvents() (as usually
700 // happens), we take care of it there. But if we have an unbalanced call
701 // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
702 // stack, we need to take care of the problem here.
703 if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
704 int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
705 while (releaseCount-- > 0)
709 return nsBaseAppShell::Exit();
711 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
714 // OnProcessNextEvent
716 // This nsIThreadObserver method is called prior to processing an event.
717 // Set up an autorelease pool that will service any autoreleased Cocoa
718 // objects during this event. This includes native events processed by
719 // ProcessNextNativeEvent. The autorelease pool will be popped by
720 // AfterProcessNextEvent, it is important for these two methods to be
725 nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
726 uint32_t aRecursionDepth)
728 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
730 NS_ASSERTION(mAutoreleasePools,
731 "No stack on which to store autorelease pool");
733 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
734 ::CFArrayAppendValue(mAutoreleasePools, pool);
736 return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait, aRecursionDepth);
738 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
741 // AfterProcessNextEvent
743 // This nsIThreadObserver method is called after event processing is complete.
744 // The Cocoa implementation cleans up the autorelease pool create by the
745 // previous OnProcessNextEvent call.
749 nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
750 uint32_t aRecursionDepth,
751 bool aEventWasProcessed)
753 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
755 CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
757 NS_ASSERTION(mAutoreleasePools && count,
758 "Processed an event, but there's no autorelease pool?");
760 const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*>
761 (::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
762 ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
765 return nsBaseAppShell::AfterProcessNextEvent(aThread, aRecursionDepth,
768 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
772 // AppShellDelegate implementation
775 @implementation AppShellDelegate
778 // Constructs the AppShellDelegate object
779 - (id)initWithAppShell:(nsAppShell*)aAppShell
781 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
783 if ((self = [self init])) {
784 mAppShell = aAppShell;
786 [[NSNotificationCenter defaultCenter] addObserver:self
787 selector:@selector(applicationWillTerminate:)
788 name:NSApplicationWillTerminateNotification
790 [[NSNotificationCenter defaultCenter] addObserver:self
791 selector:@selector(applicationDidBecomeActive:)
792 name:NSApplicationDidBecomeActiveNotification
794 [[NSDistributedNotificationCenter defaultCenter] addObserver:self
795 selector:@selector(beginMenuTracking:)
796 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
802 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
807 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
809 [[NSNotificationCenter defaultCenter] removeObserver:self];
810 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
813 NS_OBJC_END_TRY_ABORT_BLOCK;
816 // applicationWillTerminate:
818 // Notify the nsAppShell that native event processing should be discontinued.
819 - (void)applicationWillTerminate:(NSNotification*)aNotification
821 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
823 mAppShell->WillTerminate();
825 NS_OBJC_END_TRY_ABORT_BLOCK;
828 // applicationDidBecomeActive
830 // Make sure TextInputHandler::sLastModifierState is updated when we become
831 // active (since we won't have received [ChildView flagsChanged:] messages
833 - (void)applicationDidBecomeActive:(NSNotification*)aNotification
835 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
837 // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
838 // to worry about getting an NSInternalInconsistencyException here.
839 NSEvent* currentEvent = [NSApp currentEvent];
841 TextInputHandler::sLastModifierState =
842 [currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
845 NS_OBJC_END_TRY_ABORT_BLOCK;
850 // Roll up our context menu (if any) when some other app (or the OS) opens
851 // any sort of menu. But make sure we don't do this for notifications we
852 // send ourselves (whose 'sender' will be @"org.mozilla.gecko.PopupWindow").
853 - (void)beginMenuTracking:(NSNotification*)aNotification
855 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
857 NSString *sender = [aNotification object];
858 if (!sender || ![sender isEqualToString:@"org.mozilla.gecko.PopupWindow"]) {
859 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
860 nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
862 rollupListener->Rollup(0, nullptr, nullptr);
865 NS_OBJC_END_TRY_ABORT_BLOCK;
870 // We hook terminate: in order to make OS-initiated termination work nicely
871 // with Gecko's shutdown sequence. (Two ways to trigger OS-initiated
872 // termination: 1) Quit from the Dock menu; 2) Log out from (or shut down)
873 // your computer while the browser is active.)
874 @interface NSApplication (MethodSwizzling)
875 - (void)nsAppShell_NSApplication_terminate:(id)sender;
878 @implementation NSApplication (MethodSwizzling)
880 // Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
881 // has returned NSTerminateNow. This method "subclasses" and replaces the
882 // OS's original implementation. The only thing the orginal method does which
883 // we need is that it posts NSApplicationWillTerminateNotification. Everything
884 // else is unneeded (because it's handled elsewhere), or actively interferes
885 // with Gecko's shutdown sequence. For example the original terminate: method
886 // causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
887 // above), which means that nothing runs after the call to nsAppStartup::Run()
888 // in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
889 // and NS_ShutdownXPCOM() never get called.
890 - (void)nsAppShell_NSApplication_terminate:(id)sender
892 [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification