Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / chrome_browser_application_mac.mm
blob1949c5ed8f1e39955de134ff754cf35f633b485d
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/chrome_browser_application_mac.h"
7 #include <objc/objc-exception.h>
9 #import "base/auto_reset.h"
10 #include "base/command_line.h"
11 #include "base/debug/crash_logging.h"
12 #include "base/debug/stack_trace.h"
13 #import "base/logging.h"
14 #include "base/mac/call_with_eh_frame.h"
15 #import "base/mac/scoped_nsobject.h"
16 #import "base/mac/scoped_objc_class_swizzler.h"
17 #import "base/metrics/histogram.h"
18 #include "base/profiler/scoped_tracker.h"
19 #include "base/strings/stringprintf.h"
20 #import "base/strings/sys_string_conversions.h"
21 #import "chrome/browser/app_controller_mac.h"
22 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/common/crash_keys.h"
25 #import "components/crash/core/common/objc_zombie.h"
26 #include "content/public/browser/browser_accessibility_state.h"
27 #include "content/public/browser/render_view_host.h"
28 #include "content/public/browser/web_contents.h"
30 namespace chrome_browser_application_mac {
32 // Maximum number of known named exceptions we'll support.  There is
33 // no central registration, but I only find about 75 possibilities in
34 // the system frameworks, and many of them are probably not
35 // interesting to track in aggregate (those relating to distributed
36 // objects, for instance).
37 const size_t kKnownNSExceptionCount = 25;
39 const size_t kUnknownNSException = kKnownNSExceptionCount;
41 size_t BinForException(NSException* exception) {
42   // A list of common known exceptions.  The list position will
43   // determine where they live in the histogram, so never move them
44   // around, only add to the end.
45   static NSString* const kKnownNSExceptionNames[] = {
46     // Grab-bag exception, not very common.  CFArray (or other
47     // container) mutated while being enumerated is one case seen in
48     // production.
49     NSGenericException,
51     // Out-of-range on NSString or NSArray.  Quite common.
52     NSRangeException,
54     // Invalid arg to method, unrecognized selector.  Quite common.
55     NSInvalidArgumentException,
57     // malloc() returned null in object creation, I think.  Turns out
58     // to be very uncommon in production, because of the OOM killer.
59     NSMallocException,
61     // This contains things like windowserver errors, trying to draw
62     // views which aren't in windows, unable to read nib files.  By
63     // far the most common exception seen on the crash server.
64     NSInternalInconsistencyException,
66     nil
67   };
69   // Make sure our array hasn't outgrown our abilities to track it.
70   DCHECK_LE(arraysize(kKnownNSExceptionNames), kKnownNSExceptionCount);
72   NSString* name = [exception name];
73   for (int i = 0; kKnownNSExceptionNames[i]; ++i) {
74     if (name == kKnownNSExceptionNames[i]) {
75       return i;
76     }
77   }
78   return kUnknownNSException;
81 void RecordExceptionWithUma(NSException* exception) {
82   UMA_HISTOGRAM_ENUMERATION("OSX.NSException",
83       BinForException(exception), kUnknownNSException);
86 namespace {
88 objc_exception_preprocessor g_next_preprocessor = nullptr;
90 id ExceptionPreprocessor(id exception) {
91   static bool seen_first_exception = false;
93   RecordExceptionWithUma(exception);
95   const char* const kExceptionKey =
96       seen_first_exception ? crash_keys::mac::kLastNSException
97                            : crash_keys::mac::kFirstNSException;
98   NSString* value = [NSString stringWithFormat:@"%@ reason %@",
99       [exception name], [exception reason]];
100   base::debug::SetCrashKeyValue(kExceptionKey, [value UTF8String]);
102   const char* const kExceptionTraceKey =
103       seen_first_exception ? crash_keys::mac::kLastNSExceptionTrace
104                            : crash_keys::mac::kFirstNSExceptionTrace;
105   // This exception preprocessor runs prior to the one in libobjc, which sets
106   // the -[NSException callStackReturnAddresses].
107   base::debug::SetCrashKeyToStackTrace(kExceptionTraceKey,
108                                        base::debug::StackTrace());
110   seen_first_exception = true;
112   // Forward to the original version.
113   if (g_next_preprocessor)
114     return g_next_preprocessor(exception);
115   return exception;
118 }  // namespace
120 void RegisterBrowserCrApp() {
121   [BrowserCrApplication sharedApplication];
124 void Terminate() {
125   [NSApp terminate:nil];
128 void CancelTerminate() {
129   [NSApp cancelTerminate:nil];
132 }  // namespace chrome_browser_application_mac
134 // These methods are being exposed for the purposes of overriding.
135 // Used to determine when a Panel window can become the key window.
136 @interface NSApplication (PanelsCanBecomeKey)
137 - (void)_cycleWindowsReversed:(BOOL)arg1;
138 - (id)_removeWindow:(NSWindow*)window;
139 - (id)_setKeyWindow:(NSWindow*)window;
140 @end
142 @interface BrowserCrApplication (PrivateInternal)
144 // This must be called under the protection of previousKeyWindowsLock_.
145 - (void)removePreviousKeyWindow:(NSWindow*)window;
147 @end
149 @implementation BrowserCrApplication
151 + (void)initialize {
152   // Turn all deallocated Objective-C objects into zombies, keeping
153   // the most recent 10,000 of them on the treadmill.
154   ObjcEvilDoers::ZombieEnable(true, 10000);
156   if (!chrome_browser_application_mac::g_next_preprocessor) {
157     chrome_browser_application_mac::g_next_preprocessor =
158         objc_setExceptionPreprocessor(
159             &chrome_browser_application_mac::ExceptionPreprocessor);
160   }
163 - (id)init {
164   self = [super init];
166   // Sanity check to alert if overridden methods are not supported.
167   DCHECK([NSApplication
168       instancesRespondToSelector:@selector(_cycleWindowsReversed:)]);
169   DCHECK([NSApplication
170       instancesRespondToSelector:@selector(_removeWindow:)]);
171   DCHECK([NSApplication
172       instancesRespondToSelector:@selector(_setKeyWindow:)]);
174   return self;
177 // Initialize NSApplication using the custom subclass.  Check whether NSApp
178 // was already initialized using another class, because that would break
179 // some things.
180 + (NSApplication*)sharedApplication {
181   NSApplication* app = [super sharedApplication];
183   // +sharedApplication initializes the global NSApp, so if a specific
184   // NSApplication subclass is requested, require that to be the one
185   // delivered.  The practical effect is to require a consistent NSApp
186   // across the executable.
187   CHECK([NSApp isKindOfClass:self])
188       << "NSApp must be of type " << [[self className] UTF8String]
189       << ", not " << [[NSApp className] UTF8String];
191   // If the message loop was initialized before NSApp is setup, the
192   // message pump will be setup incorrectly.  Failing this implies
193   // that RegisterBrowserCrApp() should be called earlier.
194   CHECK(base::MessagePumpMac::UsingCrApp())
195       << "MessagePumpMac::Create() is using the wrong pump implementation"
196       << " for " << [[self className] UTF8String];
198   return app;
201 ////////////////////////////////////////////////////////////////////////////////
202 // HISTORICAL COMMENT (by viettrungluu, from
203 // http://codereview.chromium.org/1520006 with mild editing):
205 // A quick summary of the state of things (before the changes to shutdown):
207 // Currently, we are totally hosed (put in a bad state in which Cmd-W does the
208 // wrong thing, and which will probably eventually lead to a crash) if we begin
209 // quitting but termination is aborted for some reason.
211 // I currently know of two ways in which termination can be aborted:
212 // (1) Common case: a window has an onbeforeunload handler which pops up a
213 //     "leave web page" dialog, and the user answers "no, don't leave".
214 // (2) Uncommon case: popups are enabled (in Content Settings, i.e., the popup
215 //     blocker is disabled), and some nasty web page pops up a new window on
216 //     closure.
218 // I don't know of other ways in which termination can be aborted, but they may
219 // exist (or may be added in the future, for that matter).
221 // My CL [see above] does the following:
222 // a. Should prevent being put in a bad state (which breaks Cmd-W and leads to
223 //    crash) under all circumstances.
224 // b. Should completely handle (1) properly.
225 // c. Doesn't (yet) handle (2) properly and puts it in a weird state (but not
226 //    that bad).
227 // d. Any other ways of aborting termination would put it in that weird state.
229 // c. can be fixed by having the global flag reset on browser creation or
230 // similar (and doing so might also fix some possible d.'s as well). I haven't
231 // done this yet since I haven't thought about it carefully and since it's a
232 // corner case.
234 // The weird state: a state in which closing the last window quits the browser.
235 // This might be a bit annoying, but it's not dangerous in any way.
236 ////////////////////////////////////////////////////////////////////////////////
238 // |-terminate:| is the entry point for orderly "quit" operations in Cocoa. This
239 // includes the application menu's quit menu item and keyboard equivalent, the
240 // application's dock icon menu's quit menu item, "quit" (not "force quit") in
241 // the Activity Monitor, and quits triggered by user logout and system restart
242 // and shutdown.
244 // The default |-terminate:| implementation ends the process by calling exit(),
245 // and thus never leaves the main run loop. This is unsuitable for Chrome since
246 // Chrome depends on leaving the main run loop to perform an orderly shutdown.
247 // We support the normal |-terminate:| interface by overriding the default
248 // implementation. Our implementation, which is very specific to the needs of
249 // Chrome, works by asking the application delegate to terminate using its
250 // |-tryToTerminateApplication:| method.
252 // |-tryToTerminateApplication:| differs from the standard
253 // |-applicationShouldTerminate:| in that no special event loop is run in the
254 // case that immediate termination is not possible (e.g., if dialog boxes
255 // allowing the user to cancel have to be shown). Instead, this method sets a
256 // flag and tries to close all browsers. This flag causes the closure of the
257 // final browser window to begin actual tear-down of the application.
258 // Termination is cancelled by resetting this flag. The standard
259 // |-applicationShouldTerminate:| is not supported, and code paths leading to it
260 // must be redirected.
262 // When the last browser has been destroyed, the BrowserList calls
263 // chrome::OnAppExiting(), which is the point of no return. That will cause
264 // the NSApplicationWillTerminateNotification to be posted, which ends the
265 // NSApplication event loop, so final post- MessageLoop::Run() work is done
266 // before exiting.
267 - (void)terminate:(id)sender {
268   AppController* appController = static_cast<AppController*>([NSApp delegate]);
269   [appController tryToTerminateApplication:self];
270   // Return, don't exit. The application is responsible for exiting on its own.
273 - (void)cancelTerminate:(id)sender {
274   AppController* appController = static_cast<AppController*>([NSApp delegate]);
275   [appController stopTryingToTerminateApplication:self];
278 - (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)sender {
279   // The Dock menu contains an automagic section where you can select
280   // amongst open windows.  If a window is closed via JavaScript while
281   // the menu is up, the menu item for that window continues to exist.
282   // When a window is selected this method is called with the
283   // now-freed window as |aTarget|.  Short-circuit the call if
284   // |aTarget| is not a valid window.
285   if (anAction == @selector(_selectWindow:)) {
286     // Not using -[NSArray containsObject:] because |aTarget| may be a
287     // freed object.
288     BOOL found = NO;
289     for (NSWindow* window in [self windows]) {
290       if (window == aTarget) {
291         found = YES;
292         break;
293       }
294     }
295     if (!found) {
296       return NO;
297     }
298   }
300   // When a Cocoa control is wired to a freed object, we get crashers
301   // in the call to |super| with no useful information in the
302   // backtrace.  Attempt to add some useful information.
304   // If the action is something generic like -commandDispatch:, then
305   // the tag is essential.
306   NSInteger tag = 0;
307   if ([sender isKindOfClass:[NSControl class]]) {
308     tag = [sender tag];
309     if (tag == 0 || tag == -1) {
310       tag = [sender selectedTag];
311     }
312   } else if ([sender isKindOfClass:[NSMenuItem class]]) {
313     tag = [sender tag];
314   }
316   NSString* actionString = NSStringFromSelector(anAction);
317   std::string value = base::StringPrintf("%s tag %ld sending %s to %p",
318       [[sender className] UTF8String],
319       static_cast<long>(tag),
320       [actionString UTF8String],
321       aTarget);
322   base::debug::ScopedCrashKey key(crash_keys::mac::kSendAction, value);
324   return [super sendAction:anAction to:aTarget from:sender];
327 - (BOOL)isHandlingSendEvent {
328   return handlingSendEvent_;
331 - (void)setHandlingSendEvent:(BOOL)handlingSendEvent {
332   handlingSendEvent_ = handlingSendEvent;
335 - (void)sendEvent:(NSEvent*)event {
336   base::mac::CallWithEHFrame(^{
337     // tracked_objects::ScopedTracker does not support parameterized
338     // instrumentations, so a big switch with each bunch instrumented is
339     // required.
340     switch (event.type) {
341       case NSLeftMouseDown:
342       case NSRightMouseDown: {
343         // In kiosk mode, we want to prevent context menus from appearing,
344         // so simply discard menu-generating events instead of passing them
345         // along.
346         bool kioskMode = base::CommandLine::ForCurrentProcess()->HasSwitch(
347             switches::kKioskMode);
348         bool ctrlDown = [event modifierFlags] & NSControlKeyMask;
349         if (kioskMode && ([event type] == NSRightMouseDown || ctrlDown))
350           break;
351       }
352       // FALL THROUGH
353       case NSLeftMouseUp:
354       case NSRightMouseUp:
355       case NSMouseMoved:
356       case NSLeftMouseDragged:
357       case NSRightMouseDragged:
358       case NSMouseEntered:
359       case NSMouseExited:
360       case NSOtherMouseDown:
361       case NSOtherMouseUp:
362       case NSOtherMouseDragged: {
363         tracked_objects::ScopedTracker tracking_profile(
364             FROM_HERE_WITH_EXPLICIT_FUNCTION(
365                 "463272 -[BrowserCrApplication sendEvent:] Mouse"));
366         base::mac::ScopedSendingEvent sendingEventScoper;
367         [super sendEvent:event];
368         break;
369       }
371       case NSKeyDown:
372       case NSKeyUp: {
373         tracked_objects::ScopedTracker tracking_profile(
374             FROM_HERE_WITH_EXPLICIT_FUNCTION(
375                 "463272 -[BrowserCrApplication sendEvent:] Key"));
376         base::mac::ScopedSendingEvent sendingEventScoper;
377         [super sendEvent:event];
378         break;
379       }
381       case NSScrollWheel: {
382         tracked_objects::ScopedTracker tracking_profile(
383             FROM_HERE_WITH_EXPLICIT_FUNCTION(
384                 "463272 -[BrowserCrApplication sendEvent:] ScrollWheel"));
385         base::mac::ScopedSendingEvent sendingEventScoper;
386         [super sendEvent:event];
387         break;
388       }
390       case NSEventTypeGesture:
391       case NSEventTypeMagnify:
392       case NSEventTypeSwipe:
393       case NSEventTypeRotate:
394       case NSEventTypeBeginGesture:
395       case NSEventTypeEndGesture: {
396         tracked_objects::ScopedTracker tracking_profile(
397             FROM_HERE_WITH_EXPLICIT_FUNCTION(
398                 "463272 -[BrowserCrApplication sendEvent:] Gesture"));
399         base::mac::ScopedSendingEvent sendingEventScoper;
400         [super sendEvent:event];
401         break;
402       }
404       case NSAppKitDefined: {
405         tracked_objects::ScopedTracker tracking_profile(
406             FROM_HERE_WITH_EXPLICIT_FUNCTION(
407                 "463272 -[BrowserCrApplication sendEvent:] AppKit"));
408         base::mac::ScopedSendingEvent sendingEventScoper;
409         [super sendEvent:event];
410         break;
411       }
413       case NSSystemDefined: {
414         tracked_objects::ScopedTracker tracking_profile(
415             FROM_HERE_WITH_EXPLICIT_FUNCTION(
416                 "463272 -[BrowserCrApplication sendEvent:] System"));
417         base::mac::ScopedSendingEvent sendingEventScoper;
418         [super sendEvent:event];
419         break;
420       }
422       default: {
423         tracked_objects::ScopedTracker tracking_profile(
424             FROM_HERE_WITH_EXPLICIT_FUNCTION(
425                 "463272 -[BrowserCrApplication sendEvent:] Other"));
426         base::mac::ScopedSendingEvent sendingEventScoper;
427         [super sendEvent:event];
428       }
429     }
430   });
433 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
434   // This is an undocument attribute that's set when VoiceOver is turned on/off.
435   if ([attribute isEqualToString:@"AXEnhancedUserInterface"]) {
436     content::BrowserAccessibilityState* accessibility_state =
437         content::BrowserAccessibilityState::GetInstance();
438     if ([value intValue] == 1)
439       accessibility_state->OnScreenReaderDetected();
440     else
441       accessibility_state->DisableAccessibility();
442   }
443   return [super accessibilitySetValue:value forAttribute:attribute];
446 - (void)_cycleWindowsReversed:(BOOL)arg1 {
447   base::AutoReset<BOOL> pin(&cyclingWindows_, YES);
448   [super _cycleWindowsReversed:arg1];
451 - (BOOL)isCyclingWindows {
452   return cyclingWindows_;
455 - (id)_removeWindow:(NSWindow*)window {
456   // Note _removeWindow is called from -[NSWindow dealloc], which can happen at
457   // unpredictable times due to reference counting. Just update state.
458   {
459     base::AutoLock lock(previousKeyWindowsLock_);
460     [self removePreviousKeyWindow:window];
461   }
462   return [super _removeWindow:window];
465 - (id)_setKeyWindow:(NSWindow*)window {
466   // |window| is nil when the current key window is being closed.
467   // A separate call follows with a new value when a new key window is set.
468   // Closed windows are not tracked in previousKeyWindows_.
469   if (window != nil) {
470     base::AutoLock lock(previousKeyWindowsLock_);
471     [self removePreviousKeyWindow:window];
472     NSWindow* currentKeyWindow = [self keyWindow];
473     if (currentKeyWindow != nil && currentKeyWindow != window)
474       previousKeyWindows_.push_back(currentKeyWindow);
475   }
477   return [super _setKeyWindow:window];
480 - (NSWindow*)previousKeyWindow {
481   base::AutoLock lock(previousKeyWindowsLock_);
482   return previousKeyWindows_.empty() ? nil : previousKeyWindows_.back();
485 - (void)removePreviousKeyWindow:(NSWindow*)window {
486   previousKeyWindowsLock_.AssertAcquired();
487   std::vector<NSWindow*>::iterator window_iterator =
488       std::find(previousKeyWindows_.begin(),
489                 previousKeyWindows_.end(),
490                 window);
491   if (window_iterator != previousKeyWindows_.end()) {
492     previousKeyWindows_.erase(window_iterator);
493   }
496 @end