From 2db27be7dbb16f02587b90f150a843d8ad234563 Mon Sep 17 00:00:00 2001 From: "twiz@chromium.org" Date: Wed, 10 Feb 2010 22:46:47 +0000 Subject: CL implementing focus-dismissal of the chrome.experimental.popup set of extension APIs. Specifically, these changes cause a displayed pop-up to be dismissed when the focus shifts away from both the pop-up view, and the extension-view that launched the pop-up.I had to do a lot of investigating and trial-and-error to converge to the solution present here. I was hoping to be able to piggy-back on the existing FocusManager's various listener routines, but because the pop-up is hosted in a BubbleWidget, which is a separate top-level window with its own focus manager, I could not rely on a given focus manager to take care of the focus change notifications. To get around the above issue, I added a new type of focus listener that can listen on native-window focus change events. I added invocations to this listener throughout the Widget classes in the system so that registered listeners will be notified on focus change. I found some of the focus change events problematic, as the system will arbitrarily reassign the focus to the main browser window when shifting activation between chrome windows. (SeefocusManagerWin::ClearNativeFocus). To prevent this focus bounce from confusing focus listeners, I added a means to suppress notification of focus change during these operations. I added GTK and Mac stubs for the new widget functions that will assert when called. GTK and Cocoa development is not my expertise, so I thought // TODO(port) would be wiser.I'm uncertain of the best means to unit-test these changes. Direction in this regard would be appreciated. BUG=None TEST=None Review URL: http://codereview.chromium.org/552167 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@38685 0039d316-1c4b-4281-b951-d872f2087c98 --- views/focus/focus_manager.cc | 50 ++++++++++++++++++++++++++++++- views/focus/focus_manager.h | 70 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 117 insertions(+), 3 deletions(-) (limited to 'views/focus') diff --git a/views/focus/focus_manager.cc b/views/focus/focus_manager.cc index 9942854..f637e09 100644 --- a/views/focus/focus_manager.cc +++ b/views/focus/focus_manager.cc @@ -22,6 +22,46 @@ namespace views { +// FocusManager::WidgetFocusManager --------------------------------- + +void FocusManager::WidgetFocusManager::AddFocusChangeListener( + WidgetFocusChangeListener* listener) { + DCHECK(std::find(focus_change_listeners_.begin(), + focus_change_listeners_.end(), listener) == + focus_change_listeners_.end()) << + "Adding a WidgetFocusChangeListener twice."; + focus_change_listeners_.push_back(listener); +} + +void FocusManager::WidgetFocusManager::RemoveFocusChangeListener( + WidgetFocusChangeListener* listener) { + WidgetFocusChangeListenerList::iterator iter(std::find( + focus_change_listeners_.begin(), + focus_change_listeners_.end(), + listener)); + if (iter != focus_change_listeners_.end()) { + focus_change_listeners_.erase(iter); + } else { + NOTREACHED() << + "Attempting to remove an unregistered WidgetFocusChangeListener."; + } +} + +void FocusManager::WidgetFocusManager::OnWidgetFocusEvent( + gfx::NativeView focused_before, + gfx::NativeView focused_now) { + if (!enabled_) + return; + + // Perform a safe iteration over the focus listeners, as the array of + // may change during notification. + WidgetFocusChangeListenerList local_listeners(focus_change_listeners_); + WidgetFocusChangeListenerList::iterator iter(local_listeners.begin()); + for (;iter != local_listeners.end(); ++iter) { + (*iter)->NativeFocusWillChange(focused_before, focused_now); + } +} + // FocusManager ----------------------------------------------------- FocusManager::FocusManager(Widget* widget) @@ -277,7 +317,15 @@ void FocusManager::StoreFocusedView() { view_storage->StoreView(stored_focused_view_storage_id_, focused_view_); View* v = focused_view_; - ClearFocus(); + + { + // Temporarily disable notification. ClearFocus() will set the focus to the + // main browser window. This extra focus bounce which happens during + // deactivation can confuse registered WidgetFocusListeners, as the focus + // is not changing due to a user-initiated event. + AutoNativeNotificationDisabler local_notification_disabler; + ClearFocus(); + } if (v) v->SchedulePaint(); // Remove focus border. diff --git a/views/focus/focus_manager.h b/views/focus/focus_manager.h index aec8911..00e7a0a 100644 --- a/views/focus/focus_manager.h +++ b/views/focus/focus_manager.h @@ -5,12 +5,13 @@ #ifndef VIEWS_FOCUS_FOCUS_MANAGER_H_ #define VIEWS_FOCUS_FOCUS_MANAGER_H_ -#include -#include #include +#include +#include #include "app/gfx/native_widget_types.h" #include "base/basictypes.h" +#include "base/singleton.h" #include "views/accelerator.h" // The FocusManager class is used to handle focus traversal, store/restore @@ -132,11 +133,61 @@ class FocusChangeListener { virtual void FocusWillChange(View* focused_before, View* focused_now) = 0; }; +// This interface should be implemented by classes that want to be notified when +// the native focus is about to change. Listeners implementing this interface +// will be invoked for all native focus changes across the entire Chrome +// application. FocusChangeListeners are only called for changes within the +// children of a single top-level native-view. +class WidgetFocusChangeListener { + public: + virtual void NativeFocusWillChange(gfx::NativeView focused_before, + gfx::NativeView focused_now) = 0; +}; + class FocusManager { public: + class WidgetFocusManager { + public: + // Adds/removes a WidgetFocusChangeListener |listener| to the set of + // active listeners. + void AddFocusChangeListener(WidgetFocusChangeListener* listener); + void RemoveFocusChangeListener(WidgetFocusChangeListener* listener); + + // To be called when native-focus shifts from |focused_before| to + // |focused_now|. + // TODO(port) : Invocations to this routine are only implemented for + // the Win32 platform. Calls need to be placed appropriately for + // non-Windows environments. + void OnWidgetFocusEvent(gfx::NativeView focused_before, + gfx::NativeView focused_now); + + // Enable/Disable notification of registered listeners during calls + // to OnWidgetFocusEvent. Used to prevent unwanted focus changes from + // propagating notifications. + void EnableNotifications() { enabled_ = true; } + void DisableNotifications() { enabled_ = false; } + + private: + WidgetFocusManager() : enabled_(true) {} + + typedef std::vector + WidgetFocusChangeListenerList; + WidgetFocusChangeListenerList focus_change_listeners_; + + bool enabled_; + + friend struct DefaultSingletonTraits; + DISALLOW_COPY_AND_ASSIGN(WidgetFocusManager); + }; + explicit FocusManager(Widget* widget); ~FocusManager(); + // Returns the global WidgetFocusManager instance for the running application. + static WidgetFocusManager* GetWidgetFocusManager() { + return Singleton::get(); + } + // Processes the passed key event for accelerators and tab traversal. // Returns false if the event has been consumed and should not be processed // further. @@ -261,6 +312,21 @@ class FocusManager { DISALLOW_COPY_AND_ASSIGN(FocusManager); }; +// A basic helper class that is used to disable native focus change +// notifications within a scope. +class AutoNativeNotificationDisabler { + public: + AutoNativeNotificationDisabler() { + FocusManager::GetWidgetFocusManager()->DisableNotifications(); + } + + ~AutoNativeNotificationDisabler() { + FocusManager::GetWidgetFocusManager()->EnableNotifications(); + } + private: + DISALLOW_COPY_AND_ASSIGN(AutoNativeNotificationDisabler); +}; + } // namespace views #endif // VIEWS_FOCUS_FOCUS_MANAGER_H_ -- cgit v1.1