bug 818009 - canActivate: only click-to-play-type plugins are valid r=jaws
[gecko.git] / widget / cocoa / nsAppShell.mm
blob6435307c780a45e38354d22d310a1d559b84e3e1
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/. */
6 /*
7  * Runs the main native Cocoa run loop, interrupting it as needed to process
8  * Gecko events.
9  */
11 #import <Cocoa/Cocoa.h>
12 #include <dlfcn.h>
14 #include "CustomCocoaEvents.h"
15 #include "mozilla/WidgetTraceEvent.h"
16 #include "nsAppShell.h"
17 #include "nsCOMPtr.h"
18 #include "nsIFile.h"
19 #include "nsDirectoryServiceDefs.h"
20 #include "nsString.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"
35 #include "sampler.h"
37 #include "npapi.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));
53   return NS_OK;
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);
64     if (item.mSession) {
65       NS_ASSERTION((item.mWindow == aWindow) && (item.mSession == aSession),
66                    "PopCocoa() called without matching call to PushCocoa()!");
67       mList.RemoveElementAt(i - 1);
68       return NS_OK;
69     }
70   }
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));
81   return NS_OK;
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);
92     if (item.mWidget) {
93       NS_ASSERTION((item.mWindow == aWindow) && (item.mWidget == aWidget),
94                    "PopGecko() called without matching call to PushGecko()!");
95       mList.RemoveElementAt(i - 1);
96       return NS_OK;
97     }
98   }
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])
117     return nil;
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;
125       break;
126     }
127   }
129   return currentSession;
132 // Has a Gecko modal dialog popped up over a Cocoa app-modal dialog?
133 bool nsCocoaAppModalWindowList::GeckoModalAboveCocoaModal()
135   if (mList.IsEmpty())
136     return false;
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();
151     return;
152   }
153   [super sendEvent:anEvent];
156 - (NSEvent*)nextEventMatchingMask:(NSUInteger)mask
157                         untilDate:(NSDate*)expiration
158                            inMode:(NSString*)mode
159                           dequeue:(BOOL)flag
161   if (expiration) {
162     mozilla::HangMonitor::Suspend();
163   }
164   return [super nextEventMatchingMask:mask
165           untilDate:expiration inMode:mode dequeue:flag];
168 @end
171 // AppShellDelegate
173 // Cocoa bridge class.  An object of this class is registered to receive
174 // notifications.
176 @interface AppShellDelegate : NSObject
178   @private
179     nsAppShell* mAppShell;
182 - (id)initWithAppShell:(nsAppShell*)aAppShell;
183 - (void)applicationWillTerminate:(NSNotification*)aNotification;
184 - (void)beginMenuTracking:(NSNotification*)aNotification;
185 @end
187 // nsAppShell implementation
189 NS_IMETHODIMP
190 nsAppShell::ResumeNative(void)
192   nsresult retval = nsBaseAppShell::ResumeNative();
193   if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) &&
194       mSkippedNativeCallback)
195   {
196     mSkippedNativeCallback = false;
197     ScheduleNativeEventCallback();
198   }
199   return retval;
202 nsAppShell::nsAppShell()
203 : mAutoreleasePools(nullptr)
204 , mDelegate(nullptr)
205 , mCFRunLoop(NULL)
206 , mCFRunLoopSource(NULL)
207 , mRunningEventLoop(false)
208 , mStarted(false)
209 , mTerminated(false)
210 , mSkippedNativeCallback(false)
211 , mHadMoreEventsCount(0)
212 , mRecursionDepth(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;
225   if (mCFRunLoop) {
226     if (mCFRunLoopSource) {
227       ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
228                               kCFRunLoopCommonModes);
229       ::CFRelease(mCFRunLoopSource);
230     }
231     ::CFRelease(mCFRunLoop);
232   }
234   if (mAutoreleasePools) {
235     NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
236                  "nsAppShell destroyed without popping all autorelease pools");
237     ::CFRelease(mAutoreleasePools);
238   }
240   [mDelegate release];
242   NS_OBJC_END_TRY_ABORT_BLOCK
245 // Init
247 // Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
248 // interrupt the main native run loop.
250 // public
251 nsresult
252 nsAppShell::Init()
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()]
287       externalNameTable:
288            [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication]
289                                        forKey:@"NSOwner"]
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;
305   context.info = this;
306   context.perform = ProcessGeckoEvents;
307   
308   mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
309   NS_ENSURE_STATE(mCFRunLoopSource);
311   ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
313   rv = nsBaseAppShell::Init();
315 #ifndef __LP64__
316   TextInputHandler::InstallPluginKeyEventsHandler();
317 #endif
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:));
330     }
331     gAppShellMethodsSwizzled = true;
332   }
334   [localPool release];
336   return rv;
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().
350 // protected static
351 void
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
364     // presentable.
365     //
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)
375                                    modifierFlags:0
376                                        timestamp:0
377                                     windowNumber:0
378                                          context:NULL
379                                          subtype:kEventSubtypeNone
380                                            data1:0
381                                            data2:0]
382              atStart:NO];
383   }
385   if (self->mSuspendNativeCount <= 0) {
386     ++self->mNativeEventCallbackDepth;
387     self->NativeEventCallback();
388     --self->mNativeEventCallbackDepth;
389   } else {
390     self->mSkippedNativeCallback = true;
391   }
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)
397                                  modifierFlags:0
398                                      timestamp:0
399                                   windowNumber:0
400                                        context:NULL
401                                        subtype:kEventSubtypeNone
402                                          data1:0
403                                          data2:0]
404            atStart:NO];
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().
417   //
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
423   // unwound.
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);
429     }
430     while (releaseCount-- > self->mNativeEventCallbackDepth)
431       self->Release();
432   } else {
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()!");
441     } else {
442       self->Release();
443     }
444   }
446   NS_OBJC_END_TRY_ABORT_BLOCK;
449 // WillTerminate
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.
457 // public
458 void
459 nsAppShell::WillTerminate()
461   if (mTerminated)
462     return;
464   // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
465   // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
466   NS_ProcessPendingEvents(NS_GetCurrentThread());
468   mTerminated = true;
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().
490 // protected virtual
491 void
492 nsAppShell::ScheduleNativeEventCallback()
494   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
496   if (mTerminated)
497     return;
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().
502   NS_ADDREF_THIS();
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.
524 // protected virtual
525 bool
526 nsAppShell::ProcessNextNativeEvent(bool aMayWait)
528   bool moreEvents = false;
530   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
532   bool eventProcessed = false;
533   NSString* currentMode = nil;
535   if (mTerminated)
536     return false;
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.
543   //
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
546   // which see below.
547   if ([NSApp _isRunningAppModal] &&
548       (!gCocoaAppModalWindowList || !gCocoaAppModalWindowList->GeckoModalAboveCocoaModal()))
549     return false;
551   bool wasRunningEventLoop = mRunningEventLoop;
552   mRunningEventLoop = aMayWait;
553   NSDate* waitUntil = nil;
554   if (aMayWait)
555     waitUntil = [NSDate distantFuture];
557   NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
559   do {
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];
606     if (!currentMode)
607       currentMode = NSDefaultRunLoopMode;
609     NSEvent* nextEvent = nil;
611     if (aMayWait) {
612       mozilla::HangMonitor::Suspend();
613     }
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
621                                           untilDate:waitUntil
622                                              inMode:currentMode
623                                             dequeue:YES])) {
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.)
629         //
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
632         // expected.
633         NSModalSession currentAppModalSession = nil;
634         if (gCocoaAppModalWindowList)
635           currentAppModalSession = gCocoaAppModalWindowList->CurrentSession();
637         mozilla::HangMonitor::NotifyActivity();
639         if (currentAppModalSession) {
640           [NSApp _modalSession:currentAppModalSession sendEvent:nextEvent];
641         } else {
642           [NSApp sendEvent:nextEvent];
643         }
644         eventProcessed = true;
645       }
646     } else {
647       if (aMayWait ||
648           (nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
649                                           untilDate:nil
650                                              inMode:currentMode
651                                             dequeue:NO])) {
652         if (nextEvent && ([nextEvent type] == NSPeriodic)) {
653           nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
654                                          untilDate:waitUntil
655                                             inMode:currentMode
656                                            dequeue:YES];
657           [NSApp sendEvent:nextEvent];
658         } else {
659           [currentRunLoop runMode:currentMode beforeDate:waitUntil];
660         }
661         eventProcessed = true;
662       }
663     }
664   } while (mRunningEventLoop);
666   if (eventProcessed && (mHadMoreEventsCount < kHadMoreEventsCountMax)) {
667     moreEvents = ([NSApp nextEventMatchingMask:NSAnyEventMask
668                                      untilDate:nil
669                                         inMode:currentMode
670                                        dequeue:NO] != nil);
671   }
673   if (moreEvents) {
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;
677   } else {
678     mHadMoreEventsCount = 0;
679   }
681   mRunningEventLoop = wasRunningEventLoop;
683   NS_OBJC_END_TRY_ABORT_BLOCK;
685   if (!moreEvents) {
686     nsChildView::UpdateCurrentInputEventCount();
687   }
689   return moreEvents;
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().)
711 bool
712 nsAppShell::InGeckoMainEventLoop()
714   if ((gXULModalLevel > 0) || (mRecursionDepth > 0))
715     return false;
716   if (mNativeEventCallbackDepth <= 0)
717     return false;
718   return true;
721 // Run
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().
732 // public
733 NS_IMETHODIMP
734 nsAppShell::Run(void)
736   NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
737   if (mStarted)
738     return NS_OK;
740   mStarted = true;
741   NS_OBJC_TRY_ABORT([NSApp run]);
743   return NS_OK;
746 NS_IMETHODIMP
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).
756   if (mTerminated) {
757     NS_WARNING("nsAppShell::Exit() called redundantly");
758     return NS_OK;
759   }
761   mTerminated = true;
763   delete gCocoaAppModalWindowList;
764   gCocoaAppModalWindowList = NULL;
766 #ifndef __LP64__
767   TextInputHandler::RemovePluginKeyEventsHandler();
768 #endif
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:]
779   // will do the job.)
780   BOOL cocoaModal = [NSApp _isRunningModal];
781   NS_ASSERTION(!cocoaModal,
782                "Don't call nsAppShell::Exit() from a modal event loop!");
783   if (cocoaModal)
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)
796       NS_RELEASE_THIS();
797   }
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
811 // tightly coupled.
813 // public
814 NS_IMETHODIMP
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.
839 // public
840 NS_IMETHODIMP
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);
856   [pool release];
858   return nsBaseAppShell::AfterProcessNextEvent(aThread, aRecursionDepth);
860   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
864 // AppShellDelegate implementation
867 @implementation AppShellDelegate
868 // initWithAppShell:
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
881                                                object:NSApp];
882     [[NSNotificationCenter defaultCenter] addObserver:self
883                                              selector:@selector(applicationDidBecomeActive:)
884                                                  name:NSApplicationDidBecomeActiveNotification
885                                                object:NSApp];
886     [[NSDistributedNotificationCenter defaultCenter] addObserver:self
887                                                         selector:@selector(beginMenuTracking:)
888                                                             name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
889                                                           object:nil];
890   }
892   return self;
894   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
897 - (void)dealloc
899   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
901   [[NSNotificationCenter defaultCenter] removeObserver:self];
902   [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
903   [super dealloc];
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
924 // while inactive).
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];
932   if (currentEvent) {
933     TextInputHandler::sLastModifierState =
934       [currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
935   }
937   NS_OBJC_END_TRY_ABORT_BLOCK;
940 // beginMenuTracking
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();
953     if (rollupWidget)
954       rollupListener->Rollup(0, nullptr);
955   }
957   NS_OBJC_END_TRY_ABORT_BLOCK;
960 @end
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;
977 @end
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
983 // non-nil).
984 - (NSModalSession)nsAppShell_NSApplication_beginModalSessionForWindow:(NSWindow *)aWindow
986   NSModalSession session =
987     [self nsAppShell_NSApplication_beginModalSessionForWindow:aWindow];
988   if (gCocoaAppModalWindowList)
989     gCocoaAppModalWindowList->PushCocoa(aWindow, session);
990   return 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
1019                                                       object:NSApp];
1022 @end