diff options
author | jennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-19 20:08:27 +0000 |
---|---|---|
committer | jennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-19 20:08:27 +0000 |
commit | eaf60d8ba229a9dc7344cc305ef23607ce4b52be (patch) | |
tree | 666ac84c6970b8c6ce6301d47e3cd2bc7f4f49c8 | |
parent | 19331e339db762b810aee2b18226cafada8078bb (diff) | |
download | chromium_src-eaf60d8ba229a9dc7344cc305ef23607ce4b52be.zip chromium_src-eaf60d8ba229a9dc7344cc305ef23607ce4b52be.tar.gz chromium_src-eaf60d8ba229a9dc7344cc305ef23607ce4b52be.tar.bz2 |
Ensure the previously active window gets the focus after a Chrome window is closed on OSX.
This is a rework of the previous fix in r120593 to handle non-browser windows as well.
New logic relies on Panel browser windows returning false to -canBecomeKeyWindow in most cases.
App could be left without a key window as a result, so I also added logic to ensure that there is a key window in this case.
BUG=112038
TEST=Manually open/close tabbed windows, panels, Task Manager. Check focus after closing windows.
Review URL: https://chromiumcodereview.appspot.com/9417033
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@127517 0039d316-1c4b-4281-b951-d872f2087c98
6 files changed, 156 insertions, 17 deletions
diff --git a/chrome/browser/chrome_browser_application_mac.h b/chrome/browser/chrome_browser_application_mac.h index c6147fd..52c6be3 100644 --- a/chrome/browser/chrome_browser_application_mac.h +++ b/chrome/browser/chrome_browser_application_mac.h @@ -23,9 +23,14 @@ CrAppControlProtocol> { @private BOOL handlingSendEvent_; + BOOL cyclingWindows_; // Array of objects implementing CrApplicationEventHookProtocol. scoped_nsobject<NSMutableArray> eventHooks_; + + // App's previous key windows. Most recent key window is last. + // Does not include current key window. + scoped_nsobject<NSMutableArray> previousKeyWindows_; } // Our implementation of |-terminate:| only attempts to terminate the @@ -43,6 +48,12 @@ // context" out of it). - (void)addEventHook:(id<CrApplicationEventHookProtocol>)hook; - (void)removeEventHook:(id<CrApplicationEventHookProtocol>)hook; + +// Keep track of the previous key windows and whether windows are being +// cycled for use in determining whether a Panel window can become the +// key window. +- (id)previousKeyWindow; +- (BOOL)isCyclingWindows; @end namespace chrome_browser_application_mac { diff --git a/chrome/browser/chrome_browser_application_mac.mm b/chrome/browser/chrome_browser_application_mac.mm index fb4b525..76c1660 100644 --- a/chrome/browser/chrome_browser_application_mac.mm +++ b/chrome/browser/chrome_browser_application_mac.mm @@ -4,6 +4,7 @@ #import "chrome/browser/chrome_browser_application_mac.h" +#import "base/auto_reset.h" #import "base/logging.h" #include "base/mac/crash_logging.h" #import "base/mac/scoped_nsexception_enabler.h" @@ -206,6 +207,14 @@ void SwizzleInit() { } // namespace +// These methods are being exposed for the purposes of overriding. +// Used to determine when a Panel window can become the key window. +@interface NSApplication (PanelsCanBecomeKey) +- (void)_cycleWindowsReversed:(BOOL)arg1; +- (id)_removeWindow:(id)window; +- (id)_setKeyWindow:(id)window; +@end + @implementation BrowserCrApplication + (void)initialize { @@ -218,7 +227,17 @@ void SwizzleInit() { SwizzleInit(); if ((self = [super init])) { eventHooks_.reset([[NSMutableArray alloc] init]); + previousKeyWindows_.reset([[NSMutableArray alloc] init]); } + + // Sanity check to alert if overridden methods are not supported. + DCHECK([NSApplication + instancesRespondToSelector:@selector(_cycleWindowsReversed:)]); + DCHECK([NSApplication + instancesRespondToSelector:@selector(_removeWindow:)]); + DCHECK([NSApplication + instancesRespondToSelector:@selector(_setKeyWindow:)]); + return self; } @@ -488,4 +507,51 @@ void SwizzleInit() { return [super accessibilitySetValue:value forAttribute:attribute]; } +- (void)_cycleWindowsReversed:(BOOL)arg1 { + AutoReset<BOOL> pin(&cyclingWindows_, YES); + [super _cycleWindowsReversed:arg1]; +} + +- (BOOL)isCyclingWindows { + return cyclingWindows_; +} + +- (id)_removeWindow:(id)window { + [previousKeyWindows_ removeObject:window]; + id result = [super _removeWindow:window]; + + // Ensure app has a key window after a window is removed. + // OS wants to make a panel browser window key after closing an app window + // because panels use a higher priority window level, but panel windows may + // refuse to become key, leaving the app with no key window. The OS does + // not seem to consider other windows after the first window chosen refuses + // to become key. Force consideration of other windows here. + if ([self isActive] && [self keyWindow] == nil) { + NSWindow* key = + [self makeWindowsPerform:@selector(canBecomeKeyWindow) inOrder:YES]; + [key makeKeyWindow]; + } + + // Return result from the super class. It appears to be the app that + // owns the removed window (determined via experimentation). + return result; +} + +- (id)_setKeyWindow:(id)window { + // |window| is nil when the current key window is being closed. + // A separate call follows with a new value when a new key window is set. + // Closed windows are not tracked in previousKeyWindows_. + if (window != nil) { + [previousKeyWindows_ removeObject:window]; + NSWindow* currentKeyWindow = [self keyWindow]; + if (currentKeyWindow != nil && currentKeyWindow != window) + [previousKeyWindows_ addObject:currentKeyWindow]; + } + + return [super _setKeyWindow:window]; +} + +- (id)previousKeyWindow { + return [previousKeyWindows_ lastObject]; +} @end diff --git a/chrome/browser/ui/panels/panel_browser_window_cocoa.h b/chrome/browser/ui/panels/panel_browser_window_cocoa.h index 5a6520f8..763b30d 100644 --- a/chrome/browser/ui/panels/panel_browser_window_cocoa.h +++ b/chrome/browser/ui/panels/panel_browser_window_cocoa.h @@ -84,6 +84,12 @@ class PanelBrowserWindowCocoa : public NativePanel, // handlers. void DidCloseNativeWindow(); + // A Panel window is allowed to become the key window if the activation + // request came from the browser. + bool ActivationRequestedByBrowser() { + return activation_requested_by_browser_; + } + private: friend class PanelBrowserWindowCocoaTest; friend class NativePanelTestingCocoa; @@ -114,6 +120,12 @@ class PanelBrowserWindowCocoa : public NativePanel, bool is_shown_; // Panel is hidden on creation, Show() changes that forever. bool has_find_bar_; // Find bar should only be created once per panel. + // Allow a panel to become key if activated via browser logic, as opposed + // to by default system selection. The system will prefer a panel + // window over other application windows due to panels having a higher + // priority NSWindowLevel, so we distinguish between the two scenarios. + bool activation_requested_by_browser_; + DISALLOW_COPY_AND_ASSIGN(PanelBrowserWindowCocoa); }; diff --git a/chrome/browser/ui/panels/panel_browser_window_cocoa.mm b/chrome/browser/ui/panels/panel_browser_window_cocoa.mm index 5e01217..87302c4 100644 --- a/chrome/browser/ui/panels/panel_browser_window_cocoa.mm +++ b/chrome/browser/ui/panels/panel_browser_window_cocoa.mm @@ -4,6 +4,7 @@ #include "chrome/browser/ui/panels/panel_browser_window_cocoa.h" +#include "base/auto_reset.h" #include "base/logging.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/ui/browser.h" @@ -45,7 +46,8 @@ PanelBrowserWindowCocoa::PanelBrowserWindowCocoa(Browser* browser, panel_(panel), bounds_(bounds), is_shown_(false), - has_find_bar_(false) { + has_find_bar_(false), + activation_requested_by_browser_(false) { controller_ = [[PanelWindowControllerCocoa alloc] initWithBrowserWindow:this]; browser_->tabstrip_model()->AddObserver(this); } @@ -127,6 +129,8 @@ void PanelBrowserWindowCocoa::ClosePanel() { void PanelBrowserWindowCocoa::ActivatePanel() { if (!is_shown_) return; + + AutoReset<bool> pin(&activation_requested_by_browser_, true); [BrowserWindowUtils activateWindowForController:controller_]; } diff --git a/chrome/browser/ui/panels/panel_window_controller_cocoa.h b/chrome/browser/ui/panels/panel_window_controller_cocoa.h index a02ee80..b4efd1e 100644 --- a/chrome/browser/ui/panels/panel_window_controller_cocoa.h +++ b/chrome/browser/ui/panels/panel_window_controller_cocoa.h @@ -26,19 +26,6 @@ class PanelBrowserWindowCocoa; @class PanelTitlebarViewCocoa; -@interface PanelWindowCocoaImpl : ChromeBrowserWindow { -} -// The panels cannot be reduced to 3-px windows on the edge of the screen -// active area (above Dock). Default constraining logic makes at least a height -// of the titlebar visible, so the user could still grab it. We do 'restore' -// differently, and minimize panels to 3 px. Hence the need to override the -// constraining logic. -- (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen; - -// Prevent panel window from becoming key - for example when it is minimized. -- (BOOL)canBecomeKeyWindow; -@end - @interface PanelWindowControllerCocoa : NSWindowController <NSWindowDelegate, NSAnimationDelegate, @@ -143,6 +130,9 @@ class PanelBrowserWindowCocoa; // are not un-minimized when another panel is minimized. - (BOOL)canBecomeKeyWindow; +// Returns true if browser window requested activation of the window. +- (BOOL)activationRequestedByBrowser; + // Returns width of titlebar when shown in "icon only" mode. - (int)titlebarIconOnlyWidthInScreenCoordinates; diff --git a/chrome/browser/ui/panels/panel_window_controller_cocoa.mm b/chrome/browser/ui/panels/panel_window_controller_cocoa.mm index b2e6b9d..c9631ce 100644 --- a/chrome/browser/ui/panels/panel_window_controller_cocoa.mm +++ b/chrome/browser/ui/panels/panel_window_controller_cocoa.mm @@ -6,12 +6,14 @@ #import <Cocoa/Cocoa.h> +#include "base/auto_reset.h" #include "base/logging.h" #include "base/mac/bundle_locations.h" #include "base/mac/mac_util.h" #include "base/sys_string_conversions.h" #include "base/time.h" #include "chrome/app/chrome_command_ids.h" // IDC_* +#include "chrome/browser/chrome_browser_application_mac.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/themes/theme_service.h" @@ -59,15 +61,61 @@ enum { #endif // MAC_OS_X_VERSION_10_6 +@interface PanelWindowControllerCocoa (PanelsCanBecomeKey) +// Internal helper method for extracting the total number of panel windows +// from the panel manager. Used to decide if panel can become the key window. +- (int)numPanels; +@end + +@interface PanelWindowCocoaImpl : ChromeBrowserWindow { + // Panel windows use a higher priority NSWindowLevel to ensure they are always + // visible, causing the OS to prefer panel windows when selecting a window + // to make the key window. To counter this preference, we override + // -[NSWindow:canBecomeKeyWindow] to restrict when the panel can become the + // key window to a limited set of scenarios, such as when cycling through + // windows, when panels are the only remaining windows, when an event + // triggers window activation, etc. The panel may also be prevented from + // becoming the key window, regardless of the above scenarios, such as when + // a panel is minimized. + BOOL canBecomeKey_; // Defaults to NO. +} +@end + @implementation PanelWindowCocoaImpl +// The panels cannot be reduced to 3-px windows on the edge of the screen +// active area (above Dock). Default constraining logic makes at least a height +// of the titlebar visible, so the user could still grab it. We do 'restore' +// differently, and minimize panels to 3 px. Hence the need to override the +// constraining logic. - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen { return frameRect; } +// Prevent panel window from becoming key - for example when it is minimized. - (BOOL)canBecomeKeyWindow { + // Give precedence to controller preventing activation of the window. PanelWindowControllerCocoa* controller = - static_cast<PanelWindowControllerCocoa*>([self delegate]); - return [controller canBecomeKeyWindow]; + base::mac::ObjCCast<PanelWindowControllerCocoa>([self windowController]); + if (![controller canBecomeKeyWindow]) + return false; + + BrowserCrApplication* app = base::mac::ObjCCast<BrowserCrApplication>( + [BrowserCrApplication sharedApplication]); + + // A Panel window can become the key window only in limited scenarios. + // This prevents the system from always preferring a Panel window due + // to its higher priority NSWindowLevel when selecting a window to make key. + return canBecomeKey_ || + [controller activationRequestedByBrowser] || + [app isCyclingWindows] || + [app previousKeyWindow] == self || + [[app windows] count] == static_cast<NSUInteger>([controller numPanels]); +} + +- (void)sendEvent:(NSEvent*)anEvent { + // Allow the panel to become key in response to a mouse click. + AutoReset<BOOL> pin(&canBecomeKey_, YES); + [super sendEvent:anEvent]; } @end @@ -368,7 +416,7 @@ enum { return NO; } - // the tab strip is empty, it's ok to close the window + // The tab strip is empty, it's ok to close the window. return YES; } @@ -655,6 +703,14 @@ enum { return canBecomeKeyWindow_; } +- (int)numPanels { + return windowShim_->panel()->manager()->num_panels(); +} + +- (BOOL)activationRequestedByBrowser { + return windowShim_->ActivationRequestedByBrowser(); +} + - (void)updateWindowLevel { if (![self isWindowLoaded]) return; |