[M120 Migration]Fix for crash during chrome exit
[platform/framework/web/chromium-efl.git] / chrome / browser / chrome_browser_application_mac.mm
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "chrome/browser/chrome_browser_application_mac.h"
6
7 #include <Carbon/Carbon.h>  // for <HIToolbox/Events.h>
8
9 #include "base/apple/call_with_eh_frame.h"
10 #include "base/check.h"
11 #include "base/command_line.h"
12 #include "base/observer_list.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "base/trace_event/trace_event.h"
16 #import "chrome/browser/app_controller_mac.h"
17 #import "chrome/browser/mac/exception_processor.h"
18 #include "chrome/browser/ui/cocoa/l10n_util.h"
19 #include "chrome/common/chrome_switches.h"
20 #include "components/crash/core/common/crash_key.h"
21 #import "components/crash/core/common/objc_zombie.h"
22 #include "content/public/browser/browser_accessibility_state.h"
23 #include "content/public/browser/native_event_processor_mac.h"
24 #include "content/public/browser/native_event_processor_observer_mac.h"
25 #include "ui/base/cocoa/accessibility_focus_overrider.h"
26
27 namespace chrome_browser_application_mac {
28
29 void RegisterBrowserCrApp() {
30   [BrowserCrApplication sharedApplication];
31
32   // If there was an invocation to NSApp prior to this method, then the NSApp
33   // will not be a BrowserCrApplication, but will instead be an NSApplication.
34   // This is undesirable and we must enforce that this doesn't happen.
35   CHECK([NSApp isKindOfClass:[BrowserCrApplication class]]);
36 }
37
38 void InitializeHeadlessMode() {
39   // In headless mode the browser window exists but is always hidden, so there
40   // is no point in showing dock icon and menu bar.
41   NSApp.activationPolicy = NSApplicationActivationPolicyAccessory;
42 }
43
44 void Terminate() {
45   [NSApp terminate:nil];
46 }
47
48 void CancelTerminate() {
49   [NSApp cancelTerminate:nil];
50 }
51
52 }  // namespace chrome_browser_application_mac
53
54 namespace {
55
56 // Calling -[NSEvent description] is rather slow to build up the event
57 // description. The description is stored in a crash key to aid debugging, so
58 // this helper function constructs a shorter, but still useful, description.
59 // See <https://crbug.com/770405>.
60 std::string DescriptionForNSEvent(NSEvent* event) {
61   std::string desc = base::StringPrintf(
62       "NSEvent type=%ld modifierFlags=0x%lx locationInWindow=(%g,%g)",
63       event.type, event.modifierFlags, event.locationInWindow.x,
64       event.locationInWindow.y);
65   switch (event.type) {
66     case NSEventTypeKeyDown:
67     case NSEventTypeKeyUp: {
68       // Some NSEvents return a string with NUL in event.characters, see
69       // <https://crbug.com/826908>. To make matters worse, in rare cases,
70       // NSEvent.characters or NSEvent.charactersIgnoringModifiers can throw an
71       // NSException complaining that "TSMProcessRawKeyCode failed". Since we're
72       // trying to gather a crash key here, if that exception happens, just
73       // remark that it happened and continue rather than crashing the browser.
74       std::string characters, unmodified_characters;
75       @try {
76         characters = base::SysNSStringToUTF8([event.characters
77             stringByReplacingOccurrencesOfString:@"\0"
78                                       withString:@"\\x00"]);
79         unmodified_characters =
80             base::SysNSStringToUTF8([event.charactersIgnoringModifiers
81                 stringByReplacingOccurrencesOfString:@"\0"
82                                           withString:@"\\x00"]);
83       } @catch (id exception) {
84         characters = "(exception)";
85         unmodified_characters = "(exception)";
86       }
87       desc += base::StringPrintf(
88           " keyCode=0x%d ARepeat=%d characters='%s' unmodifiedCharacters='%s'",
89           event.keyCode, event.ARepeat, characters.c_str(),
90           unmodified_characters.c_str());
91       break;
92     }
93     case NSEventTypeLeftMouseDown:
94     case NSEventTypeLeftMouseDragged:
95     case NSEventTypeLeftMouseUp:
96     case NSEventTypeOtherMouseDown:
97     case NSEventTypeOtherMouseDragged:
98     case NSEventTypeOtherMouseUp:
99     case NSEventTypeRightMouseDown:
100     case NSEventTypeRightMouseDragged:
101     case NSEventTypeRightMouseUp:
102       desc += base::StringPrintf(" buttonNumber=%ld clickCount=%ld",
103                                  event.buttonNumber, event.clickCount);
104       break;
105     case NSEventTypeAppKitDefined:
106     case NSEventTypeSystemDefined:
107     case NSEventTypeApplicationDefined:
108     case NSEventTypePeriodic:
109       desc += base::StringPrintf(" subtype=%d data1=%ld data2=%ld",
110                                  event.subtype, event.data1, event.data2);
111       break;
112     default:
113       break;
114   }
115   return desc;
116 }
117
118 }  // namespace
119
120 @interface BrowserCrApplication () <NativeEventProcessor>
121 @end
122
123 @implementation BrowserCrApplication {
124   base::ObserverList<content::NativeEventProcessorObserver>::Unchecked
125       _observers;
126   BOOL _handlingSendEvent;
127 }
128
129 + (void)initialize {
130   // Turn all deallocated Objective-C objects into zombies, keeping
131   // the most recent 10,000 of them on the treadmill.
132   ObjcEvilDoers::ZombieEnable(true, 10000);
133
134   chrome::InstallObjcExceptionPreprocessor();
135
136   cocoa_l10n_util::ApplyForcedRTL();
137 }
138
139 // Initialize NSApplication using the custom subclass.  Check whether NSApp
140 // was already initialized using another class, because that would break
141 // some things.
142 + (NSApplication*)sharedApplication {
143   NSApplication* app = [super sharedApplication];
144
145   // +sharedApplication initializes the global NSApp, so if a specific
146   // NSApplication subclass is requested, require that to be the one
147   // delivered.  The practical effect is to require a consistent NSApp
148   // across the executable.
149   CHECK([NSApp isKindOfClass:self])
150       << "NSApp must be of type " << [[self className] UTF8String]
151       << ", not " << [[NSApp className] UTF8String];
152
153   // If the message loop was initialized before NSApp is setup, the
154   // message pump will be setup incorrectly.  Failing this implies
155   // that RegisterBrowserCrApp() should be called earlier.
156   CHECK(base::message_pump_apple::UsingCrApp())
157       << "message_pump_apple::Create() is using the wrong pump implementation"
158       << " for " << [[self className] UTF8String];
159
160   return app;
161 }
162
163 ////////////////////////////////////////////////////////////////////////////////
164 // HISTORICAL COMMENT (by viettrungluu, from
165 // http://codereview.chromium.org/1520006 with mild editing):
166 //
167 // A quick summary of the state of things (before the changes to shutdown):
168 //
169 // Currently, we are totally hosed (put in a bad state in which Cmd-W does the
170 // wrong thing, and which will probably eventually lead to a crash) if we begin
171 // quitting but termination is aborted for some reason.
172 //
173 // I currently know of two ways in which termination can be aborted:
174 // (1) Common case: a window has an onbeforeunload handler which pops up a
175 //     "leave web page" dialog, and the user answers "no, don't leave".
176 // (2) Uncommon case: popups are enabled (in Content Settings, i.e., the popup
177 //     blocker is disabled), and some nasty web page pops up a new window on
178 //     closure.
179 //
180 // I don't know of other ways in which termination can be aborted, but they may
181 // exist (or may be added in the future, for that matter).
182 //
183 // My CL [see above] does the following:
184 // a. Should prevent being put in a bad state (which breaks Cmd-W and leads to
185 //    crash) under all circumstances.
186 // b. Should completely handle (1) properly.
187 // c. Doesn't (yet) handle (2) properly and puts it in a weird state (but not
188 //    that bad).
189 // d. Any other ways of aborting termination would put it in that weird state.
190 //
191 // c. can be fixed by having the global flag reset on browser creation or
192 // similar (and doing so might also fix some possible d.'s as well). I haven't
193 // done this yet since I haven't thought about it carefully and since it's a
194 // corner case.
195 //
196 // The weird state: a state in which closing the last window quits the browser.
197 // This might be a bit annoying, but it's not dangerous in any way.
198 ////////////////////////////////////////////////////////////////////////////////
199
200 // |-terminate:| is the entry point for orderly "quit" operations in Cocoa. This
201 // includes the application menu's quit menu item and keyboard equivalent, the
202 // application's dock icon menu's quit menu item, "quit" (not "force quit") in
203 // the Activity Monitor, and quits triggered by user logout and system restart
204 // and shutdown.
205 //
206 // The default |-terminate:| implementation ends the process by calling exit(),
207 // and thus never leaves the main run loop. This is unsuitable for Chrome since
208 // Chrome depends on leaving the main run loop to perform an orderly shutdown.
209 // We support the normal |-terminate:| interface by overriding the default
210 // implementation. Our implementation, which is very specific to the needs of
211 // Chrome, works by asking the application delegate to terminate using its
212 // |-tryToTerminateApplication:| method.
213 //
214 // |-tryToTerminateApplication:| differs from the standard
215 // |-applicationShouldTerminate:| in that no special event loop is run in the
216 // case that immediate termination is not possible (e.g., if dialog boxes
217 // allowing the user to cancel have to be shown). Instead, this method sets a
218 // flag and tries to close all browsers. This flag causes the closure of the
219 // final browser window to begin actual tear-down of the application.
220 // Termination is cancelled by resetting this flag. The standard
221 // |-applicationShouldTerminate:| is not supported, and code paths leading to it
222 // must be redirected.
223 //
224 // When the last browser has been destroyed, the BrowserList calls
225 // chrome::OnAppExiting(), which is the point of no return. That will cause
226 // the NSApplicationWillTerminateNotification to be posted, which ends the
227 // NSApplication event loop, so final post- MessageLoop::Run() work is done
228 // before exiting.
229 - (void)terminate:(id)sender {
230   [AppController.sharedController tryToTerminateApplication:self];
231   // Return, don't exit. The application is responsible for exiting on its own.
232 }
233
234 - (void)cancelTerminate:(id)sender {
235   [AppController.sharedController stopTryingToTerminateApplication:self];
236 }
237
238 - (NSEvent*)nextEventMatchingMask:(NSEventMask)mask
239                         untilDate:(NSDate*)expiration
240                            inMode:(NSString*)mode
241                           dequeue:(BOOL)dequeue {
242   __block NSEvent* event = nil;
243   base::apple::CallWithEHFrame(^{
244     event = [super nextEventMatchingMask:mask
245                                untilDate:expiration
246                                   inMode:mode
247                                  dequeue:dequeue];
248   });
249   return event;
250 }
251
252 - (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)sender {
253   // The Dock menu contains an automagic section where you can select
254   // amongst open windows.  If a window is closed via JavaScript while
255   // the menu is up, the menu item for that window continues to exist.
256   // When a window is selected this method is called with the
257   // now-freed window as |aTarget|.  Short-circuit the call if
258   // |aTarget| is not a valid window.
259   if (anAction == @selector(_selectWindow:)) {
260     // Not using -[NSArray containsObject:] because |aTarget| may be a
261     // freed object.
262     BOOL found = NO;
263     for (NSWindow* window in [self windows]) {
264       if (window == aTarget) {
265         found = YES;
266         break;
267       }
268     }
269     if (!found) {
270       return NO;
271     }
272   }
273
274   // When a Cocoa control is wired to a freed object, we get crashers
275   // in the call to |super| with no useful information in the
276   // backtrace.  Attempt to add some useful information.
277
278   // If the action is something generic like -commandDispatch:, then
279   // the tag is essential.
280   NSInteger tag = 0;
281   if ([sender isKindOfClass:[NSControl class]]) {
282     tag = [sender tag];
283     if (tag == 0 || tag == -1) {
284       tag = [sender selectedTag];
285     }
286   } else if ([sender isKindOfClass:[NSMenuItem class]]) {
287     tag = [sender tag];
288   }
289
290   NSString* actionString = NSStringFromSelector(anAction);
291   std::string value = base::StringPrintf("%s tag %ld sending %s to %p",
292       [[sender className] UTF8String],
293       static_cast<long>(tag),
294       [actionString UTF8String],
295       aTarget);
296
297   static crash_reporter::CrashKeyString<256> sendActionKey("sendaction");
298   crash_reporter::ScopedCrashKeyString scopedKey(&sendActionKey, value);
299
300   __block BOOL rv;
301   base::apple::CallWithEHFrame(^{
302     rv = [super sendAction:anAction to:aTarget from:sender];
303   });
304   return rv;
305 }
306
307 - (BOOL)isHandlingSendEvent {
308   return _handlingSendEvent;
309 }
310
311 - (void)setHandlingSendEvent:(BOOL)handlingSendEvent {
312   _handlingSendEvent = handlingSendEvent;
313 }
314
315 - (void)sendEvent:(NSEvent*)event {
316   TRACE_EVENT0("toplevel", "BrowserCrApplication::sendEvent");
317
318   // TODO(bokan): Tracing added temporarily to diagnose crbug.com/1039833.
319   TRACE_EVENT_INSTANT1("toplevel", "KeyWindow", TRACE_EVENT_SCOPE_THREAD,
320                        "KeyWin", [[NSApp keyWindow] windowNumber]);
321
322   static crash_reporter::CrashKeyString<256> nseventKey("nsevent");
323   crash_reporter::ScopedCrashKeyString scopedKey(&nseventKey,
324                                                  DescriptionForNSEvent(event));
325
326   base::apple::CallWithEHFrame(^{
327     static const bool kKioskMode =
328         base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
329     if (kKioskMode) {
330       // In kiosk mode, we want to prevent context menus from appearing,
331       // so simply discard menu-generating events instead of passing them
332       // along.
333       BOOL couldTriggerContextMenu =
334           event.type == NSEventTypeRightMouseDown ||
335           (event.type == NSEventTypeLeftMouseDown &&
336            (event.modifierFlags & NSEventModifierFlagControl));
337       if (couldTriggerContextMenu)
338         return;
339     }
340     base::mac::ScopedSendingEvent sendingEventScoper;
341     content::ScopedNotifyNativeEventProcessorObserver scopedObserverNotifier(
342         &self->_observers, event);
343     // Mac Eisu and Kana keydown events are by default swallowed by sendEvent
344     // and sent directly to IME, which prevents ui keydown events from firing.
345     // These events need to be sent to [NSApp keyWindow] for handling.
346     if (event.type == NSEventTypeKeyDown &&
347         (event.keyCode == kVK_JIS_Eisu || event.keyCode == kVK_JIS_Kana)) {
348       [NSApp.keyWindow sendEvent:event];
349     } else {
350       [super sendEvent:event];
351     }
352   });
353 }
354
355 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
356   // This is an undocumented attribute that's set when VoiceOver is turned
357   // on/off.
358   if ([attribute isEqualToString:@"AXEnhancedUserInterface"]) {
359     content::BrowserAccessibilityState* accessibility_state =
360         content::BrowserAccessibilityState::GetInstance();
361     if ([value intValue] == 1)
362       accessibility_state->OnScreenReaderDetected();
363     else
364       accessibility_state->OnScreenReaderStopped();
365   }
366   return [super accessibilitySetValue:value forAttribute:attribute];
367 }
368
369 - (id)accessibilityFocusedUIElement {
370   if (id forced_focus = ui::AccessibilityFocusOverrider::GetFocusedUIElement())
371     return forced_focus;
372   return [super accessibilityFocusedUIElement];
373 }
374
375 - (NSAccessibilityRole)accessibilityRole {
376   // For non-VoiceOver AT, such as Voice Control, Apple recommends turning on
377   // a11y when an AT accesses the 'accessibilityRole' property. This function
378   // is accessed frequently so we only change the accessibility state when
379   // accessibility is disabled.
380   content::BrowserAccessibilityState* accessibility_state =
381       content::BrowserAccessibilityState::GetInstance();
382   if (!accessibility_state->GetAccessibilityMode().has_mode(
383           ui::kAXModeBasic.flags())) {
384     accessibility_state->AddAccessibilityModeFlags(ui::kAXModeBasic);
385   }
386   return [super accessibilityRole];
387 }
388
389 - (void)addNativeEventProcessorObserver:
390     (content::NativeEventProcessorObserver*)observer {
391   _observers.AddObserver(observer);
392 }
393
394 - (void)removeNativeEventProcessorObserver:
395     (content::NativeEventProcessorObserver*)observer {
396   _observers.RemoveObserver(observer);
397 }
398
399 @end