Home | History | Annotate | Download | only in browser
      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.
      4 
      5 #import "chrome/browser/chrome_browser_application_mac.h"
      6 
      7 #import "base/auto_reset.h"
      8 #include "base/debug/crash_logging.h"
      9 #include "base/debug/stack_trace.h"
     10 #import "base/logging.h"
     11 #import "base/mac/scoped_nsexception_enabler.h"
     12 #import "base/mac/scoped_nsobject.h"
     13 #import "base/metrics/histogram.h"
     14 #include "base/strings/stringprintf.h"
     15 #import "base/strings/sys_string_conversions.h"
     16 #import "chrome/browser/app_controller_mac.h"
     17 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
     18 #include "chrome/common/crash_keys.h"
     19 #import "chrome/common/mac/objc_method_swizzle.h"
     20 #import "chrome/common/mac/objc_zombie.h"
     21 #include "content/public/browser/browser_accessibility_state.h"
     22 #include "content/public/browser/render_view_host.h"
     23 #include "content/public/browser/web_contents.h"
     24 
     25 // The implementation of NSExceptions break various assumptions in the
     26 // Chrome code.  This category defines a replacement for
     27 // -initWithName:reason:userInfo: for purposes of forcing a break in
     28 // the debugger when an exception is raised.  -raise sounds more
     29 // obvious to intercept, but it doesn't catch the original throw
     30 // because the objc runtime doesn't use it.
     31 @interface NSException (CrNSExceptionSwizzle)
     32 - (id)crInitWithName:(NSString*)aName
     33               reason:(NSString*)aReason
     34             userInfo:(NSDictionary*)someUserInfo;
     35 @end
     36 
     37 static IMP gOriginalInitIMP = NULL;
     38 
     39 @implementation NSException (CrNSExceptionSwizzle)
     40 - (id)crInitWithName:(NSString*)aName
     41               reason:(NSString*)aReason
     42             userInfo:(NSDictionary*)someUserInfo {
     43   // Method only called when swizzled.
     44   DCHECK(_cmd == @selector(initWithName:reason:userInfo:));
     45 
     46   // Parts of Cocoa rely on creating and throwing exceptions. These are not
     47   // worth bugging-out over. It is very important that there be zero chance that
     48   // any Chromium code is on the stack; these must be created by Apple code and
     49   // then immediately consumed by Apple code.
     50   static NSString* const kAcceptableNSExceptionNames[] = {
     51     // If an object does not support an accessibility attribute, this will
     52     // get thrown.
     53     NSAccessibilityException,
     54 
     55     nil
     56   };
     57 
     58   BOOL found = NO;
     59   for (int i = 0; kAcceptableNSExceptionNames[i]; ++i) {
     60     if (aName == kAcceptableNSExceptionNames[i]) {
     61       found = YES;
     62     }
     63   }
     64 
     65   if (!found) {
     66     // Update breakpad with the exception info.
     67     std::string value = base::StringPrintf("%s reason %s",
     68         [aName UTF8String], [aReason UTF8String]);
     69     base::debug::SetCrashKeyValue(crash_keys::mac::kNSException, value);
     70     base::debug::SetCrashKeyToStackTrace(crash_keys::mac::kNSExceptionTrace,
     71                                          base::debug::StackTrace());
     72 
     73     // Force crash for selected exceptions to generate crash dumps.
     74     BOOL fatal = NO;
     75     if (aName == NSInternalInconsistencyException) {
     76       NSString* const kNSMenuItemArrayBoundsCheck =
     77           @"Invalid parameter not satisfying: (index >= 0) && "
     78           @"(index < [_itemArray count])";
     79       if ([aReason isEqualToString:kNSMenuItemArrayBoundsCheck]) {
     80         fatal = YES;
     81       }
     82 
     83       NSString* const kNoWindowCheck = @"View is not in any window";
     84       if ([aReason isEqualToString:kNoWindowCheck]) {
     85         fatal = YES;
     86       }
     87     }
     88 
     89     // Mostly "unrecognized selector sent to (instance|class)".  A
     90     // very small number of things like inappropriate nil being passed.
     91     if (aName == NSInvalidArgumentException) {
     92       fatal = YES;
     93 
     94       // TODO(shess): http://crbug.com/85463 throws this exception
     95       // from ImageKit.  Our code is not on the stack, so it needs to
     96       // be whitelisted for now.
     97       NSString* const kNSURLInitNilCheck =
     98           @"*** -[NSURL initFileURLWithPath:isDirectory:]: "
     99           @"nil string parameter";
    100       if ([aReason isEqualToString:kNSURLInitNilCheck]) {
    101         fatal = NO;
    102       }
    103     }
    104 
    105     // Dear reader: Something you just did provoked an NSException.
    106     // NSException is implemented in terms of setjmp()/longjmp(),
    107     // which does poor things when combined with C++ scoping
    108     // (destructors are skipped).  Chrome should be NSException-free,
    109     // please check your backtrace and see if you can't file a bug
    110     // with a repro case.
    111     const bool allow = base::mac::GetNSExceptionsAllowed();
    112     if (fatal && !allow) {
    113       LOG(FATAL) << "Someone is trying to raise an exception!  "
    114                  << value;
    115     } else {
    116       // Make sure that developers see when their code throws
    117       // exceptions.
    118       DCHECK(allow) << "Someone is trying to raise an exception!  "
    119                     << value;
    120     }
    121   }
    122 
    123   // Forward to the original version.
    124   return gOriginalInitIMP(self, _cmd, aName, aReason, someUserInfo);
    125 }
    126 @end
    127 
    128 namespace chrome_browser_application_mac {
    129 
    130 // Maximum number of known named exceptions we'll support.  There is
    131 // no central registration, but I only find about 75 possibilities in
    132 // the system frameworks, and many of them are probably not
    133 // interesting to track in aggregate (those relating to distributed
    134 // objects, for instance).
    135 const size_t kKnownNSExceptionCount = 25;
    136 
    137 const size_t kUnknownNSException = kKnownNSExceptionCount;
    138 
    139 size_t BinForException(NSException* exception) {
    140   // A list of common known exceptions.  The list position will
    141   // determine where they live in the histogram, so never move them
    142   // around, only add to the end.
    143   static NSString* const kKnownNSExceptionNames[] = {
    144     // Grab-bag exception, not very common.  CFArray (or other
    145     // container) mutated while being enumerated is one case seen in
    146     // production.
    147     NSGenericException,
    148 
    149     // Out-of-range on NSString or NSArray.  Quite common.
    150     NSRangeException,
    151 
    152     // Invalid arg to method, unrecognized selector.  Quite common.
    153     NSInvalidArgumentException,
    154 
    155     // malloc() returned null in object creation, I think.  Turns out
    156     // to be very uncommon in production, because of the OOM killer.
    157     NSMallocException,
    158 
    159     // This contains things like windowserver errors, trying to draw
    160     // views which aren't in windows, unable to read nib files.  By
    161     // far the most common exception seen on the crash server.
    162     NSInternalInconsistencyException,
    163 
    164     nil
    165   };
    166 
    167   // Make sure our array hasn't outgrown our abilities to track it.
    168   DCHECK_LE(arraysize(kKnownNSExceptionNames), kKnownNSExceptionCount);
    169 
    170   NSString* name = [exception name];
    171   for (int i = 0; kKnownNSExceptionNames[i]; ++i) {
    172     if (name == kKnownNSExceptionNames[i]) {
    173       return i;
    174     }
    175   }
    176   return kUnknownNSException;
    177 }
    178 
    179 void RecordExceptionWithUma(NSException* exception) {
    180   UMA_HISTOGRAM_ENUMERATION("OSX.NSException",
    181       BinForException(exception), kUnknownNSException);
    182 }
    183 
    184 void RegisterBrowserCrApp() {
    185   [BrowserCrApplication sharedApplication];
    186 };
    187 
    188 void Terminate() {
    189   [NSApp terminate:nil];
    190 }
    191 
    192 void CancelTerminate() {
    193   [NSApp cancelTerminate:nil];
    194 }
    195 
    196 }  // namespace chrome_browser_application_mac
    197 
    198 namespace {
    199 
    200 void SwizzleInit() {
    201   // Do-nothing wrapper so that we can arrange to only swizzle
    202   // -[NSException raise] when DCHECK() is turned on (as opposed to
    203   // replicating the preprocess logic which turns DCHECK() on).
    204   gOriginalInitIMP = ObjcEvilDoers::SwizzleImplementedInstanceMethods(
    205       [NSException class],
    206       @selector(initWithName:reason:userInfo:),
    207       @selector(crInitWithName:reason:userInfo:));
    208 }
    209 
    210 }  // namespace
    211 
    212 // These methods are being exposed for the purposes of overriding.
    213 // Used to determine when a Panel window can become the key window.
    214 @interface NSApplication (PanelsCanBecomeKey)
    215 - (void)_cycleWindowsReversed:(BOOL)arg1;
    216 - (id)_removeWindow:(NSWindow*)window;
    217 - (id)_setKeyWindow:(NSWindow*)window;
    218 @end
    219 
    220 @interface BrowserCrApplication (PrivateInternal)
    221 
    222 // This must be called under the protection of previousKeyWindowsLock_.
    223 - (void)removePreviousKeyWindow:(NSWindow*)window;
    224 
    225 @end
    226 
    227 @implementation BrowserCrApplication
    228 
    229 + (void)initialize {
    230   // Turn all deallocated Objective-C objects into zombies, keeping
    231   // the most recent 10,000 of them on the treadmill.
    232   ObjcEvilDoers::ZombieEnable(true, 10000);
    233 }
    234 
    235 - (id)init {
    236   SwizzleInit();
    237   self = [super init];
    238 
    239   // Sanity check to alert if overridden methods are not supported.
    240   DCHECK([NSApplication
    241       instancesRespondToSelector:@selector(_cycleWindowsReversed:)]);
    242   DCHECK([NSApplication
    243       instancesRespondToSelector:@selector(_removeWindow:)]);
    244   DCHECK([NSApplication
    245       instancesRespondToSelector:@selector(_setKeyWindow:)]);
    246 
    247   return self;
    248 }
    249 
    250 // Initialize NSApplication using the custom subclass.  Check whether NSApp
    251 // was already initialized using another class, because that would break
    252 // some things.
    253 + (NSApplication*)sharedApplication {
    254   NSApplication* app = [super sharedApplication];
    255 
    256   // +sharedApplication initializes the global NSApp, so if a specific
    257   // NSApplication subclass is requested, require that to be the one
    258   // delivered.  The practical effect is to require a consistent NSApp
    259   // across the executable.
    260   CHECK([NSApp isKindOfClass:self])
    261       << "NSApp must be of type " << [[self className] UTF8String]
    262       << ", not " << [[NSApp className] UTF8String];
    263 
    264   // If the message loop was initialized before NSApp is setup, the
    265   // message pump will be setup incorrectly.  Failing this implies
    266   // that RegisterBrowserCrApp() should be called earlier.
    267   CHECK(base::MessagePumpMac::UsingCrApp())
    268       << "MessagePumpMac::Create() is using the wrong pump implementation"
    269       << " for " << [[self className] UTF8String];
    270 
    271   return app;
    272 }
    273 
    274 ////////////////////////////////////////////////////////////////////////////////
    275 // HISTORICAL COMMENT (by viettrungluu, from
    276 // http://codereview.chromium.org/1520006 with mild editing):
    277 //
    278 // A quick summary of the state of things (before the changes to shutdown):
    279 //
    280 // Currently, we are totally hosed (put in a bad state in which Cmd-W does the
    281 // wrong thing, and which will probably eventually lead to a crash) if we begin
    282 // quitting but termination is aborted for some reason.
    283 //
    284 // I currently know of two ways in which termination can be aborted:
    285 // (1) Common case: a window has an onbeforeunload handler which pops up a
    286 //     "leave web page" dialog, and the user answers "no, don't leave".
    287 // (2) Uncommon case: popups are enabled (in Content Settings, i.e., the popup
    288 //     blocker is disabled), and some nasty web page pops up a new window on
    289 //     closure.
    290 //
    291 // I don't know of other ways in which termination can be aborted, but they may
    292 // exist (or may be added in the future, for that matter).
    293 //
    294 // My CL [see above] does the following:
    295 // a. Should prevent being put in a bad state (which breaks Cmd-W and leads to
    296 //    crash) under all circumstances.
    297 // b. Should completely handle (1) properly.
    298 // c. Doesn't (yet) handle (2) properly and puts it in a weird state (but not
    299 //    that bad).
    300 // d. Any other ways of aborting termination would put it in that weird state.
    301 //
    302 // c. can be fixed by having the global flag reset on browser creation or
    303 // similar (and doing so might also fix some possible d.'s as well). I haven't
    304 // done this yet since I haven't thought about it carefully and since it's a
    305 // corner case.
    306 //
    307 // The weird state: a state in which closing the last window quits the browser.
    308 // This might be a bit annoying, but it's not dangerous in any way.
    309 ////////////////////////////////////////////////////////////////////////////////
    310 
    311 // |-terminate:| is the entry point for orderly "quit" operations in Cocoa. This
    312 // includes the application menu's quit menu item and keyboard equivalent, the
    313 // application's dock icon menu's quit menu item, "quit" (not "force quit") in
    314 // the Activity Monitor, and quits triggered by user logout and system restart
    315 // and shutdown.
    316 //
    317 // The default |-terminate:| implementation ends the process by calling exit(),
    318 // and thus never leaves the main run loop. This is unsuitable for Chrome since
    319 // Chrome depends on leaving the main run loop to perform an orderly shutdown.
    320 // We support the normal |-terminate:| interface by overriding the default
    321 // implementation. Our implementation, which is very specific to the needs of
    322 // Chrome, works by asking the application delegate to terminate using its
    323 // |-tryToTerminateApplication:| method.
    324 //
    325 // |-tryToTerminateApplication:| differs from the standard
    326 // |-applicationShouldTerminate:| in that no special event loop is run in the
    327 // case that immediate termination is not possible (e.g., if dialog boxes
    328 // allowing the user to cancel have to be shown). Instead, this method sets a
    329 // flag and tries to close all browsers. This flag causes the closure of the
    330 // final browser window to begin actual tear-down of the application.
    331 // Termination is cancelled by resetting this flag. The standard
    332 // |-applicationShouldTerminate:| is not supported, and code paths leading to it
    333 // must be redirected.
    334 //
    335 // When the last browser has been destroyed, the BrowserList calls
    336 // chrome::OnAppExiting(), which is the point of no return. That will cause
    337 // the NSApplicationWillTerminateNotification to be posted, which ends the
    338 // NSApplication event loop, so final post- MessageLoop::Run() work is done
    339 // before exiting.
    340 - (void)terminate:(id)sender {
    341   AppController* appController = static_cast<AppController*>([NSApp delegate]);
    342   [appController tryToTerminateApplication:self];
    343   // Return, don't exit. The application is responsible for exiting on its own.
    344 }
    345 
    346 - (void)cancelTerminate:(id)sender {
    347   AppController* appController = static_cast<AppController*>([NSApp delegate]);
    348   [appController stopTryingToTerminateApplication:self];
    349 }
    350 
    351 - (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)sender {
    352   // The Dock menu contains an automagic section where you can select
    353   // amongst open windows.  If a window is closed via JavaScript while
    354   // the menu is up, the menu item for that window continues to exist.
    355   // When a window is selected this method is called with the
    356   // now-freed window as |aTarget|.  Short-circuit the call if
    357   // |aTarget| is not a valid window.
    358   if (anAction == @selector(_selectWindow:)) {
    359     // Not using -[NSArray containsObject:] because |aTarget| may be a
    360     // freed object.
    361     BOOL found = NO;
    362     for (NSWindow* window in [self windows]) {
    363       if (window == aTarget) {
    364         found = YES;
    365         break;
    366       }
    367     }
    368     if (!found) {
    369       return NO;
    370     }
    371   }
    372 
    373   // When a Cocoa control is wired to a freed object, we get crashers
    374   // in the call to |super| with no useful information in the
    375   // backtrace.  Attempt to add some useful information.
    376 
    377   // If the action is something generic like -commandDispatch:, then
    378   // the tag is essential.
    379   NSInteger tag = 0;
    380   if ([sender isKindOfClass:[NSControl class]]) {
    381     tag = [sender tag];
    382     if (tag == 0 || tag == -1) {
    383       tag = [sender selectedTag];
    384     }
    385   } else if ([sender isKindOfClass:[NSMenuItem class]]) {
    386     tag = [sender tag];
    387   }
    388 
    389   NSString* actionString = NSStringFromSelector(anAction);
    390   std::string value = base::StringPrintf("%s tag %ld sending %s to %p",
    391       [[sender className] UTF8String],
    392       static_cast<long>(tag),
    393       [actionString UTF8String],
    394       aTarget);
    395 
    396   base::debug::ScopedCrashKey key(crash_keys::mac::kSendAction, value);
    397 
    398   // Certain third-party code, such as print drivers, can still throw
    399   // exceptions and Chromium cannot fix them.  This provides a way to
    400   // work around those on a spot basis.
    401   bool enableNSExceptions = false;
    402 
    403   // http://crbug.com/80686 , an Epson printer driver.
    404   if (anAction == @selector(selectPDE:)) {
    405     enableNSExceptions = true;
    406   }
    407 
    408   // Minimize the window by keeping this close to the super call.
    409   scoped_ptr<base::mac::ScopedNSExceptionEnabler> enabler;
    410   if (enableNSExceptions)
    411     enabler.reset(new base::mac::ScopedNSExceptionEnabler());
    412   return [super sendAction:anAction to:aTarget from:sender];
    413 }
    414 
    415 - (BOOL)isHandlingSendEvent {
    416   return handlingSendEvent_;
    417 }
    418 
    419 - (void)setHandlingSendEvent:(BOOL)handlingSendEvent {
    420   handlingSendEvent_ = handlingSendEvent;
    421 }
    422 
    423 - (void)sendEvent:(NSEvent*)event {
    424   base::mac::ScopedSendingEvent sendingEventScoper;
    425   [super sendEvent:event];
    426 }
    427 
    428 // NSExceptions which are caught by the event loop are logged here.
    429 // NSException uses setjmp/longjmp, which can be very bad for C++, so
    430 // we attempt to track and report them.
    431 - (void)reportException:(NSException *)anException {
    432   // If we throw an exception in this code, we can create an infinite
    433   // loop.  If we throw out of the if() without resetting
    434   // |reportException|, we'll stop reporting exceptions for this run.
    435   static BOOL reportingException = NO;
    436   DCHECK(!reportingException);
    437   if (!reportingException) {
    438     reportingException = YES;
    439     chrome_browser_application_mac::RecordExceptionWithUma(anException);
    440 
    441     // http://crbug.com/45928 is a bug about needing to double-close
    442     // windows sometimes.  One theory is that |-isHandlingSendEvent|
    443     // gets latched to always return |YES|.  Since scopers are used to
    444     // manipulate that value, that should not be possible.  One way to
    445     // sidestep scopers is setjmp/longjmp (see above).  The following
    446     // is to "fix" this while the more fundamental concern is
    447     // addressed elsewhere.
    448     [self setHandlingSendEvent:NO];
    449 
    450     // If |ScopedNSExceptionEnabler| is used to allow exceptions, and an
    451     // uncaught exception is thrown, it will throw past all of the scopers.
    452     // Reset the flag so that future exceptions are not masked.
    453     base::mac::SetNSExceptionsAllowed(false);
    454 
    455     // Store some human-readable information in breakpad keys in case
    456     // there is a crash.  Since breakpad does not provide infinite
    457     // storage, we track two exceptions.  The first exception thrown
    458     // is tracked because it may be the one which caused the system to
    459     // go off the rails.  The last exception thrown is tracked because
    460     // it may be the one most directly associated with the crash.
    461     static BOOL trackedFirstException = NO;
    462 
    463     const char* const kExceptionKey =
    464         trackedFirstException ? crash_keys::mac::kLastNSException
    465                               : crash_keys::mac::kFirstNSException;
    466     NSString* value = [NSString stringWithFormat:@"%@ reason %@",
    467                                 [anException name], [anException reason]];
    468     base::debug::SetCrashKeyValue(kExceptionKey, [value UTF8String]);
    469 
    470     // Encode the callstack from point of throw.
    471     // TODO(shess): Our swizzle plus the 23-frame limit plus Cocoa
    472     // overhead may make this less than useful.  If so, perhaps skip
    473     // some items and/or use two keys.
    474     const char* const kExceptionBtKey =
    475         trackedFirstException ? crash_keys::mac::kLastNSExceptionTrace
    476                               : crash_keys::mac::kFirstNSExceptionTrace;
    477     NSArray* addressArray = [anException callStackReturnAddresses];
    478     NSUInteger addressCount = [addressArray count];
    479     if (addressCount) {
    480       // SetCrashKeyFromAddresses() only encodes 23, so that's a natural limit.
    481       const NSUInteger kAddressCountMax = 23;
    482       void* addresses[kAddressCountMax];
    483       if (addressCount > kAddressCountMax)
    484         addressCount = kAddressCountMax;
    485 
    486       for (NSUInteger i = 0; i < addressCount; ++i) {
    487         addresses[i] = reinterpret_cast<void*>(
    488             [[addressArray objectAtIndex:i] unsignedIntegerValue]);
    489       }
    490       base::debug::SetCrashKeyFromAddresses(
    491           kExceptionBtKey, addresses, static_cast<size_t>(addressCount));
    492     } else {
    493       base::debug::ClearCrashKey(kExceptionBtKey);
    494     }
    495     trackedFirstException = YES;
    496 
    497     reportingException = NO;
    498   }
    499 
    500   [super reportException:anException];
    501 }
    502 
    503 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
    504   if ([attribute isEqualToString:@"AXEnhancedUserInterface"] &&
    505       [value intValue] == 1) {
    506     content::BrowserAccessibilityState::GetInstance()->OnScreenReaderDetected();
    507     for (TabContentsIterator it; !it.done(); it.Next()) {
    508       if (content::WebContents* contents = *it)
    509         if (content::RenderViewHost* rvh = contents->GetRenderViewHost())
    510           rvh->EnableFullAccessibilityMode();
    511     }
    512   }
    513   return [super accessibilitySetValue:value forAttribute:attribute];
    514 }
    515 
    516 - (void)_cycleWindowsReversed:(BOOL)arg1 {
    517   base::AutoReset<BOOL> pin(&cyclingWindows_, YES);
    518   [super _cycleWindowsReversed:arg1];
    519 }
    520 
    521 - (BOOL)isCyclingWindows {
    522   return cyclingWindows_;
    523 }
    524 
    525 - (id)_removeWindow:(NSWindow*)window {
    526   {
    527     base::AutoLock lock(previousKeyWindowsLock_);
    528     [self removePreviousKeyWindow:window];
    529   }
    530   id result = [super _removeWindow:window];
    531 
    532   // Ensure app has a key window after a window is removed.
    533   // OS wants to make a panel browser window key after closing an app window
    534   // because panels use a higher priority window level, but panel windows may
    535   // refuse to become key, leaving the app with no key window. The OS does
    536   // not seem to consider other windows after the first window chosen refuses
    537   // to become key. Force consideration of other windows here.
    538   if ([self isActive] && [self keyWindow] == nil) {
    539     NSWindow* key =
    540         [self makeWindowsPerform:@selector(canBecomeKeyWindow) inOrder:YES];
    541     [key makeKeyWindow];
    542   }
    543 
    544   // Return result from the super class. It appears to be the app that
    545   // owns the removed window (determined via experimentation).
    546   return result;
    547 }
    548 
    549 - (id)_setKeyWindow:(NSWindow*)window {
    550   // |window| is nil when the current key window is being closed.
    551   // A separate call follows with a new value when a new key window is set.
    552   // Closed windows are not tracked in previousKeyWindows_.
    553   if (window != nil) {
    554     base::AutoLock lock(previousKeyWindowsLock_);
    555     [self removePreviousKeyWindow:window];
    556     NSWindow* currentKeyWindow = [self keyWindow];
    557     if (currentKeyWindow != nil && currentKeyWindow != window)
    558       previousKeyWindows_.push_back(currentKeyWindow);
    559   }
    560 
    561   return [super _setKeyWindow:window];
    562 }
    563 
    564 - (NSWindow*)previousKeyWindow {
    565   base::AutoLock lock(previousKeyWindowsLock_);
    566   return previousKeyWindows_.empty() ? nil : previousKeyWindows_.back();
    567 }
    568 
    569 - (void)removePreviousKeyWindow:(NSWindow*)window {
    570   previousKeyWindowsLock_.AssertAcquired();
    571   std::vector<NSWindow*>::iterator window_iterator =
    572       std::find(previousKeyWindows_.begin(),
    573                 previousKeyWindows_.end(),
    574                 window);
    575   if (window_iterator != previousKeyWindows_.end()) {
    576     previousKeyWindows_.erase(window_iterator);
    577   }
    578 }
    579 
    580 @end
    581