1 /* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * Runs the main native Cocoa run loop, interrupting it as needed to process
12 #import <Cocoa/Cocoa.h>
16 #include "mozilla/AvailableMemoryWatcher.h"
17 #include "CustomCocoaEvents.h"
18 #include "nsAppShell.h"
19 #include "gfxPlatform.h"
22 #include "nsDirectoryServiceDefs.h"
24 #include "nsIRollupListener.h"
25 #include "nsIWidget.h"
26 #include "nsMemoryPressure.h"
27 #include "nsThreadUtils.h"
28 #include "nsServiceManagerUtils.h"
29 #include "nsObjCExceptions.h"
30 #include "nsCocoaUtils.h"
31 #include "nsCocoaFeatures.h"
32 #include "nsChildView.h"
33 #include "nsToolkit.h"
34 #include "TextInputHandler.h"
35 #include "mozilla/BackgroundHangMonitor.h"
36 #include "ScreenHelperCocoa.h"
37 #include "mozilla/Hal.h"
38 #include "mozilla/ProfilerLabels.h"
39 #include "mozilla/ProfilerThreadSleep.h"
40 #include "mozilla/widget/ScreenManager.h"
41 #include "HeadlessScreenHelper.h"
42 #include "MOZMenuOpeningCoordinator.h"
44 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
45 # include "nsSandboxViolationSink.h"
48 #include <IOKit/pwr_mgt/IOPMLib.h>
49 #include "nsIDOMWakeLockListener.h"
50 #include "nsIPowerManagerService.h"
52 #include "nsIObserverService.h"
53 #include "mozilla/Services.h"
54 #include "mozilla/StaticPrefs_widget.h"
56 using namespace mozilla;
57 using namespace mozilla::widget;
59 #define WAKE_LOCK_LOG(...) \
60 MOZ_LOG(gMacWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
61 static mozilla::LazyLogModule gMacWakeLockLog("MacWakeLock");
63 // A wake lock listener that disables screen saver when requested by
64 // Gecko. For example when we're playing video in a foreground tab we
65 // don't want the screen saver to turn on.
67 class MacWakeLockListener final : public nsIDOMMozWakeLockListener {
72 ~MacWakeLockListener() {}
74 IOPMAssertionID mAssertionNoDisplaySleepID = kIOPMNullAssertionID;
75 IOPMAssertionID mAssertionNoIdleSleepID = kIOPMNullAssertionID;
77 NS_IMETHOD Callback(const nsAString& aTopic,
78 const nsAString& aState) override {
79 if (!aTopic.EqualsASCII("screen") && !aTopic.EqualsASCII("audio-playing") &&
80 !aTopic.EqualsASCII("video-playing")) {
84 // we should still hold the lock for background audio.
85 if (aTopic.EqualsASCII("audio-playing") &&
86 aState.EqualsASCII("locked-background")) {
87 WAKE_LOCK_LOG("keep audio playing even in background");
91 bool shouldKeepDisplayOn =
92 aTopic.EqualsASCII("screen") || aTopic.EqualsASCII("video-playing");
93 CFStringRef assertionType = shouldKeepDisplayOn
94 ? kIOPMAssertionTypeNoDisplaySleep
95 : kIOPMAssertionTypeNoIdleSleep;
96 IOPMAssertionID& assertionId = shouldKeepDisplayOn
97 ? mAssertionNoDisplaySleepID
98 : mAssertionNoIdleSleepID;
99 WAKE_LOCK_LOG("topic=%s, state=%s, shouldKeepDisplayOn=%d",
100 NS_ConvertUTF16toUTF8(aTopic).get(),
101 NS_ConvertUTF16toUTF8(aState).get(), shouldKeepDisplayOn);
103 // Note the wake lock code ensures that we're not sent duplicate
104 // "locked-foreground" notifications when multiple wake locks are held.
105 if (aState.EqualsASCII("locked-foreground")) {
106 if (assertionId != kIOPMNullAssertionID) {
107 WAKE_LOCK_LOG("already has a lock");
110 // Prevent screen saver.
111 CFStringRef cf_topic = ::CFStringCreateWithCharacters(
112 kCFAllocatorDefault, reinterpret_cast<const UniChar*>(aTopic.Data()),
114 IOReturn success = ::IOPMAssertionCreateWithName(
115 assertionType, kIOPMAssertionLevelOn, cf_topic, &assertionId);
117 if (success != kIOReturnSuccess) {
118 WAKE_LOCK_LOG("failed to disable screensaver");
120 WAKE_LOCK_LOG("create screensaver");
122 // Re-enable screen saver.
123 if (assertionId != kIOPMNullAssertionID) {
124 IOReturn result = ::IOPMAssertionRelease(assertionId);
125 if (result != kIOReturnSuccess) {
126 WAKE_LOCK_LOG("failed to release screensaver");
128 WAKE_LOCK_LOG("Release screensaver");
129 assertionId = kIOPMNullAssertionID;
134 }; // MacWakeLockListener
136 // defined in nsCocoaWindow.mm
137 extern int32_t gXULModalLevel;
139 static bool gAppShellMethodsSwizzled = false;
141 void OnUncaughtException(NSException* aException) {
142 nsObjCExceptionLog(aException);
144 "Uncaught Objective C exception from NSSetUncaughtExceptionHandler");
147 @implementation GeckoNSApplication
149 // Load is called very early during startup, when the Objective C runtime loads
152 NSSetUncaughtExceptionHandler(OnUncaughtException);
155 // This method is called from NSDefaultTopLevelErrorHandler, which is invoked
156 // when an Objective C exception propagates up into the native event loop. It is
157 // possible that it is also called in other cases.
158 - (void)reportException:(NSException*)aException {
159 if (ShouldIgnoreObjCException(aException)) {
163 nsObjCExceptionLog(aException);
166 MOZ_CRASH("Uncaught Objective C exception from -[GeckoNSApplication "
167 "reportException:]");
176 - (void)sendEvent:(NSEvent*)anEvent {
177 mozilla::BackgroundHangMonitor().NotifyActivity();
178 [super sendEvent:anEvent];
181 - (NSEvent*)nextEventMatchingMask:(NSEventMask)mask
182 untilDate:(NSDate*)expiration
183 inMode:(NSString*)mode
185 MOZ_ASSERT([NSApp didLaunch]);
187 mozilla::BackgroundHangMonitor().NotifyWait();
189 NSEvent* nextEvent = [super nextEventMatchingMask:mask
194 mozilla::BackgroundHangMonitor().NotifyActivity();
203 // Cocoa bridge class. An object of this class is registered to receive
206 @interface AppShellDelegate : NSObject {
208 nsAppShell* mAppShell;
211 - (id)initWithAppShell:(nsAppShell*)aAppShell;
212 - (void)applicationWillTerminate:(NSNotification*)aNotification;
215 // nsAppShell implementation
218 nsAppShell::ResumeNative(void) {
219 nsresult retval = nsBaseAppShell::ResumeNative();
220 if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) &&
221 mSkippedNativeCallback) {
222 mSkippedNativeCallback = false;
223 ScheduleNativeEventCallback();
228 nsAppShell::nsAppShell()
229 : mAutoreleasePools(nullptr),
232 mCFRunLoopSource(NULL),
233 mRunningEventLoop(false),
236 mSkippedNativeCallback(false),
237 mNativeEventCallbackDepth(0),
238 mNativeEventScheduledDepth(0) {
239 // A Cocoa event loop is running here if (and only if) we've been embedded
241 mRunningCocoaEmbedded = [NSApp isRunning] ? true : false;
244 nsAppShell::~nsAppShell() {
245 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
249 if (mMemoryPressureSource) {
250 dispatch_release(mMemoryPressureSource);
251 mMemoryPressureSource = nullptr;
255 if (mCFRunLoopSource) {
256 ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
257 kCFRunLoopCommonModes);
258 ::CFRelease(mCFRunLoopSource);
260 if (mCFRunLoopObserver) {
261 ::CFRunLoopRemoveObserver(mCFRunLoop, mCFRunLoopObserver,
262 kCFRunLoopCommonModes);
263 ::CFRelease(mCFRunLoopObserver);
265 ::CFRelease(mCFRunLoop);
268 if (mAutoreleasePools) {
269 NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
270 "nsAppShell destroyed without popping all autorelease pools");
271 ::CFRelease(mAutoreleasePools);
276 NS_OBJC_END_TRY_IGNORE_BLOCK
279 NS_IMPL_ISUPPORTS(MacWakeLockListener, nsIDOMMozWakeLockListener)
280 mozilla::StaticRefPtr<MacWakeLockListener> sWakeLockListener;
282 static void AddScreenWakeLockListener() {
283 nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
284 do_GetService(POWERMANAGERSERVICE_CONTRACTID);
285 if (sPowerManagerService) {
286 sWakeLockListener = new MacWakeLockListener();
287 sPowerManagerService->AddWakeLockListener(sWakeLockListener);
290 "Failed to retrieve PowerManagerService, wakelocks will be broken!");
294 static void RemoveScreenWakeLockListener() {
295 nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
296 do_GetService(POWERMANAGERSERVICE_CONTRACTID);
297 if (sPowerManagerService) {
298 sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
299 sPowerManagerService = nullptr;
300 sWakeLockListener = nullptr;
304 void RunLoopObserverCallback(CFRunLoopObserverRef aObserver,
305 CFRunLoopActivity aActivity, void* aInfo) {
306 static_cast<nsAppShell*>(aInfo)->OnRunLoopActivityChanged(aActivity);
309 void nsAppShell::OnRunLoopActivityChanged(CFRunLoopActivity aActivity) {
310 if (aActivity == kCFRunLoopBeforeWaiting) {
311 mozilla::BackgroundHangMonitor().NotifyWait();
314 // When the event loop is in its waiting state, we would like the profiler to
315 // know that the thread is idle. The usual way to notify the profiler of
316 // idleness would be to place a profiler label frame with the IDLE category on
317 // the stack, for the duration of the function that does the waiting. However,
318 // since macOS uses an event loop model where "the event loop calls you", we
319 // do not control the function that does the waiting; the waiting happens
320 // inside CFRunLoop code. Instead, the run loop notifies us when it enters and
321 // exits the waiting state, by calling this function. So we do not have a
322 // function under our control that stays on the stack for the duration of the
323 // wait. So, rather than putting an AutoProfilerLabel on the stack, we will
324 // manually push and pop the label frame here. The location in the stack where
325 // this label frame is inserted is somewhat arbitrary. In practice, the label
326 // frame will be at the very tip of the stack, looking like it's "inside" the
327 // mach_msg_trap wait function.
328 if (aActivity == kCFRunLoopBeforeWaiting) {
329 using ThreadRegistration = mozilla::profiler::ThreadRegistration;
330 ThreadRegistration::WithOnThreadRef(
331 [&](ThreadRegistration::OnThreadRef aOnThreadRef) {
332 ProfilingStack& profilingStack =
333 aOnThreadRef.UnlockedConstReaderAndAtomicRWRef()
334 .ProfilingStackRef();
335 mProfilingStackWhileWaiting = &profilingStack;
336 uint8_t variableOnStack = 0;
337 profilingStack.pushLabelFrame("Native event loop idle", nullptr,
339 JS::ProfilingCategoryPair::IDLE, 0);
340 profiler_thread_sleep();
343 if (mProfilingStackWhileWaiting) {
344 mProfilingStackWhileWaiting->pop();
345 mProfilingStackWhileWaiting = nullptr;
346 profiler_thread_wake();
353 // Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
354 // interrupt the main native run loop.
357 nsresult nsAppShell::Init() {
358 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
360 // No event loop is running yet (unless an embedding app that uses
361 // NSApplicationMain() is running).
362 NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
364 char* mozAppNoDock = PR_GetEnv("MOZ_APP_NO_DOCK");
365 if (mozAppNoDock && strcmp(mozAppNoDock, "") != 0) {
366 [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
369 // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
370 // by |this|. CFArray is used instead of NSArray because NSArray wants to
371 // retain each object you add to it, and you can't retain an
372 // NSAutoreleasePool.
373 mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
374 NS_ENSURE_STATE(mAutoreleasePools);
376 bool isNSApplicationProcessType =
377 (XRE_GetProcessType() != GeckoProcessType_RDD) &&
378 (XRE_GetProcessType() != GeckoProcessType_Socket);
380 if (isNSApplicationProcessType) {
381 // This call initializes NSApplication unless:
382 // 1) we're using xre -- NSApp's already been initialized by
383 // MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
384 // 2) an embedding app that uses NSApplicationMain() is running -- NSApp's
385 // already been initialized and its main run loop is already running.
386 [[NSBundle mainBundle] loadNibNamed:@"res/MainMenu"
387 owner:[GeckoNSApplication sharedApplication]
388 topLevelObjects:nil];
391 mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
392 NS_ENSURE_STATE(mDelegate);
394 // Add a CFRunLoopSource to the main native run loop. The source is
395 // responsible for interrupting the run loop when Gecko events are ready.
397 mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
398 NS_ENSURE_STATE(mCFRunLoop);
399 ::CFRetain(mCFRunLoop);
401 CFRunLoopSourceContext context;
402 bzero(&context, sizeof(context));
403 // context.version = 0;
405 context.perform = ProcessGeckoEvents;
407 mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
408 NS_ENSURE_STATE(mCFRunLoopSource);
410 ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
412 // Add a CFRunLoopObserver so that the profiler can be notified when we enter
413 // and exit the waiting state.
414 CFRunLoopObserverContext observerContext;
415 PodZero(&observerContext);
416 observerContext.info = this;
418 mCFRunLoopObserver = ::CFRunLoopObserverCreate(
420 kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting | kCFRunLoopExit, true,
421 0, RunLoopObserverCallback, &observerContext);
422 NS_ENSURE_STATE(mCFRunLoopObserver);
424 ::CFRunLoopAddObserver(mCFRunLoop, mCFRunLoopObserver, kCFRunLoopCommonModes);
428 if (XRE_IsParentProcess()) {
429 ScreenManager& screenManager = ScreenManager::GetSingleton();
431 if (gfxPlatform::IsHeadless()) {
432 screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
434 screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperCocoa>());
437 InitMemoryPressureObserver();
440 nsresult rv = nsBaseAppShell::Init();
442 if (isNSApplicationProcessType && !gAppShellMethodsSwizzled) {
443 // We should only replace the original terminate: method if we're not
444 // running in a Cocoa embedder. See bug 604901.
445 if (!mRunningCocoaEmbedded) {
446 nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
447 @selector(nsAppShell_NSApplication_terminate:));
449 gAppShellMethodsSwizzled = true;
452 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
453 if (Preferences::GetBool("security.sandbox.mac.track.violations", false)) {
454 nsSandboxViolationSink::Start();
462 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
465 // ProcessGeckoEvents
467 // The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
468 // signalled from ScheduleNativeEventCallback.
470 // Arrange for Gecko events to be processed on demand (in response to a call
471 // to ScheduleNativeEventCallback(), if processing of Gecko events via "native
472 // methods" hasn't been suspended). This happens in NativeEventCallback().
475 void nsAppShell::ProcessGeckoEvents(void* aInfo) {
476 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
477 AUTO_PROFILER_LABEL("nsAppShell::ProcessGeckoEvents", OTHER);
479 nsAppShell* self = static_cast<nsAppShell*>(aInfo);
481 if (self->mRunningEventLoop) {
482 self->mRunningEventLoop = false;
484 // The run loop may be sleeping -- [NSRunLoop runMode:...]
485 // won't return until it's given a reason to wake up. Awaken it by
486 // posting a bogus event. There's no need to make the event
489 // But _don't_ set windowNumber to '-1' -- that can lead to nasty
490 // weirdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
491 // these fake events, because the -1 has gotten changed into the number
492 // of an actual NSWindow object, and that NSWindow object has just been
493 // destroyed). Setting windowNumber to '0' seems to work fine -- this
494 // seems to prevent the OS from ever trying to associate our bogus event
495 // with a particular NSWindow object.
496 [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
497 location:NSMakePoint(0, 0)
502 subtype:kEventSubtypeNone
506 // Previously we used to send this second event regardless of
507 // self->mRunningEventLoop. However, that was removed in bug 1690687 for
508 // performance reasons. It is still needed for the mRunningEventLoop case
509 // otherwise we'll get in a cycle of sending postEvent followed by the
510 // DummyEvent inserted by nsBaseAppShell::OnProcessNextEvent. This second
511 // event will cause the second call to AcquireFirstMatchingEventInQueue in
512 // nsAppShell::ProcessNextNativeEvent to return true. Which makes
513 // nsBaseAppShell::OnProcessNextEvent call
514 // nsAppShell::ProcessNextNativeEvent again during which it will loop until
515 // it sleeps because ProcessGeckoEvents() won't be called for the
518 // This is not a good approach and we should fix things up so that only
519 // one postEvent is needed.
520 [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
521 location:NSMakePoint(0, 0)
526 subtype:kEventSubtypeNone
532 if (self->mSuspendNativeCount <= 0) {
533 ++self->mNativeEventCallbackDepth;
534 self->NativeEventCallback();
535 --self->mNativeEventCallbackDepth;
537 self->mSkippedNativeCallback = true;
540 if (self->mTerminated) {
541 // Still needed to avoid crashes on quit in most Mochitests.
542 [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
543 location:NSMakePoint(0, 0)
548 subtype:kEventSubtypeNone
553 // Normally every call to ScheduleNativeEventCallback() results in
554 // exactly one call to ProcessGeckoEvents(). So each Release() here
555 // normally balances exactly one AddRef() in ScheduleNativeEventCallback().
556 // But if Exit() is called just after ScheduleNativeEventCallback(), the
557 // corresponding call to ProcessGeckoEvents() will never happen. We check
558 // for this possibility in two different places -- here and in Exit()
559 // itself. If we find here that Exit() has been called (that mTerminated
560 // is true), it's because we've been called recursively, that Exit() was
561 // called from self->NativeEventCallback() above, and that we're unwinding
562 // the recursion. In this case we'll never be called again, and we balance
563 // here any extra calls to ScheduleNativeEventCallback().
565 // When ProcessGeckoEvents() is called recursively, it's because of a
566 // call to ScheduleNativeEventCallback() from NativeEventCallback(). We
567 // balance the "extra" AddRefs here (rather than always in Exit()) in order
568 // to ensure that 'self' stays alive until the end of this method. We also
569 // make sure not to finish the balancing until all the recursion has been
571 if (self->mTerminated) {
572 int32_t releaseCount = 0;
573 if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) {
574 releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth,
575 self->mNativeEventCallbackDepth);
577 while (releaseCount-- > self->mNativeEventCallbackDepth) self->Release();
579 // As best we can tell, every call to ProcessGeckoEvents() is triggered
580 // by a call to ScheduleNativeEventCallback(). But we've seen a few
581 // (non-reproducible) cases of double-frees that *might* have been caused
582 // by spontaneous calls (from the OS) to ProcessGeckoEvents(). So we
583 // deal with that possibility here.
584 if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) {
585 PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0);
586 NS_WARNING("Spontaneous call to ProcessGeckoEvents()!");
592 NS_OBJC_END_TRY_IGNORE_BLOCK;
597 // Called by the AppShellDelegate when an NSApplicationWillTerminate
598 // notification is posted. After this method is called, native events should
599 // no longer be processed. The NSApplicationWillTerminate notification is
600 // only posted when [NSApp terminate:] is called, which doesn't happen on a
601 // "normal" application quit.
604 void nsAppShell::WillTerminate() {
605 if (mTerminated) return;
607 // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
608 // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
609 NS_ProcessPendingEvents(NS_GetCurrentThread());
614 // ScheduleNativeEventCallback
616 // Called (possibly on a non-main thread) when Gecko has an event that
617 // needs to be processed. The Gecko event needs to be processed on the
618 // main thread, so the native run loop must be interrupted.
620 // In nsBaseAppShell.cpp, the mNativeEventPending variable is used to
621 // ensure that ScheduleNativeEventCallback() is called no more than once
622 // per call to NativeEventCallback(). ProcessGeckoEvents() can skip its
623 // call to NativeEventCallback() if processing of Gecko events by native
624 // means is suspended (using nsIAppShell::SuspendNative()), which will
625 // suspend calls from nsBaseAppShell::OnDispatchedEvent() to
626 // ScheduleNativeEventCallback(). But when Gecko event processing by
627 // native means is resumed (in ResumeNative()), an extra call is made to
628 // ScheduleNativeEventCallback() (from ResumeNative()). This triggers
629 // another call to ProcessGeckoEvents(), which calls NativeEventCallback(),
630 // and nsBaseAppShell::OnDispatchedEvent() resumes calling
631 // ScheduleNativeEventCallback().
634 void nsAppShell::ScheduleNativeEventCallback() {
635 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
637 if (mTerminated) return;
639 // Each AddRef() here is normally balanced by exactly one Release() in
640 // ProcessGeckoEvents(). But there are exceptions, for which see
641 // ProcessGeckoEvents() and Exit().
643 PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth);
645 // This will invoke ProcessGeckoEvents on the main thread.
646 ::CFRunLoopSourceSignal(mCFRunLoopSource);
647 ::CFRunLoopWakeUp(mCFRunLoop);
649 NS_OBJC_END_TRY_IGNORE_BLOCK;
652 // Undocumented Cocoa Event Manager function, present in the same form since
653 // at least OS X 10.6.
654 extern "C" EventAttributes GetEventAttributes(EventRef inEvent);
656 // ProcessNextNativeEvent
658 // If aMayWait is false, process a single native event. If it is true, run
659 // the native run loop until stopped by ProcessGeckoEvents.
661 // Returns true if more events are waiting in the native event queue.
664 bool nsAppShell::ProcessNextNativeEvent(bool aMayWait) {
665 bool moreEvents = false;
667 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
669 bool eventProcessed = false;
670 NSString* currentMode = nil;
672 if (mTerminated) return false;
674 // Do not call -[NSApplication nextEventMatchingMask:...] when we're trying to
675 // close a native menu. Doing so could confuse the NSMenu's closing mechanism.
676 // Instead, we try to unwind the stack as quickly as possible and return to
677 // the parent event loop. At that point, native events will be processed.
678 if (MOZMenuOpeningCoordinator.needToUnwindForMenuClosing) {
682 bool wasRunningEventLoop = mRunningEventLoop;
683 mRunningEventLoop = aMayWait;
684 NSDate* waitUntil = nil;
685 if (aMayWait) waitUntil = [NSDate distantFuture];
687 NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
689 EventQueueRef currentEventQueue = GetCurrentEventQueue();
692 mozilla::BackgroundHangMonitor().NotifyWait();
695 // Only call -[NSApp sendEvent:] (and indirectly send user-input events to
696 // Gecko) if aMayWait is true. Tbis ensures most calls to -[NSApp
697 // sendEvent:] happen under nsAppShell::Run(), at the lowest level of
698 // recursion -- thereby making it less likely Gecko will process user-input
699 // events in the wrong order or skip some of them. It also avoids eating
700 // too much CPU in nsBaseAppShell::OnProcessNextEvent() (which calls
701 // us) -- thereby avoiding the starvation of nsIRunnable events in
702 // nsThread::ProcessNextEvent(). For more information see bug 996848.
704 // No autorelease pool is provided here, because OnProcessNextEvent
705 // and AfterProcessNextEvent are responsible for maintaining it.
706 NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
707 "No autorelease pool for native event");
709 if (aMayWait && [[GeckoNSApplication sharedApplication] didLaunch]) {
710 currentMode = [currentRunLoop currentMode];
711 if (!currentMode) currentMode = NSDefaultRunLoopMode;
712 NSEvent* nextEvent = [NSApp nextEventMatchingMask:NSEventMaskAny
717 mozilla::BackgroundHangMonitor().NotifyActivity();
718 [NSApp sendEvent:nextEvent];
719 eventProcessed = true;
722 // In at least 10.15, AcquireFirstMatchingEventInQueue will move 1
723 // CGEvent from the CGEvent queue into the Carbon event queue.
724 // Unfortunately, once an event has been moved to the Carbon event queue
725 // it's no longer a candidate for coalescing. This means that even if we
726 // don't remove the event from the queue, just calling
727 // AcquireFirstMatchingEventInQueue can cause behaviour change. Prior to
728 // bug 1690687 landing, the event that we got from
729 // AcquireFirstMatchingEventInQueue was often our own ApplicationDefined
730 // event. However, once we stopped posting that event on every Gecko
731 // event we're much more likely to get a CGEvent. When we have a high
732 // amount of load on the main thread, we end up alternating between Gecko
733 // events and native events. Without CGEvent coalescing, the native
734 // event events can accumulate in the Carbon event queue which will
735 // manifest as laggy scrolling.
737 eventProcessed = false;
740 // AcquireFirstMatchingEventInQueue() doesn't spin the (native) event
741 // loop, though it does queue up any newly available events from the
743 EventRef currentEvent = AcquireFirstMatchingEventInQueue(
744 currentEventQueue, 0, NULL, kEventQueueOptionsNone);
748 EventAttributes attrs = GetEventAttributes(currentEvent);
749 UInt32 eventKind = GetEventKind(currentEvent);
750 UInt32 eventClass = GetEventClass(currentEvent);
752 ((eventClass == 'appl') || (eventClass == kEventClassAppleEvent) ||
753 ((eventClass == 'cgs ') &&
754 (eventKind != NSEventTypeApplicationDefined)));
755 // If attrs is kEventAttributeUserEvent or kEventAttributeMonitored
756 // (i.e. a user input event), we shouldn't process it here while
757 // aMayWait is false. Likewise if currentEvent will eventually be
758 // turned into an OS-defined Cocoa event, or otherwise needs AppKit
759 // processing. Doing otherwise risks doing too much work here, and
760 // preventing the event from being properly processed by the AppKit
762 if ((attrs != kEventAttributeNone) || osCocoaEvent) {
763 // Since we can't process the next event here (while aMayWait is false),
764 // we want moreEvents to be false on return.
765 eventProcessed = false;
766 // This call to ReleaseEvent() matches a call to RetainEvent() in
767 // AcquireFirstMatchingEventInQueue() above.
768 ReleaseEvent(currentEvent);
771 // This call to RetainEvent() matches a call to ReleaseEvent() in
772 // RemoveEventFromQueue() below.
773 RetainEvent(currentEvent);
774 RemoveEventFromQueue(currentEventQueue, currentEvent);
775 EventTargetRef eventDispatcherTarget = GetEventDispatcherTarget();
776 SendEventToEventTarget(currentEvent, eventDispatcherTarget);
777 // This call to ReleaseEvent() matches a call to RetainEvent() in
778 // AcquireFirstMatchingEventInQueue() above.
779 ReleaseEvent(currentEvent);
780 eventProcessed = true;
783 } while (mRunningEventLoop);
785 if (eventProcessed) {
787 (AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
788 kEventQueueOptionsNone) != NULL);
791 mRunningEventLoop = wasRunningEventLoop;
793 NS_OBJC_END_TRY_IGNORE_BLOCK;
796 nsChildView::UpdateCurrentInputEventCount();
802 // Attempt to work around bug 1801419 by loading and initializing the
803 // SidecarCore private framework as the app shell starts up. This normally
804 // happens on demand, the first time any Cmd-key combination is pressed, and
805 // sometimes triggers crashes, caused by an Apple bug. We hope that doing it
806 // now, and somewhat more simply, will avoid the crashes. They happen
807 // (intermittently) when SidecarCore code tries to access C strings in special
808 // sections of its own __TEXT segment, and triggers fatal page faults (which
809 // is Apple's bug). Many of the C strings are part of the Objective-C class
810 // hierarchy (class names and so forth). We hope that adding them to this
811 // hierarchy will "pin" them in place -- so they'll rarely, if ever, be paged
812 // out again. Bug 1801419's crashes happen much more often on macOS 13
813 // (Ventura) than on other versions of macOS. So we only use this hack on
815 static void PinSidecarCoreTextCStringSections() {
817 "/System/Library/PrivateFrameworks/SidecarCore.framework/SidecarCore",
822 // Explicitly run the most basic part of the initialization code that
823 // normally runs automatically on the first Cmd-key combination.
824 Class displayManagerClass = NSClassFromString(@"SidecarDisplayManager");
825 if ([displayManagerClass respondsToSelector:@selector(sharedManager)]) {
827 [displayManagerClass performSelector:@selector(sharedManager)];
828 if ([sharedManager respondsToSelector:@selector(devices)]) {
829 [sharedManager performSelector:@selector(devices)];
836 // Overrides the base class's Run() method to call [NSApp run] (which spins
837 // the native run loop until the application quits). Since (unlike the base
838 // class's Run() method) we don't process any Gecko events here, they need
839 // to be processed elsewhere (in NativeEventCallback(), called from
840 // ProcessGeckoEvents()).
842 // Camino called [NSApp run] on its own (via NSApplicationMain()), and so
843 // didn't call nsAppShell::Run().
847 nsAppShell::Run(void) {
848 NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
849 if (mStarted || mTerminated) return NS_OK;
853 if (XRE_IsParentProcess()) {
854 if (nsCocoaFeatures::OnVenturaOrLater()) {
855 PinSidecarCoreTextCStringSections();
857 AddScreenWakeLockListener();
860 // We use the native Gecko event loop in content processes.
862 if (XRE_UseNativeEventProcessing()) {
863 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
865 NS_OBJC_END_TRY_IGNORE_BLOCK;
867 rv = nsBaseAppShell::Run();
870 if (XRE_IsParentProcess()) {
871 RemoveScreenWakeLockListener();
878 nsAppShell::Exit(void) {
879 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
881 // This method is currently called more than once -- from (according to
882 // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
883 // XPCOM shutdown notification that nsBaseAppShell has registered to
884 // receive. So we need to ensure that multiple calls won't break anything.
891 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
892 nsSandboxViolationSink::Stop();
895 // Quoting from Apple's doc on the [NSApplication stop:] method (from their
896 // doc on the NSApplication class): "If this method is invoked during a
897 // modal event loop, it will break that loop but not the main event loop."
898 // nsAppShell::Exit() shouldn't be called from a modal event loop. So if
899 // it is we complain about it (to users of debug builds) and call [NSApp
900 // stop:] one extra time. (I'm not sure if modal event loops can be nested
901 // -- Apple's docs don't say one way or the other. But the return value
902 // of [NSApp _isRunningModal] doesn't change immediately after a call to
903 // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
905 BOOL cocoaModal = [NSApp _isRunningModal];
906 NS_ASSERTION(!cocoaModal,
907 "Don't call nsAppShell::Exit() from a modal event loop!");
908 if (cocoaModal) [NSApp stop:nullptr];
909 [NSApp stop:nullptr];
911 // A call to Exit() just after a call to ScheduleNativeEventCallback()
912 // prevents the (normally) matching call to ProcessGeckoEvents() from
913 // happening. If we've been called from ProcessGeckoEvents() (as usually
914 // happens), we take care of it there. But if we have an unbalanced call
915 // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
916 // stack, we need to take care of the problem here.
917 if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
918 int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
919 while (releaseCount-- > 0) NS_RELEASE_THIS();
922 return nsBaseAppShell::Exit();
924 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
927 // OnProcessNextEvent
929 // This nsIThreadObserver method is called prior to processing an event.
930 // Set up an autorelease pool that will service any autoreleased Cocoa
931 // objects during this event. This includes native events processed by
932 // ProcessNextNativeEvent. The autorelease pool will be popped by
933 // AfterProcessNextEvent, it is important for these two methods to be
938 nsAppShell::OnProcessNextEvent(nsIThreadInternal* aThread, bool aMayWait) {
939 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
941 NS_ASSERTION(mAutoreleasePools,
942 "No stack on which to store autorelease pool");
944 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
945 ::CFArrayAppendValue(mAutoreleasePools, pool);
947 return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait);
949 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
952 // AfterProcessNextEvent
954 // This nsIThreadObserver method is called after event processing is complete.
955 // The Cocoa implementation cleans up the autorelease pool create by the
956 // previous OnProcessNextEvent call.
960 nsAppShell::AfterProcessNextEvent(nsIThreadInternal* aThread,
961 bool aEventWasProcessed) {
962 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
964 CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
966 NS_ASSERTION(mAutoreleasePools && count,
967 "Processed an event, but there's no autorelease pool?");
969 const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*>(
970 ::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
971 ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
974 return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed);
976 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
979 void nsAppShell::InitMemoryPressureObserver() {
980 // Testing shows that sometimes the memory pressure event is not fired for
981 // over a minute after the memory pressure change is reflected in sysctl
982 // values. Hence this may need to be augmented with polling of the memory
983 // pressure sysctls for lower latency reactions to OS memory pressure. This
984 // was also observed when using DISPATCH_QUEUE_PRIORITY_HIGH.
985 mMemoryPressureSource = dispatch_source_create(
986 DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0,
987 DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN |
988 DISPATCH_MEMORYPRESSURE_CRITICAL,
989 dispatch_get_main_queue());
991 dispatch_source_set_event_handler(mMemoryPressureSource, ^{
992 dispatch_source_memorypressure_flags_t pressureLevel =
993 dispatch_source_get_data(mMemoryPressureSource);
994 nsAppShell::OnMemoryPressureChanged(pressureLevel);
997 dispatch_resume(mMemoryPressureSource);
999 // Initialize the memory watcher.
1000 RefPtr<mozilla::nsAvailableMemoryWatcherBase> watcher(
1001 nsAvailableMemoryWatcherBase::GetSingleton());
1004 void nsAppShell::OnMemoryPressureChanged(
1005 dispatch_source_memorypressure_flags_t aPressureLevel) {
1006 // The memory pressure dispatch source is created (above) with
1007 // dispatch_get_main_queue() which always fires on the main thread.
1008 MOZ_ASSERT(NS_IsMainThread());
1010 MacMemoryPressureLevel geckoPressureLevel;
1011 switch (aPressureLevel) {
1012 case DISPATCH_MEMORYPRESSURE_NORMAL:
1013 geckoPressureLevel = MacMemoryPressureLevel::Value::eNormal;
1015 case DISPATCH_MEMORYPRESSURE_WARN:
1016 geckoPressureLevel = MacMemoryPressureLevel::Value::eWarning;
1018 case DISPATCH_MEMORYPRESSURE_CRITICAL:
1019 geckoPressureLevel = MacMemoryPressureLevel::Value::eCritical;
1022 geckoPressureLevel = MacMemoryPressureLevel::Value::eUnexpected;
1025 RefPtr<mozilla::nsAvailableMemoryWatcherBase> watcher(
1026 nsAvailableMemoryWatcherBase::GetSingleton());
1027 watcher->OnMemoryPressureChanged(geckoPressureLevel);
1030 // AppShellDelegate implementation
1032 @implementation AppShellDelegate
1033 // initWithAppShell:
1035 // Constructs the AppShellDelegate object
1036 - (id)initWithAppShell:(nsAppShell*)aAppShell {
1037 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1039 if ((self = [self init])) {
1040 mAppShell = aAppShell;
1042 [[NSNotificationCenter defaultCenter]
1044 selector:@selector(applicationWillTerminate:)
1045 name:NSApplicationWillTerminateNotification
1047 [[NSNotificationCenter defaultCenter]
1049 selector:@selector(applicationDidBecomeActive:)
1050 name:NSApplicationDidBecomeActiveNotification
1052 [[NSNotificationCenter defaultCenter]
1054 selector:@selector(timezoneChanged:)
1055 name:NSSystemTimeZoneDidChangeNotification
1061 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
1065 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1067 [[NSNotificationCenter defaultCenter] removeObserver:self];
1068 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
1071 NS_OBJC_END_TRY_IGNORE_BLOCK;
1074 // applicationWillTerminate:
1076 // Notify the nsAppShell that native event processing should be discontinued.
1077 - (void)applicationWillTerminate:(NSNotification*)aNotification {
1078 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1080 mAppShell->WillTerminate();
1082 NS_OBJC_END_TRY_IGNORE_BLOCK;
1085 // applicationDidBecomeActive
1087 // Make sure TextInputHandler::sLastModifierState is updated when we become
1088 // active (since we won't have received [ChildView flagsChanged:] messages
1090 - (void)applicationDidBecomeActive:(NSNotification*)aNotification {
1091 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1093 // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
1094 // to worry about getting an NSInternalInconsistencyException here.
1095 NSEvent* currentEvent = [NSApp currentEvent];
1097 TextInputHandler::sLastModifierState =
1098 [currentEvent modifierFlags] &
1099 NSEventModifierFlagDeviceIndependentFlagsMask;
1102 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
1103 if (observerService) {
1104 observerService->NotifyObservers(
1105 nullptr, NS_WIDGET_MAC_APP_ACTIVATE_OBSERVER_TOPIC, nullptr);
1108 NS_OBJC_END_TRY_IGNORE_BLOCK;
1111 - (void)timezoneChanged:(NSNotification*)aNotification {
1112 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1114 nsBaseAppShell::OnSystemTimezoneChange();
1116 NS_OBJC_END_TRY_IGNORE_BLOCK;
1121 // We hook terminate: in order to make OS-initiated termination work nicely
1122 // with Gecko's shutdown sequence. (Two ways to trigger OS-initiated
1123 // termination: 1) Quit from the Dock menu; 2) Log out from (or shut down)
1124 // your computer while the browser is active.)
1125 @interface NSApplication (MethodSwizzling)
1126 - (void)nsAppShell_NSApplication_terminate:(id)sender;
1129 @implementation NSApplication (MethodSwizzling)
1131 // Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
1132 // has returned NSTerminateNow. This method "subclasses" and replaces the
1133 // OS's original implementation. The only thing the orginal method does which
1134 // we need is that it posts NSApplicationWillTerminateNotification. Everything
1135 // else is unneeded (because it's handled elsewhere), or actively interferes
1136 // with Gecko's shutdown sequence. For example the original terminate: method
1137 // causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
1138 // above), which means that nothing runs after the call to nsAppStartup::Run()
1139 // in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
1140 // and NS_ShutdownXPCOM() never get called.
1141 - (void)nsAppShell_NSApplication_terminate:(id)sender {
1142 [[NSNotificationCenter defaultCenter]
1143 postNotificationName:NSApplicationWillTerminateNotification