widget/cocoa/nsAppShell.mm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/widget/cocoa/nsAppShell.mm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1043 @@
     1.4 +/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +/*
    1.10 + * Runs the main native Cocoa run loop, interrupting it as needed to process
    1.11 + * Gecko events.
    1.12 + */
    1.13 +
    1.14 +#import <Cocoa/Cocoa.h>
    1.15 +#include <dlfcn.h>
    1.16 +
    1.17 +#include "CustomCocoaEvents.h"
    1.18 +#include "mozilla/WidgetTraceEvent.h"
    1.19 +#include "nsAppShell.h"
    1.20 +#include "nsCOMPtr.h"
    1.21 +#include "nsIFile.h"
    1.22 +#include "nsDirectoryServiceDefs.h"
    1.23 +#include "nsString.h"
    1.24 +#include "nsIRollupListener.h"
    1.25 +#include "nsIWidget.h"
    1.26 +#include "nsThreadUtils.h"
    1.27 +#include "nsIWindowMediator.h"
    1.28 +#include "nsServiceManagerUtils.h"
    1.29 +#include "nsIInterfaceRequestor.h"
    1.30 +#include "nsIWebBrowserChrome.h"
    1.31 +#include "nsObjCExceptions.h"
    1.32 +#include "nsCocoaFeatures.h"
    1.33 +#include "nsCocoaUtils.h"
    1.34 +#include "nsChildView.h"
    1.35 +#include "nsToolkit.h"
    1.36 +#include "TextInputHandler.h"
    1.37 +#include "mozilla/HangMonitor.h"
    1.38 +#include "GeckoProfiler.h"
    1.39 +#include "pratom.h"
    1.40 +
    1.41 +#include "npapi.h"
    1.42 +
    1.43 +using namespace mozilla::widget;
    1.44 +
    1.45 +// defined in nsCocoaWindow.mm
    1.46 +extern int32_t             gXULModalLevel;
    1.47 +
    1.48 +static bool gAppShellMethodsSwizzled = false;
    1.49 +// List of current Cocoa app-modal windows (nested if more than one).
    1.50 +nsCocoaAppModalWindowList *gCocoaAppModalWindowList = NULL;
    1.51 +
    1.52 +// Push a Cocoa app-modal window onto the top of our list.
    1.53 +nsresult nsCocoaAppModalWindowList::PushCocoa(NSWindow *aWindow, NSModalSession aSession)
    1.54 +{
    1.55 +  NS_ENSURE_STATE(aWindow && aSession);
    1.56 +  mList.AppendElement(nsCocoaAppModalWindowListItem(aWindow, aSession));
    1.57 +  return NS_OK;
    1.58 +}
    1.59 +
    1.60 +// Pop the topmost Cocoa app-modal window off our list.  aWindow and aSession
    1.61 +// are just used to check that it's what we expect it to be.
    1.62 +nsresult nsCocoaAppModalWindowList::PopCocoa(NSWindow *aWindow, NSModalSession aSession)
    1.63 +{
    1.64 +  NS_ENSURE_STATE(aWindow && aSession);
    1.65 +
    1.66 +  for (int i = mList.Length(); i > 0; --i) {
    1.67 +    nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1);
    1.68 +    if (item.mSession) {
    1.69 +      NS_ASSERTION((item.mWindow == aWindow) && (item.mSession == aSession),
    1.70 +                   "PopCocoa() called without matching call to PushCocoa()!");
    1.71 +      mList.RemoveElementAt(i - 1);
    1.72 +      return NS_OK;
    1.73 +    }
    1.74 +  }
    1.75 +
    1.76 +  NS_ERROR("PopCocoa() called without matching call to PushCocoa()!");
    1.77 +  return NS_ERROR_FAILURE;
    1.78 +}
    1.79 +
    1.80 +// Push a Gecko-modal window onto the top of our list.
    1.81 +nsresult nsCocoaAppModalWindowList::PushGecko(NSWindow *aWindow, nsCocoaWindow *aWidget)
    1.82 +{
    1.83 +  NS_ENSURE_STATE(aWindow && aWidget);
    1.84 +  mList.AppendElement(nsCocoaAppModalWindowListItem(aWindow, aWidget));
    1.85 +  return NS_OK;
    1.86 +}
    1.87 +
    1.88 +// Pop the topmost Gecko-modal window off our list.  aWindow and aWidget are
    1.89 +// just used to check that it's what we expect it to be.
    1.90 +nsresult nsCocoaAppModalWindowList::PopGecko(NSWindow *aWindow, nsCocoaWindow *aWidget)
    1.91 +{
    1.92 +  NS_ENSURE_STATE(aWindow && aWidget);
    1.93 +
    1.94 +  for (int i = mList.Length(); i > 0; --i) {
    1.95 +    nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1);
    1.96 +    if (item.mWidget) {
    1.97 +      NS_ASSERTION((item.mWindow == aWindow) && (item.mWidget == aWidget),
    1.98 +                   "PopGecko() called without matching call to PushGecko()!");
    1.99 +      mList.RemoveElementAt(i - 1);
   1.100 +      return NS_OK;
   1.101 +    }
   1.102 +  }
   1.103 +
   1.104 +  NS_ERROR("PopGecko() called without matching call to PushGecko()!");
   1.105 +  return NS_ERROR_FAILURE;
   1.106 +}
   1.107 +
   1.108 +// The "current session" is normally the "session" corresponding to the
   1.109 +// top-most Cocoa app-modal window (both on the screen and in our list).
   1.110 +// But because Cocoa app-modal dialog can be "interrupted" by a Gecko-modal
   1.111 +// dialog, the top-most Cocoa app-modal dialog may already have finished
   1.112 +// (and no longer be visible).  In this case we need to check the list for
   1.113 +// the "next" visible Cocoa app-modal window (and return its "session"), or
   1.114 +// (if no Cocoa app-modal window is visible) return nil.  This way we ensure
   1.115 +// (as we need to) that all nested Cocoa app-modal sessions are dealt with
   1.116 +// before we get to any Gecko-modal session(s).  See nsAppShell::
   1.117 +// ProcessNextNativeEvent() below.
   1.118 +NSModalSession nsCocoaAppModalWindowList::CurrentSession()
   1.119 +{
   1.120 +  if (![NSApp _isRunningAppModal])
   1.121 +    return nil;
   1.122 +
   1.123 +  NSModalSession currentSession = nil;
   1.124 +
   1.125 +  for (int i = mList.Length(); i > 0; --i) {
   1.126 +    nsCocoaAppModalWindowListItem &item = mList.ElementAt(i - 1);
   1.127 +    if (item.mSession && [item.mWindow isVisible]) {
   1.128 +      currentSession = item.mSession;
   1.129 +      break;
   1.130 +    }
   1.131 +  }
   1.132 +
   1.133 +  return currentSession;
   1.134 +}
   1.135 +
   1.136 +// Has a Gecko modal dialog popped up over a Cocoa app-modal dialog?
   1.137 +bool nsCocoaAppModalWindowList::GeckoModalAboveCocoaModal()
   1.138 +{
   1.139 +  if (mList.IsEmpty())
   1.140 +    return false;
   1.141 +
   1.142 +  nsCocoaAppModalWindowListItem &topItem = mList.ElementAt(mList.Length() - 1);
   1.143 +
   1.144 +  return (topItem.mWidget != nullptr);
   1.145 +}
   1.146 +
   1.147 +@implementation GeckoNSApplication
   1.148 +
   1.149 +- (void)sendEvent:(NSEvent *)anEvent
   1.150 +{
   1.151 +  mozilla::HangMonitor::NotifyActivity();
   1.152 +  if ([anEvent type] == NSApplicationDefined &&
   1.153 +      [anEvent subtype] == kEventSubtypeTrace) {
   1.154 +    mozilla::SignalTracerThread();
   1.155 +    return;
   1.156 +  }
   1.157 +  [super sendEvent:anEvent];
   1.158 +}
   1.159 +
   1.160 +- (NSEvent*)nextEventMatchingMask:(NSUInteger)mask
   1.161 +                        untilDate:(NSDate*)expiration
   1.162 +                           inMode:(NSString*)mode
   1.163 +                          dequeue:(BOOL)flag
   1.164 +{
   1.165 +  if (expiration) {
   1.166 +    mozilla::HangMonitor::Suspend();
   1.167 +  }
   1.168 +  return [super nextEventMatchingMask:mask
   1.169 +          untilDate:expiration inMode:mode dequeue:flag];
   1.170 +}
   1.171 +
   1.172 +@end
   1.173 +
   1.174 +
   1.175 +// AppShellDelegate
   1.176 +//
   1.177 +// Cocoa bridge class.  An object of this class is registered to receive
   1.178 +// notifications.
   1.179 +//
   1.180 +@interface AppShellDelegate : NSObject
   1.181 +{
   1.182 +  @private
   1.183 +    nsAppShell* mAppShell;
   1.184 +}
   1.185 +
   1.186 +- (id)initWithAppShell:(nsAppShell*)aAppShell;
   1.187 +- (void)applicationWillTerminate:(NSNotification*)aNotification;
   1.188 +- (void)beginMenuTracking:(NSNotification*)aNotification;
   1.189 +@end
   1.190 +
   1.191 +// nsAppShell implementation
   1.192 +
   1.193 +NS_IMETHODIMP
   1.194 +nsAppShell::ResumeNative(void)
   1.195 +{
   1.196 +  nsresult retval = nsBaseAppShell::ResumeNative();
   1.197 +  if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) &&
   1.198 +      mSkippedNativeCallback)
   1.199 +  {
   1.200 +    mSkippedNativeCallback = false;
   1.201 +    ScheduleNativeEventCallback();
   1.202 +  }
   1.203 +  return retval;
   1.204 +}
   1.205 +
   1.206 +nsAppShell::nsAppShell()
   1.207 +: mAutoreleasePools(nullptr)
   1.208 +, mDelegate(nullptr)
   1.209 +, mCFRunLoop(NULL)
   1.210 +, mCFRunLoopSource(NULL)
   1.211 +, mRunningEventLoop(false)
   1.212 +, mStarted(false)
   1.213 +, mTerminated(false)
   1.214 +, mSkippedNativeCallback(false)
   1.215 +, mHadMoreEventsCount(0)
   1.216 +, mRecursionDepth(0)
   1.217 +, mNativeEventCallbackDepth(0)
   1.218 +, mNativeEventScheduledDepth(0)
   1.219 +{
   1.220 +  // A Cocoa event loop is running here if (and only if) we've been embedded
   1.221 +  // by a Cocoa app (like Camino).
   1.222 +  mRunningCocoaEmbedded = [NSApp isRunning] ? true : false;
   1.223 +}
   1.224 +
   1.225 +nsAppShell::~nsAppShell()
   1.226 +{
   1.227 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   1.228 +
   1.229 +  if (mCFRunLoop) {
   1.230 +    if (mCFRunLoopSource) {
   1.231 +      ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
   1.232 +                              kCFRunLoopCommonModes);
   1.233 +      ::CFRelease(mCFRunLoopSource);
   1.234 +    }
   1.235 +    ::CFRelease(mCFRunLoop);
   1.236 +  }
   1.237 +
   1.238 +  if (mAutoreleasePools) {
   1.239 +    NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
   1.240 +                 "nsAppShell destroyed without popping all autorelease pools");
   1.241 +    ::CFRelease(mAutoreleasePools);
   1.242 +  }
   1.243 +
   1.244 +  [mDelegate release];
   1.245 +
   1.246 +  NS_OBJC_END_TRY_ABORT_BLOCK
   1.247 +}
   1.248 +
   1.249 +// An undocumented CoreGraphics framework method, present in the same form
   1.250 +// since at least OS X 10.5.
   1.251 +extern "C" CGError CGSSetDebugOptions(int options);
   1.252 +
   1.253 +// Init
   1.254 +//
   1.255 +// Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
   1.256 +// interrupt the main native run loop.
   1.257 +//
   1.258 +// public
   1.259 +nsresult
   1.260 +nsAppShell::Init()
   1.261 +{
   1.262 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   1.263 +
   1.264 +  // No event loop is running yet (unless Camino is running, or another
   1.265 +  // embedding app that uses NSApplicationMain()).
   1.266 +  NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
   1.267 +
   1.268 +  // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
   1.269 +  // by |this|.  CFArray is used instead of NSArray because NSArray wants to
   1.270 +  // retain each object you add to it, and you can't retain an
   1.271 +  // NSAutoreleasePool.
   1.272 +  mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
   1.273 +  NS_ENSURE_STATE(mAutoreleasePools);
   1.274 +
   1.275 +  // Get the path of the nib file, which lives in the GRE location
   1.276 +  nsCOMPtr<nsIFile> nibFile;
   1.277 +  nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(nibFile));
   1.278 +  NS_ENSURE_SUCCESS(rv, rv);
   1.279 +
   1.280 +  nibFile->AppendNative(NS_LITERAL_CSTRING("res"));
   1.281 +  nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib"));
   1.282 +
   1.283 +  nsAutoCString nibPath;
   1.284 +  rv = nibFile->GetNativePath(nibPath);
   1.285 +  NS_ENSURE_SUCCESS(rv, rv);
   1.286 +
   1.287 +  // This call initializes NSApplication unless:
   1.288 +  // 1) we're using xre -- NSApp's already been initialized by
   1.289 +  //    MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
   1.290 +  // 2) Camino is running (or another embedding app that uses
   1.291 +  //    NSApplicationMain()) -- NSApp's already been initialized and
   1.292 +  //    its main run loop is already running.
   1.293 +  [NSBundle loadNibFile:
   1.294 +                     [NSString stringWithUTF8String:(const char*)nibPath.get()]
   1.295 +      externalNameTable:
   1.296 +           [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication]
   1.297 +                                       forKey:@"NSOwner"]
   1.298 +               withZone:NSDefaultMallocZone()];
   1.299 +
   1.300 +  mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
   1.301 +  NS_ENSURE_STATE(mDelegate);
   1.302 +
   1.303 +  // Add a CFRunLoopSource to the main native run loop.  The source is
   1.304 +  // responsible for interrupting the run loop when Gecko events are ready.
   1.305 +
   1.306 +  mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
   1.307 +  NS_ENSURE_STATE(mCFRunLoop);
   1.308 +  ::CFRetain(mCFRunLoop);
   1.309 +
   1.310 +  CFRunLoopSourceContext context;
   1.311 +  bzero(&context, sizeof(context));
   1.312 +  // context.version = 0;
   1.313 +  context.info = this;
   1.314 +  context.perform = ProcessGeckoEvents;
   1.315 +  
   1.316 +  mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
   1.317 +  NS_ENSURE_STATE(mCFRunLoopSource);
   1.318 +
   1.319 +  ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
   1.320 +
   1.321 +  rv = nsBaseAppShell::Init();
   1.322 +
   1.323 +#ifndef __LP64__
   1.324 +  TextInputHandler::InstallPluginKeyEventsHandler();
   1.325 +#endif
   1.326 +
   1.327 +  gCocoaAppModalWindowList = new nsCocoaAppModalWindowList;
   1.328 +  if (!gAppShellMethodsSwizzled) {
   1.329 +    nsToolkit::SwizzleMethods([NSApplication class], @selector(beginModalSessionForWindow:),
   1.330 +                              @selector(nsAppShell_NSApplication_beginModalSessionForWindow:));
   1.331 +    nsToolkit::SwizzleMethods([NSApplication class], @selector(endModalSession:),
   1.332 +                              @selector(nsAppShell_NSApplication_endModalSession:));
   1.333 +    // We should only replace the original terminate: method if we're not
   1.334 +    // running in a Cocoa embedder (like Camino).  See bug 604901.
   1.335 +    if (!mRunningCocoaEmbedded) {
   1.336 +      nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
   1.337 +                                @selector(nsAppShell_NSApplication_terminate:));
   1.338 +    }
   1.339 +    gAppShellMethodsSwizzled = true;
   1.340 +  }
   1.341 +
   1.342 +  if (nsCocoaFeatures::OnYosemiteOrLater()) {
   1.343 +    // Explicitly turn off CGEvent logging.  This works around bug 1092855.
   1.344 +    // If there are already CGEvents in the log, turning off logging also
   1.345 +    // causes those events to be written to disk.  But at this point no
   1.346 +    // CGEvents have yet been processed.  CGEvents are events (usually
   1.347 +    // input events) pulled from the WindowServer.  An option of 0x80000008
   1.348 +    // turns on CGEvent logging.
   1.349 +    CGSSetDebugOptions(0x80000007);
   1.350 +  }
   1.351 +
   1.352 +  [localPool release];
   1.353 +
   1.354 +  return rv;
   1.355 +
   1.356 +  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   1.357 +}
   1.358 +
   1.359 +// ProcessGeckoEvents
   1.360 +//
   1.361 +// The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
   1.362 +// signalled from ScheduleNativeEventCallback.
   1.363 +//
   1.364 +// Arrange for Gecko events to be processed on demand (in response to a call
   1.365 +// to ScheduleNativeEventCallback(), if processing of Gecko events via "native
   1.366 +// methods" hasn't been suspended).  This happens in NativeEventCallback().
   1.367 +//
   1.368 +// protected static
   1.369 +void
   1.370 +nsAppShell::ProcessGeckoEvents(void* aInfo)
   1.371 +{
   1.372 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   1.373 +  PROFILER_LABEL("Events", "ProcessGeckoEvents");
   1.374 +  nsAppShell* self = static_cast<nsAppShell*> (aInfo);
   1.375 +
   1.376 +  if (self->mRunningEventLoop) {
   1.377 +    self->mRunningEventLoop = false;
   1.378 +
   1.379 +    // The run loop may be sleeping -- [NSRunLoop runMode:...]
   1.380 +    // won't return until it's given a reason to wake up.  Awaken it by
   1.381 +    // posting a bogus event.  There's no need to make the event
   1.382 +    // presentable.
   1.383 +    //
   1.384 +    // But _don't_ set windowNumber to '-1' -- that can lead to nasty
   1.385 +    // wierdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
   1.386 +    // these fake events, because the -1 has gotten changed into the number
   1.387 +    // of an actual NSWindow object, and that NSWindow object has just been
   1.388 +    // destroyed).  Setting windowNumber to '0' seems to work fine -- this
   1.389 +    // seems to prevent the OS from ever trying to associate our bogus event
   1.390 +    // with a particular NSWindow object.
   1.391 +    [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
   1.392 +                                        location:NSMakePoint(0,0)
   1.393 +                                   modifierFlags:0
   1.394 +                                       timestamp:0
   1.395 +                                    windowNumber:0
   1.396 +                                         context:NULL
   1.397 +                                         subtype:kEventSubtypeNone
   1.398 +                                           data1:0
   1.399 +                                           data2:0]
   1.400 +             atStart:NO];
   1.401 +  }
   1.402 +
   1.403 +  if (self->mSuspendNativeCount <= 0) {
   1.404 +    ++self->mNativeEventCallbackDepth;
   1.405 +    self->NativeEventCallback();
   1.406 +    --self->mNativeEventCallbackDepth;
   1.407 +  } else {
   1.408 +    self->mSkippedNativeCallback = true;
   1.409 +  }
   1.410 +
   1.411 +  // Still needed to fix bug 343033 ("5-10 second delay or hang or crash
   1.412 +  // when quitting Cocoa Firefox").
   1.413 +  [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
   1.414 +                                      location:NSMakePoint(0,0)
   1.415 +                                 modifierFlags:0
   1.416 +                                     timestamp:0
   1.417 +                                  windowNumber:0
   1.418 +                                       context:NULL
   1.419 +                                       subtype:kEventSubtypeNone
   1.420 +                                         data1:0
   1.421 +                                         data2:0]
   1.422 +           atStart:NO];
   1.423 +
   1.424 +  // Normally every call to ScheduleNativeEventCallback() results in
   1.425 +  // exactly one call to ProcessGeckoEvents().  So each Release() here
   1.426 +  // normally balances exactly one AddRef() in ScheduleNativeEventCallback().
   1.427 +  // But if Exit() is called just after ScheduleNativeEventCallback(), the
   1.428 +  // corresponding call to ProcessGeckoEvents() will never happen.  We check
   1.429 +  // for this possibility in two different places -- here and in Exit()
   1.430 +  // itself.  If we find here that Exit() has been called (that mTerminated
   1.431 +  // is true), it's because we've been called recursively, that Exit() was
   1.432 +  // called from self->NativeEventCallback() above, and that we're unwinding
   1.433 +  // the recursion.  In this case we'll never be called again, and we balance
   1.434 +  // here any extra calls to ScheduleNativeEventCallback().
   1.435 +  //
   1.436 +  // When ProcessGeckoEvents() is called recursively, it's because of a
   1.437 +  // call to ScheduleNativeEventCallback() from NativeEventCallback().  We
   1.438 +  // balance the "extra" AddRefs here (rather than always in Exit()) in order
   1.439 +  // to ensure that 'self' stays alive until the end of this method.  We also
   1.440 +  // make sure not to finish the balancing until all the recursion has been
   1.441 +  // unwound.
   1.442 +  if (self->mTerminated) {
   1.443 +    int32_t releaseCount = 0;
   1.444 +    if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) {
   1.445 +      releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth,
   1.446 +                                   self->mNativeEventCallbackDepth);
   1.447 +    }
   1.448 +    while (releaseCount-- > self->mNativeEventCallbackDepth)
   1.449 +      self->Release();
   1.450 +  } else {
   1.451 +    // As best we can tell, every call to ProcessGeckoEvents() is triggered
   1.452 +    // by a call to ScheduleNativeEventCallback().  But we've seen a few
   1.453 +    // (non-reproducible) cases of double-frees that *might* have been caused
   1.454 +    // by spontaneous calls (from the OS) to ProcessGeckoEvents().  So we
   1.455 +    // deal with that possibility here.
   1.456 +    if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) {
   1.457 +      PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0);
   1.458 +      NS_WARNING("Spontaneous call to ProcessGeckoEvents()!");
   1.459 +    } else {
   1.460 +      self->Release();
   1.461 +    }
   1.462 +  }
   1.463 +
   1.464 +  NS_OBJC_END_TRY_ABORT_BLOCK;
   1.465 +}
   1.466 +
   1.467 +// WillTerminate
   1.468 +//
   1.469 +// Called by the AppShellDelegate when an NSApplicationWillTerminate
   1.470 +// notification is posted.  After this method is called, native events should
   1.471 +// no longer be processed.  The NSApplicationWillTerminate notification is
   1.472 +// only posted when [NSApp terminate:] is called, which doesn't happen on a
   1.473 +// "normal" application quit.
   1.474 +//
   1.475 +// public
   1.476 +void
   1.477 +nsAppShell::WillTerminate()
   1.478 +{
   1.479 +  if (mTerminated)
   1.480 +    return;
   1.481 +
   1.482 +  // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
   1.483 +  // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
   1.484 +  NS_ProcessPendingEvents(NS_GetCurrentThread());
   1.485 +
   1.486 +  mTerminated = true;
   1.487 +}
   1.488 +
   1.489 +// ScheduleNativeEventCallback
   1.490 +//
   1.491 +// Called (possibly on a non-main thread) when Gecko has an event that
   1.492 +// needs to be processed.  The Gecko event needs to be processed on the
   1.493 +// main thread, so the native run loop must be interrupted.
   1.494 +//
   1.495 +// In nsBaseAppShell.cpp, the mNativeEventPending variable is used to
   1.496 +// ensure that ScheduleNativeEventCallback() is called no more than once
   1.497 +// per call to NativeEventCallback().  ProcessGeckoEvents() can skip its
   1.498 +// call to NativeEventCallback() if processing of Gecko events by native
   1.499 +// means is suspended (using nsIAppShell::SuspendNative()), which will
   1.500 +// suspend calls from nsBaseAppShell::OnDispatchedEvent() to
   1.501 +// ScheduleNativeEventCallback().  But when Gecko event processing by
   1.502 +// native means is resumed (in ResumeNative()), an extra call is made to
   1.503 +// ScheduleNativeEventCallback() (from ResumeNative()).  This triggers
   1.504 +// another call to ProcessGeckoEvents(), which calls NativeEventCallback(),
   1.505 +// and nsBaseAppShell::OnDispatchedEvent() resumes calling
   1.506 +// ScheduleNativeEventCallback().
   1.507 +//
   1.508 +// protected virtual
   1.509 +void
   1.510 +nsAppShell::ScheduleNativeEventCallback()
   1.511 +{
   1.512 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   1.513 +
   1.514 +  if (mTerminated)
   1.515 +    return;
   1.516 +
   1.517 +  // Each AddRef() here is normally balanced by exactly one Release() in
   1.518 +  // ProcessGeckoEvents().  But there are exceptions, for which see
   1.519 +  // ProcessGeckoEvents() and Exit().
   1.520 +  NS_ADDREF_THIS();
   1.521 +  PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth);
   1.522 +
   1.523 +  // This will invoke ProcessGeckoEvents on the main thread.
   1.524 +  ::CFRunLoopSourceSignal(mCFRunLoopSource);
   1.525 +  ::CFRunLoopWakeUp(mCFRunLoop);
   1.526 +
   1.527 +  NS_OBJC_END_TRY_ABORT_BLOCK;
   1.528 +}
   1.529 +
   1.530 +// ProcessNextNativeEvent
   1.531 +//
   1.532 +// If aMayWait is false, process a single native event.  If it is true, run
   1.533 +// the native run loop until stopped by ProcessGeckoEvents.
   1.534 +//
   1.535 +// Returns true if more events are waiting in the native event queue.
   1.536 +//
   1.537 +// But (now that we're using [NSRunLoop runMode:beforeDate:]) it's too
   1.538 +// expensive to call ProcessNextNativeEvent() many times in a row (in a
   1.539 +// tight loop), so we never return true more than kHadMoreEventsCountMax
   1.540 +// times in a row.  This doesn't seem to cause native event starvation.
   1.541 +//
   1.542 +// protected virtual
   1.543 +bool
   1.544 +nsAppShell::ProcessNextNativeEvent(bool aMayWait)
   1.545 +{
   1.546 +  bool moreEvents = false;
   1.547 +
   1.548 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   1.549 +
   1.550 +  bool eventProcessed = false;
   1.551 +  NSString* currentMode = nil;
   1.552 +
   1.553 +  if (mTerminated)
   1.554 +    return false;
   1.555 +
   1.556 +  // We don't want any native events to be processed here (via Gecko) while
   1.557 +  // Cocoa is displaying an app-modal dialog (as opposed to a window-modal
   1.558 +  // "sheet" or a Gecko-modal dialog).  Otherwise Cocoa event-processing loops
   1.559 +  // may be interrupted, and inappropriate events may get through to the
   1.560 +  // browser window(s) underneath.  This resolves bmo bugs 419668 and 420967.
   1.561 +  //
   1.562 +  // But we need more complex handling (we need to make an exception) if a
   1.563 +  // Gecko modal dialog is running above the Cocoa app-modal dialog -- for
   1.564 +  // which see below.
   1.565 +  if ([NSApp _isRunningAppModal] &&
   1.566 +      (!gCocoaAppModalWindowList || !gCocoaAppModalWindowList->GeckoModalAboveCocoaModal()))
   1.567 +    return false;
   1.568 +
   1.569 +  bool wasRunningEventLoop = mRunningEventLoop;
   1.570 +  mRunningEventLoop = aMayWait;
   1.571 +  NSDate* waitUntil = nil;
   1.572 +  if (aMayWait)
   1.573 +    waitUntil = [NSDate distantFuture];
   1.574 +
   1.575 +  NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
   1.576 +
   1.577 +  do {
   1.578 +    // No autorelease pool is provided here, because OnProcessNextEvent
   1.579 +    // and AfterProcessNextEvent are responsible for maintaining it.
   1.580 +    NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
   1.581 +                 "No autorelease pool for native event");
   1.582 +
   1.583 +    // If an event is waiting to be processed, run the main event loop
   1.584 +    // just long enough to process it.  For some reason, using [NSApp
   1.585 +    // nextEventMatchingMask:...] to dequeue the event and [NSApp sendEvent:]
   1.586 +    // to "send" it causes trouble, so we no longer do that.  (The trouble
   1.587 +    // was very strange, and only happened while processing Gecko events on
   1.588 +    // demand (via ProcessGeckoEvents()), as opposed to processing Gecko
   1.589 +    // events in a tight loop (via nsBaseAppShell::Run()):  Particularly in
   1.590 +    // Camino, mouse-down events sometimes got dropped (or mis-handled), so
   1.591 +    // that (for example) you sometimes needed to click more than once on a
   1.592 +    // button to make it work (the zoom button was particularly susceptible).
   1.593 +    // You also sometimes had to ctrl-click or right-click multiple times to
   1.594 +    // bring up a context menu.)
   1.595 +
   1.596 +    // Now that we're using [NSRunLoop runMode:beforeDate:], it's too
   1.597 +    // expensive to call ProcessNextNativeEvent() many times in a row, so we
   1.598 +    // never return true more than kHadMoreEventsCountMax in a row.  I'm not
   1.599 +    // entirely sure why [NSRunLoop runMode:beforeDate:] is too expensive,
   1.600 +    // since it and its cousin [NSRunLoop acceptInputForMode:beforeDate:] are
   1.601 +    // designed to be called in a tight loop.  Possibly the problem is due to
   1.602 +    // combining [NSRunLoop runMode:beforeDate] with [NSApp
   1.603 +    // nextEventMatchingMask:...].
   1.604 +
   1.605 +    // We special-case timer events (events of type NSPeriodic) to avoid
   1.606 +    // starving them.  Apple's documentation is very scanty, and it's now
   1.607 +    // more scanty than it used to be.  But it appears that [NSRunLoop
   1.608 +    // acceptInputForMode:beforeDate:] doesn't process timer events at all,
   1.609 +    // that it is called from [NSRunLoop runMode:beforeDate:], and that
   1.610 +    // [NSRunLoop runMode:beforeDate:], though it does process timer events,
   1.611 +    // doesn't return after doing so.  To get around this, when aWait is
   1.612 +    // false we check for timer events and process them using [NSApp
   1.613 +    // sendEvent:].  When aWait is true [NSRunLoop runMode:beforeDate:]
   1.614 +    // will only return on a "real" event.  But there's code in
   1.615 +    // ProcessGeckoEvents() that should (when need be) wake us up by sending
   1.616 +    // a "fake" "real" event.  (See Apple's current doc on [NSRunLoop
   1.617 +    // runMode:beforeDate:] and a quote from what appears to be an older
   1.618 +    // version of this doc at
   1.619 +    // http://lists.apple.com/archives/cocoa-dev/2001/May/msg00559.html.)
   1.620 +
   1.621 +    // If the current mode is something else than NSDefaultRunLoopMode, look
   1.622 +    // for events in that mode.
   1.623 +    currentMode = [currentRunLoop currentMode];
   1.624 +    if (!currentMode)
   1.625 +      currentMode = NSDefaultRunLoopMode;
   1.626 +
   1.627 +    NSEvent* nextEvent = nil;
   1.628 +
   1.629 +    if (aMayWait) {
   1.630 +      mozilla::HangMonitor::Suspend();
   1.631 +    }
   1.632 +
   1.633 +    // If we're running modal (or not in a Gecko "main" event loop) we still
   1.634 +    // need to use nextEventMatchingMask and sendEvent -- otherwise (in
   1.635 +    // Minefield) the modal window (or non-main event loop) won't receive key
   1.636 +    // events or most mouse events.
   1.637 +    //
   1.638 +    // Add aMayWait to minimize the number of calls to -[NSApp sendEvent:]
   1.639 +    // made from nsAppShell::ProcessNextNativeEvent() (and indirectly from
   1.640 +    // nsBaseAppShell::OnProcessNextEvent()), to work around bug 959281.
   1.641 +    if ([NSApp _isRunningModal] || (aMayWait && !InGeckoMainEventLoop())) {
   1.642 +      if ((nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
   1.643 +                                          untilDate:waitUntil
   1.644 +                                             inMode:currentMode
   1.645 +                                            dequeue:YES])) {
   1.646 +        // If we're in a Cocoa app-modal session that's been interrupted by a
   1.647 +        // Gecko-modal dialog, send the event to the Cocoa app-modal dialog's
   1.648 +        // session.  This ensures that the app-modal session won't be starved
   1.649 +        // of events, and fixes bugs 463473 and 442442.  (The case of an
   1.650 +        // ordinary Cocoa app-modal dialog has been dealt with above.)
   1.651 +        //
   1.652 +        // Otherwise (if we're in an ordinary Gecko-modal dialog, or if we're
   1.653 +        // otherwise not in a Gecko main event loop), process the event as
   1.654 +        // expected.
   1.655 +        NSModalSession currentAppModalSession = nil;
   1.656 +        if (gCocoaAppModalWindowList)
   1.657 +          currentAppModalSession = gCocoaAppModalWindowList->CurrentSession();
   1.658 +
   1.659 +        mozilla::HangMonitor::NotifyActivity();
   1.660 +
   1.661 +        if (currentAppModalSession) {
   1.662 +          [NSApp _modalSession:currentAppModalSession sendEvent:nextEvent];
   1.663 +        } else {
   1.664 +          [NSApp sendEvent:nextEvent];
   1.665 +        }
   1.666 +        eventProcessed = true;
   1.667 +      }
   1.668 +    } else {
   1.669 +      if (aMayWait ||
   1.670 +          (nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
   1.671 +                                          untilDate:nil
   1.672 +                                             inMode:currentMode
   1.673 +                                            dequeue:NO])) {
   1.674 +        if (nextEvent && ([nextEvent type] == NSPeriodic)) {
   1.675 +          nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
   1.676 +                                         untilDate:waitUntil
   1.677 +                                            inMode:currentMode
   1.678 +                                           dequeue:YES];
   1.679 +          [NSApp sendEvent:nextEvent];
   1.680 +        } else {
   1.681 +          [currentRunLoop runMode:currentMode beforeDate:waitUntil];
   1.682 +        }
   1.683 +        eventProcessed = true;
   1.684 +      }
   1.685 +    }
   1.686 +  } while (mRunningEventLoop);
   1.687 +
   1.688 +  if (eventProcessed && (mHadMoreEventsCount < kHadMoreEventsCountMax)) {
   1.689 +    moreEvents = ([NSApp nextEventMatchingMask:NSAnyEventMask
   1.690 +                                     untilDate:nil
   1.691 +                                        inMode:currentMode
   1.692 +                                       dequeue:NO] != nil);
   1.693 +  }
   1.694 +
   1.695 +  if (moreEvents) {
   1.696 +    // Once this reaches kHadMoreEventsCountMax, it will be reset to 0 the
   1.697 +    // next time through (whether or not we process any events then).
   1.698 +    ++mHadMoreEventsCount;
   1.699 +  } else {
   1.700 +    mHadMoreEventsCount = 0;
   1.701 +  }
   1.702 +
   1.703 +  mRunningEventLoop = wasRunningEventLoop;
   1.704 +
   1.705 +  NS_OBJC_END_TRY_ABORT_BLOCK;
   1.706 +
   1.707 +  if (!moreEvents) {
   1.708 +    nsChildView::UpdateCurrentInputEventCount();
   1.709 +  }
   1.710 +
   1.711 +  return moreEvents;
   1.712 +}
   1.713 +
   1.714 +// Returns true if Gecko events are currently being processed in its "main"
   1.715 +// event loop (or one of its "main" event loops).  Returns false if Gecko
   1.716 +// events are being processed in a "nested" event loop, or if we're not
   1.717 +// running in any sort of Gecko event loop.  How we process native events in
   1.718 +// ProcessNextNativeEvent() turns on our decision (and if we make the wrong
   1.719 +// choice, the result may be a hang).
   1.720 +//
   1.721 +// We define the "main" event loop(s) as the place (or places) where Gecko
   1.722 +// event processing "normally" takes place, and all other Gecko event loops
   1.723 +// as "nested".  The "nested" event loops are normally processed while a call
   1.724 +// from a "main" event loop is on the stack ... but not always.  For example,
   1.725 +// the Venkman JavaScript debugger runs a "nested" event loop (in jsdService::
   1.726 +// EnterNestedEventLoop()) whenever it breaks into the current script.  But
   1.727 +// if this happens as the result of the user pressing a key combination, there
   1.728 +// won't be any other Gecko event-processing call on the stack (e.g.
   1.729 +// NS_ProcessNextEvent() or NS_ProcessPendingEvents()).  (In the current
   1.730 +// nsAppShell implementation, what counts as the "main" event loop is what
   1.731 +// nsBaseAppShell::NativeEventCallback() does to process Gecko events.  We
   1.732 +// don't currently use nsBaseAppShell::Run().)
   1.733 +bool
   1.734 +nsAppShell::InGeckoMainEventLoop()
   1.735 +{
   1.736 +  if ((gXULModalLevel > 0) || (mRecursionDepth > 0))
   1.737 +    return false;
   1.738 +  if (mNativeEventCallbackDepth <= 0)
   1.739 +    return false;
   1.740 +  return true;
   1.741 +}
   1.742 +
   1.743 +// Run
   1.744 +//
   1.745 +// Overrides the base class's Run() method to call [NSApp run] (which spins
   1.746 +// the native run loop until the application quits).  Since (unlike the base
   1.747 +// class's Run() method) we don't process any Gecko events here, they need
   1.748 +// to be processed elsewhere (in NativeEventCallback(), called from
   1.749 +// ProcessGeckoEvents()).
   1.750 +//
   1.751 +// Camino calls [NSApp run] on its own (via NSApplicationMain()), and so
   1.752 +// doesn't call nsAppShell::Run().
   1.753 +//
   1.754 +// public
   1.755 +NS_IMETHODIMP
   1.756 +nsAppShell::Run(void)
   1.757 +{
   1.758 +  NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
   1.759 +  if (mStarted || mTerminated)
   1.760 +    return NS_OK;
   1.761 +
   1.762 +  mStarted = true;
   1.763 +  NS_OBJC_TRY_ABORT([NSApp run]);
   1.764 +
   1.765 +  return NS_OK;
   1.766 +}
   1.767 +
   1.768 +NS_IMETHODIMP
   1.769 +nsAppShell::Exit(void)
   1.770 +{
   1.771 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   1.772 +
   1.773 +  // This method is currently called more than once -- from (according to
   1.774 +  // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
   1.775 +  // XPCOM shutdown notification that nsBaseAppShell has registered to
   1.776 +  // receive.  So we need to ensure that multiple calls won't break anything.
   1.777 +  // But we should also complain about it (since it isn't quite kosher).
   1.778 +  if (mTerminated) {
   1.779 +    NS_WARNING("nsAppShell::Exit() called redundantly");
   1.780 +    return NS_OK;
   1.781 +  }
   1.782 +
   1.783 +  mTerminated = true;
   1.784 +
   1.785 +  delete gCocoaAppModalWindowList;
   1.786 +  gCocoaAppModalWindowList = NULL;
   1.787 +
   1.788 +#ifndef __LP64__
   1.789 +  TextInputHandler::RemovePluginKeyEventsHandler();
   1.790 +#endif
   1.791 +
   1.792 +  // Quoting from Apple's doc on the [NSApplication stop:] method (from their
   1.793 +  // doc on the NSApplication class):  "If this method is invoked during a
   1.794 +  // modal event loop, it will break that loop but not the main event loop."
   1.795 +  // nsAppShell::Exit() shouldn't be called from a modal event loop.  So if
   1.796 +  // it is we complain about it (to users of debug builds) and call [NSApp
   1.797 +  // stop:] one extra time.  (I'm not sure if modal event loops can be nested
   1.798 +  // -- Apple's docs don't say one way or the other.  But the return value
   1.799 +  // of [NSApp _isRunningModal] doesn't change immediately after a call to
   1.800 +  // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
   1.801 +  // will do the job.)
   1.802 +  BOOL cocoaModal = [NSApp _isRunningModal];
   1.803 +  NS_ASSERTION(!cocoaModal,
   1.804 +               "Don't call nsAppShell::Exit() from a modal event loop!");
   1.805 +  if (cocoaModal)
   1.806 +    [NSApp stop:nullptr];
   1.807 +  [NSApp stop:nullptr];
   1.808 +
   1.809 +  // A call to Exit() just after a call to ScheduleNativeEventCallback()
   1.810 +  // prevents the (normally) matching call to ProcessGeckoEvents() from
   1.811 +  // happening.  If we've been called from ProcessGeckoEvents() (as usually
   1.812 +  // happens), we take care of it there.  But if we have an unbalanced call
   1.813 +  // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
   1.814 +  // stack, we need to take care of the problem here.
   1.815 +  if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
   1.816 +    int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
   1.817 +    while (releaseCount-- > 0)
   1.818 +      NS_RELEASE_THIS();
   1.819 +  }
   1.820 +
   1.821 +  return nsBaseAppShell::Exit();
   1.822 +
   1.823 +  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   1.824 +}
   1.825 +
   1.826 +// OnProcessNextEvent
   1.827 +//
   1.828 +// This nsIThreadObserver method is called prior to processing an event.
   1.829 +// Set up an autorelease pool that will service any autoreleased Cocoa
   1.830 +// objects during this event.  This includes native events processed by
   1.831 +// ProcessNextNativeEvent.  The autorelease pool will be popped by
   1.832 +// AfterProcessNextEvent, it is important for these two methods to be
   1.833 +// tightly coupled.
   1.834 +//
   1.835 +// public
   1.836 +NS_IMETHODIMP
   1.837 +nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
   1.838 +                               uint32_t aRecursionDepth)
   1.839 +{
   1.840 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   1.841 +
   1.842 +  mRecursionDepth = aRecursionDepth;
   1.843 +
   1.844 +  NS_ASSERTION(mAutoreleasePools,
   1.845 +               "No stack on which to store autorelease pool");
   1.846 +
   1.847 +  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
   1.848 +  ::CFArrayAppendValue(mAutoreleasePools, pool);
   1.849 +
   1.850 +  return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait, aRecursionDepth);
   1.851 +
   1.852 +  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   1.853 +}
   1.854 +
   1.855 +// AfterProcessNextEvent
   1.856 +//
   1.857 +// This nsIThreadObserver method is called after event processing is complete.
   1.858 +// The Cocoa implementation cleans up the autorelease pool create by the
   1.859 +// previous OnProcessNextEvent call.
   1.860 +//
   1.861 +// public
   1.862 +NS_IMETHODIMP
   1.863 +nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
   1.864 +                                  uint32_t aRecursionDepth,
   1.865 +                                  bool aEventWasProcessed)
   1.866 +{
   1.867 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   1.868 +
   1.869 +  mRecursionDepth = aRecursionDepth;
   1.870 +
   1.871 +  CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
   1.872 +
   1.873 +  NS_ASSERTION(mAutoreleasePools && count,
   1.874 +               "Processed an event, but there's no autorelease pool?");
   1.875 +
   1.876 +  const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*>
   1.877 +    (::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
   1.878 +  ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
   1.879 +  [pool release];
   1.880 +
   1.881 +  return nsBaseAppShell::AfterProcessNextEvent(aThread, aRecursionDepth,
   1.882 +                                               aEventWasProcessed);
   1.883 +
   1.884 +  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   1.885 +}
   1.886 +
   1.887 +
   1.888 +// AppShellDelegate implementation
   1.889 +
   1.890 +
   1.891 +@implementation AppShellDelegate
   1.892 +// initWithAppShell:
   1.893 +//
   1.894 +// Constructs the AppShellDelegate object
   1.895 +- (id)initWithAppShell:(nsAppShell*)aAppShell
   1.896 +{
   1.897 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   1.898 +
   1.899 +  if ((self = [self init])) {
   1.900 +    mAppShell = aAppShell;
   1.901 +
   1.902 +    [[NSNotificationCenter defaultCenter] addObserver:self
   1.903 +                                             selector:@selector(applicationWillTerminate:)
   1.904 +                                                 name:NSApplicationWillTerminateNotification
   1.905 +                                               object:NSApp];
   1.906 +    [[NSNotificationCenter defaultCenter] addObserver:self
   1.907 +                                             selector:@selector(applicationDidBecomeActive:)
   1.908 +                                                 name:NSApplicationDidBecomeActiveNotification
   1.909 +                                               object:NSApp];
   1.910 +    [[NSDistributedNotificationCenter defaultCenter] addObserver:self
   1.911 +                                                        selector:@selector(beginMenuTracking:)
   1.912 +                                                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
   1.913 +                                                          object:nil];
   1.914 +  }
   1.915 +
   1.916 +  return self;
   1.917 +
   1.918 +  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   1.919 +}
   1.920 +
   1.921 +- (void)dealloc
   1.922 +{
   1.923 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   1.924 +
   1.925 +  [[NSNotificationCenter defaultCenter] removeObserver:self];
   1.926 +  [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
   1.927 +  [super dealloc];
   1.928 +
   1.929 +  NS_OBJC_END_TRY_ABORT_BLOCK;
   1.930 +}
   1.931 +
   1.932 +// applicationWillTerminate:
   1.933 +//
   1.934 +// Notify the nsAppShell that native event processing should be discontinued.
   1.935 +- (void)applicationWillTerminate:(NSNotification*)aNotification
   1.936 +{
   1.937 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   1.938 +
   1.939 +  mAppShell->WillTerminate();
   1.940 +
   1.941 +  NS_OBJC_END_TRY_ABORT_BLOCK;
   1.942 +}
   1.943 +
   1.944 +// applicationDidBecomeActive
   1.945 +//
   1.946 +// Make sure TextInputHandler::sLastModifierState is updated when we become
   1.947 +// active (since we won't have received [ChildView flagsChanged:] messages
   1.948 +// while inactive).
   1.949 +- (void)applicationDidBecomeActive:(NSNotification*)aNotification
   1.950 +{
   1.951 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   1.952 +
   1.953 +  // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
   1.954 +  // to worry about getting an NSInternalInconsistencyException here.
   1.955 +  NSEvent* currentEvent = [NSApp currentEvent];
   1.956 +  if (currentEvent) {
   1.957 +    TextInputHandler::sLastModifierState =
   1.958 +      [currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
   1.959 +  }
   1.960 +
   1.961 +  NS_OBJC_END_TRY_ABORT_BLOCK;
   1.962 +}
   1.963 +
   1.964 +// beginMenuTracking
   1.965 +//
   1.966 +// Roll up our context menu (if any) when some other app (or the OS) opens
   1.967 +// any sort of menu.  But make sure we don't do this for notifications we
   1.968 +// send ourselves (whose 'sender' will be @"org.mozilla.gecko.PopupWindow").
   1.969 +- (void)beginMenuTracking:(NSNotification*)aNotification
   1.970 +{
   1.971 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   1.972 +
   1.973 +  NSString *sender = [aNotification object];
   1.974 +  if (!sender || ![sender isEqualToString:@"org.mozilla.gecko.PopupWindow"]) {
   1.975 +    nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
   1.976 +    nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
   1.977 +    if (rollupWidget)
   1.978 +      rollupListener->Rollup(0, nullptr, nullptr);
   1.979 +  }
   1.980 +
   1.981 +  NS_OBJC_END_TRY_ABORT_BLOCK;
   1.982 +}
   1.983 +
   1.984 +@end
   1.985 +
   1.986 +// We hook beginModalSessionForWindow: and endModalSession: in order to
   1.987 +// maintain a list of Cocoa app-modal windows (and the "sessions" to which
   1.988 +// they correspond).  We need this in order to deal with the consequences
   1.989 +// of a Cocoa app-modal dialog being "interrupted" by a Gecko-modal dialog.
   1.990 +// See nsCocoaAppModalWindowList::CurrentSession() and
   1.991 +// nsAppShell::ProcessNextNativeEvent() above.
   1.992 +//
   1.993 +// We hook terminate: in order to make OS-initiated termination work nicely
   1.994 +// with Gecko's shutdown sequence.  (Two ways to trigger OS-initiated
   1.995 +// termination:  1) Quit from the Dock menu; 2) Log out from (or shut down)
   1.996 +// your computer while the browser is active.)
   1.997 +@interface NSApplication (MethodSwizzling)
   1.998 +- (NSModalSession)nsAppShell_NSApplication_beginModalSessionForWindow:(NSWindow *)aWindow;
   1.999 +- (void)nsAppShell_NSApplication_endModalSession:(NSModalSession)aSession;
  1.1000 +- (void)nsAppShell_NSApplication_terminate:(id)sender;
  1.1001 +@end
  1.1002 +
  1.1003 +@implementation NSApplication (MethodSwizzling)
  1.1004 +
  1.1005 +// Called if and only if a Cocoa app-modal session is beginning.  Always call
  1.1006 +// gCocoaAppModalWindowList->PushCocoa() here (if gCocoaAppModalWindowList is
  1.1007 +// non-nil).
  1.1008 +- (NSModalSession)nsAppShell_NSApplication_beginModalSessionForWindow:(NSWindow *)aWindow
  1.1009 +{
  1.1010 +  NSModalSession session =
  1.1011 +    [self nsAppShell_NSApplication_beginModalSessionForWindow:aWindow];
  1.1012 +  if (gCocoaAppModalWindowList)
  1.1013 +    gCocoaAppModalWindowList->PushCocoa(aWindow, session);
  1.1014 +  return session;
  1.1015 +}
  1.1016 +
  1.1017 +// Called to end any Cocoa modal session (app-modal or otherwise).  Only call
  1.1018 +// gCocoaAppModalWindowList->PopCocoa() when an app-modal session is ending
  1.1019 +// (and when gCocoaAppModalWindowList is non-nil).
  1.1020 +- (void)nsAppShell_NSApplication_endModalSession:(NSModalSession)aSession
  1.1021 +{
  1.1022 +  BOOL wasRunningAppModal = [NSApp _isRunningAppModal];
  1.1023 +  NSWindow *prevAppModalWindow = [NSApp modalWindow];
  1.1024 +  [self nsAppShell_NSApplication_endModalSession:aSession];
  1.1025 +  if (gCocoaAppModalWindowList &&
  1.1026 +      wasRunningAppModal && (prevAppModalWindow != [NSApp modalWindow]))
  1.1027 +    gCocoaAppModalWindowList->PopCocoa(prevAppModalWindow, aSession);
  1.1028 +}
  1.1029 +
  1.1030 +// Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
  1.1031 +// has returned NSTerminateNow.  This method "subclasses" and replaces the
  1.1032 +// OS's original implementation.  The only thing the orginal method does which
  1.1033 +// we need is that it posts NSApplicationWillTerminateNotification.  Everything
  1.1034 +// else is unneeded (because it's handled elsewhere), or actively interferes
  1.1035 +// with Gecko's shutdown sequence.  For example the original terminate: method
  1.1036 +// causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
  1.1037 +// above), which means that nothing runs after the call to nsAppStartup::Run()
  1.1038 +// in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
  1.1039 +// and NS_ShutdownXPCOM() never get called.
  1.1040 +- (void)nsAppShell_NSApplication_terminate:(id)sender
  1.1041 +{
  1.1042 +  [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification
  1.1043 +                                                      object:NSApp];
  1.1044 +}
  1.1045 +
  1.1046 +@end

mercurial