summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-19 20:08:27 +0000
committerjennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-19 20:08:27 +0000
commiteaf60d8ba229a9dc7344cc305ef23607ce4b52be (patch)
tree666ac84c6970b8c6ce6301d47e3cd2bc7f4f49c8
parent19331e339db762b810aee2b18226cafada8078bb (diff)
downloadchromium_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
-rw-r--r--chrome/browser/chrome_browser_application_mac.h11
-rw-r--r--chrome/browser/chrome_browser_application_mac.mm66
-rw-r--r--chrome/browser/ui/panels/panel_browser_window_cocoa.h12
-rw-r--r--chrome/browser/ui/panels/panel_browser_window_cocoa.mm6
-rw-r--r--chrome/browser/ui/panels/panel_window_controller_cocoa.h16
-rw-r--r--chrome/browser/ui/panels/panel_window_controller_cocoa.mm62
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;