1 /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 * Runs the main native Cocoa run loop, interrupting it as needed to process
11 #import <Cocoa/Cocoa.h>
14 #include "CustomCocoaEvents.h"
15 #include "mozilla/WidgetTraceEvent.h"
16 #include "nsAppShell.h"
19 #include "nsDirectoryServiceDefs.h"
21 #include "nsIRollupListener.h"
22 #include "nsIWidget.h"
23 #include "nsThreadUtils.h"
24 #include "nsIWindowMediator.h"
25 #include "nsServiceManagerUtils.h"
26 #include "nsIInterfaceRequestor.h"
27 #include "nsIWebBrowserChrome.h"
28 #include "nsObjCExceptions.h"
29 #include "nsCocoaFeatures.h"
30 #include "nsCocoaUtils.h"
31 #include "nsChildView.h"
32 #include "nsToolkit.h"
33 #include "TextInputHandler.h"
34 #include "mozilla/HangMonitor.h"
39 using namespace mozilla::widget;
41 // defined in nsCocoaWindow.mm
42 extern int32_t gXULModalLevel;
44 static bool gAppShellMethodsSwizzled = false;
45 // List of current Cocoa app-modal windows (nested if more than one).
46 nsCocoaAppModalWindowList *gCocoaAppModalWindowList = NULL;
48 // Push a Cocoa app-modal window onto the top of our list.
49 nsresult nsCocoaAppModalWindowList::PushCocoa(NSWindow *aWindow, NSModalSession aSession)
51 NS_ENSURE_STATE(aWindow && aSession);
52 mList.AppendElement(nsCocoaAppModalWindowListItem(aWindow, aSession));
56 // Pop the topmost Cocoa app-modal window off our list. aWindow and aSession
57 // are just used to check that it's what we expect it to be.
58 nsresult nsCocoaAppModalWindowList::PopCocoa(NSWindow *aWindow, NSModalSession aSession)
60 NS_ENSURE_STATE(aWindow && aSession);
62 for (int i = mList.Length(); i > 0; --i) {
63 nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1);
65 NS_ASSERTION((item.mWindow == aWindow) && (item.mSession == aSession),
66 "PopCocoa() called without matching call to PushCocoa()!");
67 mList.RemoveElementAt(i - 1);
72 NS_ERROR("PopCocoa() called without matching call to PushCocoa()!");
73 return NS_ERROR_FAILURE;
76 // Push a Gecko-modal window onto the top of our list.
77 nsresult nsCocoaAppModalWindowList::PushGecko(NSWindow *aWindow, nsCocoaWindow *aWidget)
79 NS_ENSURE_STATE(aWindow && aWidget);
80 mList.AppendElement(nsCocoaAppModalWindowListItem(aWindow, aWidget));
84 // Pop the topmost Gecko-modal window off our list. aWindow and aWidget are
85 // just used to check that it's what we expect it to be.
86 nsresult nsCocoaAppModalWindowList::PopGecko(NSWindow *aWindow, nsCocoaWindow *aWidget)
88 NS_ENSURE_STATE(aWindow && aWidget);
90 for (int i = mList.Length(); i > 0; --i) {
91 nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1);
93 NS_ASSERTION((item.mWindow == aWindow) && (item.mWidget == aWidget),
94 "PopGecko() called without matching call to PushGecko()!");
95 mList.RemoveElementAt(i - 1);
100 NS_ERROR("PopGecko() called without matching call to PushGecko()!");
101 return NS_ERROR_FAILURE;
104 // The "current session" is normally the "session" corresponding to the
105 // top-most Cocoa app-modal window (both on the screen and in our list).
106 // But because Cocoa app-modal dialog can be "interrupted" by a Gecko-modal
107 // dialog, the top-most Cocoa app-modal dialog may already have finished
108 // (and no longer be visible). In this case we need to check the list for
109 // the "next" visible Cocoa app-modal window (and return its "session"), or
110 // (if no Cocoa app-modal window is visible) return nil. This way we ensure
111 // (as we need to) that all nested Cocoa app-modal sessions are dealt with
112 // before we get to any Gecko-modal session(s). See nsAppShell::
113 // ProcessNextNativeEvent() below.
114 NSModalSession nsCocoaAppModalWindowList::CurrentSession()
116 if (![NSApp _isRunningAppModal])
119 NSModalSession currentSession = nil;
121 for (int i = mList.Length(); i > 0; --i) {
122 nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1);
123 if (item.mSession && [item.mWindow isVisible]) {
124 currentSession = item.mSession;
129 return currentSession;
132 // Has a Gecko modal dialog popped up over a Cocoa app-modal dialog?
133 bool nsCocoaAppModalWindowList::GeckoModalAboveCocoaModal()
138 nsCocoaAppModalWindowListItem &topItem = mList.ElementAt(mList.Length() - 1);
140 return (topItem.mWidget != nullptr);
143 @implementation GeckoNSApplication
145 - (void)sendEvent:(NSEvent *)anEvent
147 mozilla::HangMonitor::NotifyActivity();
148 if ([anEvent type] == NSApplicationDefined &&
149 [anEvent subtype] == kEventSubtypeTrace) {
150 mozilla::SignalTracerThread();
153 [super sendEvent:anEvent];
156 - (NSEvent*)nextEventMatchingMask:(NSUInteger)mask
157 untilDate:(NSDate*)expiration
158 inMode:(NSString*)mode
162 mozilla::HangMonitor::Suspend();
164 return [super nextEventMatchingMask:mask
165 untilDate:expiration inMode:mode dequeue:flag];
173 // Cocoa bridge class. An object of this class is registered to receive
176 @interface AppShellDelegate : NSObject
179 nsAppShell* mAppShell;
182 - (id)initWithAppShell:(nsAppShell*)aAppShell;
183 - (void)applicationWillTerminate:(NSNotification*)aNotification;
184 - (void)beginMenuTracking:(NSNotification*)aNotification;
187 // nsAppShell implementation
190 nsAppShell::ResumeNative(void)
192 nsresult retval = nsBaseAppShell::ResumeNative();
193 if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) &&
194 mSkippedNativeCallback)
196 mSkippedNativeCallback = false;
197 ScheduleNativeEventCallback();
202 nsAppShell::nsAppShell()
203 : mAutoreleasePools(nullptr)
206 , mCFRunLoopSource(NULL)
207 , mRunningEventLoop(false)
210 , mSkippedNativeCallback(false)
211 , mHadMoreEventsCount(0)
213 , mNativeEventCallbackDepth(0)
214 , mNativeEventScheduledDepth(0)
216 // A Cocoa event loop is running here if (and only if) we've been embedded
217 // by a Cocoa app (like Camino).
218 mRunningCocoaEmbedded = [NSApp isRunning] ? true : false;
221 nsAppShell::~nsAppShell()
223 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
226 if (mCFRunLoopSource) {
227 ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
228 kCFRunLoopCommonModes);
229 ::CFRelease(mCFRunLoopSource);
231 ::CFRelease(mCFRunLoop);
234 if (mAutoreleasePools) {
235 NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
236 "nsAppShell destroyed without popping all autorelease pools");
237 ::CFRelease(mAutoreleasePools);
242 NS_OBJC_END_TRY_ABORT_BLOCK
247 // Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
248 // interrupt the main native run loop.
254 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
256 // No event loop is running yet (unless Camino is running, or another
257 // embedding app that uses NSApplicationMain()).
258 NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
260 // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
261 // by |this|. CFArray is used instead of NSArray because NSArray wants to
262 // retain each object you add to it, and you can't retain an
263 // NSAutoreleasePool.
264 mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
265 NS_ENSURE_STATE(mAutoreleasePools);
267 // Get the path of the nib file, which lives in the GRE location
268 nsCOMPtr<nsIFile> nibFile;
269 nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(nibFile));
270 NS_ENSURE_SUCCESS(rv, rv);
272 nibFile->AppendNative(NS_LITERAL_CSTRING("res"));
273 nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib"));
275 nsAutoCString nibPath;
276 rv = nibFile->GetNativePath(nibPath);
277 NS_ENSURE_SUCCESS(rv, rv);
279 // This call initializes NSApplication unless:
280 // 1) we're using xre -- NSApp's already been initialized by
281 // MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
282 // 2) Camino is running (or another embedding app that uses
283 // NSApplicationMain()) -- NSApp's already been initialized and
284 // its main run loop is already running.
285 [NSBundle loadNibFile:
286 [NSString stringWithUTF8String:(const char*)nibPath.get()]
288 [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication]
290 withZone:NSDefaultMallocZone()];
292 mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
293 NS_ENSURE_STATE(mDelegate);
295 // Add a CFRunLoopSource to the main native run loop. The source is
296 // responsible for interrupting the run loop when Gecko events are ready.
298 mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
299 NS_ENSURE_STATE(mCFRunLoop);
300 ::CFRetain(mCFRunLoop);
302 CFRunLoopSourceContext context;
303 bzero(&context, sizeof(context));
304 // context.version = 0;
306 context.perform = ProcessGeckoEvents;
308 mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
309 NS_ENSURE_STATE(mCFRunLoopSource);
311 ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
313 rv = nsBaseAppShell::Init();
316 TextInputHandler::InstallPluginKeyEventsHandler();
319 gCocoaAppModalWindowList = new nsCocoaAppModalWindowList;
320 if (!gAppShellMethodsSwizzled) {
321 nsToolkit::SwizzleMethods([NSApplication class], @selector(beginModalSessionForWindow:),
322 @selector(nsAppShell_NSApplication_beginModalSessionForWindow:));
323 nsToolkit::SwizzleMethods([NSApplication class], @selector(endModalSession:),
324 @selector(nsAppShell_NSApplication_endModalSession:));
325 // We should only replace the original terminate: method if we're not
326 // running in a Cocoa embedder (like Camino). See bug 604901.
327 if (!mRunningCocoaEmbedded) {
328 nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
329 @selector(nsAppShell_NSApplication_terminate:));
331 gAppShellMethodsSwizzled = true;
338 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
341 // ProcessGeckoEvents
343 // The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
344 // signalled from ScheduleNativeEventCallback.
346 // Arrange for Gecko events to be processed on demand (in response to a call
347 // to ScheduleNativeEventCallback(), if processing of Gecko events via "native
348 // methods" hasn't been suspended). This happens in NativeEventCallback().
352 nsAppShell::ProcessGeckoEvents(void* aInfo)
354 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
355 SAMPLE_LABEL("Events", "ProcessGeckoEvents");
356 nsAppShell* self = static_cast<nsAppShell*> (aInfo);
358 if (self->mRunningEventLoop) {
359 self->mRunningEventLoop = false;
361 // The run loop may be sleeping -- [NSRunLoop runMode:...]
362 // won't return until it's given a reason to wake up. Awaken it by
363 // posting a bogus event. There's no need to make the event
366 // But _don't_ set windowNumber to '-1' -- that can lead to nasty
367 // wierdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
368 // these fake events, because the -1 has gotten changed into the number
369 // of an actual NSWindow object, and that NSWindow object has just been
370 // destroyed). Setting windowNumber to '0' seems to work fine -- this
371 // seems to prevent the OS from ever trying to associate our bogus event
372 // with a particular NSWindow object.
373 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
374 location:NSMakePoint(0,0)
379 subtype:kEventSubtypeNone
385 if (self->mSuspendNativeCount <= 0) {
386 ++self->mNativeEventCallbackDepth;
387 self->NativeEventCallback();
388 --self->mNativeEventCallbackDepth;
390 self->mSkippedNativeCallback = true;
393 // Still needed to fix bug 343033 ("5-10 second delay or hang or crash
394 // when quitting Cocoa Firefox").
395 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
396 location:NSMakePoint(0,0)
401 subtype:kEventSubtypeNone
406 // Normally every call to ScheduleNativeEventCallback() results in
407 // exactly one call to ProcessGeckoEvents(). So each Release() here
408 // normally balances exactly one AddRef() in ScheduleNativeEventCallback().
409 // But if Exit() is called just after ScheduleNativeEventCallback(), the
410 // corresponding call to ProcessGeckoEvents() will never happen. We check
411 // for this possibility in two different places -- here and in Exit()
412 // itself. If we find here that Exit() has been called (that mTerminated
413 // is true), it's because we've been called recursively, that Exit() was
414 // called from self->NativeEventCallback() above, and that we're unwinding
415 // the recursion. In this case we'll never be called again, and we balance
416 // here any extra calls to ScheduleNativeEventCallback().
418 // When ProcessGeckoEvents() is called recursively, it's because of a
419 // call to ScheduleNativeEventCallback() from NativeEventCallback(). We
420 // balance the "extra" AddRefs here (rather than always in Exit()) in order
421 // to ensure that 'self' stays alive until the end of this method. We also
422 // make sure not to finish the balancing until all the recursion has been
424 if (self->mTerminated) {
425 int32_t releaseCount = 0;
426 if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) {
427 releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth,
428 self->mNativeEventCallbackDepth);
430 while (releaseCount-- > self->mNativeEventCallbackDepth)
433 // As best we can tell, every call to ProcessGeckoEvents() is triggered
434 // by a call to ScheduleNativeEventCallback(). But we've seen a few
435 // (non-reproducible) cases of double-frees that *might* have been caused
436 // by spontaneous calls (from the OS) to ProcessGeckoEvents(). So we
437 // deal with that possibility here.
438 if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) {
439 PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0);
440 NS_WARNING("Spontaneous call to ProcessGeckoEvents()!");
446 NS_OBJC_END_TRY_ABORT_BLOCK;
451 // Called by the AppShellDelegate when an NSApplicationWillTerminate
452 // notification is posted. After this method is called, native events should
453 // no longer be processed. The NSApplicationWillTerminate notification is
454 // only posted when [NSApp terminate:] is called, which doesn't happen on a
455 // "normal" application quit.
459 nsAppShell::WillTerminate()
464 // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
465 // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
466 NS_ProcessPendingEvents(NS_GetCurrentThread());
471 // ScheduleNativeEventCallback
473 // Called (possibly on a non-main thread) when Gecko has an event that
474 // needs to be processed. The Gecko event needs to be processed on the
475 // main thread, so the native run loop must be interrupted.
477 // In nsBaseAppShell.cpp, the mNativeEventPending variable is used to
478 // ensure that ScheduleNativeEventCallback() is called no more than once
479 // per call to NativeEventCallback(). ProcessGeckoEvents() can skip its
480 // call to NativeEventCallback() if processing of Gecko events by native
481 // means is suspended (using nsIAppShell::SuspendNative()), which will
482 // suspend calls from nsBaseAppShell::OnDispatchedEvent() to
483 // ScheduleNativeEventCallback(). But when Gecko event processing by
484 // native means is resumed (in ResumeNative()), an extra call is made to
485 // ScheduleNativeEventCallback() (from ResumeNative()). This triggers
486 // another call to ProcessGeckoEvents(), which calls NativeEventCallback(),
487 // and nsBaseAppShell::OnDispatchedEvent() resumes calling
488 // ScheduleNativeEventCallback().
492 nsAppShell::ScheduleNativeEventCallback()
494 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
499 // Each AddRef() here is normally balanced by exactly one Release() in
500 // ProcessGeckoEvents(). But there are exceptions, for which see
501 // ProcessGeckoEvents() and Exit().
503 PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth);
505 // This will invoke ProcessGeckoEvents on the main thread.
506 ::CFRunLoopSourceSignal(mCFRunLoopSource);
507 ::CFRunLoopWakeUp(mCFRunLoop);
509 NS_OBJC_END_TRY_ABORT_BLOCK;
512 // ProcessNextNativeEvent
514 // If aMayWait is false, process a single native event. If it is true, run
515 // the native run loop until stopped by ProcessGeckoEvents.
517 // Returns true if more events are waiting in the native event queue.
519 // But (now that we're using [NSRunLoop runMode:beforeDate:]) it's too
520 // expensive to call ProcessNextNativeEvent() many times in a row (in a
521 // tight loop), so we never return true more than kHadMoreEventsCountMax
522 // times in a row. This doesn't seem to cause native event starvation.
526 nsAppShell::ProcessNextNativeEvent(bool aMayWait)
528 bool moreEvents = false;
530 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
532 bool eventProcessed = false;
533 NSString* currentMode = nil;
538 // We don't want any native events to be processed here (via Gecko) while
539 // Cocoa is displaying an app-modal dialog (as opposed to a window-modal
540 // "sheet" or a Gecko-modal dialog). Otherwise Cocoa event-processing loops
541 // may be interrupted, and inappropriate events may get through to the
542 // browser window(s) underneath. This resolves bmo bugs 419668 and 420967.
544 // But we need more complex handling (we need to make an exception) if a
545 // Gecko modal dialog is running above the Cocoa app-modal dialog -- for
547 if ([NSApp _isRunningAppModal] &&
548 (!gCocoaAppModalWindowList || !gCocoaAppModalWindowList->GeckoModalAboveCocoaModal()))
551 bool wasRunningEventLoop = mRunningEventLoop;
552 mRunningEventLoop = aMayWait;
553 NSDate* waitUntil = nil;
555 waitUntil = [NSDate distantFuture];
557 NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
560 // No autorelease pool is provided here, because OnProcessNextEvent
561 // and AfterProcessNextEvent are responsible for maintaining it.
562 NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
563 "No autorelease pool for native event");
565 // If an event is waiting to be processed, run the main event loop
566 // just long enough to process it. For some reason, using [NSApp
567 // nextEventMatchingMask:...] to dequeue the event and [NSApp sendEvent:]
568 // to "send" it causes trouble, so we no longer do that. (The trouble
569 // was very strange, and only happened while processing Gecko events on
570 // demand (via ProcessGeckoEvents()), as opposed to processing Gecko
571 // events in a tight loop (via nsBaseAppShell::Run()): Particularly in
572 // Camino, mouse-down events sometimes got dropped (or mis-handled), so
573 // that (for example) you sometimes needed to click more than once on a
574 // button to make it work (the zoom button was particularly susceptible).
575 // You also sometimes had to ctrl-click or right-click multiple times to
576 // bring up a context menu.)
578 // Now that we're using [NSRunLoop runMode:beforeDate:], it's too
579 // expensive to call ProcessNextNativeEvent() many times in a row, so we
580 // never return true more than kHadMoreEventsCountMax in a row. I'm not
581 // entirely sure why [NSRunLoop runMode:beforeDate:] is too expensive,
582 // since it and its cousin [NSRunLoop acceptInputForMode:beforeDate:] are
583 // designed to be called in a tight loop. Possibly the problem is due to
584 // combining [NSRunLoop runMode:beforeDate] with [NSApp
585 // nextEventMatchingMask:...].
587 // We special-case timer events (events of type NSPeriodic) to avoid
588 // starving them. Apple's documentation is very scanty, and it's now
589 // more scanty than it used to be. But it appears that [NSRunLoop
590 // acceptInputForMode:beforeDate:] doesn't process timer events at all,
591 // that it is called from [NSRunLoop runMode:beforeDate:], and that
592 // [NSRunLoop runMode:beforeDate:], though it does process timer events,
593 // doesn't return after doing so. To get around this, when aWait is
594 // false we check for timer events and process them using [NSApp
595 // sendEvent:]. When aWait is true [NSRunLoop runMode:beforeDate:]
596 // will only return on a "real" event. But there's code in
597 // ProcessGeckoEvents() that should (when need be) wake us up by sending
598 // a "fake" "real" event. (See Apple's current doc on [NSRunLoop
599 // runMode:beforeDate:] and a quote from what appears to be an older
600 // version of this doc at
601 // http://lists.apple.com/archives/cocoa-dev/2001/May/msg00559.html.)
603 // If the current mode is something else than NSDefaultRunLoopMode, look
604 // for events in that mode.
605 currentMode = [currentRunLoop currentMode];
607 currentMode = NSDefaultRunLoopMode;
609 NSEvent* nextEvent = nil;
612 mozilla::HangMonitor::Suspend();
615 // If we're running modal (or not in a Gecko "main" event loop) we still
616 // need to use nextEventMatchingMask and sendEvent -- otherwise (in
617 // Minefield) the modal window (or non-main event loop) won't receive key
618 // events or most mouse events.
619 if ([NSApp _isRunningModal] || !InGeckoMainEventLoop()) {
620 if ((nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
624 // If we're in a Cocoa app-modal session that's been interrupted by a
625 // Gecko-modal dialog, send the event to the Cocoa app-modal dialog's
626 // session. This ensures that the app-modal session won't be starved
627 // of events, and fixes bugs 463473 and 442442. (The case of an
628 // ordinary Cocoa app-modal dialog has been dealt with above.)
630 // Otherwise (if we're in an ordinary Gecko-modal dialog, or if we're
631 // otherwise not in a Gecko main event loop), process the event as
633 NSModalSession currentAppModalSession = nil;
634 if (gCocoaAppModalWindowList)
635 currentAppModalSession = gCocoaAppModalWindowList->CurrentSession();
637 mozilla::HangMonitor::NotifyActivity();
639 if (currentAppModalSession) {
640 [NSApp _modalSession:currentAppModalSession sendEvent:nextEvent];
642 [NSApp sendEvent:nextEvent];
644 eventProcessed = true;
648 (nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
652 if (nextEvent && ([nextEvent type] == NSPeriodic)) {
653 nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
657 [NSApp sendEvent:nextEvent];
659 [currentRunLoop runMode:currentMode beforeDate:waitUntil];
661 eventProcessed = true;
664 } while (mRunningEventLoop);
666 if (eventProcessed && (mHadMoreEventsCount < kHadMoreEventsCountMax)) {
667 moreEvents = ([NSApp nextEventMatchingMask:NSAnyEventMask
674 // Once this reaches kHadMoreEventsCountMax, it will be reset to 0 the
675 // next time through (whether or not we process any events then).
676 ++mHadMoreEventsCount;
678 mHadMoreEventsCount = 0;
681 mRunningEventLoop = wasRunningEventLoop;
683 NS_OBJC_END_TRY_ABORT_BLOCK;
686 nsChildView::UpdateCurrentInputEventCount();
692 // Returns true if Gecko events are currently being processed in its "main"
693 // event loop (or one of its "main" event loops). Returns false if Gecko
694 // events are being processed in a "nested" event loop, or if we're not
695 // running in any sort of Gecko event loop. How we process native events in
696 // ProcessNextNativeEvent() turns on our decision (and if we make the wrong
697 // choice, the result may be a hang).
699 // We define the "main" event loop(s) as the place (or places) where Gecko
700 // event processing "normally" takes place, and all other Gecko event loops
701 // as "nested". The "nested" event loops are normally processed while a call
702 // from a "main" event loop is on the stack ... but not always. For example,
703 // the Venkman JavaScript debugger runs a "nested" event loop (in jsdService::
704 // EnterNestedEventLoop()) whenever it breaks into the current script. But
705 // if this happens as the result of the user pressing a key combination, there
706 // won't be any other Gecko event-processing call on the stack (e.g.
707 // NS_ProcessNextEvent() or NS_ProcessPendingEvents()). (In the current
708 // nsAppShell implementation, what counts as the "main" event loop is what
709 // nsBaseAppShell::NativeEventCallback() does to process Gecko events. We
710 // don't currently use nsBaseAppShell::Run().)
712 nsAppShell::InGeckoMainEventLoop()
714 if ((gXULModalLevel > 0) || (mRecursionDepth > 0))
716 if (mNativeEventCallbackDepth <= 0)
723 // Overrides the base class's Run() method to call [NSApp run] (which spins
724 // the native run loop until the application quits). Since (unlike the base
725 // class's Run() method) we don't process any Gecko events here, they need
726 // to be processed elsewhere (in NativeEventCallback(), called from
727 // ProcessGeckoEvents()).
729 // Camino calls [NSApp run] on its own (via NSApplicationMain()), and so
730 // doesn't call nsAppShell::Run().
734 nsAppShell::Run(void)
736 NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
741 NS_OBJC_TRY_ABORT([NSApp run]);
747 nsAppShell::Exit(void)
749 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
751 // This method is currently called more than once -- from (according to
752 // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
753 // XPCOM shutdown notification that nsBaseAppShell has registered to
754 // receive. So we need to ensure that multiple calls won't break anything.
755 // But we should also complain about it (since it isn't quite kosher).
757 NS_WARNING("nsAppShell::Exit() called redundantly");
763 delete gCocoaAppModalWindowList;
764 gCocoaAppModalWindowList = NULL;
767 TextInputHandler::RemovePluginKeyEventsHandler();
770 // Quoting from Apple's doc on the [NSApplication stop:] method (from their
771 // doc on the NSApplication class): "If this method is invoked during a
772 // modal event loop, it will break that loop but not the main event loop."
773 // nsAppShell::Exit() shouldn't be called from a modal event loop. So if
774 // it is we complain about it (to users of debug builds) and call [NSApp
775 // stop:] one extra time. (I'm not sure if modal event loops can be nested
776 // -- Apple's docs don't say one way or the other. But the return value
777 // of [NSApp _isRunningModal] doesn't change immediately after a call to
778 // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
780 BOOL cocoaModal = [NSApp _isRunningModal];
781 NS_ASSERTION(!cocoaModal,
782 "Don't call nsAppShell::Exit() from a modal event loop!");
784 [NSApp stop:nullptr];
785 [NSApp stop:nullptr];
787 // A call to Exit() just after a call to ScheduleNativeEventCallback()
788 // prevents the (normally) matching call to ProcessGeckoEvents() from
789 // happening. If we've been called from ProcessGeckoEvents() (as usually
790 // happens), we take care of it there. But if we have an unbalanced call
791 // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
792 // stack, we need to take care of the problem here.
793 if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
794 int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
795 while (releaseCount-- > 0)
799 return nsBaseAppShell::Exit();
801 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
804 // OnProcessNextEvent
806 // This nsIThreadObserver method is called prior to processing an event.
807 // Set up an autorelease pool that will service any autoreleased Cocoa
808 // objects during this event. This includes native events processed by
809 // ProcessNextNativeEvent. The autorelease pool will be popped by
810 // AfterProcessNextEvent, it is important for these two methods to be
815 nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
816 uint32_t aRecursionDepth)
818 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
820 mRecursionDepth = aRecursionDepth;
822 NS_ASSERTION(mAutoreleasePools,
823 "No stack on which to store autorelease pool");
825 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
826 ::CFArrayAppendValue(mAutoreleasePools, pool);
828 return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait, aRecursionDepth);
830 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
833 // AfterProcessNextEvent
835 // This nsIThreadObserver method is called after event processing is complete.
836 // The Cocoa implementation cleans up the autorelease pool create by the
837 // previous OnProcessNextEvent call.
841 nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
842 uint32_t aRecursionDepth)
844 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
846 mRecursionDepth = aRecursionDepth;
848 CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
850 NS_ASSERTION(mAutoreleasePools && count,
851 "Processed an event, but there's no autorelease pool?");
853 const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*>
854 (::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
855 ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
858 return nsBaseAppShell::AfterProcessNextEvent(aThread, aRecursionDepth);
860 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
864 // AppShellDelegate implementation
867 @implementation AppShellDelegate
870 // Constructs the AppShellDelegate object
871 - (id)initWithAppShell:(nsAppShell*)aAppShell
873 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
875 if ((self = [self init])) {
876 mAppShell = aAppShell;
878 [[NSNotificationCenter defaultCenter] addObserver:self
879 selector:@selector(applicationWillTerminate:)
880 name:NSApplicationWillTerminateNotification
882 [[NSNotificationCenter defaultCenter] addObserver:self
883 selector:@selector(applicationDidBecomeActive:)
884 name:NSApplicationDidBecomeActiveNotification
886 [[NSDistributedNotificationCenter defaultCenter] addObserver:self
887 selector:@selector(beginMenuTracking:)
888 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
894 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
899 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
901 [[NSNotificationCenter defaultCenter] removeObserver:self];
902 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
905 NS_OBJC_END_TRY_ABORT_BLOCK;
908 // applicationWillTerminate:
910 // Notify the nsAppShell that native event processing should be discontinued.
911 - (void)applicationWillTerminate:(NSNotification*)aNotification
913 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
915 mAppShell->WillTerminate();
917 NS_OBJC_END_TRY_ABORT_BLOCK;
920 // applicationDidBecomeActive
922 // Make sure TextInputHandler::sLastModifierState is updated when we become
923 // active (since we won't have received [ChildView flagsChanged:] messages
925 - (void)applicationDidBecomeActive:(NSNotification*)aNotification
927 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
929 // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
930 // to worry about getting an NSInternalInconsistencyException here.
931 NSEvent* currentEvent = [NSApp currentEvent];
933 TextInputHandler::sLastModifierState =
934 [currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
937 NS_OBJC_END_TRY_ABORT_BLOCK;
942 // Roll up our context menu (if any) when some other app (or the OS) opens
943 // any sort of menu. But make sure we don't do this for notifications we
944 // send ourselves (whose 'sender' will be @"org.mozilla.gecko.PopupWindow").
945 - (void)beginMenuTracking:(NSNotification*)aNotification
947 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
949 NSString *sender = [aNotification object];
950 if (!sender || ![sender isEqualToString:@"org.mozilla.gecko.PopupWindow"]) {
951 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
952 nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
954 rollupListener->Rollup(0, nullptr);
957 NS_OBJC_END_TRY_ABORT_BLOCK;
962 // We hook beginModalSessionForWindow: and endModalSession: in order to
963 // maintain a list of Cocoa app-modal windows (and the "sessions" to which
964 // they correspond). We need this in order to deal with the consequences
965 // of a Cocoa app-modal dialog being "interrupted" by a Gecko-modal dialog.
966 // See nsCocoaAppModalWindowList::CurrentSession() and
967 // nsAppShell::ProcessNextNativeEvent() above.
969 // We hook terminate: in order to make OS-initiated termination work nicely
970 // with Gecko's shutdown sequence. (Two ways to trigger OS-initiated
971 // termination: 1) Quit from the Dock menu; 2) Log out from (or shut down)
972 // your computer while the browser is active.)
973 @interface NSApplication (MethodSwizzling)
974 - (NSModalSession)nsAppShell_NSApplication_beginModalSessionForWindow:(NSWindow *)aWindow;
975 - (void)nsAppShell_NSApplication_endModalSession:(NSModalSession)aSession;
976 - (void)nsAppShell_NSApplication_terminate:(id)sender;
979 @implementation NSApplication (MethodSwizzling)
981 // Called if and only if a Cocoa app-modal session is beginning. Always call
982 // gCocoaAppModalWindowList->PushCocoa() here (if gCocoaAppModalWindowList is
984 - (NSModalSession)nsAppShell_NSApplication_beginModalSessionForWindow:(NSWindow *)aWindow
986 NSModalSession session =
987 [self nsAppShell_NSApplication_beginModalSessionForWindow:aWindow];
988 if (gCocoaAppModalWindowList)
989 gCocoaAppModalWindowList->PushCocoa(aWindow, session);
993 // Called to end any Cocoa modal session (app-modal or otherwise). Only call
994 // gCocoaAppModalWindowList->PopCocoa() when an app-modal session is ending
995 // (and when gCocoaAppModalWindowList is non-nil).
996 - (void)nsAppShell_NSApplication_endModalSession:(NSModalSession)aSession
998 BOOL wasRunningAppModal = [NSApp _isRunningAppModal];
999 NSWindow *prevAppModalWindow = [NSApp modalWindow];
1000 [self nsAppShell_NSApplication_endModalSession:aSession];
1001 if (gCocoaAppModalWindowList &&
1002 wasRunningAppModal && (prevAppModalWindow != [NSApp modalWindow]))
1003 gCocoaAppModalWindowList->PopCocoa(prevAppModalWindow, aSession);
1006 // Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
1007 // has returned NSTerminateNow. This method "subclasses" and replaces the
1008 // OS's original implementation. The only thing the orginal method does which
1009 // we need is that it posts NSApplicationWillTerminateNotification. Everything
1010 // else is unneeded (because it's handled elsewhere), or actively interferes
1011 // with Gecko's shutdown sequence. For example the original terminate: method
1012 // causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
1013 // above), which means that nothing runs after the call to nsAppStartup::Run()
1014 // in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
1015 // and NS_ShutdownXPCOM() never get called.
1016 - (void)nsAppShell_NSApplication_terminate:(id)sender
1018 [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification