diff options
author | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-30 01:04:08 +0000 |
---|---|---|
committer | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-30 01:04:08 +0000 |
commit | 242aa4f94fa0bcc9558e17217c464cf5a98f790b (patch) | |
tree | 49c51ac5c88c2ead11294d9406dcb6026e50b6a3 | |
parent | da0ade73522ccbb0284cedf2764a68465f691dc5 (diff) | |
download | chromium_src-242aa4f94fa0bcc9558e17217c464cf5a98f790b.zip chromium_src-242aa4f94fa0bcc9558e17217c464cf5a98f790b.tar.gz chromium_src-242aa4f94fa0bcc9558e17217c464cf5a98f790b.tar.bz2 |
Fix cycling of focusable panes in Chrome OS.
Update the ash window cycle controller so it can tell aura::Window to rotate
pane focus and pass it the cycle direction, rather than just activating each
window. The end result is that when you press Ctrl+Back and Ctrl+Forward
you get a consistent cycle now, going through all panes within the most
recently active browser window and then the launcher and status area.
BUG=154598
Review URL: https://chromiumcodereview.appspot.com/13861032
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@197199 0039d316-1c4b-4281-b951-d872f2087c98
29 files changed, 391 insertions, 249 deletions
diff --git a/ash/accelerators/accelerator_controller.cc b/ash/accelerators/accelerator_controller.cc index 19f3f3f..d813b41 100644 --- a/ash/accelerators/accelerator_controller.cc +++ b/ash/accelerators/accelerator_controller.cc @@ -121,17 +121,14 @@ bool HandleToggleSpokenFeedback() { #endif // defined(OS_CHROMEOS) bool HandleRotatePaneFocus(Shell::Direction direction) { - if (!Shell::GetInstance()->delegate()->RotatePaneFocus(direction)) { - // No browser window is available. Focus the launcher. - Shell* shell = Shell::GetInstance(); - switch (direction) { - case Shell::FORWARD: - shell->focus_cycler()->RotateFocus(internal::FocusCycler::FORWARD); - break; - case Shell::BACKWARD: - shell->focus_cycler()->RotateFocus(internal::FocusCycler::BACKWARD); - break; - } + Shell* shell = Shell::GetInstance(); + switch (direction) { + case Shell::FORWARD: + shell->focus_cycler()->RotateFocus(internal::FocusCycler::FORWARD); + break; + case Shell::BACKWARD: + shell->focus_cycler()->RotateFocus(internal::FocusCycler::BACKWARD); + break; } return true; } diff --git a/ash/focus_cycler.cc b/ash/focus_cycler.cc index ee9d22f..81611dc 100644 --- a/ash/focus_cycler.cc +++ b/ash/focus_cycler.cc @@ -6,6 +6,7 @@ #include "ash/shell.h" #include "ash/wm/window_cycle_controller.h" +#include "ash/wm/window_util.h" #include "ui/aura/client/activation_client.h" #include "ui/aura/window.h" #include "ui/views/accessible_pane_view.h" @@ -35,6 +36,19 @@ void FocusCycler::AddWidget(views::Widget* widget) { } void FocusCycler::RotateFocus(Direction direction) { + aura::Window* window = ash::wm::GetActiveWindow(); + if (window) { + views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window); + // First try to rotate focus within the active widget. If that succeeds, + // we're done. + if (widget && widget->GetFocusManager()->RotatePaneFocus( + direction == BACKWARD ? + views::FocusManager::kBackward : views::FocusManager::kForward, + views::FocusManager::kNoWrap)) { + return; + } + } + const bool has_window = HasFocusableWindow(); int index = 0; int count = static_cast<int>(widgets_.size()); @@ -61,12 +75,23 @@ void FocusCycler::RotateFocus(Direction direction) { break; if (index == browser_index) { - // Activate the first window. - WindowCycleController::Direction window_direction = - direction == FORWARD ? WindowCycleController::FORWARD : - WindowCycleController::BACKWARD; + // Activate the most recently active browser window. ash::Shell::GetInstance()->window_cycle_controller()->HandleCycleWindow( - window_direction, false); + WindowCycleController::FORWARD, false); + + // Rotate pane focus within that window. + aura::Window* window = ash::wm::GetActiveWindow(); + if (!window) + break; + views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window); + if (!widget) + break; + views::FocusManager* focus_manager = widget->GetFocusManager(); + focus_manager->ClearFocus(); + focus_manager->RotatePaneFocus( + direction == BACKWARD ? + views::FocusManager::kBackward : views::FocusManager::kForward, + views::FocusManager::kWrap); break; } else { if (FocusWidget(widgets_[index])) diff --git a/ash/focus_cycler_unittest.cc b/ash/focus_cycler_unittest.cc index 0ff7a9b..5854f8d 100644 --- a/ash/focus_cycler_unittest.cc +++ b/ash/focus_cycler_unittest.cc @@ -15,8 +15,11 @@ #include "ash/wm/window_util.h" #include "ash/test/ash_test_base.h" #include "ash/shell_factory.h" +#include "ui/aura/root_window.h" +#include "ui/aura/test/event_generator.h" #include "ui/aura/test/test_windows.h" #include "ui/aura/window.h" +#include "ui/views/accessible_pane_view.h" #include "ui/views/controls/button/menu_button.h" #include "ui/views/widget/widget.h" @@ -34,6 +37,31 @@ internal::StatusAreaWidgetDelegate* GetStatusAreaWidgetDelegate( widget->GetContentsView()); } +class PanedWidgetDelegate : public views::WidgetDelegate { + public: + PanedWidgetDelegate(views::Widget* widget) : widget_(widget) {} + + void SetAccessiblePanes(const std::vector<views::View*>& panes) { + accessible_panes_ = panes; + } + + // views::WidgetDelegate. + virtual void GetAccessiblePanes(std::vector<views::View*>* panes) { + std::copy(accessible_panes_.begin(), accessible_panes_.end(), + std::back_inserter(*panes)); + } + virtual views::Widget* GetWidget() OVERRIDE { + return widget_; + }; + virtual const views::Widget* GetWidget() const OVERRIDE { + return widget_; + } + + private: + views::Widget* widget_; + std::vector<views::View*> accessible_panes_; +}; + } // namespace class FocusCyclerTest : public AshTestBase { @@ -261,5 +289,113 @@ TEST_F(FocusCyclerTest, Shelf_CycleFocusBackwardInvisible) { EXPECT_TRUE(wm::IsActiveWindow(window0.get())); } +TEST_F(FocusCyclerTest, CycleFocusThroughWindowWithPanes) { + ASSERT_TRUE(CreateTray()); + + InstallFocusCycleOnShelf(); + + scoped_ptr<views::Widget> browser_widget(new views::Widget); + PanedWidgetDelegate* test_widget_delegate = + new PanedWidgetDelegate(browser_widget.get()); + views::Widget::InitParams widget_params( + views::Widget::InitParams::TYPE_WINDOW); + widget_params.context = CurrentContext(); + widget_params.delegate = test_widget_delegate; + widget_params.ownership = + views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + browser_widget->Init(widget_params); + browser_widget->Show(); + + aura::Window* browser_window = browser_widget->GetNativeView(); + + views::View* root_view = browser_widget->GetRootView(); + + views::AccessiblePaneView* pane1 = new views::AccessiblePaneView(); + root_view->AddChildView(pane1); + + views::View* view1 = new views::View; + view1->set_focusable(true); + pane1->AddChildView(view1); + + views::View* view2 = new views::View; + view2->set_focusable(true); + pane1->AddChildView(view2); + + views::AccessiblePaneView* pane2 = new views::AccessiblePaneView(); + root_view->AddChildView(pane2); + + views::View* view3 = new views::View; + view3->set_focusable(true); + pane2->AddChildView(view3); + + views::View* view4 = new views::View; + view4->set_focusable(true); + pane2->AddChildView(view4); + + std::vector<views::View*> panes; + panes.push_back(pane1); + panes.push_back(pane2); + + test_widget_delegate->SetAccessiblePanes(panes); + + views::FocusManager* focus_manager = browser_widget->GetFocusManager(); + + // Cycle focus to the status area. + focus_cycler()->RotateFocus(FocusCycler::FORWARD); + EXPECT_TRUE(tray()->GetWidget()->IsActive()); + + // Cycle focus to the shelf. + focus_cycler()->RotateFocus(FocusCycler::FORWARD); + EXPECT_TRUE(shelf_widget()->IsActive()); + + // Cycle focus to the first pane in the browser. + focus_cycler()->RotateFocus(FocusCycler::FORWARD); + EXPECT_TRUE(wm::IsActiveWindow(browser_window)); + EXPECT_EQ(focus_manager->GetFocusedView(), view1); + + // Cycle focus to the second pane in the browser. + focus_cycler()->RotateFocus(FocusCycler::FORWARD); + EXPECT_TRUE(wm::IsActiveWindow(browser_window)); + EXPECT_EQ(focus_manager->GetFocusedView(), view3); + + // Cycle focus back to the status area. + focus_cycler()->RotateFocus(FocusCycler::FORWARD); + EXPECT_TRUE(tray()->GetWidget()->IsActive()); + + // Reverse direction - back to the second pane in the browser. + focus_cycler()->RotateFocus(FocusCycler::BACKWARD); + EXPECT_TRUE(wm::IsActiveWindow(browser_window)); + EXPECT_EQ(focus_manager->GetFocusedView(), view3); + + // Back to the first pane in the browser. + focus_cycler()->RotateFocus(FocusCycler::BACKWARD); + EXPECT_TRUE(wm::IsActiveWindow(browser_window)); + EXPECT_EQ(focus_manager->GetFocusedView(), view1); + + // Back to the shelf. + focus_cycler()->RotateFocus(FocusCycler::BACKWARD); + EXPECT_TRUE(shelf_widget()->IsActive()); + + // Back to the status area. + focus_cycler()->RotateFocus(FocusCycler::BACKWARD); + EXPECT_TRUE(tray()->GetWidget()->IsActive()); + + // Pressing "Escape" while on the status area should + // deactivate it, and activate the browser window. + aura::RootWindow* root = Shell::GetPrimaryRootWindow(); + aura::test::EventGenerator event_generator(root, root); + event_generator.PressKey(ui::VKEY_ESCAPE, 0); + EXPECT_TRUE(wm::IsActiveWindow(browser_window)); + EXPECT_EQ(focus_manager->GetFocusedView(), view1); + + // Similarly, pressing "Escape" while on the shelf. + // should do the same thing. + focus_cycler()->RotateFocus(FocusCycler::BACKWARD); + EXPECT_TRUE(shelf_widget()->IsActive()); + event_generator.PressKey(ui::VKEY_ESCAPE, 0); + EXPECT_TRUE(wm::IsActiveWindow(browser_window)); + EXPECT_EQ(focus_manager->GetFocusedView(), view1); +} + } // namespace test } // namespace ash diff --git a/ash/launcher/launcher.cc b/ash/launcher/launcher.cc index 226432c..57d9184 100644 --- a/ash/launcher/launcher.cc +++ b/ash/launcher/launcher.cc @@ -38,6 +38,8 @@ namespace ash { +const char Launcher::kNativeViewName[] = "LauncherView"; + Launcher::Launcher(LauncherModel* launcher_model, LauncherDelegate* launcher_delegate, ShelfWidget* shelf_widget) @@ -49,7 +51,7 @@ Launcher::Launcher(LauncherModel* launcher_model, launcher_model, delegate_, shelf_widget_->shelf_layout_manager()); launcher_view_->Init(); shelf_widget_->GetContentsView()->AddChildView(launcher_view_); - shelf_widget_->GetNativeView()->SetName("LauncherView"); + shelf_widget_->GetNativeView()->SetName(kNativeViewName); shelf_widget_->GetNativeView()->SetProperty( internal::kStayInSameRootWindowKey, true); delegate_->OnLauncherCreated(this); diff --git a/ash/launcher/launcher.h b/ash/launcher/launcher.h index 8c61ce3..7d7f3cd 100644 --- a/ash/launcher/launcher.h +++ b/ash/launcher/launcher.h @@ -40,6 +40,8 @@ class ShelfWidget; class ASH_EXPORT Launcher { public: + static const char kNativeViewName[]; + Launcher(LauncherModel* launcher_model, LauncherDelegate* launcher_delegate, ShelfWidget* shelf_widget); diff --git a/ash/shell/shell_delegate_impl.cc b/ash/shell/shell_delegate_impl.cc index 25d092a..cabd683 100644 --- a/ash/shell/shell_delegate_impl.cc +++ b/ash/shell/shell_delegate_impl.cc @@ -119,10 +119,6 @@ void ShellDelegateImpl::OpenMobileSetup(const std::string& service_path) { void ShellDelegateImpl::RestoreTab() { } -bool ShellDelegateImpl::RotatePaneFocus(Shell::Direction direction) { - return true; -} - void ShellDelegateImpl::ShowKeyboardOverlay() { } diff --git a/ash/shell/shell_delegate_impl.h b/ash/shell/shell_delegate_impl.h index ddd6b95..a74fd7f 100644 --- a/ash/shell/shell_delegate_impl.h +++ b/ash/shell/shell_delegate_impl.h @@ -41,7 +41,6 @@ class ShellDelegateImpl : public ash::ShellDelegate { virtual void OpenCrosh() OVERRIDE; virtual void OpenMobileSetup(const std::string& service_path) OVERRIDE; virtual void RestoreTab() OVERRIDE; - virtual bool RotatePaneFocus(Shell::Direction direction) OVERRIDE; virtual void ShowKeyboardOverlay() OVERRIDE; virtual keyboard::KeyboardControllerProxy* CreateKeyboardControllerProxy() OVERRIDE; diff --git a/ash/shell_delegate.h b/ash/shell_delegate.h index c561bf1..5edc151 100644 --- a/ash/shell_delegate.h +++ b/ash/shell_delegate.h @@ -145,10 +145,6 @@ class ASH_EXPORT ShellDelegate { // Invoked when the user uses Shift+Ctrl+T to restore the closed tab. virtual void RestoreTab() = 0; - // Moves keyboard focus to the next pane. Returns false if no browser window - // is created. - virtual bool RotatePaneFocus(Shell::Direction direction) = 0; - // Shows the keyboard shortcut overlay. virtual void ShowKeyboardOverlay() = 0; diff --git a/ash/system/status_area_widget.cc b/ash/system/status_area_widget.cc index f818cbf..1430bd1 100644 --- a/ash/system/status_area_widget.cc +++ b/ash/system/status_area_widget.cc @@ -24,6 +24,8 @@ namespace ash { namespace internal { +const char StatusAreaWidget::kNativeViewName[] = "StatusAreaWidget"; + StatusAreaWidget::StatusAreaWidget(aura::Window* status_container) : status_area_widget_delegate_(new internal::StatusAreaWidgetDelegate), system_tray_(NULL), @@ -37,7 +39,7 @@ StatusAreaWidget::StatusAreaWidget(aura::Window* status_container) Init(params); set_focus_on_creation(false); SetContentsView(status_area_widget_delegate_); - GetNativeView()->SetName("StatusAreaWidget"); + GetNativeView()->SetName(kNativeViewName); GetNativeView()->SetProperty(internal::kStayInSameRootWindowKey, true); } diff --git a/ash/system/status_area_widget.h b/ash/system/status_area_widget.h index 78a959d..4056710 100644 --- a/ash/system/status_area_widget.h +++ b/ash/system/status_area_widget.h @@ -22,6 +22,8 @@ class StatusAreaWidgetDelegate; class ASH_EXPORT StatusAreaWidget : public views::Widget { public: + static const char kNativeViewName[]; + explicit StatusAreaWidget(aura::Window* status_container); virtual ~StatusAreaWidget(); diff --git a/ash/test/test_shell_delegate.cc b/ash/test/test_shell_delegate.cc index 17792c7..f37f851 100644 --- a/ash/test/test_shell_delegate.cc +++ b/ash/test/test_shell_delegate.cc @@ -82,10 +82,6 @@ void TestShellDelegate::OpenMobileSetup(const std::string& service_path) { void TestShellDelegate::RestoreTab() { } -bool TestShellDelegate::RotatePaneFocus(Shell::Direction direction) { - return true; -} - void TestShellDelegate::ShowKeyboardOverlay() { } diff --git a/ash/test/test_shell_delegate.h b/ash/test/test_shell_delegate.h index 04f2997..2603a2a 100644 --- a/ash/test/test_shell_delegate.h +++ b/ash/test/test_shell_delegate.h @@ -40,7 +40,6 @@ class TestShellDelegate : public ShellDelegate { virtual void OpenCrosh() OVERRIDE; virtual void OpenMobileSetup(const std::string& service_path) OVERRIDE; virtual void RestoreTab() OVERRIDE; - virtual bool RotatePaneFocus(Shell::Direction direction) OVERRIDE; virtual void ShowKeyboardOverlay() OVERRIDE; virtual keyboard::KeyboardControllerProxy* CreateKeyboardControllerProxy() OVERRIDE; diff --git a/chrome/browser/chromeos/system/ash_focus_cycle_browsertest.cc b/chrome/browser/chromeos/system/ash_focus_cycle_browsertest.cc deleted file mode 100644 index 6a909cf..0000000 --- a/chrome/browser/chromeos/system/ash_focus_cycle_browsertest.cc +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "ash/focus_cycler.h" -#include "ash/shell.h" -#include "ash/wm/window_util.h" -#include "base/bind.h" -#include "base/memory/scoped_ptr.h" -#include "base/run_loop.h" -#include "base/stringprintf.h" -#include "chrome/browser/chromeos/cros/cros_in_process_browser_test.h" -#include "chrome/test/base/ui_controls.h" -#include "content/public/test/test_utils.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "ui/aura/window.h" -#include "ui/base/accessibility/accessibility_types.h" -#include "ui/base/accessibility/accessible_view_state.h" -#include "ui/views/focus/focus_manager.h" -#include "ui/views/widget/widget.h" - -namespace chromeos { - -class AshFocusCycleTest : public CrosInProcessBrowserTest { - protected: - AshFocusCycleTest() {} - virtual ~AshFocusCycleTest() {} - - void CheckFocusedViewIsAccessible() { - SCOPED_TRACE(base::StringPrintf( - "while active window is %s", - ash::wm::GetActiveWindow()->name().c_str())); - aura::Window* window = ash::wm::GetActiveWindow(); - CHECK(window); - views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window); - CHECK(widget); - views::FocusManager* focus_manager = widget->GetFocusManager(); - CHECK(focus_manager); - views::View* view = focus_manager->GetFocusedView(); - ASSERT_TRUE(view != NULL); - ui::AccessibleViewState state; - view->GetAccessibleState(&state); - ASSERT_NE(state.role, ui::AccessibilityTypes::ROLE_CLIENT); - ASSERT_FALSE(state.name.empty()); - } - - void PressEscape() { - aura::Window* window = ash::wm::GetActiveWindow(); - CHECK(window); - wait_for_key_press_.reset(new base::RunLoop()); - ui_controls::SendKeyPressNotifyWhenDone( - window, ui::VKEY_ESCAPE, - false, false, false, false, - base::Bind(&AshFocusCycleTest::QuitWaiting, base::Unretained(this))); - wait_for_key_press_->Run(); - } - - private: - scoped_ptr<base::RunLoop> wait_for_key_press_; - - void QuitWaiting() { - wait_for_key_press_->Quit(); - } -}; - -IN_PROC_BROWSER_TEST_F(AshFocusCycleTest, CycleFocus) { - // Initially, the browser frame should have focus. - ASSERT_STREQ("BrowserFrameAura", ash::wm::GetActiveWindow()->name().c_str()); - CheckFocusedViewIsAccessible(); - - // Rotate focus, as if the user pressed Ctrl+Forward (Ctrl+F2). - // The first item focused should be in the system tray. - ash::Shell* shell = ash::Shell::GetInstance(); - shell->RotateFocus(ash::Shell::FORWARD); - ASSERT_STREQ("StatusAreaWidget", ash::wm::GetActiveWindow()->name().c_str()); - CheckFocusedViewIsAccessible(); - - // Rotate focus again, now we should have something in the launcher focused. - shell->RotateFocus(ash::Shell::FORWARD); - ASSERT_STREQ("LauncherView", ash::wm::GetActiveWindow()->name().c_str()); - CheckFocusedViewIsAccessible(); - - // Finally we should be back at the browser frame. - shell->RotateFocus(ash::Shell::FORWARD); - ASSERT_STREQ("BrowserFrameAura", ash::wm::GetActiveWindow()->name().c_str()); - CheckFocusedViewIsAccessible(); -} - -IN_PROC_BROWSER_TEST_F(AshFocusCycleTest, EscapeFromSystemTray) { - ash::Shell* shell = ash::Shell::GetInstance(); - shell->RotateFocus(ash::Shell::FORWARD); - ASSERT_STREQ("StatusAreaWidget", ash::wm::GetActiveWindow()->name().c_str()); - - PressEscape(); - ASSERT_STREQ("BrowserFrameAura", ash::wm::GetActiveWindow()->name().c_str()); - CheckFocusedViewIsAccessible(); -} - -IN_PROC_BROWSER_TEST_F(AshFocusCycleTest, EscapeFromLauncher) { - ash::Shell* shell = ash::Shell::GetInstance(); - shell->RotateFocus(ash::Shell::FORWARD); - shell->RotateFocus(ash::Shell::FORWARD); - ASSERT_STREQ("LauncherView", ash::wm::GetActiveWindow()->name().c_str()); - - PressEscape(); - ASSERT_STREQ("BrowserFrameAura", ash::wm::GetActiveWindow()->name().c_str()); - CheckFocusedViewIsAccessible(); -} - -} // namespace chromeos diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.cc b/chrome/browser/ui/ash/chrome_shell_delegate.cc index d6de758..07cf21f 100644 --- a/chrome/browser/ui/ash/chrome_shell_delegate.cc +++ b/chrome/browser/ui/ash/chrome_shell_delegate.cc @@ -168,26 +168,6 @@ void ChromeShellDelegate::RestoreTab() { } } -bool ChromeShellDelegate::RotatePaneFocus(ash::Shell::Direction direction) { - aura::Window* window = ash::wm::GetActiveWindow(); - if (!window) - return false; - - Browser* browser = chrome::FindBrowserWithWindow(window); - if (!browser) - return false; - - switch (direction) { - case ash::Shell::FORWARD: - chrome::FocusNextPane(browser); - break; - case ash::Shell::BACKWARD: - chrome::FocusPreviousPane(browser); - break; - } - return true; -} - void ChromeShellDelegate::ShowTaskManager() { chrome::OpenTaskManager(NULL, false); } diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.h b/chrome/browser/ui/ash/chrome_shell_delegate.h index 7357130..7ec4365 100644 --- a/chrome/browser/ui/ash/chrome_shell_delegate.h +++ b/chrome/browser/ui/ash/chrome_shell_delegate.h @@ -61,7 +61,6 @@ class ChromeShellDelegate : public ash::ShellDelegate, virtual void OpenCrosh() OVERRIDE; virtual void OpenMobileSetup(const std::string& service_path) OVERRIDE; virtual void RestoreTab() OVERRIDE; - virtual bool RotatePaneFocus(ash::Shell::Direction direction) OVERRIDE; virtual void ShowKeyboardOverlay() OVERRIDE; virtual keyboard::KeyboardControllerProxy* CreateKeyboardControllerProxy() OVERRIDE; diff --git a/chrome/browser/ui/views/frame/browser_frame_aura.cc b/chrome/browser/ui/views/frame/browser_frame_aura.cc index 61fd350..a9688e1 100644 --- a/chrome/browser/ui/views/frame/browser_frame_aura.cc +++ b/chrome/browser/ui/views/frame/browser_frame_aura.cc @@ -91,12 +91,15 @@ class BrowserFrameAura::WindowPropertyWatcher : public aura::WindowObserver { /////////////////////////////////////////////////////////////////////////////// // BrowserFrameAura, public: +// static +const char BrowserFrameAura::kWindowName[] = "BrowserFrameAura"; + BrowserFrameAura::BrowserFrameAura(BrowserFrame* browser_frame, BrowserView* browser_view) : views::NativeWidgetAura(browser_frame), browser_view_(browser_view), window_property_watcher_(new WindowPropertyWatcher(this, browser_frame)) { - GetNativeWindow()->SetName("BrowserFrameAura"); + GetNativeWindow()->SetName(kWindowName); GetNativeWindow()->AddObserver(window_property_watcher_.get()); #if defined(USE_ASH) bool gets_own_workspace = false; diff --git a/chrome/browser/ui/views/frame/browser_frame_aura.h b/chrome/browser/ui/views/frame/browser_frame_aura.h index 8b9b6b1..97bc371 100644 --- a/chrome/browser/ui/views/frame/browser_frame_aura.h +++ b/chrome/browser/ui/views/frame/browser_frame_aura.h @@ -22,6 +22,8 @@ class BrowserView; class BrowserFrameAura : public views::NativeWidgetAura, public NativeBrowserFrame { public: + static const char kWindowName[]; + BrowserFrameAura(BrowserFrame* browser_frame, BrowserView* browser_view); BrowserView* browser_view() const { return browser_view_; } diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc index 4924567..3a994a2 100644 --- a/chrome/browser/ui/views/frame/browser_view.cc +++ b/chrome/browser/ui/views/frame/browser_view.cc @@ -1034,75 +1034,10 @@ void BrowserView::FocusAppMenu() { } void BrowserView::RotatePaneFocus(bool forwards) { - // This gets called when the user presses F6 (forwards) or Shift+F6 - // (backwards) to rotate to the next pane. Here, our "panes" are the - // tab contents and each of our accessible toolbars, infobars, downloads - // shelf, etc. When a pane has focus, all of its controls are accessible - // in the tab traversal, and the tab traversal is "trapped" within that pane. - // - // Get a vector of all panes in the order we want them to be focused, - // with NULL to represent the tab contents getting focus. If one of these - // is currently invisible or has no focusable children it will be - // automatically skipped. - std::vector<views::AccessiblePaneView*> accessible_panes; - GetAccessiblePanes(&accessible_panes); - int pane_count = static_cast<int>(accessible_panes.size()); - int special_index = -1; - - std::vector<views::View*> accessible_views( - accessible_panes.begin(), accessible_panes.end()); - accessible_views.push_back(GetTabContentsContainerView()); - if (devtools_container_->visible()) - accessible_views.push_back(devtools_container_); - int count = static_cast<int>(accessible_views.size()); - - // Figure out which view (if any) currently has the focus. - const views::View* focused_view = GetFocusManager()->GetFocusedView(); - int index = -1; - if (focused_view) { - for (int i = 0; i < count; ++i) { - if (accessible_views[i] == focused_view || - accessible_views[i]->Contains(focused_view)) { - index = i; - break; - } - } - } - - // If the focus isn't currently in a pane, save the focus so we - // can restore it if the user presses Escape. - if (focused_view && index >= pane_count) - GetFocusManager()->StoreFocusedView(true); - -#if defined(OS_CHROMEOS) && defined(USE_AURA) - // Add the special panes to the rotation. - special_index = count; - ++count; -#endif - - // Try to focus the next pane; if SetPaneFocusAndFocusDefault returns - // false it means the pane didn't have any focusable controls, so skip - // it and try the next one. - for (;;) { - if (forwards) - index = (index + 1) % count; - else - index = ((index - 1) + count) % count; - - if (index == special_index) { -#if defined(USE_ASH) - ash::Shell::GetInstance()->RotateFocus( - forwards ? ash::Shell::FORWARD : ash::Shell::BACKWARD); -#endif - break; - } else if (index < pane_count) { - if (accessible_panes[index]->SetPaneFocusAndFocusDefault()) - break; - } else { - accessible_views[index]->RequestFocus(); - break; - } - } + GetWidget()->GetFocusManager()->RotatePaneFocus( + forwards ? + views::FocusManager::kForward : views::FocusManager::kBackward, + views::FocusManager::kWrap); } void BrowserView::DestroyBrowser() { @@ -1802,6 +1737,23 @@ const views::Widget* BrowserView::GetWidget() const { return View::GetWidget(); } +void BrowserView::GetAccessiblePanes(std::vector<views::View*>* panes) { + // This should be in the order of pane traversal of the panes using F6 + // (Windows) or Ctrl+Back/Forward (Chrome OS). If one of these is + // invisible or has no focusable children, it will be automatically + // skipped. + panes->push_back(toolbar_); + if (bookmark_bar_view_.get()) + panes->push_back(bookmark_bar_view_.get()); + if (infobar_container_) + panes->push_back(infobar_container_); + if (download_shelf_.get()) + panes->push_back(download_shelf_.get()); + panes->push_back(GetTabContentsContainerView()); + if (devtools_container_->visible()) + panes->push_back(devtools_container_); +} + /////////////////////////////////////////////////////////////////////////////// // BrowserView, views::ClientView overrides: @@ -1917,20 +1869,6 @@ bool BrowserView::AcceleratorPressed(const ui::Accelerator& accelerator) { /////////////////////////////////////////////////////////////////////////////// // BrowserView, private -void BrowserView::GetAccessiblePanes( - std::vector<views::AccessiblePaneView*>* panes) { - // This should be in the order of pane traversal of the panes using F6. - // If one of these is invisible or has no focusable children, it will be - // automatically skipped. - panes->push_back(toolbar_); - if (bookmark_bar_view_.get()) - panes->push_back(bookmark_bar_view_.get()); - if (infobar_container_) - panes->push_back(infobar_container_); - if (download_shelf_.get()) - panes->push_back(download_shelf_.get()); -} - SkColor BrowserView::GetInfoBarSeparatorColor() const { // NOTE: Keep this in sync with ToolbarView::OnPaint()! return (IsTabStripVisible() || !frame_->ShouldUseNativeFrame()) ? diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h index 296dfde..2b9c438 100644 --- a/chrome/browser/ui/views/frame/browser_view.h +++ b/chrome/browser/ui/views/frame/browser_view.h @@ -408,6 +408,7 @@ class BrowserView : public BrowserWindow, virtual void OnWidgetMove() OVERRIDE; virtual views::Widget* GetWidget() OVERRIDE; virtual const views::Widget* GetWidget() const OVERRIDE; + virtual void GetAccessiblePanes(std::vector<View*>* panes) OVERRIDE; // Overridden from views::WidgetObserver: virtual void OnWidgetActivationChanged(views::Widget* widget, diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index db677e0..2ee6816 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -392,7 +392,6 @@ 'browser/chromeos/login/screen_locker_tester.cc', 'browser/chromeos/login/screen_locker_tester.h', 'browser/chromeos/login/wallpaper_manager_browsertest.cc', - 'browser/chromeos/system/ash_focus_cycle_browsertest.cc', 'test/data/chromeos/service_login.html', ], 'sources!': [ diff --git a/ui/views/accessible_pane_view.cc b/ui/views/accessible_pane_view.cc index d344471..31b0e92 100644 --- a/ui/views/accessible_pane_view.cc +++ b/ui/views/accessible_pane_view.cc @@ -211,6 +211,10 @@ void AccessiblePaneView::GetAccessibleState(ui::AccessibleViewState* state) { state->role = ui::AccessibilityTypes::ROLE_PANE; } +void AccessiblePaneView::RequestFocus() { + SetPaneFocusAndFocusDefault(); +} + //////////////////////////////////////////////////////////////////////////////// // FocusChangeListener overrides: diff --git a/ui/views/accessible_pane_view.h b/ui/views/accessible_pane_view.h index a189919..99c392c 100644 --- a/ui/views/accessible_pane_view.h +++ b/ui/views/accessible_pane_view.h @@ -45,6 +45,7 @@ class VIEWS_EXPORT AccessiblePaneView : public View, OVERRIDE; virtual void SetVisible(bool flag) OVERRIDE; virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE; + virtual void RequestFocus() OVERRIDE; // Overridden from FocusChangeListener: virtual void OnWillChangeFocus(View* focused_before, diff --git a/ui/views/focus/focus_manager.cc b/ui/views/focus/focus_manager.cc index bee68c2..03f050a 100644 --- a/ui/views/focus/focus_manager.cc +++ b/ui/views/focus/focus_manager.cc @@ -19,6 +19,7 @@ #include "ui/views/view.h" #include "ui/views/widget/root_view.h" #include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" namespace views { @@ -154,6 +155,64 @@ void FocusManager::ClearNativeFocus() { widget_->ClearNativeFocus(); } +bool FocusManager::RotatePaneFocus(Direction direction, + FocusCycleWrappingBehavior wrap) { + // Get the list of all accessible panes. + std::vector<View*> panes; + widget_->widget_delegate()->GetAccessiblePanes(&panes); + + // Count the number of panes and set the default index if no pane + // is initially focused. + int count = static_cast<int>(panes.size()); + if (count == 0) + return false; + + // Initialize |index| to an appropriate starting index if nothing is + // focused initially. + int index = direction == kBackward ? 0 : count - 1; + + // Check to see if a pane already has focus and update the index accordingly. + const views::View* focused_view = GetFocusedView(); + if (focused_view) { + for (int i = 0; i < count; i++) { + if (panes[i] && panes[i]->Contains(focused_view)) { + index = i; + break; + } + } + } + + // Rotate focus. + int start_index = index; + for (;;) { + if (direction == kBackward) + index--; + else + index++; + + if (wrap == kNoWrap && (index >= count || index < 0)) + return false; + index = (index + count) % count; + + // Ensure that we don't loop more than once. + if (index == start_index) + break; + + views::View* pane = panes[index]; + DCHECK(pane); + + if (!pane->visible()) + continue; + + pane->RequestFocus(); + focused_view = GetFocusedView(); + if (pane == focused_view || pane->Contains(focused_view)) + return true; + } + + return false; +} + View* FocusManager::GetNextFocusableView(View* original_starting_view, bool reverse, bool dont_loop) { diff --git a/ui/views/focus/focus_manager.h b/ui/views/focus/focus_manager.h index 55310cf..7e92f00 100644 --- a/ui/views/focus/focus_manager.h +++ b/ui/views/focus/focus_manager.h @@ -136,6 +136,17 @@ class VIEWS_EXPORT FocusManager { kReasonDirectFocusChange }; + // TODO: use Direction in place of bool reverse throughout. + enum Direction { + kForward, + kBackward + }; + + enum FocusCycleWrappingBehavior { + kWrap, + kNoWrap + }; + FocusManager(Widget* widget, FocusManagerDelegate* delegate); virtual ~FocusManager(); @@ -267,6 +278,15 @@ class VIEWS_EXPORT FocusManager { // Clears the native view having the focus. virtual void ClearNativeFocus(); + // Focuses the next keyboard-accessible pane, taken from the list of + // views returned by WidgetDelegate::GetAccessiblePanes(). If there are + // no panes, the widget's root view is treated as a single pane. + // A keyboard-accessible pane should subclass from AccessiblePaneView in + // order to trap keyboard focus within that pane. If |wrap| is kWrap, + // it keeps cycling within this widget, otherwise it returns false after + // reaching the last pane so that focus can cycle to another widget. + bool RotatePaneFocus(Direction direction, FocusCycleWrappingBehavior wrap); + // Convenience method that returns true if the passed |key_event| should // trigger tab traversal (if it is a TAB key press with or without SHIFT // pressed). diff --git a/ui/views/focus/focus_manager_test.cc b/ui/views/focus/focus_manager_test.cc index 2868112..528f345 100644 --- a/ui/views/focus/focus_manager_test.cc +++ b/ui/views/focus/focus_manager_test.cc @@ -4,6 +4,8 @@ #include "ui/views/focus/focus_manager_test.h" +#include <algorithm> + #include "ui/views/focus/focus_manager.h" #include "ui/views/widget/widget.h" @@ -70,6 +72,11 @@ const Widget* FocusManagerTest::GetWidget() const { return contents_view_->GetWidget(); } +void FocusManagerTest::GetAccessiblePanes(std::vector<View*>* panes) { + std::copy(accessible_panes_.begin(), accessible_panes_.end(), + std::back_inserter(*panes)); +} + //////////////////////////////////////////////////////////////////////////////// // FocusManagerTest, protected: @@ -89,6 +96,10 @@ void FocusManagerTest::AddWidgetFocusChangeListener( WidgetFocusManager::GetInstance()->AddFocusChangeListener(listener); } +void FocusManagerTest::SetAccessiblePanes(const std::vector<View*>& panes) { + accessible_panes_ = panes; +} + #if defined(OS_WIN) && !defined(USE_AURA) void FocusManagerTest::SimulateActivateWindow() { SendMessage(GetWidget()->GetNativeWindow(), WM_ACTIVATE, WA_ACTIVE, NULL); diff --git a/ui/views/focus/focus_manager_test.h b/ui/views/focus/focus_manager_test.h index f2c0102..53e7b00 100644 --- a/ui/views/focus/focus_manager_test.h +++ b/ui/views/focus/focus_manager_test.h @@ -31,6 +31,7 @@ class FocusManagerTest : public ViewsTestBase, virtual View* GetContentsView() OVERRIDE; virtual Widget* GetWidget() OVERRIDE; virtual const Widget* GetWidget() const OVERRIDE; + virtual void GetAccessiblePanes(std::vector<View*>* panes) OVERRIDE; protected: // Called after the Widget is initialized and the content view is added. @@ -40,6 +41,9 @@ class FocusManagerTest : public ViewsTestBase, void AddFocusChangeListener(FocusChangeListener* listener); void AddWidgetFocusChangeListener(WidgetFocusChangeListener* listener); + // For testing FocusManager::RotatePaneFocus(). + void SetAccessiblePanes(const std::vector<View*>& panes); + #if defined(OS_WIN) && !defined(USE_AURA) // Mocks activating/deactivating the window. void SimulateActivateWindow(); @@ -53,6 +57,7 @@ class FocusManagerTest : public ViewsTestBase, View* contents_view_; FocusChangeListener* focus_change_listener_; WidgetFocusChangeListener* widget_focus_change_listener_; + std::vector<View*> accessible_panes_; DISALLOW_COPY_AND_ASSIGN(FocusManagerTest); }; diff --git a/ui/views/focus/focus_manager_unittest.cc b/ui/views/focus/focus_manager_unittest.cc index 44b5028..4f09d8b 100644 --- a/ui/views/focus/focus_manager_unittest.cc +++ b/ui/views/focus/focus_manager_unittest.cc @@ -8,6 +8,7 @@ #include "base/utf_string_conversions.h" #include "ui/base/accelerators/accelerator.h" #include "ui/base/keycodes/keyboard_codes.h" +#include "ui/views/accessible_pane_view.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/focus/accelerator_handler.h" @@ -646,4 +647,76 @@ TEST_F(FocusManagerTest, FocusInAboutToRequestFocusFromTabTraversal) { EXPECT_TRUE(v1->HasFocus()); } +TEST_F(FocusManagerTest, RotatePaneFocus) { + views::AccessiblePaneView* pane1 = new AccessiblePaneView(); + GetContentsView()->AddChildView(pane1); + + views::View* v1 = new View; + v1->set_focusable(true); + pane1->AddChildView(v1); + + views::View* v2 = new View; + v2->set_focusable(true); + pane1->AddChildView(v2); + + views::AccessiblePaneView* pane2 = new AccessiblePaneView(); + GetContentsView()->AddChildView(pane2); + + views::View* v3 = new View; + v3->set_focusable(true); + pane2->AddChildView(v3); + + views::View* v4 = new View; + v4->set_focusable(true); + pane2->AddChildView(v4); + + std::vector<views::View*> panes; + panes.push_back(pane1); + panes.push_back(pane2); + SetAccessiblePanes(panes); + + FocusManager* focus_manager = GetWidget()->GetFocusManager(); + + // Advance forwards. Focus should stay trapped within each pane. + EXPECT_TRUE(focus_manager->RotatePaneFocus( + FocusManager::kForward, FocusManager::kWrap)); + EXPECT_EQ(v1, focus_manager->GetFocusedView()); + focus_manager->AdvanceFocus(false); + EXPECT_EQ(v2, focus_manager->GetFocusedView()); + focus_manager->AdvanceFocus(false); + EXPECT_EQ(v1, focus_manager->GetFocusedView()); + + EXPECT_TRUE(focus_manager->RotatePaneFocus( + FocusManager::kForward, FocusManager::kWrap)); + EXPECT_EQ(v3, focus_manager->GetFocusedView()); + focus_manager->AdvanceFocus(false); + EXPECT_EQ(v4, focus_manager->GetFocusedView()); + focus_manager->AdvanceFocus(false); + EXPECT_EQ(v3, focus_manager->GetFocusedView()); + + EXPECT_TRUE(focus_manager->RotatePaneFocus( + FocusManager::kForward, FocusManager::kWrap)); + EXPECT_EQ(v1, focus_manager->GetFocusedView()); + + // Advance backwards. + EXPECT_TRUE(focus_manager->RotatePaneFocus( + FocusManager::kBackward, FocusManager::kWrap)); + EXPECT_EQ(v3, focus_manager->GetFocusedView()); + + EXPECT_TRUE(focus_manager->RotatePaneFocus( + FocusManager::kBackward, FocusManager::kWrap)); + EXPECT_EQ(v1, focus_manager->GetFocusedView()); + + // Advance without wrap. When it gets to the end of the list of + // panes, RotatePaneFocus should return false but the current + // focused view shouldn't change. + EXPECT_TRUE(focus_manager->RotatePaneFocus( + FocusManager::kForward, FocusManager::kNoWrap)); + EXPECT_EQ(v3, focus_manager->GetFocusedView()); + + EXPECT_FALSE(focus_manager->RotatePaneFocus( + FocusManager::kForward, FocusManager::kNoWrap)); + EXPECT_EQ(v3, focus_manager->GetFocusedView()); +} + } // namespace views diff --git a/ui/views/view.h b/ui/views/view.h index 8bd5603..306e3a9 100644 --- a/ui/views/view.h +++ b/ui/views/view.h @@ -732,7 +732,7 @@ class VIEWS_EXPORT View : public ui::LayerDelegate, virtual const FocusManager* GetFocusManager() const; // Request keyboard focus. The receiving view will become the focused view. - void RequestFocus(); + virtual void RequestFocus(); // Invoked when a view is about to be requested for focus due to the focus // traversal. Reverse is this request was generated going backward diff --git a/ui/views/widget/widget_delegate.h b/ui/views/widget/widget_delegate.h index a23f39a..6bf4d16 100644 --- a/ui/views/widget/widget_delegate.h +++ b/ui/views/widget/widget_delegate.h @@ -6,6 +6,7 @@ #define UI_VIEWS_WIDGET_WIDGET_DELEGATE_H_ #include <string> +#include <vector> #include "ui/base/accessibility/accessibility_types.h" #include "ui/base/ui_base_types.h" @@ -155,6 +156,10 @@ class VIEWS_EXPORT WidgetDelegate { gfx::NativeView child, const gfx::Point& location); + // Populates |panes| with accessible panes in this window that can + // be cycled through with keyboard focus. + virtual void GetAccessiblePanes(std::vector<View*>* panes) {} + protected: virtual ~WidgetDelegate() {} |