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 "mozilla/WidgetTraceEvent.h"
19 #include "nsAppShell.h"
20 #include "gfxPlatform.h"
23 #include "nsDirectoryServiceDefs.h"
25 #include "nsIRollupListener.h"
26 #include "nsIWidget.h"
27 #include "nsMemoryPressure.h"
28 #include "nsThreadUtils.h"
29 #include "nsServiceManagerUtils.h"
30 #include "nsObjCExceptions.h"
31 #include "nsCocoaUtils.h"
32 #include "nsCocoaFeatures.h"
33 #include "nsChildView.h"
34 #include "nsToolkit.h"
35 #include "TextInputHandler.h"
36 #include "mozilla/BackgroundHangMonitor.h"
37 #include "ScreenHelperCocoa.h"
38 #include "mozilla/Hal.h"
39 #include "mozilla/ProfilerLabels.h"
40 #include "mozilla/ProfilerThreadSleep.h"
41 #include "mozilla/widget/ScreenManager.h"
42 #include "HeadlessScreenHelper.h"
43 #include "MOZMenuOpeningCoordinator.h"
45 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
46 # include "nsSandboxViolationSink.h"
49 #include <IOKit/pwr_mgt/IOPMLib.h>
50 #include "nsIDOMWakeLockListener.h"
51 #include "nsIPowerManagerService.h"
53 #include "nsIObserverService.h"
54 #include "mozilla/Services.h"
55 #include "mozilla/StaticPrefs_widget.h"
57 using namespace mozilla;
58 using namespace mozilla::widget;
60 #define WAKE_LOCK_LOG(...) \
61 MOZ_LOG(gMacWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
62 static mozilla::LazyLogModule gMacWakeLockLog("MacWakeLock");
64 // A wake lock listener that disables screen saver when requested by
65 // Gecko. For example when we're playing video in a foreground tab we
66 // don't want the screen saver to turn on.
68 class MacWakeLockListener final : public nsIDOMMozWakeLockListener {
73 ~MacWakeLockListener() {}
75 IOPMAssertionID mAssertionNoDisplaySleepID = kIOPMNullAssertionID;
76 IOPMAssertionID mAssertionNoIdleSleepID = kIOPMNullAssertionID;
78 NS_IMETHOD Callback(const nsAString& aTopic,
79 const nsAString& aState) override {
80 if (!aTopic.EqualsASCII("screen") && !aTopic.EqualsASCII("audio-playing") &&
81 !aTopic.EqualsASCII("video-playing")) {
85 // we should still hold the lock for background audio.
86 if (aTopic.EqualsASCII("audio-playing") &&
87 aState.EqualsASCII("locked-background")) {
88 WAKE_LOCK_LOG("keep audio playing even in background");
92 bool shouldKeepDisplayOn =
93 aTopic.EqualsASCII("screen") || aTopic.EqualsASCII("video-playing");
94 CFStringRef assertionType = shouldKeepDisplayOn
95 ? kIOPMAssertionTypeNoDisplaySleep
96 : kIOPMAssertionTypeNoIdleSleep;
97 IOPMAssertionID& assertionId = shouldKeepDisplayOn
98 ? mAssertionNoDisplaySleepID
99 : mAssertionNoIdleSleepID;
100 WAKE_LOCK_LOG("topic=%s, state=%s, shouldKeepDisplayOn=%d",
101 NS_ConvertUTF16toUTF8(aTopic).get(),
102 NS_ConvertUTF16toUTF8(aState).get(), shouldKeepDisplayOn);
104 // Note the wake lock code ensures that we're not sent duplicate
105 // "locked-foreground" notifications when multiple wake locks are held.
106 if (aState.EqualsASCII("locked-foreground")) {
107 if (assertionId != kIOPMNullAssertionID) {
108 WAKE_LOCK_LOG("already has a lock");
111 // Prevent screen saver.
112 CFStringRef cf_topic = ::CFStringCreateWithCharacters(
113 kCFAllocatorDefault, reinterpret_cast<const UniChar*>(aTopic.Data()),
115 IOReturn success = ::IOPMAssertionCreateWithName(
116 assertionType, kIOPMAssertionLevelOn, cf_topic, &assertionId);
118 if (success != kIOReturnSuccess) {
119 WAKE_LOCK_LOG("failed to disable screensaver");
121 WAKE_LOCK_LOG("create screensaver");
123 // Re-enable screen saver.
124 if (assertionId != kIOPMNullAssertionID) {
125 IOReturn result = ::IOPMAssertionRelease(assertionId);
126 if (result != kIOReturnSuccess) {
127 WAKE_LOCK_LOG("failed to release screensaver");
129 WAKE_LOCK_LOG("Release screensaver");
130 assertionId = kIOPMNullAssertionID;
135 }; // MacWakeLockListener
137 // defined in nsCocoaWindow.mm
138 extern int32_t gXULModalLevel;
140 static bool gAppShellMethodsSwizzled = false;
142 void OnUncaughtException(NSException* aException) {
143 nsObjCExceptionLog(aException);
145 "Uncaught Objective C exception from NSSetUncaughtExceptionHandler");
148 @implementation GeckoNSApplication
150 // Load is called very early during startup, when the Objective C runtime loads
153 NSSetUncaughtExceptionHandler(OnUncaughtException);
156 // This method is called from NSDefaultTopLevelErrorHandler, which is invoked
157 // when an Objective C exception propagates up into the native event loop. It is
158 // possible that it is also called in other cases.
159 - (void)reportException:(NSException*)aException {
160 if (ShouldIgnoreObjCException(aException)) {
164 nsObjCExceptionLog(aException);
167 MOZ_CRASH("Uncaught Objective C exception from -[GeckoNSApplication "
168 "reportException:]");
177 - (void)sendEvent:(NSEvent*)anEvent {
178 mozilla::BackgroundHangMonitor().NotifyActivity();
180 if ([anEvent type] == NSEventTypeApplicationDefined &&
181 [anEvent subtype] == kEventSubtypeTrace) {
182 mozilla::SignalTracerThread();
185 [super sendEvent:anEvent];
188 - (NSEvent*)nextEventMatchingMask:(NSEventMask)mask
189 untilDate:(NSDate*)expiration
190 inMode:(NSString*)mode
192 MOZ_ASSERT([NSApp didLaunch]);
194 mozilla::BackgroundHangMonitor().NotifyWait();
196 NSEvent* nextEvent = [super nextEventMatchingMask:mask
201 mozilla::BackgroundHangMonitor().NotifyActivity();
210 // Cocoa bridge class. An object of this class is registered to receive
213 @interface AppShellDelegate : NSObject {
215 nsAppShell* mAppShell;
218 - (id)initWithAppShell:(nsAppShell*)aAppShell;
219 - (void)applicationWillTerminate:(NSNotification*)aNotification;
222 // nsAppShell implementation
225 nsAppShell::ResumeNative(void) {
226 nsresult retval = nsBaseAppShell::ResumeNative();
227 if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) &&
228 mSkippedNativeCallback) {
229 mSkippedNativeCallback = false;
230 ScheduleNativeEventCallback();
235 nsAppShell::nsAppShell()
236 : mAutoreleasePools(nullptr),
239 mCFRunLoopSource(NULL),
240 mRunningEventLoop(false),
243 mSkippedNativeCallback(false),
244 mNativeEventCallbackDepth(0),
245 mNativeEventScheduledDepth(0) {
246 // A Cocoa event loop is running here if (and only if) we've been embedded
248 mRunningCocoaEmbedded = [NSApp isRunning] ? true : false;
251 nsAppShell::~nsAppShell() {
252 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
256 if (mMemoryPressureSource) {
257 dispatch_release(mMemoryPressureSource);
258 mMemoryPressureSource = nullptr;
262 if (mCFRunLoopSource) {
263 ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
264 kCFRunLoopCommonModes);
265 ::CFRelease(mCFRunLoopSource);
267 if (mCFRunLoopObserver) {
268 ::CFRunLoopRemoveObserver(mCFRunLoop, mCFRunLoopObserver,
269 kCFRunLoopCommonModes);
270 ::CFRelease(mCFRunLoopObserver);
272 ::CFRelease(mCFRunLoop);
275 if (mAutoreleasePools) {
276 NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
277 "nsAppShell destroyed without popping all autorelease pools");
278 ::CFRelease(mAutoreleasePools);
283 NS_OBJC_END_TRY_IGNORE_BLOCK
286 NS_IMPL_ISUPPORTS(MacWakeLockListener, nsIDOMMozWakeLockListener)
287 mozilla::StaticRefPtr<MacWakeLockListener> sWakeLockListener;
289 static void AddScreenWakeLockListener() {
290 nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
291 do_GetService(POWERMANAGERSERVICE_CONTRACTID);
292 if (sPowerManagerService) {
293 sWakeLockListener = new MacWakeLockListener();
294 sPowerManagerService->AddWakeLockListener(sWakeLockListener);
297 "Failed to retrieve PowerManagerService, wakelocks will be broken!");
301 static void RemoveScreenWakeLockListener() {
302 nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
303 do_GetService(POWERMANAGERSERVICE_CONTRACTID);
304 if (sPowerManagerService) {
305 sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
306 sPowerManagerService = nullptr;
307 sWakeLockListener = nullptr;
311 void RunLoopObserverCallback(CFRunLoopObserverRef aObserver,
312 CFRunLoopActivity aActivity, void* aInfo) {
313 static_cast<nsAppShell*>(aInfo)->OnRunLoopActivityChanged(aActivity);
316 void nsAppShell::OnRunLoopActivityChanged(CFRunLoopActivity aActivity) {
317 if (aActivity == kCFRunLoopBeforeWaiting) {
318 mozilla::BackgroundHangMonitor().NotifyWait();
321 // When the event loop is in its waiting state, we would like the profiler to
322 // know that the thread is idle. The usual way to notify the profiler of
323 // idleness would be to place a profiler label frame with the IDLE category on
324 // the stack, for the duration of the function that does the waiting. However,
325 // since macOS uses an event loop model where "the event loop calls you", we
326 // do not control the function that does the waiting; the waiting happens
327 // inside CFRunLoop code. Instead, the run loop notifies us when it enters and
328 // exits the waiting state, by calling this function. So we do not have a
329 // function under our control that stays on the stack for the duration of the
330 // wait. So, rather than putting an AutoProfilerLabel on the stack, we will
331 // manually push and pop the label frame here. The location in the stack where
332 // this label frame is inserted is somewhat arbitrary. In practice, the label
333 // frame will be at the very tip of the stack, looking like it's "inside" the
334 // mach_msg_trap wait function.
335 if (aActivity == kCFRunLoopBeforeWaiting) {
336 using ThreadRegistration = mozilla::profiler::ThreadRegistration;
337 ThreadRegistration::WithOnThreadRef(
338 [&](ThreadRegistration::OnThreadRef aOnThreadRef) {
339 ProfilingStack& profilingStack =
340 aOnThreadRef.UnlockedConstReaderAndAtomicRWRef()
341 .ProfilingStackRef();
342 mProfilingStackWhileWaiting = &profilingStack;
343 uint8_t variableOnStack = 0;
344 profilingStack.pushLabelFrame("Native event loop idle", nullptr,
346 JS::ProfilingCategoryPair::IDLE, 0);
347 profiler_thread_sleep();
350 if (mProfilingStackWhileWaiting) {
351 mProfilingStackWhileWaiting->pop();
352 mProfilingStackWhileWaiting = nullptr;
353 profiler_thread_wake();
360 // Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
361 // interrupt the main native run loop.
364 nsresult nsAppShell::Init() {
365 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
367 // No event loop is running yet (unless an embedding app that uses
368 // NSApplicationMain() is running).
369 NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
371 char* mozAppNoDock = PR_GetEnv("MOZ_APP_NO_DOCK");
372 if (mozAppNoDock && strcmp(mozAppNoDock, "") != 0) {
373 [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
376 // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
377 // by |this|. CFArray is used instead of NSArray because NSArray wants to
378 // retain each object you add to it, and you can't retain an
379 // NSAutoreleasePool.
380 mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
381 NS_ENSURE_STATE(mAutoreleasePools);
383 bool isNSApplicationProcessType =
384 (XRE_GetProcessType() != GeckoProcessType_RDD) &&
385 (XRE_GetProcessType() != GeckoProcessType_Socket);
387 if (isNSApplicationProcessType) {
388 // This call initializes NSApplication unless:
389 // 1) we're using xre -- NSApp's already been initialized by
390 // MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
391 // 2) an embedding app that uses NSApplicationMain() is running -- NSApp's
392 // already been initialized and its main run loop is already running.
393 [[NSBundle mainBundle] loadNibNamed:@"res/MainMenu"
394 owner:[GeckoNSApplication sharedApplication]
395 topLevelObjects:nil];
398 mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
399 NS_ENSURE_STATE(mDelegate);
401 // Add a CFRunLoopSource to the main native run loop. The source is
402 // responsible for interrupting the run loop when Gecko events are ready.
404 mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
405 NS_ENSURE_STATE(mCFRunLoop);
406 ::CFRetain(mCFRunLoop);
408 CFRunLoopSourceContext context;
409 bzero(&context, sizeof(context));
410 // context.version = 0;
412 context.perform = ProcessGeckoEvents;
414 mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
415 NS_ENSURE_STATE(mCFRunLoopSource);
417 ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
419 // Add a CFRunLoopObserver so that the profiler can be notified when we enter
420 // and exit the waiting state.
421 CFRunLoopObserverContext observerContext;
422 PodZero(&observerContext);
423 observerContext.info = this;
425 mCFRunLoopObserver = ::CFRunLoopObserverCreate(
427 kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting | kCFRunLoopExit, true,
428 0, RunLoopObserverCallback, &observerContext);
429 NS_ENSURE_STATE(mCFRunLoopObserver);
431 ::CFRunLoopAddObserver(mCFRunLoop, mCFRunLoopObserver, kCFRunLoopCommonModes);
435 if (XRE_IsParentProcess()) {
436 ScreenManager& screenManager = ScreenManager::GetSingleton();
438 if (gfxPlatform::IsHeadless()) {
439 screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
441 screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperCocoa>());
444 InitMemoryPressureObserver();
447 nsresult rv = nsBaseAppShell::Init();
449 if (isNSApplicationProcessType && !gAppShellMethodsSwizzled) {
450 // We should only replace the original terminate: method if we're not
451 // running in a Cocoa embedder. See bug 604901.
452 if (!mRunningCocoaEmbedded) {
453 nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
454 @selector(nsAppShell_NSApplication_terminate:));
456 gAppShellMethodsSwizzled = true;
459 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
460 if (Preferences::GetBool("security.sandbox.mac.track.violations", false)) {
461 nsSandboxViolationSink::Start();
469 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
472 // ProcessGeckoEvents
474 // The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
475 // signalled from ScheduleNativeEventCallback.
477 // Arrange for Gecko events to be processed on demand (in response to a call
478 // to ScheduleNativeEventCallback(), if processing of Gecko events via "native
479 // methods" hasn't been suspended). This happens in NativeEventCallback().
482 void nsAppShell::ProcessGeckoEvents(void* aInfo) {
483 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
484 AUTO_PROFILER_LABEL("nsAppShell::ProcessGeckoEvents", OTHER);
486 nsAppShell* self = static_cast<nsAppShell*>(aInfo);
488 if (self->mRunningEventLoop) {
489 self->mRunningEventLoop = false;
491 // The run loop may be sleeping -- [NSRunLoop runMode:...]
492 // won't return until it's given a reason to wake up. Awaken it by
493 // posting a bogus event. There's no need to make the event
496 // But _don't_ set windowNumber to '-1' -- that can lead to nasty
497 // weirdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
498 // these fake events, because the -1 has gotten changed into the number
499 // of an actual NSWindow object, and that NSWindow object has just been
500 // destroyed). Setting windowNumber to '0' seems to work fine -- this
501 // seems to prevent the OS from ever trying to associate our bogus event
502 // with a particular NSWindow object.
503 [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
504 location:NSMakePoint(0, 0)
509 subtype:kEventSubtypeNone
513 // Previously we used to send this second event regardless of
514 // self->mRunningEventLoop. However, that was removed in bug 1690687 for
515 // performance reasons. It is still needed for the mRunningEventLoop case
516 // otherwise we'll get in a cycle of sending postEvent followed by the
517 // DummyEvent inserted by nsBaseAppShell::OnProcessNextEvent. This second
518 // event will cause the second call to AcquireFirstMatchingEventInQueue in
519 // nsAppShell::ProcessNextNativeEvent to return true. Which makes
520 // nsBaseAppShell::OnProcessNextEvent call
521 // nsAppShell::ProcessNextNativeEvent again during which it will loop until
522 // it sleeps because ProcessGeckoEvents() won't be called for the
525 // This is not a good approach and we should fix things up so that only
526 // one postEvent is needed.
527 [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
528 location:NSMakePoint(0, 0)
533 subtype:kEventSubtypeNone
539 if (self->mSuspendNativeCount <= 0) {
540 ++self->mNativeEventCallbackDepth;
541 self->NativeEventCallback();
542 --self->mNativeEventCallbackDepth;
544 self->mSkippedNativeCallback = true;
547 if (self->mTerminated) {
548 // Still needed to avoid crashes on quit in most Mochitests.
549 [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
550 location:NSMakePoint(0, 0)
555 subtype:kEventSubtypeNone
560 // Normally every call to ScheduleNativeEventCallback() results in
561 // exactly one call to ProcessGeckoEvents(). So each Release() here
562 // normally balances exactly one AddRef() in ScheduleNativeEventCallback().
563 // But if Exit() is called just after ScheduleNativeEventCallback(), the
564 // corresponding call to ProcessGeckoEvents() will never happen. We check
565 // for this possibility in two different places -- here and in Exit()
566 // itself. If we find here that Exit() has been called (that mTerminated
567 // is true), it's because we've been called recursively, that Exit() was
568 // called from self->NativeEventCallback() above, and that we're unwinding
569 // the recursion. In this case we'll never be called again, and we balance
570 // here any extra calls to ScheduleNativeEventCallback().
572 // When ProcessGeckoEvents() is called recursively, it's because of a
573 // call to ScheduleNativeEventCallback() from NativeEventCallback(). We
574 // balance the "extra" AddRefs here (rather than always in Exit()) in order
575 // to ensure that 'self' stays alive until the end of this method. We also
576 // make sure not to finish the balancing until all the recursion has been
578 if (self->mTerminated) {
579 int32_t releaseCount = 0;
580 if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) {
581 releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth,
582 self->mNativeEventCallbackDepth);
584 while (releaseCount-- > self->mNativeEventCallbackDepth) self->Release();
586 // As best we can tell, every call to ProcessGeckoEvents() is triggered
587 // by a call to ScheduleNativeEventCallback(). But we've seen a few
588 // (non-reproducible) cases of double-frees that *might* have been caused
589 // by spontaneous calls (from the OS) to ProcessGeckoEvents(). So we
590 // deal with that possibility here.
591 if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) {
592 PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0);
593 NS_WARNING("Spontaneous call to ProcessGeckoEvents()!");
599 NS_OBJC_END_TRY_IGNORE_BLOCK;
604 // Called by the AppShellDelegate when an NSApplicationWillTerminate
605 // notification is posted. After this method is called, native events should
606 // no longer be processed. The NSApplicationWillTerminate notification is
607 // only posted when [NSApp terminate:] is called, which doesn't happen on a
608 // "normal" application quit.
611 void nsAppShell::WillTerminate() {
612 if (mTerminated) return;
614 // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
615 // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
616 NS_ProcessPendingEvents(NS_GetCurrentThread());
621 // ScheduleNativeEventCallback
623 // Called (possibly on a non-main thread) when Gecko has an event that
624 // needs to be processed. The Gecko event needs to be processed on the
625 // main thread, so the native run loop must be interrupted.
627 // In nsBaseAppShell.cpp, the mNativeEventPending variable is used to
628 // ensure that ScheduleNativeEventCallback() is called no more than once
629 // per call to NativeEventCallback(). ProcessGeckoEvents() can skip its
630 // call to NativeEventCallback() if processing of Gecko events by native
631 // means is suspended (using nsIAppShell::SuspendNative()), which will
632 // suspend calls from nsBaseAppShell::OnDispatchedEvent() to
633 // ScheduleNativeEventCallback(). But when Gecko event processing by
634 // native means is resumed (in ResumeNative()), an extra call is made to
635 // ScheduleNativeEventCallback() (from ResumeNative()). This triggers
636 // another call to ProcessGeckoEvents(), which calls NativeEventCallback(),
637 // and nsBaseAppShell::OnDispatchedEvent() resumes calling
638 // ScheduleNativeEventCallback().
641 void nsAppShell::ScheduleNativeEventCallback() {
642 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
644 if (mTerminated) return;
646 // Each AddRef() here is normally balanced by exactly one Release() in
647 // ProcessGeckoEvents(). But there are exceptions, for which see
648 // ProcessGeckoEvents() and Exit().
650 PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth);
652 // This will invoke ProcessGeckoEvents on the main thread.
653 ::CFRunLoopSourceSignal(mCFRunLoopSource);
654 ::CFRunLoopWakeUp(mCFRunLoop);
656 NS_OBJC_END_TRY_IGNORE_BLOCK;
659 // Undocumented Cocoa Event Manager function, present in the same form since
660 // at least OS X 10.6.
661 extern "C" EventAttributes GetEventAttributes(EventRef inEvent);
663 // ProcessNextNativeEvent
665 // If aMayWait is false, process a single native event. If it is true, run
666 // the native run loop until stopped by ProcessGeckoEvents.
668 // Returns true if more events are waiting in the native event queue.
671 bool nsAppShell::ProcessNextNativeEvent(bool aMayWait) {
672 bool moreEvents = false;
674 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
676 bool eventProcessed = false;
677 NSString* currentMode = nil;
679 if (mTerminated) return false;
681 // Do not call -[NSApplication nextEventMatchingMask:...] when we're trying to
682 // close a native menu. Doing so could confuse the NSMenu's closing mechanism.
683 // Instead, we try to unwind the stack as quickly as possible and return to
684 // the parent event loop. At that point, native events will be processed.
685 if (MOZMenuOpeningCoordinator.needToUnwindForMenuClosing) {
689 bool wasRunningEventLoop = mRunningEventLoop;
690 mRunningEventLoop = aMayWait;
691 NSDate* waitUntil = nil;
692 if (aMayWait) waitUntil = [NSDate distantFuture];
694 NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
696 EventQueueRef currentEventQueue = GetCurrentEventQueue();
699 mozilla::BackgroundHangMonitor().NotifyWait();
702 // Only call -[NSApp sendEvent:] (and indirectly send user-input events to
703 // Gecko) if aMayWait is true. Tbis ensures most calls to -[NSApp
704 // sendEvent:] happen under nsAppShell::Run(), at the lowest level of
705 // recursion -- thereby making it less likely Gecko will process user-input
706 // events in the wrong order or skip some of them. It also avoids eating
707 // too much CPU in nsBaseAppShell::OnProcessNextEvent() (which calls
708 // us) -- thereby avoiding the starvation of nsIRunnable events in
709 // nsThread::ProcessNextEvent(). For more information see bug 996848.
711 // No autorelease pool is provided here, because OnProcessNextEvent
712 // and AfterProcessNextEvent are responsible for maintaining it.
713 NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
714 "No autorelease pool for native event");
716 if (aMayWait && [[GeckoNSApplication sharedApplication] didLaunch]) {
717 currentMode = [currentRunLoop currentMode];
718 if (!currentMode) currentMode = NSDefaultRunLoopMode;
719 NSEvent* nextEvent = [NSApp nextEventMatchingMask:NSEventMaskAny
724 mozilla::BackgroundHangMonitor().NotifyActivity();
725 [NSApp sendEvent:nextEvent];
726 eventProcessed = true;
729 // In at least 10.15, AcquireFirstMatchingEventInQueue will move 1
730 // CGEvent from the CGEvent queue into the Carbon event queue.
731 // Unfortunately, once an event has been moved to the Carbon event queue
732 // it's no longer a candidate for coalescing. This means that even if we
733 // don't remove the event from the queue, just calling
734 // AcquireFirstMatchingEventInQueue can cause behaviour change. Prior to
735 // bug 1690687 landing, the event that we got from
736 // AcquireFirstMatchingEventInQueue was often our own ApplicationDefined
737 // event. However, once we stopped posting that event on every Gecko
738 // event we're much more likely to get a CGEvent. When we have a high
739 // amount of load on the main thread, we end up alternating between Gecko
740 // events and native events. Without CGEvent coalescing, the native
741 // event events can accumulate in the Carbon event queue which will
742 // manifest as laggy scrolling.
744 eventProcessed = false;
747 // AcquireFirstMatchingEventInQueue() doesn't spin the (native) event
748 // loop, though it does queue up any newly available events from the
750 EventRef currentEvent = AcquireFirstMatchingEventInQueue(
751 currentEventQueue, 0, NULL, kEventQueueOptionsNone);
755 EventAttributes attrs = GetEventAttributes(currentEvent);
756 UInt32 eventKind = GetEventKind(currentEvent);
757 UInt32 eventClass = GetEventClass(currentEvent);
759 ((eventClass == 'appl') || (eventClass == kEventClassAppleEvent) ||
760 ((eventClass == 'cgs ') &&
761 (eventKind != NSEventTypeApplicationDefined)));
762 // If attrs is kEventAttributeUserEvent or kEventAttributeMonitored
763 // (i.e. a user input event), we shouldn't process it here while
764 // aMayWait is false. Likewise if currentEvent will eventually be
765 // turned into an OS-defined Cocoa event, or otherwise needs AppKit
766 // processing. Doing otherwise risks doing too much work here, and
767 // preventing the event from being properly processed by the AppKit
769 if ((attrs != kEventAttributeNone) || osCocoaEvent) {
770 // Since we can't process the next event here (while aMayWait is false),
771 // we want moreEvents to be false on return.
772 eventProcessed = false;
773 // This call to ReleaseEvent() matches a call to RetainEvent() in
774 // AcquireFirstMatchingEventInQueue() above.
775 ReleaseEvent(currentEvent);
778 // This call to RetainEvent() matches a call to ReleaseEvent() in
779 // RemoveEventFromQueue() below.
780 RetainEvent(currentEvent);
781 RemoveEventFromQueue(currentEventQueue, currentEvent);
782 EventTargetRef eventDispatcherTarget = GetEventDispatcherTarget();
783 SendEventToEventTarget(currentEvent, eventDispatcherTarget);
784 // This call to ReleaseEvent() matches a call to RetainEvent() in
785 // AcquireFirstMatchingEventInQueue() above.
786 ReleaseEvent(currentEvent);
787 eventProcessed = true;
790 } while (mRunningEventLoop);
792 if (eventProcessed) {
794 (AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
795 kEventQueueOptionsNone) != NULL);
798 mRunningEventLoop = wasRunningEventLoop;
800 NS_OBJC_END_TRY_IGNORE_BLOCK;
803 nsChildView::UpdateCurrentInputEventCount();
809 // Attempt to work around bug 1801419 by loading and initializing the
810 // SidecarCore private framework as the app shell starts up. This normally
811 // happens on demand, the first time any Cmd-key combination is pressed, and
812 // sometimes triggers crashes, caused by an Apple bug. We hope that doing it
813 // now, and somewhat more simply, will avoid the crashes. They happen
814 // (intermittently) when SidecarCore code tries to access C strings in special
815 // sections of its own __TEXT segment, and triggers fatal page faults (which
816 // is Apple's bug). Many of the C strings are part of the Objective-C class
817 // hierarchy (class names and so forth). We hope that adding them to this
818 // hierarchy will "pin" them in place -- so they'll rarely, if ever, be paged
819 // out again. Bug 1801419's crashes happen much more often on macOS 13
820 // (Ventura) than on other versions of macOS. So we only use this hack on
822 static void PinSidecarCoreTextCStringSections() {
824 "/System/Library/PrivateFrameworks/SidecarCore.framework/SidecarCore",
829 // Explicitly run the most basic part of the initialization code that
830 // normally runs automatically on the first Cmd-key combination.
831 Class displayManagerClass = NSClassFromString(@"SidecarDisplayManager");
832 if ([displayManagerClass respondsToSelector:@selector(sharedManager)]) {
834 [displayManagerClass performSelector:@selector(sharedManager)];
835 if ([sharedManager respondsToSelector:@selector(devices)]) {
836 [sharedManager performSelector:@selector(devices)];
843 // Overrides the base class's Run() method to call [NSApp run] (which spins
844 // the native run loop until the application quits). Since (unlike the base
845 // class's Run() method) we don't process any Gecko events here, they need
846 // to be processed elsewhere (in NativeEventCallback(), called from
847 // ProcessGeckoEvents()).
849 // Camino called [NSApp run] on its own (via NSApplicationMain()), and so
850 // didn't call nsAppShell::Run().
854 nsAppShell::Run(void) {
855 NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
856 if (mStarted || mTerminated) return NS_OK;
860 if (XRE_IsParentProcess()) {
861 if (nsCocoaFeatures::OnVenturaOrLater()) {
862 PinSidecarCoreTextCStringSections();
864 AddScreenWakeLockListener();
867 // We use the native Gecko event loop in content processes.
869 if (XRE_UseNativeEventProcessing()) {
870 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
872 NS_OBJC_END_TRY_IGNORE_BLOCK;
874 rv = nsBaseAppShell::Run();
877 if (XRE_IsParentProcess()) {
878 RemoveScreenWakeLockListener();
885 nsAppShell::Exit(void) {
886 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
888 // This method is currently called more than once -- from (according to
889 // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
890 // XPCOM shutdown notification that nsBaseAppShell has registered to
891 // receive. So we need to ensure that multiple calls won't break anything.
898 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
899 nsSandboxViolationSink::Stop();
902 // Quoting from Apple's doc on the [NSApplication stop:] method (from their
903 // doc on the NSApplication class): "If this method is invoked during a
904 // modal event loop, it will break that loop but not the main event loop."
905 // nsAppShell::Exit() shouldn't be called from a modal event loop. So if
906 // it is we complain about it (to users of debug builds) and call [NSApp
907 // stop:] one extra time. (I'm not sure if modal event loops can be nested
908 // -- Apple's docs don't say one way or the other. But the return value
909 // of [NSApp _isRunningModal] doesn't change immediately after a call to
910 // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
912 BOOL cocoaModal = [NSApp _isRunningModal];
913 NS_ASSERTION(!cocoaModal,
914 "Don't call nsAppShell::Exit() from a modal event loop!");
915 if (cocoaModal) [NSApp stop:nullptr];
916 [NSApp stop:nullptr];
918 // A call to Exit() just after a call to ScheduleNativeEventCallback()
919 // prevents the (normally) matching call to ProcessGeckoEvents() from
920 // happening. If we've been called from ProcessGeckoEvents() (as usually
921 // happens), we take care of it there. But if we have an unbalanced call
922 // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
923 // stack, we need to take care of the problem here.
924 if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
925 int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
926 while (releaseCount-- > 0) NS_RELEASE_THIS();
929 return nsBaseAppShell::Exit();
931 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
934 // OnProcessNextEvent
936 // This nsIThreadObserver method is called prior to processing an event.
937 // Set up an autorelease pool that will service any autoreleased Cocoa
938 // objects during this event. This includes native events processed by
939 // ProcessNextNativeEvent. The autorelease pool will be popped by
940 // AfterProcessNextEvent, it is important for these two methods to be
945 nsAppShell::OnProcessNextEvent(nsIThreadInternal* aThread, bool aMayWait) {
946 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
948 NS_ASSERTION(mAutoreleasePools,
949 "No stack on which to store autorelease pool");
951 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
952 ::CFArrayAppendValue(mAutoreleasePools, pool);
954 return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait);
956 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
959 // AfterProcessNextEvent
961 // This nsIThreadObserver method is called after event processing is complete.
962 // The Cocoa implementation cleans up the autorelease pool create by the
963 // previous OnProcessNextEvent call.
967 nsAppShell::AfterProcessNextEvent(nsIThreadInternal* aThread,
968 bool aEventWasProcessed) {
969 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
971 CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
973 NS_ASSERTION(mAutoreleasePools && count,
974 "Processed an event, but there's no autorelease pool?");
976 const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*>(
977 ::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
978 ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
981 return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed);
983 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
986 void nsAppShell::InitMemoryPressureObserver() {
987 // Testing shows that sometimes the memory pressure event is not fired for
988 // over a minute after the memory pressure change is reflected in sysctl
989 // values. Hence this may need to be augmented with polling of the memory
990 // pressure sysctls for lower latency reactions to OS memory pressure. This
991 // was also observed when using DISPATCH_QUEUE_PRIORITY_HIGH.
992 mMemoryPressureSource = dispatch_source_create(
993 DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0,
994 DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN |
995 DISPATCH_MEMORYPRESSURE_CRITICAL,
996 dispatch_get_main_queue());
998 dispatch_source_set_event_handler(mMemoryPressureSource, ^{
999 dispatch_source_memorypressure_flags_t pressureLevel =
1000 dispatch_source_get_data(mMemoryPressureSource);
1001 nsAppShell::OnMemoryPressureChanged(pressureLevel);
1004 dispatch_resume(mMemoryPressureSource);
1006 // Initialize the memory watcher.
1007 RefPtr<mozilla::nsAvailableMemoryWatcherBase> watcher(
1008 nsAvailableMemoryWatcherBase::GetSingleton());
1011 void nsAppShell::OnMemoryPressureChanged(
1012 dispatch_source_memorypressure_flags_t aPressureLevel) {
1013 // The memory pressure dispatch source is created (above) with
1014 // dispatch_get_main_queue() which always fires on the main thread.
1015 MOZ_ASSERT(NS_IsMainThread());
1017 MacMemoryPressureLevel geckoPressureLevel;
1018 switch (aPressureLevel) {
1019 case DISPATCH_MEMORYPRESSURE_NORMAL:
1020 geckoPressureLevel = MacMemoryPressureLevel::Value::eNormal;
1022 case DISPATCH_MEMORYPRESSURE_WARN:
1023 geckoPressureLevel = MacMemoryPressureLevel::Value::eWarning;
1025 case DISPATCH_MEMORYPRESSURE_CRITICAL:
1026 geckoPressureLevel = MacMemoryPressureLevel::Value::eCritical;
1029 geckoPressureLevel = MacMemoryPressureLevel::Value::eUnexpected;
1032 RefPtr<mozilla::nsAvailableMemoryWatcherBase> watcher(
1033 nsAvailableMemoryWatcherBase::GetSingleton());
1034 watcher->OnMemoryPressureChanged(geckoPressureLevel);
1037 // AppShellDelegate implementation
1039 @implementation AppShellDelegate
1040 // initWithAppShell:
1042 // Constructs the AppShellDelegate object
1043 - (id)initWithAppShell:(nsAppShell*)aAppShell {
1044 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1046 if ((self = [self init])) {
1047 mAppShell = aAppShell;
1049 [[NSNotificationCenter defaultCenter]
1051 selector:@selector(applicationWillTerminate:)
1052 name:NSApplicationWillTerminateNotification
1054 [[NSNotificationCenter defaultCenter]
1056 selector:@selector(applicationDidBecomeActive:)
1057 name:NSApplicationDidBecomeActiveNotification
1059 [[NSNotificationCenter defaultCenter]
1061 selector:@selector(timezoneChanged:)
1062 name:NSSystemTimeZoneDidChangeNotification
1068 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
1072 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1074 [[NSNotificationCenter defaultCenter] removeObserver:self];
1075 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
1078 NS_OBJC_END_TRY_IGNORE_BLOCK;
1081 // applicationWillTerminate:
1083 // Notify the nsAppShell that native event processing should be discontinued.
1084 - (void)applicationWillTerminate:(NSNotification*)aNotification {
1085 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1087 mAppShell->WillTerminate();
1089 NS_OBJC_END_TRY_IGNORE_BLOCK;
1092 // applicationDidBecomeActive
1094 // Make sure TextInputHandler::sLastModifierState is updated when we become
1095 // active (since we won't have received [ChildView flagsChanged:] messages
1097 - (void)applicationDidBecomeActive:(NSNotification*)aNotification {
1098 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1100 // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
1101 // to worry about getting an NSInternalInconsistencyException here.
1102 NSEvent* currentEvent = [NSApp currentEvent];
1104 TextInputHandler::sLastModifierState =
1105 [currentEvent modifierFlags] &
1106 NSEventModifierFlagDeviceIndependentFlagsMask;
1109 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
1110 if (observerService) {
1111 observerService->NotifyObservers(
1112 nullptr, NS_WIDGET_MAC_APP_ACTIVATE_OBSERVER_TOPIC, nullptr);
1115 NS_OBJC_END_TRY_IGNORE_BLOCK;
1118 - (void)timezoneChanged:(NSNotification*)aNotification {
1119 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1121 nsBaseAppShell::OnSystemTimezoneChange();
1123 NS_OBJC_END_TRY_IGNORE_BLOCK;
1128 // We hook terminate: in order to make OS-initiated termination work nicely
1129 // with Gecko's shutdown sequence. (Two ways to trigger OS-initiated
1130 // termination: 1) Quit from the Dock menu; 2) Log out from (or shut down)
1131 // your computer while the browser is active.)
1132 @interface NSApplication (MethodSwizzling)
1133 - (void)nsAppShell_NSApplication_terminate:(id)sender;
1136 @implementation NSApplication (MethodSwizzling)
1138 // Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
1139 // has returned NSTerminateNow. This method "subclasses" and replaces the
1140 // OS's original implementation. The only thing the orginal method does which
1141 // we need is that it posts NSApplicationWillTerminateNotification. Everything
1142 // else is unneeded (because it's handled elsewhere), or actively interferes
1143 // with Gecko's shutdown sequence. For example the original terminate: method
1144 // causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
1145 // above), which means that nothing runs after the call to nsAppStartup::Run()
1146 // in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
1147 // and NS_ShutdownXPCOM() never get called.
1148 - (void)nsAppShell_NSApplication_terminate:(id)sender {
1149 [[NSNotificationCenter defaultCenter]
1150 postNotificationName:NSApplicationWillTerminateNotification