diff options
author | twiz@chromium.org <twiz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-10 22:46:47 +0000 |
---|---|---|
committer | twiz@chromium.org <twiz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-10 22:46:47 +0000 |
commit | 2db27be7dbb16f02587b90f150a843d8ad234563 (patch) | |
tree | 9bfd5b6b2ff4637a69744ce35249361d1f3ea353 /views | |
parent | e1ce144bd04336891bb12b967095e938ed5f1bbb (diff) | |
download | chromium_src-2db27be7dbb16f02587b90f150a843d8ad234563.zip chromium_src-2db27be7dbb16f02587b90f150a843d8ad234563.tar.gz chromium_src-2db27be7dbb16f02587b90f150a843d8ad234563.tar.bz2 |
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
Diffstat (limited to 'views')
-rw-r--r-- | views/controls/native/native_view_host.cc | 15 | ||||
-rw-r--r-- | views/controls/native/native_view_host.h | 1 | ||||
-rw-r--r-- | views/focus/focus_manager.cc | 50 | ||||
-rw-r--r-- | views/focus/focus_manager.h | 70 | ||||
-rw-r--r-- | views/view.cc | 8 | ||||
-rw-r--r-- | views/view.h | 4 | ||||
-rw-r--r-- | views/widget/widget.h | 4 | ||||
-rw-r--r-- | views/widget/widget_gtk.cc | 9 | ||||
-rw-r--r-- | views/widget/widget_gtk.h | 2 | ||||
-rw-r--r-- | views/widget/widget_win.cc | 33 | ||||
-rw-r--r-- | views/widget/widget_win.h | 3 |
11 files changed, 194 insertions, 5 deletions
diff --git a/views/controls/native/native_view_host.cc b/views/controls/native/native_view_host.cc index 1274e5c..79abc1d 100644 --- a/views/controls/native/native_view_host.cc +++ b/views/controls/native/native_view_host.cc @@ -7,6 +7,7 @@ #include "base/logging.h" #include "app/gfx/canvas.h" #include "views/controls/native/native_view_host_wrapper.h" +#include "views/widget/root_view.h" #include "views/widget/widget.h" namespace views { @@ -145,6 +146,20 @@ void NativeViewHost::Focus() { native_wrapper_->SetFocus(); } +bool NativeViewHost::ContainsNativeView(gfx::NativeView native_view) const { + if (native_view == native_view_) + return true; + + views::Widget* native_widget = + views::Widget::GetWidgetFromNativeView(native_view_); + views::RootView* root_view = + native_widget ? native_widget->GetRootView() : NULL; + if (root_view && root_view->ContainsNativeView(native_view)) + return true; + + return View::ContainsNativeView(native_view); +} + //////////////////////////////////////////////////////////////////////////////// // NativeViewHost, private: diff --git a/views/controls/native/native_view_host.h b/views/controls/native/native_view_host.h index d6115b2..5a9a403 100644 --- a/views/controls/native/native_view_host.h +++ b/views/controls/native/native_view_host.h @@ -74,6 +74,7 @@ class NativeViewHost : public View { virtual void Paint(gfx::Canvas* canvas); virtual void VisibilityChanged(View* starting_from, bool is_visible); virtual void Focus(); + virtual bool ContainsNativeView(gfx::NativeView native_view) const; protected: virtual void VisibleBoundsInRootChanged(); 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 <vector> -#include <map> #include <list> +#include <map> +#include <vector> #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<WidgetFocusChangeListener*> + WidgetFocusChangeListenerList; + WidgetFocusChangeListenerList focus_change_listeners_; + + bool enabled_; + + friend struct DefaultSingletonTraits<WidgetFocusManager>; + DISALLOW_COPY_AND_ASSIGN(WidgetFocusManager); + }; + explicit FocusManager(Widget* widget); ~FocusManager(); + // Returns the global WidgetFocusManager instance for the running application. + static WidgetFocusManager* GetWidgetFocusManager() { + return Singleton<WidgetFocusManager>::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_ diff --git a/views/view.cc b/views/view.cc index 53f1102..3b823a8 100644 --- a/views/view.cc +++ b/views/view.cc @@ -788,6 +788,14 @@ Window* View::GetWindow() const { return widget ? widget->GetWindow() : NULL; } +bool View::ContainsNativeView(gfx::NativeView native_view) const { + for (int i = 0, count = GetChildViewCount(); i < count; ++i) { + if (GetChildViewAt(i)->ContainsNativeView(native_view)) + return true; + } + return false; +} + // Get the containing RootView RootView* View::GetRootView() { Widget* widget = GetWidget(); diff --git a/views/view.h b/views/view.h index 5ae8be5..b44c04d 100644 --- a/views/view.h +++ b/views/view.h @@ -455,6 +455,10 @@ class View : public AcceleratorTarget { // level windows (as is done for popups, bubbles and menus). virtual Window* GetWindow() const; + // Returns true if the native view |native_view| is contained in the view + // hierarchy beneath this view. + virtual bool ContainsNativeView(gfx::NativeView native_view) const; + // Get the containing RootView virtual RootView* GetRootView(); diff --git a/views/widget/widget.h b/views/widget/widget.h index fa8111e..a3959b4 100644 --- a/views/widget/widget.h +++ b/views/widget/widget.h @@ -193,6 +193,10 @@ class Widget { // Forwarded from the RootView so that the widget can do any cleanup. virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child) = 0; + + // Returns true if the native view |native_view| is contained in the + // views::View hierarchy rooted at this widget. + virtual bool ContainsNativeView(gfx::NativeView native_view) = 0; }; } // namespace views diff --git a/views/widget/widget_gtk.cc b/views/widget/widget_gtk.cc index 3f9b3c4..44bc82d 100644 --- a/views/widget/widget_gtk.cc +++ b/views/widget/widget_gtk.cc @@ -590,6 +590,12 @@ void WidgetGtk::ViewHierarchyChanged(bool is_add, View *parent, drop_target_->ResetTargetViewIfEquals(child); } +bool WidgetGtk::ContainsNativeView(gfx::NativeView native_view) { + // TODO(port) See implementation in WidgetWin::ContainsNativeView. + NOTREACHED() << "WidgetGtk::ContainsNativeView is not implemented."; + return false; +} + //////////////////////////////////////////////////////////////////////////////// // WidgetGtk, MessageLoopForUI::Observer implementation: @@ -1239,7 +1245,8 @@ void WidgetGtk::CreateGtkWidget(GtkWidget* parent, const gfx::Rect& bounds) { gtk_fixed_set_has_window(GTK_FIXED(window_contents_), true); gtk_container_add(GTK_CONTAINER(widget_), window_contents_); gtk_widget_show(window_contents_); - g_object_set_data(G_OBJECT(window_contents_), kWidgetKey, this); + g_object_set_data(G_OBJECT(window_contents_), kWidgetKey, + static_cast<Widget*>(this)); if (transparent_) ConfigureWidgetForTransparentBackground(); diff --git a/views/widget/widget_gtk.h b/views/widget/widget_gtk.h index 9d73bf0..0c0096c 100644 --- a/views/widget/widget_gtk.h +++ b/views/widget/widget_gtk.h @@ -168,6 +168,8 @@ class WidgetGtk virtual FocusManager* GetFocusManager(); virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + virtual bool ContainsNativeView(gfx::NativeView native_view); + // Overridden from MessageLoopForUI::Observer: virtual void WillProcessEvent(GdkEvent* event); diff --git a/views/widget/widget_win.cc b/views/widget/widget_win.cc index 6f75506..ea54036 100644 --- a/views/widget/widget_win.cc +++ b/views/widget/widget_win.cc @@ -383,6 +383,28 @@ void WidgetWin::ViewHierarchyChanged(bool is_add, View *parent, drop_target_->ResetTargetViewIfEquals(child); } + +bool WidgetWin::ContainsNativeView(gfx::NativeView native_view) { + if (hwnd() == native_view) + return true; + + // Traverse the set of parents of the given view to determine if native_view + // is a descendant of this window. + HWND parent_window = ::GetParent(native_view); + HWND previous_child = native_view; + while (parent_window && parent_window != previous_child) { + if (hwnd() == parent_window) + return true; + previous_child = parent_window; + parent_window = ::GetParent(parent_window); + } + + // A views::NativeViewHost may contain the given native view, without it being + // an ancestor of hwnd(), so traverse the views::View hierarchy looking for + // such views. + return GetRootView()->ContainsNativeView(native_view); +} + //////////////////////////////////////////////////////////////////////////////// // MessageLoop::Observer @@ -595,6 +617,13 @@ void WidgetWin::OnKeyUp(TCHAR c, UINT rep_cnt, UINT flags) { SetMsgHandled(root_view->ProcessKeyEvent(event)); } +void WidgetWin::OnKillFocus(HWND focused_window) { + GetFocusManager()->GetWidgetFocusManager()->OnWidgetFocusEvent( + this->GetNativeView(), + focused_window); + SetMsgHandled(FALSE); +} + // TODO(pkasting): ORing the pressed/released button into the flags is _wrong_. // It makes it impossible to tell which button was modified when multiple // buttons are/were held down. We need to instead put the modified button into @@ -800,6 +829,9 @@ LRESULT WidgetWin::OnReflectedMessage(UINT msg, } void WidgetWin::OnSetFocus(HWND focused_window) { + GetFocusManager()->GetWidgetFocusManager()->OnWidgetFocusEvent( + focused_window, + this->GetNativeView()); SetMsgHandled(FALSE); } @@ -1236,7 +1268,6 @@ void Widget::FindAllRootViews(HWND window, root_views->push_back(*it); } - //////////////////////////////////////////////////////////////////////////////// // Widget, public: diff --git a/views/widget/widget_win.h b/views/widget/widget_win.h index 28a3782..ed233d4 100644 --- a/views/widget/widget_win.h +++ b/views/widget/widget_win.h @@ -130,6 +130,7 @@ class WidgetWin : public app::WindowImpl, MSG_WM_INITMENUPOPUP(OnInitMenuPopup) MSG_WM_KEYDOWN(OnKeyDown) MSG_WM_KEYUP(OnKeyUp) + MSG_WM_KILLFOCUS(OnKillFocus) MSG_WM_SYSKEYDOWN(OnKeyDown) MSG_WM_SYSKEYUP(OnKeyUp) MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk) @@ -207,6 +208,7 @@ class WidgetWin : public app::WindowImpl, virtual FocusManager* GetFocusManager(); virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + virtual bool ContainsNativeView(gfx::NativeView native_view); // Overridden from MessageLoop::Observer: void WillProcessMessage(const MSG& msg); @@ -335,6 +337,7 @@ class WidgetWin : public app::WindowImpl, virtual void OnInitMenuPopup(HMENU menu, UINT position, BOOL is_system_menu); virtual void OnKeyDown(TCHAR c, UINT rep_cnt, UINT flags); virtual void OnKeyUp(TCHAR c, UINT rep_cnt, UINT flags); + virtual void OnKillFocus(HWND focused_window); virtual void OnLButtonDblClk(UINT flags, const CPoint& point); virtual void OnLButtonDown(UINT flags, const CPoint& point); virtual void OnLButtonUp(UINT flags, const CPoint& point); |