diff options
author | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-31 15:33:01 +0000 |
---|---|---|
committer | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-31 15:33:01 +0000 |
commit | cc8243726aa110ed22194e7ebde5ea69430b6ab4 (patch) | |
tree | 455cce5da51deeb47c962ea7c2cb62331e896973 /chrome/browser/automation | |
parent | 3c5f059a2881b0d990e49cbc2b1e5a3dd4e9df7f (diff) | |
download | chromium_src-cc8243726aa110ed22194e7ebde5ea69430b6ab4.zip chromium_src-cc8243726aa110ed22194e7ebde5ea69430b6ab4.tar.gz chromium_src-cc8243726aa110ed22194e7ebde5ea69430b6ab4.tar.bz2 |
Keyboard accessibility for the page and app menus.
Works on Windows, and on Linux with toolkit_views.
The goal is to make Chrome behave more like a standard Windows
application, for users who rely on the keyboard and expect standard
keyboard accelerators to work.
Pressing F10, or pressing and releasing Alt, will set focus to the
Page menu, as if it was the first item in a menu bar.
Pressing enter, space, up arrow, or down arrow will open the focused menu.
Once a menu is opened, pressing left and right arrows will switch between
the two menus. Pressing escape will return focus to the title of the
previously open menu.
A new UI test attempts to select something from the menus using only the
keyboard. It works on Linux (with toolkit_views) and on Windows.
BUG=none
TEST=New keyboard accessibility interactive ui test.
Review URL: http://codereview.chromium.org/660323
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43216 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/automation')
-rw-r--r-- | chrome/browser/automation/automation_provider.cc | 31 | ||||
-rw-r--r-- | chrome/browser/automation/automation_provider.h | 25 | ||||
-rw-r--r-- | chrome/browser/automation/automation_provider_gtk.cc | 5 | ||||
-rw-r--r-- | chrome/browser/automation/automation_provider_mac.mm | 5 | ||||
-rw-r--r-- | chrome/browser/automation/automation_provider_views.cc | 152 | ||||
-rw-r--r-- | chrome/browser/automation/automation_provider_win.cc | 13 | ||||
-rw-r--r-- | chrome/browser/automation/ui_controls_win.cc | 15 |
7 files changed, 222 insertions, 24 deletions
diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc index 0c264a28..57a9506 100644 --- a/chrome/browser/automation/automation_provider.cc +++ b/chrome/browser/automation/automation_provider.cc @@ -140,7 +140,8 @@ class ClickTask : public Task { AutomationProvider::AutomationProvider(Profile* profile) : redirect_query_(0), profile_(profile), - reply_message_(NULL) { + reply_message_(NULL), + popup_menu_waiter_(NULL) { browser_tracker_.reset(new AutomationBrowserTracker(this)); tab_tracker_.reset(new AutomationTabTracker(this)); window_tracker_.reset(new AutomationWindowTracker(this)); @@ -499,6 +500,14 @@ void AutomationProvider::OnMessageReceived(const IPC::Message& message) { LoginWithUserAndPass) #endif IPC_MESSAGE_HANDLER(AutomationMsg_RemoveBrowsingData, RemoveBrowsingData) +#if defined(TOOLKIT_VIEWS) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_WaitForFocusedViewIDToChange, + WaitForFocusedViewIDToChange) + IPC_MESSAGE_HANDLER(AutomationMsg_StartTrackingPopupMenus, + StartTrackingPopupMenus) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_WaitForPopupMenuToOpen, + WaitForPopupMenuToOpen) +#endif IPC_END_MESSAGE_MAP() } @@ -2537,3 +2546,23 @@ void AutomationProvider::SetContentSetting( *success = true; } } + +#if !defined(TOOLKIT_VIEWS) +void AutomationProvider::GetFocusedViewID(int handle, int* view_id) { + NOTIMPLEMENTED(); +}; + +void AutomationProvider::WaitForFocusedViewIDToChange( + int handle, int previous_view_id, IPC::Message* reply_message) { + NOTIMPLEMENTED(); +} + +void AutomationProvider::StartTrackingPopupMenus( + int browser_handle, bool* success) { + NOTIMPLEMENTED(); +} + +void AutomationProvider::WaitForPopupMenuToOpen(IPC::Message* reply_message) { + NOTIMPLEMENTED(); +} +#endif // !defined(TOOLKIT_VIEWS) diff --git a/chrome/browser/automation/automation_provider.h b/chrome/browser/automation/automation_provider.h index 6da47e2..2d680b1 100644 --- a/chrome/browser/automation/automation_provider.h +++ b/chrome/browser/automation/automation_provider.h @@ -35,6 +35,7 @@ #endif // defined(OS_WIN) struct AutomationMsg_Find_Params; +class PopupMenuWaiter; namespace IPC { struct Reposition_Params; @@ -133,6 +134,7 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>, protected: friend class base::RefCounted<AutomationProvider>; + friend class PopupMenuWaiter; virtual ~AutomationProvider(); private: @@ -582,6 +584,22 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>, // Returns NULL on failure. RenderViewHost* GetViewForTab(int tab_handle); + // Block until the focused view ID changes to something other than + // previous_view_id. + void WaitForFocusedViewIDToChange(int handle, + int previous_view_id, + IPC::Message* reply_message); + + // Start tracking popup menus. Must be called before executing the + // command that might open the popup menu; then call WaitForPopupMenuToOpen. + void StartTrackingPopupMenus(int browser_handle, bool* success); + + // Wait until a popup menu has opened. + void WaitForPopupMenuToOpen(IPC::Message* reply_message); + + // Method called by the popup menu tracker when a popup menu is opened. + void NotifyPopupMenuOpened(); + typedef ObserverList<NotificationObserver> NotificationObserverList; typedef std::map<NavigationController*, LoginHandler*> LoginHandlerMap; typedef std::map<int, ExtensionPortContainer*> PortContainerMap; @@ -617,6 +635,13 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>, IPC::Message* reply_message_; + // Keep track of whether a popup menu has been opened since the last time + // that StartTrackingPopupMenus has been called. + bool popup_menu_opened_; + + // A temporary object that receives a notification when a popup menu opens. + PopupMenuWaiter* popup_menu_waiter_; + DISALLOW_COPY_AND_ASSIGN(AutomationProvider); }; diff --git a/chrome/browser/automation/automation_provider_gtk.cc b/chrome/browser/automation/automation_provider_gtk.cc index 601e81a..62b378f 100644 --- a/chrome/browser/automation/automation_provider_gtk.cc +++ b/chrome/browser/automation/automation_provider_gtk.cc @@ -79,10 +79,6 @@ void AutomationProvider::IsWindowMaximized(int handle, bool* is_maximized, NOTIMPLEMENTED(); } -void AutomationProvider::GetFocusedViewID(int handle, int* view_id) { - NOTIMPLEMENTED(); -} - void AutomationProvider::PrintAsync(int tab_handle) { NOTIMPLEMENTED(); } @@ -227,4 +223,3 @@ void AutomationProvider::GetWindowTitle(int handle, string16* text) { const gchar* title = gtk_window_get_title(window); text->assign(UTF8ToUTF16(title)); } - diff --git a/chrome/browser/automation/automation_provider_mac.mm b/chrome/browser/automation/automation_provider_mac.mm index 8dff5f7..9f2ed97 100644 --- a/chrome/browser/automation/automation_provider_mac.mm +++ b/chrome/browser/automation/automation_provider_mac.mm @@ -66,10 +66,6 @@ void AutomationProvider::IsWindowMaximized(int handle, bool* is_maximized, NOTIMPLEMENTED(); } -void AutomationProvider::GetFocusedViewID(int handle, int* view_id) { - NOTIMPLEMENTED(); -} - void AutomationProvider::PrintAsync(int tab_handle) { NOTIMPLEMENTED(); } @@ -126,4 +122,3 @@ void AutomationProvider::GetWindowTitle(int handle, string16* text) { text->assign(base::SysNSStringToUTF16(title)); } - diff --git a/chrome/browser/automation/automation_provider_views.cc b/chrome/browser/automation/automation_provider_views.cc index f161105..f7a461c 100644 --- a/chrome/browser/automation/automation_provider_views.cc +++ b/chrome/browser/automation/automation_provider_views.cc @@ -4,7 +4,12 @@ #include "chrome/browser/automation/automation_provider.h" +#include "chrome/browser/views/frame/browser_view.h" +#include "chrome/browser/views/toolbar_view.h" +#include "chrome/test/automation/automation_messages.h" #include "gfx/point.h" +#include "views/controls/menu/menu_wrapper.h" +#include "views/focus/focus_manager.h" #include "views/view.h" #include "views/widget/root_view.h" #include "views/widget/widget.h" @@ -34,3 +39,150 @@ void AutomationProvider::WindowGetViewBounds(int handle, int view_id, } } +void AutomationProvider::GetFocusedViewID(int handle, int* view_id) { + *view_id = -1; + if (window_tracker_->ContainsHandle(handle)) { + gfx::NativeWindow window = window_tracker_->GetResource(handle); + views::FocusManager* focus_manager = + views::FocusManager::GetFocusManagerForNativeWindow(window); + DCHECK(focus_manager); + views::View* focused_view = focus_manager->GetFocusedView(); + if (focused_view) + *view_id = focused_view->GetID(); + } +} + +// Helper class that waits until the focus has changed to a view other +// than the one with the provided view id. +class ViewFocusChangeWaiter : public views::FocusChangeListener { + public: + ViewFocusChangeWaiter(views::FocusManager* focus_manager, + int previous_view_id, + AutomationProvider* automation, + IPC::Message* reply_message) + : focus_manager_(focus_manager), + previous_view_id_(previous_view_id), + automation_(automation), + reply_message_(reply_message), + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { + focus_manager_->AddFocusChangeListener(this); + // Call the focus change notification once in case the focus has + // already changed. + FocusWillChange(NULL, focus_manager_->GetFocusedView()); + } + + ~ViewFocusChangeWaiter() { + focus_manager_->RemoveFocusChangeListener(this); + } + + // Inherited from FocusChangeListener + virtual void FocusWillChange(views::View* focused_before, + views::View* focused_now) { + // This listener is called before focus actually changes. Post a task + // that will get run after focus changes. + MessageLoop::current()->PostTask( + FROM_HERE, + method_factory_.NewRunnableMethod( + &ViewFocusChangeWaiter::FocusChanged, + focused_before, + focused_now)); + } + + private: + void FocusChanged(views::View* focused_before, + views::View* focused_now) { + if (focused_now && focused_now->GetID() != previous_view_id_) { + AutomationMsg_WaitForFocusedViewIDToChange::WriteReplyParams( + reply_message_, true, focused_now->GetID()); + + automation_->Send(reply_message_); + delete this; + } + } + + views::FocusManager* focus_manager_; + int previous_view_id_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + ScopedRunnableMethodFactory<ViewFocusChangeWaiter> method_factory_; + + DISALLOW_COPY_AND_ASSIGN(ViewFocusChangeWaiter); +}; + +void AutomationProvider::WaitForFocusedViewIDToChange( + int handle, int previous_view_id, IPC::Message* reply_message) { + if (!window_tracker_->ContainsHandle(handle)) + return; + gfx::NativeWindow window = window_tracker_->GetResource(handle); + views::FocusManager* focus_manager = + views::FocusManager::GetFocusManagerForNativeWindow(window); + + // The waiter will respond to the IPC and delete itself when done. + new ViewFocusChangeWaiter(focus_manager, + previous_view_id, + this, + reply_message); +} + +class PopupMenuWaiter : public views::MenuListener { + public: + PopupMenuWaiter(ToolbarView* toolbar_view, + AutomationProvider* automation) + : toolbar_view_(toolbar_view), + automation_(automation), + reply_message_(NULL) { + toolbar_view_->AddMenuListener(this); + } + + // Implementation of views::MenuListener + virtual void OnMenuOpened() { + toolbar_view_->RemoveMenuListener(this); + automation_->popup_menu_opened_ = true; + automation_->popup_menu_waiter_ = NULL; + if (reply_message_) { + AutomationMsg_WaitForPopupMenuToOpen::WriteReplyParams( + reply_message_, true); + automation_->Send(reply_message_); + } + delete this; + } + + void set_reply_message(IPC::Message* reply_message) { + reply_message_ = reply_message; + } + + private: + ToolbarView* toolbar_view_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + + DISALLOW_COPY_AND_ASSIGN(PopupMenuWaiter); +}; + +void AutomationProvider::StartTrackingPopupMenus( + int browser_handle, bool* success) { + if (browser_tracker_->ContainsHandle(browser_handle)) { + Browser* browser = browser_tracker_->GetResource(browser_handle); + BrowserView* browser_view = reinterpret_cast<BrowserView*>( + browser->window()); + ToolbarView* toolbar_view = browser_view->GetToolbarView(); + popup_menu_opened_ = false; + popup_menu_waiter_ = new PopupMenuWaiter(toolbar_view, this); + *success = true; + } +} + +void AutomationProvider::WaitForPopupMenuToOpen(IPC::Message* reply_message) { + // See if the menu already opened and return true if so. + if (popup_menu_opened_) { + AutomationMsg_WaitForPopupMenuToOpen::WriteReplyParams( + reply_message, true); + Send(reply_message); + return; + } + + // Otherwise, register this reply message with the waiter, + // which will handle responding to this IPC when the popup + // menu opens. + popup_menu_waiter_->set_reply_message(reply_message); +} diff --git a/chrome/browser/automation/automation_provider_win.cc b/chrome/browser/automation/automation_provider_win.cc index c352018..d86e0b5 100644 --- a/chrome/browser/automation/automation_provider_win.cc +++ b/chrome/browser/automation/automation_provider_win.cc @@ -231,19 +231,6 @@ void AutomationProvider::WindowSimulateDrag(int handle, } } -void AutomationProvider::GetFocusedViewID(int handle, int* view_id) { - *view_id = -1; - if (window_tracker_->ContainsHandle(handle)) { - HWND hwnd = window_tracker_->GetResource(handle); - views::FocusManager* focus_manager = - views::FocusManager::GetFocusManagerForNativeView(hwnd); - DCHECK(focus_manager); - views::View* focused_view = focus_manager->GetFocusedView(); - if (focused_view) - *view_id = focused_view->GetID(); - } -} - void AutomationProvider::GetWindowBounds(int handle, gfx::Rect* bounds, bool* success) { *success = false; diff --git a/chrome/browser/automation/ui_controls_win.cc b/chrome/browser/automation/ui_controls_win.cc index 3ff1a5c..82a31d0 100644 --- a/chrome/browser/automation/ui_controls_win.cc +++ b/chrome/browser/automation/ui_controls_win.cc @@ -166,6 +166,21 @@ bool SendKeyPressImpl(base::KeyboardCode key, scoped_refptr<InputDispatcher> dispatcher( task ? new InputDispatcher(task, WM_KEYUP) : NULL); + // If a pop-up menu is open, it won't receive events sent using SendInput. + // Check for a pop-up menu using its window class (#32768) and if one + // exists, send the key event directly there. + HWND popup_menu = ::FindWindow(L"#32768", 0); + if (popup_menu != NULL && popup_menu == ::GetTopWindow(NULL)) { + WPARAM w_param = win_util::KeyboardCodeToWin(key); + LPARAM l_param = 0; + ::SendMessage(popup_menu, WM_KEYDOWN, w_param, l_param); + ::SendMessage(popup_menu, WM_KEYUP, w_param, l_param); + + if (dispatcher.get()) + dispatcher->AddRef(); + return true; + } + INPUT input[8] = { 0 }; // 8, assuming all the modifiers are activated int i = 0; |