|
1 /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 /* |
|
7 * Runs the main native Cocoa run loop, interrupting it as needed to process |
|
8 * Gecko events. |
|
9 */ |
|
10 |
|
11 #import <Cocoa/Cocoa.h> |
|
12 #include <dlfcn.h> |
|
13 |
|
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" |
|
37 |
|
38 #include "npapi.h" |
|
39 |
|
40 using namespace mozilla::widget; |
|
41 |
|
42 // defined in nsCocoaWindow.mm |
|
43 extern int32_t gXULModalLevel; |
|
44 |
|
45 static bool gAppShellMethodsSwizzled = false; |
|
46 // List of current Cocoa app-modal windows (nested if more than one). |
|
47 nsCocoaAppModalWindowList *gCocoaAppModalWindowList = NULL; |
|
48 |
|
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 } |
|
56 |
|
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); |
|
62 |
|
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 } |
|
72 |
|
73 NS_ERROR("PopCocoa() called without matching call to PushCocoa()!"); |
|
74 return NS_ERROR_FAILURE; |
|
75 } |
|
76 |
|
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 } |
|
84 |
|
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); |
|
90 |
|
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 } |
|
100 |
|
101 NS_ERROR("PopGecko() called without matching call to PushGecko()!"); |
|
102 return NS_ERROR_FAILURE; |
|
103 } |
|
104 |
|
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; |
|
119 |
|
120 NSModalSession currentSession = nil; |
|
121 |
|
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 } |
|
129 |
|
130 return currentSession; |
|
131 } |
|
132 |
|
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; |
|
138 |
|
139 nsCocoaAppModalWindowListItem &topItem = mList.ElementAt(mList.Length() - 1); |
|
140 |
|
141 return (topItem.mWidget != nullptr); |
|
142 } |
|
143 |
|
144 @implementation GeckoNSApplication |
|
145 |
|
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 } |
|
156 |
|
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 } |
|
168 |
|
169 @end |
|
170 |
|
171 |
|
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 } |
|
182 |
|
183 - (id)initWithAppShell:(nsAppShell*)aAppShell; |
|
184 - (void)applicationWillTerminate:(NSNotification*)aNotification; |
|
185 - (void)beginMenuTracking:(NSNotification*)aNotification; |
|
186 @end |
|
187 |
|
188 // nsAppShell implementation |
|
189 |
|
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 } |
|
202 |
|
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 } |
|
221 |
|
222 nsAppShell::~nsAppShell() |
|
223 { |
|
224 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
225 |
|
226 if (mCFRunLoop) { |
|
227 if (mCFRunLoopSource) { |
|
228 ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource, |
|
229 kCFRunLoopCommonModes); |
|
230 ::CFRelease(mCFRunLoopSource); |
|
231 } |
|
232 ::CFRelease(mCFRunLoop); |
|
233 } |
|
234 |
|
235 if (mAutoreleasePools) { |
|
236 NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0, |
|
237 "nsAppShell destroyed without popping all autorelease pools"); |
|
238 ::CFRelease(mAutoreleasePools); |
|
239 } |
|
240 |
|
241 [mDelegate release]; |
|
242 |
|
243 NS_OBJC_END_TRY_ABORT_BLOCK |
|
244 } |
|
245 |
|
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); |
|
249 |
|
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; |
|
260 |
|
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]; |
|
264 |
|
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); |
|
271 |
|
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); |
|
276 |
|
277 nibFile->AppendNative(NS_LITERAL_CSTRING("res")); |
|
278 nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib")); |
|
279 |
|
280 nsAutoCString nibPath; |
|
281 rv = nibFile->GetNativePath(nibPath); |
|
282 NS_ENSURE_SUCCESS(rv, rv); |
|
283 |
|
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()]; |
|
296 |
|
297 mDelegate = [[AppShellDelegate alloc] initWithAppShell:this]; |
|
298 NS_ENSURE_STATE(mDelegate); |
|
299 |
|
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. |
|
302 |
|
303 mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop]; |
|
304 NS_ENSURE_STATE(mCFRunLoop); |
|
305 ::CFRetain(mCFRunLoop); |
|
306 |
|
307 CFRunLoopSourceContext context; |
|
308 bzero(&context, sizeof(context)); |
|
309 // context.version = 0; |
|
310 context.info = this; |
|
311 context.perform = ProcessGeckoEvents; |
|
312 |
|
313 mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); |
|
314 NS_ENSURE_STATE(mCFRunLoopSource); |
|
315 |
|
316 ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes); |
|
317 |
|
318 rv = nsBaseAppShell::Init(); |
|
319 |
|
320 #ifndef __LP64__ |
|
321 TextInputHandler::InstallPluginKeyEventsHandler(); |
|
322 #endif |
|
323 |
|
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 } |
|
338 |
|
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 } |
|
348 |
|
349 [localPool release]; |
|
350 |
|
351 return rv; |
|
352 |
|
353 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; |
|
354 } |
|
355 |
|
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); |
|
372 |
|
373 if (self->mRunningEventLoop) { |
|
374 self->mRunningEventLoop = false; |
|
375 |
|
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 } |
|
399 |
|
400 if (self->mSuspendNativeCount <= 0) { |
|
401 ++self->mNativeEventCallbackDepth; |
|
402 self->NativeEventCallback(); |
|
403 --self->mNativeEventCallbackDepth; |
|
404 } else { |
|
405 self->mSkippedNativeCallback = true; |
|
406 } |
|
407 |
|
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]; |
|
420 |
|
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 } |
|
460 |
|
461 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
462 } |
|
463 |
|
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; |
|
478 |
|
479 // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called |
|
480 // from [MacApplicationDelegate applicationShouldTerminate:]) gets run. |
|
481 NS_ProcessPendingEvents(NS_GetCurrentThread()); |
|
482 |
|
483 mTerminated = true; |
|
484 } |
|
485 |
|
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; |
|
510 |
|
511 if (mTerminated) |
|
512 return; |
|
513 |
|
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); |
|
519 |
|
520 // This will invoke ProcessGeckoEvents on the main thread. |
|
521 ::CFRunLoopSourceSignal(mCFRunLoopSource); |
|
522 ::CFRunLoopWakeUp(mCFRunLoop); |
|
523 |
|
524 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
525 } |
|
526 |
|
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; |
|
544 |
|
545 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
546 |
|
547 bool eventProcessed = false; |
|
548 NSString* currentMode = nil; |
|
549 |
|
550 if (mTerminated) |
|
551 return false; |
|
552 |
|
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; |
|
565 |
|
566 bool wasRunningEventLoop = mRunningEventLoop; |
|
567 mRunningEventLoop = aMayWait; |
|
568 NSDate* waitUntil = nil; |
|
569 if (aMayWait) |
|
570 waitUntil = [NSDate distantFuture]; |
|
571 |
|
572 NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop]; |
|
573 |
|
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"); |
|
579 |
|
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.) |
|
592 |
|
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:...]. |
|
601 |
|
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.) |
|
617 |
|
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; |
|
623 |
|
624 NSEvent* nextEvent = nil; |
|
625 |
|
626 if (aMayWait) { |
|
627 mozilla::HangMonitor::Suspend(); |
|
628 } |
|
629 |
|
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(); |
|
655 |
|
656 mozilla::HangMonitor::NotifyActivity(); |
|
657 |
|
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); |
|
684 |
|
685 if (eventProcessed && (mHadMoreEventsCount < kHadMoreEventsCountMax)) { |
|
686 moreEvents = ([NSApp nextEventMatchingMask:NSAnyEventMask |
|
687 untilDate:nil |
|
688 inMode:currentMode |
|
689 dequeue:NO] != nil); |
|
690 } |
|
691 |
|
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 } |
|
699 |
|
700 mRunningEventLoop = wasRunningEventLoop; |
|
701 |
|
702 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
703 |
|
704 if (!moreEvents) { |
|
705 nsChildView::UpdateCurrentInputEventCount(); |
|
706 } |
|
707 |
|
708 return moreEvents; |
|
709 } |
|
710 |
|
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 } |
|
739 |
|
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; |
|
758 |
|
759 mStarted = true; |
|
760 NS_OBJC_TRY_ABORT([NSApp run]); |
|
761 |
|
762 return NS_OK; |
|
763 } |
|
764 |
|
765 NS_IMETHODIMP |
|
766 nsAppShell::Exit(void) |
|
767 { |
|
768 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; |
|
769 |
|
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 } |
|
779 |
|
780 mTerminated = true; |
|
781 |
|
782 delete gCocoaAppModalWindowList; |
|
783 gCocoaAppModalWindowList = NULL; |
|
784 |
|
785 #ifndef __LP64__ |
|
786 TextInputHandler::RemovePluginKeyEventsHandler(); |
|
787 #endif |
|
788 |
|
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]; |
|
805 |
|
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 } |
|
817 |
|
818 return nsBaseAppShell::Exit(); |
|
819 |
|
820 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; |
|
821 } |
|
822 |
|
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; |
|
838 |
|
839 mRecursionDepth = aRecursionDepth; |
|
840 |
|
841 NS_ASSERTION(mAutoreleasePools, |
|
842 "No stack on which to store autorelease pool"); |
|
843 |
|
844 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; |
|
845 ::CFArrayAppendValue(mAutoreleasePools, pool); |
|
846 |
|
847 return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait, aRecursionDepth); |
|
848 |
|
849 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; |
|
850 } |
|
851 |
|
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; |
|
865 |
|
866 mRecursionDepth = aRecursionDepth; |
|
867 |
|
868 CFIndex count = ::CFArrayGetCount(mAutoreleasePools); |
|
869 |
|
870 NS_ASSERTION(mAutoreleasePools && count, |
|
871 "Processed an event, but there's no autorelease pool?"); |
|
872 |
|
873 const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*> |
|
874 (::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1)); |
|
875 ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1); |
|
876 [pool release]; |
|
877 |
|
878 return nsBaseAppShell::AfterProcessNextEvent(aThread, aRecursionDepth, |
|
879 aEventWasProcessed); |
|
880 |
|
881 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; |
|
882 } |
|
883 |
|
884 |
|
885 // AppShellDelegate implementation |
|
886 |
|
887 |
|
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; |
|
895 |
|
896 if ((self = [self init])) { |
|
897 mAppShell = aAppShell; |
|
898 |
|
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 } |
|
912 |
|
913 return self; |
|
914 |
|
915 NS_OBJC_END_TRY_ABORT_BLOCK_NIL; |
|
916 } |
|
917 |
|
918 - (void)dealloc |
|
919 { |
|
920 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
921 |
|
922 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
|
923 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; |
|
924 [super dealloc]; |
|
925 |
|
926 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
927 } |
|
928 |
|
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; |
|
935 |
|
936 mAppShell->WillTerminate(); |
|
937 |
|
938 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
939 } |
|
940 |
|
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; |
|
949 |
|
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 } |
|
957 |
|
958 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
959 } |
|
960 |
|
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; |
|
969 |
|
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 } |
|
977 |
|
978 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
979 } |
|
980 |
|
981 @end |
|
982 |
|
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 |
|
999 |
|
1000 @implementation NSApplication (MethodSwizzling) |
|
1001 |
|
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 } |
|
1013 |
|
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 } |
|
1026 |
|
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 } |
|
1042 |
|
1043 @end |