Bug 996848 - Rewrite Mac nsAppShell native event handling. r=spohl

This commit is contained in:
Steven Michaud 2014-05-07 11:13:27 -05:00
parent 6198b6623c
commit e10c706955
3 changed files with 62 additions and 353 deletions

View File

@ -16,36 +16,6 @@ class nsCocoaWindow;
#include "nsBaseAppShell.h" #include "nsBaseAppShell.h"
#include "nsTArray.h" #include "nsTArray.h"
typedef struct _nsCocoaAppModalWindowListItem {
_nsCocoaAppModalWindowListItem(NSWindow *aWindow, NSModalSession aSession) :
mWindow(aWindow), mSession(aSession), mWidget(nullptr) {}
_nsCocoaAppModalWindowListItem(NSWindow *aWindow, nsCocoaWindow *aWidget) :
mWindow(aWindow), mSession(nil), mWidget(aWidget) {}
NSWindow *mWindow; // Weak
NSModalSession mSession; // Weak (not retainable)
nsCocoaWindow *mWidget; // Weak
} nsCocoaAppModalWindowListItem;
class nsCocoaAppModalWindowList {
public:
nsCocoaAppModalWindowList() {}
~nsCocoaAppModalWindowList() {}
// Push a Cocoa app-modal window onto the top of our list.
nsresult PushCocoa(NSWindow *aWindow, NSModalSession aSession);
// Pop the topmost Cocoa app-modal window off our list.
nsresult PopCocoa(NSWindow *aWindow, NSModalSession aSession);
// Push a Gecko-modal window onto the top of our list.
nsresult PushGecko(NSWindow *aWindow, nsCocoaWindow *aWidget);
// Pop the topmost Gecko-modal window off our list.
nsresult PopGecko(NSWindow *aWindow, nsCocoaWindow *aWidget);
// Return the "session" of the top-most visible Cocoa app-modal window.
NSModalSession CurrentSession();
// Has a Gecko modal dialog popped up over a Cocoa app-modal dialog?
bool GeckoModalAboveCocoaModal();
private:
nsTArray<nsCocoaAppModalWindowListItem> mList;
};
// GeckoNSApplication // GeckoNSApplication
// //
// Subclass of NSApplication for filtering out certain events. // Subclass of NSApplication for filtering out certain events.
@ -82,8 +52,6 @@ protected:
virtual void ScheduleNativeEventCallback(); virtual void ScheduleNativeEventCallback();
virtual bool ProcessNextNativeEvent(bool aMayWait); virtual bool ProcessNextNativeEvent(bool aMayWait);
bool InGeckoMainEventLoop();
static void ProcessGeckoEvents(void* aInfo); static void ProcessGeckoEvents(void* aInfo);
protected: protected:
@ -99,17 +67,6 @@ protected:
bool mSkippedNativeCallback; bool mSkippedNativeCallback;
bool mRunningCocoaEmbedded; bool mRunningCocoaEmbedded;
// mHadMoreEventsCount and kHadMoreEventsCountMax are used in
// ProcessNextNativeEvent().
uint32_t mHadMoreEventsCount;
// Setting kHadMoreEventsCountMax to '10' contributed to a fairly large
// (about 10%) increase in the number of calls to malloc (though without
// effecting the total amount of memory used). Cutting it to '3'
// reduced the number of calls by 6%-7% (reducing the original regression
// to 3%-4%). See bmo bug 395397.
static const uint32_t kHadMoreEventsCountMax = 3;
int32_t mRecursionDepth;
int32_t mNativeEventCallbackDepth; int32_t mNativeEventCallbackDepth;
// Can be set from different threads, so must be modified atomically // Can be set from different threads, so must be modified atomically
int32_t mNativeEventScheduledDepth; int32_t mNativeEventScheduledDepth;

View File

@ -43,103 +43,6 @@ using namespace mozilla::widget;
extern int32_t gXULModalLevel; extern int32_t gXULModalLevel;
static bool gAppShellMethodsSwizzled = false; static bool gAppShellMethodsSwizzled = false;
// List of current Cocoa app-modal windows (nested if more than one).
nsCocoaAppModalWindowList *gCocoaAppModalWindowList = NULL;
// Push a Cocoa app-modal window onto the top of our list.
nsresult nsCocoaAppModalWindowList::PushCocoa(NSWindow *aWindow, NSModalSession aSession)
{
NS_ENSURE_STATE(aWindow && aSession);
mList.AppendElement(nsCocoaAppModalWindowListItem(aWindow, aSession));
return NS_OK;
}
// Pop the topmost Cocoa app-modal window off our list. aWindow and aSession
// are just used to check that it's what we expect it to be.
nsresult nsCocoaAppModalWindowList::PopCocoa(NSWindow *aWindow, NSModalSession aSession)
{
NS_ENSURE_STATE(aWindow && aSession);
for (int i = mList.Length(); i > 0; --i) {
nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1);
if (item.mSession) {
NS_ASSERTION((item.mWindow == aWindow) && (item.mSession == aSession),
"PopCocoa() called without matching call to PushCocoa()!");
mList.RemoveElementAt(i - 1);
return NS_OK;
}
}
NS_ERROR("PopCocoa() called without matching call to PushCocoa()!");
return NS_ERROR_FAILURE;
}
// Push a Gecko-modal window onto the top of our list.
nsresult nsCocoaAppModalWindowList::PushGecko(NSWindow *aWindow, nsCocoaWindow *aWidget)
{
NS_ENSURE_STATE(aWindow && aWidget);
mList.AppendElement(nsCocoaAppModalWindowListItem(aWindow, aWidget));
return NS_OK;
}
// Pop the topmost Gecko-modal window off our list. aWindow and aWidget are
// just used to check that it's what we expect it to be.
nsresult nsCocoaAppModalWindowList::PopGecko(NSWindow *aWindow, nsCocoaWindow *aWidget)
{
NS_ENSURE_STATE(aWindow && aWidget);
for (int i = mList.Length(); i > 0; --i) {
nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1);
if (item.mWidget) {
NS_ASSERTION((item.mWindow == aWindow) && (item.mWidget == aWidget),
"PopGecko() called without matching call to PushGecko()!");
mList.RemoveElementAt(i - 1);
return NS_OK;
}
}
NS_ERROR("PopGecko() called without matching call to PushGecko()!");
return NS_ERROR_FAILURE;
}
// The "current session" is normally the "session" corresponding to the
// top-most Cocoa app-modal window (both on the screen and in our list).
// But because Cocoa app-modal dialog can be "interrupted" by a Gecko-modal
// dialog, the top-most Cocoa app-modal dialog may already have finished
// (and no longer be visible). In this case we need to check the list for
// the "next" visible Cocoa app-modal window (and return its "session"), or
// (if no Cocoa app-modal window is visible) return nil. This way we ensure
// (as we need to) that all nested Cocoa app-modal sessions are dealt with
// before we get to any Gecko-modal session(s). See nsAppShell::
// ProcessNextNativeEvent() below.
NSModalSession nsCocoaAppModalWindowList::CurrentSession()
{
if (![NSApp _isRunningAppModal])
return nil;
NSModalSession currentSession = nil;
for (int i = mList.Length(); i > 0; --i) {
nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1);
if (item.mSession && [item.mWindow isVisible]) {
currentSession = item.mSession;
break;
}
}
return currentSession;
}
// Has a Gecko modal dialog popped up over a Cocoa app-modal dialog?
bool nsCocoaAppModalWindowList::GeckoModalAboveCocoaModal()
{
if (mList.IsEmpty())
return false;
nsCocoaAppModalWindowListItem &topItem = mList.ElementAt(mList.Length() - 1);
return (topItem.mWidget != nullptr);
}
@implementation GeckoNSApplication @implementation GeckoNSApplication
@ -209,8 +112,6 @@ nsAppShell::nsAppShell()
, mStarted(false) , mStarted(false)
, mTerminated(false) , mTerminated(false)
, mSkippedNativeCallback(false) , mSkippedNativeCallback(false)
, mHadMoreEventsCount(0)
, mRecursionDepth(0)
, mNativeEventCallbackDepth(0) , mNativeEventCallbackDepth(0)
, mNativeEventScheduledDepth(0) , mNativeEventScheduledDepth(0)
{ {
@ -317,12 +218,7 @@ nsAppShell::Init()
TextInputHandler::InstallPluginKeyEventsHandler(); TextInputHandler::InstallPluginKeyEventsHandler();
#endif #endif
gCocoaAppModalWindowList = new nsCocoaAppModalWindowList;
if (!gAppShellMethodsSwizzled) { if (!gAppShellMethodsSwizzled) {
nsToolkit::SwizzleMethods([NSApplication class], @selector(beginModalSessionForWindow:),
@selector(nsAppShell_NSApplication_beginModalSessionForWindow:));
nsToolkit::SwizzleMethods([NSApplication class], @selector(endModalSession:),
@selector(nsAppShell_NSApplication_endModalSession:));
// We should only replace the original terminate: method if we're not // We should only replace the original terminate: method if we're not
// running in a Cocoa embedder (like Camino). See bug 604901. // running in a Cocoa embedder (like Camino). See bug 604901.
if (!mRunningCocoaEmbedded) { if (!mRunningCocoaEmbedded) {
@ -391,8 +287,7 @@ nsAppShell::ProcessGeckoEvents(void* aInfo)
self->mSkippedNativeCallback = true; self->mSkippedNativeCallback = true;
} }
// Still needed to fix bug 343033 ("5-10 second delay or hang or crash // Still needed to avoid crashes on quit in most Mochitests.
// when quitting Cocoa Firefox").
[NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
location:NSMakePoint(0,0) location:NSMakePoint(0,0)
modifierFlags:0 modifierFlags:0
@ -510,6 +405,10 @@ nsAppShell::ScheduleNativeEventCallback()
NS_OBJC_END_TRY_ABORT_BLOCK; 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 // ProcessNextNativeEvent
// //
// If aMayWait is false, process a single native event. If it is true, run // If aMayWait is false, process a single native event. If it is true, run
@ -517,11 +416,6 @@ nsAppShell::ScheduleNativeEventCallback()
// //
// Returns true if more events are waiting in the native event queue. // Returns true if more events are waiting in the native event queue.
// //
// But (now that we're using [NSRunLoop runMode:beforeDate:]) it's too
// expensive to call ProcessNextNativeEvent() many times in a row (in a
// tight loop), so we never return true more than kHadMoreEventsCountMax
// times in a row. This doesn't seem to cause native event starvation.
//
// protected virtual // protected virtual
bool bool
nsAppShell::ProcessNextNativeEvent(bool aMayWait) nsAppShell::ProcessNextNativeEvent(bool aMayWait)
@ -536,19 +430,6 @@ nsAppShell::ProcessNextNativeEvent(bool aMayWait)
if (mTerminated) if (mTerminated)
return false; return false;
// We don't want any native events to be processed here (via Gecko) while
// Cocoa is displaying an app-modal dialog (as opposed to a window-modal
// "sheet" or a Gecko-modal dialog). Otherwise Cocoa event-processing loops
// may be interrupted, and inappropriate events may get through to the
// browser window(s) underneath. This resolves bmo bugs 419668 and 420967.
//
// But we need more complex handling (we need to make an exception) if a
// Gecko modal dialog is running above the Cocoa app-modal dialog -- for
// which see below.
if ([NSApp _isRunningAppModal] &&
(!gCocoaAppModalWindowList || !gCocoaAppModalWindowList->GeckoModalAboveCocoaModal()))
return false;
bool wasRunningEventLoop = mRunningEventLoop; bool wasRunningEventLoop = mRunningEventLoop;
mRunningEventLoop = aMayWait; mRunningEventLoop = aMayWait;
NSDate* waitUntil = nil; NSDate* waitUntil = nil;
@ -557,130 +438,78 @@ nsAppShell::ProcessNextNativeEvent(bool aMayWait)
NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop]; NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
EventQueueRef currentEventQueue = GetCurrentEventQueue();
EventTargetRef eventDispatcherTarget = GetEventDispatcherTarget();
if (aMayWait) {
mozilla::HangMonitor::Suspend();
}
// 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 { do {
// No autorelease pool is provided here, because OnProcessNextEvent // No autorelease pool is provided here, because OnProcessNextEvent
// and AfterProcessNextEvent are responsible for maintaining it. // and AfterProcessNextEvent are responsible for maintaining it.
NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools), NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
"No autorelease pool for native event"); "No autorelease pool for native event");
// If an event is waiting to be processed, run the main event loop
// just long enough to process it. For some reason, using [NSApp
// nextEventMatchingMask:...] to dequeue the event and [NSApp sendEvent:]
// to "send" it causes trouble, so we no longer do that. (The trouble
// was very strange, and only happened while processing Gecko events on
// demand (via ProcessGeckoEvents()), as opposed to processing Gecko
// events in a tight loop (via nsBaseAppShell::Run()): Particularly in
// Camino, mouse-down events sometimes got dropped (or mis-handled), so
// that (for example) you sometimes needed to click more than once on a
// button to make it work (the zoom button was particularly susceptible).
// You also sometimes had to ctrl-click or right-click multiple times to
// bring up a context menu.)
// Now that we're using [NSRunLoop runMode:beforeDate:], it's too
// expensive to call ProcessNextNativeEvent() many times in a row, so we
// never return true more than kHadMoreEventsCountMax in a row. I'm not
// entirely sure why [NSRunLoop runMode:beforeDate:] is too expensive,
// since it and its cousin [NSRunLoop acceptInputForMode:beforeDate:] are
// designed to be called in a tight loop. Possibly the problem is due to
// combining [NSRunLoop runMode:beforeDate] with [NSApp
// nextEventMatchingMask:...].
// We special-case timer events (events of type NSPeriodic) to avoid
// starving them. Apple's documentation is very scanty, and it's now
// more scanty than it used to be. But it appears that [NSRunLoop
// acceptInputForMode:beforeDate:] doesn't process timer events at all,
// that it is called from [NSRunLoop runMode:beforeDate:], and that
// [NSRunLoop runMode:beforeDate:], though it does process timer events,
// doesn't return after doing so. To get around this, when aWait is
// false we check for timer events and process them using [NSApp
// sendEvent:]. When aWait is true [NSRunLoop runMode:beforeDate:]
// will only return on a "real" event. But there's code in
// ProcessGeckoEvents() that should (when need be) wake us up by sending
// a "fake" "real" event. (See Apple's current doc on [NSRunLoop
// runMode:beforeDate:] and a quote from what appears to be an older
// version of this doc at
// http://lists.apple.com/archives/cocoa-dev/2001/May/msg00559.html.)
// If the current mode is something else than NSDefaultRunLoopMode, look
// for events in that mode.
currentMode = [currentRunLoop currentMode];
if (!currentMode)
currentMode = NSDefaultRunLoopMode;
NSEvent* nextEvent = nil;
if (aMayWait) { if (aMayWait) {
mozilla::HangMonitor::Suspend(); currentMode = [currentRunLoop currentMode];
} if (!currentMode)
currentMode = NSDefaultRunLoopMode;
// If we're running modal (or not in a Gecko "main" event loop) we still NSEvent *nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
// need to use nextEventMatchingMask and sendEvent -- otherwise (in untilDate:waitUntil
// Minefield) the modal window (or non-main event loop) won't receive key inMode:currentMode
// events or most mouse events. dequeue:YES];
// if (nextEvent) {
// Add aMayWait to minimize the number of calls to -[NSApp sendEvent:]
// made from nsAppShell::ProcessNextNativeEvent() (and indirectly from
// nsBaseAppShell::OnProcessNextEvent()), to work around bug 959281.
if ([NSApp _isRunningModal] || (aMayWait && !InGeckoMainEventLoop())) {
if ((nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
untilDate:waitUntil
inMode:currentMode
dequeue:YES])) {
// If we're in a Cocoa app-modal session that's been interrupted by a
// Gecko-modal dialog, send the event to the Cocoa app-modal dialog's
// session. This ensures that the app-modal session won't be starved
// of events, and fixes bugs 463473 and 442442. (The case of an
// ordinary Cocoa app-modal dialog has been dealt with above.)
//
// Otherwise (if we're in an ordinary Gecko-modal dialog, or if we're
// otherwise not in a Gecko main event loop), process the event as
// expected.
NSModalSession currentAppModalSession = nil;
if (gCocoaAppModalWindowList)
currentAppModalSession = gCocoaAppModalWindowList->CurrentSession();
mozilla::HangMonitor::NotifyActivity(); mozilla::HangMonitor::NotifyActivity();
[NSApp sendEvent:nextEvent];
if (currentAppModalSession) {
[NSApp _modalSession:currentAppModalSession sendEvent:nextEvent];
} else {
[NSApp sendEvent:nextEvent];
}
eventProcessed = true; eventProcessed = true;
} }
} else { } else {
if (aMayWait || // AcquireFirstMatchingEventInQueue() doesn't spin the (native) event
(nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask // loop, though it does queue up any newly available events from the
untilDate:nil // window server.
inMode:currentMode EventRef currentEvent = AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
dequeue:NO])) { kEventQueueOptionsNone);
if (nextEvent && ([nextEvent type] == NSPeriodic)) { if (!currentEvent) {
nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask continue;
untilDate:waitUntil
inMode:currentMode
dequeue:YES];
[NSApp sendEvent:nextEvent];
} else {
[currentRunLoop runMode:currentMode beforeDate:waitUntil];
}
eventProcessed = true;
} }
EventAttributes attrs = GetEventAttributes(currentEvent);
// If attrs is kEventAttributeUserEvent or kEventAttributeMonitored
// (i.e. a user input event), we shouldn't process it here while
// aMayWait is false.
if (attrs != kEventAttributeNone) {
// 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); } while (mRunningEventLoop);
if (eventProcessed && (mHadMoreEventsCount < kHadMoreEventsCountMax)) { if (eventProcessed) {
moreEvents = ([NSApp nextEventMatchingMask:NSAnyEventMask moreEvents =
untilDate:nil (AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
inMode:currentMode kEventQueueOptionsNone) != NULL);
dequeue:NO] != nil);
}
if (moreEvents) {
// Once this reaches kHadMoreEventsCountMax, it will be reset to 0 the
// next time through (whether or not we process any events then).
++mHadMoreEventsCount;
} else {
mHadMoreEventsCount = 0;
} }
mRunningEventLoop = wasRunningEventLoop; mRunningEventLoop = wasRunningEventLoop;
@ -694,35 +523,6 @@ nsAppShell::ProcessNextNativeEvent(bool aMayWait)
return moreEvents; return moreEvents;
} }
// Returns true if Gecko events are currently being processed in its "main"
// event loop (or one of its "main" event loops). Returns false if Gecko
// events are being processed in a "nested" event loop, or if we're not
// running in any sort of Gecko event loop. How we process native events in
// ProcessNextNativeEvent() turns on our decision (and if we make the wrong
// choice, the result may be a hang).
//
// We define the "main" event loop(s) as the place (or places) where Gecko
// event processing "normally" takes place, and all other Gecko event loops
// as "nested". The "nested" event loops are normally processed while a call
// from a "main" event loop is on the stack ... but not always. For example,
// the Venkman JavaScript debugger runs a "nested" event loop (in jsdService::
// EnterNestedEventLoop()) whenever it breaks into the current script. But
// if this happens as the result of the user pressing a key combination, there
// won't be any other Gecko event-processing call on the stack (e.g.
// NS_ProcessNextEvent() or NS_ProcessPendingEvents()). (In the current
// nsAppShell implementation, what counts as the "main" event loop is what
// nsBaseAppShell::NativeEventCallback() does to process Gecko events. We
// don't currently use nsBaseAppShell::Run().)
bool
nsAppShell::InGeckoMainEventLoop()
{
if ((gXULModalLevel > 0) || (mRecursionDepth > 0))
return false;
if (mNativeEventCallbackDepth <= 0)
return false;
return true;
}
// Run // Run
// //
// Overrides the base class's Run() method to call [NSApp run] (which spins // Overrides the base class's Run() method to call [NSApp run] (which spins
@ -765,9 +565,6 @@ nsAppShell::Exit(void)
mTerminated = true; mTerminated = true;
delete gCocoaAppModalWindowList;
gCocoaAppModalWindowList = NULL;
#ifndef __LP64__ #ifndef __LP64__
TextInputHandler::RemovePluginKeyEventsHandler(); TextInputHandler::RemovePluginKeyEventsHandler();
#endif #endif
@ -822,8 +619,6 @@ nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
{ {
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
mRecursionDepth = aRecursionDepth;
NS_ASSERTION(mAutoreleasePools, NS_ASSERTION(mAutoreleasePools,
"No stack on which to store autorelease pool"); "No stack on which to store autorelease pool");
@ -849,8 +644,6 @@ nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
{ {
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
mRecursionDepth = aRecursionDepth;
CFIndex count = ::CFArrayGetCount(mAutoreleasePools); CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
NS_ASSERTION(mAutoreleasePools && count, NS_ASSERTION(mAutoreleasePools && count,
@ -966,50 +759,16 @@ nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
@end @end
// We hook beginModalSessionForWindow: and endModalSession: in order to
// maintain a list of Cocoa app-modal windows (and the "sessions" to which
// they correspond). We need this in order to deal with the consequences
// of a Cocoa app-modal dialog being "interrupted" by a Gecko-modal dialog.
// See nsCocoaAppModalWindowList::CurrentSession() and
// nsAppShell::ProcessNextNativeEvent() above.
//
// We hook terminate: in order to make OS-initiated termination work nicely // We hook terminate: in order to make OS-initiated termination work nicely
// with Gecko's shutdown sequence. (Two ways to trigger OS-initiated // 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) // termination: 1) Quit from the Dock menu; 2) Log out from (or shut down)
// your computer while the browser is active.) // your computer while the browser is active.)
@interface NSApplication (MethodSwizzling) @interface NSApplication (MethodSwizzling)
- (NSModalSession)nsAppShell_NSApplication_beginModalSessionForWindow:(NSWindow *)aWindow;
- (void)nsAppShell_NSApplication_endModalSession:(NSModalSession)aSession;
- (void)nsAppShell_NSApplication_terminate:(id)sender; - (void)nsAppShell_NSApplication_terminate:(id)sender;
@end @end
@implementation NSApplication (MethodSwizzling) @implementation NSApplication (MethodSwizzling)
// Called if and only if a Cocoa app-modal session is beginning. Always call
// gCocoaAppModalWindowList->PushCocoa() here (if gCocoaAppModalWindowList is
// non-nil).
- (NSModalSession)nsAppShell_NSApplication_beginModalSessionForWindow:(NSWindow *)aWindow
{
NSModalSession session =
[self nsAppShell_NSApplication_beginModalSessionForWindow:aWindow];
if (gCocoaAppModalWindowList)
gCocoaAppModalWindowList->PushCocoa(aWindow, session);
return session;
}
// Called to end any Cocoa modal session (app-modal or otherwise). Only call
// gCocoaAppModalWindowList->PopCocoa() when an app-modal session is ending
// (and when gCocoaAppModalWindowList is non-nil).
- (void)nsAppShell_NSApplication_endModalSession:(NSModalSession)aSession
{
BOOL wasRunningAppModal = [NSApp _isRunningAppModal];
NSWindow *prevAppModalWindow = [NSApp modalWindow];
[self nsAppShell_NSApplication_endModalSession:aSession];
if (gCocoaAppModalWindowList &&
wasRunningAppModal && (prevAppModalWindow != [NSApp modalWindow]))
gCocoaAppModalWindowList->PopCocoa(prevAppModalWindow, aSession);
}
// Called by the OS after [MacApplicationDelegate applicationShouldTerminate:] // Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
// has returned NSTerminateNow. This method "subclasses" and replaces the // has returned NSTerminateNow. This method "subclasses" and replaces the
// OS's original implementation. The only thing the orginal method does which // OS's original implementation. The only thing the orginal method does which

View File

@ -50,9 +50,6 @@ using namespace mozilla::layers;
using namespace mozilla::widget; using namespace mozilla::widget;
using namespace mozilla; using namespace mozilla;
// defined in nsAppShell.mm
extern nsCocoaAppModalWindowList *gCocoaAppModalWindowList;
int32_t gXULModalLevel = 0; int32_t gXULModalLevel = 0;
// In principle there should be only one app-modal window at any given time. // In principle there should be only one app-modal window at any given time.
@ -610,8 +607,6 @@ NS_IMETHODIMP nsCocoaWindow::SetModal(bool aState)
nsCocoaWindow *aParent = static_cast<nsCocoaWindow*>(mParent); nsCocoaWindow *aParent = static_cast<nsCocoaWindow*>(mParent);
if (aState) { if (aState) {
++gXULModalLevel; ++gXULModalLevel;
if (gCocoaAppModalWindowList)
gCocoaAppModalWindowList->PushGecko(mWindow, this);
// When a non-sheet window gets "set modal", make the window(s) that it // When a non-sheet window gets "set modal", make the window(s) that it
// appears over behave as they should. We can't rely on native methods to // appears over behave as they should. We can't rely on native methods to
// do this, for the following reason: The OS runs modal non-sheet windows // do this, for the following reason: The OS runs modal non-sheet windows
@ -643,8 +638,6 @@ NS_IMETHODIMP nsCocoaWindow::SetModal(bool aState)
else { else {
--gXULModalLevel; --gXULModalLevel;
NS_ASSERTION(gXULModalLevel >= 0, "Mismatched call to nsCocoaWindow::SetModal(false)!"); NS_ASSERTION(gXULModalLevel >= 0, "Mismatched call to nsCocoaWindow::SetModal(false)!");
if (gCocoaAppModalWindowList)
gCocoaAppModalWindowList->PopGecko(mWindow, this);
if (mWindowType != eWindowType_sheet) { if (mWindowType != eWindowType_sheet) {
while (aParent) { while (aParent) {
if (--aParent->mNumModalDescendents == 0) { if (--aParent->mNumModalDescendents == 0) {