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