Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(105)

Side by Side Diff: base/chrome_application_mac.mm

Issue 345051: Cleans up our autorelease handling so that we don't create a layered ... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 11 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "chrome/browser/chrome_application_mac.h" 5 #import "chrome_application_mac.h"
6 6
7 #import "base/histogram.h" 7 #include "base/logging.h"
8 #import "base/logging.h"
9 #import "base/scoped_nsobject.h"
10 #import "base/sys_string_conversions.h"
11 #import "chrome/app/breakpad_mac.h"
12 #import "chrome/browser/cocoa/chrome_event_processing_window.h"
13 #import "chrome/browser/cocoa/objc_method_swizzle.h"
14 #import "chrome/browser/renderer_host/render_widget_host_view_mac.h"
15 8
16 // The implementation of NSExceptions break various assumptions in the 9 @interface CrApplication ()
17 // Chrome code. This category defines a replacement for 10 @property(readwrite,
18 // -initWithName:reason:userInfo: for purposes of forcing a break in 11 getter=isHandlingSendEvent,
19 // the debugger when an exception is raised. -raise sounds more 12 nonatomic) BOOL handlingSendEvent;
20 // obvious to intercept, but it doesn't catch the original throw
21 // because the objc runtime doesn't use it.
22 @interface NSException (NSExceptionSwizzle)
23 - (id)chromeInitWithName:(NSString *)aName
24 reason:(NSString *)aReason
25 userInfo:(NSDictionary *)someUserInfo;
26 @end 13 @end
27 14
28 static IMP gOriginalInitIMP = NULL; 15 @implementation CrApplication
16 @synthesize handlingSendEvent = handlingSendEvent_;
29 17
30 @implementation NSException (NSExceptionSwizzle) 18 // Initialize NSApplication using the custom subclass. Check whether NSApp
31 - (id)chromeInitWithName:(NSString *)aName 19 // was already initialized using another class, because that would break
32 reason:(NSString *)aReason 20 // some things.
33 userInfo:(NSDictionary *)someUserInfo { 21 + (NSApplication*)sharedApplication {
34 // Method only called when swizzled. 22 NSApplication* app = [super sharedApplication];
35 DCHECK(_cmd == @selector(initWithName:reason:userInfo:)); 23 if (![NSApp isKindOfClass:self]) {
36 24 LOG(ERROR) << "NSApp should be of type " << [[self className] UTF8String]
37 // Dear reader: something you just did provoked an NSException. 25 << ", not " << [[NSApp className] UTF8String];
38 // Please check your backtrace and see if you can't file a bug with 26 DCHECK(false) << "NSApp is of wrong type";
39 // a repro case. You should be able to safely continue past the
40 // NOTREACHED(), but feel free to comment it out locally if it is
41 // making your job hard.
42 DLOG(ERROR) << "Someone is preparing to raise an exception! "
43 << base::SysNSStringToUTF8(aName) << " *** "
44 << base::SysNSStringToUTF8(aReason);
45 NOTREACHED();
46
47 // Forward to the original version.
48 return gOriginalInitIMP(self, _cmd, aName, aReason, someUserInfo);
49 }
50 @end
51
52 namespace CrApplicationNSException {
53
54 // Maximum number of known named exceptions we'll support. There is
55 // no central registration, but I only find about 75 possibilities in
56 // the system frameworks, and many of them are probably not
57 // interesting to track in aggregate (those relating to distributed
58 // objects, for instance).
59 const size_t kKnownNSExceptionCount = 25;
60
61 const size_t kUnknownNSException = kKnownNSExceptionCount;
62
63 size_t BinForException(NSException* exception) {
64 // A list of common known exceptions. The list position will
65 // determine where they live in the histogram, so never move them
66 // around, only add to the end.
67 static const NSString* kKnownNSExceptionNames[] = {
68 // ???
69 NSGenericException,
70
71 // Out-of-range on NSString or NSArray.
72 NSRangeException,
73
74 // Invalid arg to method, unrecognized selector.
75 NSInvalidArgumentException,
76
77 // malloc() returned null in object creation, I think.
78 NSMallocException,
79
80 nil
81 };
82
83 // Make sure our array hasn't outgrown our abilities to track it.
84 DCHECK_LE(arraysize(kKnownNSExceptionNames), kKnownNSExceptionCount);
85
86 const NSString* name = [exception name];
87 for (int i = 0; kKnownNSExceptionNames[i]; ++i) {
88 if (name == kKnownNSExceptionNames[i]) {
89 return i;
90 }
91 } 27 }
92 return kUnknownNSException; 28 return app;
93 }
94
95 void RecordExceptionWithUma(NSException* exception) {
96 static LinearHistogram histogram("OSX.NSException", 0, kUnknownNSException,
97 kUnknownNSException + 1);
98 histogram.SetFlags(kUmaTargetedHistogramFlag);
99 histogram.Add(BinForException(exception));
100 }
101
102 } // CrApplicationNSException
103
104 namespace {
105
106 // Helper to make it easy to get crash keys right.
107 // TODO(shess): Find a better home for this. app/breakpad_mac.h
108 // doesn't work.
109 class ScopedCrashKey {
110 public:
111 ScopedCrashKey(NSString* key, NSString* value)
112 : crash_key_([key retain]) {
113 SetCrashKeyValue(crash_key_.get(), value);
114 }
115 ~ScopedCrashKey() {
116 ClearCrashKeyValue(crash_key_.get());
117 }
118
119 private:
120 scoped_nsobject<NSString> crash_key_;
121 };
122
123 // Do-nothing wrapper so that we can arrange to only swizzle
124 // -[NSException raise] when DCHECK() is turned on (as opposed to
125 // replicating the preprocess logic which turns DCHECK() on).
126 BOOL SwizzleNSExceptionInit() {
127 gOriginalInitIMP = ObjcEvilDoers::SwizzleImplementedInstanceMethods(
128 [NSException class],
129 @selector(initWithName:reason:userInfo:),
130 @selector(chromeInitWithName:reason:userInfo:));
131 return YES;
132 }
133
134 } // namespace
135
136 @implementation CrApplication
137
138 - init {
139 // TODO(shess): Push this somewhere where it can apply to the plugin
140 // and renderer processes, and where it can intercept uncaught
141 // exceptions.
142 DCHECK(SwizzleNSExceptionInit());
143 return [super init];
144 }
145
146 // -terminate: is the entry point for orderly "quit" operations in Cocoa.
147 // This includes the application menu's quit menu item and keyboard
148 // equivalent, the application's dock icon menu's quit menu item, "quit" (not
149 // "force quit") in the Activity Monitor, and quits triggered by user logout
150 // and system restart and shutdown.
151 //
152 // The default NSApplication -terminate: implementation will end the process
153 // by calling exit(), and thus never leave the main run loop. This is
154 // unsuitable for Chrome's purposes. Chrome depends on leaving the main
155 // run loop to perform a proper orderly shutdown. This design is ingrained
156 // in the application and the assumptions that its code makes, and is
157 // entirely reasonable and works well on other platforms, but it's not
158 // compatible with the standard Cocoa quit sequence. Quits originated from
159 // within the application can be redirected to not use -terminate:, but
160 // quits from elsewhere cannot be.
161 //
162 // To allow the Cocoa-based Chrome to support the standard Cocoa -terminate:
163 // interface, and allow all quits to cause Chrome to shut down properly
164 // regardless of their origin, -terminate: is overriden. The custom
165 // -terminate: does not end the application with exit(). Instead, it simply
166 // returns after posting the normal NSApplicationWillTerminateNotification
167 // notification. The application is responsible for exiting on its own in
168 // whatever way it deems appropriate. In Chrome's case, the main run loop will
169 // end and the applicaton will exit by returning from main().
170 //
171 // This implementation of -terminate: is scaled back and is not as
172 // fully-featured as the implementation in NSApplication, nor is it a direct
173 // drop-in replacement -terminate: in most applications. It is
174 // purpose-specific to Chrome.
175 - (void)terminate:(id)sender {
176 NSApplicationTerminateReply shouldTerminate = NSTerminateNow;
177 SEL selector = @selector(applicationShouldTerminate:);
178 if ([[self delegate] respondsToSelector:selector])
179 shouldTerminate = [[self delegate] applicationShouldTerminate:self];
180
181 // If shouldTerminate is NSTerminateLater, the application is expected to
182 // call -replyToApplicationShouldTerminate: when it knows whether or not it
183 // should terminate. If the argument is YES,
184 // -replyToApplicationShouldTerminate: will call -terminate:. This will
185 // result in another call to the delegate's -applicationShouldTerminate:,
186 // which would be expected to return NSTerminateNow at that point.
187 if (shouldTerminate != NSTerminateNow)
188 return;
189
190 [[NSNotificationCenter defaultCenter]
191 postNotificationName:NSApplicationWillTerminateNotification
192 object:self];
193
194 // Return, don't exit. The application is responsible for exiting on its
195 // own.
196 }
197
198 - (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)sender {
199 // The Dock menu contains an automagic section where you can select
200 // amongst open windows. If a window is closed via JavaScript while
201 // the menu is up, the menu item for that window continues to exist.
202 // When a window is selected this method is called with the
203 // now-freed window as |aTarget|. Short-circuit the call if
204 // |aTarget| is not a valid window.
205 if (anAction == @selector(_selectWindow:)) {
206 // Not using -[NSArray containsObject:] because |aTarget| may be a
207 // freed object.
208 BOOL found = NO;
209 for (NSWindow* window in [self windows]) {
210 if (window == aTarget) {
211 found = YES;
212 break;
213 }
214 }
215 if (!found) {
216 return NO;
217 }
218 }
219
220 // When a Cocoa control is wired to a freed object, we get crashers
221 // in the call to |super| with no useful information in the
222 // backtrace. Attempt to add some useful information.
223 static const NSString* kActionKey = @"sendaction";
224
225 // If the action is something generic like -commandDispatch:, then
226 // the tag is essential.
227 NSInteger tag = 0;
228 if ([sender isKindOfClass:[NSControl class]]) {
229 tag = [sender tag];
230 if (tag == 0 || tag == -1) {
231 tag = [sender selectedTag];
232 }
233 } else if ([sender isKindOfClass:[NSMenuItem class]]) {
234 tag = [sender tag];
235 }
236
237 NSString* actionString = NSStringFromSelector(anAction);
238 NSString* value =
239 [NSString stringWithFormat:@"%@ tag %d sending %@ to %p",
240 [sender className], tag, actionString, aTarget];
241
242 ScopedCrashKey key(kActionKey, value);
243 return [super sendAction:anAction to:aTarget from:sender];
244 } 29 }
245 30
246 - (void)sendEvent:(NSEvent*)event { 31 - (void)sendEvent:(NSEvent*)event {
247 // The superclass's |sendEvent:| sends keyboard events to the menu and the key 32 chrome_application_mac::ScopedSendingEvent sendingEventScoper(self);
248 // view loop before dispatching them to |keyDown:|. Since we want to send keys
249 // to the renderer before sending them to the menu, and we never want them to
250 // the kev view loop when the web is focussed, we change this behavior.
251 if ([[self keyWindow]
252 isKindOfClass:[ChromeEventProcessingWindow class]]) {
253 if ([static_cast<ChromeEventProcessingWindow*>([self keyWindow])
254 shortcircuitEvent:event])
255 return;
256 }
257
258 [super sendEvent:event]; 33 [super sendEvent:event];
259 } 34 }
260 35
261 // NSExceptions which are caught by the event loop are logged here.
262 // NSException uses setjmp/longjmp, which can be very bad for C++, so
263 // we attempt to track and report them.
264 - (void)reportException:(NSException *)anException {
265 // If we throw an exception in this code, we can create an infinite
266 // loop. If we throw out of the if() without resetting
267 // |reportException|, we'll stop reporting exceptions for this run.
268 static BOOL reportingException = NO;
269 DCHECK(!reportingException);
270 if (!reportingException) {
271 reportingException = YES;
272 CrApplicationNSException::RecordExceptionWithUma(anException);
273
274 // Store some human-readable information in breakpad keys in case
275 // there is a crash. Since breakpad does not provide infinite
276 // storage, we track two exceptions. The first exception thrown
277 // is tracked because it may be the one which caused the system to
278 // go off the rails. The last exception thrown is tracked because
279 // it may be the one most directly associated with the crash.
280 static const NSString* kFirstExceptionKey = @"firstexception";
281 static BOOL trackedFirstException = NO;
282 static const NSString* kLastExceptionKey = @"lastexception";
283
284 // TODO(shess): It would be useful to post some stacktrace info
285 // from the exception.
286 // 10.6 has -[NSException callStackSymbols]
287 // 10.5 has -[NSException callStackReturnAddresses]
288 // 10.5 has backtrace_symbols().
289 // I've tried to combine the latter two, but got nothing useful.
290 // The addresses are right, though, maybe we could train the crash
291 // server to decode them for us.
292
293 NSString* value = [NSString stringWithFormat:@"%@ reason %@",
294 [anException name], [anException reason]];
295 if (!trackedFirstException) {
296 SetCrashKeyValue(kFirstExceptionKey, value);
297 trackedFirstException = YES;
298 } else {
299 SetCrashKeyValue(kLastExceptionKey, value);
300 }
301
302 reportingException = NO;
303 }
304
305 [super reportException:anException];
306 }
307
308 @end 36 @end
309 37
310 namespace CrApplicationCC { 38 namespace chrome_application_mac {
311 39
312 void Terminate() { 40 ScopedSendingEvent::ScopedSendingEvent(CrApplication* app) : app_(app) {
313 [NSApp terminate:nil]; 41 handling_ = [app_ isHandlingSendEvent];
42 [app_ setHandlingSendEvent:YES];
314 } 43 }
315 44
316 } // namespace CrApplicationCC 45 ScopedSendingEvent::~ScopedSendingEvent() {
46 [app_ setHandlingSendEvent:handling_];
47 }
48
49 } // namespace chrome_application_mac
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698