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