widget/cocoa/nsAppShell.mm

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial