widget/cocoa/nsAppShell.mm

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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 "GeckoProfiler.h"
    36 #include "pratom.h"
    38 #include "npapi.h"
    40 using namespace mozilla::widget;
    42 // defined in nsCocoaWindow.mm
    43 extern int32_t             gXULModalLevel;
    45 static bool gAppShellMethodsSwizzled = false;
    46 // List of current Cocoa app-modal windows (nested if more than one).
    47 nsCocoaAppModalWindowList *gCocoaAppModalWindowList = NULL;
    49 // Push a Cocoa app-modal window onto the top of our list.
    50 nsresult nsCocoaAppModalWindowList::PushCocoa(NSWindow *aWindow, NSModalSession aSession)
    51 {
    52   NS_ENSURE_STATE(aWindow && aSession);
    53   mList.AppendElement(nsCocoaAppModalWindowListItem(aWindow, aSession));
    54   return NS_OK;
    55 }
    57 // Pop the topmost Cocoa app-modal window off our list.  aWindow and aSession
    58 // are just used to check that it's what we expect it to be.
    59 nsresult nsCocoaAppModalWindowList::PopCocoa(NSWindow *aWindow, NSModalSession aSession)
    60 {
    61   NS_ENSURE_STATE(aWindow && aSession);
    63   for (int i = mList.Length(); i > 0; --i) {
    64     nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1);
    65     if (item.mSession) {
    66       NS_ASSERTION((item.mWindow == aWindow) && (item.mSession == aSession),
    67                    "PopCocoa() called without matching call to PushCocoa()!");
    68       mList.RemoveElementAt(i - 1);
    69       return NS_OK;
    70     }
    71   }
    73   NS_ERROR("PopCocoa() called without matching call to PushCocoa()!");
    74   return NS_ERROR_FAILURE;
    75 }
    77 // Push a Gecko-modal window onto the top of our list.
    78 nsresult nsCocoaAppModalWindowList::PushGecko(NSWindow *aWindow, nsCocoaWindow *aWidget)
    79 {
    80   NS_ENSURE_STATE(aWindow && aWidget);
    81   mList.AppendElement(nsCocoaAppModalWindowListItem(aWindow, aWidget));
    82   return NS_OK;
    83 }
    85 // Pop the topmost Gecko-modal window off our list.  aWindow and aWidget are
    86 // just used to check that it's what we expect it to be.
    87 nsresult nsCocoaAppModalWindowList::PopGecko(NSWindow *aWindow, nsCocoaWindow *aWidget)
    88 {
    89   NS_ENSURE_STATE(aWindow && aWidget);
    91   for (int i = mList.Length(); i > 0; --i) {
    92     nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1);
    93     if (item.mWidget) {
    94       NS_ASSERTION((item.mWindow == aWindow) && (item.mWidget == aWidget),
    95                    "PopGecko() called without matching call to PushGecko()!");
    96       mList.RemoveElementAt(i - 1);
    97       return NS_OK;
    98     }
    99   }
   101   NS_ERROR("PopGecko() called without matching call to PushGecko()!");
   102   return NS_ERROR_FAILURE;
   103 }
   105 // The "current session" is normally the "session" corresponding to the
   106 // top-most Cocoa app-modal window (both on the screen and in our list).
   107 // But because Cocoa app-modal dialog can be "interrupted" by a Gecko-modal
   108 // dialog, the top-most Cocoa app-modal dialog may already have finished
   109 // (and no longer be visible).  In this case we need to check the list for
   110 // the "next" visible Cocoa app-modal window (and return its "session"), or
   111 // (if no Cocoa app-modal window is visible) return nil.  This way we ensure
   112 // (as we need to) that all nested Cocoa app-modal sessions are dealt with
   113 // before we get to any Gecko-modal session(s).  See nsAppShell::
   114 // ProcessNextNativeEvent() below.
   115 NSModalSession nsCocoaAppModalWindowList::CurrentSession()
   116 {
   117   if (![NSApp _isRunningAppModal])
   118     return nil;
   120   NSModalSession currentSession = nil;
   122   for (int i = mList.Length(); i > 0; --i) {
   123     nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1);
   124     if (item.mSession && [item.mWindow isVisible]) {
   125       currentSession = item.mSession;
   126       break;
   127     }
   128   }
   130   return currentSession;
   131 }
   133 // Has a Gecko modal dialog popped up over a Cocoa app-modal dialog?
   134 bool nsCocoaAppModalWindowList::GeckoModalAboveCocoaModal()
   135 {
   136   if (mList.IsEmpty())
   137     return false;
   139   nsCocoaAppModalWindowListItem &topItem = mList.ElementAt(mList.Length() - 1);
   141   return (topItem.mWidget != nullptr);
   142 }
   144 @implementation GeckoNSApplication
   146 - (void)sendEvent:(NSEvent *)anEvent
   147 {
   148   mozilla::HangMonitor::NotifyActivity();
   149   if ([anEvent type] == NSApplicationDefined &&
   150       [anEvent subtype] == kEventSubtypeTrace) {
   151     mozilla::SignalTracerThread();
   152     return;
   153   }
   154   [super sendEvent:anEvent];
   155 }
   157 - (NSEvent*)nextEventMatchingMask:(NSUInteger)mask
   158                         untilDate:(NSDate*)expiration
   159                            inMode:(NSString*)mode
   160                           dequeue:(BOOL)flag
   161 {
   162   if (expiration) {
   163     mozilla::HangMonitor::Suspend();
   164   }
   165   return [super nextEventMatchingMask:mask
   166           untilDate:expiration inMode:mode dequeue:flag];
   167 }
   169 @end
   172 // AppShellDelegate
   173 //
   174 // Cocoa bridge class.  An object of this class is registered to receive
   175 // notifications.
   176 //
   177 @interface AppShellDelegate : NSObject
   178 {
   179   @private
   180     nsAppShell* mAppShell;
   181 }
   183 - (id)initWithAppShell:(nsAppShell*)aAppShell;
   184 - (void)applicationWillTerminate:(NSNotification*)aNotification;
   185 - (void)beginMenuTracking:(NSNotification*)aNotification;
   186 @end
   188 // nsAppShell implementation
   190 NS_IMETHODIMP
   191 nsAppShell::ResumeNative(void)
   192 {
   193   nsresult retval = nsBaseAppShell::ResumeNative();
   194   if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) &&
   195       mSkippedNativeCallback)
   196   {
   197     mSkippedNativeCallback = false;
   198     ScheduleNativeEventCallback();
   199   }
   200   return retval;
   201 }
   203 nsAppShell::nsAppShell()
   204 : mAutoreleasePools(nullptr)
   205 , mDelegate(nullptr)
   206 , mCFRunLoop(NULL)
   207 , mCFRunLoopSource(NULL)
   208 , mRunningEventLoop(false)
   209 , mStarted(false)
   210 , mTerminated(false)
   211 , mSkippedNativeCallback(false)
   212 , mHadMoreEventsCount(0)
   213 , mRecursionDepth(0)
   214 , mNativeEventCallbackDepth(0)
   215 , mNativeEventScheduledDepth(0)
   216 {
   217   // A Cocoa event loop is running here if (and only if) we've been embedded
   218   // by a Cocoa app (like Camino).
   219   mRunningCocoaEmbedded = [NSApp isRunning] ? true : false;
   220 }
   222 nsAppShell::~nsAppShell()
   223 {
   224   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   226   if (mCFRunLoop) {
   227     if (mCFRunLoopSource) {
   228       ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
   229                               kCFRunLoopCommonModes);
   230       ::CFRelease(mCFRunLoopSource);
   231     }
   232     ::CFRelease(mCFRunLoop);
   233   }
   235   if (mAutoreleasePools) {
   236     NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
   237                  "nsAppShell destroyed without popping all autorelease pools");
   238     ::CFRelease(mAutoreleasePools);
   239   }
   241   [mDelegate release];
   243   NS_OBJC_END_TRY_ABORT_BLOCK
   244 }
   246 // An undocumented CoreGraphics framework method, present in the same form
   247 // since at least OS X 10.5.
   248 extern "C" CGError CGSSetDebugOptions(int options);
   250 // Init
   251 //
   252 // Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
   253 // interrupt the main native run loop.
   254 //
   255 // public
   256 nsresult
   257 nsAppShell::Init()
   258 {
   259   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   261   // No event loop is running yet (unless Camino is running, or another
   262   // embedding app that uses NSApplicationMain()).
   263   NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
   265   // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
   266   // by |this|.  CFArray is used instead of NSArray because NSArray wants to
   267   // retain each object you add to it, and you can't retain an
   268   // NSAutoreleasePool.
   269   mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
   270   NS_ENSURE_STATE(mAutoreleasePools);
   272   // Get the path of the nib file, which lives in the GRE location
   273   nsCOMPtr<nsIFile> nibFile;
   274   nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(nibFile));
   275   NS_ENSURE_SUCCESS(rv, rv);
   277   nibFile->AppendNative(NS_LITERAL_CSTRING("res"));
   278   nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib"));
   280   nsAutoCString nibPath;
   281   rv = nibFile->GetNativePath(nibPath);
   282   NS_ENSURE_SUCCESS(rv, rv);
   284   // This call initializes NSApplication unless:
   285   // 1) we're using xre -- NSApp's already been initialized by
   286   //    MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
   287   // 2) Camino is running (or another embedding app that uses
   288   //    NSApplicationMain()) -- NSApp's already been initialized and
   289   //    its main run loop is already running.
   290   [NSBundle loadNibFile:
   291                      [NSString stringWithUTF8String:(const char*)nibPath.get()]
   292       externalNameTable:
   293            [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication]
   294                                        forKey:@"NSOwner"]
   295                withZone:NSDefaultMallocZone()];
   297   mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
   298   NS_ENSURE_STATE(mDelegate);
   300   // Add a CFRunLoopSource to the main native run loop.  The source is
   301   // responsible for interrupting the run loop when Gecko events are ready.
   303   mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
   304   NS_ENSURE_STATE(mCFRunLoop);
   305   ::CFRetain(mCFRunLoop);
   307   CFRunLoopSourceContext context;
   308   bzero(&context, sizeof(context));
   309   // context.version = 0;
   310   context.info = this;
   311   context.perform = ProcessGeckoEvents;
   313   mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
   314   NS_ENSURE_STATE(mCFRunLoopSource);
   316   ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
   318   rv = nsBaseAppShell::Init();
   320 #ifndef __LP64__
   321   TextInputHandler::InstallPluginKeyEventsHandler();
   322 #endif
   324   gCocoaAppModalWindowList = new nsCocoaAppModalWindowList;
   325   if (!gAppShellMethodsSwizzled) {
   326     nsToolkit::SwizzleMethods([NSApplication class], @selector(beginModalSessionForWindow:),
   327                               @selector(nsAppShell_NSApplication_beginModalSessionForWindow:));
   328     nsToolkit::SwizzleMethods([NSApplication class], @selector(endModalSession:),
   329                               @selector(nsAppShell_NSApplication_endModalSession:));
   330     // We should only replace the original terminate: method if we're not
   331     // running in a Cocoa embedder (like Camino).  See bug 604901.
   332     if (!mRunningCocoaEmbedded) {
   333       nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
   334                                 @selector(nsAppShell_NSApplication_terminate:));
   335     }
   336     gAppShellMethodsSwizzled = true;
   337   }
   339   if (nsCocoaFeatures::OnYosemiteOrLater()) {
   340     // Explicitly turn off CGEvent logging.  This works around bug 1092855.
   341     // If there are already CGEvents in the log, turning off logging also
   342     // causes those events to be written to disk.  But at this point no
   343     // CGEvents have yet been processed.  CGEvents are events (usually
   344     // input events) pulled from the WindowServer.  An option of 0x80000008
   345     // turns on CGEvent logging.
   346     CGSSetDebugOptions(0x80000007);
   347   }
   349   [localPool release];
   351   return rv;
   353   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   354 }
   356 // ProcessGeckoEvents
   357 //
   358 // The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
   359 // signalled from ScheduleNativeEventCallback.
   360 //
   361 // Arrange for Gecko events to be processed on demand (in response to a call
   362 // to ScheduleNativeEventCallback(), if processing of Gecko events via "native
   363 // methods" hasn't been suspended).  This happens in NativeEventCallback().
   364 //
   365 // protected static
   366 void
   367 nsAppShell::ProcessGeckoEvents(void* aInfo)
   368 {
   369   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   370   PROFILER_LABEL("Events", "ProcessGeckoEvents");
   371   nsAppShell* self = static_cast<nsAppShell*> (aInfo);
   373   if (self->mRunningEventLoop) {
   374     self->mRunningEventLoop = false;
   376     // The run loop may be sleeping -- [NSRunLoop runMode:...]
   377     // won't return until it's given a reason to wake up.  Awaken it by
   378     // posting a bogus event.  There's no need to make the event
   379     // presentable.
   380     //
   381     // But _don't_ set windowNumber to '-1' -- that can lead to nasty
   382     // wierdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
   383     // these fake events, because the -1 has gotten changed into the number
   384     // of an actual NSWindow object, and that NSWindow object has just been
   385     // destroyed).  Setting windowNumber to '0' seems to work fine -- this
   386     // seems to prevent the OS from ever trying to associate our bogus event
   387     // with a particular NSWindow object.
   388     [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
   389                                         location:NSMakePoint(0,0)
   390                                    modifierFlags:0
   391                                        timestamp:0
   392                                     windowNumber:0
   393                                          context:NULL
   394                                          subtype:kEventSubtypeNone
   395                                            data1:0
   396                                            data2:0]
   397              atStart:NO];
   398   }
   400   if (self->mSuspendNativeCount <= 0) {
   401     ++self->mNativeEventCallbackDepth;
   402     self->NativeEventCallback();
   403     --self->mNativeEventCallbackDepth;
   404   } else {
   405     self->mSkippedNativeCallback = true;
   406   }
   408   // Still needed to fix bug 343033 ("5-10 second delay or hang or crash
   409   // when quitting Cocoa Firefox").
   410   [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
   411                                       location:NSMakePoint(0,0)
   412                                  modifierFlags:0
   413                                      timestamp:0
   414                                   windowNumber:0
   415                                        context:NULL
   416                                        subtype:kEventSubtypeNone
   417                                          data1:0
   418                                          data2:0]
   419            atStart:NO];
   421   // Normally every call to ScheduleNativeEventCallback() results in
   422   // exactly one call to ProcessGeckoEvents().  So each Release() here
   423   // normally balances exactly one AddRef() in ScheduleNativeEventCallback().
   424   // But if Exit() is called just after ScheduleNativeEventCallback(), the
   425   // corresponding call to ProcessGeckoEvents() will never happen.  We check
   426   // for this possibility in two different places -- here and in Exit()
   427   // itself.  If we find here that Exit() has been called (that mTerminated
   428   // is true), it's because we've been called recursively, that Exit() was
   429   // called from self->NativeEventCallback() above, and that we're unwinding
   430   // the recursion.  In this case we'll never be called again, and we balance
   431   // here any extra calls to ScheduleNativeEventCallback().
   432   //
   433   // When ProcessGeckoEvents() is called recursively, it's because of a
   434   // call to ScheduleNativeEventCallback() from NativeEventCallback().  We
   435   // balance the "extra" AddRefs here (rather than always in Exit()) in order
   436   // to ensure that 'self' stays alive until the end of this method.  We also
   437   // make sure not to finish the balancing until all the recursion has been
   438   // unwound.
   439   if (self->mTerminated) {
   440     int32_t releaseCount = 0;
   441     if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) {
   442       releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth,
   443                                    self->mNativeEventCallbackDepth);
   444     }
   445     while (releaseCount-- > self->mNativeEventCallbackDepth)
   446       self->Release();
   447   } else {
   448     // As best we can tell, every call to ProcessGeckoEvents() is triggered
   449     // by a call to ScheduleNativeEventCallback().  But we've seen a few
   450     // (non-reproducible) cases of double-frees that *might* have been caused
   451     // by spontaneous calls (from the OS) to ProcessGeckoEvents().  So we
   452     // deal with that possibility here.
   453     if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) {
   454       PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0);
   455       NS_WARNING("Spontaneous call to ProcessGeckoEvents()!");
   456     } else {
   457       self->Release();
   458     }
   459   }
   461   NS_OBJC_END_TRY_ABORT_BLOCK;
   462 }
   464 // WillTerminate
   465 //
   466 // Called by the AppShellDelegate when an NSApplicationWillTerminate
   467 // notification is posted.  After this method is called, native events should
   468 // no longer be processed.  The NSApplicationWillTerminate notification is
   469 // only posted when [NSApp terminate:] is called, which doesn't happen on a
   470 // "normal" application quit.
   471 //
   472 // public
   473 void
   474 nsAppShell::WillTerminate()
   475 {
   476   if (mTerminated)
   477     return;
   479   // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
   480   // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
   481   NS_ProcessPendingEvents(NS_GetCurrentThread());
   483   mTerminated = true;
   484 }
   486 // ScheduleNativeEventCallback
   487 //
   488 // Called (possibly on a non-main thread) when Gecko has an event that
   489 // needs to be processed.  The Gecko event needs to be processed on the
   490 // main thread, so the native run loop must be interrupted.
   491 //
   492 // In nsBaseAppShell.cpp, the mNativeEventPending variable is used to
   493 // ensure that ScheduleNativeEventCallback() is called no more than once
   494 // per call to NativeEventCallback().  ProcessGeckoEvents() can skip its
   495 // call to NativeEventCallback() if processing of Gecko events by native
   496 // means is suspended (using nsIAppShell::SuspendNative()), which will
   497 // suspend calls from nsBaseAppShell::OnDispatchedEvent() to
   498 // ScheduleNativeEventCallback().  But when Gecko event processing by
   499 // native means is resumed (in ResumeNative()), an extra call is made to
   500 // ScheduleNativeEventCallback() (from ResumeNative()).  This triggers
   501 // another call to ProcessGeckoEvents(), which calls NativeEventCallback(),
   502 // and nsBaseAppShell::OnDispatchedEvent() resumes calling
   503 // ScheduleNativeEventCallback().
   504 //
   505 // protected virtual
   506 void
   507 nsAppShell::ScheduleNativeEventCallback()
   508 {
   509   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   511   if (mTerminated)
   512     return;
   514   // Each AddRef() here is normally balanced by exactly one Release() in
   515   // ProcessGeckoEvents().  But there are exceptions, for which see
   516   // ProcessGeckoEvents() and Exit().
   517   NS_ADDREF_THIS();
   518   PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth);
   520   // This will invoke ProcessGeckoEvents on the main thread.
   521   ::CFRunLoopSourceSignal(mCFRunLoopSource);
   522   ::CFRunLoopWakeUp(mCFRunLoop);
   524   NS_OBJC_END_TRY_ABORT_BLOCK;
   525 }
   527 // ProcessNextNativeEvent
   528 //
   529 // If aMayWait is false, process a single native event.  If it is true, run
   530 // the native run loop until stopped by ProcessGeckoEvents.
   531 //
   532 // Returns true if more events are waiting in the native event queue.
   533 //
   534 // But (now that we're using [NSRunLoop runMode:beforeDate:]) it's too
   535 // expensive to call ProcessNextNativeEvent() many times in a row (in a
   536 // tight loop), so we never return true more than kHadMoreEventsCountMax
   537 // times in a row.  This doesn't seem to cause native event starvation.
   538 //
   539 // protected virtual
   540 bool
   541 nsAppShell::ProcessNextNativeEvent(bool aMayWait)
   542 {
   543   bool moreEvents = false;
   545   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   547   bool eventProcessed = false;
   548   NSString* currentMode = nil;
   550   if (mTerminated)
   551     return false;
   553   // We don't want any native events to be processed here (via Gecko) while
   554   // Cocoa is displaying an app-modal dialog (as opposed to a window-modal
   555   // "sheet" or a Gecko-modal dialog).  Otherwise Cocoa event-processing loops
   556   // may be interrupted, and inappropriate events may get through to the
   557   // browser window(s) underneath.  This resolves bmo bugs 419668 and 420967.
   558   //
   559   // But we need more complex handling (we need to make an exception) if a
   560   // Gecko modal dialog is running above the Cocoa app-modal dialog -- for
   561   // which see below.
   562   if ([NSApp _isRunningAppModal] &&
   563       (!gCocoaAppModalWindowList || !gCocoaAppModalWindowList->GeckoModalAboveCocoaModal()))
   564     return false;
   566   bool wasRunningEventLoop = mRunningEventLoop;
   567   mRunningEventLoop = aMayWait;
   568   NSDate* waitUntil = nil;
   569   if (aMayWait)
   570     waitUntil = [NSDate distantFuture];
   572   NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
   574   do {
   575     // No autorelease pool is provided here, because OnProcessNextEvent
   576     // and AfterProcessNextEvent are responsible for maintaining it.
   577     NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
   578                  "No autorelease pool for native event");
   580     // If an event is waiting to be processed, run the main event loop
   581     // just long enough to process it.  For some reason, using [NSApp
   582     // nextEventMatchingMask:...] to dequeue the event and [NSApp sendEvent:]
   583     // to "send" it causes trouble, so we no longer do that.  (The trouble
   584     // was very strange, and only happened while processing Gecko events on
   585     // demand (via ProcessGeckoEvents()), as opposed to processing Gecko
   586     // events in a tight loop (via nsBaseAppShell::Run()):  Particularly in
   587     // Camino, mouse-down events sometimes got dropped (or mis-handled), so
   588     // that (for example) you sometimes needed to click more than once on a
   589     // button to make it work (the zoom button was particularly susceptible).
   590     // You also sometimes had to ctrl-click or right-click multiple times to
   591     // bring up a context menu.)
   593     // Now that we're using [NSRunLoop runMode:beforeDate:], it's too
   594     // expensive to call ProcessNextNativeEvent() many times in a row, so we
   595     // never return true more than kHadMoreEventsCountMax in a row.  I'm not
   596     // entirely sure why [NSRunLoop runMode:beforeDate:] is too expensive,
   597     // since it and its cousin [NSRunLoop acceptInputForMode:beforeDate:] are
   598     // designed to be called in a tight loop.  Possibly the problem is due to
   599     // combining [NSRunLoop runMode:beforeDate] with [NSApp
   600     // nextEventMatchingMask:...].
   602     // We special-case timer events (events of type NSPeriodic) to avoid
   603     // starving them.  Apple's documentation is very scanty, and it's now
   604     // more scanty than it used to be.  But it appears that [NSRunLoop
   605     // acceptInputForMode:beforeDate:] doesn't process timer events at all,
   606     // that it is called from [NSRunLoop runMode:beforeDate:], and that
   607     // [NSRunLoop runMode:beforeDate:], though it does process timer events,
   608     // doesn't return after doing so.  To get around this, when aWait is
   609     // false we check for timer events and process them using [NSApp
   610     // sendEvent:].  When aWait is true [NSRunLoop runMode:beforeDate:]
   611     // will only return on a "real" event.  But there's code in
   612     // ProcessGeckoEvents() that should (when need be) wake us up by sending
   613     // a "fake" "real" event.  (See Apple's current doc on [NSRunLoop
   614     // runMode:beforeDate:] and a quote from what appears to be an older
   615     // version of this doc at
   616     // http://lists.apple.com/archives/cocoa-dev/2001/May/msg00559.html.)
   618     // If the current mode is something else than NSDefaultRunLoopMode, look
   619     // for events in that mode.
   620     currentMode = [currentRunLoop currentMode];
   621     if (!currentMode)
   622       currentMode = NSDefaultRunLoopMode;
   624     NSEvent* nextEvent = nil;
   626     if (aMayWait) {
   627       mozilla::HangMonitor::Suspend();
   628     }
   630     // If we're running modal (or not in a Gecko "main" event loop) we still
   631     // need to use nextEventMatchingMask and sendEvent -- otherwise (in
   632     // Minefield) the modal window (or non-main event loop) won't receive key
   633     // events or most mouse events.
   634     //
   635     // Add aMayWait to minimize the number of calls to -[NSApp sendEvent:]
   636     // made from nsAppShell::ProcessNextNativeEvent() (and indirectly from
   637     // nsBaseAppShell::OnProcessNextEvent()), to work around bug 959281.
   638     if ([NSApp _isRunningModal] || (aMayWait && !InGeckoMainEventLoop())) {
   639       if ((nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
   640                                           untilDate:waitUntil
   641                                              inMode:currentMode
   642                                             dequeue:YES])) {
   643         // If we're in a Cocoa app-modal session that's been interrupted by a
   644         // Gecko-modal dialog, send the event to the Cocoa app-modal dialog's
   645         // session.  This ensures that the app-modal session won't be starved
   646         // of events, and fixes bugs 463473 and 442442.  (The case of an
   647         // ordinary Cocoa app-modal dialog has been dealt with above.)
   648         //
   649         // Otherwise (if we're in an ordinary Gecko-modal dialog, or if we're
   650         // otherwise not in a Gecko main event loop), process the event as
   651         // expected.
   652         NSModalSession currentAppModalSession = nil;
   653         if (gCocoaAppModalWindowList)
   654           currentAppModalSession = gCocoaAppModalWindowList->CurrentSession();
   656         mozilla::HangMonitor::NotifyActivity();
   658         if (currentAppModalSession) {
   659           [NSApp _modalSession:currentAppModalSession sendEvent:nextEvent];
   660         } else {
   661           [NSApp sendEvent:nextEvent];
   662         }
   663         eventProcessed = true;
   664       }
   665     } else {
   666       if (aMayWait ||
   667           (nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
   668                                           untilDate:nil
   669                                              inMode:currentMode
   670                                             dequeue:NO])) {
   671         if (nextEvent && ([nextEvent type] == NSPeriodic)) {
   672           nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
   673                                          untilDate:waitUntil
   674                                             inMode:currentMode
   675                                            dequeue:YES];
   676           [NSApp sendEvent:nextEvent];
   677         } else {
   678           [currentRunLoop runMode:currentMode beforeDate:waitUntil];
   679         }
   680         eventProcessed = true;
   681       }
   682     }
   683   } while (mRunningEventLoop);
   685   if (eventProcessed && (mHadMoreEventsCount < kHadMoreEventsCountMax)) {
   686     moreEvents = ([NSApp nextEventMatchingMask:NSAnyEventMask
   687                                      untilDate:nil
   688                                         inMode:currentMode
   689                                        dequeue:NO] != nil);
   690   }
   692   if (moreEvents) {
   693     // Once this reaches kHadMoreEventsCountMax, it will be reset to 0 the
   694     // next time through (whether or not we process any events then).
   695     ++mHadMoreEventsCount;
   696   } else {
   697     mHadMoreEventsCount = 0;
   698   }
   700   mRunningEventLoop = wasRunningEventLoop;
   702   NS_OBJC_END_TRY_ABORT_BLOCK;
   704   if (!moreEvents) {
   705     nsChildView::UpdateCurrentInputEventCount();
   706   }
   708   return moreEvents;
   709 }
   711 // Returns true if Gecko events are currently being processed in its "main"
   712 // event loop (or one of its "main" event loops).  Returns false if Gecko
   713 // events are being processed in a "nested" event loop, or if we're not
   714 // running in any sort of Gecko event loop.  How we process native events in
   715 // ProcessNextNativeEvent() turns on our decision (and if we make the wrong
   716 // choice, the result may be a hang).
   717 //
   718 // We define the "main" event loop(s) as the place (or places) where Gecko
   719 // event processing "normally" takes place, and all other Gecko event loops
   720 // as "nested".  The "nested" event loops are normally processed while a call
   721 // from a "main" event loop is on the stack ... but not always.  For example,
   722 // the Venkman JavaScript debugger runs a "nested" event loop (in jsdService::
   723 // EnterNestedEventLoop()) whenever it breaks into the current script.  But
   724 // if this happens as the result of the user pressing a key combination, there
   725 // won't be any other Gecko event-processing call on the stack (e.g.
   726 // NS_ProcessNextEvent() or NS_ProcessPendingEvents()).  (In the current
   727 // nsAppShell implementation, what counts as the "main" event loop is what
   728 // nsBaseAppShell::NativeEventCallback() does to process Gecko events.  We
   729 // don't currently use nsBaseAppShell::Run().)
   730 bool
   731 nsAppShell::InGeckoMainEventLoop()
   732 {
   733   if ((gXULModalLevel > 0) || (mRecursionDepth > 0))
   734     return false;
   735   if (mNativeEventCallbackDepth <= 0)
   736     return false;
   737   return true;
   738 }
   740 // Run
   741 //
   742 // Overrides the base class's Run() method to call [NSApp run] (which spins
   743 // the native run loop until the application quits).  Since (unlike the base
   744 // class's Run() method) we don't process any Gecko events here, they need
   745 // to be processed elsewhere (in NativeEventCallback(), called from
   746 // ProcessGeckoEvents()).
   747 //
   748 // Camino calls [NSApp run] on its own (via NSApplicationMain()), and so
   749 // doesn't call nsAppShell::Run().
   750 //
   751 // public
   752 NS_IMETHODIMP
   753 nsAppShell::Run(void)
   754 {
   755   NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
   756   if (mStarted || mTerminated)
   757     return NS_OK;
   759   mStarted = true;
   760   NS_OBJC_TRY_ABORT([NSApp run]);
   762   return NS_OK;
   763 }
   765 NS_IMETHODIMP
   766 nsAppShell::Exit(void)
   767 {
   768   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   770   // This method is currently called more than once -- from (according to
   771   // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
   772   // XPCOM shutdown notification that nsBaseAppShell has registered to
   773   // receive.  So we need to ensure that multiple calls won't break anything.
   774   // But we should also complain about it (since it isn't quite kosher).
   775   if (mTerminated) {
   776     NS_WARNING("nsAppShell::Exit() called redundantly");
   777     return NS_OK;
   778   }
   780   mTerminated = true;
   782   delete gCocoaAppModalWindowList;
   783   gCocoaAppModalWindowList = NULL;
   785 #ifndef __LP64__
   786   TextInputHandler::RemovePluginKeyEventsHandler();
   787 #endif
   789   // Quoting from Apple's doc on the [NSApplication stop:] method (from their
   790   // doc on the NSApplication class):  "If this method is invoked during a
   791   // modal event loop, it will break that loop but not the main event loop."
   792   // nsAppShell::Exit() shouldn't be called from a modal event loop.  So if
   793   // it is we complain about it (to users of debug builds) and call [NSApp
   794   // stop:] one extra time.  (I'm not sure if modal event loops can be nested
   795   // -- Apple's docs don't say one way or the other.  But the return value
   796   // of [NSApp _isRunningModal] doesn't change immediately after a call to
   797   // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
   798   // will do the job.)
   799   BOOL cocoaModal = [NSApp _isRunningModal];
   800   NS_ASSERTION(!cocoaModal,
   801                "Don't call nsAppShell::Exit() from a modal event loop!");
   802   if (cocoaModal)
   803     [NSApp stop:nullptr];
   804   [NSApp stop:nullptr];
   806   // A call to Exit() just after a call to ScheduleNativeEventCallback()
   807   // prevents the (normally) matching call to ProcessGeckoEvents() from
   808   // happening.  If we've been called from ProcessGeckoEvents() (as usually
   809   // happens), we take care of it there.  But if we have an unbalanced call
   810   // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
   811   // stack, we need to take care of the problem here.
   812   if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
   813     int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
   814     while (releaseCount-- > 0)
   815       NS_RELEASE_THIS();
   816   }
   818   return nsBaseAppShell::Exit();
   820   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   821 }
   823 // OnProcessNextEvent
   824 //
   825 // This nsIThreadObserver method is called prior to processing an event.
   826 // Set up an autorelease pool that will service any autoreleased Cocoa
   827 // objects during this event.  This includes native events processed by
   828 // ProcessNextNativeEvent.  The autorelease pool will be popped by
   829 // AfterProcessNextEvent, it is important for these two methods to be
   830 // tightly coupled.
   831 //
   832 // public
   833 NS_IMETHODIMP
   834 nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
   835                                uint32_t aRecursionDepth)
   836 {
   837   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   839   mRecursionDepth = aRecursionDepth;
   841   NS_ASSERTION(mAutoreleasePools,
   842                "No stack on which to store autorelease pool");
   844   NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
   845   ::CFArrayAppendValue(mAutoreleasePools, pool);
   847   return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait, aRecursionDepth);
   849   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   850 }
   852 // AfterProcessNextEvent
   853 //
   854 // This nsIThreadObserver method is called after event processing is complete.
   855 // The Cocoa implementation cleans up the autorelease pool create by the
   856 // previous OnProcessNextEvent call.
   857 //
   858 // public
   859 NS_IMETHODIMP
   860 nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
   861                                   uint32_t aRecursionDepth,
   862                                   bool aEventWasProcessed)
   863 {
   864   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   866   mRecursionDepth = aRecursionDepth;
   868   CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
   870   NS_ASSERTION(mAutoreleasePools && count,
   871                "Processed an event, but there's no autorelease pool?");
   873   const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*>
   874     (::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
   875   ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
   876   [pool release];
   878   return nsBaseAppShell::AfterProcessNextEvent(aThread, aRecursionDepth,
   879                                                aEventWasProcessed);
   881   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   882 }
   885 // AppShellDelegate implementation
   888 @implementation AppShellDelegate
   889 // initWithAppShell:
   890 //
   891 // Constructs the AppShellDelegate object
   892 - (id)initWithAppShell:(nsAppShell*)aAppShell
   893 {
   894   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   896   if ((self = [self init])) {
   897     mAppShell = aAppShell;
   899     [[NSNotificationCenter defaultCenter] addObserver:self
   900                                              selector:@selector(applicationWillTerminate:)
   901                                                  name:NSApplicationWillTerminateNotification
   902                                                object:NSApp];
   903     [[NSNotificationCenter defaultCenter] addObserver:self
   904                                              selector:@selector(applicationDidBecomeActive:)
   905                                                  name:NSApplicationDidBecomeActiveNotification
   906                                                object:NSApp];
   907     [[NSDistributedNotificationCenter defaultCenter] addObserver:self
   908                                                         selector:@selector(beginMenuTracking:)
   909                                                             name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
   910                                                           object:nil];
   911   }
   913   return self;
   915   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   916 }
   918 - (void)dealloc
   919 {
   920   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   922   [[NSNotificationCenter defaultCenter] removeObserver:self];
   923   [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
   924   [super dealloc];
   926   NS_OBJC_END_TRY_ABORT_BLOCK;
   927 }
   929 // applicationWillTerminate:
   930 //
   931 // Notify the nsAppShell that native event processing should be discontinued.
   932 - (void)applicationWillTerminate:(NSNotification*)aNotification
   933 {
   934   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   936   mAppShell->WillTerminate();
   938   NS_OBJC_END_TRY_ABORT_BLOCK;
   939 }
   941 // applicationDidBecomeActive
   942 //
   943 // Make sure TextInputHandler::sLastModifierState is updated when we become
   944 // active (since we won't have received [ChildView flagsChanged:] messages
   945 // while inactive).
   946 - (void)applicationDidBecomeActive:(NSNotification*)aNotification
   947 {
   948   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   950   // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
   951   // to worry about getting an NSInternalInconsistencyException here.
   952   NSEvent* currentEvent = [NSApp currentEvent];
   953   if (currentEvent) {
   954     TextInputHandler::sLastModifierState =
   955       [currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
   956   }
   958   NS_OBJC_END_TRY_ABORT_BLOCK;
   959 }
   961 // beginMenuTracking
   962 //
   963 // Roll up our context menu (if any) when some other app (or the OS) opens
   964 // any sort of menu.  But make sure we don't do this for notifications we
   965 // send ourselves (whose 'sender' will be @"org.mozilla.gecko.PopupWindow").
   966 - (void)beginMenuTracking:(NSNotification*)aNotification
   967 {
   968   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   970   NSString *sender = [aNotification object];
   971   if (!sender || ![sender isEqualToString:@"org.mozilla.gecko.PopupWindow"]) {
   972     nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
   973     nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
   974     if (rollupWidget)
   975       rollupListener->Rollup(0, nullptr, nullptr);
   976   }
   978   NS_OBJC_END_TRY_ABORT_BLOCK;
   979 }
   981 @end
   983 // We hook beginModalSessionForWindow: and endModalSession: in order to
   984 // maintain a list of Cocoa app-modal windows (and the "sessions" to which
   985 // they correspond).  We need this in order to deal with the consequences
   986 // of a Cocoa app-modal dialog being "interrupted" by a Gecko-modal dialog.
   987 // See nsCocoaAppModalWindowList::CurrentSession() and
   988 // nsAppShell::ProcessNextNativeEvent() above.
   989 //
   990 // We hook terminate: in order to make OS-initiated termination work nicely
   991 // with Gecko's shutdown sequence.  (Two ways to trigger OS-initiated
   992 // termination:  1) Quit from the Dock menu; 2) Log out from (or shut down)
   993 // your computer while the browser is active.)
   994 @interface NSApplication (MethodSwizzling)
   995 - (NSModalSession)nsAppShell_NSApplication_beginModalSessionForWindow:(NSWindow *)aWindow;
   996 - (void)nsAppShell_NSApplication_endModalSession:(NSModalSession)aSession;
   997 - (void)nsAppShell_NSApplication_terminate:(id)sender;
   998 @end
  1000 @implementation NSApplication (MethodSwizzling)
  1002 // Called if and only if a Cocoa app-modal session is beginning.  Always call
  1003 // gCocoaAppModalWindowList->PushCocoa() here (if gCocoaAppModalWindowList is
  1004 // non-nil).
  1005 - (NSModalSession)nsAppShell_NSApplication_beginModalSessionForWindow:(NSWindow *)aWindow
  1007   NSModalSession session =
  1008     [self nsAppShell_NSApplication_beginModalSessionForWindow:aWindow];
  1009   if (gCocoaAppModalWindowList)
  1010     gCocoaAppModalWindowList->PushCocoa(aWindow, session);
  1011   return session;
  1014 // Called to end any Cocoa modal session (app-modal or otherwise).  Only call
  1015 // gCocoaAppModalWindowList->PopCocoa() when an app-modal session is ending
  1016 // (and when gCocoaAppModalWindowList is non-nil).
  1017 - (void)nsAppShell_NSApplication_endModalSession:(NSModalSession)aSession
  1019   BOOL wasRunningAppModal = [NSApp _isRunningAppModal];
  1020   NSWindow *prevAppModalWindow = [NSApp modalWindow];
  1021   [self nsAppShell_NSApplication_endModalSession:aSession];
  1022   if (gCocoaAppModalWindowList &&
  1023       wasRunningAppModal && (prevAppModalWindow != [NSApp modalWindow]))
  1024     gCocoaAppModalWindowList->PopCocoa(prevAppModalWindow, aSession);
  1027 // Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
  1028 // has returned NSTerminateNow.  This method "subclasses" and replaces the
  1029 // OS's original implementation.  The only thing the orginal method does which
  1030 // we need is that it posts NSApplicationWillTerminateNotification.  Everything
  1031 // else is unneeded (because it's handled elsewhere), or actively interferes
  1032 // with Gecko's shutdown sequence.  For example the original terminate: method
  1033 // causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
  1034 // above), which means that nothing runs after the call to nsAppStartup::Run()
  1035 // in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
  1036 // and NS_ShutdownXPCOM() never get called.
  1037 - (void)nsAppShell_NSApplication_terminate:(id)sender
  1039   [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification
  1040                                                       object:NSApp];
  1043 @end

mercurial