summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-06 13:46:24 +0000
committerdmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-06 13:46:24 +0000
commit83acf96f3a4632e4c962ebc3f0f84e3f989322df (patch)
tree1d56226dd349c3648a7675aef7e365556fe245fe
parente80bea9a23243ddf8f58c0358df8118159800b53 (diff)
downloadchromium_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.cc13
-rw-r--r--chrome/browser/accessibility_events.h34
-rw-r--r--chrome/browser/extensions/extension_accessibility_api.cc18
-rw-r--r--chrome/browser/extensions/extension_accessibility_api.h2
-rw-r--r--chrome/browser/extensions/extension_accessibility_api_constants.cc5
-rw-r--r--chrome/browser/extensions/extension_accessibility_api_constants.h5
-rw-r--r--chrome/browser/gtk/accessibility_event_router_gtk.cc199
-rw-r--r--chrome/browser/gtk/accessibility_event_router_gtk.h46
-rw-r--r--chrome/browser/gtk/accessible_widget_helper_gtk.cc2
-rw-r--r--chrome/browser/gtk/accessible_widget_helper_gtk.h8
-rw-r--r--chrome/browser/views/frame/browser_view.cc8
-rw-r--r--chrome/browser/views/frame/browser_view.h15
-rwxr-xr-xchrome/common/extensions/api/extension_api.json45
-rw-r--r--chrome/common/notification_type.h10
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