[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / chrome_browser_application_mac.mm
blob3368c06351486e4ff3bf4657a30be1c00d1b420f
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 #import "base/auto_reset.h"
8 #include "base/debug/crash_logging.h"
9 #include "base/debug/stack_trace.h"
10 #import "base/logging.h"
11 #import "base/mac/scoped_nsexception_enabler.h"
12 #import "base/mac/scoped_nsobject.h"
13 #import "base/metrics/histogram.h"
14 #include "base/strings/stringprintf.h"
15 #import "base/strings/sys_string_conversions.h"
16 #import "chrome/browser/app_controller_mac.h"
17 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
18 #include "chrome/common/crash_keys.h"
19 #import "chrome/common/mac/objc_method_swizzle.h"
20 #import "chrome/common/mac/objc_zombie.h"
21 #include "content/public/browser/browser_accessibility_state.h"
22 #include "content/public/browser/render_view_host.h"
23 #include "content/public/browser/web_contents.h"
25 namespace {
27 // Tracking for cases being hit by -crInitWithName:reason:userInfo:.
28 enum ExceptionEventType {
29   EXCEPTION_ACCESSIBILITY = 0,
30   EXCEPTION_MENU_ITEM_BOUNDS_CHECK,
31   EXCEPTION_VIEW_NOT_IN_WINDOW,
32   EXCEPTION_NSURL_INIT_NIL,
33   EXCEPTION_NSDATADETECTOR_NIL_STRING,
35   // Always keep this at the end.
36   EXCEPTION_MAX,
39 void RecordExceptionEvent(ExceptionEventType event_type) {
40   UMA_HISTOGRAM_ENUMERATION("OSX.ExceptionHandlerEvents",
41                             event_type, EXCEPTION_MAX);
44 }  // namespace
46 // The implementation of NSExceptions break various assumptions in the
47 // Chrome code.  This category defines a replacement for
48 // -initWithName:reason:userInfo: for purposes of forcing a break in
49 // the debugger when an exception is raised.  -raise sounds more
50 // obvious to intercept, but it doesn't catch the original throw
51 // because the objc runtime doesn't use it.
52 @interface NSException (CrNSExceptionSwizzle)
53 - (id)crInitWithName:(NSString*)aName
54               reason:(NSString*)aReason
55             userInfo:(NSDictionary*)someUserInfo;
56 @end
58 static IMP gOriginalInitIMP = NULL;
60 @implementation NSException (CrNSExceptionSwizzle)
61 - (id)crInitWithName:(NSString*)aName
62               reason:(NSString*)aReason
63             userInfo:(NSDictionary*)someUserInfo {
64   // Method only called when swizzled.
65   DCHECK(_cmd == @selector(initWithName:reason:userInfo:));
67   // Parts of Cocoa rely on creating and throwing exceptions. These are not
68   // worth bugging-out over. It is very important that there be zero chance that
69   // any Chromium code is on the stack; these must be created by Apple code and
70   // then immediately consumed by Apple code.
71   static NSString* const kAcceptableNSExceptionNames[] = {
72     // If an object does not support an accessibility attribute, this will
73     // get thrown.
74     NSAccessibilityException,
75   };
77   BOOL found = NO;
78   for (size_t i = 0; i < arraysize(kAcceptableNSExceptionNames); ++i) {
79     if (aName == kAcceptableNSExceptionNames[i]) {
80       found = YES;
81       RecordExceptionEvent(EXCEPTION_ACCESSIBILITY);
82       break;
83     }
84   }
86   if (!found) {
87     // Update breakpad with the exception info.
88     std::string value = base::StringPrintf("%s reason %s",
89         [aName UTF8String], [aReason UTF8String]);
90     base::debug::SetCrashKeyValue(crash_keys::mac::kNSException, value);
91     base::debug::SetCrashKeyToStackTrace(crash_keys::mac::kNSExceptionTrace,
92                                          base::debug::StackTrace());
94     // Force crash for selected exceptions to generate crash dumps.
95     BOOL fatal = NO;
96     if (aName == NSInternalInconsistencyException) {
97       NSString* const kNSMenuItemArrayBoundsCheck =
98           @"Invalid parameter not satisfying: (index >= 0) && "
99           @"(index < [_itemArray count])";
100       if ([aReason isEqualToString:kNSMenuItemArrayBoundsCheck]) {
101         RecordExceptionEvent(EXCEPTION_MENU_ITEM_BOUNDS_CHECK);
102         fatal = YES;
103       }
105       NSString* const kNoWindowCheck = @"View is not in any window";
106       if ([aReason isEqualToString:kNoWindowCheck]) {
107         RecordExceptionEvent(EXCEPTION_VIEW_NOT_IN_WINDOW);
108         fatal = YES;
109       }
110     }
112     // Mostly "unrecognized selector sent to (instance|class)".  A
113     // very small number of things like inappropriate nil being passed.
114     if (aName == NSInvalidArgumentException) {
115       fatal = YES;
117       // TODO(shess): http://crbug.com/85463 throws this exception
118       // from ImageKit.  Our code is not on the stack, so it needs to
119       // be whitelisted for now.
120       NSString* const kNSURLInitNilCheck =
121           @"*** -[NSURL initFileURLWithPath:isDirectory:]: "
122           @"nil string parameter";
123       if ([aReason isEqualToString:kNSURLInitNilCheck]) {
124         RecordExceptionEvent(EXCEPTION_NSURL_INIT_NIL);
125         fatal = NO;
126       }
128       // TODO(shess): <http://crbug.com/316759> OSX 10.9 is failing
129       // trying to extract structure from a string.
130       NSString* const kNSDataDetectorNilCheck =
131           @"*** -[NSDataDetector enumerateMatchesInString:"
132           @"options:range:usingBlock:]: nil argument";
133       if ([aReason isEqualToString:kNSDataDetectorNilCheck]) {
134         RecordExceptionEvent(EXCEPTION_NSDATADETECTOR_NIL_STRING);
135         fatal = NO;
136       }
137     }
139     // Dear reader: Something you just did provoked an NSException.
140     // NSException is implemented in terms of setjmp()/longjmp(),
141     // which does poor things when combined with C++ scoping
142     // (destructors are skipped).  Chrome should be NSException-free,
143     // please check your backtrace and see if you can't file a bug
144     // with a repro case.
145     const bool allow = base::mac::GetNSExceptionsAllowed();
146     if (fatal && !allow) {
147       LOG(FATAL) << "Someone is trying to raise an exception!  "
148                  << value;
149     } else {
150       // Make sure that developers see when their code throws
151       // exceptions.
152       DCHECK(allow) << "Someone is trying to raise an exception!  "
153                     << value;
154     }
155   }
157   // Forward to the original version.
158   return gOriginalInitIMP(self, _cmd, aName, aReason, someUserInfo);
160 @end
162 namespace chrome_browser_application_mac {
164 // Maximum number of known named exceptions we'll support.  There is
165 // no central registration, but I only find about 75 possibilities in
166 // the system frameworks, and many of them are probably not
167 // interesting to track in aggregate (those relating to distributed
168 // objects, for instance).
169 const size_t kKnownNSExceptionCount = 25;
171 const size_t kUnknownNSException = kKnownNSExceptionCount;
173 size_t BinForException(NSException* exception) {
174   // A list of common known exceptions.  The list position will
175   // determine where they live in the histogram, so never move them
176   // around, only add to the end.
177   static NSString* const kKnownNSExceptionNames[] = {
178     // Grab-bag exception, not very common.  CFArray (or other
179     // container) mutated while being enumerated is one case seen in
180     // production.
181     NSGenericException,
183     // Out-of-range on NSString or NSArray.  Quite common.
184     NSRangeException,
186     // Invalid arg to method, unrecognized selector.  Quite common.
187     NSInvalidArgumentException,
189     // malloc() returned null in object creation, I think.  Turns out
190     // to be very uncommon in production, because of the OOM killer.
191     NSMallocException,
193     // This contains things like windowserver errors, trying to draw
194     // views which aren't in windows, unable to read nib files.  By
195     // far the most common exception seen on the crash server.
196     NSInternalInconsistencyException,
198     nil
199   };
201   // Make sure our array hasn't outgrown our abilities to track it.
202   DCHECK_LE(arraysize(kKnownNSExceptionNames), kKnownNSExceptionCount);
204   NSString* name = [exception name];
205   for (int i = 0; kKnownNSExceptionNames[i]; ++i) {
206     if (name == kKnownNSExceptionNames[i]) {
207       return i;
208     }
209   }
210   return kUnknownNSException;
213 void RecordExceptionWithUma(NSException* exception) {
214   UMA_HISTOGRAM_ENUMERATION("OSX.NSException",
215       BinForException(exception), kUnknownNSException);
218 void RegisterBrowserCrApp() {
219   [BrowserCrApplication sharedApplication];
222 void Terminate() {
223   [NSApp terminate:nil];
226 void CancelTerminate() {
227   [NSApp cancelTerminate:nil];
230 }  // namespace chrome_browser_application_mac
232 namespace {
234 void SwizzleInit() {
235   // Do-nothing wrapper so that we can arrange to only swizzle
236   // -[NSException raise] when DCHECK() is turned on (as opposed to
237   // replicating the preprocess logic which turns DCHECK() on).
238   gOriginalInitIMP = ObjcEvilDoers::SwizzleImplementedInstanceMethods(
239       [NSException class],
240       @selector(initWithName:reason:userInfo:),
241       @selector(crInitWithName:reason:userInfo:));
244 }  // namespace
246 // These methods are being exposed for the purposes of overriding.
247 // Used to determine when a Panel window can become the key window.
248 @interface NSApplication (PanelsCanBecomeKey)
249 - (void)_cycleWindowsReversed:(BOOL)arg1;
250 - (id)_removeWindow:(NSWindow*)window;
251 - (id)_setKeyWindow:(NSWindow*)window;
252 @end
254 @interface BrowserCrApplication (PrivateInternal)
256 // This must be called under the protection of previousKeyWindowsLock_.
257 - (void)removePreviousKeyWindow:(NSWindow*)window;
259 @end
261 @implementation BrowserCrApplication
263 + (void)initialize {
264   // Turn all deallocated Objective-C objects into zombies, keeping
265   // the most recent 10,000 of them on the treadmill.
266   ObjcEvilDoers::ZombieEnable(true, 10000);
269 - (id)init {
270   SwizzleInit();
271   self = [super init];
273   // Sanity check to alert if overridden methods are not supported.
274   DCHECK([NSApplication
275       instancesRespondToSelector:@selector(_cycleWindowsReversed:)]);
276   DCHECK([NSApplication
277       instancesRespondToSelector:@selector(_removeWindow:)]);
278   DCHECK([NSApplication
279       instancesRespondToSelector:@selector(_setKeyWindow:)]);
281   return self;
284 // Initialize NSApplication using the custom subclass.  Check whether NSApp
285 // was already initialized using another class, because that would break
286 // some things.
287 + (NSApplication*)sharedApplication {
288   NSApplication* app = [super sharedApplication];
290   // +sharedApplication initializes the global NSApp, so if a specific
291   // NSApplication subclass is requested, require that to be the one
292   // delivered.  The practical effect is to require a consistent NSApp
293   // across the executable.
294   CHECK([NSApp isKindOfClass:self])
295       << "NSApp must be of type " << [[self className] UTF8String]
296       << ", not " << [[NSApp className] UTF8String];
298   // If the message loop was initialized before NSApp is setup, the
299   // message pump will be setup incorrectly.  Failing this implies
300   // that RegisterBrowserCrApp() should be called earlier.
301   CHECK(base::MessagePumpMac::UsingCrApp())
302       << "MessagePumpMac::Create() is using the wrong pump implementation"
303       << " for " << [[self className] UTF8String];
305   return app;
308 ////////////////////////////////////////////////////////////////////////////////
309 // HISTORICAL COMMENT (by viettrungluu, from
310 // http://codereview.chromium.org/1520006 with mild editing):
312 // A quick summary of the state of things (before the changes to shutdown):
314 // Currently, we are totally hosed (put in a bad state in which Cmd-W does the
315 // wrong thing, and which will probably eventually lead to a crash) if we begin
316 // quitting but termination is aborted for some reason.
318 // I currently know of two ways in which termination can be aborted:
319 // (1) Common case: a window has an onbeforeunload handler which pops up a
320 //     "leave web page" dialog, and the user answers "no, don't leave".
321 // (2) Uncommon case: popups are enabled (in Content Settings, i.e., the popup
322 //     blocker is disabled), and some nasty web page pops up a new window on
323 //     closure.
325 // I don't know of other ways in which termination can be aborted, but they may
326 // exist (or may be added in the future, for that matter).
328 // My CL [see above] does the following:
329 // a. Should prevent being put in a bad state (which breaks Cmd-W and leads to
330 //    crash) under all circumstances.
331 // b. Should completely handle (1) properly.
332 // c. Doesn't (yet) handle (2) properly and puts it in a weird state (but not
333 //    that bad).
334 // d. Any other ways of aborting termination would put it in that weird state.
336 // c. can be fixed by having the global flag reset on browser creation or
337 // similar (and doing so might also fix some possible d.'s as well). I haven't
338 // done this yet since I haven't thought about it carefully and since it's a
339 // corner case.
341 // The weird state: a state in which closing the last window quits the browser.
342 // This might be a bit annoying, but it's not dangerous in any way.
343 ////////////////////////////////////////////////////////////////////////////////
345 // |-terminate:| is the entry point for orderly "quit" operations in Cocoa. This
346 // includes the application menu's quit menu item and keyboard equivalent, the
347 // application's dock icon menu's quit menu item, "quit" (not "force quit") in
348 // the Activity Monitor, and quits triggered by user logout and system restart
349 // and shutdown.
351 // The default |-terminate:| implementation ends the process by calling exit(),
352 // and thus never leaves the main run loop. This is unsuitable for Chrome since
353 // Chrome depends on leaving the main run loop to perform an orderly shutdown.
354 // We support the normal |-terminate:| interface by overriding the default
355 // implementation. Our implementation, which is very specific to the needs of
356 // Chrome, works by asking the application delegate to terminate using its
357 // |-tryToTerminateApplication:| method.
359 // |-tryToTerminateApplication:| differs from the standard
360 // |-applicationShouldTerminate:| in that no special event loop is run in the
361 // case that immediate termination is not possible (e.g., if dialog boxes
362 // allowing the user to cancel have to be shown). Instead, this method sets a
363 // flag and tries to close all browsers. This flag causes the closure of the
364 // final browser window to begin actual tear-down of the application.
365 // Termination is cancelled by resetting this flag. The standard
366 // |-applicationShouldTerminate:| is not supported, and code paths leading to it
367 // must be redirected.
369 // When the last browser has been destroyed, the BrowserList calls
370 // chrome::OnAppExiting(), which is the point of no return. That will cause
371 // the NSApplicationWillTerminateNotification to be posted, which ends the
372 // NSApplication event loop, so final post- MessageLoop::Run() work is done
373 // before exiting.
374 - (void)terminate:(id)sender {
375   AppController* appController = static_cast<AppController*>([NSApp delegate]);
376   [appController tryToTerminateApplication:self];
377   // Return, don't exit. The application is responsible for exiting on its own.
380 - (void)cancelTerminate:(id)sender {
381   AppController* appController = static_cast<AppController*>([NSApp delegate]);
382   [appController stopTryingToTerminateApplication:self];
385 - (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)sender {
386   // The Dock menu contains an automagic section where you can select
387   // amongst open windows.  If a window is closed via JavaScript while
388   // the menu is up, the menu item for that window continues to exist.
389   // When a window is selected this method is called with the
390   // now-freed window as |aTarget|.  Short-circuit the call if
391   // |aTarget| is not a valid window.
392   if (anAction == @selector(_selectWindow:)) {
393     // Not using -[NSArray containsObject:] because |aTarget| may be a
394     // freed object.
395     BOOL found = NO;
396     for (NSWindow* window in [self windows]) {
397       if (window == aTarget) {
398         found = YES;
399         break;
400       }
401     }
402     if (!found) {
403       return NO;
404     }
405   }
407   // When a Cocoa control is wired to a freed object, we get crashers
408   // in the call to |super| with no useful information in the
409   // backtrace.  Attempt to add some useful information.
411   // If the action is something generic like -commandDispatch:, then
412   // the tag is essential.
413   NSInteger tag = 0;
414   if ([sender isKindOfClass:[NSControl class]]) {
415     tag = [sender tag];
416     if (tag == 0 || tag == -1) {
417       tag = [sender selectedTag];
418     }
419   } else if ([sender isKindOfClass:[NSMenuItem class]]) {
420     tag = [sender tag];
421   }
423   NSString* actionString = NSStringFromSelector(anAction);
424   std::string value = base::StringPrintf("%s tag %ld sending %s to %p",
425       [[sender className] UTF8String],
426       static_cast<long>(tag),
427       [actionString UTF8String],
428       aTarget);
430   base::debug::ScopedCrashKey key(crash_keys::mac::kSendAction, value);
432   // Certain third-party code, such as print drivers, can still throw
433   // exceptions and Chromium cannot fix them.  This provides a way to
434   // work around those on a spot basis.
435   bool enableNSExceptions = false;
437   // http://crbug.com/80686 , an Epson printer driver.
438   if (anAction == @selector(selectPDE:)) {
439     enableNSExceptions = true;
440   }
442   // Minimize the window by keeping this close to the super call.
443   scoped_ptr<base::mac::ScopedNSExceptionEnabler> enabler;
444   if (enableNSExceptions)
445     enabler.reset(new base::mac::ScopedNSExceptionEnabler());
446   return [super sendAction:anAction to:aTarget from:sender];
449 - (BOOL)isHandlingSendEvent {
450   return handlingSendEvent_;
453 - (void)setHandlingSendEvent:(BOOL)handlingSendEvent {
454   handlingSendEvent_ = handlingSendEvent;
457 - (void)sendEvent:(NSEvent*)event {
458   base::mac::ScopedSendingEvent sendingEventScoper;
459   [super sendEvent:event];
462 // NSExceptions which are caught by the event loop are logged here.
463 // NSException uses setjmp/longjmp, which can be very bad for C++, so
464 // we attempt to track and report them.
465 - (void)reportException:(NSException *)anException {
466   // If we throw an exception in this code, we can create an infinite
467   // loop.  If we throw out of the if() without resetting
468   // |reportException|, we'll stop reporting exceptions for this run.
469   static BOOL reportingException = NO;
470   DCHECK(!reportingException);
471   if (!reportingException) {
472     reportingException = YES;
473     chrome_browser_application_mac::RecordExceptionWithUma(anException);
475     // http://crbug.com/45928 is a bug about needing to double-close
476     // windows sometimes.  One theory is that |-isHandlingSendEvent|
477     // gets latched to always return |YES|.  Since scopers are used to
478     // manipulate that value, that should not be possible.  One way to
479     // sidestep scopers is setjmp/longjmp (see above).  The following
480     // is to "fix" this while the more fundamental concern is
481     // addressed elsewhere.
482     [self setHandlingSendEvent:NO];
484     // If |ScopedNSExceptionEnabler| is used to allow exceptions, and an
485     // uncaught exception is thrown, it will throw past all of the scopers.
486     // Reset the flag so that future exceptions are not masked.
487     base::mac::SetNSExceptionsAllowed(false);
489     // Store some human-readable information in breakpad keys in case
490     // there is a crash.  Since breakpad does not provide infinite
491     // storage, we track two exceptions.  The first exception thrown
492     // is tracked because it may be the one which caused the system to
493     // go off the rails.  The last exception thrown is tracked because
494     // it may be the one most directly associated with the crash.
495     static BOOL trackedFirstException = NO;
497     const char* const kExceptionKey =
498         trackedFirstException ? crash_keys::mac::kLastNSException
499                               : crash_keys::mac::kFirstNSException;
500     NSString* value = [NSString stringWithFormat:@"%@ reason %@",
501                                 [anException name], [anException reason]];
502     base::debug::SetCrashKeyValue(kExceptionKey, [value UTF8String]);
504     // Encode the callstack from point of throw.
505     // TODO(shess): Our swizzle plus the 23-frame limit plus Cocoa
506     // overhead may make this less than useful.  If so, perhaps skip
507     // some items and/or use two keys.
508     const char* const kExceptionBtKey =
509         trackedFirstException ? crash_keys::mac::kLastNSExceptionTrace
510                               : crash_keys::mac::kFirstNSExceptionTrace;
511     NSArray* addressArray = [anException callStackReturnAddresses];
512     NSUInteger addressCount = [addressArray count];
513     if (addressCount) {
514       // SetCrashKeyFromAddresses() only encodes 23, so that's a natural limit.
515       const NSUInteger kAddressCountMax = 23;
516       void* addresses[kAddressCountMax];
517       if (addressCount > kAddressCountMax)
518         addressCount = kAddressCountMax;
520       for (NSUInteger i = 0; i < addressCount; ++i) {
521         addresses[i] = reinterpret_cast<void*>(
522             [[addressArray objectAtIndex:i] unsignedIntegerValue]);
523       }
524       base::debug::SetCrashKeyFromAddresses(
525           kExceptionBtKey, addresses, static_cast<size_t>(addressCount));
526     } else {
527       base::debug::ClearCrashKey(kExceptionBtKey);
528     }
529     trackedFirstException = YES;
531     reportingException = NO;
532   }
534   [super reportException:anException];
537 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
538   if ([attribute isEqualToString:@"AXEnhancedUserInterface"] &&
539       [value intValue] == 1) {
540     content::BrowserAccessibilityState::GetInstance()->OnScreenReaderDetected();
541     for (TabContentsIterator it; !it.done(); it.Next()) {
542       if (content::WebContents* contents = *it)
543         if (content::RenderViewHost* rvh = contents->GetRenderViewHost())
544           rvh->EnableFullAccessibilityMode();
545     }
546   }
547   return [super accessibilitySetValue:value forAttribute:attribute];
550 - (void)_cycleWindowsReversed:(BOOL)arg1 {
551   base::AutoReset<BOOL> pin(&cyclingWindows_, YES);
552   [super _cycleWindowsReversed:arg1];
555 - (BOOL)isCyclingWindows {
556   return cyclingWindows_;
559 - (id)_removeWindow:(NSWindow*)window {
560   {
561     base::AutoLock lock(previousKeyWindowsLock_);
562     [self removePreviousKeyWindow:window];
563   }
564   id result = [super _removeWindow:window];
566   // Ensure app has a key window after a window is removed.
567   // OS wants to make a panel browser window key after closing an app window
568   // because panels use a higher priority window level, but panel windows may
569   // refuse to become key, leaving the app with no key window. The OS does
570   // not seem to consider other windows after the first window chosen refuses
571   // to become key. Force consideration of other windows here.
572   if ([self isActive] && [self keyWindow] == nil) {
573     NSWindow* key =
574         [self makeWindowsPerform:@selector(canBecomeKeyWindow) inOrder:YES];
575     [key makeKeyWindow];
576   }
578   // Return result from the super class. It appears to be the app that
579   // owns the removed window (determined via experimentation).
580   return result;
583 - (id)_setKeyWindow:(NSWindow*)window {
584   // |window| is nil when the current key window is being closed.
585   // A separate call follows with a new value when a new key window is set.
586   // Closed windows are not tracked in previousKeyWindows_.
587   if (window != nil) {
588     base::AutoLock lock(previousKeyWindowsLock_);
589     [self removePreviousKeyWindow:window];
590     NSWindow* currentKeyWindow = [self keyWindow];
591     if (currentKeyWindow != nil && currentKeyWindow != window)
592       previousKeyWindows_.push_back(currentKeyWindow);
593   }
595   return [super _setKeyWindow:window];
598 - (NSWindow*)previousKeyWindow {
599   base::AutoLock lock(previousKeyWindowsLock_);
600   return previousKeyWindows_.empty() ? nil : previousKeyWindows_.back();
603 - (void)removePreviousKeyWindow:(NSWindow*)window {
604   previousKeyWindowsLock_.AssertAcquired();
605   std::vector<NSWindow*>::iterator window_iterator =
606       std::find(previousKeyWindows_.begin(),
607                 previousKeyWindows_.end(),
608                 window);
609   if (window_iterator != previousKeyWindows_.end()) {
610     previousKeyWindows_.erase(window_iterator);
611   }
614 @end