forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			981 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			981 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| /*
 | |
|  * Runs the main native Cocoa run loop, interrupting it as needed to process
 | |
|  * Gecko events.
 | |
|  */
 | |
| 
 | |
| #import <Cocoa/Cocoa.h>
 | |
| 
 | |
| #include "CustomCocoaEvents.h"
 | |
| #include "mozilla/WidgetTraceEvent.h"
 | |
| #include "nsAppShell.h"
 | |
| #include "nsCOMPtr.h"
 | |
| #include "nsIFile.h"
 | |
| #include "nsDirectoryServiceDefs.h"
 | |
| #include "nsString.h"
 | |
| #include "nsIRollupListener.h"
 | |
| #include "nsIWidget.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include "nsIWindowMediator.h"
 | |
| #include "nsServiceManagerUtils.h"
 | |
| #include "nsIInterfaceRequestor.h"
 | |
| #include "nsIWebBrowserChrome.h"
 | |
| #include "nsObjCExceptions.h"
 | |
| #include "nsCocoaFeatures.h"
 | |
| #include "nsCocoaUtils.h"
 | |
| #include "nsChildView.h"
 | |
| #include "nsToolkit.h"
 | |
| #include "TextInputHandler.h"
 | |
| #include "mozilla/BackgroundHangMonitor.h"
 | |
| #include "GeckoProfiler.h"
 | |
| #include "ScreenHelperCocoa.h"
 | |
| #include "mozilla/widget/ScreenManager.h"
 | |
| #include "HeadlessScreenHelper.h"
 | |
| #include "pratom.h"
 | |
| #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
 | |
| #include "nsSandboxViolationSink.h"
 | |
| #endif
 | |
| 
 | |
| #include <IOKit/pwr_mgt/IOPMLib.h>
 | |
| #include "nsIDOMWakeLockListener.h"
 | |
| #include "nsIPowerManagerService.h"
 | |
| 
 | |
| using namespace mozilla::widget;
 | |
| 
 | |
| #define WAKE_LOCK_LOG(...) MOZ_LOG(gMacWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
 | |
| static mozilla::LazyLogModule gMacWakeLockLog("MacWakeLock");
 | |
| 
 | |
| // A wake lock listener that disables screen saver when requested by
 | |
| // Gecko. For example when we're playing video in a foreground tab we
 | |
| // don't want the screen saver to turn on.
 | |
| 
 | |
| class MacWakeLockListener final : public nsIDOMMozWakeLockListener {
 | |
| public:
 | |
|   NS_DECL_ISUPPORTS;
 | |
| 
 | |
| private:
 | |
|   ~MacWakeLockListener() {}
 | |
| 
 | |
|   IOPMAssertionID mAssertionNoDisplaySleepID = kIOPMNullAssertionID;
 | |
|   IOPMAssertionID mAssertionNoIdleSleepID = kIOPMNullAssertionID;
 | |
| 
 | |
|   NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) override {
 | |
|     if (!aTopic.EqualsASCII("screen") &&
 | |
|         !aTopic.EqualsASCII("audio-playing") &&
 | |
|         !aTopic.EqualsASCII("video-playing")) {
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     // we should still hold the lock for background audio.
 | |
|     if (aTopic.EqualsASCII("audio-playing") &&
 | |
|         aState.EqualsASCII("locked-background")) {
 | |
|       WAKE_LOCK_LOG("keep audio playing even in background");
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     bool shouldKeepDisplayOn = aTopic.EqualsASCII("screen") ||
 | |
|                                aTopic.EqualsASCII("video-playing");
 | |
|     CFStringRef assertionType = shouldKeepDisplayOn ?
 | |
|       kIOPMAssertionTypeNoDisplaySleep : kIOPMAssertionTypeNoIdleSleep;
 | |
|     IOPMAssertionID& assertionId = shouldKeepDisplayOn ?
 | |
|       mAssertionNoDisplaySleepID : mAssertionNoIdleSleepID;
 | |
| 
 | |
|     // Note the wake lock code ensures that we're not sent duplicate
 | |
|     // "locked-foreground" notifications when multiple wake locks are held.
 | |
|     if (aState.EqualsASCII("locked-foreground")) {
 | |
|       if (assertionId != kIOPMNullAssertionID) {
 | |
|         WAKE_LOCK_LOG("already has a lock");
 | |
|         return NS_OK;
 | |
|       }
 | |
|       // Prevent screen saver.
 | |
|       CFStringRef cf_topic =
 | |
|         ::CFStringCreateWithCharacters(kCFAllocatorDefault,
 | |
|                                        reinterpret_cast<const UniChar*>
 | |
|                                          (aTopic.Data()),
 | |
|                                        aTopic.Length());
 | |
|       IOReturn success =
 | |
|         ::IOPMAssertionCreateWithName(assertionType,
 | |
|                                       kIOPMAssertionLevelOn,
 | |
|                                       cf_topic,
 | |
|                                       &assertionId);
 | |
|       CFRelease(cf_topic);
 | |
|       if (success != kIOReturnSuccess) {
 | |
|         WAKE_LOCK_LOG("failed to disable screensaver");
 | |
|       }
 | |
|       WAKE_LOCK_LOG("create screensaver");
 | |
|     } else {
 | |
|       // Re-enable screen saver.
 | |
|       if (assertionId != kIOPMNullAssertionID) {
 | |
|         IOReturn result = ::IOPMAssertionRelease(assertionId);
 | |
|         if (result != kIOReturnSuccess) {
 | |
|           WAKE_LOCK_LOG("failed to release screensaver");
 | |
|         }
 | |
|         WAKE_LOCK_LOG("Release screensaver");
 | |
|         assertionId = kIOPMNullAssertionID;
 | |
|       }
 | |
|     }
 | |
|     return NS_OK;
 | |
|   }
 | |
| }; // MacWakeLockListener
 | |
| 
 | |
| // defined in nsCocoaWindow.mm
 | |
| extern int32_t             gXULModalLevel;
 | |
| 
 | |
| static bool gAppShellMethodsSwizzled = false;
 | |
| 
 | |
| @implementation GeckoNSApplication
 | |
| 
 | |
| - (void)sendEvent:(NSEvent *)anEvent
 | |
| {
 | |
|   // Mark this function as non-idle because it's one of the exit points from
 | |
|   // the event loop (running inside of -[GeckoNSApplication nextEventMatchingMask:...])
 | |
|   // into non-idle code. So we basically unset the IDLE category from the inside.
 | |
|   AUTO_PROFILER_LABEL("-[GeckoNSApplication sendEvent:]", OTHER);
 | |
| 
 | |
|   mozilla::BackgroundHangMonitor().NotifyActivity();
 | |
| 
 | |
|   if ([anEvent type] == NSApplicationDefined &&
 | |
|       [anEvent subtype] == kEventSubtypeTrace) {
 | |
|     mozilla::SignalTracerThread();
 | |
|     return;
 | |
|   }
 | |
|   [super sendEvent:anEvent];
 | |
| }
 | |
| 
 | |
| #if defined(MAC_OS_X_VERSION_10_12) && \
 | |
|     MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 && \
 | |
|     __LP64__
 | |
| // 10.12 changed `mask` to NSEventMask (unsigned long long) for x86_64 builds.
 | |
| - (NSEvent*)nextEventMatchingMask:(NSEventMask)mask
 | |
| #else
 | |
| - (NSEvent*)nextEventMatchingMask:(NSUInteger)mask
 | |
| #endif
 | |
|                         untilDate:(NSDate*)expiration
 | |
|                            inMode:(NSString*)mode
 | |
|                           dequeue:(BOOL)flag
 | |
| {
 | |
|   // When we're waiting in the event loop, this is the last function under our
 | |
|   // control that's on the stack, so this is the function that we mark with the
 | |
|   // IDLE category.
 | |
|   // However, when we're processing an event or when our CFRunLoopSource runs,
 | |
|   // this function is still on the stack - "the event loop calls us". So we
 | |
|   // need to mark functions that enter non-idle code with a different profiler
 | |
|   // category, usually OTHER. This gives the profiler a rough approximation of
 | |
|   // idleness but isn't perfect. For example, sometimes there's some Cocoa-
 | |
|   // internal activity that's triggered from the event loop, and we'll
 | |
|   // misidentify the stacks for that activity as idle because there's no Gecko
 | |
|   // code on the stack that can change the stack's category to something
 | |
|   // non-idle.
 | |
|   AUTO_PROFILER_LABEL("-[GeckoNSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]", IDLE);
 | |
| 
 | |
|   if (expiration) {
 | |
|     mozilla::BackgroundHangMonitor().NotifyWait();
 | |
|   }
 | |
|   NSEvent* nextEvent = [super nextEventMatchingMask:mask
 | |
|                         untilDate:expiration inMode:mode dequeue:flag];
 | |
|   if (expiration) {
 | |
|     mozilla::BackgroundHangMonitor().NotifyActivity();
 | |
|   }
 | |
|   return nextEvent;
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| // AppShellDelegate
 | |
| //
 | |
| // Cocoa bridge class.  An object of this class is registered to receive
 | |
| // notifications.
 | |
| //
 | |
| @interface AppShellDelegate : NSObject
 | |
| {
 | |
|   @private
 | |
|     nsAppShell* mAppShell;
 | |
| }
 | |
| 
 | |
| - (id)initWithAppShell:(nsAppShell*)aAppShell;
 | |
| - (void)applicationWillTerminate:(NSNotification*)aNotification;
 | |
| - (void)beginMenuTracking:(NSNotification*)aNotification;
 | |
| @end
 | |
| 
 | |
| // nsAppShell implementation
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsAppShell::ResumeNative(void)
 | |
| {
 | |
|   nsresult retval = nsBaseAppShell::ResumeNative();
 | |
|   if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) &&
 | |
|       mSkippedNativeCallback)
 | |
|   {
 | |
|     mSkippedNativeCallback = false;
 | |
|     ScheduleNativeEventCallback();
 | |
|   }
 | |
|   return retval;
 | |
| }
 | |
| 
 | |
| nsAppShell::nsAppShell()
 | |
| : mAutoreleasePools(nullptr)
 | |
| , mDelegate(nullptr)
 | |
| , mCFRunLoop(NULL)
 | |
| , mCFRunLoopSource(NULL)
 | |
| , mRunningEventLoop(false)
 | |
| , mStarted(false)
 | |
| , mTerminated(false)
 | |
| , mSkippedNativeCallback(false)
 | |
| , mNativeEventCallbackDepth(0)
 | |
| , mNativeEventScheduledDepth(0)
 | |
| {
 | |
|   // A Cocoa event loop is running here if (and only if) we've been embedded
 | |
|   // by a Cocoa app.
 | |
|   mRunningCocoaEmbedded = [NSApp isRunning] ? true : false;
 | |
| }
 | |
| 
 | |
| nsAppShell::~nsAppShell()
 | |
| {
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 | |
| 
 | |
|   if (mCFRunLoop) {
 | |
|     if (mCFRunLoopSource) {
 | |
|       ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
 | |
|                               kCFRunLoopCommonModes);
 | |
|       ::CFRelease(mCFRunLoopSource);
 | |
|     }
 | |
|     ::CFRelease(mCFRunLoop);
 | |
|   }
 | |
| 
 | |
|   if (mAutoreleasePools) {
 | |
|     NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
 | |
|                  "nsAppShell destroyed without popping all autorelease pools");
 | |
|     ::CFRelease(mAutoreleasePools);
 | |
|   }
 | |
| 
 | |
|   [mDelegate release];
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(MacWakeLockListener, nsIDOMMozWakeLockListener)
 | |
| mozilla::StaticRefPtr<MacWakeLockListener> sWakeLockListener;
 | |
| 
 | |
| static void
 | |
| AddScreenWakeLockListener()
 | |
| {
 | |
|   nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(
 | |
|                                                           POWERMANAGERSERVICE_CONTRACTID);
 | |
|   if (sPowerManagerService) {
 | |
|     sWakeLockListener = new MacWakeLockListener();
 | |
|     sPowerManagerService->AddWakeLockListener(sWakeLockListener);
 | |
|   } else {
 | |
|     NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void
 | |
| RemoveScreenWakeLockListener()
 | |
| {
 | |
|   nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(
 | |
|                                                           POWERMANAGERSERVICE_CONTRACTID);
 | |
|   if (sPowerManagerService) {
 | |
|     sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
 | |
|     sPowerManagerService = nullptr;
 | |
|     sWakeLockListener = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // An undocumented CoreGraphics framework method, present in the same form
 | |
| // since at least OS X 10.5.
 | |
| extern "C" CGError CGSSetDebugOptions(int options);
 | |
| 
 | |
| // Init
 | |
| //
 | |
| // Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
 | |
| // interrupt the main native run loop.
 | |
| //
 | |
| // public
 | |
| nsresult
 | |
| nsAppShell::Init()
 | |
| {
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 | |
| 
 | |
|   // No event loop is running yet (unless an embedding app that uses
 | |
|   // NSApplicationMain() is running).
 | |
|   NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
 | |
| 
 | |
|   // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
 | |
|   // by |this|.  CFArray is used instead of NSArray because NSArray wants to
 | |
|   // retain each object you add to it, and you can't retain an
 | |
|   // NSAutoreleasePool.
 | |
|   mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
 | |
|   NS_ENSURE_STATE(mAutoreleasePools);
 | |
| 
 | |
|   // Get the path of the nib file, which lives in the GRE location
 | |
|   nsCOMPtr<nsIFile> nibFile;
 | |
|   nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(nibFile));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nibFile->AppendNative(NS_LITERAL_CSTRING("res"));
 | |
|   nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib"));
 | |
| 
 | |
|   nsAutoCString nibPath;
 | |
|   rv = nibFile->GetNativePath(nibPath);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // This call initializes NSApplication unless:
 | |
|   // 1) we're using xre -- NSApp's already been initialized by
 | |
|   //    MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
 | |
|   // 2) an embedding app that uses NSApplicationMain() is running -- NSApp's
 | |
|   //    already been initialized and its main run loop is already running.
 | |
|   [NSBundle loadNibFile:
 | |
|                      [NSString stringWithUTF8String:(const char*)nibPath.get()]
 | |
|       externalNameTable:
 | |
|            [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication]
 | |
|                                        forKey:@"NSOwner"]
 | |
|                withZone:NSDefaultMallocZone()];
 | |
| 
 | |
|   mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
 | |
|   NS_ENSURE_STATE(mDelegate);
 | |
| 
 | |
|   // Add a CFRunLoopSource to the main native run loop.  The source is
 | |
|   // responsible for interrupting the run loop when Gecko events are ready.
 | |
| 
 | |
|   mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
 | |
|   NS_ENSURE_STATE(mCFRunLoop);
 | |
|   ::CFRetain(mCFRunLoop);
 | |
| 
 | |
|   CFRunLoopSourceContext context;
 | |
|   bzero(&context, sizeof(context));
 | |
|   // context.version = 0;
 | |
|   context.info = this;
 | |
|   context.perform = ProcessGeckoEvents;
 | |
| 
 | |
|   mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
 | |
|   NS_ENSURE_STATE(mCFRunLoopSource);
 | |
| 
 | |
|   ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
 | |
| 
 | |
|   if (XRE_IsParentProcess()) {
 | |
|     ScreenManager& screenManager = ScreenManager::GetSingleton();
 | |
| 
 | |
|     if (gfxPlatform::IsHeadless()) {
 | |
|       screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
 | |
|     } else {
 | |
|       screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperCocoa>());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   rv = nsBaseAppShell::Init();
 | |
| 
 | |
|   if (!gAppShellMethodsSwizzled) {
 | |
|     // We should only replace the original terminate: method if we're not
 | |
|     // running in a Cocoa embedder. See bug 604901.
 | |
|     if (!mRunningCocoaEmbedded) {
 | |
|       nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
 | |
|                                 @selector(nsAppShell_NSApplication_terminate:));
 | |
|     }
 | |
|     gAppShellMethodsSwizzled = true;
 | |
|   }
 | |
| 
 | |
|   // The bug that this works around was introduced in OS X 10.10.0
 | |
|   // and fixed in OS X 10.10.2. Order these version checks so as
 | |
|   // few as possible will actually end up running.
 | |
|   if (nsCocoaFeatures::OSXVersionMinor() == 10 &&
 | |
|       nsCocoaFeatures::OSXVersionBugFix() < 2 &&
 | |
|       nsCocoaFeatures::OSXVersionMajor() == 10) {
 | |
|     // Explicitly turn off CGEvent logging.  This works around bug 1092855.
 | |
|     // If there are already CGEvents in the log, turning off logging also
 | |
|     // causes those events to be written to disk.  But at this point no
 | |
|     // CGEvents have yet been processed.  CGEvents are events (usually
 | |
|     // input events) pulled from the WindowServer.  An option of 0x80000008
 | |
|     // turns on CGEvent logging.
 | |
|     CGSSetDebugOptions(0x80000007);
 | |
|   }
 | |
| 
 | |
| #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
 | |
|   if (Preferences::GetBool("security.sandbox.mac.track.violations", false)) {
 | |
|     nsSandboxViolationSink::Start();
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   [localPool release];
 | |
| 
 | |
|   return rv;
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 | |
| }
 | |
| 
 | |
| // ProcessGeckoEvents
 | |
| //
 | |
| // The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
 | |
| // signalled from ScheduleNativeEventCallback.
 | |
| //
 | |
| // Arrange for Gecko events to be processed on demand (in response to a call
 | |
| // to ScheduleNativeEventCallback(), if processing of Gecko events via "native
 | |
| // methods" hasn't been suspended).  This happens in NativeEventCallback().
 | |
| //
 | |
| // protected static
 | |
| void
 | |
| nsAppShell::ProcessGeckoEvents(void* aInfo)
 | |
| {
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 | |
|   AUTO_PROFILER_LABEL("nsAppShell::ProcessGeckoEvents", OTHER);
 | |
| 
 | |
|   nsAppShell* self = static_cast<nsAppShell*> (aInfo);
 | |
| 
 | |
|   if (self->mRunningEventLoop) {
 | |
|     self->mRunningEventLoop = false;
 | |
| 
 | |
|     // The run loop may be sleeping -- [NSRunLoop runMode:...]
 | |
|     // won't return until it's given a reason to wake up.  Awaken it by
 | |
|     // posting a bogus event.  There's no need to make the event
 | |
|     // presentable.
 | |
|     //
 | |
|     // But _don't_ set windowNumber to '-1' -- that can lead to nasty
 | |
|     // weirdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
 | |
|     // these fake events, because the -1 has gotten changed into the number
 | |
|     // of an actual NSWindow object, and that NSWindow object has just been
 | |
|     // destroyed).  Setting windowNumber to '0' seems to work fine -- this
 | |
|     // seems to prevent the OS from ever trying to associate our bogus event
 | |
|     // with a particular NSWindow object.
 | |
|     [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
 | |
|                                         location:NSMakePoint(0,0)
 | |
|                                    modifierFlags:0
 | |
|                                        timestamp:0
 | |
|                                     windowNumber:0
 | |
|                                          context:NULL
 | |
|                                          subtype:kEventSubtypeNone
 | |
|                                            data1:0
 | |
|                                            data2:0]
 | |
|              atStart:NO];
 | |
|   }
 | |
| 
 | |
|   if (self->mSuspendNativeCount <= 0) {
 | |
|     ++self->mNativeEventCallbackDepth;
 | |
|     self->NativeEventCallback();
 | |
|     --self->mNativeEventCallbackDepth;
 | |
|   } else {
 | |
|     self->mSkippedNativeCallback = true;
 | |
|   }
 | |
| 
 | |
|   // Still needed to avoid crashes on quit in most Mochitests.
 | |
|   [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
 | |
|                                       location:NSMakePoint(0,0)
 | |
|                                  modifierFlags:0
 | |
|                                      timestamp:0
 | |
|                                   windowNumber:0
 | |
|                                        context:NULL
 | |
|                                        subtype:kEventSubtypeNone
 | |
|                                          data1:0
 | |
|                                          data2:0]
 | |
|            atStart:NO];
 | |
| 
 | |
|   // Normally every call to ScheduleNativeEventCallback() results in
 | |
|   // exactly one call to ProcessGeckoEvents().  So each Release() here
 | |
|   // normally balances exactly one AddRef() in ScheduleNativeEventCallback().
 | |
|   // But if Exit() is called just after ScheduleNativeEventCallback(), the
 | |
|   // corresponding call to ProcessGeckoEvents() will never happen.  We check
 | |
|   // for this possibility in two different places -- here and in Exit()
 | |
|   // itself.  If we find here that Exit() has been called (that mTerminated
 | |
|   // is true), it's because we've been called recursively, that Exit() was
 | |
|   // called from self->NativeEventCallback() above, and that we're unwinding
 | |
|   // the recursion.  In this case we'll never be called again, and we balance
 | |
|   // here any extra calls to ScheduleNativeEventCallback().
 | |
|   //
 | |
|   // When ProcessGeckoEvents() is called recursively, it's because of a
 | |
|   // call to ScheduleNativeEventCallback() from NativeEventCallback().  We
 | |
|   // balance the "extra" AddRefs here (rather than always in Exit()) in order
 | |
|   // to ensure that 'self' stays alive until the end of this method.  We also
 | |
|   // make sure not to finish the balancing until all the recursion has been
 | |
|   // unwound.
 | |
|   if (self->mTerminated) {
 | |
|     int32_t releaseCount = 0;
 | |
|     if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) {
 | |
|       releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth,
 | |
|                                    self->mNativeEventCallbackDepth);
 | |
|     }
 | |
|     while (releaseCount-- > self->mNativeEventCallbackDepth)
 | |
|       self->Release();
 | |
|   } else {
 | |
|     // As best we can tell, every call to ProcessGeckoEvents() is triggered
 | |
|     // by a call to ScheduleNativeEventCallback().  But we've seen a few
 | |
|     // (non-reproducible) cases of double-frees that *might* have been caused
 | |
|     // by spontaneous calls (from the OS) to ProcessGeckoEvents().  So we
 | |
|     // deal with that possibility here.
 | |
|     if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) {
 | |
|       PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0);
 | |
|       NS_WARNING("Spontaneous call to ProcessGeckoEvents()!");
 | |
|     } else {
 | |
|       self->Release();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK;
 | |
| }
 | |
| 
 | |
| // WillTerminate
 | |
| //
 | |
| // Called by the AppShellDelegate when an NSApplicationWillTerminate
 | |
| // notification is posted.  After this method is called, native events should
 | |
| // no longer be processed.  The NSApplicationWillTerminate notification is
 | |
| // only posted when [NSApp terminate:] is called, which doesn't happen on a
 | |
| // "normal" application quit.
 | |
| //
 | |
| // public
 | |
| void
 | |
| nsAppShell::WillTerminate()
 | |
| {
 | |
|   if (mTerminated)
 | |
|     return;
 | |
| 
 | |
|   // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
 | |
|   // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
 | |
|   NS_ProcessPendingEvents(NS_GetCurrentThread());
 | |
| 
 | |
|   mTerminated = true;
 | |
| }
 | |
| 
 | |
| // ScheduleNativeEventCallback
 | |
| //
 | |
| // Called (possibly on a non-main thread) when Gecko has an event that
 | |
| // needs to be processed.  The Gecko event needs to be processed on the
 | |
| // main thread, so the native run loop must be interrupted.
 | |
| //
 | |
| // In nsBaseAppShell.cpp, the mNativeEventPending variable is used to
 | |
| // ensure that ScheduleNativeEventCallback() is called no more than once
 | |
| // per call to NativeEventCallback().  ProcessGeckoEvents() can skip its
 | |
| // call to NativeEventCallback() if processing of Gecko events by native
 | |
| // means is suspended (using nsIAppShell::SuspendNative()), which will
 | |
| // suspend calls from nsBaseAppShell::OnDispatchedEvent() to
 | |
| // ScheduleNativeEventCallback().  But when Gecko event processing by
 | |
| // native means is resumed (in ResumeNative()), an extra call is made to
 | |
| // ScheduleNativeEventCallback() (from ResumeNative()).  This triggers
 | |
| // another call to ProcessGeckoEvents(), which calls NativeEventCallback(),
 | |
| // and nsBaseAppShell::OnDispatchedEvent() resumes calling
 | |
| // ScheduleNativeEventCallback().
 | |
| //
 | |
| // protected virtual
 | |
| void
 | |
| nsAppShell::ScheduleNativeEventCallback()
 | |
| {
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 | |
| 
 | |
|   if (mTerminated)
 | |
|     return;
 | |
| 
 | |
|   // Each AddRef() here is normally balanced by exactly one Release() in
 | |
|   // ProcessGeckoEvents().  But there are exceptions, for which see
 | |
|   // ProcessGeckoEvents() and Exit().
 | |
|   NS_ADDREF_THIS();
 | |
|   PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth);
 | |
| 
 | |
|   // This will invoke ProcessGeckoEvents on the main thread.
 | |
|   ::CFRunLoopSourceSignal(mCFRunLoopSource);
 | |
|   ::CFRunLoopWakeUp(mCFRunLoop);
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK;
 | |
| }
 | |
| 
 | |
| // Undocumented Cocoa Event Manager function, present in the same form since
 | |
| // at least OS X 10.6.
 | |
| extern "C" EventAttributes GetEventAttributes(EventRef inEvent);
 | |
| 
 | |
| // ProcessNextNativeEvent
 | |
| //
 | |
| // If aMayWait is false, process a single native event.  If it is true, run
 | |
| // the native run loop until stopped by ProcessGeckoEvents.
 | |
| //
 | |
| // Returns true if more events are waiting in the native event queue.
 | |
| //
 | |
| // protected virtual
 | |
| bool
 | |
| nsAppShell::ProcessNextNativeEvent(bool aMayWait)
 | |
| {
 | |
|   bool moreEvents = false;
 | |
| 
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 | |
| 
 | |
|   bool eventProcessed = false;
 | |
|   NSString* currentMode = nil;
 | |
| 
 | |
|   if (mTerminated)
 | |
|     return false;
 | |
| 
 | |
|   bool wasRunningEventLoop = mRunningEventLoop;
 | |
|   mRunningEventLoop = aMayWait;
 | |
|   NSDate* waitUntil = nil;
 | |
|   if (aMayWait)
 | |
|     waitUntil = [NSDate distantFuture];
 | |
| 
 | |
|   NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
 | |
| 
 | |
|   EventQueueRef currentEventQueue = GetCurrentEventQueue();
 | |
|   EventTargetRef eventDispatcherTarget = GetEventDispatcherTarget();
 | |
| 
 | |
|   if (aMayWait) {
 | |
|     mozilla::BackgroundHangMonitor().NotifyWait();
 | |
|   }
 | |
| 
 | |
|   // Only call -[NSApp sendEvent:] (and indirectly send user-input events to
 | |
|   // Gecko) if aMayWait is true.  Tbis ensures most calls to -[NSApp
 | |
|   // sendEvent:] happen under nsAppShell::Run(), at the lowest level of
 | |
|   // recursion -- thereby making it less likely Gecko will process user-input
 | |
|   // events in the wrong order or skip some of them.  It also avoids eating
 | |
|   // too much CPU in nsBaseAppShell::OnProcessNextEvent() (which calls
 | |
|   // us) -- thereby avoiding the starvation of nsIRunnable events in
 | |
|   // nsThread::ProcessNextEvent().  For more information see bug 996848.
 | |
|   do {
 | |
|     // No autorelease pool is provided here, because OnProcessNextEvent
 | |
|     // and AfterProcessNextEvent are responsible for maintaining it.
 | |
|     NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
 | |
|                  "No autorelease pool for native event");
 | |
| 
 | |
|     if (aMayWait) {
 | |
|       currentMode = [currentRunLoop currentMode];
 | |
|       if (!currentMode)
 | |
|         currentMode = NSDefaultRunLoopMode;
 | |
|       NSEvent *nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
 | |
|                                               untilDate:waitUntil
 | |
|                                                  inMode:currentMode
 | |
|                                                 dequeue:YES];
 | |
|       if (nextEvent) {
 | |
|         mozilla::BackgroundHangMonitor().NotifyActivity();
 | |
|         [NSApp sendEvent:nextEvent];
 | |
|         eventProcessed = true;
 | |
|       }
 | |
|     } else {
 | |
|       // AcquireFirstMatchingEventInQueue() doesn't spin the (native) event
 | |
|       // loop, though it does queue up any newly available events from the
 | |
|       // window server.
 | |
|       EventRef currentEvent = AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
 | |
|                                                                kEventQueueOptionsNone);
 | |
|       if (!currentEvent) {
 | |
|         continue;
 | |
|       }
 | |
|       EventAttributes attrs = GetEventAttributes(currentEvent);
 | |
|       UInt32 eventKind = GetEventKind(currentEvent);
 | |
|       UInt32 eventClass = GetEventClass(currentEvent);
 | |
|       bool osCocoaEvent =
 | |
|         ((eventClass == 'appl') || (eventClass == kEventClassAppleEvent) ||
 | |
|          ((eventClass == 'cgs ') && (eventKind != NSApplicationDefined)));
 | |
|       // If attrs is kEventAttributeUserEvent or kEventAttributeMonitored
 | |
|       // (i.e. a user input event), we shouldn't process it here while
 | |
|       // aMayWait is false.  Likewise if currentEvent will eventually be
 | |
|       // turned into an OS-defined Cocoa event, or otherwise needs AppKit
 | |
|       // processing.  Doing otherwise risks doing too much work here, and
 | |
|       // preventing the event from being properly processed by the AppKit
 | |
|       // framework.
 | |
|       if ((attrs != kEventAttributeNone) || osCocoaEvent) {
 | |
|         // Since we can't process the next event here (while aMayWait is false),
 | |
|         // we want moreEvents to be false on return.
 | |
|         eventProcessed = false;
 | |
|         // This call to ReleaseEvent() matches a call to RetainEvent() in
 | |
|         // AcquireFirstMatchingEventInQueue() above.
 | |
|         ReleaseEvent(currentEvent);
 | |
|         break;
 | |
|       }
 | |
|       // This call to RetainEvent() matches a call to ReleaseEvent() in
 | |
|       // RemoveEventFromQueue() below.
 | |
|       RetainEvent(currentEvent);
 | |
|       RemoveEventFromQueue(currentEventQueue, currentEvent);
 | |
|       SendEventToEventTarget(currentEvent, eventDispatcherTarget);
 | |
|       // This call to ReleaseEvent() matches a call to RetainEvent() in
 | |
|       // AcquireFirstMatchingEventInQueue() above.
 | |
|       ReleaseEvent(currentEvent);
 | |
|       eventProcessed = true;
 | |
|     }
 | |
|   } while (mRunningEventLoop);
 | |
| 
 | |
|   if (eventProcessed) {
 | |
|     moreEvents =
 | |
|       (AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
 | |
|                                         kEventQueueOptionsNone) != NULL);
 | |
|   }
 | |
| 
 | |
|   mRunningEventLoop = wasRunningEventLoop;
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK;
 | |
| 
 | |
|   if (!moreEvents) {
 | |
|     nsChildView::UpdateCurrentInputEventCount();
 | |
|   }
 | |
| 
 | |
|   return moreEvents;
 | |
| }
 | |
| 
 | |
| // Run
 | |
| //
 | |
| // Overrides the base class's Run() method to call [NSApp run] (which spins
 | |
| // the native run loop until the application quits).  Since (unlike the base
 | |
| // class's Run() method) we don't process any Gecko events here, they need
 | |
| // to be processed elsewhere (in NativeEventCallback(), called from
 | |
| // ProcessGeckoEvents()).
 | |
| //
 | |
| // Camino called [NSApp run] on its own (via NSApplicationMain()), and so
 | |
| // didn't call nsAppShell::Run().
 | |
| //
 | |
| // public
 | |
| NS_IMETHODIMP
 | |
| nsAppShell::Run(void)
 | |
| {
 | |
|   NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
 | |
|   if (mStarted || mTerminated)
 | |
|     return NS_OK;
 | |
| 
 | |
|   mStarted = true;
 | |
| 
 | |
|   if (XRE_IsParentProcess()) {
 | |
|     AddScreenWakeLockListener();
 | |
|   }
 | |
| 
 | |
|   // We use the native Gecko event loop in content processes.
 | |
|   nsresult rv = NS_OK;
 | |
|   if (XRE_UseNativeEventProcessing()) {
 | |
|     NS_OBJC_TRY_ABORT([NSApp run]);
 | |
|   } else {
 | |
|     rv = nsBaseAppShell::Run();
 | |
|   }
 | |
| 
 | |
|   if (XRE_IsParentProcess()) {
 | |
|     RemoveScreenWakeLockListener();
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsAppShell::Exit(void)
 | |
| {
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 | |
| 
 | |
|   // This method is currently called more than once -- from (according to
 | |
|   // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
 | |
|   // XPCOM shutdown notification that nsBaseAppShell has registered to
 | |
|   // receive.  So we need to ensure that multiple calls won't break anything.
 | |
|   // But we should also complain about it (since it isn't quite kosher).
 | |
|   if (mTerminated) {
 | |
|     NS_WARNING("nsAppShell::Exit() called redundantly");
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   mTerminated = true;
 | |
| 
 | |
| #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
 | |
|   nsSandboxViolationSink::Stop();
 | |
| #endif
 | |
| 
 | |
|   // Quoting from Apple's doc on the [NSApplication stop:] method (from their
 | |
|   // doc on the NSApplication class):  "If this method is invoked during a
 | |
|   // modal event loop, it will break that loop but not the main event loop."
 | |
|   // nsAppShell::Exit() shouldn't be called from a modal event loop.  So if
 | |
|   // it is we complain about it (to users of debug builds) and call [NSApp
 | |
|   // stop:] one extra time.  (I'm not sure if modal event loops can be nested
 | |
|   // -- Apple's docs don't say one way or the other.  But the return value
 | |
|   // of [NSApp _isRunningModal] doesn't change immediately after a call to
 | |
|   // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
 | |
|   // will do the job.)
 | |
|   BOOL cocoaModal = [NSApp _isRunningModal];
 | |
|   NS_ASSERTION(!cocoaModal,
 | |
|                "Don't call nsAppShell::Exit() from a modal event loop!");
 | |
|   if (cocoaModal)
 | |
|     [NSApp stop:nullptr];
 | |
|   [NSApp stop:nullptr];
 | |
| 
 | |
|   // A call to Exit() just after a call to ScheduleNativeEventCallback()
 | |
|   // prevents the (normally) matching call to ProcessGeckoEvents() from
 | |
|   // happening.  If we've been called from ProcessGeckoEvents() (as usually
 | |
|   // happens), we take care of it there.  But if we have an unbalanced call
 | |
|   // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
 | |
|   // stack, we need to take care of the problem here.
 | |
|   if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
 | |
|     int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
 | |
|     while (releaseCount-- > 0)
 | |
|       NS_RELEASE_THIS();
 | |
|   }
 | |
| 
 | |
|   return nsBaseAppShell::Exit();
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 | |
| }
 | |
| 
 | |
| // OnProcessNextEvent
 | |
| //
 | |
| // This nsIThreadObserver method is called prior to processing an event.
 | |
| // Set up an autorelease pool that will service any autoreleased Cocoa
 | |
| // objects during this event.  This includes native events processed by
 | |
| // ProcessNextNativeEvent.  The autorelease pool will be popped by
 | |
| // AfterProcessNextEvent, it is important for these two methods to be
 | |
| // tightly coupled.
 | |
| //
 | |
| // public
 | |
| NS_IMETHODIMP
 | |
| nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait)
 | |
| {
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 | |
| 
 | |
|   NS_ASSERTION(mAutoreleasePools,
 | |
|                "No stack on which to store autorelease pool");
 | |
| 
 | |
|   NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
 | |
|   ::CFArrayAppendValue(mAutoreleasePools, pool);
 | |
| 
 | |
|   return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait);
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 | |
| }
 | |
| 
 | |
| // AfterProcessNextEvent
 | |
| //
 | |
| // This nsIThreadObserver method is called after event processing is complete.
 | |
| // The Cocoa implementation cleans up the autorelease pool create by the
 | |
| // previous OnProcessNextEvent call.
 | |
| //
 | |
| // public
 | |
| NS_IMETHODIMP
 | |
| nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
 | |
|                                   bool aEventWasProcessed)
 | |
| {
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 | |
| 
 | |
|   CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
 | |
| 
 | |
|   NS_ASSERTION(mAutoreleasePools && count,
 | |
|                "Processed an event, but there's no autorelease pool?");
 | |
| 
 | |
|   const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*>
 | |
|     (::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
 | |
|   ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
 | |
|   [pool release];
 | |
| 
 | |
|   return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed);
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 | |
| }
 | |
| 
 | |
| 
 | |
| // AppShellDelegate implementation
 | |
| 
 | |
| 
 | |
| @implementation AppShellDelegate
 | |
| // initWithAppShell:
 | |
| //
 | |
| // Constructs the AppShellDelegate object
 | |
| - (id)initWithAppShell:(nsAppShell*)aAppShell
 | |
| {
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 | |
| 
 | |
|   if ((self = [self init])) {
 | |
|     mAppShell = aAppShell;
 | |
| 
 | |
|     [[NSNotificationCenter defaultCenter] addObserver:self
 | |
|                                              selector:@selector(applicationWillTerminate:)
 | |
|                                                  name:NSApplicationWillTerminateNotification
 | |
|                                                object:NSApp];
 | |
|     [[NSNotificationCenter defaultCenter] addObserver:self
 | |
|                                              selector:@selector(applicationDidBecomeActive:)
 | |
|                                                  name:NSApplicationDidBecomeActiveNotification
 | |
|                                                object:NSApp];
 | |
|     [[NSDistributedNotificationCenter defaultCenter] addObserver:self
 | |
|                                                         selector:@selector(beginMenuTracking:)
 | |
|                                                             name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
 | |
|                                                           object:nil];
 | |
|   }
 | |
| 
 | |
|   return self;
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 | |
| }
 | |
| 
 | |
| - (void)dealloc
 | |
| {
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 | |
| 
 | |
|   [[NSNotificationCenter defaultCenter] removeObserver:self];
 | |
|   [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
 | |
|   [super dealloc];
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK;
 | |
| }
 | |
| 
 | |
| // applicationWillTerminate:
 | |
| //
 | |
| // Notify the nsAppShell that native event processing should be discontinued.
 | |
| - (void)applicationWillTerminate:(NSNotification*)aNotification
 | |
| {
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 | |
| 
 | |
|   mAppShell->WillTerminate();
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK;
 | |
| }
 | |
| 
 | |
| // applicationDidBecomeActive
 | |
| //
 | |
| // Make sure TextInputHandler::sLastModifierState is updated when we become
 | |
| // active (since we won't have received [ChildView flagsChanged:] messages
 | |
| // while inactive).
 | |
| - (void)applicationDidBecomeActive:(NSNotification*)aNotification
 | |
| {
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 | |
| 
 | |
|   // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
 | |
|   // to worry about getting an NSInternalInconsistencyException here.
 | |
|   NSEvent* currentEvent = [NSApp currentEvent];
 | |
|   if (currentEvent) {
 | |
|     TextInputHandler::sLastModifierState =
 | |
|       [currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
 | |
|   }
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK;
 | |
| }
 | |
| 
 | |
| // beginMenuTracking
 | |
| //
 | |
| // Roll up our context menu (if any) when some other app (or the OS) opens
 | |
| // any sort of menu.  But make sure we don't do this for notifications we
 | |
| // send ourselves (whose 'sender' will be @"org.mozilla.gecko.PopupWindow").
 | |
| - (void)beginMenuTracking:(NSNotification*)aNotification
 | |
| {
 | |
|   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 | |
| 
 | |
|   NSString *sender = [aNotification object];
 | |
|   if (!sender || ![sender isEqualToString:@"org.mozilla.gecko.PopupWindow"]) {
 | |
|     nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
 | |
|     nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
 | |
|     if (rollupWidget)
 | |
|       rollupListener->Rollup(0, true, nullptr, nullptr);
 | |
|   }
 | |
| 
 | |
|   NS_OBJC_END_TRY_ABORT_BLOCK;
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| // We hook terminate: in order to make OS-initiated termination work nicely
 | |
| // with Gecko's shutdown sequence.  (Two ways to trigger OS-initiated
 | |
| // termination:  1) Quit from the Dock menu; 2) Log out from (or shut down)
 | |
| // your computer while the browser is active.)
 | |
| @interface NSApplication (MethodSwizzling)
 | |
| - (void)nsAppShell_NSApplication_terminate:(id)sender;
 | |
| @end
 | |
| 
 | |
| @implementation NSApplication (MethodSwizzling)
 | |
| 
 | |
| // Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
 | |
| // has returned NSTerminateNow.  This method "subclasses" and replaces the
 | |
| // OS's original implementation.  The only thing the orginal method does which
 | |
| // we need is that it posts NSApplicationWillTerminateNotification.  Everything
 | |
| // else is unneeded (because it's handled elsewhere), or actively interferes
 | |
| // with Gecko's shutdown sequence.  For example the original terminate: method
 | |
| // causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
 | |
| // above), which means that nothing runs after the call to nsAppStartup::Run()
 | |
| // in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
 | |
| // and NS_ShutdownXPCOM() never get called.
 | |
| - (void)nsAppShell_NSApplication_terminate:(id)sender
 | |
| {
 | |
|   [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification
 | |
|                                                       object:NSApp];
 | |
| }
 | |
| 
 | |
| @end
 | 
