1/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */ 2/* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6/* 7 * Runs the main native Cocoa run loop, interrupting it as needed to process 8 * Gecko events. 9 */ 10 11#import <Cocoa/Cocoa.h> 12 13#include "CustomCocoaEvents.h" 14#include "mozilla/WidgetTraceEvent.h" 15#include "nsAppShell.h" 16#include "nsCOMPtr.h" 17#include "nsIFile.h" 18#include "nsDirectoryServiceDefs.h" 19#include "nsString.h" 20#include "nsIRollupListener.h" 21#include "nsIWidget.h" 22#include "nsThreadUtils.h" 23#include "nsIWindowMediator.h" 24#include "nsServiceManagerUtils.h" 25#include "nsIInterfaceRequestor.h" 26#include "nsIWebBrowserChrome.h" 27#include "nsObjCExceptions.h" 28#include "nsCocoaFeatures.h" 29#include "nsCocoaUtils.h" 30#include "nsChildView.h" 31#include "nsToolkit.h" 32#include "TextInputHandler.h" 33#include "mozilla/HangMonitor.h" 34#include "GeckoProfiler.h" 35#include "pratom.h" 36#if !defined(RELEASE_OR_BETA) || defined(DEBUG) 37#include "nsSandboxViolationSink.h" 38#endif 39 40#include <IOKit/pwr_mgt/IOPMLib.h> 41#include "nsIDOMWakeLockListener.h" 42#include "nsIPowerManagerService.h" 43 44using namespace mozilla::widget; 45 46// A wake lock listener that disables screen saver when requested by 47// Gecko. For example when we're playing video in a foreground tab we 48// don't want the screen saver to turn on. 49 50class MacWakeLockListener final : public nsIDOMMozWakeLockListener { 51public: 52 NS_DECL_ISUPPORTS; 53 54private: 55 ~MacWakeLockListener() {} 56 57 IOPMAssertionID mAssertionID = kIOPMNullAssertionID; 58 59 NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) override { 60 if (!aTopic.EqualsASCII("screen")) { 61 return NS_OK; 62 } 63 // Note the wake lock code ensures that we're not sent duplicate 64 // "locked-foreground" notifications when multiple wake locks are held. 65 if (aState.EqualsASCII("locked-foreground")) { 66 // Prevent screen saver. 67 CFStringRef cf_topic = 68 ::CFStringCreateWithCharacters(kCFAllocatorDefault, 69 reinterpret_cast<const UniChar*> 70 (aTopic.Data()), 71 aTopic.Length()); 72 IOReturn success = 73 ::IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, 74 kIOPMAssertionLevelOn, 75 cf_topic, 76 &mAssertionID); 77 CFRelease(cf_topic); 78 if (success != kIOReturnSuccess) { 79 NS_WARNING("failed to disable screensaver"); 80 } 81 } else { 82 // Re-enable screen saver. 83 NS_WARNING("Releasing screensaver"); 84 if (mAssertionID != kIOPMNullAssertionID) { 85 IOReturn result = ::IOPMAssertionRelease(mAssertionID); 86 if (result != kIOReturnSuccess) { 87 NS_WARNING("failed to release screensaver"); 88 } 89 } 90 } 91 return NS_OK; 92 } 93}; // MacWakeLockListener 94 95// defined in nsCocoaWindow.mm 96extern int32_t gXULModalLevel; 97 98static bool gAppShellMethodsSwizzled = false; 99 100@implementation GeckoNSApplication 101 102- (void)sendEvent:(NSEvent *)anEvent 103{ 104 mozilla::HangMonitor::NotifyActivity(); 105 if ([anEvent type] == NSApplicationDefined && 106 [anEvent subtype] == kEventSubtypeTrace) { 107 mozilla::SignalTracerThread(); 108 return; 109 } 110 [super sendEvent:anEvent]; 111} 112 113#if defined(MAC_OS_X_VERSION_10_12) && \ 114 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 && \ 115 __LP64__ 116// 10.12 changed `mask` to NSEventMask (unsigned long long) for x86_64 builds. 117- (NSEvent*)nextEventMatchingMask:(NSEventMask)mask 118#else 119- (NSEvent*)nextEventMatchingMask:(NSUInteger)mask 120#endif 121 untilDate:(NSDate*)expiration 122 inMode:(NSString*)mode 123 dequeue:(BOOL)flag 124{ 125 if (expiration) { 126 mozilla::HangMonitor::Suspend(); 127 } 128 NSEvent* nextEvent = [super nextEventMatchingMask:mask 129 untilDate:expiration inMode:mode dequeue:flag]; 130 if (expiration) { 131 mozilla::HangMonitor::NotifyActivity(); 132 } 133 return nextEvent; 134} 135 136@end 137 138// AppShellDelegate 139// 140// Cocoa bridge class. An object of this class is registered to receive 141// notifications. 142// 143@interface AppShellDelegate : NSObject 144{ 145 @private 146 nsAppShell* mAppShell; 147} 148 149- (id)initWithAppShell:(nsAppShell*)aAppShell; 150- (void)applicationWillTerminate:(NSNotification*)aNotification; 151- (void)beginMenuTracking:(NSNotification*)aNotification; 152@end 153 154// nsAppShell implementation 155 156NS_IMETHODIMP 157nsAppShell::ResumeNative(void) 158{ 159 nsresult retval = nsBaseAppShell::ResumeNative(); 160 if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) && 161 mSkippedNativeCallback) 162 { 163 mSkippedNativeCallback = false; 164 ScheduleNativeEventCallback(); 165 } 166 return retval; 167} 168 169nsAppShell::nsAppShell() 170: mAutoreleasePools(nullptr) 171, mDelegate(nullptr) 172, mCFRunLoop(NULL) 173, mCFRunLoopSource(NULL) 174, mRunningEventLoop(false) 175, mStarted(false) 176, mTerminated(false) 177, mSkippedNativeCallback(false) 178, mNativeEventCallbackDepth(0) 179, mNativeEventScheduledDepth(0) 180{ 181 // A Cocoa event loop is running here if (and only if) we've been embedded 182 // by a Cocoa app. 183 mRunningCocoaEmbedded = [NSApp isRunning] ? true : false; 184} 185 186nsAppShell::~nsAppShell() 187{ 188 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 189 190 if (mCFRunLoop) { 191 if (mCFRunLoopSource) { 192 ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource, 193 kCFRunLoopCommonModes); 194 ::CFRelease(mCFRunLoopSource); 195 } 196 ::CFRelease(mCFRunLoop); 197 } 198 199 if (mAutoreleasePools) { 200 NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0, 201 "nsAppShell destroyed without popping all autorelease pools"); 202 ::CFRelease(mAutoreleasePools); 203 } 204 205 [mDelegate release]; 206 207 NS_OBJC_END_TRY_ABORT_BLOCK 208} 209 210NS_IMPL_ISUPPORTS(MacWakeLockListener, nsIDOMMozWakeLockListener) 211mozilla::StaticRefPtr<MacWakeLockListener> sWakeLockListener; 212 213static void 214AddScreenWakeLockListener() 215{ 216 nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService( 217 POWERMANAGERSERVICE_CONTRACTID); 218 if (sPowerManagerService) { 219 sWakeLockListener = new MacWakeLockListener(); 220 sPowerManagerService->AddWakeLockListener(sWakeLockListener); 221 } else { 222 NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!"); 223 } 224} 225 226static void 227RemoveScreenWakeLockListener() 228{ 229 nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService( 230 POWERMANAGERSERVICE_CONTRACTID); 231 if (sPowerManagerService) { 232 sPowerManagerService->RemoveWakeLockListener(sWakeLockListener); 233 sPowerManagerService = nullptr; 234 sWakeLockListener = nullptr; 235 } 236} 237 238// An undocumented CoreGraphics framework method, present in the same form 239// since at least OS X 10.5. 240extern "C" CGError CGSSetDebugOptions(int options); 241 242// Init 243// 244// Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to 245// interrupt the main native run loop. 246// 247// public 248nsresult 249nsAppShell::Init() 250{ 251 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 252 253 // No event loop is running yet (unless an embedding app that uses 254 // NSApplicationMain() is running). 255 NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init]; 256 257 // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created 258 // by |this|. CFArray is used instead of NSArray because NSArray wants to 259 // retain each object you add to it, and you can't retain an 260 // NSAutoreleasePool. 261 mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr); 262 NS_ENSURE_STATE(mAutoreleasePools); 263 264 // Get the path of the nib file, which lives in the GRE location 265 nsCOMPtr<nsIFile> nibFile; 266 nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(nibFile)); 267 NS_ENSURE_SUCCESS(rv, rv); 268 269 nibFile->AppendNative(NS_LITERAL_CSTRING("res")); 270 nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib")); 271 272 nsAutoCString nibPath; 273 rv = nibFile->GetNativePath(nibPath); 274 NS_ENSURE_SUCCESS(rv, rv); 275 276 // This call initializes NSApplication unless: 277 // 1) we're using xre -- NSApp's already been initialized by 278 // MacApplicationDelegate.mm's EnsureUseCocoaDockAPI(). 279 // 2) an embedding app that uses NSApplicationMain() is running -- NSApp's 280 // already been initialized and its main run loop is already running. 281 [NSBundle loadNibFile: 282 [NSString stringWithUTF8String:(const char*)nibPath.get()] 283 externalNameTable: 284 [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication] 285 forKey:@"NSOwner"] 286 withZone:NSDefaultMallocZone()]; 287 288 mDelegate = [[AppShellDelegate alloc] initWithAppShell:this]; 289 NS_ENSURE_STATE(mDelegate); 290 291 // Add a CFRunLoopSource to the main native run loop. The source is 292 // responsible for interrupting the run loop when Gecko events are ready. 293 294 mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop]; 295 NS_ENSURE_STATE(mCFRunLoop); 296 ::CFRetain(mCFRunLoop); 297 298 CFRunLoopSourceContext context; 299 bzero(&context, sizeof(context)); 300 // context.version = 0; 301 context.info = this; 302 context.perform = ProcessGeckoEvents; 303 304 mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); 305 NS_ENSURE_STATE(mCFRunLoopSource); 306 307 ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes); 308 309 rv = nsBaseAppShell::Init(); 310 311 if (!gAppShellMethodsSwizzled) { 312 // We should only replace the original terminate: method if we're not 313 // running in a Cocoa embedder. See bug 604901. 314 if (!mRunningCocoaEmbedded) { 315 nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:), 316 @selector(nsAppShell_NSApplication_terminate:)); 317 } 318 gAppShellMethodsSwizzled = true; 319 } 320 321 if (nsCocoaFeatures::OnYosemiteOrLater()) { 322 // Explicitly turn off CGEvent logging. This works around bug 1092855. 323 // If there are already CGEvents in the log, turning off logging also 324 // causes those events to be written to disk. But at this point no 325 // CGEvents have yet been processed. CGEvents are events (usually 326 // input events) pulled from the WindowServer. An option of 0x80000008 327 // turns on CGEvent logging. 328 CGSSetDebugOptions(0x80000007); 329 } 330 331#if !defined(RELEASE_OR_BETA) || defined(DEBUG) 332 if (Preferences::GetBool("security.sandbox.mac.track.violations", false)) { 333 nsSandboxViolationSink::Start(); 334 } 335#endif 336 337 [localPool release]; 338 339 return rv; 340 341 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 342} 343 344// ProcessGeckoEvents 345// 346// The "perform" target of mCFRunLoop, called when mCFRunLoopSource is 347// signalled from ScheduleNativeEventCallback. 348// 349// Arrange for Gecko events to be processed on demand (in response to a call 350// to ScheduleNativeEventCallback(), if processing of Gecko events via "native 351// methods" hasn't been suspended). This happens in NativeEventCallback(). 352// 353// protected static 354void 355nsAppShell::ProcessGeckoEvents(void* aInfo) 356{ 357 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 358 PROFILER_LABEL("Events", "ProcessGeckoEvents", 359 js::ProfileEntry::Category::EVENTS); 360 361 nsAppShell* self = static_cast<nsAppShell*> (aInfo); 362 363 if (self->mRunningEventLoop) { 364 self->mRunningEventLoop = false; 365 366 // The run loop may be sleeping -- [NSRunLoop runMode:...] 367 // won't return until it's given a reason to wake up. Awaken it by 368 // posting a bogus event. There's no need to make the event 369 // presentable. 370 // 371 // But _don't_ set windowNumber to '-1' -- that can lead to nasty 372 // weirdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of 373 // these fake events, because the -1 has gotten changed into the number 374 // of an actual NSWindow object, and that NSWindow object has just been 375 // destroyed). Setting windowNumber to '0' seems to work fine -- this 376 // seems to prevent the OS from ever trying to associate our bogus event 377 // with a particular NSWindow object. 378 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined 379 location:NSMakePoint(0,0) 380 modifierFlags:0 381 timestamp:0 382 windowNumber:0 383 context:NULL 384 subtype:kEventSubtypeNone 385 data1:0 386 data2:0] 387 atStart:NO]; 388 } 389 390 if (self->mSuspendNativeCount <= 0) { 391 ++self->mNativeEventCallbackDepth; 392 self->NativeEventCallback(); 393 --self->mNativeEventCallbackDepth; 394 } else { 395 self->mSkippedNativeCallback = true; 396 } 397 398 // Still needed to avoid crashes on quit in most Mochitests. 399 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined 400 location:NSMakePoint(0,0) 401 modifierFlags:0 402 timestamp:0 403 windowNumber:0 404 context:NULL 405 subtype:kEventSubtypeNone 406 data1:0 407 data2:0] 408 atStart:NO]; 409 410 // Normally every call to ScheduleNativeEventCallback() results in 411 // exactly one call to ProcessGeckoEvents(). So each Release() here 412 // normally balances exactly one AddRef() in ScheduleNativeEventCallback(). 413 // But if Exit() is called just after ScheduleNativeEventCallback(), the 414 // corresponding call to ProcessGeckoEvents() will never happen. We check 415 // for this possibility in two different places -- here and in Exit() 416 // itself. If we find here that Exit() has been called (that mTerminated 417 // is true), it's because we've been called recursively, that Exit() was 418 // called from self->NativeEventCallback() above, and that we're unwinding 419 // the recursion. In this case we'll never be called again, and we balance 420 // here any extra calls to ScheduleNativeEventCallback(). 421 // 422 // When ProcessGeckoEvents() is called recursively, it's because of a 423 // call to ScheduleNativeEventCallback() from NativeEventCallback(). We 424 // balance the "extra" AddRefs here (rather than always in Exit()) in order 425 // to ensure that 'self' stays alive until the end of this method. We also 426 // make sure not to finish the balancing until all the recursion has been 427 // unwound. 428 if (self->mTerminated) { 429 int32_t releaseCount = 0; 430 if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) { 431 releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 432 self->mNativeEventCallbackDepth); 433 } 434 while (releaseCount-- > self->mNativeEventCallbackDepth) 435 self->Release(); 436 } else { 437 // As best we can tell, every call to ProcessGeckoEvents() is triggered 438 // by a call to ScheduleNativeEventCallback(). But we've seen a few 439 // (non-reproducible) cases of double-frees that *might* have been caused 440 // by spontaneous calls (from the OS) to ProcessGeckoEvents(). So we 441 // deal with that possibility here. 442 if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) { 443 PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0); 444 NS_WARNING("Spontaneous call to ProcessGeckoEvents()!"); 445 } else { 446 self->Release(); 447 } 448 } 449 450 NS_OBJC_END_TRY_ABORT_BLOCK; 451} 452 453// WillTerminate 454// 455// Called by the AppShellDelegate when an NSApplicationWillTerminate 456// notification is posted. After this method is called, native events should 457// no longer be processed. The NSApplicationWillTerminate notification is 458// only posted when [NSApp terminate:] is called, which doesn't happen on a 459// "normal" application quit. 460// 461// public 462void 463nsAppShell::WillTerminate() 464{ 465 if (mTerminated) 466 return; 467 468 // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called 469 // from [MacApplicationDelegate applicationShouldTerminate:]) gets run. 470 NS_ProcessPendingEvents(NS_GetCurrentThread()); 471 472 mTerminated = true; 473} 474 475// ScheduleNativeEventCallback 476// 477// Called (possibly on a non-main thread) when Gecko has an event that 478// needs to be processed. The Gecko event needs to be processed on the 479// main thread, so the native run loop must be interrupted. 480// 481// In nsBaseAppShell.cpp, the mNativeEventPending variable is used to 482// ensure that ScheduleNativeEventCallback() is called no more than once 483// per call to NativeEventCallback(). ProcessGeckoEvents() can skip its 484// call to NativeEventCallback() if processing of Gecko events by native 485// means is suspended (using nsIAppShell::SuspendNative()), which will 486// suspend calls from nsBaseAppShell::OnDispatchedEvent() to 487// ScheduleNativeEventCallback(). But when Gecko event processing by 488// native means is resumed (in ResumeNative()), an extra call is made to 489// ScheduleNativeEventCallback() (from ResumeNative()). This triggers 490// another call to ProcessGeckoEvents(), which calls NativeEventCallback(), 491// and nsBaseAppShell::OnDispatchedEvent() resumes calling 492// ScheduleNativeEventCallback(). 493// 494// protected virtual 495void 496nsAppShell::ScheduleNativeEventCallback() 497{ 498 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 499 500 if (mTerminated) 501 return; 502 503 // Each AddRef() here is normally balanced by exactly one Release() in 504 // ProcessGeckoEvents(). But there are exceptions, for which see 505 // ProcessGeckoEvents() and Exit(). 506 NS_ADDREF_THIS(); 507 PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth); 508 509 // This will invoke ProcessGeckoEvents on the main thread. 510 ::CFRunLoopSourceSignal(mCFRunLoopSource); 511 ::CFRunLoopWakeUp(mCFRunLoop); 512 513 NS_OBJC_END_TRY_ABORT_BLOCK; 514} 515 516// Undocumented Cocoa Event Manager function, present in the same form since 517// at least OS X 10.6. 518extern "C" EventAttributes GetEventAttributes(EventRef inEvent); 519 520// ProcessNextNativeEvent 521// 522// If aMayWait is false, process a single native event. If it is true, run 523// the native run loop until stopped by ProcessGeckoEvents. 524// 525// Returns true if more events are waiting in the native event queue. 526// 527// protected virtual 528bool 529nsAppShell::ProcessNextNativeEvent(bool aMayWait) 530{ 531 bool moreEvents = false; 532 533 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 534 535 bool eventProcessed = false; 536 NSString* currentMode = nil; 537 538 if (mTerminated) 539 return false; 540 541 bool wasRunningEventLoop = mRunningEventLoop; 542 mRunningEventLoop = aMayWait; 543 NSDate* waitUntil = nil; 544 if (aMayWait) 545 waitUntil = [NSDate distantFuture]; 546 547 NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop]; 548 549 EventQueueRef currentEventQueue = GetCurrentEventQueue(); 550 EventTargetRef eventDispatcherTarget = GetEventDispatcherTarget(); 551 552 if (aMayWait) { 553 mozilla::HangMonitor::Suspend(); 554 } 555 556 // Only call -[NSApp sendEvent:] (and indirectly send user-input events to 557 // Gecko) if aMayWait is true. Tbis ensures most calls to -[NSApp 558 // sendEvent:] happen under nsAppShell::Run(), at the lowest level of 559 // recursion -- thereby making it less likely Gecko will process user-input 560 // events in the wrong order or skip some of them. It also avoids eating 561 // too much CPU in nsBaseAppShell::OnProcessNextEvent() (which calls 562 // us) -- thereby avoiding the starvation of nsIRunnable events in 563 // nsThread::ProcessNextEvent(). For more information see bug 996848. 564 do { 565 // No autorelease pool is provided here, because OnProcessNextEvent 566 // and AfterProcessNextEvent are responsible for maintaining it. 567 NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools), 568 "No autorelease pool for native event"); 569 570 if (aMayWait) { 571 currentMode = [currentRunLoop currentMode]; 572 if (!currentMode) 573 currentMode = NSDefaultRunLoopMode; 574 NSEvent *nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask 575 untilDate:waitUntil 576 inMode:currentMode 577 dequeue:YES]; 578 if (nextEvent) { 579 mozilla::HangMonitor::NotifyActivity(); 580 [NSApp sendEvent:nextEvent]; 581 eventProcessed = true; 582 } 583 } else { 584 // AcquireFirstMatchingEventInQueue() doesn't spin the (native) event 585 // loop, though it does queue up any newly available events from the 586 // window server. 587 EventRef currentEvent = AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL, 588 kEventQueueOptionsNone); 589 if (!currentEvent) { 590 continue; 591 } 592 EventAttributes attrs = GetEventAttributes(currentEvent); 593 UInt32 eventKind = GetEventKind(currentEvent); 594 UInt32 eventClass = GetEventClass(currentEvent); 595 bool osCocoaEvent = 596 ((eventClass == 'appl') || (eventClass == kEventClassAppleEvent) || 597 ((eventClass == 'cgs ') && (eventKind != NSApplicationDefined))); 598 // If attrs is kEventAttributeUserEvent or kEventAttributeMonitored 599 // (i.e. a user input event), we shouldn't process it here while 600 // aMayWait is false. Likewise if currentEvent will eventually be 601 // turned into an OS-defined Cocoa event, or otherwise needs AppKit 602 // processing. Doing otherwise risks doing too much work here, and 603 // preventing the event from being properly processed by the AppKit 604 // framework. 605 if ((attrs != kEventAttributeNone) || osCocoaEvent) { 606 // Since we can't process the next event here (while aMayWait is false), 607 // we want moreEvents to be false on return. 608 eventProcessed = false; 609 // This call to ReleaseEvent() matches a call to RetainEvent() in 610 // AcquireFirstMatchingEventInQueue() above. 611 ReleaseEvent(currentEvent); 612 break; 613 } 614 // This call to RetainEvent() matches a call to ReleaseEvent() in 615 // RemoveEventFromQueue() below. 616 RetainEvent(currentEvent); 617 RemoveEventFromQueue(currentEventQueue, currentEvent); 618 SendEventToEventTarget(currentEvent, eventDispatcherTarget); 619 // This call to ReleaseEvent() matches a call to RetainEvent() in 620 // AcquireFirstMatchingEventInQueue() above. 621 ReleaseEvent(currentEvent); 622 eventProcessed = true; 623 } 624 } while (mRunningEventLoop); 625 626 if (eventProcessed) { 627 moreEvents = 628 (AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL, 629 kEventQueueOptionsNone) != NULL); 630 } 631 632 mRunningEventLoop = wasRunningEventLoop; 633 634 NS_OBJC_END_TRY_ABORT_BLOCK; 635 636 if (!moreEvents) { 637 nsChildView::UpdateCurrentInputEventCount(); 638 } 639 640 return moreEvents; 641} 642 643// Run 644// 645// Overrides the base class's Run() method to call [NSApp run] (which spins 646// the native run loop until the application quits). Since (unlike the base 647// class's Run() method) we don't process any Gecko events here, they need 648// to be processed elsewhere (in NativeEventCallback(), called from 649// ProcessGeckoEvents()). 650// 651// Camino called [NSApp run] on its own (via NSApplicationMain()), and so 652// didn't call nsAppShell::Run(). 653// 654// public 655NS_IMETHODIMP 656nsAppShell::Run(void) 657{ 658 NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times"); 659 if (mStarted || mTerminated) 660 return NS_OK; 661 662 mStarted = true; 663 664 AddScreenWakeLockListener(); 665 666 NS_OBJC_TRY_ABORT([NSApp run]); 667 668 RemoveScreenWakeLockListener(); 669 670 return NS_OK; 671} 672 673NS_IMETHODIMP 674nsAppShell::Exit(void) 675{ 676 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 677 678 // This method is currently called more than once -- from (according to 679 // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an 680 // XPCOM shutdown notification that nsBaseAppShell has registered to 681 // receive. So we need to ensure that multiple calls won't break anything. 682 // But we should also complain about it (since it isn't quite kosher). 683 if (mTerminated) { 684 NS_WARNING("nsAppShell::Exit() called redundantly"); 685 return NS_OK; 686 } 687 688 mTerminated = true; 689 690#if !defined(RELEASE_OR_BETA) || defined(DEBUG) 691 nsSandboxViolationSink::Stop(); 692#endif 693 694 // Quoting from Apple's doc on the [NSApplication stop:] method (from their 695 // doc on the NSApplication class): "If this method is invoked during a 696 // modal event loop, it will break that loop but not the main event loop." 697 // nsAppShell::Exit() shouldn't be called from a modal event loop. So if 698 // it is we complain about it (to users of debug builds) and call [NSApp 699 // stop:] one extra time. (I'm not sure if modal event loops can be nested 700 // -- Apple's docs don't say one way or the other. But the return value 701 // of [NSApp _isRunningModal] doesn't change immediately after a call to 702 // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:] 703 // will do the job.) 704 BOOL cocoaModal = [NSApp _isRunningModal]; 705 NS_ASSERTION(!cocoaModal, 706 "Don't call nsAppShell::Exit() from a modal event loop!"); 707 if (cocoaModal) 708 [NSApp stop:nullptr]; 709 [NSApp stop:nullptr]; 710 711 // A call to Exit() just after a call to ScheduleNativeEventCallback() 712 // prevents the (normally) matching call to ProcessGeckoEvents() from 713 // happening. If we've been called from ProcessGeckoEvents() (as usually 714 // happens), we take care of it there. But if we have an unbalanced call 715 // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the 716 // stack, we need to take care of the problem here. 717 if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) { 718 int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0); 719 while (releaseCount-- > 0) 720 NS_RELEASE_THIS(); 721 } 722 723 return nsBaseAppShell::Exit(); 724 725 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 726} 727 728// OnProcessNextEvent 729// 730// This nsIThreadObserver method is called prior to processing an event. 731// Set up an autorelease pool that will service any autoreleased Cocoa 732// objects during this event. This includes native events processed by 733// ProcessNextNativeEvent. The autorelease pool will be popped by 734// AfterProcessNextEvent, it is important for these two methods to be 735// tightly coupled. 736// 737// public 738NS_IMETHODIMP 739nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait) 740{ 741 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 742 743 NS_ASSERTION(mAutoreleasePools, 744 "No stack on which to store autorelease pool"); 745 746 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 747 ::CFArrayAppendValue(mAutoreleasePools, pool); 748 749 return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait); 750 751 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 752} 753 754// AfterProcessNextEvent 755// 756// This nsIThreadObserver method is called after event processing is complete. 757// The Cocoa implementation cleans up the autorelease pool create by the 758// previous OnProcessNextEvent call. 759// 760// public 761NS_IMETHODIMP 762nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread, 763 bool aEventWasProcessed) 764{ 765 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 766 767 CFIndex count = ::CFArrayGetCount(mAutoreleasePools); 768 769 NS_ASSERTION(mAutoreleasePools && count, 770 "Processed an event, but there's no autorelease pool?"); 771 772 const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*> 773 (::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1)); 774 ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1); 775 [pool release]; 776 777 return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed); 778 779 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 780} 781 782 783// AppShellDelegate implementation 784 785 786@implementation AppShellDelegate 787// initWithAppShell: 788// 789// Constructs the AppShellDelegate object 790- (id)initWithAppShell:(nsAppShell*)aAppShell 791{ 792 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 793 794 if ((self = [self init])) { 795 mAppShell = aAppShell; 796 797 [[NSNotificationCenter defaultCenter] addObserver:self 798 selector:@selector(applicationWillTerminate:) 799 name:NSApplicationWillTerminateNotification 800 object:NSApp]; 801 [[NSNotificationCenter defaultCenter] addObserver:self 802 selector:@selector(applicationDidBecomeActive:) 803 name:NSApplicationDidBecomeActiveNotification 804 object:NSApp]; 805 [[NSDistributedNotificationCenter defaultCenter] addObserver:self 806 selector:@selector(beginMenuTracking:) 807 name:@"com.apple.HIToolbox.beginMenuTrackingNotification" 808 object:nil]; 809 } 810 811 return self; 812 813 NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 814} 815 816- (void)dealloc 817{ 818 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 819 820 [[NSNotificationCenter defaultCenter] removeObserver:self]; 821 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; 822 [super dealloc]; 823 824 NS_OBJC_END_TRY_ABORT_BLOCK; 825} 826 827// applicationWillTerminate: 828// 829// Notify the nsAppShell that native event processing should be discontinued. 830- (void)applicationWillTerminate:(NSNotification*)aNotification 831{ 832 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 833 834 mAppShell->WillTerminate(); 835 836 NS_OBJC_END_TRY_ABORT_BLOCK; 837} 838 839// applicationDidBecomeActive 840// 841// Make sure TextInputHandler::sLastModifierState is updated when we become 842// active (since we won't have received [ChildView flagsChanged:] messages 843// while inactive). 844- (void)applicationDidBecomeActive:(NSNotification*)aNotification 845{ 846 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 847 848 // [NSEvent modifierFlags] is valid on every kind of event, so we don't need 849 // to worry about getting an NSInternalInconsistencyException here. 850 NSEvent* currentEvent = [NSApp currentEvent]; 851 if (currentEvent) { 852 TextInputHandler::sLastModifierState = 853 [currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask; 854 } 855 856 NS_OBJC_END_TRY_ABORT_BLOCK; 857} 858 859// beginMenuTracking 860// 861// Roll up our context menu (if any) when some other app (or the OS) opens 862// any sort of menu. But make sure we don't do this for notifications we 863// send ourselves (whose 'sender' will be @"org.mozilla.gecko.PopupWindow"). 864- (void)beginMenuTracking:(NSNotification*)aNotification 865{ 866 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 867 868 NSString *sender = [aNotification object]; 869 if (!sender || ![sender isEqualToString:@"org.mozilla.gecko.PopupWindow"]) { 870 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); 871 nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget(); 872 if (rollupWidget) 873 rollupListener->Rollup(0, true, nullptr, nullptr); 874 } 875 876 NS_OBJC_END_TRY_ABORT_BLOCK; 877} 878 879@end 880 881// We hook terminate: in order to make OS-initiated termination work nicely 882// with Gecko's shutdown sequence. (Two ways to trigger OS-initiated 883// termination: 1) Quit from the Dock menu; 2) Log out from (or shut down) 884// your computer while the browser is active.) 885@interface NSApplication (MethodSwizzling) 886- (void)nsAppShell_NSApplication_terminate:(id)sender; 887@end 888 889@implementation NSApplication (MethodSwizzling) 890 891// Called by the OS after [MacApplicationDelegate applicationShouldTerminate:] 892// has returned NSTerminateNow. This method "subclasses" and replaces the 893// OS's original implementation. The only thing the orginal method does which 894// we need is that it posts NSApplicationWillTerminateNotification. Everything 895// else is unneeded (because it's handled elsewhere), or actively interferes 896// with Gecko's shutdown sequence. For example the original terminate: method 897// causes the app to exit() inside [NSApp run] (called from nsAppShell::Run() 898// above), which means that nothing runs after the call to nsAppStartup::Run() 899// in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor 900// and NS_ShutdownXPCOM() never get called. 901- (void)nsAppShell_NSApplication_terminate:(id)sender 902{ 903 [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification 904 object:NSApp]; 905} 906 907@end 908