OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 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_browser_application_mac.h" | 5 #import "chrome/browser/chrome_browser_application_mac.h" |
6 | 6 |
7 #import "base/histogram.h" | 7 #import "base/histogram.h" |
8 #import "base/logging.h" | 8 #import "base/logging.h" |
9 #import "base/scoped_nsobject.h" | 9 #import "base/scoped_nsobject.h" |
10 #import "base/sys_string_conversions.h" | 10 #import "base/sys_string_conversions.h" |
11 #import "chrome/app/breakpad_mac.h" | 11 #import "chrome/app/breakpad_mac.h" |
| 12 #import "chrome/browser/app_controller_mac.h" |
12 #import "chrome/browser/cocoa/objc_method_swizzle.h" | 13 #import "chrome/browser/cocoa/objc_method_swizzle.h" |
13 | 14 |
14 // The implementation of NSExceptions break various assumptions in the | 15 // The implementation of NSExceptions break various assumptions in the |
15 // Chrome code. This category defines a replacement for | 16 // Chrome code. This category defines a replacement for |
16 // -initWithName:reason:userInfo: for purposes of forcing a break in | 17 // -initWithName:reason:userInfo: for purposes of forcing a break in |
17 // the debugger when an exception is raised. -raise sounds more | 18 // the debugger when an exception is raised. -raise sounds more |
18 // obvious to intercept, but it doesn't catch the original throw | 19 // obvious to intercept, but it doesn't catch the original throw |
19 // because the objc runtime doesn't use it. | 20 // because the objc runtime doesn't use it. |
20 @interface NSException (NSExceptionSwizzle) | 21 @interface NSException (NSExceptionSwizzle) |
21 - (id)chromeInitWithName:(NSString *)aName | 22 - (id)chromeInitWithName:(NSString *)aName |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
113 | 114 |
114 void RecordExceptionWithUma(NSException* exception) { | 115 void RecordExceptionWithUma(NSException* exception) { |
115 UMA_HISTOGRAM_ENUMERATION("OSX.NSException", | 116 UMA_HISTOGRAM_ENUMERATION("OSX.NSException", |
116 BinForException(exception), kUnknownNSException); | 117 BinForException(exception), kUnknownNSException); |
117 } | 118 } |
118 | 119 |
119 void Terminate() { | 120 void Terminate() { |
120 [NSApp terminate:nil]; | 121 [NSApp terminate:nil]; |
121 } | 122 } |
122 | 123 |
| 124 void CancelTerminate() { |
| 125 [NSApp cancelTerminate:nil]; |
| 126 } |
| 127 |
123 } // namespace chrome_browser_application_mac | 128 } // namespace chrome_browser_application_mac |
124 | 129 |
125 namespace { | 130 namespace { |
126 | 131 |
127 // Helper to make it easy to get crash keys right. | 132 // Helper to make it easy to get crash keys right. |
128 // TODO(shess): Find a better home for this. app/breakpad_mac.h | 133 // TODO(shess): Find a better home for this. app/breakpad_mac.h |
129 // doesn't work. | 134 // doesn't work. |
130 class ScopedCrashKey { | 135 class ScopedCrashKey { |
131 public: | 136 public: |
132 ScopedCrashKey(NSString* key, NSString* value) | 137 ScopedCrashKey(NSString* key, NSString* value) |
(...skipping 21 matching lines...) Expand all Loading... |
154 | 159 |
155 } // namespace | 160 } // namespace |
156 | 161 |
157 @implementation BrowserCrApplication | 162 @implementation BrowserCrApplication |
158 | 163 |
159 - init { | 164 - init { |
160 DCHECK(SwizzleNSExceptionInit()); | 165 DCHECK(SwizzleNSExceptionInit()); |
161 return [super init]; | 166 return [super init]; |
162 } | 167 } |
163 | 168 |
164 // -terminate: is the entry point for orderly "quit" operations in Cocoa. | 169 //////////////////////////////////////////////////////////////////////////////// |
165 // This includes the application menu's quit menu item and keyboard | 170 // HISTORICAL COMMENT (by viettrungluu, from |
166 // equivalent, the application's dock icon menu's quit menu item, "quit" (not | 171 // http://codereview.chromium.org/1520006 with mild editing): |
167 // "force quit") in the Activity Monitor, and quits triggered by user logout | |
168 // and system restart and shutdown. | |
169 // | 172 // |
170 // The default NSApplication -terminate: implementation will end the process | 173 // A quick summary of the state of things (before the changes to shutdown): |
171 // by calling exit(), and thus never leave the main run loop. This is | |
172 // unsuitable for Chrome's purposes. Chrome depends on leaving the main | |
173 // run loop to perform a proper orderly shutdown. This design is ingrained | |
174 // in the application and the assumptions that its code makes, and is | |
175 // entirely reasonable and works well on other platforms, but it's not | |
176 // compatible with the standard Cocoa quit sequence. Quits originated from | |
177 // within the application can be redirected to not use -terminate:, but | |
178 // quits from elsewhere cannot be. | |
179 // | 174 // |
180 // To allow the Cocoa-based Chrome to support the standard Cocoa -terminate: | 175 // Currently, we are totally hosed (put in a bad state in which Cmd-W does the |
181 // interface, and allow all quits to cause Chrome to shut down properly | 176 // wrong thing, and which will probably eventually lead to a crash) if we begin |
182 // regardless of their origin, -terminate: is overriden. The custom | 177 // quitting but termination is aborted for some reason. |
183 // -terminate: does not end the application with exit(). Instead, it simply | |
184 // returns after posting the normal NSApplicationWillTerminateNotification | |
185 // notification. The application is responsible for exiting on its own in | |
186 // whatever way it deems appropriate. In Chrome's case, the main run loop will | |
187 // end and the applicaton will exit by returning from main(). | |
188 // | 178 // |
189 // This implementation of -terminate: is scaled back and is not as | 179 // I currently know of two ways in which termination can be aborted: |
190 // fully-featured as the implementation in NSApplication, nor is it a direct | 180 // (1) Common case: a window has an onbeforeunload handler which pops up a |
191 // drop-in replacement -terminate: in most applications. It is | 181 // "leave web page" dialog, and the user answers "no, don't leave". |
192 // purpose-specific to Chrome. | 182 // (2) Uncommon case: popups are enabled (in Content Settings, i.e., the popup |
| 183 // blocker is disabled), and some nasty web page pops up a new window on |
| 184 // closure. |
| 185 // |
| 186 // I don't know of other ways in which termination can be aborted, but they may |
| 187 // exist (or may be added in the future, for that matter). |
| 188 // |
| 189 // My CL [see above] does the following: |
| 190 // a. Should prevent being put in a bad state (which breaks Cmd-W and leads to |
| 191 // crash) under all circumstances. |
| 192 // b. Should completely handle (1) properly. |
| 193 // c. Doesn't (yet) handle (2) properly and puts it in a weird state (but not |
| 194 // that bad). |
| 195 // d. Any other ways of aborting termination would put it in that weird state. |
| 196 // |
| 197 // c. can be fixed by having the global flag reset on browser creation or |
| 198 // similar (and doing so might also fix some possible d.'s as well). I haven't |
| 199 // done this yet since I haven't thought about it carefully and since it's a |
| 200 // corner case. |
| 201 // |
| 202 // The weird state: a state in which closing the last window quits the browser. |
| 203 // This might be a bit annoying, but it's not dangerous in any way. |
| 204 //////////////////////////////////////////////////////////////////////////////// |
| 205 |
| 206 // |-terminate:| is the entry point for orderly "quit" operations in Cocoa. This |
| 207 // includes the application menu's quit menu item and keyboard equivalent, the |
| 208 // application's dock icon menu's quit menu item, "quit" (not "force quit") in |
| 209 // the Activity Monitor, and quits triggered by user logout and system restart |
| 210 // and shutdown. |
| 211 // |
| 212 // The default |-terminate:| implementation ends the process by calling exit(), |
| 213 // and thus never leaves the main run loop. This is unsuitable for Chrome since |
| 214 // Chrome depends on leaving the main run loop to perform an orderly shutdown. |
| 215 // We support the normal |-terminate:| interface by overriding the default |
| 216 // implementation. Our implementation, which is very specific to the needs of |
| 217 // Chrome, works by asking the application delegate to terminate using its |
| 218 // |-tryToTerminateApplication:| method. |
| 219 // |
| 220 // |-tryToTerminateApplication:| differs from the standard |
| 221 // |-applicationShouldTerminate:| in that no special event loop is run in the |
| 222 // case that immediate termination is not possible (e.g., if dialog boxes |
| 223 // allowing the user to cancel have to be shown). Instead, this method sets a |
| 224 // flag and tries to close all browsers. This flag causes the closure of the |
| 225 // final browser window to begin actual tear-down of the application. |
| 226 // Termination is cancelled by resetting this flag. The standard |
| 227 // |-applicationShouldTerminate:| is not supported, and code paths leading to it |
| 228 // must be redirected. |
193 - (void)terminate:(id)sender { | 229 - (void)terminate:(id)sender { |
194 NSApplicationTerminateReply shouldTerminate = NSTerminateNow; | 230 AppController* appController = static_cast<AppController*>([NSApp delegate]); |
195 SEL selector = @selector(applicationShouldTerminate:); | 231 if ([appController tryToTerminateApplication:self]) { |
196 if ([[self delegate] respondsToSelector:selector]) | 232 [[NSNotificationCenter defaultCenter] |
197 shouldTerminate = [[self delegate] applicationShouldTerminate:self]; | 233 postNotificationName:NSApplicationWillTerminateNotification |
| 234 object:self]; |
| 235 } |
198 | 236 |
199 // If shouldTerminate is NSTerminateLater, the application is expected to | 237 // Return, don't exit. The application is responsible for exiting on its own. |
200 // call -replyToApplicationShouldTerminate: when it knows whether or not it | 238 } |
201 // should terminate. If the argument is YES, | |
202 // -replyToApplicationShouldTerminate: will call -terminate:. This will | |
203 // result in another call to the delegate's -applicationShouldTerminate:, | |
204 // which would be expected to return NSTerminateNow at that point. | |
205 if (shouldTerminate != NSTerminateNow) | |
206 return; | |
207 | 239 |
208 [[NSNotificationCenter defaultCenter] | 240 - (void)cancelTerminate:(id)sender { |
209 postNotificationName:NSApplicationWillTerminateNotification | 241 AppController* appController = static_cast<AppController*>([NSApp delegate]); |
210 object:self]; | 242 [appController stopTryingToTerminateApplication:self]; |
211 | |
212 // Return, don't exit. The application is responsible for exiting on its | |
213 // own. | |
214 } | 243 } |
215 | 244 |
216 - (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)sender { | 245 - (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)sender { |
217 // The Dock menu contains an automagic section where you can select | 246 // The Dock menu contains an automagic section where you can select |
218 // amongst open windows. If a window is closed via JavaScript while | 247 // amongst open windows. If a window is closed via JavaScript while |
219 // the menu is up, the menu item for that window continues to exist. | 248 // the menu is up, the menu item for that window continues to exist. |
220 // When a window is selected this method is called with the | 249 // When a window is selected this method is called with the |
221 // now-freed window as |aTarget|. Short-circuit the call if | 250 // now-freed window as |aTarget|. Short-circuit the call if |
222 // |aTarget| is not a valid window. | 251 // |aTarget| is not a valid window. |
223 if (anAction == @selector(_selectWindow:)) { | 252 if (anAction == @selector(_selectWindow:)) { |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
302 SetCrashKeyValue(kLastExceptionKey, value); | 331 SetCrashKeyValue(kLastExceptionKey, value); |
303 } | 332 } |
304 | 333 |
305 reportingException = NO; | 334 reportingException = NO; |
306 } | 335 } |
307 | 336 |
308 [super reportException:anException]; | 337 [super reportException:anException]; |
309 } | 338 } |
310 | 339 |
311 @end | 340 @end |
OLD | NEW |