diff options
author | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-06 13:46:24 +0000 |
---|---|---|
committer | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-06 13:46:24 +0000 |
commit | 83acf96f3a4632e4c962ebc3f0f84e3f989322df (patch) | |
tree | 1d56226dd349c3648a7675aef7e365556fe245fe | |
parent | e80bea9a23243ddf8f58c0358df8118159800b53 (diff) | |
download | chromium_src-83acf96f3a4632e4c962ebc3f0f84e3f989322df.zip chromium_src-83acf96f3a4632e4c962ebc3f0f84e3f989322df.tar.gz chromium_src-83acf96f3a4632e4c962ebc3f0f84e3f989322df.tar.bz2 |
Add menu and menu item events to the accessibility extension api, and
generate menu item notifications for gtk menus.
The code to generate menu open and close events is a little more complicated
and will come in a future patch.
BUG=none
TEST=navigated menus with keyboard, watched notifications fire
Review URL: http://codereview.chromium.org/1585011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43707 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/accessibility_events.cc | 13 | ||||
-rw-r--r-- | chrome/browser/accessibility_events.h | 34 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_accessibility_api.cc | 18 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_accessibility_api.h | 2 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_accessibility_api_constants.cc | 5 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_accessibility_api_constants.h | 5 | ||||
-rw-r--r-- | chrome/browser/gtk/accessibility_event_router_gtk.cc | 199 | ||||
-rw-r--r-- | chrome/browser/gtk/accessibility_event_router_gtk.h | 46 | ||||
-rw-r--r-- | chrome/browser/gtk/accessible_widget_helper_gtk.cc | 2 | ||||
-rw-r--r-- | chrome/browser/gtk/accessible_widget_helper_gtk.h | 8 | ||||
-rw-r--r-- | chrome/browser/views/frame/browser_view.cc | 8 | ||||
-rw-r--r-- | chrome/browser/views/frame/browser_view.h | 15 | ||||
-rwxr-xr-x | chrome/common/extensions/api/extension_api.json | 45 | ||||
-rw-r--r-- | chrome/common/notification_type.h | 10 |
14 files changed, 325 insertions, 85 deletions
diff --git a/chrome/browser/accessibility_events.cc b/chrome/browser/accessibility_events.cc index b300183..79d2af9 100644 --- a/chrome/browser/accessibility_events.cc +++ b/chrome/browser/accessibility_events.cc @@ -87,3 +87,16 @@ void AccessibilityListBoxInfo::SerializeToDict(DictionaryValue *dict) const { dict->SetInteger(keys::kItemIndexKey, item_index_); dict->SetInteger(keys::kItemCountKey, item_count_); } + +void AccessibilityMenuInfo::SerializeToDict(DictionaryValue *dict) const { + AccessibilityControlInfo::SerializeToDict(dict); + dict->SetString(keys::kTypeKey, keys::kTypeMenu); +} + +void AccessibilityMenuItemInfo::SerializeToDict(DictionaryValue *dict) const { + AccessibilityControlInfo::SerializeToDict(dict); + dict->SetString(keys::kTypeKey, keys::kTypeMenuItem); + dict->SetBoolean(keys::kHasSubmenuKey, has_submenu_); + dict->SetInteger(keys::kItemIndexKey, item_index_); + dict->SetInteger(keys::kItemCountKey, item_count_); +} diff --git a/chrome/browser/accessibility_events.h b/chrome/browser/accessibility_events.h index da9189c..a1fe7ae 100644 --- a/chrome/browser/accessibility_events.h +++ b/chrome/browser/accessibility_events.h @@ -238,4 +238,38 @@ class AccessibilityListBoxInfo : public AccessibilityControlInfo { int item_count_; }; +// Accessibility information about a menu; this class is used by +// onMenuOpened, onMenuClosed, and onControlFocused event listeners. +class AccessibilityMenuInfo : public AccessibilityControlInfo { + public: + AccessibilityMenuInfo(Profile* profile, std::string menu_name) + : AccessibilityControlInfo(profile, menu_name) { } + + virtual void SerializeToDict(DictionaryValue *dict) const; +}; + +// Accessibility information about a menu item; this class is used by +// onControlFocused event listeners. +class AccessibilityMenuItemInfo : public AccessibilityControlInfo { + public: + AccessibilityMenuItemInfo(Profile* profile, + std::string name, + bool has_submenu, + int item_index, + int item_count) + : AccessibilityControlInfo(profile, name), + has_submenu_(has_submenu), + item_index_(item_index), + item_count_(item_count) { + } + + virtual void SerializeToDict(DictionaryValue *dict) const; + + private: + bool has_submenu_; + // The 0-based index of the current item and the number of total items. + int item_index_; + int item_count_; +}; + #endif // CHROME_BROWSER_ACCESSIBILITY_EVENTS_H_ diff --git a/chrome/browser/extensions/extension_accessibility_api.cc b/chrome/browser/extensions/extension_accessibility_api.cc index 38745a7..3b6a28f 100644 --- a/chrome/browser/extensions/extension_accessibility_api.cc +++ b/chrome/browser/extensions/extension_accessibility_api.cc @@ -86,6 +86,12 @@ void ExtensionAccessibilityEventRouter::Observe( case NotificationType::ACCESSIBILITY_TEXT_CHANGED: OnTextChanged(Details<const AccessibilityControlInfo>(details).ptr()); break; + case NotificationType::ACCESSIBILITY_MENU_OPENED: + OnMenuOpened(Details<const AccessibilityMenuInfo>(details).ptr()); + break; + case NotificationType::ACCESSIBILITY_MENU_CLOSED: + OnMenuClosed(Details<const AccessibilityMenuInfo>(details).ptr()); + break; default: NOTREACHED(); } @@ -152,6 +158,18 @@ void ExtensionAccessibilityEventRouter::OnTextChanged( DispatchEvent(info->profile(), keys::kOnTextChanged, json_args); } +void ExtensionAccessibilityEventRouter::OnMenuOpened( + const AccessibilityMenuInfo* info) { + std::string json_args = ControlInfoToJsonString(info); + DispatchEvent(info->profile(), keys::kOnMenuOpened, json_args); +} + +void ExtensionAccessibilityEventRouter::OnMenuClosed( + const AccessibilityMenuInfo* info) { + std::string json_args = ControlInfoToJsonString(info); + DispatchEvent(info->profile(), keys::kOnMenuClosed, json_args); +} + void ExtensionAccessibilityEventRouter::DispatchEvent( Profile* profile, const char* event_name, diff --git a/chrome/browser/extensions/extension_accessibility_api.h b/chrome/browser/extensions/extension_accessibility_api.h index 9325dfb..bc724c1 100644 --- a/chrome/browser/extensions/extension_accessibility_api.h +++ b/chrome/browser/extensions/extension_accessibility_api.h @@ -59,6 +59,8 @@ class ExtensionAccessibilityEventRouter : public NotificationObserver { void OnControlFocused(const AccessibilityControlInfo* details); void OnControlAction(const AccessibilityControlInfo* details); void OnTextChanged(const AccessibilityControlInfo* details); + void OnMenuOpened(const AccessibilityMenuInfo* details); + void OnMenuClosed(const AccessibilityMenuInfo* details); void DispatchEvent(Profile* profile, const char* event_name, diff --git a/chrome/browser/extensions/extension_accessibility_api_constants.cc b/chrome/browser/extensions/extension_accessibility_api_constants.cc index 81e60e8..8ddf5a0 100644 --- a/chrome/browser/extensions/extension_accessibility_api_constants.cc +++ b/chrome/browser/extensions/extension_accessibility_api_constants.cc @@ -17,6 +17,7 @@ const wchar_t kItemIndexKey[] = L"details.itemIndex"; const wchar_t kSelectionStartKey[] = L"details.selectionStart"; const wchar_t kSelectionEndKey[] = L"details.selectionEnd"; const wchar_t kCheckedKey[] = L"details.isChecked"; +const wchar_t kHasSubmenuKey[] = L"details.hasSubmenu"; // Events. const char kOnWindowOpened[] = "experimental.accessibility.onWindowOpened"; @@ -24,6 +25,8 @@ const char kOnWindowClosed[] = "experimental.accessibility.onWindowClosed"; const char kOnControlFocused[] = "experimental.accessibility.onControlFocused"; const char kOnControlAction[] = "experimental.accessibility.onControlAction"; const char kOnTextChanged[] = "experimental.accessibility.onTextChanged"; +const char kOnMenuOpened[] = "experimental.accessibility.onMenuOpened"; +const char kOnMenuClosed[] = "experimental.accessibility.onMenuClosed"; // Types of controls that can receive accessibility events. const char kTypeButton[] = "button"; @@ -31,6 +34,8 @@ const char kTypeCheckbox[] = "checkbox"; const char kTypeComboBox[] = "combobox"; const char kTypeLink[] = "link"; const char kTypeListBox[] = "listbox"; +const char kTypeMenu[] = "menu"; +const char kTypeMenuItem[] = "menuitem"; const char kTypeRadioButton[] = "radiobutton"; const char kTypeTab[] = "tab"; const char kTypeTextBox[] = "textbox"; diff --git a/chrome/browser/extensions/extension_accessibility_api_constants.h b/chrome/browser/extensions/extension_accessibility_api_constants.h index 37a7210..c5ce6c8 100644 --- a/chrome/browser/extensions/extension_accessibility_api_constants.h +++ b/chrome/browser/extensions/extension_accessibility_api_constants.h @@ -20,6 +20,7 @@ extern const wchar_t kItemIndexKey[]; extern const wchar_t kSelectionStartKey[]; extern const wchar_t kSelectionEndKey[]; extern const wchar_t kCheckedKey[]; +extern const wchar_t kHasSubmenuKey[]; // Events. extern const char kOnWindowOpened[]; @@ -27,6 +28,8 @@ extern const char kOnWindowClosed[]; extern const char kOnControlFocused[]; extern const char kOnControlAction[]; extern const char kOnTextChanged[]; +extern const char kOnMenuOpened[]; +extern const char kOnMenuClosed[]; // Types of controls that can receive accessibility events extern const char kTypeButton[]; @@ -34,6 +37,8 @@ extern const char kTypeCheckbox[]; extern const char kTypeComboBox[]; extern const char kTypeLink[]; extern const char kTypeListBox[]; +extern const char kTypeMenu[]; +extern const char kTypeMenuItem[]; extern const char kTypeRadioButton[]; extern const char kTypeTab[]; extern const char kTypeTextBox[]; diff --git a/chrome/browser/gtk/accessibility_event_router_gtk.cc b/chrome/browser/gtk/accessibility_event_router_gtk.cc index 833a47d..e0714de 100644 --- a/chrome/browser/gtk/accessibility_event_router_gtk.cc +++ b/chrome/browser/gtk/accessibility_event_router_gtk.cc @@ -24,10 +24,10 @@ gboolean OnWidgetFocused(GSignalInvocationHint *ihint, const GValue* param_values, gpointer user_data) { GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); - reinterpret_cast<AccessibilityEventRouter *>(user_data)-> + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> DispatchAccessibilityNotification( widget, NotificationType::ACCESSIBILITY_CONTROL_FOCUSED); - return true; + return TRUE; } gboolean OnButtonClicked(GSignalInvocationHint *ihint, @@ -38,10 +38,10 @@ gboolean OnButtonClicked(GSignalInvocationHint *ihint, // Skip toggle buttons because we're also listening on "toggle" events. if (GTK_IS_TOGGLE_BUTTON(widget)) return true; - reinterpret_cast<AccessibilityEventRouter *>(user_data)-> + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> DispatchAccessibilityNotification( widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); - return true; + return TRUE; } gboolean OnButtonToggled(GSignalInvocationHint *ihint, @@ -55,10 +55,10 @@ gboolean OnButtonToggled(GSignalInvocationHint *ihint, // a different radio button the group. if (GTK_IS_RADIO_BUTTON(widget) && !checked) return true; - reinterpret_cast<AccessibilityEventRouter *>(user_data)-> + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> DispatchAccessibilityNotification( widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); - return true; + return TRUE; } gboolean OnPageSwitched(GSignalInvocationHint *ihint, @@ -68,10 +68,10 @@ gboolean OnPageSwitched(GSignalInvocationHint *ihint, GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); // The page hasn't switched yet, so defer calling // DispatchAccessibilityNotification. - reinterpret_cast<AccessibilityEventRouter *>(user_data)-> + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> PostDispatchAccessibilityNotification( widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); - return true; + return TRUE; } gboolean OnComboBoxChanged(GSignalInvocationHint *ihint, @@ -81,10 +81,10 @@ gboolean OnComboBoxChanged(GSignalInvocationHint *ihint, GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); if (!GTK_IS_COMBO_BOX(widget)) return true; - reinterpret_cast<AccessibilityEventRouter *>(user_data)-> + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> DispatchAccessibilityNotification( widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); - return true; + return TRUE; } gboolean OnTreeViewCursorChanged(GSignalInvocationHint *ihint, @@ -95,10 +95,10 @@ gboolean OnTreeViewCursorChanged(GSignalInvocationHint *ihint, if (!GTK_IS_TREE_VIEW(widget)) { return true; } - reinterpret_cast<AccessibilityEventRouter *>(user_data)-> + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> DispatchAccessibilityNotification( widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); - return true; + return TRUE; } gboolean OnEntryChanged(GSignalInvocationHint *ihint, @@ -107,46 +107,71 @@ gboolean OnEntryChanged(GSignalInvocationHint *ihint, gpointer user_data) { GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); if (!GTK_IS_ENTRY(widget)) { - return true; + return TRUE; } // The text hasn't changed yet, so defer calling // DispatchAccessibilityNotification. - reinterpret_cast<AccessibilityEventRouter *>(user_data)-> + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> PostDispatchAccessibilityNotification( widget, NotificationType::ACCESSIBILITY_TEXT_CHANGED); - return true; + return TRUE; +} + +gboolean OnMenuMoveCurrent(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + // Get the widget (the GtkMenu). + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + + // Moving may move us into or out of a submenu, so after the menu + // item moves, |widget| may not be valid anymore. To be safe, then, + // find the topmost ancestor of this menu and post the notification + // dispatch on that menu. Then the dispatcher will recurse into submenus + // as necessary to figure out which item is focused. + while (GTK_MENU_SHELL(widget)->parent_menu_shell) + widget = GTK_MENU_SHELL(widget)->parent_menu_shell; + + // The menu item hasn't moved yet, so we want to defer calling + // DispatchAccessibilityNotification until after it does. + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> + PostDispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_CONTROL_FOCUSED); + return TRUE; } } // anonymous namespace -AccessibilityEventRouter::AccessibilityEventRouter() - : method_factory_(this) { +AccessibilityEventRouterGtk::AccessibilityEventRouterGtk() + : listening_(false), + most_recent_profile_(NULL), + method_factory_(this) { // We don't want our event listeners to be installed if accessibility is // disabled. Install listeners so we can install and uninstall them as // needed, then install them now if it's currently enabled. - ExtensionAccessibilityEventRouter *accessibility_event_router = + ExtensionAccessibilityEventRouter *extension_event_router = ExtensionAccessibilityEventRouter::GetInstance(); - accessibility_event_router->AddOnEnabledListener( + extension_event_router->AddOnEnabledListener( NewCallback(this, - &AccessibilityEventRouter::InstallEventListeners)); - accessibility_event_router->AddOnDisabledListener( + &AccessibilityEventRouterGtk::InstallEventListeners)); + extension_event_router->AddOnDisabledListener( NewCallback(this, - &AccessibilityEventRouter::RemoveEventListeners)); - if (accessibility_event_router->IsAccessibilityEnabled()) { + &AccessibilityEventRouterGtk::RemoveEventListeners)); + if (extension_event_router->IsAccessibilityEnabled()) { InstallEventListeners(); } } -AccessibilityEventRouter::~AccessibilityEventRouter() { +AccessibilityEventRouterGtk::~AccessibilityEventRouterGtk() { RemoveEventListeners(); } // static -AccessibilityEventRouter* AccessibilityEventRouter::GetInstance() { - return Singleton<AccessibilityEventRouter>::get(); +AccessibilityEventRouterGtk* AccessibilityEventRouterGtk::GetInstance() { + return Singleton<AccessibilityEventRouterGtk>::get(); } -void AccessibilityEventRouter::InstallEventListener( +void AccessibilityEventRouterGtk::InstallEventListener( const char* signal_name, GType widget_type, GSignalEmissionHook hook_func) { @@ -156,7 +181,7 @@ void AccessibilityEventRouter::InstallEventListener( installed_hooks_.push_back(InstalledHook(signal_id, hook_id)); } -void AccessibilityEventRouter::InstallEventListeners() { +void AccessibilityEventRouterGtk::InstallEventListeners() { // Create and destroy each type of widget we need signals for, // to ensure their modules are loaded, otherwise g_signal_lookup // might fail. @@ -178,11 +203,12 @@ void AccessibilityEventRouter::InstallEventListeners() { InstallEventListener("focus-in-event", GTK_TYPE_WIDGET, OnWidgetFocused); InstallEventListener("switch-page", GTK_TYPE_NOTEBOOK, OnPageSwitched); InstallEventListener("toggled", GTK_TYPE_TOGGLE_BUTTON, OnButtonToggled); + InstallEventListener("move-current", GTK_TYPE_MENU, OnMenuMoveCurrent); listening_ = true; } -void AccessibilityEventRouter::RemoveEventListeners() { +void AccessibilityEventRouterGtk::RemoveEventListeners() { for (size_t i = 0; i < installed_hooks_.size(); i++) { g_signal_remove_emission_hook( installed_hooks_[i].signal_id, @@ -193,60 +219,60 @@ void AccessibilityEventRouter::RemoveEventListeners() { listening_ = false; } -void AccessibilityEventRouter::AddRootWidget( +void AccessibilityEventRouterGtk::AddRootWidget( GtkWidget* root_widget, Profile* profile) { root_widget_profile_map_[root_widget] = profile; } -void AccessibilityEventRouter::RemoveRootWidget(GtkWidget* root_widget) { +void AccessibilityEventRouterGtk::RemoveRootWidget(GtkWidget* root_widget) { DCHECK(root_widget_profile_map_.find(root_widget) != root_widget_profile_map_.end()); root_widget_profile_map_.erase(root_widget); } -void AccessibilityEventRouter::IgnoreWidget(GtkWidget* widget) { +void AccessibilityEventRouterGtk::IgnoreWidget(GtkWidget* widget) { widget_info_map_[widget].ignore = true; } -void AccessibilityEventRouter::SetWidgetName( +void AccessibilityEventRouterGtk::SetWidgetName( GtkWidget* widget, std::string name) { widget_info_map_[widget].name = name; } -void AccessibilityEventRouter::RemoveWidget(GtkWidget* widget) { +void AccessibilityEventRouterGtk::RemoveWidget(GtkWidget* widget) { DCHECK(widget_info_map_.find(widget) != widget_info_map_.end()); widget_info_map_.erase(widget); } -bool AccessibilityEventRouter::IsWidgetAccessible( - GtkWidget* widget, Profile** profile) { +void AccessibilityEventRouterGtk::FindWidget( + GtkWidget* widget, Profile** profile, bool* is_accessible) { + *is_accessible = false; + // First see if it's a descendant of a root widget. - bool is_accessible = false; for (base::hash_map<GtkWidget*, Profile*>::const_iterator iter = root_widget_profile_map_.begin(); iter != root_widget_profile_map_.end(); ++iter) { if (gtk_widget_is_ancestor(widget, iter->first)) { - is_accessible = true; + *is_accessible = true; if (profile) *profile = iter->second; break; } } - if (!is_accessible) - return false; + if (!*is_accessible) + return; // Now make sure it's not marked as a widget to be ignored. base::hash_map<GtkWidget*, WidgetInfo>::const_iterator iter = widget_info_map_.find(widget); if (iter != widget_info_map_.end() && iter->second.ignore) { - is_accessible = false; + *is_accessible = false; + return; } - - return is_accessible; } -std::string AccessibilityEventRouter::GetWidgetName(GtkWidget* widget) { +std::string AccessibilityEventRouterGtk::GetWidgetName(GtkWidget* widget) { base::hash_map<GtkWidget*, WidgetInfo>::const_iterator iter = widget_info_map_.find(widget); if (iter != widget_info_map_.end()) { @@ -256,20 +282,35 @@ std::string AccessibilityEventRouter::GetWidgetName(GtkWidget* widget) { } } -void AccessibilityEventRouter::StartListening() { +void AccessibilityEventRouterGtk::StartListening() { listening_ = true; } -void AccessibilityEventRouter::StopListening() { +void AccessibilityEventRouterGtk::StopListening() { listening_ = false; } -void AccessibilityEventRouter::DispatchAccessibilityNotification( +void AccessibilityEventRouterGtk::DispatchAccessibilityNotification( GtkWidget* widget, NotificationType type) { if (!listening_) return; - Profile *profile; - if (!IsWidgetAccessible(widget, &profile)) + + Profile* profile = NULL; + bool is_accessible; + FindWidget(widget, &profile, &is_accessible); + if (profile) + most_recent_profile_ = profile; + + // Special case: a GtkMenu isn't associated with any particular + // toplevel window, so menu events get routed to the profile of + // the most recent event that was associated with a window. + if (GTK_IS_MENU_SHELL(widget) && most_recent_profile_) { + SendMenuItemNotification(widget, type, most_recent_profile_); + return; + } + + // In all other cases, return if this widget wasn't marked as accessible. + if (!is_accessible) return; // The order of these checks matters, because, for example, a radio button @@ -309,19 +350,19 @@ void AccessibilityEventRouter::DispatchAccessibilityNotification( StopListening(); MessageLoop::current()->PostTask( FROM_HERE, method_factory_.NewRunnableMethod( - &AccessibilityEventRouter::StartListening)); + &AccessibilityEventRouterGtk::StartListening)); } -void AccessibilityEventRouter::PostDispatchAccessibilityNotification( +void AccessibilityEventRouterGtk::PostDispatchAccessibilityNotification( GtkWidget* widget, NotificationType type) { MessageLoop::current()->PostTask( FROM_HERE, method_factory_.NewRunnableMethod( - &AccessibilityEventRouter::DispatchAccessibilityNotification, + &AccessibilityEventRouterGtk::DispatchAccessibilityNotification, widget, type)); } -void AccessibilityEventRouter::SendRadioButtonNotification( +void AccessibilityEventRouterGtk::SendRadioButtonNotification( GtkWidget* widget, NotificationType type, Profile* profile) { // Get the radio button name std::string button_name = GetWidgetName(widget); @@ -350,7 +391,7 @@ void AccessibilityEventRouter::SendRadioButtonNotification( SendAccessibilityNotification(type, &info); } -void AccessibilityEventRouter::SendCheckboxNotification( +void AccessibilityEventRouterGtk::SendCheckboxNotification( GtkWidget* widget, NotificationType type, Profile* profile) { std::string button_name = GetWidgetName(widget); if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) @@ -360,7 +401,7 @@ void AccessibilityEventRouter::SendCheckboxNotification( SendAccessibilityNotification(type, &info); } -void AccessibilityEventRouter::SendButtonNotification( +void AccessibilityEventRouterGtk::SendButtonNotification( GtkWidget* widget, NotificationType type, Profile* profile) { std::string button_name = GetWidgetName(widget); if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) @@ -369,7 +410,7 @@ void AccessibilityEventRouter::SendButtonNotification( SendAccessibilityNotification(type, &info); } -void AccessibilityEventRouter::SendTextBoxNotification( +void AccessibilityEventRouterGtk::SendTextBoxNotification( GtkWidget* widget, NotificationType type, Profile* profile) { std::string name = GetWidgetName(widget); std::string value = gtk_entry_get_text(GTK_ENTRY(widget)); @@ -381,7 +422,7 @@ void AccessibilityEventRouter::SendTextBoxNotification( SendAccessibilityNotification(type, &info); } -void AccessibilityEventRouter::SendTabNotification( +void AccessibilityEventRouterGtk::SendTabNotification( GtkWidget* widget, NotificationType type, Profile* profile) { int index = gtk_notebook_get_current_page(GTK_NOTEBOOK(widget)); int page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(widget)); @@ -395,7 +436,7 @@ void AccessibilityEventRouter::SendTabNotification( SendAccessibilityNotification(type, &info); } -void AccessibilityEventRouter::SendComboBoxNotification( +void AccessibilityEventRouterGtk::SendComboBoxNotification( GtkWidget* widget, NotificationType type, Profile* profile) { // Get the index of the selected item. Will return -1 if no item is // active, which matches the semantics of the extension API. @@ -442,7 +483,7 @@ void AccessibilityEventRouter::SendComboBoxNotification( SendAccessibilityNotification(type, &info); } -void AccessibilityEventRouter::SendListBoxNotification( +void AccessibilityEventRouterGtk::SendListBoxNotification( GtkWidget* widget, NotificationType type, Profile* profile) { // Get the number of items. GtkTreeModel* model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget)); @@ -485,3 +526,43 @@ void AccessibilityEventRouter::SendListBoxNotification( AccessibilityListBoxInfo info(profile, name, value, index, count); SendAccessibilityNotification(type, &info); } + +void AccessibilityEventRouterGtk::SendMenuItemNotification( + GtkWidget* menu, NotificationType type, Profile* profile) { + // Find the focused menu item, recursing into submenus as needed. + GtkWidget* menu_item = GTK_MENU_SHELL(menu)->active_menu_item; + if (!menu_item) + return; + GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); + while (submenu && GTK_MENU_SHELL(submenu)->active_menu_item) { + menu = submenu; + menu_item = GTK_MENU_SHELL(menu)->active_menu_item; + if (!menu_item) + return; + submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); + } + + // Figure out the item index and total number of items. + GList* items = gtk_container_get_children(GTK_CONTAINER(menu)); + guint count = g_list_length(items); + int index = g_list_index(items, static_cast<gconstpointer>(menu_item)); + + // Get the menu item's label. + std::string name; +#if GTK_CHECK_VERSION(2, 16, 0) + name = gtk_menu_item_get_label(GTK_MENU_ITEM(menu_item)); +#else + GList* children = gtk_container_get_children(GTK_CONTAINER(menu_item)); + for (GList* l = g_list_first(children); l != NULL; l = g_list_next(l)) { + GtkWidget* child = static_cast<GtkWidget*>(l->data); + if (GTK_IS_LABEL(child)) { + name = gtk_label_get_label(GTK_LABEL(child)); + break; + } + } +#endif + + // Send the event. + AccessibilityMenuItemInfo info(profile, name, submenu != NULL, index, count); + SendAccessibilityNotification(type, &info); +} diff --git a/chrome/browser/gtk/accessibility_event_router_gtk.h b/chrome/browser/gtk/accessibility_event_router_gtk.h index 57c65d9..662e783 100644 --- a/chrome/browser/gtk/accessibility_event_router_gtk.h +++ b/chrome/browser/gtk/accessibility_event_router_gtk.h @@ -36,6 +36,10 @@ struct InstalledHook { gulong hook_id; }; +// NOTE: This class is part of the Accessibility Extension API, which lets +// extensions receive accessibility events. It's distinct from code that +// implements platform accessibility APIs like MSAA or ATK. +// // Singleton class that adds a signal emission hook to many gtk events, and // then sends an accessibility notification whenever a relevant event is // sent to an accessible control. @@ -47,7 +51,7 @@ struct InstalledHook { // // You can use Profile::PauseAccessibilityEvents to prevent a flurry // of accessibility events when a window is being created or initialized. -class AccessibilityEventRouter { +class AccessibilityEventRouterGtk { public: // Internal information about a particular widget to override the // information we get directly from gtk. @@ -62,7 +66,7 @@ class AccessibilityEventRouter { }; // Get the single instance of this class. - static AccessibilityEventRouter* GetInstance(); + static AccessibilityEventRouterGtk* GetInstance(); // Start sending accessibility events for this widget and all of its // descendants. Notifications will go to the specified profile. @@ -86,15 +90,6 @@ class AccessibilityEventRouter { // The following methods are only for use by gtk signal handlers. // - // Returns true if this widget is a descendant of one of our registered - // root widgets and not in the set of ignored widgets. If |profile| is - // not null, return the profile where notifications associated with this - // widget should be sent. - bool IsWidgetAccessible(GtkWidget* widget, Profile** profile); - - // Return the name of a widget. - std::string GetWidgetName(GtkWidget* widget); - // Called by the signal handler. Checks the type of the widget and // calls one of the more specific Send*Notification methods, below. void DispatchAccessibilityNotification( @@ -105,6 +100,17 @@ class AccessibilityEventRouter { void PostDispatchAccessibilityNotification( GtkWidget* widget, NotificationType type); + private: + AccessibilityEventRouterGtk(); + virtual ~AccessibilityEventRouterGtk(); + + // Given a widget, determine if it's the descendant of a root widget + // that's mapped to a profile and if so, if it's marked as accessible. + void FindWidget(GtkWidget* widget, Profile** profile, bool* is_accessible); + + // Return the name of a widget. + std::string GetWidgetName(GtkWidget* widget); + // Each of these methods constructs an AccessibilityControlInfo object // and sends a notification of a specific accessibility event. void SendButtonNotification( @@ -115,6 +121,8 @@ class AccessibilityEventRouter { GtkWidget* widget, NotificationType type, Profile* profile); void SendListBoxNotification( GtkWidget* widget, NotificationType type, Profile* profile); + void SendMenuItemNotification( + GtkWidget* widget, NotificationType type, Profile* profile); void SendRadioButtonNotification( GtkWidget* widget, NotificationType type, Profile* profile); void SendTabNotification( @@ -129,10 +137,6 @@ class AccessibilityEventRouter { void StartListening(); void StopListening(); - private: - AccessibilityEventRouter(); - virtual ~AccessibilityEventRouter(); - // Add a signal emission hook for one particular signal name and // widget type, and save the hook_id in installed_hooks so we can // remove it later. @@ -141,7 +145,7 @@ class AccessibilityEventRouter { GType widget_type, GSignalEmissionHook hook_func); - friend struct DefaultSingletonTraits<AccessibilityEventRouter>; + friend struct DefaultSingletonTraits<AccessibilityEventRouterGtk>; // The set of all root widgets; only descendants of these will generate // accessibility notifications. @@ -156,8 +160,14 @@ class AccessibilityEventRouter { // True if we are currently listening to signals. bool listening_; - // Used to schedule invocations of StartListening(). - ScopedRunnableMethodFactory<AccessibilityEventRouter> method_factory_; + // The profile associated with the most recent window event - used to + // figure out where to route a few events that can't be directly traced + // to a window with a profile (like menu events). + Profile* most_recent_profile_; + + // Used to schedule invocations of StartListening() and to defer handling + // of some events until the next time through the event loop. + ScopedRunnableMethodFactory<AccessibilityEventRouterGtk> method_factory_; }; #endif // CHROME_BROWSER_GTK_ACCESSIBILITY_EVENT_ROUTER_GTK_H_ diff --git a/chrome/browser/gtk/accessible_widget_helper_gtk.cc b/chrome/browser/gtk/accessible_widget_helper_gtk.cc index e54c6ff..50bc107 100644 --- a/chrome/browser/gtk/accessible_widget_helper_gtk.cc +++ b/chrome/browser/gtk/accessible_widget_helper_gtk.cc @@ -11,7 +11,7 @@ AccessibleWidgetHelper::AccessibleWidgetHelper( GtkWidget* root_widget, Profile* profile) - : accessibility_event_router_(AccessibilityEventRouter::GetInstance()), + : accessibility_event_router_(AccessibilityEventRouterGtk::GetInstance()), profile_(profile), root_widget_(root_widget) { accessibility_event_router_->AddRootWidget(root_widget_, profile); diff --git a/chrome/browser/gtk/accessible_widget_helper_gtk.h b/chrome/browser/gtk/accessible_widget_helper_gtk.h index b677c39..fdd244d 100644 --- a/chrome/browser/gtk/accessible_widget_helper_gtk.h +++ b/chrome/browser/gtk/accessible_widget_helper_gtk.h @@ -17,6 +17,10 @@ class Profile; +// NOTE: This class is part of the Accessibility Extension API, which lets +// extensions receive accessibility events. It's distinct from code that +// implements platform accessibility APIs like MSAA or ATK. +// // Helper class that helps to manage the accessibility information for all // of the widgets in a container. Create an instance of this class for // each container GtkWidget (like a dialog) that should send accessibility @@ -27,7 +31,7 @@ class Profile; // widget or change its details. // // All of the information managed by this class is registered with the -// (global) AccessibilityEventRouter and unregistered when this object is +// (global) AccessibilityEventRouterGtk and unregistered when this object is // destroyed. class AccessibleWidgetHelper { public: @@ -55,7 +59,7 @@ class AccessibleWidgetHelper { void SetWidgetName(GtkWidget* widget, int string_id); private: - AccessibilityEventRouter* accessibility_event_router_; + AccessibilityEventRouterGtk* accessibility_event_router_; Profile* profile_; GtkWidget* root_widget_; std::string window_title_; diff --git a/chrome/browser/views/frame/browser_view.cc b/chrome/browser/views/frame/browser_view.cc index 65c5391c..dba7844 100644 --- a/chrome/browser/views/frame/browser_view.cc +++ b/chrome/browser/views/frame/browser_view.cc @@ -65,6 +65,7 @@ #include "chrome/browser/aeropeek_manager.h" #include "chrome/browser/jumplist.h" #elif defined(OS_LINUX) +#include "chrome/browser/gtk/accessible_widget_helper_gtk.h" #include "chrome/browser/views/accelerator_table_gtk.h" #include "views/window/hit_test.h" #endif @@ -639,6 +640,13 @@ bool BrowserView::IsPositionInWindowCaption(const gfx::Point& point) { // BrowserView, BrowserWindow implementation: void BrowserView::Show() { + #if defined(OS_LINUX) + if (!accessible_widget_helper_.get()) { + accessible_widget_helper_.reset(new AccessibleWidgetHelper( + GTK_WIDGET(GetWindow()->GetNativeWindow()), browser_->profile())); + } + #endif + // If the window is already visible, just activate it. if (frame_->GetWindow()->IsVisible()) { frame_->GetWindow()->Activate(); diff --git a/chrome/browser/views/frame/browser_view.h b/chrome/browser/views/frame/browser_view.h index 2926b97..8b6c120 100644 --- a/chrome/browser/views/frame/browser_view.h +++ b/chrome/browser/views/frame/browser_view.h @@ -46,10 +46,6 @@ class ExtensionShelf; class FullscreenExitBubble; class HtmlDialogUIDelegate; class InfoBarContainer; -#if defined(OS_WIN) -class AeroPeekManager; -class JumpList; -#endif class LocationBarView; class SideTabStrip; class StatusBubbleViews; @@ -57,6 +53,13 @@ class TabContentsContainer; class ToolbarView; class ZoomMenuModel; +#if defined(OS_WIN) +class AeroPeekManager; +class JumpList; +#elif defined(OS_LINUX) +class AccessibleWidgetHelper; +#endif + namespace views { class ExternalFocusTracker; class Menu; @@ -570,6 +573,10 @@ class BrowserView : public BrowserBubbleHost, UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_; + #if defined(OS_LINUX) + scoped_ptr<AccessibleWidgetHelper> accessible_widget_helper_; + #endif + DISALLOW_COPY_AND_ASSIGN(BrowserView); }; diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json index b554298..6512c6d 100755 --- a/chrome/common/extensions/api/extension_api.json +++ b/chrome/common/extensions/api/extension_api.json @@ -243,7 +243,7 @@ "type": { "type": "string", "description": "The type of this object, which determines the contents of 'details'.", - "enum": ["button", "checkbox", "combobox", "link", "radiobutton", "tab", "textbox", "window"] + "enum": ["button", "checkbox", "combobox", "link", "menu", "menuitem", "radiobutton", "tab", "textbox", "window"] }, "name": { "type": "string", @@ -255,6 +255,8 @@ "choices": [ { "$ref": "CheckboxDetails" }, { "$ref": "ComboBoxDetails" }, + { "$ref": "MenuDetails" }, + { "$ref": "MenuItemDetails" }, { "$ref": "RadioButtonDetails" }, { "$ref": "TabDetails" }, { "$ref": "TextBoxDetails" } @@ -291,6 +293,23 @@ } }, { + "id": "MenuDetails", + "type": "object", + "description": "Information about the state of a drop-down menu.", + "properties": { + } + }, + { + "id": "MenuItemDetails", + "type": "object", + "description": "Information about a menu item.", + "properties": { + "hasSubmenu": {"type": "boolean", "description": "True if this item opens a submenu."}, + "itemCount": {"type": "integer", "description": "The number of items in the menu."}, + "itemIndex": {"type": "integer", "description": "The 0-based index of this menu item."} + } + }, + { "id": "RadioButtonDetails", "type": "object", "description": "Information about the state of a radio button.", @@ -416,6 +435,30 @@ "description": "Details of the control where the text changed." } ] + }, + { + "name": "onMenuOpened", + "type": "function", + "description": "Fired when a menu is opened.", + "parameters": [ + { + "$ref": "AccessibilityObject", + "name": "menu", + "description": "Information about the menu that was opened." + } + ] + }, + { + "name": "onMenuClosed", + "type": "function", + "description": "Fired when a menu is closed.", + "parameters": [ + { + "$ref": "AccessibilityObject", + "name": "menu", + "description": "Information about the menu that was closed." + } + ] } ] }, diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h index 6bb7efb..ed37fa8 100644 --- a/chrome/common/notification_type.h +++ b/chrome/common/notification_type.h @@ -893,6 +893,16 @@ class NotificationType { // Details will be an AccessibilityControlInfo. ACCESSIBILITY_TEXT_CHANGED, + // Notification that a pop-down menu was opened, for propagating + // to an accessibility extension. + // Details will be an AccessibilityMenuInfo. + ACCESSIBILITY_MENU_OPENED, + + // Notification that a pop-down menu was closed, for propagating + // to an accessibility extension. + // Details will be an AccessibilityMenuInfo. + ACCESSIBILITY_MENU_CLOSED, + // Content Settings -------------------------------------------------------- // Sent when content settings change. The source is a HostContentSettings |