1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview A utility class for general braille functionality.
10 goog
.provide('cvox.BrailleUtil');
12 goog
.require('cvox.ChromeVox');
13 goog
.require('cvox.DomUtil');
14 goog
.require('cvox.EditableTextAreaShadow');
15 goog
.require('cvox.Focuser');
16 goog
.require('cvox.NavBraille');
17 goog
.require('cvox.NodeStateUtil');
18 goog
.require('cvox.Spannable');
19 goog
.require('cvox.ValueSelectionSpan');
20 goog
.require('cvox.ValueSpan');
24 * Trimmable whitespace character that appears between consecutive items in
28 cvox
.BrailleUtil
.ITEM_SEPARATOR
= ' ';
32 * Messages considered as containers in braille.
33 * Containers are distinguished from roles by their appearance higher up in the
34 * DOM tree of a selected node.
35 * This list should be very short.
36 * @type {!Array<string>}
38 cvox
.BrailleUtil
.CONTAINER
= [
49 * Maps a ChromeVox message id to a braille template.
50 * The template takes one-character specifiers:
51 * n: replaced with braille name.
52 * r: replaced with braille role.
53 * s: replaced with braille state.
54 * c: replaced with braille container role; this potentially returns whitespace,
55 * so place at the beginning or end of templates for trimming.
56 * v: replaced with braille value.
57 * @type {Object<string>}
59 cvox
.BrailleUtil
.TEMPLATE
= {
61 'aria_role_alert': 'r: n',
62 'aria_role_button': 'n r s',
63 'aria_role_checkbox': 'n r s',
64 'aria_role_menuitemcheckbox': 'n r s',
65 'aria_role_menuitemradio': 'n r s',
66 'aria_role_radio': 'n r s',
67 'aria_role_textbox': 'n: v r s',
68 'input_type_button': 'n r s',
69 'input_type_checkbox': 'n r s',
70 'input_type_email': 'n: v r s',
71 'input_type_number': 'n: v r s',
72 'input_type_password': 'n: v r s',
73 'input_type_radio': 'n r s',
74 'input_type_search': 'n: v r s',
75 'input_type_submit': 'n r s',
76 'input_type_text': 'n: v r s',
77 'input_type_tel': 'n: v r s',
78 'input_type_url': 'n: v r s',
79 'tag_button': 'n r s',
80 'tag_textarea': 'n: v r s'
85 * Gets the braille name for a node.
86 * See DomUtil for a more precise definition of 'name'.
87 * Additionally, whitespace is trimmed.
88 * @param {Node} node The node.
89 * @return {string} The string representation.
91 cvox
.BrailleUtil
.getName = function(node
) {
95 return cvox
.DomUtil
.getName(node
).trim();
100 * Gets the braille role message id for a node.
101 * See DomUtil for a more precise definition of 'role'.
102 * @param {Node} node The node.
103 * @return {string} The string representation.
105 cvox
.BrailleUtil
.getRoleMsg = function(node
) {
109 var roleMsg
= cvox
.DomUtil
.getRoleMsg(node
, cvox
.VERBOSITY_VERBOSE
);
111 roleMsg
= cvox
.DomUtil
.collapseWhitespace(roleMsg
);
113 if (roleMsg
&& (roleMsg
.length
> 0)) {
114 if (cvox
.ChromeVox
.msgs
.getMsg(roleMsg
+ '_brl')) {
123 * Transforms a {@code cvox.NodeState} list of state messages to the
124 * corresponding messages for braille and expands them into a localized
125 * string suitable for output on a braille display.
126 * @param {cvox.NodeState} stateMsgs The states to expand. The content of this
128 * @return {string} The string representation.
131 cvox
.BrailleUtil
.expandStateMsgs_ = function(stateMsgs
) {
132 stateMsgs
.forEach(function(state
) {
133 // Check to see if a variant of the message with '_brl' exists,
136 // Note: many messages are templatized, and if we don't pass any
137 // argument to substitute, getMsg might throw an error if the
138 // resulting string is empty. To avoid this, we pass a dummy
139 // substitution string array here.
140 var dummySubs
= ['dummy', 'dummy', 'dummy'];
141 if (cvox
.ChromeVox
.msgs
.getMsg(state
[0] + '_brl', dummySubs
)) {
145 return cvox
.NodeStateUtil
.expand(stateMsgs
);
150 * Gets the braille container role of a node.
151 * @param {Node} prev The previous node in navigation.
152 * @param {Node} node The node.
153 * @return {string} The string representation.
155 cvox
.BrailleUtil
.getContainer = function(prev
, node
) {
156 if (!prev
|| !node
) {
159 var ancestors
= cvox
.DomUtil
.getUniqueAncestors(prev
, node
);
160 for (var i
= 0, container
; container
= ancestors
[i
]; i
++) {
161 var msg
= cvox
.BrailleUtil
.getRoleMsg(container
);
162 if (msg
&& cvox
.BrailleUtil
.CONTAINER
.indexOf(msg
) != -1) {
163 return cvox
.ChromeVox
.msgs
.getMsg(msg
);
171 * Gets the braille value of a node. A {@code cvox.ValueSpan} will be
172 * attached, along with (possibly) a {@code cvox.ValueSelectionSpan}.
173 * @param {Node} node The node.
174 * @return {!cvox.Spannable} The value spannable.
176 cvox
.BrailleUtil
.getValue = function(node
) {
178 return new cvox
.Spannable();
180 var valueSpan
= new cvox
.ValueSpan(0 /* offset */);
181 if (cvox
.DomUtil
.isInputTypeText(node
)) {
182 var value
= node
.value
;
183 if (node
.type
=== 'password') {
184 value
= value
.replace(/./g
, '*');
186 var spannable
= new cvox
.Spannable(value
, valueSpan
);
187 if (node
=== document
.activeElement
&&
188 cvox
.DomUtil
.doesInputSupportSelection(node
)) {
189 var selectionStart
= cvox
.BrailleUtil
.clamp_(
190 node
.selectionStart
, 0, spannable
.getLength());
191 var selectionEnd
= cvox
.BrailleUtil
.clamp_(
192 node
.selectionEnd
, 0, spannable
.getLength());
193 spannable
.setSpan(new cvox
.ValueSelectionSpan(),
194 Math
.min(selectionStart
, selectionEnd
),
195 Math
.max(selectionStart
, selectionEnd
));
198 } else if (node
instanceof HTMLTextAreaElement
) {
199 var shadow
= new cvox
.EditableTextAreaShadow();
201 var lineIndex
= shadow
.getLineIndex(node
.selectionEnd
);
202 var lineStart
= shadow
.getLineStart(lineIndex
);
203 var lineEnd
= shadow
.getLineEnd(lineIndex
);
204 var lineText
= node
.value
.substring(lineStart
, lineEnd
);
205 valueSpan
.offset
= lineStart
;
206 var spannable
= new cvox
.Spannable(lineText
, valueSpan
);
207 if (node
=== document
.activeElement
) {
208 var selectionStart
= cvox
.BrailleUtil
.clamp_(
209 node
.selectionStart
- lineStart
, 0, spannable
.getLength());
210 var selectionEnd
= cvox
.BrailleUtil
.clamp_(
211 node
.selectionEnd
- lineStart
, 0, spannable
.getLength());
212 spannable
.setSpan(new cvox
.ValueSelectionSpan(),
213 Math
.min(selectionStart
, selectionEnd
),
214 Math
.max(selectionStart
, selectionEnd
));
218 return new cvox
.Spannable(cvox
.DomUtil
.getValue(node
), valueSpan
);
224 * Gets the templated representation of braille.
225 * @param {Node} prev The previous node (during navigation).
226 * @param {Node} node The node.
227 * @param {{name:(undefined|string),
228 * role:(undefined|string),
229 * roleMsg:(undefined|string),
230 * state:(undefined|string),
231 * container:(undefined|string),
232 * value:(undefined|cvox.Spannable)}|Object=} opt_override Override a
233 * specific property for the given node.
234 * @return {!cvox.Spannable} The string representation.
236 cvox
.BrailleUtil
.getTemplated = function(prev
, node
, opt_override
) {
237 opt_override
= opt_override
? opt_override
: {};
238 var roleMsg
= opt_override
.roleMsg
||
239 (node
? cvox
.DomUtil
.getRoleMsg(node
, cvox
.VERBOSITY_VERBOSE
) : '');
240 var template
= cvox
.BrailleUtil
.TEMPLATE
[roleMsg
] ||
241 cvox
.BrailleUtil
.TEMPLATE
['base'];
242 var state
= opt_override
.state
;
245 state
= cvox
.BrailleUtil
.expandStateMsgs_(
246 cvox
.DomUtil
.getStateMsgs(node
, true));
251 var role
= opt_override
.role
|| '';
252 if (!role
&& roleMsg
) {
253 role
= cvox
.ChromeVox
.msgs
.getMsg(roleMsg
+ '_brl') ||
254 cvox
.ChromeVox
.msgs
.getMsg(roleMsg
);
257 var templated
= new cvox
.Spannable();
258 var mapChar = function(c
) {
261 return opt_override
.name
|| cvox
.BrailleUtil
.getName(node
);
267 return opt_override
.container
||
268 cvox
.BrailleUtil
.getContainer(prev
, node
);
270 return opt_override
.value
|| cvox
.BrailleUtil
.getValue(node
);
275 for (var i
= 0; i
< template
.length
; i
++) {
276 var component
= mapChar(template
[i
]);
277 templated
.append(component
);
278 // Ignore the next whitespace separator if the current component is empty,
279 // unless the empty value has a selection, in which case the cursor
280 // should be placed on the empty space after the empty value.
281 if (!component
.toString() && template
[i
+ 1] == ' ' &&
282 (!(component
instanceof cvox
.Spannable
) ||
283 !/**@type {cvox.Spannable}*/(component
).getSpanInstanceOf(
284 cvox
.ValueSelectionSpan
))) {
288 return templated
.trimRight();
293 * Creates a braille value from a string and, optionally, a selection range.
294 * A {@code cvox.ValueSpan} will be attached, along with a
295 * {@code cvox.ValueSelectionSpan} if applicable.
296 * @param {string} text The text to display as the value.
297 * @param {number=} opt_selStart Selection start.
298 * @param {number=} opt_selEnd Selection end if different from selection start.
299 * @param {number=} opt_textOffset Start offset of text.
300 * @return {!cvox.Spannable} The value spannable.
302 cvox
.BrailleUtil
.createValue = function(text
, opt_selStart
, opt_selEnd
,
304 var spannable
= new cvox
.Spannable(
305 text
, new cvox
.ValueSpan(opt_textOffset
|| 0));
306 if (goog
.isDef(opt_selStart
)) {
307 opt_selEnd
= goog
.isDef(opt_selEnd
) ? opt_selEnd
: opt_selStart
;
308 // TODO(plundblad): This looses the distinction between the selection
309 // anchor (start) and focus (end). We should use that information to
310 // decide where to pan the braille display.
311 if (opt_selStart
> opt_selEnd
) {
312 var temp
= opt_selStart
;
313 opt_selStart
= opt_selEnd
;
317 spannable
.setSpan(new cvox
.ValueSelectionSpan(), opt_selStart
, opt_selEnd
);
324 * Activates a position in a nav braille. Moves the caret in text fields
325 * and simulates a mouse click on the node at the position.
327 * @param {!cvox.NavBraille} braille the nav braille representing the display
328 * content that was active when the user issued the key command.
329 * The annotations in the spannable are used to decide what
330 * node to activate and what part of the node value (if any) to
332 * @param {number=} opt_displayPosition position of the display that the user
333 * activated, relative to the start of braille.
335 cvox
.BrailleUtil
.click = function(braille
, opt_displayPosition
) {
337 var spans
= braille
.text
.getSpans(opt_displayPosition
|| 0);
338 var node
= spans
.filter(function(n
) { return n
instanceof Node
; })[0];
340 if (goog
.isDef(opt_displayPosition
) &&
341 (cvox
.DomUtil
.isInputTypeText(node
) ||
342 node
instanceof HTMLTextAreaElement
)) {
343 var valueSpan
= spans
.filter(
345 return s
instanceof cvox
.ValueSpan
;
348 if (document
.activeElement
!== node
) {
349 cvox
.Focuser
.setFocus(node
);
351 var cursorPosition
= opt_displayPosition
-
352 braille
.text
.getSpanStart(valueSpan
) +
354 cvox
.ChromeVoxEventWatcher
.setUpTextHandler();
355 node
.selectionStart
= node
.selectionEnd
= cursorPosition
;
356 cvox
.ChromeVoxEventWatcher
.handleTextChanged(true);
362 cvox
.DomUtil
.clickElem(
363 node
|| cvox
.ChromeVox
.navigationManager
.getCurrentNode(),
364 false, false, false, true);
370 * Clamps a number so it is within the given boundaries.
371 * @param {number} number The number to clamp.
372 * @param {number} min The minimum value to return.
373 * @param {number} max The maximum value to return.
374 * @return {number} {@code number} if it is within the bounds, or the nearest
375 * number within the bounds otherwise.
378 cvox
.BrailleUtil
.clamp_ = function(number
, min
, max
) {
379 return Math
.min(Math
.max(number
, min
), max
);