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