summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-30 14:14:29 +0000
committerdmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-30 14:14:29 +0000
commit414591b421922b06e50c99fd41cd6ed2033a21ad (patch)
treea87c7961fd5f834f68d1babe1d87adbe77366b62
parente3fe59bfb0912596e8975f9c50b01683c19ee5d9 (diff)
downloadchromium_src-414591b421922b06e50c99fd41cd6ed2033a21ad.zip
chromium_src-414591b421922b06e50c99fd41cd6ed2033a21ad.tar.gz
chromium_src-414591b421922b06e50c99fd41cd6ed2033a21ad.tar.bz2
Add detailed speech descriptions for Chrome OS login screen accessibility
events. BUG=none TEST=Added new unittest: WizardAccessibilityHandlerTest Review URL: http://codereview.chromium.org/3223011 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@61062 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/app/generated_resources.grd77
-rw-r--r--chrome/browser/accessibility_events.h26
-rw-r--r--chrome/browser/chromeos/login/wizard_accessibility_handler.cc431
-rw-r--r--chrome/browser/chromeos/login/wizard_accessibility_handler.h73
-rw-r--r--chrome/browser/chromeos/login/wizard_accessibility_handler_unittest.cc164
-rw-r--r--chrome/browser/chromeos/login/wizard_accessibility_helper.cc4
-rw-r--r--chrome/browser/chromeos/login/wizard_accessibility_helper.h4
-rw-r--r--chrome/browser/chromeos/login/wizard_controller.cc4
-rw-r--r--chrome/browser/extensions/extension_accessibility_api.cc6
-rw-r--r--chrome/browser/views/accessible_view_helper.cc32
-rw-r--r--chrome/browser/views/accessible_view_helper.h4
-rw-r--r--chrome/chrome_tests.gypi1
12 files changed, 800 insertions, 26 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 2e90870..a48b749 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -9480,6 +9480,83 @@ Keep your key file in a safe place. You will need it to create new versions of y
<message name="IDS_PROXY_PORT" desc="Label for the Port box.">
Port
</message>
+
+ <!-- Accessibility strings, to be spoken -->
+ <message name="IDS_CHROMEOS_ACC_BUTTON" desc="Label indicating that a control is a pushbutton.">
+ Button
+ </message>
+ <message name="IDS_CHROMEOS_ACC_CHECKBOX_UNCHECKED" desc="Label indicating that a control is an unchecked check box.">
+ Unchecked check box
+ </message>
+ <message name="IDS_CHROMEOS_ACC_CHECKBOX_CHECKED" desc="Label indicating that a control is a checked check box.">
+ Checked check box
+ </message>
+ <message name="IDS_CHROMEOS_ACC_RADIO_UNSELECTED" desc="Label indicating that a control is an unselected radio button.">
+ Unselected radio button
+ </message>
+ <message name="IDS_CHROMEOS_ACC_RADIO_SELECTED" desc="Label indicating that a control is a selected radio button.">
+ Selected radio button
+ </message>
+ <message name="IDS_CHROMEOS_ACC_COMBOBOX" desc="Label indicating that a control is a combo box, a control that pops down a list of options to choose from.">
+ Combo box
+ </message>
+ <message name="IDS_CHROMEOS_ACC_LISTBOX" desc="Label indicating that a control is a list box, a control that shows a fixed list of options to choose from.">
+ List box
+ </message>
+ <message name="IDS_CHROMEOS_ACC_LINK" desc="Label indicating that a control is a clickable link.">
+ Link
+ </message>
+ <message name="IDS_CHROMEOS_ACC_MENU" desc="Label indicating that a control is a menu that will drop down to reveal options if you click on it.">
+ Menu
+ </message>
+ <message name="IDS_CHROMEOS_ACC_TAB" desc="Label indicating that a control is one tab in a tabbed dialog or window.">
+ Tab
+ </message>
+ <message name="IDS_CHROMEOS_ACC_TEXTBOX" desc="Label indicating that a control is an editable text box.">
+ Text box
+ </message>
+ <message name="IDS_CHROMEOS_ACC_PASSWORDBOX" desc="Label indicating that a control is a secure editable text box for a password, where the keys you type will be obscured.">
+ Password text box
+ </message>
+ <message name="IDS_CHROMEOS_ACC_INDEX_OF_COUNT" desc="Phrase indicating that the current selected item is number INDEX out of a list of COUNT choices, for example if the list is A, B, C, D, E and the user has selected C the phrase would be '3 of 5'.">
+ <ph name="INDEX">$1<ex>3</ex></ph> of <ph name="COUNT">$2<ex>5</ex></ph>
+ </message>
+ <message name="IDS_CHROMEOS_ACC_HAS_SUBMENU" desc="Phrase indicating that a menu item has a submenu.">
+ Has submenu
+ </message>
+ <message name="IDS_CHROMEOS_ACC_SELECTED" desc="Phrase indicating that a control was selected or activated.">
+ Selected
+ </message>
+ <message name="IDS_CHROMEOS_ACC_EXCLAMATION_POINT" desc="The name of the exclamation point character.">
+ Exclamation point
+ </message>
+ <message name="IDS_CHROMEOS_ACC_LEFT_PAREN" desc="The name of the left parenthesis character, abbreviated or shortened if possible.">
+ Left paren
+ </message>
+ <message name="IDS_CHROMEOS_ACC_RIGHT_PAREN" desc="The name of the left parenthesis character, abbreviated or shortened if possible.">
+ Right paren
+ </message>
+ <message name="IDS_CHROMEOS_ACC_SEMICOLON" desc="The name of the semicolon character.">
+ Semicolon
+ </message>
+ <message name="IDS_CHROMEOS_ACC_COLON" desc="The name of the colon character.">
+ Colon
+ </message>
+ <message name="IDS_CHROMEOS_ACC_QUOTE" desc="The name of the quotation mark character, abbreviated or shortened if possible.">
+ Quote
+ </message>
+ <message name="IDS_CHROMEOS_ACC_COMMA" desc="The name of the comma character.">
+ Comma
+ </message>
+ <message name="IDS_CHROMEOS_ACC_PERIOD" desc="The name of the period character.">
+ Period
+ </message>
+ <message name="IDS_CHROMEOS_ACC_SPACE" desc="The name of a space character.">
+ Space
+ </message>
+ <message name="IDS_CHROMEOS_ACC_TEXT_UNSELECTED" desc="A phrase to indicate to the user that text has been unselected.">
+ Unselected
+ </message>
</if>
<if expr="pp_ifdef('chromeos') and lang == 'ja'">
<message name="IDS_STATUSBAR_IME_JAPANESE_IME_STATUS_HIRAGANA" desc="In the language menu button, this shows a character set in use is set to Hiragana.">
diff --git a/chrome/browser/accessibility_events.h b/chrome/browser/accessibility_events.h
index a0ef971..4e765c2 100644
--- a/chrome/browser/accessibility_events.h
+++ b/chrome/browser/accessibility_events.h
@@ -101,6 +101,10 @@ class AccessibilityRadioButtonInfo : public AccessibilityControlInfo {
void SetChecked(bool checked) { checked_ = checked; }
+ int item_index() const { return item_index_; }
+ int item_count() const { return item_count_; }
+ bool checked() const { return checked_; }
+
private:
bool checked_;
// The 0-based index of this radio button and number of buttons in the group.
@@ -125,6 +129,8 @@ class AccessibilityCheckboxInfo : public AccessibilityControlInfo {
void SetChecked(bool checked) { checked_ = checked; }
+ bool checked() const { return checked_; }
+
private:
bool checked_;
};
@@ -151,6 +157,9 @@ class AccessibilityTabInfo : public AccessibilityControlInfo {
name_ = tab_name;
}
+ int tab_index() const { return tab_index_; }
+ int tab_count() const { return tab_count_; }
+
private:
// The 0-based index of this tab and number of tabs in the group.
int tab_index_;
@@ -181,6 +190,10 @@ class AccessibilityComboBoxInfo : public AccessibilityControlInfo {
value_ = value;
}
+ int item_index() const { return item_index_; }
+ int item_count() const { return item_count_; }
+ const std::string& value() const { return value_; }
+
private:
std::string value_;
// The 0-based index of the current item and the number of total items.
@@ -214,6 +227,11 @@ class AccessibilityTextBoxInfo : public AccessibilityControlInfo {
selection_end_ = selection_end;
}
+ const std::string& value() const { return value_; }
+ bool password() const { return password_; }
+ int selection_start() const { return selection_start_; }
+ int selection_end() const { return selection_end_; }
+
private:
std::string value_;
bool password_;
@@ -245,6 +263,10 @@ class AccessibilityListBoxInfo : public AccessibilityControlInfo {
value_ = value;
}
+ int item_index() const { return item_index_; }
+ int item_count() const { return item_count_; }
+ const std::string& value() const { return value_; }
+
private:
std::string value_;
// The 0-based index of the current item and the number of total items.
@@ -283,6 +305,10 @@ class AccessibilityMenuItemInfo : public AccessibilityControlInfo {
virtual void SerializeToDict(DictionaryValue* dict) const;
+ int item_index() const { return item_index_; }
+ int item_count() const { return item_count_; }
+ bool has_submenu() const { return has_submenu_; }
+
private:
bool has_submenu_;
// The 0-based index of the current item and the number of total items.
diff --git a/chrome/browser/chromeos/login/wizard_accessibility_handler.cc b/chrome/browser/chromeos/login/wizard_accessibility_handler.cc
index 6271f12..e98201e 100644
--- a/chrome/browser/chromeos/login/wizard_accessibility_handler.cc
+++ b/chrome/browser/chromeos/login/wizard_accessibility_handler.cc
@@ -4,33 +4,113 @@
#include "chrome/browser/chromeos/login/wizard_accessibility_handler.h"
+#include <algorithm>
+
+#include "app/l10n_util.h"
+#include "base/i18n/char_iterator.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/string_number_conversions.h"
#include "chrome/browser/accessibility_events.h"
+#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/cros/speech_synthesis_library.h"
+#include "chrome/browser/extensions/extension_accessibility_api.h"
+#include "chrome/browser/extensions/extension_accessibility_api_constants.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/profile_manager.h"
+#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_service.h"
+#include "grit/generated_resources.h"
+
+namespace keys = extension_accessibility_api_constants;
+
+namespace {
+
+static std::string SubstringUTF8(std::string str, int start, int len) {
+ base::UTF8CharIterator iter(&str);
+ for (int i = 0; i < start; i++) {
+ if (!iter.Advance())
+ return std::string();
+ }
+
+ int byte_start = iter.array_pos();
+ for (int i = 0; i < len; i++) {
+ if (!iter.Advance())
+ break;
+ }
+ int byte_len = iter.array_pos() - byte_start;
+
+ return str.substr(byte_start, byte_len);
+}
+
+// If the string consists of a single character and that character is
+// punctuation that is not normally spoken by TTS, replace the string
+// with a description of that character (like "period" for ".").
+std::string DescribePunctuation(const std::string& str) {
+ if (str == "!") {
+ return l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_EXCLAMATION_POINT);
+ } else if (str == "(") {
+ return l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_LEFT_PAREN);
+ } else if (str == ")") {
+ return l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_RIGHT_PAREN);
+ } else if (str == ";") {
+ return l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_SEMICOLON);
+ } else if (str == ":") {
+ return l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_COLON);
+ } else if (str == "\"") {
+ return l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_QUOTE);
+ } else if (str == ",") {
+ return l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_COMMA);
+ } else if (str == ".") {
+ return l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_PERIOD);
+ } else if (str == " ") {
+ return l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_SPACE);
+ } else {
+ return str;
+ }
+}
+
+// Append words and separate adding a space if needed. Call
+// DescribePunctuation on to_append so that single punctuation
+// characters are expanded ('.' -> 'period') but punctuation
+// in the middle of a larger phrase are handled by the speech
+// engine.
+void AppendUtterance(std::string to_append, std::string* str) {
+ if ((*str).size())
+ *str += " ";
+
+ *str += DescribePunctuation(to_append);
+}
+
+// Append a localized string from its message ID, adding a space if needed.
+void AppendUtterance(int message_id, std::string* str) {
+ AppendUtterance(l10n_util::GetStringUTF8(message_id), str);
+}
+
+// Append a phrase of the form "3 of 5", adding a space if needed.
+void AppendIndexOfCount(int index, int count, std::string* str) {
+ string16 index_str = base::IntToString16(index);
+ string16 count_str = base::IntToString16(count);
+ AppendUtterance(l10n_util::GetStringFUTF8(IDS_CHROMEOS_ACC_INDEX_OF_COUNT,
+ index_str,
+ count_str), str);
+}
+
+} // anonymous namespace
+
+namespace chromeos {
void WizardAccessibilityHandler::Observe(
NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
- const AccessibilityControlInfo *info = NULL;
- info = Details<const AccessibilityControlInfo>(details).ptr();
- switch (type.value) {
- case NotificationType::ACCESSIBILITY_CONTROL_FOCUSED:
- Speak(info->name().c_str(), false, true);
- break;
- case NotificationType::ACCESSIBILITY_CONTROL_ACTION:
- break;
- case NotificationType::ACCESSIBILITY_TEXT_CHANGED:
- break;
- case NotificationType::ACCESSIBILITY_MENU_OPENED:
- break;
- case NotificationType::ACCESSIBILITY_MENU_CLOSED:
- break;
- default:
- NOTREACHED() << "Cannot recognize notification type: " << type.value;
- break;
- }
+ const AccessibilityControlInfo *control_info =
+ Details<const AccessibilityControlInfo>(details).ptr();
+ std::string description;
+ EarconType earcon = NO_EARCON;
+ DescribeAccessibilityEvent(type, control_info, &description, &earcon);
+ Speak(description.c_str(), false, true);
}
void WizardAccessibilityHandler::Speak(const char* speak_str,
@@ -50,3 +130,318 @@ void WizardAccessibilityHandler::Speak(const char* speak_str,
Speak(speak_str);
}
}
+
+void WizardAccessibilityHandler::DescribeAccessibilityEvent(
+ NotificationType event_type,
+ const AccessibilityControlInfo* control_info,
+ std::string* out_spoken_description,
+ EarconType* out_earcon) {
+ *out_spoken_description = std::string();
+ *out_earcon = NO_EARCON;
+
+ switch (event_type.value) {
+ case NotificationType::ACCESSIBILITY_CONTROL_FOCUSED:
+ DescribeControl(control_info, false, out_spoken_description, out_earcon);
+ break;
+ case NotificationType::ACCESSIBILITY_CONTROL_ACTION:
+ DescribeControl(control_info, true, out_spoken_description, out_earcon);
+ break;
+ case NotificationType::ACCESSIBILITY_TEXT_CHANGED:
+ DescribeTextChanged(control_info, out_spoken_description, out_earcon);
+ break;
+ case NotificationType::ACCESSIBILITY_MENU_OPENED:
+ *out_earcon = EARCON_OBJECT_OPENED;
+ break;
+ case NotificationType::ACCESSIBILITY_MENU_CLOSED:
+ *out_earcon = EARCON_OBJECT_CLOSED;
+ break;
+ default:
+ NOTREACHED();
+ return;
+ }
+
+ if (control_info->type() == keys::kTypeTextBox) {
+ const AccessibilityTextBoxInfo* text_box =
+ static_cast<const AccessibilityTextBoxInfo*>(control_info);
+ previous_text_value_ = GetTextBoxValue(text_box);
+ previous_text_selection_start_ = text_box->selection_start();
+ previous_text_selection_end_ = text_box->selection_end();
+ }
+}
+
+void WizardAccessibilityHandler::DescribeControl(
+ const AccessibilityControlInfo* control_info,
+ bool is_action,
+ std::string* out_spoken_description,
+ EarconType* out_earcon) {
+ if (control_info->type() == keys::kTypeButton) {
+ *out_earcon = EARCON_BUTTON;
+ AppendUtterance(control_info->name(), out_spoken_description);
+ AppendUtterance(IDS_CHROMEOS_ACC_BUTTON, out_spoken_description);
+ } else if (control_info->type() == keys::kTypeCheckbox) {
+ AppendUtterance(control_info->name(), out_spoken_description);
+ const AccessibilityCheckboxInfo* checkbox_info =
+ static_cast<const AccessibilityCheckboxInfo*>(control_info);
+ if (checkbox_info->checked()) {
+ *out_earcon = EARCON_CHECK_ON;
+ AppendUtterance(IDS_CHROMEOS_ACC_CHECKBOX_CHECKED,
+ out_spoken_description);
+ } else {
+ *out_earcon = EARCON_CHECK_OFF;
+ AppendUtterance(IDS_CHROMEOS_ACC_CHECKBOX_UNCHECKED,
+ out_spoken_description);
+ }
+ } else if (control_info->type() == keys::kTypeComboBox) {
+ *out_earcon = EARCON_LISTBOX;
+ const AccessibilityComboBoxInfo* combobox_info =
+ static_cast<const AccessibilityComboBoxInfo*>(control_info);
+ AppendUtterance(combobox_info->value(), out_spoken_description);
+ AppendUtterance(combobox_info->name(), out_spoken_description);
+ AppendUtterance(IDS_CHROMEOS_ACC_COMBOBOX, out_spoken_description);
+ AppendIndexOfCount(combobox_info->item_index() + 1,
+ combobox_info->item_count(),
+ out_spoken_description);
+ } else if (control_info->type() == keys::kTypeLink) {
+ *out_earcon = EARCON_LINK;
+ AppendUtterance(control_info->name(), out_spoken_description);
+ AppendUtterance(IDS_CHROMEOS_ACC_LINK, out_spoken_description);
+ } else if (control_info->type() == keys::kTypeListBox) {
+ *out_earcon = EARCON_LISTBOX;
+ const AccessibilityListBoxInfo* listbox_info =
+ static_cast<const AccessibilityListBoxInfo*>(control_info);
+ AppendUtterance(listbox_info->value(), out_spoken_description);
+ AppendUtterance(listbox_info->name(), out_spoken_description);
+ AppendUtterance(IDS_CHROMEOS_ACC_LISTBOX, out_spoken_description);
+ AppendIndexOfCount(listbox_info->item_index() + 1,
+ listbox_info->item_count(),
+ out_spoken_description);
+ } else if (control_info->type() == keys::kTypeMenu) {
+ *out_earcon = EARCON_MENU;
+ AppendUtterance(control_info->name(), out_spoken_description);
+ AppendUtterance(IDS_CHROMEOS_ACC_MENU, out_spoken_description);
+ } else if (control_info->type() == keys::kTypeMenuItem) {
+ const AccessibilityMenuItemInfo* menu_item_info =
+ static_cast<const AccessibilityMenuItemInfo*>(control_info);
+ AppendUtterance(menu_item_info->name(), out_spoken_description);
+ if (menu_item_info->has_submenu())
+ AppendUtterance(IDS_CHROMEOS_ACC_HAS_SUBMENU, out_spoken_description);
+ AppendIndexOfCount(menu_item_info->item_index() + 1,
+ menu_item_info->item_count(),
+ out_spoken_description);
+ } else if (control_info->type() == keys::kTypeRadioButton) {
+ AppendUtterance(control_info->name(), out_spoken_description);
+ const AccessibilityRadioButtonInfo* radio_info =
+ static_cast<const AccessibilityRadioButtonInfo*>(control_info);
+ if (radio_info->checked()) {
+ *out_earcon = EARCON_CHECK_ON;
+ AppendUtterance(IDS_CHROMEOS_ACC_RADIO_SELECTED, out_spoken_description);
+ } else {
+ *out_earcon = EARCON_CHECK_OFF;
+ AppendUtterance(IDS_CHROMEOS_ACC_RADIO_UNSELECTED,
+ out_spoken_description);
+ }
+ AppendIndexOfCount(radio_info->item_index() + 1,
+ radio_info->item_count(),
+ out_spoken_description);
+ } else if (control_info->type() == keys::kTypeTab) {
+ *out_earcon = EARCON_TAB;
+ AppendUtterance(control_info->name(), out_spoken_description);
+ const AccessibilityTabInfo* tab_info =
+ static_cast<const AccessibilityTabInfo*>(control_info);
+ AppendUtterance(IDS_CHROMEOS_ACC_TAB, out_spoken_description);
+ AppendIndexOfCount(tab_info->tab_index() + 1,
+ tab_info->tab_count(),
+ out_spoken_description);
+ } else if (control_info->type() == keys::kTypeTextBox) {
+ *out_earcon = EARCON_TEXTBOX;
+ const AccessibilityTextBoxInfo* textbox_info =
+ static_cast<const AccessibilityTextBoxInfo*>(control_info);
+ AppendUtterance(GetTextBoxValue(textbox_info), out_spoken_description);
+ AppendUtterance(textbox_info->name(), out_spoken_description);
+ if (textbox_info->password()) {
+ AppendUtterance(IDS_CHROMEOS_ACC_PASSWORDBOX, out_spoken_description);
+ } else {
+ AppendUtterance(IDS_CHROMEOS_ACC_TEXTBOX, out_spoken_description);
+ }
+ } else if (control_info->type() == keys::kTypeWindow) {
+ // No feedback when a window gets focus
+ }
+
+ if (is_action)
+ AppendUtterance(IDS_CHROMEOS_ACC_SELECTED, out_spoken_description);
+}
+
+void WizardAccessibilityHandler::DescribeTextChanged(
+ const AccessibilityControlInfo* control_info,
+ std::string* out_spoken_description,
+ EarconType* out_earcon) {
+ DCHECK_EQ(control_info->type(), keys::kTypeTextBox);
+ const AccessibilityTextBoxInfo* text_box =
+ static_cast<const AccessibilityTextBoxInfo*>(control_info);
+
+ std::string old_value = previous_text_value_;
+ int old_start = previous_text_selection_start_;
+ int old_end = previous_text_selection_end_;
+ std::string new_value = GetTextBoxValue(text_box);
+ int new_start = text_box->selection_start();
+ int new_end = text_box->selection_end();
+
+ if (new_value == old_value) {
+ DescribeTextSelectionChanged(new_value,
+ old_start, old_end,
+ new_start, new_end,
+ out_spoken_description);
+ } else {
+ DescribeTextContentsChanged(old_value, new_value,
+ out_spoken_description);
+ }
+}
+
+std::string WizardAccessibilityHandler::GetTextBoxValue(
+ const AccessibilityTextBoxInfo* textbox_info) {
+ std::string value = textbox_info->value();
+ if (textbox_info->password()) {
+ base::UTF8CharIterator iter(&value);
+ std::string obscured;
+ while (!iter.end()) {
+ obscured += "*";
+ iter.Advance();
+ }
+ return obscured;
+ } else {
+ return value;
+ }
+}
+
+void WizardAccessibilityHandler::DescribeTextSelectionChanged(
+ const std::string& value,
+ int old_start,
+ int old_end,
+ int new_start,
+ int new_end,
+ std::string* out_spoken_description) {
+ if (new_start == new_end) {
+ // It's currently a cursor.
+ if (old_start != old_end) {
+ // It was previously a selection, so just announce 'unselected'.
+ AppendUtterance(IDS_CHROMEOS_ACC_TEXT_UNSELECTED, out_spoken_description);
+ } else if (old_start == new_start + 1 || old_start == new_start - 1) {
+ // Moved by one character; read it.
+ AppendUtterance(SubstringUTF8(value, std::min(old_start, new_start), 1),
+ out_spoken_description);
+ } else {
+ // Moved by more than one character. Read all characters crossed.
+ AppendUtterance(SubstringUTF8(value,
+ std::min(old_start, new_start),
+ abs(old_start - new_start)),
+ out_spoken_description);
+ }
+ } else {
+ // It's currently a selection.
+ if (old_start == old_end) {
+ // It was previously a cursor.
+ AppendUtterance(SubstringUTF8(value, new_start, new_end - new_start),
+ out_spoken_description);
+ } else if (old_start == new_start && old_end < new_end) {
+ // Added to end of selection.
+ AppendUtterance(SubstringUTF8(value, old_end, new_end - old_end),
+ out_spoken_description);
+ } else if (old_start == new_start && old_end > new_end) {
+ // Removed from end of selection.
+ AppendUtterance(SubstringUTF8(value, new_end, old_end - new_end),
+ out_spoken_description);
+ } else if (old_end == new_end && old_start > new_start) {
+ // Added to beginning of selection.
+ AppendUtterance(SubstringUTF8(value, new_start, old_start - new_start),
+ out_spoken_description);
+ } else if (old_end == new_end && old_start < new_start) {
+ // Removed from beginning of selection.
+ AppendUtterance(SubstringUTF8(value, old_start, new_start - old_start),
+ out_spoken_description);
+ } else {
+ // The selection changed but it wasn't an obvious extension of
+ // a previous selection. Just read the new selection.
+ AppendUtterance(SubstringUTF8(value, new_start, new_end - new_start),
+ out_spoken_description);
+ }
+ }
+}
+
+void WizardAccessibilityHandler::DescribeTextContentsChanged(
+ const std::string& old_value,
+ const std::string& new_value,
+ std::string* out_spoken_description) {
+ int old_array_len = old_value.size();
+ int new_array_len = new_value.size();
+
+ // Get the unicode characters and indices of the start of each
+ // character's UTF8-encoded representation.
+ scoped_array<int32> old_chars(new int32[old_array_len]);
+ scoped_array<int> old_indices(new int[old_array_len + 1]);
+ base::UTF8CharIterator old_iter(&old_value);
+ while (!old_iter.end()) {
+ old_chars[old_iter.char_pos()] = old_iter.get();
+ old_indices[old_iter.char_pos()] = old_iter.array_pos();
+ old_iter.Advance();
+ }
+ int old_char_len = old_iter.char_pos();
+ old_indices[old_char_len] = old_iter.array_pos();
+
+ scoped_array<int32> new_chars(new int32[new_array_len]);
+ scoped_array<int> new_indices(new int[new_array_len + 1]);
+ base::UTF8CharIterator new_iter(&new_value);
+ while (!new_iter.end()) {
+ new_chars[new_iter.char_pos()] = new_iter.get();
+ new_indices[new_iter.char_pos()] = new_iter.array_pos();
+ new_iter.Advance();
+ }
+ int new_char_len = new_iter.char_pos();
+ new_indices[new_char_len] = new_iter.array_pos();
+
+ // Find the common prefix of the two strings.
+ int prefix_char_len = 0;
+ while (prefix_char_len < old_char_len &&
+ prefix_char_len < new_char_len &&
+ old_chars[prefix_char_len] == new_chars[prefix_char_len]) {
+ prefix_char_len++;
+ }
+
+ // Find the common suffix of the two stirngs.
+ int suffix_char_len = 0;
+ while (suffix_char_len < old_char_len - prefix_char_len &&
+ suffix_char_len < new_char_len - prefix_char_len &&
+ (old_chars[old_char_len - suffix_char_len - 1] ==
+ new_chars[new_char_len - suffix_char_len - 1])) {
+ suffix_char_len++;
+ }
+
+ int old_suffix_char_start = old_char_len - suffix_char_len;
+ int new_suffix_char_start = new_char_len - suffix_char_len;
+
+ // Find the substring that was deleted (if any) to get the new string
+ // from the old - it's the part in the middle of the old string if you
+ // remove the common prefix and suffix.
+ std::string deleted = old_value.substr(
+ old_indices[prefix_char_len],
+ old_indices[old_suffix_char_start] - old_indices[prefix_char_len]);
+
+ // Find the substring that was inserted (if any) to get the new string
+ // from the old - it's the part in the middle of the new string if you
+ // remove the common prefix and suffix.
+ std::string inserted = new_value.substr(
+ new_indices[prefix_char_len],
+ new_indices[new_suffix_char_start] - new_indices[prefix_char_len]);
+
+ if (inserted.size() > 0 && deleted.size() > 0) {
+ // Replace one substring with another, speak inserted text.
+ AppendUtterance(inserted, out_spoken_description);
+ } else if (inserted.size() > 0) {
+ // Speak inserted text.
+ AppendUtterance(inserted, out_spoken_description);
+ } else if (deleted.size() > 0) {
+ // Speak deleted text.
+ AppendUtterance(deleted, out_spoken_description);
+ }
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/chromeos/login/wizard_accessibility_handler.h b/chrome/browser/chromeos/login/wizard_accessibility_handler.h
index 68987b4..5fda4dd 100644
--- a/chrome/browser/chromeos/login/wizard_accessibility_handler.h
+++ b/chrome/browser/chromeos/login/wizard_accessibility_handler.h
@@ -6,23 +6,94 @@
#define CHROME_BROWSER_CHROMEOS_LOGIN_WIZARD_ACCESSIBILITY_HANDLER_H_
#pragma once
+#include <string>
+
+#include "base/gtest_prod_util.h"
#include "chrome/common/notification_observer.h"
#include "chrome/common/notification_source.h"
#include "chrome/common/notification_type.h"
+class AccessibilityControlInfo;
+class AccessibilityTextBoxInfo;
+
+namespace chromeos {
+
+enum EarconType {
+ NO_EARCON,
+ EARCON_BUTTON,
+ EARCON_CHECK_OFF,
+ EARCON_CHECK_ON,
+ EARCON_ELLIPSES,
+ EARCON_LINK,
+ EARCON_LISTBOX,
+ EARCON_MENU,
+ EARCON_OBJECT_OPENED,
+ EARCON_OBJECT_CLOSED,
+ EARCON_TAB,
+ EARCON_TEXTBOX,
+};
+
// Class that handles the accessibility notifications and generates
// appropriate spoken/audio feedback.
class WizardAccessibilityHandler : public NotificationObserver {
public:
+ WizardAccessibilityHandler() { }
+
// Speaks the specified string.
void Speak(const char* speak_str, bool queue, bool interruptible);
private:
-
// Override from NotificationObserver.
virtual void Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details);
+
+ // Get text to speak and an earcon identifier (which may be NONE) for any
+ // accessibility event.
+ void DescribeAccessibilityEvent(NotificationType event_type,
+ const AccessibilityControlInfo* control_info,
+ std::string* out_spoken_description,
+ EarconType* out_earcon);
+
+ // Get text to speak and an optional earcon identifier, specifically for
+ // a focus or select accessibility event on a control.
+ void DescribeControl(const AccessibilityControlInfo* control_info,
+ bool is_action,
+ std::string* out_spoken_description,
+ EarconType* out_earcon);
+
+ // Get text to speak when a text control has changed in some way, either
+ // the contents or selection/cursor.
+ void DescribeTextChanged(const AccessibilityControlInfo* control_info,
+ std::string* out_spoken_description,
+ EarconType* out_earcon);
+
+ // Get the text from an AccessibilityTextBoxInfo, obscuring the
+ // text if it's a password field.
+ std::string GetTextBoxValue(const AccessibilityTextBoxInfo* textbox_info);
+
+ // Get text to speak when only the selection/cursor has changed.
+ void DescribeTextSelectionChanged(const std::string& value,
+ int old_start, int old_end,
+ int new_start, int new_end,
+ std::string* out_spoken_description);
+
+ // Get text to speak when the contents of a text control has changed.
+ void DescribeTextContentsChanged(const std::string& old_value,
+ const std::string& new_value,
+ std::string* out_spoken_description);
+
+ int previous_text_selection_start_;
+ int previous_text_selection_end_;
+ std::string previous_text_value_;
+
+ friend class WizardAccessibilityHandlerTest;
+ FRIEND_TEST_ALL_PREFIXES(WizardAccessibilityHandlerTest, TestFocusEvents);
+ FRIEND_TEST_ALL_PREFIXES(WizardAccessibilityHandlerTest, TestTextEvents);
+
+ DISALLOW_COPY_AND_ASSIGN(WizardAccessibilityHandler);
};
+} // namespace chromeos
+
#endif // CHROME_BROWSER_CHROMEOS_LOGIN_WIZARD_ACCESSIBILITY_HANDLER_H_
diff --git a/chrome/browser/chromeos/login/wizard_accessibility_handler_unittest.cc b/chrome/browser/chromeos/login/wizard_accessibility_handler_unittest.cc
new file mode 100644
index 0000000..b1afe99
--- /dev/null
+++ b/chrome/browser/chromeos/login/wizard_accessibility_handler_unittest.cc
@@ -0,0 +1,164 @@
+// 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/accessibility_events.h"
+#include "chrome/browser/chromeos/login/wizard_accessibility_handler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using chromeos::EarconType;
+using chromeos::WizardAccessibilityHandler;
+
+namespace chromeos {
+
+class WizardAccessibilityHandlerTest : public testing::Test {
+ protected:
+ void ChangeText(WizardAccessibilityHandler* handler,
+ AccessibilityTextBoxInfo* textbox_info,
+ const std::string& value,
+ int selection_start,
+ int selection_end,
+ std::string* description) {
+ textbox_info->SetValue(value, selection_start, selection_end);
+ EarconType earcon = chromeos::NO_EARCON;
+ handler->DescribeAccessibilityEvent(
+ NotificationType::ACCESSIBILITY_TEXT_CHANGED,
+ textbox_info,
+ description,
+ &earcon);
+ }
+};
+
+TEST_F(WizardAccessibilityHandlerTest, TestFocusEvents) {
+ WizardAccessibilityHandler handler;
+
+ std::string description;
+ EarconType earcon;
+
+ // No need to test every possible control, but test several types
+ // to exercise different types of string concatenation.
+
+ // NOTE: unittests are forced to run under the en-US locale, so it's
+ // safe to do these string comparisons without using l10n_util.
+
+ // Test a simple control.
+ std::string button_name = "Save";
+ AccessibilityButtonInfo button_info(NULL, button_name);
+ handler.DescribeAccessibilityEvent(
+ NotificationType::ACCESSIBILITY_CONTROL_FOCUSED,
+ &button_info,
+ &description,
+ &earcon);
+ EXPECT_EQ(chromeos::EARCON_BUTTON, earcon);
+ EXPECT_EQ("Save Button", description);
+
+ // Test a control with multiple states.
+ std::string checkbox_name = "Accessibility";
+ AccessibilityCheckboxInfo checkbox_info(NULL, checkbox_name, false);
+ handler.DescribeAccessibilityEvent(
+ NotificationType::ACCESSIBILITY_CONTROL_FOCUSED,
+ &checkbox_info,
+ &description,
+ &earcon);
+ EXPECT_EQ(chromeos::EARCON_CHECK_OFF, earcon);
+ EXPECT_EQ("Accessibility Unchecked check box", description);
+ checkbox_info.SetChecked(true);
+ handler.DescribeAccessibilityEvent(
+ NotificationType::ACCESSIBILITY_CONTROL_FOCUSED,
+ &checkbox_info,
+ &description,
+ &earcon);
+ EXPECT_EQ(chromeos::EARCON_CHECK_ON, earcon);
+ EXPECT_EQ("Accessibility Checked check box", description);
+
+ // Test a control with a value and index.
+ std::string combobox_name = "Language";
+ std::string combobox_value = "English";
+ AccessibilityComboBoxInfo combobox_info(
+ NULL, combobox_name, combobox_value, 12, 35);
+ handler.DescribeAccessibilityEvent(
+ NotificationType::ACCESSIBILITY_CONTROL_FOCUSED,
+ &combobox_info,
+ &description,
+ &earcon);
+ EXPECT_EQ(chromeos::EARCON_LISTBOX, earcon);
+ EXPECT_EQ("English Language Combo box 13 of 35", description);
+}
+
+TEST_F(WizardAccessibilityHandlerTest, TestTextEvents) {
+ WizardAccessibilityHandler handler;
+
+ std::string description;
+ EarconType earcon;
+
+ AccessibilityTextBoxInfo textbox_info(NULL, "", false);
+ handler.DescribeAccessibilityEvent(
+ NotificationType::ACCESSIBILITY_CONTROL_FOCUSED,
+ &textbox_info,
+ &description,
+ &earcon);
+ EXPECT_EQ("Text box", description);
+ EXPECT_EQ(chromeos::EARCON_TEXTBOX, earcon);
+
+ // Type "hello world.", one character at a time.
+ ChangeText(&handler, &textbox_info, "h", 1, 1, &description);
+ EXPECT_EQ("h", description);
+ ChangeText(&handler, &textbox_info, "he", 2, 2, &description);
+ EXPECT_EQ("e", description);
+ ChangeText(&handler, &textbox_info, "hel", 3, 3, &description);
+ EXPECT_EQ("l", description);
+ ChangeText(&handler, &textbox_info, "hell", 4, 4, &description);
+ EXPECT_EQ("l", description);
+ ChangeText(&handler, &textbox_info, "hello", 5, 5, &description);
+ EXPECT_EQ("o", description);
+ ChangeText(&handler, &textbox_info, "hello ", 6, 6, &description);
+ EXPECT_EQ("Space", description);
+ ChangeText(&handler, &textbox_info, "hello w", 7, 7, &description);
+ EXPECT_EQ("w", description);
+ ChangeText(&handler, &textbox_info, "hello wo", 8, 8, &description);
+ EXPECT_EQ("o", description);
+ ChangeText(&handler, &textbox_info, "hello wor", 9, 9, &description);
+ EXPECT_EQ("r", description);
+ ChangeText(&handler, &textbox_info, "hello worl", 10, 10, &description);
+ EXPECT_EQ("l", description);
+ ChangeText(&handler, &textbox_info, "hello world", 11, 11, &description);
+ EXPECT_EQ("d", description);
+ ChangeText(&handler, &textbox_info, "hello world.", 12, 12, &description);
+ EXPECT_EQ("Period", description);
+
+ // Move by characters and by words.
+ ChangeText(&handler, &textbox_info, "hello world.", 11, 11, &description);
+ EXPECT_EQ("Period", description);
+ ChangeText(&handler, &textbox_info, "hello world.", 6, 6, &description);
+ EXPECT_EQ("world", description);
+ ChangeText(&handler, &textbox_info, "hello world.", 0, 0, &description);
+ EXPECT_EQ("hello ", description);
+ ChangeText(&handler, &textbox_info, "hello world.", 1, 1, &description);
+ EXPECT_EQ("h", description);
+ ChangeText(&handler, &textbox_info, "hello world.", 5, 5, &description);
+ EXPECT_EQ("ello", description);
+
+ // Delete characters and words.
+ ChangeText(&handler, &textbox_info, "hell world.", 4, 4, &description);
+ EXPECT_EQ("o", description);
+ ChangeText(&handler, &textbox_info, "hel world.", 3, 3, &description);
+ EXPECT_EQ("l", description);
+ ChangeText(&handler, &textbox_info, " world.", 0, 0, &description);
+ EXPECT_EQ("hel", description);
+
+ // Select characters and words.
+ ChangeText(&handler, &textbox_info, " world.", 0, 1, &description);
+ EXPECT_EQ("Space", description);
+ ChangeText(&handler, &textbox_info, " world.", 0, 4, &description);
+ EXPECT_EQ("wor", description);
+ ChangeText(&handler, &textbox_info, " world.", 1, 4, &description);
+ EXPECT_EQ("Space", description);
+ ChangeText(&handler, &textbox_info, " world.", 4, 4, &description);
+ EXPECT_EQ("Unselected", description);
+
+ // If the string suddenly changes, it should just speak the new value.
+ ChangeText(&handler, &textbox_info, "Potato", 0, 0, &description);
+ EXPECT_EQ("Potato", description);
+}
+
+}
diff --git a/chrome/browser/chromeos/login/wizard_accessibility_helper.cc b/chrome/browser/chromeos/login/wizard_accessibility_helper.cc
index eea0bc2..db0f097 100644
--- a/chrome/browser/chromeos/login/wizard_accessibility_helper.cc
+++ b/chrome/browser/chromeos/login/wizard_accessibility_helper.cc
@@ -17,6 +17,8 @@
#include "views/accelerator.h"
#include "views/view.h"
+namespace chromeos {
+
scoped_ptr<views::Accelerator> WizardAccessibilityHelper::accelerator_;
// static
@@ -114,3 +116,5 @@ void WizardAccessibilityHelper::AddViewToBuffer(views::View* view_tree) {
if (!view_exists)
views_buffer_[view_tree] = false;
}
+
+} // namespace chromeos
diff --git a/chrome/browser/chromeos/login/wizard_accessibility_helper.h b/chrome/browser/chromeos/login/wizard_accessibility_helper.h
index 9344a47..27c9a9b 100644
--- a/chrome/browser/chromeos/login/wizard_accessibility_helper.h
+++ b/chrome/browser/chromeos/login/wizard_accessibility_helper.h
@@ -22,6 +22,8 @@ class Accelerator;
class View;
}
+namespace chromeos {
+
// Class that provides convenience methods to enable accessibility for a
// specified View.
class WizardAccessibilityHelper {
@@ -74,4 +76,6 @@ class WizardAccessibilityHelper {
DISALLOW_COPY_AND_ASSIGN(WizardAccessibilityHelper);
};
+} // namespace chromeos
+
#endif // CHROME_BROWSER_CHROMEOS_LOGIN_WIZARD_ACCESSIBILITY_HELPER_H_
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index fa0f938..2a8afdd 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -79,7 +79,7 @@ class ContentView : public views::View {
public:
ContentView()
: accel_enable_accessibility_(
- WizardAccessibilityHelper::GetAccelerator()) {
+ chromeos::WizardAccessibilityHelper::GetAccelerator()) {
AddAccelerator(accel_enable_accessibility_);
#if !defined(OFFICIAL_BUILD)
accel_account_screen_ = views::Accelerator(app::VKEY_A,
@@ -119,7 +119,7 @@ class ContentView : public views::View {
return false;
if (accel == accel_enable_accessibility_) {
- WizardAccessibilityHelper::GetInstance()->EnableAccessibility(
+ chromeos::WizardAccessibilityHelper::GetInstance()->EnableAccessibility(
controller->contents());
#if !defined(OFFICIAL_BUILD)
} else if (accel == accel_account_screen_) {
diff --git a/chrome/browser/extensions/extension_accessibility_api.cc b/chrome/browser/extensions/extension_accessibility_api.cc
index bf310f2..83a6194 100644
--- a/chrome/browser/extensions/extension_accessibility_api.cc
+++ b/chrome/browser/extensions/extension_accessibility_api.cc
@@ -64,6 +64,12 @@ void ExtensionAccessibilityEventRouter::ObserveProfile(Profile* profile) {
registrar_.Add(this,
NotificationType::ACCESSIBILITY_TEXT_CHANGED,
NotificationService::AllSources());
+ registrar_.Add(this,
+ NotificationType::ACCESSIBILITY_MENU_OPENED,
+ NotificationService::AllSources());
+ registrar_.Add(this,
+ NotificationType::ACCESSIBILITY_MENU_CLOSED,
+ NotificationService::AllSources());
}
}
diff --git a/chrome/browser/views/accessible_view_helper.cc b/chrome/browser/views/accessible_view_helper.cc
index 63f381b..aa86ef9 100644
--- a/chrome/browser/views/accessible_view_helper.cc
+++ b/chrome/browser/views/accessible_view_helper.cc
@@ -9,6 +9,7 @@
#include "chrome/browser/profile.h"
#include "chrome/browser/views/accessibility_event_router_views.h"
#include "chrome/common/notification_service.h"
+#include "views/controls/native/native_view_host.h"
#include "views/widget/widget.h"
#include "views/view.h"
@@ -59,6 +60,12 @@ void AccessibleViewHelper::IgnoreView(views::View* view) {
accessibility_event_router_->IgnoreView(view);
managed_views_.push_back(view);
+
+ #if defined(OS_LINUX)
+ gfx::NativeView native_view = GetNativeView(view);
+ if (native_view)
+ widget_helper_->IgnoreWidget(native_view);
+ #endif
}
void AccessibleViewHelper::SetViewName(views::View* view, std::string name) {
@@ -67,13 +74,28 @@ void AccessibleViewHelper::SetViewName(views::View* view, std::string name) {
accessibility_event_router_->SetViewName(view, name);
managed_views_.push_back(view);
+
+ #if defined(OS_LINUX)
+ gfx::NativeView native_view = GetNativeView(view);
+ if (native_view)
+ widget_helper_->SetWidgetName(native_view, name);
+ #endif
}
void AccessibleViewHelper::SetViewName(views::View* view, int string_id) {
- if (!view_tree_)
- return;
+ const std::string name = l10n_util::GetStringUTF8(string_id);
+ SetViewName(view, name);
+}
- std::string name = l10n_util::GetStringUTF8(string_id);
- accessibility_event_router_->SetViewName(view, name);
- managed_views_.push_back(view);
+gfx::NativeView AccessibleViewHelper::GetNativeView(views::View* view) const {
+ if (view->GetClassName() == views::NativeViewHost::kViewClassName)
+ return static_cast<views::NativeViewHost*>(view)->native_view();
+
+ for (int i = 0; i < view->GetChildViewCount(); i++) {
+ gfx::NativeView native_view = GetNativeView(view->GetChildViewAt(i));
+ if (native_view)
+ return native_view;
+ }
+
+ return NULL;
}
diff --git a/chrome/browser/views/accessible_view_helper.h b/chrome/browser/views/accessible_view_helper.h
index 2f6f434..e3a0f93 100644
--- a/chrome/browser/views/accessible_view_helper.h
+++ b/chrome/browser/views/accessible_view_helper.h
@@ -13,6 +13,7 @@
#include "base/scoped_ptr.h"
#include "base/singleton.h"
#include "chrome/browser/accessibility_events.h"
+#include "gfx/native_widget_types.h"
#if defined(OS_LINUX)
#include "chrome/browser/gtk/accessible_widget_helper_gtk.h"
@@ -66,6 +67,9 @@ class AccessibleViewHelper {
void SetViewName(views::View* view, int string_id);
private:
+ // Returns a native view if the given view has a native view in it.
+ gfx::NativeView GetNativeView(views::View* view) const;
+
AccessibilityEventRouterViews* accessibility_event_router_;
Profile* profile_;
views::View* view_tree_;
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index f020871..622cb85a 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -877,6 +877,7 @@
'browser/chromeos/login/owner_key_utils_unittest.cc',
'browser/chromeos/login/owner_manager_unittest.cc',
'browser/chromeos/login/ownership_service_unittest.cc',
+ 'browser/chromeos/login/wizard_accessibility_handler_unittest.cc',
'browser/chromeos/login/signed_settings_unittest.cc',
'browser/chromeos/login/signed_settings_helper_unittest.cc',
'browser/chromeos/notifications/desktop_notifications_unittest.cc',