Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(564)

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js

Issue 2150623002: Refactor: Extract a KeyboardHandler and CommandHandler from Background (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix test. Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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.
4
5 /**
6 * @fileoverview ChromeVox commands.
7 */
8
9 goog.provide('CommandHandler');
10
11 goog.require('ChromeVoxState');
12 goog.require('Output');
13 goog.require('cvox.ChromeVoxBackground');
14
15 goog.scope(function() {
16 var AutomationNode = chrome.automation.AutomationNode;
17 var Dir = constants.Dir;
18 var EventType = chrome.automation.EventType;
19 var RoleType = chrome.automation.RoleType;
20
21 /**
22 * Handles ChromeVox Next commands.
23 * @param {string} command
24 * @return {boolean} True if the command should propagate.
25 */
26 CommandHandler.onCommand = function(command) {
27 // Check for loss of focus which results in us invalidating our current
28 // range. Note this call is synchronis.
29 chrome.automation.getFocus(function(focusedNode) {
30 var cur = ChromeVoxState.instance.currentRange;
31 if (cur && !cur.isValid()) {
32 ChromeVoxState.instance.setCurrentRange(
33 cursors.Range.fromNode(focusedNode));
34 }
35
36 if (!focusedNode)
37 ChromeVoxState.instance.setCurrentRange(null);
38 });
39
40 // These commands don't require a current range and work in all modes.
41 switch (command) {
42 case 'speakTimeAndDate':
43 chrome.automation.getDesktop(function(d) {
44 // First, try speaking the on-screen time.
45 var allTime = d.findAll({role: RoleType.time});
46 allTime.filter(function(t) { return t.root.role == RoleType.desktop; });
47
48 var timeString = '';
49 allTime.forEach(function(t) {
50 if (t.name) timeString = t.name;
51 });
52 if (timeString) {
53 cvox.ChromeVox.tts.speak(timeString, cvox.QueueMode.FLUSH);
54 } else {
55 // Fallback to the old way of speaking time.
56 var output = new Output();
57 var dateTime = new Date();
58 output
59 .withString(
60 dateTime.toLocaleTimeString() + ', ' +
61 dateTime.toLocaleDateString())
62 .go();
63 }
64 });
65 return false;
66 case 'showOptionsPage':
67 chrome.runtime.openOptionsPage();
68 break;
69 case 'toggleChromeVox':
70 if (cvox.ChromeVox.isChromeOS)
71 return false;
72
73 cvox.ChromeVox.isActive = !cvox.ChromeVox.isActive;
74 if (!cvox.ChromeVox.isActive) {
75 var msg = Msgs.getMsg('chromevox_inactive');
76 cvox.ChromeVox.tts.speak(msg, cvox.QueueMode.FLUSH);
77 return false;
78 }
79 break;
80 case 'toggleStickyMode':
81 cvox.ChromeVoxBackground.setPref(
82 'sticky', !cvox.ChromeVox.isStickyPrefOn, true);
83
84 if (cvox.ChromeVox.isStickyPrefOn)
85 chrome.accessibilityPrivate.setKeyboardListener(true, true);
86 else
87 chrome.accessibilityPrivate.setKeyboardListener(true, false);
88 return false;
89 case 'passThroughMode':
90 cvox.ChromeVox.passThroughMode = true;
91 cvox.ChromeVox.tts.speak(
92 Msgs.getMsg('pass_through_key'), cvox.QueueMode.QUEUE);
93 return true;
94 case 'showKbExplorerPage':
95 var explorerPage = {url: 'chromevox/background/kbexplorer.html'};
96 chrome.tabs.create(explorerPage);
97 break;
98 case 'decreaseTtsRate':
99 CommandHandler.increaseOrDecreaseSpeechProperty_(
100 cvox.AbstractTts.RATE, false);
101 return false;
102 case 'increaseTtsRate':
103 CommandHandler.increaseOrDecreaseSpeechProperty_(
104 cvox.AbstractTts.RATE, true);
105 return false;
106 case 'decreaseTtsPitch':
107 CommandHandler.increaseOrDecreaseSpeechProperty_(
108 cvox.AbstractTts.PITCH, false);
109 return false;
110 case 'increaseTtsPitch':
111 CommandHandler.increaseOrDecreaseSpeechProperty_(
112 cvox.AbstractTts.PITCH, true);
113 return false;
114 case 'decreaseTtsVolume':
115 CommandHandler.increaseOrDecreaseSpeechProperty_(
116 cvox.AbstractTts.VOLUME, false);
117 return false;
118 case 'increaseTtsVolume':
119 CommandHandler.increaseOrDecreaseSpeechProperty_(
120 cvox.AbstractTts.VOLUME, true);
121 return false;
122 case 'stopSpeech':
123 cvox.ChromeVox.tts.stop();
124 ChromeVoxState.isReadingContinuously = false;
125 return false;
126 case 'toggleEarcons':
127 cvox.AbstractEarcons.enabled = !cvox.AbstractEarcons.enabled;
128 var announce = cvox.AbstractEarcons.enabled ? Msgs.getMsg('earcons_on') :
129 Msgs.getMsg('earcons_off');
130 cvox.ChromeVox.tts.speak(
131 announce, cvox.QueueMode.FLUSH,
132 cvox.AbstractTts.PERSONALITY_ANNOTATION);
133 return false;
134 case 'cycleTypingEcho':
135 cvox.ChromeVox.typingEcho =
136 cvox.TypingEcho.cycle(cvox.ChromeVox.typingEcho);
137 var announce = '';
138 switch (cvox.ChromeVox.typingEcho) {
139 case cvox.TypingEcho.CHARACTER:
140 announce = Msgs.getMsg('character_echo');
141 break;
142 case cvox.TypingEcho.WORD:
143 announce = Msgs.getMsg('word_echo');
144 break;
145 case cvox.TypingEcho.CHARACTER_AND_WORD:
146 announce = Msgs.getMsg('character_and_word_echo');
147 break;
148 case cvox.TypingEcho.NONE:
149 announce = Msgs.getMsg('none_echo');
150 break;
151 }
152 cvox.ChromeVox.tts.speak(
153 announce, cvox.QueueMode.FLUSH,
154 cvox.AbstractTts.PERSONALITY_ANNOTATION);
155 return false;
156 case 'cyclePunctuationEcho':
157 cvox.ChromeVox.tts.speak(
158 Msgs.getMsg(ChromeVoxState.backgroundTts.cyclePunctuationEcho()),
159 cvox.QueueMode.FLUSH);
160 return false;
161 case 'reportIssue':
162 var url = Background.ISSUE_URL;
163 var description = {};
164 description['Mode'] = ChromeVoxState.instance.mode;
165 description['Version'] = chrome.app.getDetails().version;
166 description['Reproduction Steps'] = '%0a1.%0a2.%0a3.';
167 for (var key in description)
168 url += key + ':%20' + description[key] + '%0a';
169 chrome.tabs.create({url: url});
170 return false;
171 case 'toggleBrailleCaptions':
172 cvox.BrailleCaptionsBackground.setActive(
173 !cvox.BrailleCaptionsBackground.isEnabled());
174 return false;
175 case 'toggleChromeVoxVersion':
176 if (!ChromeVoxState.instance.toggleNext())
177 return false;
178 if (ChromeVoxState.instance.currentRange) {
179 ChromeVoxState.instance.navigateToRange(
180 ChromeVoxState.instance.currentRange);
181 }
182 break;
183 case 'showNextUpdatePage':
184 (new PanelCommand(PanelCommandType.TUTORIAL)).send();
185 return false;
186 default:
187 break;
188 }
189
190 // Require a current range.
191 if (!ChromeVoxState.instance.currentRange_)
192 return true;
193
194 // Next/compat commands hereafter.
195 if (ChromeVoxState.instance.mode == ChromeVoxMode.CLASSIC) return true;
196
197 var current = ChromeVoxState.instance.currentRange_;
198 var dir = Dir.FORWARD;
199 var pred = null;
200 var predErrorMsg = undefined;
201 var speechProps = {};
202 switch (command) {
203 case 'nextCharacter':
204 speechProps['phoneticCharacters'] = true;
205 current = current.move(cursors.Unit.CHARACTER, Dir.FORWARD);
206 break;
207 case 'previousCharacter':
208 speechProps['phoneticCharacters'] = true;
209 current = current.move(cursors.Unit.CHARACTER, Dir.BACKWARD);
210 break;
211 case 'nextWord':
212 current = current.move(cursors.Unit.WORD, Dir.FORWARD);
213 break;
214 case 'previousWord':
215 current = current.move(cursors.Unit.WORD, Dir.BACKWARD);
216 break;
217 case 'forward':
218 case 'nextLine':
219 current = current.move(cursors.Unit.LINE, Dir.FORWARD);
220 break;
221 case 'backward':
222 case 'previousLine':
223 current = current.move(cursors.Unit.LINE, Dir.BACKWARD);
224 break;
225 case 'nextButton':
226 dir = Dir.FORWARD;
227 pred = AutomationPredicate.button;
228 predErrorMsg = 'no_next_button';
229 break;
230 case 'previousButton':
231 dir = Dir.BACKWARD;
232 pred = AutomationPredicate.button;
233 predErrorMsg = 'no_previous_button';
234 break;
235 case 'nextCheckbox':
236 dir = Dir.FORWARD;
237 pred = AutomationPredicate.checkBox;
238 predErrorMsg = 'no_next_checkbox';
239 break;
240 case 'previousCheckbox':
241 dir = Dir.BACKWARD;
242 pred = AutomationPredicate.checkBox;
243 predErrorMsg = 'no_previous_checkbox';
244 break;
245 case 'nextComboBox':
246 dir = Dir.FORWARD;
247 pred = AutomationPredicate.comboBox;
248 predErrorMsg = 'no_next_combo_box';
249 break;
250 case 'previousComboBox':
251 dir = Dir.BACKWARD;
252 pred = AutomationPredicate.comboBox;
253 predErrorMsg = 'no_previous_combo_box';
254 break;
255 case 'nextEditText':
256 dir = Dir.FORWARD;
257 pred = AutomationPredicate.editText;
258 predErrorMsg = 'no_next_edit_text';
259 break;
260 case 'previousEditText':
261 dir = Dir.BACKWARD;
262 pred = AutomationPredicate.editText;
263 predErrorMsg = 'no_previous_edit_text';
264 break;
265 case 'nextFormField':
266 dir = Dir.FORWARD;
267 pred = AutomationPredicate.formField;
268 predErrorMsg = 'no_next_form_field';
269 break;
270 case 'previousFormField':
271 dir = Dir.BACKWARD;
272 pred = AutomationPredicate.formField;
273 predErrorMsg = 'no_previous_form_field';
274 break;
275 case 'nextHeading':
276 dir = Dir.FORWARD;
277 pred = AutomationPredicate.heading;
278 predErrorMsg = 'no_next_heading';
279 break;
280 case 'previousHeading':
281 dir = Dir.BACKWARD;
282 pred = AutomationPredicate.heading;
283 predErrorMsg = 'no_previous_heading';
284 break;
285 case 'nextLink':
286 dir = Dir.FORWARD;
287 pred = AutomationPredicate.link;
288 predErrorMsg = 'no_next_link';
289 break;
290 case 'previousLink':
291 dir = Dir.BACKWARD;
292 pred = AutomationPredicate.link;
293 predErrorMsg = 'no_previous_link';
294 break;
295 case 'nextTable':
296 dir = Dir.FORWARD;
297 pred = AutomationPredicate.table;
298 predErrorMsg = 'no_next_table';
299 break;
300 case 'previousTable':
301 dir = Dir.BACKWARD;
302 pred = AutomationPredicate.table;
303 predErrorMsg = 'no_previous_table';
304 break;
305 case 'nextVisitedLink':
306 dir = Dir.FORWARD;
307 pred = AutomationPredicate.visitedLink;
308 predErrorMsg = 'no_next_visited_link';
309 break;
310 case 'previousVisitedLink':
311 dir = Dir.BACKWARD;
312 pred = AutomationPredicate.visitedLink;
313 predErrorMsg = 'no_previous_visited_link';
314 break;
315 case 'right':
316 case 'nextObject':
317 current = current.move(cursors.Unit.DOM_NODE, Dir.FORWARD);
318 break;
319 case 'left':
320 case 'previousObject':
321 current = current.move(cursors.Unit.DOM_NODE, Dir.BACKWARD);
322 break;
323 case 'jumpToTop':
324 var node = AutomationUtil.findNodePost(
325 current.start.node.root, Dir.FORWARD, AutomationPredicate.leaf);
326 if (node)
327 current = cursors.Range.fromNode(node);
328 break;
329 case 'jumpToBottom':
330 var node = AutomationUtil.findNodePost(
331 current.start.node.root, Dir.BACKWARD, AutomationPredicate.leaf);
332 if (node)
333 current = cursors.Range.fromNode(node);
334 break;
335 case 'forceClickOnCurrentItem':
336 if (ChromeVoxState.instance.currentRange_) {
337 var actionNode = ChromeVoxState.instance.currentRange_.start.node;
338 if (actionNode.role == RoleType.inlineTextBox)
339 actionNode = actionNode.parent;
340 actionNode.doDefault();
341 }
342 // Skip all other processing; if focus changes, we should get an event
343 // for that.
344 return false;
345 case 'readFromHere':
346 ChromeVoxState.isReadingContinuously = true;
347 var continueReading = function() {
348 if (!ChromeVoxState.isReadingContinuously ||
349 !ChromeVoxState.instance.currentRange_)
350 return;
351
352 var prevRange = ChromeVoxState.instance.currentRange_;
353 var newRange =
354 ChromeVoxState.instance.currentRange_.move(
355 cursors.Unit.DOM_NODE, Dir.FORWARD);
356
357 // Stop if we've wrapped back to the document.
358 var maybeDoc = newRange.start.node;
359 if (maybeDoc.role == RoleType.rootWebArea &&
360 maybeDoc.parent.root.role == RoleType.desktop) {
361 ChromeVoxState.isReadingContinuously = false;
362 return;
363 }
364
365 ChromeVoxState.instance.setCurrentRange(newRange);
366
367 new Output()
368 .withRichSpeechAndBraille(ChromeVoxState.instance.currentRange_,
369 prevRange,
370 Output.EventType.NAVIGATE)
371 .onSpeechEnd(continueReading)
372 .go();
373 }.bind(this);
374
375 new Output()
376 .withRichSpeechAndBraille(ChromeVoxState.instance.currentRange_,
377 null,
378 Output.EventType.NAVIGATE)
379 .onSpeechEnd(continueReading)
380 .go();
381
382 return false;
383 case 'contextMenu':
384 if (ChromeVoxState.instance.currentRange_) {
385 var actionNode = ChromeVoxState.instance.currentRange_.start.node;
386 if (actionNode.role == RoleType.inlineTextBox)
387 actionNode = actionNode.parent;
388 actionNode.showContextMenu();
389 return false;
390 }
391 break;
392 case 'toggleKeyboardHelp':
393 ChromeVoxState.instance.startExcursion();
394 (new PanelCommand(PanelCommandType.OPEN_MENUS)).send();
395 return false;
396 case 'showHeadingsList':
397 ChromeVoxState.instance.startExcursion();
398 (new PanelCommand(PanelCommandType.OPEN_MENUS, 'role_heading')).send();
399 return false;
400 case 'showFormsList':
401 ChromeVoxState.instance.startExcursion();
402 (new PanelCommand(PanelCommandType.OPEN_MENUS, 'role_form')).send();
403 return false;
404 case 'showLandmarksList':
405 ChromeVoxState.instance.startExcursion();
406 (new PanelCommand(PanelCommandType.OPEN_MENUS, 'role_landmark')).send();
407 return false;
408 case 'showLinksList':
409 ChromeVoxState.instance.startExcursion();
410 (new PanelCommand(PanelCommandType.OPEN_MENUS, 'role_link')).send();
411 return false;
412 case 'showTablesList':
413 ChromeVoxState.instance.startExcursion();
414 (new PanelCommand(PanelCommandType.OPEN_MENUS, 'table_strategy')).send();
415 return false;
416 case 'toggleSearchWidget':
417 (new PanelCommand(PanelCommandType.SEARCH)).send();
418 return false;
419 case 'readCurrentTitle':
420 var target = ChromeVoxState.instance.currentRange_.start.node;
421 var output = new Output();
422
423 if (target.root.role == RoleType.rootWebArea) {
424 // Web.
425 target = target.root;
426 output.withString(target.name || target.docUrl);
427 } else {
428 // Views.
429 while (target.role != RoleType.window) target = target.parent;
430 if (target)
431 output.withString(target.name || '');
432 }
433 output.go();
434 return false;
435 case 'readCurrentURL':
436 var output = new Output();
437 var target = ChromeVoxState.instance.currentRange_.start.node.root;
438 output.withString(target.docUrl || '').go();
439 return false;
440 case 'copy':
441 var textarea = document.createElement('textarea');
442 document.body.appendChild(textarea);
443 textarea.focus();
444 document.execCommand('paste');
445 var clipboardContent = textarea.value;
446 textarea.remove();
447 cvox.ChromeVox.tts.speak(
448 Msgs.getMsg('copy', [clipboardContent]), cvox.QueueMode.FLUSH);
449 ChromeVoxState.instance.pageSel_ = null;
450 return true;
451 case 'toggleSelection':
452 if (!ChromeVoxState.instance.pageSel_) {
453 ChromeVoxState.instance.pageSel_ = ChromeVoxState.instance.currentRange;
454 } else {
455 var root = ChromeVoxState.instance.currentRange_.start.node.root;
456 if (root && root.anchorObject && root.focusObject) {
457 var sel = new cursors.Range(
458 new cursors.Cursor(root.anchorObject, root.anchorOffset),
459 new cursors.Cursor(root.focusObject, root.focusOffset));
460 var o = new Output()
461 .format('@end_selection')
462 .withSpeechAndBraille(sel, sel, Output.EventType.NAVIGATE)
463 .go();
464 }
465 ChromeVoxState.instance.pageSel_ = null;
466 return false;
467 }
468 break;
469 default:
470 return true;
471 }
472
473 if (pred) {
474 var bound = current.getBound(dir).node;
475 if (bound) {
476 var node = AutomationUtil.findNextNode(
477 bound, dir, pred, {skipInitialAncestry: true});
478
479 if (node) {
480 node = AutomationUtil.findNodePre(
481 node, Dir.FORWARD, AutomationPredicate.object) ||
482 node;
483 }
484
485 if (node) {
486 current = cursors.Range.fromNode(node);
487 } else {
488 if (predErrorMsg) {
489 cvox.ChromeVox.tts.speak(
490 Msgs.getMsg(predErrorMsg), cvox.QueueMode.FLUSH);
491 }
492 return false;
493 }
494 }
495 }
496
497 if (current)
498 ChromeVoxState.instance.navigateToRange(current, undefined, speechProps);
499
500 return false;
501 };
502
503 /**
504 * React to mode changes.
505 * @param {ChromeVoxMode} newMode
506 * @param {ChromeVoxMode?} oldMode
507 */
508 CommandHandler.onModeChanged = function(newMode, oldMode) {
509 // Previously uninitialized.
510 if (!oldMode)
511 cvox.ChromeVoxKbHandler.commandHandler = CommandHandler.onCommand;
512
513 var hasListener =
514 chrome.commands.onCommand.hasListener(CommandHandler.onCommand);
515 if (newMode == ChromeVoxMode.CLASSIC && hasListener)
516 chrome.commands.onCommand.removeListener(CommandHandler.onCommand);
517 else if (newMode == ChromeVoxMode.CLASSIC && !hasListener)
518 chrome.commands.onCommand.addListener(CommandHandler.onCommand);
519 };
520
521 /**
522 * Increase or decrease a speech property and make an announcement.
523 * @param {string} propertyName The name of the property to change.
524 * @param {boolean} increase If true, increases the property value by one
525 * step size, otherwise decreases.
526 * @private
527 */
528 CommandHandler.increaseOrDecreaseSpeechProperty_ =
529 function(propertyName, increase) {
530 cvox.ChromeVox.tts.increaseOrDecreaseProperty(propertyName, increase);
531 var announcement;
532 var valueAsPercent = Math.round(
533 cvox.ChromeVox.tts.propertyToPercentage(propertyName) * 100);
534 switch (propertyName) {
535 case cvox.AbstractTts.RATE:
536 announcement = Msgs.getMsg('announce_rate', [valueAsPercent]);
537 break;
538 case cvox.AbstractTts.PITCH:
539 announcement = Msgs.getMsg('announce_pitch', [valueAsPercent]);
540 break;
541 case cvox.AbstractTts.VOLUME:
542 announcement = Msgs.getMsg('announce_volume', [valueAsPercent]);
543 break;
544 }
545 if (announcement) {
546 cvox.ChromeVox.tts.speak(
547 announcement, cvox.QueueMode.FLUSH,
548 cvox.AbstractTts.PERSONALITY_ANNOTATION);
549 }
550 };
551
552 }); // goog.scope
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698