summaryrefslogtreecommitdiffstats
path: root/chrome/browser/chrome_browser_application_mac.mm
diff options
context:
space:
mode:
authordmaclach@chromium.org <dmaclach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-05 21:53:01 +0000
committerdmaclach@chromium.org <dmaclach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-05 21:53:01 +0000
commitaaa47ee9d83f773d37aa4fd4a04097425ce62063 (patch)
tree9250680ad28ed8c31fdb967b702e2b638bd80347 /chrome/browser/chrome_browser_application_mac.mm
parent0d6bb06531d9b7d68e6d78b79b4ec29e68a059cb (diff)
downloadchromium_src-aaa47ee9d83f773d37aa4fd4a04097425ce62063.zip
chromium_src-aaa47ee9d83f773d37aa4fd4a04097425ce62063.tar.gz
chromium_src-aaa47ee9d83f773d37aa4fd4a04097425ce62063.tar.bz2
Cleans up our autorelease pool handling by making sure that an autorelease pool isn't created while the app is handling an event sent via -[NSApp sendEvent].
Branches browser/chrome_application_mac into browser/chrome_browser_application and base/chrome_application. Renderers will run as chrome_applications, and browsers will run as chrome_browser_applications. BUG=26418, 25462, 25463, 25465 TEST=1) See bug 25857. 2) Start up. Open 3+ windows. 3)Quit. See bugs for other repro cases. Review URL: http://codereview.chromium.org/345051 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@31135 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/chrome_browser_application_mac.mm')
-rw-r--r--chrome/browser/chrome_browser_application_mac.mm309
1 files changed, 309 insertions, 0 deletions
diff --git a/chrome/browser/chrome_browser_application_mac.mm b/chrome/browser/chrome_browser_application_mac.mm
new file mode 100644
index 0000000..57c6fab
--- /dev/null
+++ b/chrome/browser/chrome_browser_application_mac.mm
@@ -0,0 +1,309 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/chrome_browser_application_mac.h"
+
+#import "base/histogram.h"
+#import "base/logging.h"
+#import "base/scoped_nsobject.h"
+#import "base/sys_string_conversions.h"
+#import "chrome/app/breakpad_mac.h"
+#import "chrome/browser/cocoa/chrome_event_processing_window.h"
+#import "chrome/browser/cocoa/objc_method_swizzle.h"
+
+// The implementation of NSExceptions break various assumptions in the
+// Chrome code. This category defines a replacement for
+// -initWithName:reason:userInfo: for purposes of forcing a break in
+// the debugger when an exception is raised. -raise sounds more
+// obvious to intercept, but it doesn't catch the original throw
+// because the objc runtime doesn't use it.
+@interface NSException (NSExceptionSwizzle)
+- (id)chromeInitWithName:(NSString *)aName
+ reason:(NSString *)aReason
+ userInfo:(NSDictionary *)someUserInfo;
+@end
+
+static IMP gOriginalInitIMP = NULL;
+
+@implementation NSException (NSExceptionSwizzle)
+- (id)chromeInitWithName:(NSString *)aName
+ reason:(NSString *)aReason
+ userInfo:(NSDictionary *)someUserInfo {
+ // Method only called when swizzled.
+ DCHECK(_cmd == @selector(initWithName:reason:userInfo:));
+
+ // Dear reader: something you just did provoked an NSException.
+ // Please check your backtrace and see if you can't file a bug with
+ // a repro case. You should be able to safely continue past the
+ // NOTREACHED(), but feel free to comment it out locally if it is
+ // making your job hard.
+ DLOG(ERROR) << "Someone is preparing to raise an exception! "
+ << base::SysNSStringToUTF8(aName) << " *** "
+ << base::SysNSStringToUTF8(aReason);
+ NOTREACHED();
+
+ // Forward to the original version.
+ return gOriginalInitIMP(self, _cmd, aName, aReason, someUserInfo);
+}
+@end
+
+namespace chrome_browser_application_mac {
+
+// Maximum number of known named exceptions we'll support. There is
+// no central registration, but I only find about 75 possibilities in
+// the system frameworks, and many of them are probably not
+// interesting to track in aggregate (those relating to distributed
+// objects, for instance).
+const size_t kKnownNSExceptionCount = 25;
+
+const size_t kUnknownNSException = kKnownNSExceptionCount;
+
+size_t BinForException(NSException* exception) {
+ // A list of common known exceptions. The list position will
+ // determine where they live in the histogram, so never move them
+ // around, only add to the end.
+ static const NSString* kKnownNSExceptionNames[] = {
+ // ???
+ NSGenericException,
+
+ // Out-of-range on NSString or NSArray.
+ NSRangeException,
+
+ // Invalid arg to method, unrecognized selector.
+ NSInvalidArgumentException,
+
+ // malloc() returned null in object creation, I think.
+ NSMallocException,
+
+ nil
+ };
+
+ // Make sure our array hasn't outgrown our abilities to track it.
+ DCHECK_LE(arraysize(kKnownNSExceptionNames), kKnownNSExceptionCount);
+
+ const NSString* name = [exception name];
+ for (int i = 0; kKnownNSExceptionNames[i]; ++i) {
+ if (name == kKnownNSExceptionNames[i]) {
+ return i;
+ }
+ }
+ return kUnknownNSException;
+}
+
+void RecordExceptionWithUma(NSException* exception) {
+ static LinearHistogram histogram("OSX.NSException", 0, kUnknownNSException,
+ kUnknownNSException + 1);
+ histogram.SetFlags(kUmaTargetedHistogramFlag);
+ histogram.Add(BinForException(exception));
+}
+
+void Terminate() {
+ [NSApp terminate:nil];
+}
+
+} // namespace chrome_browser_application_mac
+
+namespace {
+
+// Helper to make it easy to get crash keys right.
+// TODO(shess): Find a better home for this. app/breakpad_mac.h
+// doesn't work.
+class ScopedCrashKey {
+ public:
+ ScopedCrashKey(NSString* key, NSString* value)
+ : crash_key_([key retain]) {
+ SetCrashKeyValue(crash_key_.get(), value);
+ }
+ ~ScopedCrashKey() {
+ ClearCrashKeyValue(crash_key_.get());
+ }
+
+ private:
+ scoped_nsobject<NSString> crash_key_;
+};
+
+// Do-nothing wrapper so that we can arrange to only swizzle
+// -[NSException raise] when DCHECK() is turned on (as opposed to
+// replicating the preprocess logic which turns DCHECK() on).
+BOOL SwizzleNSExceptionInit() {
+ gOriginalInitIMP = ObjcEvilDoers::SwizzleImplementedInstanceMethods(
+ [NSException class],
+ @selector(initWithName:reason:userInfo:),
+ @selector(chromeInitWithName:reason:userInfo:));
+ return YES;
+}
+
+} // namespace
+
+@implementation BrowserCrApplication
+
+- init {
+ DCHECK(SwizzleNSExceptionInit());
+ return [super init];
+}
+
+// -terminate: is the entry point for orderly "quit" operations in Cocoa.
+// This includes the application menu's quit menu item and keyboard
+// equivalent, the application's dock icon menu's quit menu item, "quit" (not
+// "force quit") in the Activity Monitor, and quits triggered by user logout
+// and system restart and shutdown.
+//
+// The default NSApplication -terminate: implementation will end the process
+// by calling exit(), and thus never leave the main run loop. This is
+// unsuitable for Chrome's purposes. Chrome depends on leaving the main
+// run loop to perform a proper orderly shutdown. This design is ingrained
+// in the application and the assumptions that its code makes, and is
+// entirely reasonable and works well on other platforms, but it's not
+// compatible with the standard Cocoa quit sequence. Quits originated from
+// within the application can be redirected to not use -terminate:, but
+// quits from elsewhere cannot be.
+//
+// To allow the Cocoa-based Chrome to support the standard Cocoa -terminate:
+// interface, and allow all quits to cause Chrome to shut down properly
+// regardless of their origin, -terminate: is overriden. The custom
+// -terminate: does not end the application with exit(). Instead, it simply
+// returns after posting the normal NSApplicationWillTerminateNotification
+// notification. The application is responsible for exiting on its own in
+// whatever way it deems appropriate. In Chrome's case, the main run loop will
+// end and the applicaton will exit by returning from main().
+//
+// This implementation of -terminate: is scaled back and is not as
+// fully-featured as the implementation in NSApplication, nor is it a direct
+// drop-in replacement -terminate: in most applications. It is
+// purpose-specific to Chrome.
+- (void)terminate:(id)sender {
+ NSApplicationTerminateReply shouldTerminate = NSTerminateNow;
+ SEL selector = @selector(applicationShouldTerminate:);
+ if ([[self delegate] respondsToSelector:selector])
+ shouldTerminate = [[self delegate] applicationShouldTerminate:self];
+
+ // If shouldTerminate is NSTerminateLater, the application is expected to
+ // call -replyToApplicationShouldTerminate: when it knows whether or not it
+ // should terminate. If the argument is YES,
+ // -replyToApplicationShouldTerminate: will call -terminate:. This will
+ // result in another call to the delegate's -applicationShouldTerminate:,
+ // which would be expected to return NSTerminateNow at that point.
+ if (shouldTerminate != NSTerminateNow)
+ return;
+
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:NSApplicationWillTerminateNotification
+ object:self];
+
+ // Return, don't exit. The application is responsible for exiting on its
+ // own.
+}
+
+- (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)sender {
+ // The Dock menu contains an automagic section where you can select
+ // amongst open windows. If a window is closed via JavaScript while
+ // the menu is up, the menu item for that window continues to exist.
+ // When a window is selected this method is called with the
+ // now-freed window as |aTarget|. Short-circuit the call if
+ // |aTarget| is not a valid window.
+ if (anAction == @selector(_selectWindow:)) {
+ // Not using -[NSArray containsObject:] because |aTarget| may be a
+ // freed object.
+ BOOL found = NO;
+ for (NSWindow* window in [self windows]) {
+ if (window == aTarget) {
+ found = YES;
+ break;
+ }
+ }
+ if (!found) {
+ return NO;
+ }
+ }
+
+ // When a Cocoa control is wired to a freed object, we get crashers
+ // in the call to |super| with no useful information in the
+ // backtrace. Attempt to add some useful information.
+ static const NSString* kActionKey = @"sendaction";
+
+ // If the action is something generic like -commandDispatch:, then
+ // the tag is essential.
+ NSInteger tag = 0;
+ if ([sender isKindOfClass:[NSControl class]]) {
+ tag = [sender tag];
+ if (tag == 0 || tag == -1) {
+ tag = [sender selectedTag];
+ }
+ } else if ([sender isKindOfClass:[NSMenuItem class]]) {
+ tag = [sender tag];
+ }
+
+ NSString* actionString = NSStringFromSelector(anAction);
+ NSString* value =
+ [NSString stringWithFormat:@"%@ tag %d sending %@ to %p",
+ [sender className], tag, actionString, aTarget];
+
+ ScopedCrashKey key(kActionKey, value);
+ return [super sendAction:anAction to:aTarget from:sender];
+}
+
+- (void)sendEvent:(NSEvent*)event {
+ chrome_application_mac::ScopedSendingEvent scoper(self);
+ // The superclass's |sendEvent:| sends keyboard events to the menu and the key
+ // view loop before dispatching them to |keyDown:|. Since we want to send keys
+ // to the renderer before sending them to the menu, and we never want them to
+ // the kev view loop when the web is focussed, we change this behavior.
+ if ([[self keyWindow]
+ isKindOfClass:[ChromeEventProcessingWindow class]]) {
+ if ([static_cast<ChromeEventProcessingWindow*>([self keyWindow])
+ shortcircuitEvent:event])
+ return;
+ }
+
+ [super sendEvent:event];
+}
+
+// NSExceptions which are caught by the event loop are logged here.
+// NSException uses setjmp/longjmp, which can be very bad for C++, so
+// we attempt to track and report them.
+- (void)reportException:(NSException *)anException {
+ // If we throw an exception in this code, we can create an infinite
+ // loop. If we throw out of the if() without resetting
+ // |reportException|, we'll stop reporting exceptions for this run.
+ static BOOL reportingException = NO;
+ DCHECK(!reportingException);
+ if (!reportingException) {
+ reportingException = YES;
+ chrome_browser_application_mac::RecordExceptionWithUma(anException);
+
+ // Store some human-readable information in breakpad keys in case
+ // there is a crash. Since breakpad does not provide infinite
+ // storage, we track two exceptions. The first exception thrown
+ // is tracked because it may be the one which caused the system to
+ // go off the rails. The last exception thrown is tracked because
+ // it may be the one most directly associated with the crash.
+ static const NSString* kFirstExceptionKey = @"firstexception";
+ static BOOL trackedFirstException = NO;
+ static const NSString* kLastExceptionKey = @"lastexception";
+
+ // TODO(shess): It would be useful to post some stacktrace info
+ // from the exception.
+ // 10.6 has -[NSException callStackSymbols]
+ // 10.5 has -[NSException callStackReturnAddresses]
+ // 10.5 has backtrace_symbols().
+ // I've tried to combine the latter two, but got nothing useful.
+ // The addresses are right, though, maybe we could train the crash
+ // server to decode them for us.
+
+ NSString* value = [NSString stringWithFormat:@"%@ reason %@",
+ [anException name], [anException reason]];
+ if (!trackedFirstException) {
+ SetCrashKeyValue(kFirstExceptionKey, value);
+ trackedFirstException = YES;
+ } else {
+ SetCrashKeyValue(kLastExceptionKey, value);
+ }
+
+ reportingException = NO;
+ }
+
+ [super reportException:anException];
+}
+
+@end