michael@0: /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Runs the main native Cocoa run loop, interrupting it as needed to process michael@0: * Gecko events. michael@0: */ michael@0: michael@0: #import michael@0: #include michael@0: michael@0: #include "CustomCocoaEvents.h" michael@0: #include "mozilla/WidgetTraceEvent.h" michael@0: #include "nsAppShell.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIFile.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsString.h" michael@0: #include "nsIRollupListener.h" michael@0: #include "nsIWidget.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIWindowMediator.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "nsIWebBrowserChrome.h" michael@0: #include "nsObjCExceptions.h" michael@0: #include "nsCocoaFeatures.h" michael@0: #include "nsCocoaUtils.h" michael@0: #include "nsChildView.h" michael@0: #include "nsToolkit.h" michael@0: #include "TextInputHandler.h" michael@0: #include "mozilla/HangMonitor.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "pratom.h" michael@0: michael@0: #include "npapi.h" michael@0: michael@0: using namespace mozilla::widget; michael@0: michael@0: // defined in nsCocoaWindow.mm michael@0: extern int32_t gXULModalLevel; michael@0: michael@0: static bool gAppShellMethodsSwizzled = false; michael@0: // List of current Cocoa app-modal windows (nested if more than one). michael@0: nsCocoaAppModalWindowList *gCocoaAppModalWindowList = NULL; michael@0: michael@0: // Push a Cocoa app-modal window onto the top of our list. michael@0: nsresult nsCocoaAppModalWindowList::PushCocoa(NSWindow *aWindow, NSModalSession aSession) michael@0: { michael@0: NS_ENSURE_STATE(aWindow && aSession); michael@0: mList.AppendElement(nsCocoaAppModalWindowListItem(aWindow, aSession)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Pop the topmost Cocoa app-modal window off our list. aWindow and aSession michael@0: // are just used to check that it's what we expect it to be. michael@0: nsresult nsCocoaAppModalWindowList::PopCocoa(NSWindow *aWindow, NSModalSession aSession) michael@0: { michael@0: NS_ENSURE_STATE(aWindow && aSession); michael@0: michael@0: for (int i = mList.Length(); i > 0; --i) { michael@0: nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1); michael@0: if (item.mSession) { michael@0: NS_ASSERTION((item.mWindow == aWindow) && (item.mSession == aSession), michael@0: "PopCocoa() called without matching call to PushCocoa()!"); michael@0: mList.RemoveElementAt(i - 1); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: NS_ERROR("PopCocoa() called without matching call to PushCocoa()!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Push a Gecko-modal window onto the top of our list. michael@0: nsresult nsCocoaAppModalWindowList::PushGecko(NSWindow *aWindow, nsCocoaWindow *aWidget) michael@0: { michael@0: NS_ENSURE_STATE(aWindow && aWidget); michael@0: mList.AppendElement(nsCocoaAppModalWindowListItem(aWindow, aWidget)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Pop the topmost Gecko-modal window off our list. aWindow and aWidget are michael@0: // just used to check that it's what we expect it to be. michael@0: nsresult nsCocoaAppModalWindowList::PopGecko(NSWindow *aWindow, nsCocoaWindow *aWidget) michael@0: { michael@0: NS_ENSURE_STATE(aWindow && aWidget); michael@0: michael@0: for (int i = mList.Length(); i > 0; --i) { michael@0: nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1); michael@0: if (item.mWidget) { michael@0: NS_ASSERTION((item.mWindow == aWindow) && (item.mWidget == aWidget), michael@0: "PopGecko() called without matching call to PushGecko()!"); michael@0: mList.RemoveElementAt(i - 1); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: NS_ERROR("PopGecko() called without matching call to PushGecko()!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // The "current session" is normally the "session" corresponding to the michael@0: // top-most Cocoa app-modal window (both on the screen and in our list). michael@0: // But because Cocoa app-modal dialog can be "interrupted" by a Gecko-modal michael@0: // dialog, the top-most Cocoa app-modal dialog may already have finished michael@0: // (and no longer be visible). In this case we need to check the list for michael@0: // the "next" visible Cocoa app-modal window (and return its "session"), or michael@0: // (if no Cocoa app-modal window is visible) return nil. This way we ensure michael@0: // (as we need to) that all nested Cocoa app-modal sessions are dealt with michael@0: // before we get to any Gecko-modal session(s). See nsAppShell:: michael@0: // ProcessNextNativeEvent() below. michael@0: NSModalSession nsCocoaAppModalWindowList::CurrentSession() michael@0: { michael@0: if (![NSApp _isRunningAppModal]) michael@0: return nil; michael@0: michael@0: NSModalSession currentSession = nil; michael@0: michael@0: for (int i = mList.Length(); i > 0; --i) { michael@0: nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1); michael@0: if (item.mSession && [item.mWindow isVisible]) { michael@0: currentSession = item.mSession; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return currentSession; michael@0: } michael@0: michael@0: // Has a Gecko modal dialog popped up over a Cocoa app-modal dialog? michael@0: bool nsCocoaAppModalWindowList::GeckoModalAboveCocoaModal() michael@0: { michael@0: if (mList.IsEmpty()) michael@0: return false; michael@0: michael@0: nsCocoaAppModalWindowListItem &topItem = mList.ElementAt(mList.Length() - 1); michael@0: michael@0: return (topItem.mWidget != nullptr); michael@0: } michael@0: michael@0: @implementation GeckoNSApplication michael@0: michael@0: - (void)sendEvent:(NSEvent *)anEvent michael@0: { michael@0: mozilla::HangMonitor::NotifyActivity(); michael@0: if ([anEvent type] == NSApplicationDefined && michael@0: [anEvent subtype] == kEventSubtypeTrace) { michael@0: mozilla::SignalTracerThread(); michael@0: return; michael@0: } michael@0: [super sendEvent:anEvent]; michael@0: } michael@0: michael@0: - (NSEvent*)nextEventMatchingMask:(NSUInteger)mask michael@0: untilDate:(NSDate*)expiration michael@0: inMode:(NSString*)mode michael@0: dequeue:(BOOL)flag michael@0: { michael@0: if (expiration) { michael@0: mozilla::HangMonitor::Suspend(); michael@0: } michael@0: return [super nextEventMatchingMask:mask michael@0: untilDate:expiration inMode:mode dequeue:flag]; michael@0: } michael@0: michael@0: @end michael@0: michael@0: michael@0: // AppShellDelegate michael@0: // michael@0: // Cocoa bridge class. An object of this class is registered to receive michael@0: // notifications. michael@0: // michael@0: @interface AppShellDelegate : NSObject michael@0: { michael@0: @private michael@0: nsAppShell* mAppShell; michael@0: } michael@0: michael@0: - (id)initWithAppShell:(nsAppShell*)aAppShell; michael@0: - (void)applicationWillTerminate:(NSNotification*)aNotification; michael@0: - (void)beginMenuTracking:(NSNotification*)aNotification; michael@0: @end michael@0: michael@0: // nsAppShell implementation michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppShell::ResumeNative(void) michael@0: { michael@0: nsresult retval = nsBaseAppShell::ResumeNative(); michael@0: if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) && michael@0: mSkippedNativeCallback) michael@0: { michael@0: mSkippedNativeCallback = false; michael@0: ScheduleNativeEventCallback(); michael@0: } michael@0: return retval; michael@0: } michael@0: michael@0: nsAppShell::nsAppShell() michael@0: : mAutoreleasePools(nullptr) michael@0: , mDelegate(nullptr) michael@0: , mCFRunLoop(NULL) michael@0: , mCFRunLoopSource(NULL) michael@0: , mRunningEventLoop(false) michael@0: , mStarted(false) michael@0: , mTerminated(false) michael@0: , mSkippedNativeCallback(false) michael@0: , mHadMoreEventsCount(0) michael@0: , mRecursionDepth(0) michael@0: , mNativeEventCallbackDepth(0) michael@0: , mNativeEventScheduledDepth(0) michael@0: { michael@0: // A Cocoa event loop is running here if (and only if) we've been embedded michael@0: // by a Cocoa app (like Camino). michael@0: mRunningCocoaEmbedded = [NSApp isRunning] ? true : false; michael@0: } michael@0: michael@0: nsAppShell::~nsAppShell() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (mCFRunLoop) { michael@0: if (mCFRunLoopSource) { michael@0: ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource, michael@0: kCFRunLoopCommonModes); michael@0: ::CFRelease(mCFRunLoopSource); michael@0: } michael@0: ::CFRelease(mCFRunLoop); michael@0: } michael@0: michael@0: if (mAutoreleasePools) { michael@0: NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0, michael@0: "nsAppShell destroyed without popping all autorelease pools"); michael@0: ::CFRelease(mAutoreleasePools); michael@0: } michael@0: michael@0: [mDelegate release]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK michael@0: } michael@0: michael@0: // An undocumented CoreGraphics framework method, present in the same form michael@0: // since at least OS X 10.5. michael@0: extern "C" CGError CGSSetDebugOptions(int options); michael@0: michael@0: // Init michael@0: // michael@0: // Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to michael@0: // interrupt the main native run loop. michael@0: // michael@0: // public michael@0: nsresult michael@0: nsAppShell::Init() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: // No event loop is running yet (unless Camino is running, or another michael@0: // embedding app that uses NSApplicationMain()). michael@0: NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init]; michael@0: michael@0: // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created michael@0: // by |this|. CFArray is used instead of NSArray because NSArray wants to michael@0: // retain each object you add to it, and you can't retain an michael@0: // NSAutoreleasePool. michael@0: mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr); michael@0: NS_ENSURE_STATE(mAutoreleasePools); michael@0: michael@0: // Get the path of the nib file, which lives in the GRE location michael@0: nsCOMPtr nibFile; michael@0: nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(nibFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nibFile->AppendNative(NS_LITERAL_CSTRING("res")); michael@0: nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib")); michael@0: michael@0: nsAutoCString nibPath; michael@0: rv = nibFile->GetNativePath(nibPath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // This call initializes NSApplication unless: michael@0: // 1) we're using xre -- NSApp's already been initialized by michael@0: // MacApplicationDelegate.mm's EnsureUseCocoaDockAPI(). michael@0: // 2) Camino is running (or another embedding app that uses michael@0: // NSApplicationMain()) -- NSApp's already been initialized and michael@0: // its main run loop is already running. michael@0: [NSBundle loadNibFile: michael@0: [NSString stringWithUTF8String:(const char*)nibPath.get()] michael@0: externalNameTable: michael@0: [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication] michael@0: forKey:@"NSOwner"] michael@0: withZone:NSDefaultMallocZone()]; michael@0: michael@0: mDelegate = [[AppShellDelegate alloc] initWithAppShell:this]; michael@0: NS_ENSURE_STATE(mDelegate); michael@0: michael@0: // Add a CFRunLoopSource to the main native run loop. The source is michael@0: // responsible for interrupting the run loop when Gecko events are ready. michael@0: michael@0: mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop]; michael@0: NS_ENSURE_STATE(mCFRunLoop); michael@0: ::CFRetain(mCFRunLoop); michael@0: michael@0: CFRunLoopSourceContext context; michael@0: bzero(&context, sizeof(context)); michael@0: // context.version = 0; michael@0: context.info = this; michael@0: context.perform = ProcessGeckoEvents; michael@0: michael@0: mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); michael@0: NS_ENSURE_STATE(mCFRunLoopSource); michael@0: michael@0: ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes); michael@0: michael@0: rv = nsBaseAppShell::Init(); michael@0: michael@0: #ifndef __LP64__ michael@0: TextInputHandler::InstallPluginKeyEventsHandler(); michael@0: #endif michael@0: michael@0: gCocoaAppModalWindowList = new nsCocoaAppModalWindowList; michael@0: if (!gAppShellMethodsSwizzled) { michael@0: nsToolkit::SwizzleMethods([NSApplication class], @selector(beginModalSessionForWindow:), michael@0: @selector(nsAppShell_NSApplication_beginModalSessionForWindow:)); michael@0: nsToolkit::SwizzleMethods([NSApplication class], @selector(endModalSession:), michael@0: @selector(nsAppShell_NSApplication_endModalSession:)); michael@0: // We should only replace the original terminate: method if we're not michael@0: // running in a Cocoa embedder (like Camino). See bug 604901. michael@0: if (!mRunningCocoaEmbedded) { michael@0: nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:), michael@0: @selector(nsAppShell_NSApplication_terminate:)); michael@0: } michael@0: gAppShellMethodsSwizzled = true; michael@0: } michael@0: michael@0: if (nsCocoaFeatures::OnYosemiteOrLater()) { michael@0: // Explicitly turn off CGEvent logging. This works around bug 1092855. michael@0: // If there are already CGEvents in the log, turning off logging also michael@0: // causes those events to be written to disk. But at this point no michael@0: // CGEvents have yet been processed. CGEvents are events (usually michael@0: // input events) pulled from the WindowServer. An option of 0x80000008 michael@0: // turns on CGEvent logging. michael@0: CGSSetDebugOptions(0x80000007); michael@0: } michael@0: michael@0: [localPool release]; michael@0: michael@0: return rv; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // ProcessGeckoEvents michael@0: // michael@0: // The "perform" target of mCFRunLoop, called when mCFRunLoopSource is michael@0: // signalled from ScheduleNativeEventCallback. michael@0: // michael@0: // Arrange for Gecko events to be processed on demand (in response to a call michael@0: // to ScheduleNativeEventCallback(), if processing of Gecko events via "native michael@0: // methods" hasn't been suspended). This happens in NativeEventCallback(). michael@0: // michael@0: // protected static michael@0: void michael@0: nsAppShell::ProcessGeckoEvents(void* aInfo) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: PROFILER_LABEL("Events", "ProcessGeckoEvents"); michael@0: nsAppShell* self = static_cast (aInfo); michael@0: michael@0: if (self->mRunningEventLoop) { michael@0: self->mRunningEventLoop = false; michael@0: michael@0: // The run loop may be sleeping -- [NSRunLoop runMode:...] michael@0: // won't return until it's given a reason to wake up. Awaken it by michael@0: // posting a bogus event. There's no need to make the event michael@0: // presentable. michael@0: // michael@0: // But _don't_ set windowNumber to '-1' -- that can lead to nasty michael@0: // wierdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of michael@0: // these fake events, because the -1 has gotten changed into the number michael@0: // of an actual NSWindow object, and that NSWindow object has just been michael@0: // destroyed). Setting windowNumber to '0' seems to work fine -- this michael@0: // seems to prevent the OS from ever trying to associate our bogus event michael@0: // with a particular NSWindow object. michael@0: [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined michael@0: location:NSMakePoint(0,0) michael@0: modifierFlags:0 michael@0: timestamp:0 michael@0: windowNumber:0 michael@0: context:NULL michael@0: subtype:kEventSubtypeNone michael@0: data1:0 michael@0: data2:0] michael@0: atStart:NO]; michael@0: } michael@0: michael@0: if (self->mSuspendNativeCount <= 0) { michael@0: ++self->mNativeEventCallbackDepth; michael@0: self->NativeEventCallback(); michael@0: --self->mNativeEventCallbackDepth; michael@0: } else { michael@0: self->mSkippedNativeCallback = true; michael@0: } michael@0: michael@0: // Still needed to fix bug 343033 ("5-10 second delay or hang or crash michael@0: // when quitting Cocoa Firefox"). michael@0: [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined michael@0: location:NSMakePoint(0,0) michael@0: modifierFlags:0 michael@0: timestamp:0 michael@0: windowNumber:0 michael@0: context:NULL michael@0: subtype:kEventSubtypeNone michael@0: data1:0 michael@0: data2:0] michael@0: atStart:NO]; michael@0: michael@0: // Normally every call to ScheduleNativeEventCallback() results in michael@0: // exactly one call to ProcessGeckoEvents(). So each Release() here michael@0: // normally balances exactly one AddRef() in ScheduleNativeEventCallback(). michael@0: // But if Exit() is called just after ScheduleNativeEventCallback(), the michael@0: // corresponding call to ProcessGeckoEvents() will never happen. We check michael@0: // for this possibility in two different places -- here and in Exit() michael@0: // itself. If we find here that Exit() has been called (that mTerminated michael@0: // is true), it's because we've been called recursively, that Exit() was michael@0: // called from self->NativeEventCallback() above, and that we're unwinding michael@0: // the recursion. In this case we'll never be called again, and we balance michael@0: // here any extra calls to ScheduleNativeEventCallback(). michael@0: // michael@0: // When ProcessGeckoEvents() is called recursively, it's because of a michael@0: // call to ScheduleNativeEventCallback() from NativeEventCallback(). We michael@0: // balance the "extra" AddRefs here (rather than always in Exit()) in order michael@0: // to ensure that 'self' stays alive until the end of this method. We also michael@0: // make sure not to finish the balancing until all the recursion has been michael@0: // unwound. michael@0: if (self->mTerminated) { michael@0: int32_t releaseCount = 0; michael@0: if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) { michael@0: releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, michael@0: self->mNativeEventCallbackDepth); michael@0: } michael@0: while (releaseCount-- > self->mNativeEventCallbackDepth) michael@0: self->Release(); michael@0: } else { michael@0: // As best we can tell, every call to ProcessGeckoEvents() is triggered michael@0: // by a call to ScheduleNativeEventCallback(). But we've seen a few michael@0: // (non-reproducible) cases of double-frees that *might* have been caused michael@0: // by spontaneous calls (from the OS) to ProcessGeckoEvents(). So we michael@0: // deal with that possibility here. michael@0: if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) { michael@0: PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0); michael@0: NS_WARNING("Spontaneous call to ProcessGeckoEvents()!"); michael@0: } else { michael@0: self->Release(); michael@0: } michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // WillTerminate michael@0: // michael@0: // Called by the AppShellDelegate when an NSApplicationWillTerminate michael@0: // notification is posted. After this method is called, native events should michael@0: // no longer be processed. The NSApplicationWillTerminate notification is michael@0: // only posted when [NSApp terminate:] is called, which doesn't happen on a michael@0: // "normal" application quit. michael@0: // michael@0: // public michael@0: void michael@0: nsAppShell::WillTerminate() michael@0: { michael@0: if (mTerminated) michael@0: return; michael@0: michael@0: // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called michael@0: // from [MacApplicationDelegate applicationShouldTerminate:]) gets run. michael@0: NS_ProcessPendingEvents(NS_GetCurrentThread()); michael@0: michael@0: mTerminated = true; michael@0: } michael@0: michael@0: // ScheduleNativeEventCallback michael@0: // michael@0: // Called (possibly on a non-main thread) when Gecko has an event that michael@0: // needs to be processed. The Gecko event needs to be processed on the michael@0: // main thread, so the native run loop must be interrupted. michael@0: // michael@0: // In nsBaseAppShell.cpp, the mNativeEventPending variable is used to michael@0: // ensure that ScheduleNativeEventCallback() is called no more than once michael@0: // per call to NativeEventCallback(). ProcessGeckoEvents() can skip its michael@0: // call to NativeEventCallback() if processing of Gecko events by native michael@0: // means is suspended (using nsIAppShell::SuspendNative()), which will michael@0: // suspend calls from nsBaseAppShell::OnDispatchedEvent() to michael@0: // ScheduleNativeEventCallback(). But when Gecko event processing by michael@0: // native means is resumed (in ResumeNative()), an extra call is made to michael@0: // ScheduleNativeEventCallback() (from ResumeNative()). This triggers michael@0: // another call to ProcessGeckoEvents(), which calls NativeEventCallback(), michael@0: // and nsBaseAppShell::OnDispatchedEvent() resumes calling michael@0: // ScheduleNativeEventCallback(). michael@0: // michael@0: // protected virtual michael@0: void michael@0: nsAppShell::ScheduleNativeEventCallback() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (mTerminated) michael@0: return; michael@0: michael@0: // Each AddRef() here is normally balanced by exactly one Release() in michael@0: // ProcessGeckoEvents(). But there are exceptions, for which see michael@0: // ProcessGeckoEvents() and Exit(). michael@0: NS_ADDREF_THIS(); michael@0: PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth); michael@0: michael@0: // This will invoke ProcessGeckoEvents on the main thread. michael@0: ::CFRunLoopSourceSignal(mCFRunLoopSource); michael@0: ::CFRunLoopWakeUp(mCFRunLoop); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // ProcessNextNativeEvent michael@0: // michael@0: // If aMayWait is false, process a single native event. If it is true, run michael@0: // the native run loop until stopped by ProcessGeckoEvents. michael@0: // michael@0: // Returns true if more events are waiting in the native event queue. michael@0: // michael@0: // But (now that we're using [NSRunLoop runMode:beforeDate:]) it's too michael@0: // expensive to call ProcessNextNativeEvent() many times in a row (in a michael@0: // tight loop), so we never return true more than kHadMoreEventsCountMax michael@0: // times in a row. This doesn't seem to cause native event starvation. michael@0: // michael@0: // protected virtual michael@0: bool michael@0: nsAppShell::ProcessNextNativeEvent(bool aMayWait) michael@0: { michael@0: bool moreEvents = false; michael@0: michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: bool eventProcessed = false; michael@0: NSString* currentMode = nil; michael@0: michael@0: if (mTerminated) michael@0: return false; michael@0: michael@0: // We don't want any native events to be processed here (via Gecko) while michael@0: // Cocoa is displaying an app-modal dialog (as opposed to a window-modal michael@0: // "sheet" or a Gecko-modal dialog). Otherwise Cocoa event-processing loops michael@0: // may be interrupted, and inappropriate events may get through to the michael@0: // browser window(s) underneath. This resolves bmo bugs 419668 and 420967. michael@0: // michael@0: // But we need more complex handling (we need to make an exception) if a michael@0: // Gecko modal dialog is running above the Cocoa app-modal dialog -- for michael@0: // which see below. michael@0: if ([NSApp _isRunningAppModal] && michael@0: (!gCocoaAppModalWindowList || !gCocoaAppModalWindowList->GeckoModalAboveCocoaModal())) michael@0: return false; michael@0: michael@0: bool wasRunningEventLoop = mRunningEventLoop; michael@0: mRunningEventLoop = aMayWait; michael@0: NSDate* waitUntil = nil; michael@0: if (aMayWait) michael@0: waitUntil = [NSDate distantFuture]; michael@0: michael@0: NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop]; michael@0: michael@0: do { michael@0: // No autorelease pool is provided here, because OnProcessNextEvent michael@0: // and AfterProcessNextEvent are responsible for maintaining it. michael@0: NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools), michael@0: "No autorelease pool for native event"); michael@0: michael@0: // If an event is waiting to be processed, run the main event loop michael@0: // just long enough to process it. For some reason, using [NSApp michael@0: // nextEventMatchingMask:...] to dequeue the event and [NSApp sendEvent:] michael@0: // to "send" it causes trouble, so we no longer do that. (The trouble michael@0: // was very strange, and only happened while processing Gecko events on michael@0: // demand (via ProcessGeckoEvents()), as opposed to processing Gecko michael@0: // events in a tight loop (via nsBaseAppShell::Run()): Particularly in michael@0: // Camino, mouse-down events sometimes got dropped (or mis-handled), so michael@0: // that (for example) you sometimes needed to click more than once on a michael@0: // button to make it work (the zoom button was particularly susceptible). michael@0: // You also sometimes had to ctrl-click or right-click multiple times to michael@0: // bring up a context menu.) michael@0: michael@0: // Now that we're using [NSRunLoop runMode:beforeDate:], it's too michael@0: // expensive to call ProcessNextNativeEvent() many times in a row, so we michael@0: // never return true more than kHadMoreEventsCountMax in a row. I'm not michael@0: // entirely sure why [NSRunLoop runMode:beforeDate:] is too expensive, michael@0: // since it and its cousin [NSRunLoop acceptInputForMode:beforeDate:] are michael@0: // designed to be called in a tight loop. Possibly the problem is due to michael@0: // combining [NSRunLoop runMode:beforeDate] with [NSApp michael@0: // nextEventMatchingMask:...]. michael@0: michael@0: // We special-case timer events (events of type NSPeriodic) to avoid michael@0: // starving them. Apple's documentation is very scanty, and it's now michael@0: // more scanty than it used to be. But it appears that [NSRunLoop michael@0: // acceptInputForMode:beforeDate:] doesn't process timer events at all, michael@0: // that it is called from [NSRunLoop runMode:beforeDate:], and that michael@0: // [NSRunLoop runMode:beforeDate:], though it does process timer events, michael@0: // doesn't return after doing so. To get around this, when aWait is michael@0: // false we check for timer events and process them using [NSApp michael@0: // sendEvent:]. When aWait is true [NSRunLoop runMode:beforeDate:] michael@0: // will only return on a "real" event. But there's code in michael@0: // ProcessGeckoEvents() that should (when need be) wake us up by sending michael@0: // a "fake" "real" event. (See Apple's current doc on [NSRunLoop michael@0: // runMode:beforeDate:] and a quote from what appears to be an older michael@0: // version of this doc at michael@0: // http://lists.apple.com/archives/cocoa-dev/2001/May/msg00559.html.) michael@0: michael@0: // If the current mode is something else than NSDefaultRunLoopMode, look michael@0: // for events in that mode. michael@0: currentMode = [currentRunLoop currentMode]; michael@0: if (!currentMode) michael@0: currentMode = NSDefaultRunLoopMode; michael@0: michael@0: NSEvent* nextEvent = nil; michael@0: michael@0: if (aMayWait) { michael@0: mozilla::HangMonitor::Suspend(); michael@0: } michael@0: michael@0: // If we're running modal (or not in a Gecko "main" event loop) we still michael@0: // need to use nextEventMatchingMask and sendEvent -- otherwise (in michael@0: // Minefield) the modal window (or non-main event loop) won't receive key michael@0: // events or most mouse events. michael@0: // michael@0: // Add aMayWait to minimize the number of calls to -[NSApp sendEvent:] michael@0: // made from nsAppShell::ProcessNextNativeEvent() (and indirectly from michael@0: // nsBaseAppShell::OnProcessNextEvent()), to work around bug 959281. michael@0: if ([NSApp _isRunningModal] || (aMayWait && !InGeckoMainEventLoop())) { michael@0: if ((nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask michael@0: untilDate:waitUntil michael@0: inMode:currentMode michael@0: dequeue:YES])) { michael@0: // If we're in a Cocoa app-modal session that's been interrupted by a michael@0: // Gecko-modal dialog, send the event to the Cocoa app-modal dialog's michael@0: // session. This ensures that the app-modal session won't be starved michael@0: // of events, and fixes bugs 463473 and 442442. (The case of an michael@0: // ordinary Cocoa app-modal dialog has been dealt with above.) michael@0: // michael@0: // Otherwise (if we're in an ordinary Gecko-modal dialog, or if we're michael@0: // otherwise not in a Gecko main event loop), process the event as michael@0: // expected. michael@0: NSModalSession currentAppModalSession = nil; michael@0: if (gCocoaAppModalWindowList) michael@0: currentAppModalSession = gCocoaAppModalWindowList->CurrentSession(); michael@0: michael@0: mozilla::HangMonitor::NotifyActivity(); michael@0: michael@0: if (currentAppModalSession) { michael@0: [NSApp _modalSession:currentAppModalSession sendEvent:nextEvent]; michael@0: } else { michael@0: [NSApp sendEvent:nextEvent]; michael@0: } michael@0: eventProcessed = true; michael@0: } michael@0: } else { michael@0: if (aMayWait || michael@0: (nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask michael@0: untilDate:nil michael@0: inMode:currentMode michael@0: dequeue:NO])) { michael@0: if (nextEvent && ([nextEvent type] == NSPeriodic)) { michael@0: nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask michael@0: untilDate:waitUntil michael@0: inMode:currentMode michael@0: dequeue:YES]; michael@0: [NSApp sendEvent:nextEvent]; michael@0: } else { michael@0: [currentRunLoop runMode:currentMode beforeDate:waitUntil]; michael@0: } michael@0: eventProcessed = true; michael@0: } michael@0: } michael@0: } while (mRunningEventLoop); michael@0: michael@0: if (eventProcessed && (mHadMoreEventsCount < kHadMoreEventsCountMax)) { michael@0: moreEvents = ([NSApp nextEventMatchingMask:NSAnyEventMask michael@0: untilDate:nil michael@0: inMode:currentMode michael@0: dequeue:NO] != nil); michael@0: } michael@0: michael@0: if (moreEvents) { michael@0: // Once this reaches kHadMoreEventsCountMax, it will be reset to 0 the michael@0: // next time through (whether or not we process any events then). michael@0: ++mHadMoreEventsCount; michael@0: } else { michael@0: mHadMoreEventsCount = 0; michael@0: } michael@0: michael@0: mRunningEventLoop = wasRunningEventLoop; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: michael@0: if (!moreEvents) { michael@0: nsChildView::UpdateCurrentInputEventCount(); michael@0: } michael@0: michael@0: return moreEvents; michael@0: } michael@0: michael@0: // Returns true if Gecko events are currently being processed in its "main" michael@0: // event loop (or one of its "main" event loops). Returns false if Gecko michael@0: // events are being processed in a "nested" event loop, or if we're not michael@0: // running in any sort of Gecko event loop. How we process native events in michael@0: // ProcessNextNativeEvent() turns on our decision (and if we make the wrong michael@0: // choice, the result may be a hang). michael@0: // michael@0: // We define the "main" event loop(s) as the place (or places) where Gecko michael@0: // event processing "normally" takes place, and all other Gecko event loops michael@0: // as "nested". The "nested" event loops are normally processed while a call michael@0: // from a "main" event loop is on the stack ... but not always. For example, michael@0: // the Venkman JavaScript debugger runs a "nested" event loop (in jsdService:: michael@0: // EnterNestedEventLoop()) whenever it breaks into the current script. But michael@0: // if this happens as the result of the user pressing a key combination, there michael@0: // won't be any other Gecko event-processing call on the stack (e.g. michael@0: // NS_ProcessNextEvent() or NS_ProcessPendingEvents()). (In the current michael@0: // nsAppShell implementation, what counts as the "main" event loop is what michael@0: // nsBaseAppShell::NativeEventCallback() does to process Gecko events. We michael@0: // don't currently use nsBaseAppShell::Run().) michael@0: bool michael@0: nsAppShell::InGeckoMainEventLoop() michael@0: { michael@0: if ((gXULModalLevel > 0) || (mRecursionDepth > 0)) michael@0: return false; michael@0: if (mNativeEventCallbackDepth <= 0) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: // Run michael@0: // michael@0: // Overrides the base class's Run() method to call [NSApp run] (which spins michael@0: // the native run loop until the application quits). Since (unlike the base michael@0: // class's Run() method) we don't process any Gecko events here, they need michael@0: // to be processed elsewhere (in NativeEventCallback(), called from michael@0: // ProcessGeckoEvents()). michael@0: // michael@0: // Camino calls [NSApp run] on its own (via NSApplicationMain()), and so michael@0: // doesn't call nsAppShell::Run(). michael@0: // michael@0: // public michael@0: NS_IMETHODIMP michael@0: nsAppShell::Run(void) michael@0: { michael@0: NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times"); michael@0: if (mStarted || mTerminated) michael@0: return NS_OK; michael@0: michael@0: mStarted = true; michael@0: NS_OBJC_TRY_ABORT([NSApp run]); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppShell::Exit(void) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: // This method is currently called more than once -- from (according to michael@0: // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an michael@0: // XPCOM shutdown notification that nsBaseAppShell has registered to michael@0: // receive. So we need to ensure that multiple calls won't break anything. michael@0: // But we should also complain about it (since it isn't quite kosher). michael@0: if (mTerminated) { michael@0: NS_WARNING("nsAppShell::Exit() called redundantly"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mTerminated = true; michael@0: michael@0: delete gCocoaAppModalWindowList; michael@0: gCocoaAppModalWindowList = NULL; michael@0: michael@0: #ifndef __LP64__ michael@0: TextInputHandler::RemovePluginKeyEventsHandler(); michael@0: #endif michael@0: michael@0: // Quoting from Apple's doc on the [NSApplication stop:] method (from their michael@0: // doc on the NSApplication class): "If this method is invoked during a michael@0: // modal event loop, it will break that loop but not the main event loop." michael@0: // nsAppShell::Exit() shouldn't be called from a modal event loop. So if michael@0: // it is we complain about it (to users of debug builds) and call [NSApp michael@0: // stop:] one extra time. (I'm not sure if modal event loops can be nested michael@0: // -- Apple's docs don't say one way or the other. But the return value michael@0: // of [NSApp _isRunningModal] doesn't change immediately after a call to michael@0: // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:] michael@0: // will do the job.) michael@0: BOOL cocoaModal = [NSApp _isRunningModal]; michael@0: NS_ASSERTION(!cocoaModal, michael@0: "Don't call nsAppShell::Exit() from a modal event loop!"); michael@0: if (cocoaModal) michael@0: [NSApp stop:nullptr]; michael@0: [NSApp stop:nullptr]; michael@0: michael@0: // A call to Exit() just after a call to ScheduleNativeEventCallback() michael@0: // prevents the (normally) matching call to ProcessGeckoEvents() from michael@0: // happening. If we've been called from ProcessGeckoEvents() (as usually michael@0: // happens), we take care of it there. But if we have an unbalanced call michael@0: // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the michael@0: // stack, we need to take care of the problem here. michael@0: if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) { michael@0: int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0); michael@0: while (releaseCount-- > 0) michael@0: NS_RELEASE_THIS(); michael@0: } michael@0: michael@0: return nsBaseAppShell::Exit(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // OnProcessNextEvent michael@0: // michael@0: // This nsIThreadObserver method is called prior to processing an event. michael@0: // Set up an autorelease pool that will service any autoreleased Cocoa michael@0: // objects during this event. This includes native events processed by michael@0: // ProcessNextNativeEvent. The autorelease pool will be popped by michael@0: // AfterProcessNextEvent, it is important for these two methods to be michael@0: // tightly coupled. michael@0: // michael@0: // public michael@0: NS_IMETHODIMP michael@0: nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait, michael@0: uint32_t aRecursionDepth) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: mRecursionDepth = aRecursionDepth; michael@0: michael@0: NS_ASSERTION(mAutoreleasePools, michael@0: "No stack on which to store autorelease pool"); michael@0: michael@0: NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; michael@0: ::CFArrayAppendValue(mAutoreleasePools, pool); michael@0: michael@0: return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait, aRecursionDepth); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // AfterProcessNextEvent michael@0: // michael@0: // This nsIThreadObserver method is called after event processing is complete. michael@0: // The Cocoa implementation cleans up the autorelease pool create by the michael@0: // previous OnProcessNextEvent call. michael@0: // michael@0: // public michael@0: NS_IMETHODIMP michael@0: nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread, michael@0: uint32_t aRecursionDepth, michael@0: bool aEventWasProcessed) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: mRecursionDepth = aRecursionDepth; michael@0: michael@0: CFIndex count = ::CFArrayGetCount(mAutoreleasePools); michael@0: michael@0: NS_ASSERTION(mAutoreleasePools && count, michael@0: "Processed an event, but there's no autorelease pool?"); michael@0: michael@0: const NSAutoreleasePool* pool = static_cast michael@0: (::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1)); michael@0: ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1); michael@0: [pool release]; michael@0: michael@0: return nsBaseAppShell::AfterProcessNextEvent(aThread, aRecursionDepth, michael@0: aEventWasProcessed); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: michael@0: // AppShellDelegate implementation michael@0: michael@0: michael@0: @implementation AppShellDelegate michael@0: // initWithAppShell: michael@0: // michael@0: // Constructs the AppShellDelegate object michael@0: - (id)initWithAppShell:(nsAppShell*)aAppShell michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: if ((self = [self init])) { michael@0: mAppShell = aAppShell; michael@0: michael@0: [[NSNotificationCenter defaultCenter] addObserver:self michael@0: selector:@selector(applicationWillTerminate:) michael@0: name:NSApplicationWillTerminateNotification michael@0: object:NSApp]; michael@0: [[NSNotificationCenter defaultCenter] addObserver:self michael@0: selector:@selector(applicationDidBecomeActive:) michael@0: name:NSApplicationDidBecomeActiveNotification michael@0: object:NSApp]; michael@0: [[NSDistributedNotificationCenter defaultCenter] addObserver:self michael@0: selector:@selector(beginMenuTracking:) michael@0: name:@"com.apple.HIToolbox.beginMenuTrackingNotification" michael@0: object:nil]; michael@0: } michael@0: michael@0: return self; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (void)dealloc michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: [[NSNotificationCenter defaultCenter] removeObserver:self]; michael@0: [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; michael@0: [super dealloc]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // applicationWillTerminate: michael@0: // michael@0: // Notify the nsAppShell that native event processing should be discontinued. michael@0: - (void)applicationWillTerminate:(NSNotification*)aNotification michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: mAppShell->WillTerminate(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // applicationDidBecomeActive michael@0: // michael@0: // Make sure TextInputHandler::sLastModifierState is updated when we become michael@0: // active (since we won't have received [ChildView flagsChanged:] messages michael@0: // while inactive). michael@0: - (void)applicationDidBecomeActive:(NSNotification*)aNotification michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // [NSEvent modifierFlags] is valid on every kind of event, so we don't need michael@0: // to worry about getting an NSInternalInconsistencyException here. michael@0: NSEvent* currentEvent = [NSApp currentEvent]; michael@0: if (currentEvent) { michael@0: TextInputHandler::sLastModifierState = michael@0: [currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // beginMenuTracking michael@0: // michael@0: // Roll up our context menu (if any) when some other app (or the OS) opens michael@0: // any sort of menu. But make sure we don't do this for notifications we michael@0: // send ourselves (whose 'sender' will be @"org.mozilla.gecko.PopupWindow"). michael@0: - (void)beginMenuTracking:(NSNotification*)aNotification michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NSString *sender = [aNotification object]; michael@0: if (!sender || ![sender isEqualToString:@"org.mozilla.gecko.PopupWindow"]) { michael@0: nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); michael@0: nsCOMPtr rollupWidget = rollupListener->GetRollupWidget(); michael@0: if (rollupWidget) michael@0: rollupListener->Rollup(0, nullptr, nullptr); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: @end michael@0: michael@0: // We hook beginModalSessionForWindow: and endModalSession: in order to michael@0: // maintain a list of Cocoa app-modal windows (and the "sessions" to which michael@0: // they correspond). We need this in order to deal with the consequences michael@0: // of a Cocoa app-modal dialog being "interrupted" by a Gecko-modal dialog. michael@0: // See nsCocoaAppModalWindowList::CurrentSession() and michael@0: // nsAppShell::ProcessNextNativeEvent() above. michael@0: // michael@0: // We hook terminate: in order to make OS-initiated termination work nicely michael@0: // with Gecko's shutdown sequence. (Two ways to trigger OS-initiated michael@0: // termination: 1) Quit from the Dock menu; 2) Log out from (or shut down) michael@0: // your computer while the browser is active.) michael@0: @interface NSApplication (MethodSwizzling) michael@0: - (NSModalSession)nsAppShell_NSApplication_beginModalSessionForWindow:(NSWindow *)aWindow; michael@0: - (void)nsAppShell_NSApplication_endModalSession:(NSModalSession)aSession; michael@0: - (void)nsAppShell_NSApplication_terminate:(id)sender; michael@0: @end michael@0: michael@0: @implementation NSApplication (MethodSwizzling) michael@0: michael@0: // Called if and only if a Cocoa app-modal session is beginning. Always call michael@0: // gCocoaAppModalWindowList->PushCocoa() here (if gCocoaAppModalWindowList is michael@0: // non-nil). michael@0: - (NSModalSession)nsAppShell_NSApplication_beginModalSessionForWindow:(NSWindow *)aWindow michael@0: { michael@0: NSModalSession session = michael@0: [self nsAppShell_NSApplication_beginModalSessionForWindow:aWindow]; michael@0: if (gCocoaAppModalWindowList) michael@0: gCocoaAppModalWindowList->PushCocoa(aWindow, session); michael@0: return session; michael@0: } michael@0: michael@0: // Called to end any Cocoa modal session (app-modal or otherwise). Only call michael@0: // gCocoaAppModalWindowList->PopCocoa() when an app-modal session is ending michael@0: // (and when gCocoaAppModalWindowList is non-nil). michael@0: - (void)nsAppShell_NSApplication_endModalSession:(NSModalSession)aSession michael@0: { michael@0: BOOL wasRunningAppModal = [NSApp _isRunningAppModal]; michael@0: NSWindow *prevAppModalWindow = [NSApp modalWindow]; michael@0: [self nsAppShell_NSApplication_endModalSession:aSession]; michael@0: if (gCocoaAppModalWindowList && michael@0: wasRunningAppModal && (prevAppModalWindow != [NSApp modalWindow])) michael@0: gCocoaAppModalWindowList->PopCocoa(prevAppModalWindow, aSession); michael@0: } michael@0: michael@0: // Called by the OS after [MacApplicationDelegate applicationShouldTerminate:] michael@0: // has returned NSTerminateNow. This method "subclasses" and replaces the michael@0: // OS's original implementation. The only thing the orginal method does which michael@0: // we need is that it posts NSApplicationWillTerminateNotification. Everything michael@0: // else is unneeded (because it's handled elsewhere), or actively interferes michael@0: // with Gecko's shutdown sequence. For example the original terminate: method michael@0: // causes the app to exit() inside [NSApp run] (called from nsAppShell::Run() michael@0: // above), which means that nothing runs after the call to nsAppStartup::Run() michael@0: // in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor michael@0: // and NS_ShutdownXPCOM() never get called. michael@0: - (void)nsAppShell_NSApplication_terminate:(id)sender michael@0: { michael@0: [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification michael@0: object:NSApp]; michael@0: } michael@0: michael@0: @end