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