diff options
author | estade@chromium.org <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-01-30 01:18:56 +0000 |
---|---|---|
committer | estade@chromium.org <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-01-30 01:18:56 +0000 |
commit | 5cbe1e21980f42927d7d1c11cc860131be266e7e (patch) | |
tree | 3b52e15044336cb89e3004214b4714e6148e5ea9 /chrome/browser | |
parent | 7a12518454d36fb4ac79431f56106b38cd2482ab (diff) | |
download | chromium_src-5cbe1e21980f42927d7d1c11cc860131be266e7e.zip chromium_src-5cbe1e21980f42927d7d1c11cc860131be266e7e.tar.gz chromium_src-5cbe1e21980f42927d7d1c11cc860131be266e7e.tar.bz2 |
Add an accessibility API for events raised outside of the web content.
BUG=none
TEST=none
patch by Dominic Mazzoni <dmazzoni [at] google>
review url: http://codereview.chromium.org/402099/show
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@37597 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
17 files changed, 1011 insertions, 5 deletions
diff --git a/chrome/browser/extensions/extension_accessibility_api.cc b/chrome/browser/extensions/extension_accessibility_api.cc new file mode 100644 index 0000000..24428af --- /dev/null +++ b/chrome/browser/extensions/extension_accessibility_api.cc @@ -0,0 +1,181 @@ +// Copyright (c) 2010 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 "chrome/browser/extensions/extension_tabs_module.h" + +#include "base/json/json_writer.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_window.h" +#include "chrome/browser/extensions/extension_accessibility_api.h" +#include "chrome/browser/extensions/extension_accessibility_api_constants.h" +#include "chrome/browser/extensions/extension_function_dispatcher.h" +#include "chrome/browser/extensions/extension_message_service.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/browser/profile.h" +#include "chrome/common/extensions/extension.h" + +namespace keys = extension_accessibility_api_constants; + +// Returns the AccessibilityControlInfo serialized into a JSON string, +// consisting of an array of a single object of type AccessibilityObject, +// as defined in the accessibility extension api's json schema. +std::string ControlInfoToJsonString(const AccessibilityControlInfo* info) { + ListValue args; + DictionaryValue* dict = new DictionaryValue(); + info->SerializeToDict(dict); + args.Append(dict); + std::string json_args; + base::JSONWriter::Write(&args, false, &json_args); + return json_args; +} + +ExtensionAccessibilityEventRouter* + ExtensionAccessibilityEventRouter::GetInstance() { + return Singleton<ExtensionAccessibilityEventRouter>::get(); +} + +void ExtensionAccessibilityEventRouter::ObserveProfile(Profile* profile) { + last_focused_control_dict_.Clear(); + + if (registrar_.IsEmpty()) { + registrar_.Add(this, + NotificationType::ACCESSIBILITY_WINDOW_OPENED, + NotificationService::AllSources()); + registrar_.Add(this, + NotificationType::ACCESSIBILITY_WINDOW_CLOSED, + NotificationService::AllSources()); + registrar_.Add(this, + NotificationType::ACCESSIBILITY_CONTROL_FOCUSED, + NotificationService::AllSources()); + registrar_.Add(this, + NotificationType::ACCESSIBILITY_CONTROL_ACTION, + NotificationService::AllSources()); + registrar_.Add(this, + NotificationType::ACCESSIBILITY_TEXT_CHANGED, + NotificationService::AllSources()); + } +} + +void ExtensionAccessibilityEventRouter::Observe( + NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + case NotificationType::ACCESSIBILITY_WINDOW_OPENED: + OnWindowOpened(Details<const AccessibilityWindowInfo>(details).ptr()); + break; + case NotificationType::ACCESSIBILITY_WINDOW_CLOSED: + OnWindowClosed(Details<const AccessibilityWindowInfo>(details).ptr()); + break; + case NotificationType::ACCESSIBILITY_CONTROL_FOCUSED: + OnControlFocused(Details<const AccessibilityControlInfo>(details).ptr()); + break; + case NotificationType::ACCESSIBILITY_CONTROL_ACTION: + OnControlAction(Details<const AccessibilityControlInfo>(details).ptr()); + break; + case NotificationType::ACCESSIBILITY_TEXT_CHANGED: + OnTextChanged(Details<const AccessibilityControlInfo>(details).ptr()); + break; + default: + NOTREACHED(); + } +} + +void ExtensionAccessibilityEventRouter::SetAccessibilityEnabled(bool enabled) { + if (enabled_ != enabled) { + enabled_ = enabled; + if (enabled_) { + for (unsigned int i = 0; i < on_enabled_listeners_.size(); i++) { + on_enabled_listeners_[i]->Run(); + } + } else { + for (unsigned int i = 0; i < on_disabled_listeners_.size(); i++) { + on_disabled_listeners_[i]->Run(); + } + } + } +} + +bool ExtensionAccessibilityEventRouter::IsAccessibilityEnabled() const { + return enabled_; +} + +void ExtensionAccessibilityEventRouter::AddOnEnabledListener( + Callback* callback) { + on_enabled_listeners_.push_back(callback); +} + +void ExtensionAccessibilityEventRouter::AddOnDisabledListener( + Callback* callback) { + on_disabled_listeners_.push_back(callback); +} + +void ExtensionAccessibilityEventRouter::OnWindowOpened( + const AccessibilityWindowInfo* info) { + std::string json_args = ControlInfoToJsonString(info); + DispatchEvent(info->profile(), keys::kOnWindowOpened, json_args); +} + +void ExtensionAccessibilityEventRouter::OnWindowClosed( + const AccessibilityWindowInfo* info) { + std::string json_args = ControlInfoToJsonString(info); + DispatchEvent(info->profile(), keys::kOnWindowClosed, json_args); +} + +void ExtensionAccessibilityEventRouter::OnControlFocused( + const AccessibilityControlInfo* info) { + last_focused_control_dict_.Clear(); + info->SerializeToDict(&last_focused_control_dict_); + std::string json_args = ControlInfoToJsonString(info); + DispatchEvent(info->profile(), keys::kOnControlFocused, json_args); +} + +void ExtensionAccessibilityEventRouter::OnControlAction( + const AccessibilityControlInfo* info) { + std::string json_args = ControlInfoToJsonString(info); + DispatchEvent(info->profile(), keys::kOnControlAction, json_args); +} + +void ExtensionAccessibilityEventRouter::OnTextChanged( + const AccessibilityControlInfo* info) { + std::string json_args = ControlInfoToJsonString(info); + DispatchEvent(info->profile(), keys::kOnTextChanged, json_args); +} + +void ExtensionAccessibilityEventRouter::DispatchEvent( + Profile* profile, + const char* event_name, + const std::string& json_args) { + if (enabled_ && profile && profile->GetExtensionMessageService()) { + profile->GetExtensionMessageService()-> + DispatchEventToRenderers(event_name, json_args); + } +} + +bool SetAccessibilityEnabledFunction::RunImpl() { + bool enabled; + EXTENSION_FUNCTION_VALIDATE(args_->GetAsBoolean(&enabled)); + ExtensionAccessibilityEventRouter::GetInstance() + ->SetAccessibilityEnabled(enabled); + return true; +} + +bool GetFocusedControlFunction::RunImpl() { + // Get the serialized dict from the last focused control and return it. + // However, if the dict is empty, that means we haven't seen any focus + // events yet, so return null instead. + ExtensionAccessibilityEventRouter *accessibility_event_router = + ExtensionAccessibilityEventRouter::GetInstance(); + DictionaryValue *last_focused_control_dict = + accessibility_event_router->last_focused_control_dict(); + if (last_focused_control_dict->size()) { + result_.reset(last_focused_control_dict->DeepCopyWithoutEmptyChildren()); + } else { + result_.reset(Value::CreateNullValue()); + } + return true; +} diff --git a/chrome/browser/extensions/extension_accessibility_api.h b/chrome/browser/extensions/extension_accessibility_api.h new file mode 100644 index 0000000..afae039 --- /dev/null +++ b/chrome/browser/extensions/extension_accessibility_api.h @@ -0,0 +1,96 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_ACCESSIBILITY_API_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_ACCESSIBILITY_API_H_ + +#include <string> +#include <vector> + +#include "base/singleton.h" +#include "chrome/browser/extensions/extension_function.h" +#include "chrome/common/accessibility_events.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_registrar.h" + +// Observes the profile and routes accessibility notifications as events +// to the extension system. +class ExtensionAccessibilityEventRouter : public NotificationObserver { + public: + // Single instance of the event router. + static ExtensionAccessibilityEventRouter* GetInstance(); + + // Safe to call multiple times. + void ObserveProfile(Profile* profile); + + // Get the dict representing the last control that received an + // OnControlFocus event. + DictionaryValue* last_focused_control_dict() { + return &last_focused_control_dict_; + } + + // Accessibility support is disabled until an extension expicitly enables + // it, so that this extension api has no impact on Chrome's performance + // otherwise. These methods handle enabling, disabling, querying the + // status, and installing callbacks to execute when accessibility support + // is enabled or disabled. + void SetAccessibilityEnabled(bool enabled); + bool IsAccessibilityEnabled() const; + typedef Callback0::Type Callback; + void AddOnEnabledListener(Callback* callback); + void AddOnDisabledListener(Callback* callback); + + private: + friend struct DefaultSingletonTraits<ExtensionAccessibilityEventRouter>; + + ExtensionAccessibilityEventRouter() + : enabled_(false) {} + virtual ~ExtensionAccessibilityEventRouter() {} + + // NotificationObserver::Observe. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + void OnWindowOpened(const AccessibilityWindowInfo* details); + void OnWindowClosed(const AccessibilityWindowInfo* details); + void OnControlFocused(const AccessibilityControlInfo* details); + void OnControlAction(const AccessibilityControlInfo* details); + void OnTextChanged(const AccessibilityControlInfo* details); + + void DispatchEvent(Profile* profile, + const char* event_name, + const std::string& json_args); + + // Used for tracking registrations to history service notifications. + NotificationRegistrar registrar_; + + DictionaryValue last_focused_control_dict_; + + bool enabled_; + std::vector<Callback*> on_enabled_listeners_; + std::vector<Callback*> on_disabled_listeners_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionAccessibilityEventRouter); +}; + +// API function that enables or disables accessibility support. Event +// listeners are only installed when accessibility support is enabled, to +// minimize the impact. +class SetAccessibilityEnabledFunction : public SyncExtensionFunction { + virtual ~SetAccessibilityEnabledFunction() {} + virtual bool RunImpl(); + DECLARE_EXTENSION_FUNCTION_NAME( + "experimental.accessibility.setAccessibilityEnabled") +}; + +// API function that returns the most recent focused control. +class GetFocusedControlFunction : public SyncExtensionFunction { + virtual ~GetFocusedControlFunction() {} + virtual bool RunImpl(); + DECLARE_EXTENSION_FUNCTION_NAME( + "experimental.accessibility.getFocusedControl") +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_ACCESSIBILITY_API_H_ diff --git a/chrome/browser/extensions/extension_accessibility_api_constants.cc b/chrome/browser/extensions/extension_accessibility_api_constants.cc new file mode 100644 index 0000000..72d6138 --- /dev/null +++ b/chrome/browser/extensions/extension_accessibility_api_constants.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2010 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 "chrome/browser/extensions/extension_accessibility_api_constants.h" + +namespace extension_accessibility_api_constants { + +// String keys for AccessibilityObject properties. +const wchar_t kTypeKey[] = L"type"; +const wchar_t kNameKey[] = L"name"; +const wchar_t kDetailsKey[] = L"details"; +const wchar_t kValueKey[] = L"details.value"; +const wchar_t kPasswordKey[] = L"details.isPassword"; +const wchar_t kItemCountKey[] = L"details.itemCount"; +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"; + +// Events. +const char kOnWindowOpened[] = "experimental.accessibility.onWindowOpened"; +const char kOnWindowClosed[] = "experimental.accessibility.onWindowClosed"; +const char kOnControlFocused[] = "experimental.accessibility.onControlFocused"; +const char kOnControlAction[] = "experimental.accessibility.onControlAction"; +const char kOnTextChanged[] = "experimental.accessibility.onTextChanged"; + +// Types of controls that can receive accessibility events. +extern const char kTypeButton[] = "button"; +extern const char kTypeCheckbox[] = "checkbox"; +extern const char kTypeComboBox[] = "combobox"; +extern const char kTypeLink[] = "link"; +extern const char kTypeRadioButton[] = "radiobutton"; +extern const char kTypeTab[] = "tab"; +extern const char kTypeTextBox[] = "textbox"; +extern const char kTypeWindow[] = "window"; + +} // namespace extension_accessibility_api_constants diff --git a/chrome/browser/extensions/extension_accessibility_api_constants.h b/chrome/browser/extensions/extension_accessibility_api_constants.h new file mode 100644 index 0000000..2020ec8 --- /dev/null +++ b/chrome/browser/extensions/extension_accessibility_api_constants.h @@ -0,0 +1,43 @@ +// Copyright (c) 2010 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. + +// Constants used to for the Accessibility API. + +#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_ACCESSIBILITY_API_CONSTANTS_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_ACCESSIBILITY_API_CONSTANTS_H_ + +namespace extension_accessibility_api_constants { + +// Keys. +extern const wchar_t kTypeKey[]; +extern const wchar_t kNameKey[]; +extern const wchar_t kDetailsKey[]; +extern const wchar_t kValueKey[]; +extern const wchar_t kPasswordKey[]; +extern const wchar_t kItemCountKey[]; +extern const wchar_t kItemIndexKey[]; +extern const wchar_t kSelectionStartKey[]; +extern const wchar_t kSelectionEndKey[]; +extern const wchar_t kCheckedKey[]; + +// Events. +extern const char kOnWindowOpened[]; +extern const char kOnWindowClosed[]; +extern const char kOnControlFocused[]; +extern const char kOnControlAction[]; +extern const char kOnTextChanged[]; + +// Types of controls that can receive accessibility events +extern const char kTypeButton[]; +extern const char kTypeCheckbox[]; +extern const char kTypeComboBox[]; +extern const char kTypeLink[]; +extern const char kTypeRadioButton[]; +extern const char kTypeTab[]; +extern const char kTypeTextBox[]; +extern const char kTypeWindow[]; + +}; // namespace extension_accessibility_api_constants + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_ACCESSIBILITY_API_CONSTANTS_H_ diff --git a/chrome/browser/extensions/extension_accessibility_apitest.cc b/chrome/browser/extensions/extension_accessibility_apitest.cc new file mode 100644 index 0000000..692dc9c --- /dev/null +++ b/chrome/browser/extensions/extension_accessibility_apitest.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2009 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 "chrome/browser/extensions/extension_apitest.h" + +// This extension is currently only supported on Linux and Chrome OS. +#if defined(OS_LINUX) +#define MAYBE_Accessibility Accessibility +#else +#define MAYBE_Accessibility DISABLED_Accessibility +#endif + +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_Accessibility) { + StartHTTPServer(); + ASSERT_TRUE(RunExtensionTest("accessibility")) << message_; +} diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc index bdba709..936d871 100644 --- a/chrome/browser/extensions/extension_function_dispatcher.cc +++ b/chrome/browser/extensions/extension_function_dispatcher.cc @@ -10,6 +10,7 @@ #include "chrome/browser/browser.h" #include "chrome/browser/browser_window.h" #include "chrome/browser/extensions/execute_code_in_tab_function.h" +#include "chrome/browser/extensions/extension_accessibility_api.h" #include "chrome/browser/extensions/extension_bookmark_manager_api.h" #include "chrome/browser/extensions/extension_bookmarks_module.h" #include "chrome/browser/extensions/extension_bookmarks_module_constants.h" @@ -169,6 +170,10 @@ void FactoryRegistry::ResetFunctions() { RegisterFunction<ExtensionTestFailFunction>(); RegisterFunction<ExtensionTestLogFunction>(); RegisterFunction<ExtensionTestQuotaResetFunction>(); + + // Accessibility. + RegisterFunction<GetFocusedControlFunction>(); + RegisterFunction<SetAccessibilityEnabledFunction>(); } void FactoryRegistry::GetAllNames(std::vector<std::string>* names) { diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index 0846dad..3834ed5 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -14,6 +14,7 @@ #include "chrome/browser/chrome_thread.h" #include "chrome/browser/debugger/devtools_manager.h" #include "chrome/browser/extensions/crx_installer.h" +#include "chrome/browser/extensions/extension_accessibility_api.h" #include "chrome/browser/extensions/extension_bookmarks_module.h" #include "chrome/browser/extensions/extension_browser_event_router.h" #include "chrome/browser/extensions/extension_dom_ui.h" @@ -164,6 +165,7 @@ void ExtensionsService::Init() { // Start up the extension event routers. ExtensionHistoryEventRouter::GetInstance()->ObserveProfile(profile_); + ExtensionAccessibilityEventRouter::GetInstance()->ObserveProfile(profile_); LoadAllExtensions(); diff --git a/chrome/browser/gtk/accessibility_event_router_gtk.cc b/chrome/browser/gtk/accessibility_event_router_gtk.cc new file mode 100644 index 0000000..c020721 --- /dev/null +++ b/chrome/browser/gtk/accessibility_event_router_gtk.cc @@ -0,0 +1,286 @@ +// Copyright (c) 2010 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 "chrome/browser/gtk/accessibility_event_router_gtk.h" + +#include "base/basictypes.h" +#include "base/stl_util-inl.h" +#include "chrome/browser/extensions/extension_accessibility_api.h" +#include "chrome/browser/gtk/gtk_chrome_link_button.h" +#include "chrome/browser/profile.h" +#include "chrome/common/notification_type.h" + +namespace { + +// +// Callbacks triggered by signals on gtk widgets. +// + +gboolean OnWidgetFocused(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + reinterpret_cast<AccessibilityEventRouter *>(user_data) + ->DispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_CONTROL_FOCUSED); + return true; +} + +gboolean OnButtonClicked(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + // Skip toggle buttons because we're also listening on "toggle" events. + if (GTK_IS_TOGGLE_BUTTON(widget)) + return true; + reinterpret_cast<AccessibilityEventRouter *>(user_data) + ->DispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); + return true; +} + +gboolean OnButtonToggled(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + bool checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); + // Skip propagating an "uncheck" event for a radio button because it's + // redundant; there will always be a corresponding "check" event for + // a different radio button the group. + if (GTK_IS_RADIO_BUTTON(widget) && !checked) + return true; + reinterpret_cast<AccessibilityEventRouter *>(user_data) + ->DispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); + return true; +} + +gboolean OnSwitchPage(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + reinterpret_cast<AccessibilityEventRouter *>(user_data) + ->DispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); + return true; +} + +} // anonymous namespace + +AccessibilityEventRouter::AccessibilityEventRouter() { + // 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::GetInstance(); + accessibility_event_router->AddOnEnabledListener( + NewCallback(this, + &AccessibilityEventRouter::InstallEventListeners)); + accessibility_event_router->AddOnDisabledListener( + NewCallback(this, + &AccessibilityEventRouter::RemoveEventListeners)); + if (accessibility_event_router->IsAccessibilityEnabled()) { + InstallEventListeners(); + } +} + +// static +AccessibilityEventRouter* AccessibilityEventRouter::GetInstance() { + return Singleton<AccessibilityEventRouter>::get(); +} + +void AccessibilityEventRouter::InstallEventListeners() { + // Create and destroy a GtkNotebook to ensure this module is loaded, + // otherwise we can't lookup its signals. All of the other modules we + // need will already be loaded by the time we get here. + g_object_unref(g_object_ref_sink(gtk_notebook_new())); + + // Add signal emission hooks for the events we're interested in. + focus_hook_ = g_signal_add_emission_hook( + g_signal_lookup("focus-in-event", GTK_TYPE_WIDGET), + 0, OnWidgetFocused, (gpointer)this, NULL); + click_hook_ = g_signal_add_emission_hook( + g_signal_lookup("clicked", GTK_TYPE_BUTTON), + 0, OnButtonClicked, (gpointer)this, NULL); + toggle_hook_ = g_signal_add_emission_hook( + g_signal_lookup("toggled", GTK_TYPE_TOGGLE_BUTTON), + 0, OnButtonToggled, (gpointer)this, NULL); + switch_page_hook_ = g_signal_add_emission_hook( + g_signal_lookup("switch-page", GTK_TYPE_NOTEBOOK), + 0, OnSwitchPage, (gpointer)this, NULL); +} + +void AccessibilityEventRouter::RemoveEventListeners() { + g_signal_remove_emission_hook( + g_signal_lookup("focus-in-event", GTK_TYPE_WIDGET), focus_hook_); + g_signal_remove_emission_hook( + g_signal_lookup("clicked", GTK_TYPE_BUTTON), click_hook_); + g_signal_remove_emission_hook( + g_signal_lookup("toggled", GTK_TYPE_TOGGLE_BUTTON), toggle_hook_); + g_signal_remove_emission_hook( + g_signal_lookup("switch-page", GTK_TYPE_NOTEBOOK), switch_page_hook_); +} + +void AccessibilityEventRouter::AddRootWidget( + GtkWidget* root_widget, Profile* profile) { + root_widget_profile_map_[root_widget] = profile; +} + +void AccessibilityEventRouter::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) { + widget_info_map_[widget].ignore = true; +} + +void AccessibilityEventRouter::SetWidgetName( + GtkWidget* widget, std::string name) { + widget_info_map_[widget].name = name; +} + +void AccessibilityEventRouter::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) { + // 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; + if (profile) + *profile = iter->second; + break; + } + } + if (!is_accessible) + return false; + + // 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; + } + + return is_accessible; +} + +std::string AccessibilityEventRouter::GetWidgetName(GtkWidget* widget) { + base::hash_map<GtkWidget*, WidgetInfo>::const_iterator iter = + widget_info_map_.find(widget); + if (iter != widget_info_map_.end()) { + return iter->second.name; + } else { + return ""; + } +} + +void AccessibilityEventRouter::DispatchAccessibilityNotification( + GtkWidget* widget, NotificationType type) { + Profile *profile; + if (!IsWidgetAccessible(widget, &profile)) + return; + + // The order of these checks matters, because, for example, a radio button + // is a subclass of button. We need to catch the most specific type that + // we can handle for each object. + if (GTK_IS_RADIO_BUTTON(widget)) { + SendRadioButtonNotification(widget, type, profile); + } else if (GTK_IS_TOGGLE_BUTTON(widget)) { + SendCheckboxNotification(widget, type, profile); + } else if (GTK_IS_BUTTON(widget)) { + SendButtonNotification(widget, type, profile); + } else if (GTK_IS_ENTRY(widget)) { + SendTextBoxNotification(widget, type, profile); + } else if (GTK_IS_NOTEBOOK(widget)) { + SendTabNotification(widget, type, profile); + } +} + +void AccessibilityEventRouter::SendRadioButtonNotification( + GtkWidget* widget, NotificationType type, Profile* profile) { + // Get the radio button name + std::string button_name = GetWidgetName(widget); + if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) + button_name = gtk_button_get_label(GTK_BUTTON(widget)); + + // Get its state + bool checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); + + // Get the index of this radio button and the total number of + // radio buttons in the group. + int item_count = 0; + int item_index = -1; + for (GSList* group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget)); + group; + group = group->next) { + if (group->data == widget) { + item_index = item_count; + } + item_count++; + } + item_index = item_count - 1 - item_index; + + AccessibilityRadioButtonInfo info( + profile, button_name, checked, item_index, item_count); + SendAccessibilityNotification(type, &info); +} + +void AccessibilityEventRouter::SendCheckboxNotification( + GtkWidget* widget, NotificationType type, Profile* profile) { + std::string button_name = GetWidgetName(widget); + if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) + button_name = gtk_button_get_label(GTK_BUTTON(widget)); + bool checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); + AccessibilityCheckboxInfo info(profile, button_name, checked); + SendAccessibilityNotification(type, &info); +} + +void AccessibilityEventRouter::SendButtonNotification( + GtkWidget* widget, NotificationType type, Profile* profile) { + std::string button_name = GetWidgetName(widget); + if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) + button_name = gtk_button_get_label(GTK_BUTTON(widget)); + AccessibilityButtonInfo info(profile, button_name); + SendAccessibilityNotification(type, &info); +} + +void AccessibilityEventRouter::SendTextBoxNotification( + GtkWidget* widget, NotificationType type, Profile* profile) { + std::string name = GetWidgetName(widget); + std::string value = gtk_entry_get_text(GTK_ENTRY(widget)); + gint start_pos; + gint end_pos; + gtk_editable_get_selection_bounds(GTK_EDITABLE(widget), &start_pos, &end_pos); + AccessibilityTextBoxInfo info(profile, name, false); + info.SetValue(value, start_pos, end_pos); + SendAccessibilityNotification(type, &info); +} + +void AccessibilityEventRouter::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)); + GtkWidget* page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(widget), index); + GtkWidget* label = gtk_notebook_get_tab_label(GTK_NOTEBOOK(widget), page); + std::string name = GetWidgetName(widget); + if (name.empty() && gtk_label_get_text(GTK_LABEL(label))) { + name = gtk_label_get_text(GTK_LABEL(label)); + } + AccessibilityTabInfo info(profile, name, index, page_count); + SendAccessibilityNotification(type, &info); +} diff --git a/chrome/browser/gtk/accessibility_event_router_gtk.h b/chrome/browser/gtk/accessibility_event_router_gtk.h new file mode 100644 index 0000000..d0fd99d --- /dev/null +++ b/chrome/browser/gtk/accessibility_event_router_gtk.h @@ -0,0 +1,130 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_GTK_ACCESSIBILITY_EVENT_ROUTER_GTK_H_ +#define CHROME_BROWSER_GTK_ACCESSIBILITY_EVENT_ROUTER_GTK_H_ + +#include <gtk/gtk.h> + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/singleton.h" +#include "chrome/common/accessibility_events.h" + +class Profile; + +// Allows us to use (GtkWidget*) in a hash_map with gcc. +namespace __gnu_cxx { +template<> +struct hash<GtkWidget*> { + size_t operator()(GtkWidget* widget) const { + return reinterpret_cast<size_t>(widget); + } +}; +} // namespace __gnu_cxx + +// 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. +// +// Gtk widgets are not accessible by default. When you register a root widget, +// that widget and all of its descendants will start sending accessibility +// event notifications. You can then override the default behavior for +// specific descendants using other methods. +// +// You can use Profile::PauseAccessibilityEvents to prevent a flurry +// of accessibility events when a window is being created or initialized. +class AccessibilityEventRouter { + public: + // Internal information about a particular widget to override the + // information we get directly from gtk. + struct WidgetInfo { + // If nonempty, will use this name instead of the widget's label. + std::string name; + + // If true, will ignore this widget and not send accessibility events. + bool ignore; + }; + + // Get the single instance of this class. + static AccessibilityEventRouter* GetInstance(); + + // Start sending accessibility events for this widget and all of its + // descendants. Notifications will go to the specified profile. + void AddRootWidget(GtkWidget* root_widget, Profile* profile); + + // Stop sending accessibility events for this widget and all of its + // descendants. + void RemoveRootWidget(GtkWidget* root_widget); + + // Don't send any events for this widget. + void IgnoreWidget(GtkWidget* widget); + + // Use the following string as the name of this widget, instead of the + // gtk label associated with the widget. + void SetWidgetName(GtkWidget* widget, std::string name); + + // Forget all information about this widget. + void RemoveWidget(GtkWidget* widget); + + // + // 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( + GtkWidget* widget, NotificationType type); + + // Each of these methods constructs an AccessibilityControlInfo object + // and sends a notification of a specific accessibility event. + void SendRadioButtonNotification( + GtkWidget* widget, NotificationType type, Profile* profile); + void SendCheckboxNotification( + GtkWidget* widget, NotificationType type, Profile* profile); + void SendButtonNotification( + GtkWidget* widget, NotificationType type, Profile* profile); + void SendTextBoxNotification( + GtkWidget* widget, NotificationType type, Profile* profile); + void SendTabNotification( + GtkWidget* widget, NotificationType type, Profile* profile); + + void InstallEventListeners(); + void RemoveEventListeners(); + + private: + AccessibilityEventRouter(); + virtual ~AccessibilityEventRouter() {} + + friend struct DefaultSingletonTraits<AccessibilityEventRouter>; + + // The set of all root widgets; only descendants of these will generate + // accessibility notifications. + base::hash_map<GtkWidget*, Profile*> root_widget_profile_map_; + + // Extra information about specific widgets. + base::hash_map<GtkWidget*, WidgetInfo> widget_info_map_; + + // Installed event listener hook ids so we can remove them later. + gulong focus_hook_; + gulong click_hook_; + gulong toggle_hook_; + gulong switch_page_hook_; + + std::vector<gulong> event_listener_hook_ids_; +}; + +#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 new file mode 100644 index 0000000..64710b1 --- /dev/null +++ b/chrome/browser/gtk/accessible_widget_helper_gtk.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2010 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 "chrome/browser/gtk/accessible_widget_helper_gtk.h" + +#include "app/l10n_util.h" +#include "chrome/browser/profile.h" + +AccessibleWidgetHelper::AccessibleWidgetHelper( + GtkWidget* root_widget, Profile* profile) + : accessibility_event_router_(AccessibilityEventRouter::GetInstance()), + root_widget_(root_widget) { + accessibility_event_router_->AddRootWidget(root_widget_, profile); +} + +AccessibleWidgetHelper::~AccessibleWidgetHelper() { + if (root_widget_) + accessibility_event_router_->RemoveRootWidget(root_widget_); + for (unsigned int i = 0; i < managed_widgets_.size(); i++) { + accessibility_event_router_->RemoveWidget(managed_widgets_[i]); + } +} + +void AccessibleWidgetHelper::IgnoreWidget(GtkWidget* widget) { + accessibility_event_router_->IgnoreWidget(widget); + managed_widgets_.push_back(widget); +} + +void AccessibleWidgetHelper::SetWidgetName( + GtkWidget* widget, std::string name) { + accessibility_event_router_->SetWidgetName(widget, name); + managed_widgets_.push_back(widget); +} + +void AccessibleWidgetHelper::SetWidgetName( + GtkWidget* widget, int string_id) { + std::string name = l10n_util::GetStringUTF8(string_id); + accessibility_event_router_->SetWidgetName(widget, name); + managed_widgets_.push_back(widget); +} diff --git a/chrome/browser/gtk/accessible_widget_helper_gtk.h b/chrome/browser/gtk/accessible_widget_helper_gtk.h new file mode 100644 index 0000000..775b92e --- /dev/null +++ b/chrome/browser/gtk/accessible_widget_helper_gtk.h @@ -0,0 +1,58 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_GTK_ACCESSIBLE_WIDGET_HELPER_GTK_H_ +#define CHROME_BROWSER_GTK_ACCESSIBLE_WIDGET_HELPER_GTK_H_ + +#include <gtk/gtk.h> + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/singleton.h" +#include "chrome/browser/gtk/accessibility_event_router_gtk.h" +#include "chrome/common/accessibility_events.h" + +class Profile; + +// 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 +// events for all of its descendants. +// +// Most controls have default behavior for accessibility; when this needs +// to be augmented, call one of the methods below to ignore a particular +// 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 +// destroyed. +class AccessibleWidgetHelper { + public: + // Contruct an AccessibleWidgetHelper that makes the given root widget + // accessible for the lifetime of this object, sending accessibility + // notifications to the given profile. + AccessibleWidgetHelper(GtkWidget* root_widget, Profile* profile); + + virtual ~AccessibleWidgetHelper(); + + // Do not send accessibility events for this widget + void IgnoreWidget(GtkWidget* widget); + + // Use the following string as the name of this widget, instead of the + // gtk label associated with the widget. + void SetWidgetName(GtkWidget* widget, std::string name); + + // Use the following string as the name of this widget, instead of the + // gtk label associated with the widget. + void SetWidgetName(GtkWidget* widget, int string_id); + + private: + AccessibilityEventRouter* accessibility_event_router_; + GtkWidget* root_widget_; + std::vector<GtkWidget*> managed_widgets_; +}; + +#endif // CHROME_BROWSER_GTK_ACCESSIBLE_WIDGET_HELPER_GTK_H_ diff --git a/chrome/browser/gtk/location_bar_view_gtk.cc b/chrome/browser/gtk/location_bar_view_gtk.cc index 2842fcd..a52105d 100644 --- a/chrome/browser/gtk/location_bar_view_gtk.cc +++ b/chrome/browser/gtk/location_bar_view_gtk.cc @@ -19,6 +19,7 @@ #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/command_updater.h" +#include "chrome/browser/extensions/extension_accessibility_api_constants.h" #include "chrome/browser/extensions/extension_action_context_menu_model.h" #include "chrome/browser/extensions/extension_browser_event_router.h" #include "chrome/browser/extensions/extension_tabs_module.h" @@ -32,6 +33,7 @@ #include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/search_engines/template_url_model.h" #include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/accessibility_events.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_action.h" #include "chrome/common/gtk_util.h" @@ -449,6 +451,14 @@ void LocationBarViewGtk::OnKillFocus() { } void LocationBarViewGtk::OnSetFocus() { + AccessibilityTextBoxInfo info( + profile_, + l10n_util::GetStringUTF8(IDS_ACCNAME_LOCATION).c_str(), + false); + NotificationService::current()->Notify( + NotificationType::ACCESSIBILITY_CONTROL_FOCUSED, + Source<Profile>(profile_), + Details<AccessibilityTextBoxInfo>(&info)); } SkBitmap LocationBarViewGtk::GetFavIcon() const { diff --git a/chrome/browser/gtk/options/advanced_contents_gtk.cc b/chrome/browser/gtk/options/advanced_contents_gtk.cc index d493a39..2ceb997 100644 --- a/chrome/browser/gtk/options/advanced_contents_gtk.cc +++ b/chrome/browser/gtk/options/advanced_contents_gtk.cc @@ -18,6 +18,7 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/download/download_manager.h" #include "chrome/browser/fonts_languages_window.h" +#include "chrome/browser/gtk/accessible_widget_helper_gtk.h" #include "chrome/browser/gtk/gtk_chrome_link_button.h" #include "chrome/browser/gtk/options/cookies_view.h" #include "chrome/browser/gtk/options/options_layout_gtk.h" @@ -82,6 +83,7 @@ GtkWidget* AddCheckButtonWithWrappedLabel(int string_id, GtkWidget* checkbox = CreateCheckButtonWithWrappedLabel(string_id); gtk_box_pack_start(GTK_BOX(container), checkbox, FALSE, FALSE, 0); g_signal_connect(checkbox, "toggled", handler, data); + return checkbox; } @@ -167,13 +169,19 @@ class DownloadSection : public OptionsPageBase { // then turning around and saving them again. bool pref_changing_; + scoped_ptr<AccessibleWidgetHelper> accessible_widget_helper_; + DISALLOW_COPY_AND_ASSIGN(DownloadSection); }; DownloadSection::DownloadSection(Profile* profile) - : OptionsPageBase(profile), pref_changing_(true) { + : OptionsPageBase(profile), + pref_changing_(true) { page_ = gtk_vbox_new(FALSE, gtk_util::kControlSpacing); + accessible_widget_helper_.reset(new AccessibleWidgetHelper( + page_, profile)); + // Download location options. download_location_button_ = gtk_file_chooser_button_new( l10n_util::GetStringUTF8( @@ -212,6 +220,9 @@ DownloadSection::DownloadSection(Profile* profile) FALSE, FALSE, 0); g_signal_connect(download_ask_for_save_location_checkbox_, "clicked", G_CALLBACK(OnDownloadAskForSaveLocationChanged), this); + accessible_widget_helper_->SetWidgetName( + download_ask_for_save_location_checkbox_, + IDS_OPTIONS_DOWNLOADLOCATION_ASKFORSAVELOCATION); // Option for resetting file handlers. reset_file_handlers_label_ = CreateWrappedLabel( @@ -359,6 +370,7 @@ NetworkSection::NetworkSection(Profile* profile) IDS_OPTIONS_PROXIES_CONFIGURE_BUTTON).c_str()); g_signal_connect(change_proxies_button, "clicked", G_CALLBACK(OnChangeProxiesButtonClicked), this); + // Stick it in an hbox so it doesn't expand to the whole width. GtkWidget* button_hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(button_hbox), @@ -526,6 +538,8 @@ class PrivacySection : public OptionsPageBase { // then turning around and saving them again. bool pref_changing_; + scoped_ptr<AccessibleWidgetHelper> accessible_widget_helper_; + DISALLOW_COPY_AND_ASSIGN(PrivacySection); }; @@ -534,6 +548,9 @@ PrivacySection::PrivacySection(Profile* profile) pref_changing_(true) { page_ = gtk_vbox_new(FALSE, gtk_util::kControlSpacing); + accessible_widget_helper_.reset(new AccessibleWidgetHelper( + page_, profile)); + GtkWidget* section_description_label = CreateWrappedLabel( IDS_OPTIONS_DISABLE_SERVICES); gtk_misc_set_alignment(GTK_MISC(section_description_label), 0, 0); @@ -557,6 +574,8 @@ PrivacySection::PrivacySection(Profile* profile) FALSE, FALSE, 0); g_signal_connect(enable_link_doctor_checkbox_, "clicked", G_CALLBACK(OnEnableLinkDoctorChange), this); + accessible_widget_helper_->SetWidgetName( + enable_link_doctor_checkbox_, IDS_OPTIONS_LINKDOCTOR_PREF); enable_suggest_checkbox_ = CreateCheckButtonWithWrappedLabel( IDS_OPTIONS_SUGGEST_PREF); @@ -564,6 +583,8 @@ PrivacySection::PrivacySection(Profile* profile) FALSE, FALSE, 0); g_signal_connect(enable_suggest_checkbox_, "clicked", G_CALLBACK(OnEnableSuggestChange), this); + accessible_widget_helper_->SetWidgetName( + enable_suggest_checkbox_, IDS_OPTIONS_SUGGEST_PREF); enable_dns_prefetching_checkbox_ = CreateCheckButtonWithWrappedLabel( IDS_NETWORK_DNS_PREFETCH_ENABLED_DESCRIPTION); @@ -571,6 +592,9 @@ PrivacySection::PrivacySection(Profile* profile) FALSE, FALSE, 0); g_signal_connect(enable_dns_prefetching_checkbox_, "clicked", G_CALLBACK(OnDNSPrefetchingChange), this); + accessible_widget_helper_->SetWidgetName( + enable_dns_prefetching_checkbox_, + IDS_NETWORK_DNS_PREFETCH_ENABLED_DESCRIPTION); enable_safe_browsing_checkbox_ = CreateCheckButtonWithWrappedLabel( IDS_OPTIONS_SAFEBROWSING_ENABLEPROTECTION); @@ -578,6 +602,9 @@ PrivacySection::PrivacySection(Profile* profile) FALSE, FALSE, 0); g_signal_connect(enable_safe_browsing_checkbox_, "clicked", G_CALLBACK(OnSafeBrowsingChange), this); + accessible_widget_helper_->SetWidgetName( + enable_safe_browsing_checkbox_, + IDS_OPTIONS_SAFEBROWSING_ENABLEPROTECTION); #if defined(GOOGLE_CHROME_BUILD) reporting_enabled_checkbox_ = CreateCheckButtonWithWrappedLabel( @@ -586,6 +613,8 @@ PrivacySection::PrivacySection(Profile* profile) FALSE, FALSE, 0); g_signal_connect(reporting_enabled_checkbox_, "clicked", G_CALLBACK(OnLoggingChange), this); + accessible_widget_helper_->SetWidgetName( + reporting_enabled_checkbox_, IDS_OPTIONS_ENABLE_LOGGING); #endif GtkWidget* cookie_description_label = gtk_label_new( @@ -620,6 +649,7 @@ PrivacySection::PrivacySection(Profile* profile) IDS_OPTIONS_COOKIES_SHOWCOOKIES_WEBSITE_PERMISSIONS).c_str()); g_signal_connect(show_cookies_button, "clicked", G_CALLBACK(OnShowCookiesButtonClicked), this); + // Stick it in an hbox so it doesn't expand to the whole width. GtkWidget* button_hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(button_hbox), show_cookies_button, @@ -876,13 +906,19 @@ class SecuritySection : public OptionsPageBase { // then turning around and saving them again. bool pref_changing_; + scoped_ptr<AccessibleWidgetHelper> accessible_widget_helper_; + DISALLOW_COPY_AND_ASSIGN(SecuritySection); }; SecuritySection::SecuritySection(Profile* profile) - : OptionsPageBase(profile), pref_changing_(true) { + : OptionsPageBase(profile), + pref_changing_(true) { page_ = gtk_vbox_new(FALSE, gtk_util::kControlSpacing); + accessible_widget_helper_.reset(new AccessibleWidgetHelper( + page_, profile)); + GtkWidget* manage_certificates_label = CreateWrappedLabel( IDS_OPTIONS_CERTIFICATES_LABEL); gtk_misc_set_alignment(GTK_MISC(manage_certificates_label), 0, 0); @@ -909,13 +945,20 @@ SecuritySection::SecuritySection(Profile* profile) rev_checking_enabled_checkbox_ = AddCheckButtonWithWrappedLabel( IDS_OPTIONS_SSL_CHECKREVOCATION, page_, G_CALLBACK(OnRevCheckingEnabledToggled), this); + accessible_widget_helper_->SetWidgetName( + rev_checking_enabled_checkbox_, IDS_OPTIONS_SSL_CHECKREVOCATION); ssl2_enabled_checkbox_ = AddCheckButtonWithWrappedLabel( IDS_OPTIONS_SSL_USESSL2, page_, G_CALLBACK(OnSSL2EnabledToggled), this); + accessible_widget_helper_->SetWidgetName( + ssl2_enabled_checkbox_, IDS_OPTIONS_SSL_USESSL2); ssl3_enabled_checkbox_ = AddCheckButtonWithWrappedLabel( IDS_OPTIONS_SSL_USESSL3, page_, G_CALLBACK(OnSSL3EnabledToggled), this); + accessible_widget_helper_->SetWidgetName( + ssl3_enabled_checkbox_, IDS_OPTIONS_SSL_USESSL3); tls1_enabled_checkbox_ = AddCheckButtonWithWrappedLabel( IDS_OPTIONS_SSL_USETLS1, page_, G_CALLBACK(OnTLS1EnabledToggled), this); - + accessible_widget_helper_->SetWidgetName( + tls1_enabled_checkbox_, IDS_OPTIONS_SSL_USETLS1); rev_checking_enabled_.Init(prefs::kCertRevocationCheckingEnabled, profile->GetPrefs(), this); diff --git a/chrome/browser/gtk/options/content_page_gtk.h b/chrome/browser/gtk/options/content_page_gtk.h index ba70861..b5ed627a 100644 --- a/chrome/browser/gtk/options/content_page_gtk.h +++ b/chrome/browser/gtk/options/content_page_gtk.h @@ -7,9 +7,9 @@ #include <gtk/gtk.h> -#include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/options_page_base.h" #include "chrome/browser/profile.h" +#include "chrome/browser/sync/profile_sync_service.h" #include "chrome/common/pref_member.h" class ContentPageGtk : public OptionsPageBase, diff --git a/chrome/browser/gtk/options/general_page_gtk.cc b/chrome/browser/gtk/options/general_page_gtk.cc index b491c629..d9ae203 100644 --- a/chrome/browser/gtk/options/general_page_gtk.cc +++ b/chrome/browser/gtk/options/general_page_gtk.cc @@ -17,6 +17,7 @@ #include "chrome/browser/session_startup_pref.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/gtk_util.h" +#include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" #include "chrome/common/url_constants.h" @@ -293,6 +294,7 @@ GtkWidget* GeneralPageGtk::InitHomepageGroup() { G_CALLBACK(OnNewTabIsHomePageToggled), this); gtk_box_pack_start(GTK_BOX(homepage_hbox), homepage_use_url_radio_, FALSE, FALSE, 0); + homepage_use_url_entry_ = gtk_entry_new(); g_signal_connect(G_OBJECT(homepage_use_url_entry_), "changed", G_CALLBACK(OnHomepageUseUrlEntryChanged), this); @@ -361,6 +363,7 @@ GtkWidget* GeneralPageGtk::InitDefaultBrowserGroup() { l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)).c_str()); g_signal_connect(G_OBJECT(default_browser_use_as_default_button_), "clicked", G_CALLBACK(OnBrowserUseAsDefaultClicked), this); + gtk_box_pack_start(GTK_BOX(vbox), default_browser_use_as_default_button_, FALSE, FALSE, 0); @@ -377,6 +380,7 @@ void GeneralPageGtk::OnStartupRadioToggled(GtkToggleButton* toggle_button, GeneralPageGtk* general_page) { if (general_page->initializing_) return; + if (!gtk_toggle_button_get_active(toggle_button)) { // When selecting a radio button, we get two signals (one for the old radio // being toggled off, one for the new one being toggled on.) Ignore the diff --git a/chrome/browser/gtk/options/options_window_gtk.cc b/chrome/browser/gtk/options/options_window_gtk.cc index 2a12e12..709ee45 100644 --- a/chrome/browser/gtk/options/options_window_gtk.cc +++ b/chrome/browser/gtk/options/options_window_gtk.cc @@ -8,15 +8,19 @@ #include "app/l10n_util.h" #include "base/message_loop.h" +#include "base/scoped_ptr.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/browser_window.h" +#include "chrome/browser/gtk/accessible_widget_helper_gtk.h" #include "chrome/browser/gtk/options/advanced_page_gtk.h" #include "chrome/browser/gtk/options/content_page_gtk.h" #include "chrome/browser/gtk/options/general_page_gtk.h" #include "chrome/browser/profile.h" #include "chrome/browser/window_sizer.h" +#include "chrome/common/accessibility_events.h" #include "chrome/common/gtk_util.h" +#include "chrome/common/notification_service.h" #include "chrome/common/pref_member.h" #include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" @@ -68,6 +72,8 @@ class OptionsWindowGtk { // The last page the user was on when they opened the Options window. IntegerPrefMember last_selected_page_; + scoped_ptr<AccessibleWidgetHelper> accessibility_widget_helper_; + DISALLOW_COPY_AND_ASSIGN(OptionsWindowGtk); }; @@ -85,6 +91,7 @@ OptionsWindowGtk::OptionsWindowGtk(Profile* profile) general_page_(profile_), content_page_(profile_), advanced_page_(profile_) { + // We don't need to observe changes in this value. last_selected_page_.Init(prefs::kOptionsWindowLastTabIndex, g_browser_process->local_state(), NULL); @@ -105,6 +112,9 @@ OptionsWindowGtk::OptionsWindowGtk(Profile* profile) gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog_)->vbox), gtk_util::kContentAreaSpacing); + accessibility_widget_helper_.reset(new AccessibleWidgetHelper( + dialog_, profile)); + notebook_ = gtk_notebook_new(); #if defined(OS_CHROMEOS) @@ -248,10 +258,30 @@ void ShowOptionsWindow(OptionsPage page, OptionsGroup highlight_group, Profile* profile) { DCHECK(profile); + // If there's already an existing options window, activate it and switch to // the specified page. if (!options_window) { + // Creating and initializing a bunch of controls generates a bunch of + // spurious events as control values change. Temporarily suppress + // accessibility events until the window is created. + profile->PauseAccessibilityEvents(); + + // Create the options window. options_window = new OptionsWindowGtk(profile); + + // Resume accessibility events. + profile->ResumeAccessibilityEvents(); } options_window->ShowOptionsPage(page, highlight_group); + + std::string name = l10n_util::GetStringFUTF8( + IDS_OPTIONS_DIALOG_TITLE, + WideToUTF16(l10n_util::GetString(IDS_PRODUCT_NAME))); + AccessibilityWindowInfo info(profile, name); + + NotificationService::current()->Notify( + NotificationType::ACCESSIBILITY_WINDOW_OPENED, + Source<Profile>(profile), + Details<AccessibilityWindowInfo>(&info)); } diff --git a/chrome/browser/profile.h b/chrome/browser/profile.h index 1068370..946e8b6 100644 --- a/chrome/browser/profile.h +++ b/chrome/browser/profile.h @@ -103,7 +103,7 @@ class Profile { // Value that represents no profile Id. static const ProfileId InvalidProfileId; - Profile() : restored_last_session_(false) {} + Profile() : restored_last_session_(false), accessibility_pause_level_(0) {} virtual ~Profile() {} // Profile prefs are registered as soon as the prefs are loaded for the first @@ -385,11 +385,33 @@ class Profile { return restored_last_session_; } + // Stop sending accessibility events until ResumeAccessibilityEvents(). + // Calls to Pause nest; no events will be sent until the number of + // Resume calls matches the number of Pause calls received. + void PauseAccessibilityEvents() { + accessibility_pause_level_++; + } + + void ResumeAccessibilityEvents() { + DCHECK(accessibility_pause_level_ > 0); + accessibility_pause_level_--; + } + + bool ShouldSendAccessibilityEvents() { + return 0 == accessibility_pause_level_; + } + protected: static URLRequestContextGetter* default_request_context_; private: bool restored_last_session_; + + // Accessibility events will only be propagated when the pause + // level is zero. PauseAccessibilityEvents and ResumeAccessibilityEvents + // increment and decrement the level, respectively, rather than set it to + // true or false, so that calls can be nested. + int accessibility_pause_level_; }; class OffTheRecordProfileImpl; |