diff options
Diffstat (limited to 'chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util.js')
-rw-r--r-- | chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util.js | 428 |
1 files changed, 428 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util.js new file mode 100644 index 00000000000..4e2ce623171 --- /dev/null +++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util.js @@ -0,0 +1,428 @@ +// Copyright 2014 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. + +/** + * @fileoverview A utility class for general braille functionality. + */ + + +goog.provide('cvox.BrailleUtil'); + +goog.require('cvox.ChromeVox'); +goog.require('cvox.DomUtil'); +goog.require('cvox.Focuser'); +goog.require('cvox.NavBraille'); +goog.require('cvox.NodeStateUtil'); +goog.require('cvox.Spannable'); + + +/** + * Trimmable whitespace character that appears between consecutive items in + * braille. + * @const {string} + */ +cvox.BrailleUtil.ITEM_SEPARATOR = ' '; + + +/** + * Messages considered as containers in braille. + * Containers are distinguished from roles by their appearance higher up in the + * DOM tree of a selected node. + * This list should be very short. + * @type {!Array.<string>} + */ +cvox.BrailleUtil.CONTAINER = [ + 'tag_h1_brl', + 'tag_h2_brl', + 'tag_h3_brl', + 'tag_h4_brl', + 'tag_h5_brl', + 'tag_h6_brl' +]; + + +/** + * Maps a ChromeVox message id to a braille template. + * The template takes one-character specifiers: + * n: replaced with braille name. + * r: replaced with braille role. + * s: replaced with braille state. + * c: replaced with braille container role; this potentially returns whitespace, + * so place at the beginning or end of templates for trimming. + * v: replaced with braille value. + * @type {Object.<string, string>} + */ +cvox.BrailleUtil.TEMPLATE = { + 'base': 'c n v r s', + 'aria_role_alert': 'r: n', + 'aria_role_button': '[n]', + 'aria_role_textbox': 'n: v r', + 'input_type_button': '[n]', + 'input_type_checkbox': 'n (s)', + 'input_type_email': 'n: v r', + 'input_type_number': 'n: v r', + 'input_type_password': 'n: v r', + 'input_type_search': 'n: v r', + 'input_type_submit': '[n]', + 'input_type_text': 'n: v r', + 'input_type_tel': 'n: v r', + 'input_type_url': 'n: v r', + 'tag_button': '[n]', + 'tag_textarea': 'n: v r' +}; + + +/** + * Attached to the value region of a braille spannable. + * @param {number} offset The offset of the span into the value. + * @constructor + */ +cvox.BrailleUtil.ValueSpan = function(offset) { + /** + * The offset of the span into the value. + * @type {number} + */ + this.offset = offset; +}; + + +/** + * Creates a value span from a json serializable object. + * @param {!Object} obj The json serializable object to convert. + * @return {!cvox.BrailleUtil.ValueSpan} The value span. + */ +cvox.BrailleUtil.ValueSpan.fromJson = function(obj) { + return new cvox.BrailleUtil.ValueSpan(obj.offset); +}; + + +/** + * Converts this object to a json serializable object. + * @return {!Object} The JSON representation. + */ +cvox.BrailleUtil.ValueSpan.prototype.toJson = function() { + return this; +}; + + +cvox.Spannable.registerSerializableSpan( + cvox.BrailleUtil.ValueSpan, + 'cvox.BrailleUtil.ValueSpan', + cvox.BrailleUtil.ValueSpan.fromJson, + cvox.BrailleUtil.ValueSpan.prototype.toJson); + + +/** + * Attached to the selected text within a value. + * @constructor + */ +cvox.BrailleUtil.ValueSelectionSpan = function() { +}; + + +cvox.Spannable.registerStatelessSerializableSpan( + cvox.BrailleUtil.ValueSelectionSpan, + 'cvox.BrailleUtil.ValueSelectionSpan'); + + +/** + * Gets the braille name for a node. + * See DomUtil for a more precise definition of 'name'. + * Additionally, whitespace is trimmed. + * @param {Node} node The node. + * @return {string} The string representation. + */ +cvox.BrailleUtil.getName = function(node) { + if (!node) { + return ''; + } + return cvox.DomUtil.getName(node).trim(); +}; + + +/** + * Gets the braille role message id for a node. + * See DomUtil for a more precise definition of 'role'. + * @param {Node} node The node. + * @return {string} The string representation. + */ +cvox.BrailleUtil.getRoleMsg = function(node) { + if (!node) { + return ''; + } + var roleMsg = cvox.DomUtil.getRoleMsg(node, cvox.VERBOSITY_VERBOSE); + if (roleMsg) { + roleMsg = cvox.DomUtil.collapseWhitespace(roleMsg); + } + if (roleMsg && (roleMsg.length > 0)) { + if (cvox.ChromeVox.msgs.getMsg(roleMsg + '_brl')) { + roleMsg += '_brl'; + } + } + return roleMsg; +}; + + +/** + * Gets the braille role of a node. + * See DomUtil for a more precise definition of 'role'. + * @param {Node} node The node. + * @return {string} The string representation. + */ +cvox.BrailleUtil.getRole = function(node) { + if (!node) { + return ''; + } + var roleMsg = cvox.BrailleUtil.getRoleMsg(node); + return roleMsg ? cvox.ChromeVox.msgs.getMsg(roleMsg) : ''; +}; + + +/** + * Gets the braille state of a node. + * @param {Node} node The node. + * @return {string} The string representation. + */ +cvox.BrailleUtil.getState = function(node) { + if (!node) { + return ''; + } + return cvox.NodeStateUtil.expand( + cvox.DomUtil.getStateMsgs(node, true).map(function(state) { + // Check to see if a variant of the message with '_brl' exists, + // and use it if so. + // + // Note: many messages are templatized, and if we don't pass any + // argument to substitute, getMsg might throw an error if the + // resulting string is empty. To avoid this, we pass a dummy + // substitution string array here. + var dummySubs = ['dummy', 'dummy', 'dummy']; + if (cvox.ChromeVox.msgs.getMsg(state[0] + '_brl', dummySubs)) { + state[0] += '_brl'; + } + return state; + })); +}; + + +/** + * Gets the braille container role of a node. + * @param {Node} prev The previous node in navigation. + * @param {Node} node The node. + * @return {string} The string representation. + */ +cvox.BrailleUtil.getContainer = function(prev, node) { + if (!prev || !node) { + return ''; + } + var ancestors = cvox.DomUtil.getUniqueAncestors(prev, node); + for (var i = 0, container; container = ancestors[i]; i++) { + var msg = cvox.BrailleUtil.getRoleMsg(container); + if (msg && cvox.BrailleUtil.CONTAINER.indexOf(msg) != -1) { + return cvox.ChromeVox.msgs.getMsg(msg); + } + } + return ''; +}; + + +/** + * Gets the braille value of a node. A cvox.BrailleUtil.ValueSpan will be + * attached, along with (possibly) a cvox.BrailleUtil.ValueSelectionSpan. + * @param {Node} node The node. + * @return {!cvox.Spannable} The value spannable. + */ +cvox.BrailleUtil.getValue = function(node) { + if (!node) { + return new cvox.Spannable(); + } + var valueSpan = new cvox.BrailleUtil.ValueSpan(0 /* offset */); + if (cvox.DomUtil.isInputTypeText(node)) { + var value = node.value; + if (node.type === 'password') { + value = value.replace(/./g, '*'); + } + var spannable = new cvox.Spannable(value, valueSpan); + if (node === document.activeElement && + cvox.DomUtil.doesInputSupportSelection(node)) { + var selectionStart = cvox.BrailleUtil.clamp_( + node.selectionStart, 0, spannable.getLength()); + var selectionEnd = cvox.BrailleUtil.clamp_( + node.selectionEnd, 0, spannable.getLength()); + spannable.setSpan(new cvox.BrailleUtil.ValueSelectionSpan(), + Math.min(selectionStart, selectionEnd), + Math.max(selectionStart, selectionEnd)); + } + return spannable; + } else if (node instanceof HTMLTextAreaElement) { + var shadow = new cvox.EditableTextAreaShadow(); + shadow.update(node); + var lineIndex = shadow.getLineIndex(node.selectionEnd); + var lineStart = shadow.getLineStart(lineIndex); + var lineEnd = shadow.getLineEnd(lineIndex); + var lineText = node.value.substring(lineStart, lineEnd); + valueSpan.offset = lineStart; + var spannable = new cvox.Spannable(lineText, valueSpan); + if (node === document.activeElement) { + var selectionStart = cvox.BrailleUtil.clamp_( + node.selectionStart - lineStart, 0, spannable.getLength()); + var selectionEnd = cvox.BrailleUtil.clamp_( + node.selectionEnd - lineStart, 0, spannable.getLength()); + spannable.setSpan(new cvox.BrailleUtil.ValueSelectionSpan(), + Math.min(selectionStart, selectionEnd), + Math.max(selectionStart, selectionEnd)); + } + return spannable; + } else { + return new cvox.Spannable(cvox.DomUtil.getValue(node), valueSpan); + } +}; + + +/** + * Gets the templated representation of braille. + * @param {Node} prev The previous node (during navigation). + * @param {Node} node The node. + * @param {{name:(undefined|string), + * role:(undefined|string), + * roleMsg:(undefined|string), + * state:(undefined|string), + * container:(undefined|string), + * value:(undefined|cvox.Spannable)}|Object=} opt_override Override a + * specific property for the given node. + * @return {!cvox.Spannable} The string representation. + */ +cvox.BrailleUtil.getTemplated = function(prev, node, opt_override) { + opt_override = opt_override ? opt_override : {}; + var roleMsg = opt_override.roleMsg || + (node ? cvox.DomUtil.getRoleMsg(node, cvox.VERBOSITY_VERBOSE) : ''); + var role = opt_override.role; + if (!role && opt_override.roleMsg) { + role = cvox.ChromeVox.msgs.getMsg(opt_override.roleMsg + '_brl') || + cvox.ChromeVox.msgs.getMsg(opt_override.roleMsg); + } + role = role || cvox.BrailleUtil.getRole(node); + var template = cvox.BrailleUtil.TEMPLATE[roleMsg] || + cvox.BrailleUtil.TEMPLATE['base']; + + var templated = new cvox.Spannable(); + var mapChar = function(c) { + switch (c) { + case 'n': + return opt_override.name || cvox.BrailleUtil.getName(node); + case 'r': + return role; + case 's': + return opt_override.state || cvox.BrailleUtil.getState(node); + case 'c': + return opt_override.container || + cvox.BrailleUtil.getContainer(prev, node); + case 'v': + return opt_override.value || cvox.BrailleUtil.getValue(node); + default: + return c; + } + }; + for (var i = 0; i < template.length; i++) { + var component = mapChar(template[i]); + templated.append(component); + // Ignore the next whitespace separator if the current component is empty. + if (!component.toString() && template[i + 1] == ' ') { + i++; + } + } + return templated.trimRight(); +}; + + +/** + * Creates a braille value from a string and, optionally, a selection range. + * A cvox.BrailleUtil.ValueSpan will be + * attached, along with a cvox.BrailleUtil.ValueSelectionSpan if applicable. + * @param {string} text The text to display as the value. + * @param {number=} opt_selStart Selection start. + * @param {number=} opt_selEnd Selection end if different from selection start. + * @param {number=} opt_textOffset Start offset of text. + * @return {!cvox.Spannable} The value spannable. + */ +cvox.BrailleUtil.createValue = function(text, opt_selStart, opt_selEnd, + opt_textOffset) { + var spannable = new cvox.Spannable( + text, new cvox.BrailleUtil.ValueSpan(opt_textOffset || 0)); + if (goog.isDef(opt_selStart)) { + opt_selEnd = goog.isDef(opt_selEnd) ? opt_selEnd : opt_selStart; + // TODO(plundblad): This looses the distinction between the selection + // anchor (start) and focus (end). We should use that information to + // decide where to pan the braille display. + if (opt_selStart > opt_selEnd) { + var temp = opt_selStart; + opt_selStart = opt_selEnd; + opt_selEnd = temp; + } + + spannable.setSpan(new cvox.BrailleUtil.ValueSelectionSpan(), + opt_selStart, opt_selEnd); + } + return spannable; +}; + + +/** + * Activates a position in a nav braille. Moves the caret in text fields + * and simulates a mouse click on the node at the position. + * + * @param {!cvox.NavBraille} braille the nav braille representing the display + * content that was active when the user issued the key command. + * The annotations in the spannable are used to decide what + * node to activate and what part of the node value (if any) to + * move the caret to. + * @param {number=} opt_displayPosition position of the display that the user + * activated, relative to the start of braille. + */ +cvox.BrailleUtil.click = function(braille, opt_displayPosition) { + var handled = false; + var spans = braille.text.getSpans(opt_displayPosition || 0); + var node = spans.filter(function(n) { return n instanceof Node; })[0]; + if (node) { + if (goog.isDef(opt_displayPosition) && + (cvox.DomUtil.isInputTypeText(node) || + node instanceof HTMLTextAreaElement)) { + var valueSpan = spans.filter( + function(s) { + return s instanceof cvox.BrailleUtil.ValueSpan; + })[0]; + if (valueSpan) { + if (document.activeElement !== node) { + cvox.Focuser.setFocus(node); + } + var cursorPosition = opt_displayPosition - + braille.text.getSpanStart(valueSpan) + + valueSpan.offset; + cvox.ChromeVoxEventWatcher.setUpTextHandler(); + node.selectionStart = node.selectionEnd = cursorPosition; + cvox.ChromeVoxEventWatcher.handleTextChanged(true); + handled = true; + } + } + } + if (!handled) { + cvox.DomUtil.clickElem( + node || cvox.ChromeVox.navigationManager.getCurrentNode(), + false, false, false, true); + } +}; + + +/** + * Clamps a number so it is within the given boundaries. + * @param {number} number The number to clamp. + * @param {number} min The minimum value to return. + * @param {number} max The maximum value to return. + * @return {number} {@code number} if it is within the bounds, or the nearest + * number within the bounds otherwise. + * @private + */ +cvox.BrailleUtil.clamp_ = function(number, min, max) { + return Math.min(Math.max(number, min), max); +}; |