Commit faca50ae authored by sky@chromium.org's avatar sky@chromium.org

Fixes focus traversal bug

When on the last focusable view in a Container FocusManager would
start searching from Widget. Problem is child Widgets share the same
FocusManager, meaning focus in a child widget wraps to the parent,
which is unexpected in most situations. In theory there could be
situations where we should wrap to the parent, but I don't think we
have any.

BUG=276213
TEST=covered by test
R=dmazzoni@chromium.org

Review URL: https://chromiumcodereview.appspot.com/23475012

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@221437 0039d316-1c4b-4281-b951-d872f2087c98
parent 22966839
...@@ -139,7 +139,7 @@ bool FocusManager::ContainsView(View* view) { ...@@ -139,7 +139,7 @@ bool FocusManager::ContainsView(View* view) {
} }
void FocusManager::AdvanceFocus(bool reverse) { void FocusManager::AdvanceFocus(bool reverse) {
View* v = GetNextFocusableView(focused_view_, reverse, false); View* v = GetNextFocusableView(focused_view_, NULL, reverse, false);
// Note: Do not skip this next block when v == focused_view_. If the user // Note: Do not skip this next block when v == focused_view_. If the user
// tabs past the last focusable element in a webpage, we'll get here, and if // tabs past the last focusable element in a webpage, we'll get here, and if
// the TabContentsContainerView is the only focusable view (possible in // the TabContentsContainerView is the only focusable view (possible in
...@@ -219,6 +219,7 @@ bool FocusManager::RotatePaneFocus(Direction direction, ...@@ -219,6 +219,7 @@ bool FocusManager::RotatePaneFocus(Direction direction,
} }
View* FocusManager::GetNextFocusableView(View* original_starting_view, View* FocusManager::GetNextFocusableView(View* original_starting_view,
Widget* starting_widget,
bool reverse, bool reverse,
bool dont_loop) { bool dont_loop) {
FocusTraversable* focus_traversable = NULL; FocusTraversable* focus_traversable = NULL;
...@@ -262,7 +263,8 @@ View* FocusManager::GetNextFocusableView(View* original_starting_view, ...@@ -262,7 +263,8 @@ View* FocusManager::GetNextFocusableView(View* original_starting_view,
} }
} }
} else { } else {
focus_traversable = widget_->GetFocusTraversable(); Widget* widget = starting_widget ? starting_widget : widget_;
focus_traversable = widget->GetFocusTraversable();
} }
// Traverse the FocusTraversable tree down to find the focusable view. // Traverse the FocusTraversable tree down to find the focusable view.
...@@ -303,9 +305,12 @@ View* FocusManager::GetNextFocusableView(View* original_starting_view, ...@@ -303,9 +305,12 @@ View* FocusManager::GetNextFocusableView(View* original_starting_view,
// infinitely looping in empty windows. // infinitely looping in empty windows.
if (!dont_loop && original_starting_view) { if (!dont_loop && original_starting_view) {
// Easy, just clear the selection and press tab again. // Easy, just clear the selection and press tab again.
// By calling with NULL as the starting view, we'll start from the // By calling with NULL as the starting view, we'll start from either
// top_root_view. // the starting views widget or |widget_|.
return GetNextFocusableView(NULL, reverse, true); Widget* widget = original_starting_view->GetWidget();
if (widget->widget_delegate()->ShouldAdvanceFocusToTopLevelWidget())
widget = widget_;
return GetNextFocusableView(NULL, widget, reverse, true);
} }
} }
return NULL; return NULL;
......
...@@ -308,8 +308,16 @@ class VIEWS_EXPORT FocusManager { ...@@ -308,8 +308,16 @@ class VIEWS_EXPORT FocusManager {
} }
private: private:
// Returns the next focusable view. // Returns the next focusable view. Traversal starts at |starting_view|. If
View* GetNextFocusableView(View* starting_view, bool reverse, bool dont_loop); // |starting_view| is NULL |starting_widget| is consuled to determine which
// Widget to start from. See
// WidgetDelegate::ShouldAdvanceFocusToTopLevelWidget() for details. If both
// |starting_view| and |starting_widget| are NULL, traversal starts at
// |widget_|.
View* GetNextFocusableView(View* starting_view,
Widget* starting_widget,
bool reverse,
bool dont_loop);
// Returns the focusable view found in the FocusTraversable specified starting // Returns the focusable view found in the FocusTraversable specified starting
// at the specified view. This traverses down along the FocusTraversable // at the specified view. This traverses down along the FocusTraversable
......
...@@ -829,4 +829,85 @@ TEST_F(FocusManagerTest, StoreFocusedView) { ...@@ -829,4 +829,85 @@ TEST_F(FocusManagerTest, StoreFocusedView) {
EXPECT_EQ(&view, GetFocusManager()->GetStoredFocusView()); EXPECT_EQ(&view, GetFocusManager()->GetStoredFocusView());
} }
namespace {
// Trivial WidgetDelegate implementation that allows setting return value of
// ShouldAdvanceFocusToTopLevelWidget().
class AdvanceFocusWidgetDelegate : public WidgetDelegate {
public:
explicit AdvanceFocusWidgetDelegate(Widget* widget)
: widget_(widget),
should_advance_focus_to_parent_(false) {}
virtual ~AdvanceFocusWidgetDelegate() {}
void set_should_advance_focus_to_parent(bool value) {
should_advance_focus_to_parent_ = value;
}
// WidgetDelegate overrides:
virtual bool ShouldAdvanceFocusToTopLevelWidget() const OVERRIDE {
return should_advance_focus_to_parent_;
}
virtual Widget* GetWidget() OVERRIDE { return widget_; }
virtual const Widget* GetWidget() const OVERRIDE { return widget_; }
private:
Widget* widget_;
bool should_advance_focus_to_parent_;
DISALLOW_COPY_AND_ASSIGN(AdvanceFocusWidgetDelegate);
};
} // namespace
// Verifies focus wrapping happens in the same widget.
TEST_F(FocusManagerTest, AdvanceFocusStaysInWidget) {
// Add |widget_view| as a child of the Widget.
View* widget_view = new View;
widget_view->set_focusable(true);
widget_view->SetBounds(20, 0, 20, 20);
GetContentsView()->AddChildView(widget_view);
// Create a widget with two views, focus the second.
scoped_ptr<AdvanceFocusWidgetDelegate> delegate;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.child = true;
params.bounds = gfx::Rect(10, 10, 100, 100);
params.parent = GetWidget()->GetNativeView();
Widget child_widget;
delegate.reset(new AdvanceFocusWidgetDelegate(&child_widget));
params.delegate = delegate.get();
child_widget.Init(params);
View* view1 = new View;
view1->set_focusable(true);
view1->SetBounds(0, 0, 20, 20);
View* view2 = new View;
view2->set_focusable(true);
view2->SetBounds(20, 0, 20, 20);
child_widget.client_view()->AddChildView(view1);
child_widget.client_view()->AddChildView(view2);
child_widget.Show();
view2->RequestFocus();
EXPECT_EQ(view2, GetFocusManager()->GetFocusedView());
// Advance focus backwards, which should focus the first.
GetFocusManager()->AdvanceFocus(false);
EXPECT_EQ(view1, GetFocusManager()->GetFocusedView());
// Focus forward to |view2|.
GetFocusManager()->AdvanceFocus(true);
EXPECT_EQ(view2, GetFocusManager()->GetFocusedView());
// And forward again, wrapping back to |view1|.
GetFocusManager()->AdvanceFocus(true);
EXPECT_EQ(view1, GetFocusManager()->GetFocusedView());
// Allow focus to go to the parent, and focus backwards which should now move
// up |widget_view| (in the parent).
delegate->set_should_advance_focus_to_parent(true);
GetFocusManager()->AdvanceFocus(true);
EXPECT_EQ(widget_view, GetFocusManager()->GetFocusedView());
}
} // namespace views } // namespace views
...@@ -86,10 +86,15 @@ class DefaultWidgetDelegate : public WidgetDelegate { ...@@ -86,10 +86,15 @@ class DefaultWidgetDelegate : public WidgetDelegate {
virtual const Widget* GetWidget() const OVERRIDE { virtual const Widget* GetWidget() const OVERRIDE {
return widget_; return widget_;
} }
virtual bool CanActivate() const OVERRIDE { virtual bool CanActivate() const OVERRIDE {
return can_activate_; return can_activate_;
} }
virtual bool ShouldAdvanceFocusToTopLevelWidget() const OVERRIDE {
// In most situations where a Widget is used without a delegate the Widget
// is used as a container, so that we want focus to advance to the top-level
// widget. A good example of this is the find bar.
return true;
}
private: private:
Widget* widget_; Widget* widget_;
......
...@@ -769,8 +769,8 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, ...@@ -769,8 +769,8 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate,
ObserverList<WidgetObserver> observers_; ObserverList<WidgetObserver> observers_;
// Non-owned pointer to the Widget's delegate. May be NULL if no delegate is // Non-owned pointer to the Widget's delegate. If a NULL delegate is supplied
// being used. // to Init() a default WidgetDelegate is created.
WidgetDelegate* widget_delegate_; WidgetDelegate* widget_delegate_;
// The root of the View hierarchy attached to this window. // The root of the View hierarchy attached to this window.
......
...@@ -162,6 +162,10 @@ void WidgetDelegate::GetWidgetHitTestMask(gfx::Path* mask) const { ...@@ -162,6 +162,10 @@ void WidgetDelegate::GetWidgetHitTestMask(gfx::Path* mask) const {
DCHECK(mask); DCHECK(mask);
} }
bool WidgetDelegate::ShouldAdvanceFocusToTopLevelWidget() const {
return false;
}
bool WidgetDelegate::ShouldDescendIntoChildForEventHandling( bool WidgetDelegate::ShouldDescendIntoChildForEventHandling(
gfx::NativeView child, gfx::NativeView child,
const gfx::Point& location) { const gfx::Point& location) {
......
...@@ -159,6 +159,11 @@ class VIEWS_EXPORT WidgetDelegate { ...@@ -159,6 +159,11 @@ class VIEWS_EXPORT WidgetDelegate {
// Provides the hit-test mask if HasHitTestMask above returns true. // Provides the hit-test mask if HasHitTestMask above returns true.
virtual void GetWidgetHitTestMask(gfx::Path* mask) const; virtual void GetWidgetHitTestMask(gfx::Path* mask) const;
// Returns true if focus should advance to the top level widget when
// tab/shift-tab is hit and on the last/first focusable view. Default returns
// false, which means tab/shift-tab never advance to the top level Widget.
virtual bool ShouldAdvanceFocusToTopLevelWidget() const;
// Returns true if event handling should descend into |child|. // Returns true if event handling should descend into |child|.
// |location| is in terms of the Window. // |location| is in terms of the Window.
virtual bool ShouldDescendIntoChildForEventHandling( virtual bool ShouldDescendIntoChildForEventHandling(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment