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

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

Issue 1457683009: Complete live region support in ChromeVox Next. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addressed last feedback Created 5 years 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
1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview The entry point for all ChromeVox2 related code for the 6 * @fileoverview The entry point for all ChromeVox2 related code for the
7 * background page. 7 * background page.
8 */ 8 */
9 9
10 goog.provide('Background'); 10 goog.provide('Background');
11 goog.provide('ChromeVoxMode');
12 goog.provide('global'); 11 goog.provide('global');
13 12
14 goog.require('AutomationPredicate'); 13 goog.require('AutomationPredicate');
15 goog.require('AutomationUtil'); 14 goog.require('AutomationUtil');
15 goog.require('ChromeVoxState');
16 goog.require('LiveRegions');
16 goog.require('NextEarcons'); 17 goog.require('NextEarcons');
17 goog.require('Output'); 18 goog.require('Output');
18 goog.require('Output.EventType'); 19 goog.require('Output.EventType');
19 goog.require('cursors.Cursor'); 20 goog.require('cursors.Cursor');
20 goog.require('cvox.BrailleKeyCommand'); 21 goog.require('cvox.BrailleKeyCommand');
21 goog.require('cvox.ChromeVoxEditableTextBase'); 22 goog.require('cvox.ChromeVoxEditableTextBase');
22 goog.require('cvox.ChromeVoxKbHandler'); 23 goog.require('cvox.ChromeVoxKbHandler');
23 goog.require('cvox.ClassicEarcons'); 24 goog.require('cvox.ClassicEarcons');
24 goog.require('cvox.ExtensionBridge'); 25 goog.require('cvox.ExtensionBridge');
25 goog.require('cvox.NavBraille'); 26 goog.require('cvox.NavBraille');
26 27
27 goog.scope(function() { 28 goog.scope(function() {
28 var AutomationNode = chrome.automation.AutomationNode; 29 var AutomationNode = chrome.automation.AutomationNode;
29 var Dir = AutomationUtil.Dir; 30 var Dir = AutomationUtil.Dir;
30 var EventType = chrome.automation.EventType; 31 var EventType = chrome.automation.EventType;
31 var RoleType = chrome.automation.RoleType; 32 var RoleType = chrome.automation.RoleType;
32 33
33 /** 34 /**
34 * All possible modes ChromeVox can run.
35 * @enum {string}
36 */
37 ChromeVoxMode = {
38 CLASSIC: 'classic',
39 COMPAT: 'compat',
40 NEXT: 'next',
41 FORCE_NEXT: 'force_next'
42 };
43
44 /**
45 * ChromeVox2 background page. 35 * ChromeVox2 background page.
46 * @constructor 36 * @constructor
37 * @extends {ChromeVoxState}
47 */ 38 */
48 Background = function() { 39 Background = function() {
40 ChromeVoxState.call(this);
41
49 /** 42 /**
50 * A list of site substring patterns to use with ChromeVox next. Keep these 43 * A list of site substring patterns to use with ChromeVox next. Keep these
51 * strings relatively specific. 44 * strings relatively specific.
52 * @type {!Array<string>} 45 * @type {!Array<string>}
53 * @private 46 * @private
54 */ 47 */
55 this.whitelist_ = ['chromevox_next_test']; 48 this.whitelist_ = ['chromevox_next_test'];
56 49
57 /** 50 /**
58 * Regular expression for blacklisting classic. 51 * Regular expression for blacklisting classic.
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
120 }); 113 });
121 114
122 cvox.ExtensionBridge.addMessageListener(this.onMessage_); 115 cvox.ExtensionBridge.addMessageListener(this.onMessage_);
123 116
124 document.addEventListener('keydown', this.onKeyDown.bind(this), true); 117 document.addEventListener('keydown', this.onKeyDown.bind(this), true);
125 cvox.ChromeVoxKbHandler.commandHandler = this.onGotCommand.bind(this); 118 cvox.ChromeVoxKbHandler.commandHandler = this.onGotCommand.bind(this);
126 119
127 // Classic keymap. 120 // Classic keymap.
128 cvox.ChromeVoxKbHandler.handlerKeyMap = cvox.KeyMap.fromDefaults(); 121 cvox.ChromeVoxKbHandler.handlerKeyMap = cvox.KeyMap.fromDefaults();
129 122
130 chrome.automation.addTreeChangeObserver(this.onTreeChange); 123 // Live region handler.
124 this.liveRegions_ = new LiveRegions(this);
131 }; 125 };
132 126
133 Background.prototype = { 127 Background.prototype = {
134 /** Forces ChromeVox Next to be active for all tabs. */ 128 __proto__: ChromeVoxState.prototype,
135 forceChromeVoxNextActive: function() {
136 this.setChromeVoxMode(ChromeVoxMode.FORCE_NEXT);
137 },
138 129
139 /** @type {ChromeVoxMode} */ 130 /**
140 get mode() { 131 * @override
132 */
133 getMode: function() {
141 return this.mode_; 134 return this.mode_;
142 }, 135 },
143 136
144 /** @type {cursors.Range} */ 137 /**
145 get currentRange() { 138 * @override
139 */
140 setMode: function(mode, opt_injectClassic) {
141 // Switching key maps potentially affects the key codes that involve
142 // sequencing. Without resetting this list, potentially stale key codes
143 // remain. The key codes themselves get pushed in
144 // cvox.KeySequence.deserialize which gets called by cvox.KeyMap.
145 cvox.ChromeVox.sequenceSwitchKeyCodes = [];
146 if (mode === ChromeVoxMode.CLASSIC || mode === ChromeVoxMode.COMPAT)
147 cvox.ChromeVoxKbHandler.handlerKeyMap = cvox.KeyMap.fromDefaults();
148 else
149 cvox.ChromeVoxKbHandler.handlerKeyMap = cvox.KeyMap.fromNext();
150
151 if (mode == ChromeVoxMode.CLASSIC) {
152 if (chrome.commands &&
153 chrome.commands.onCommand.hasListener(this.onGotCommand))
154 chrome.commands.onCommand.removeListener(this.onGotCommand);
155 } else {
156 if (chrome.commands &&
157 !chrome.commands.onCommand.hasListener(this.onGotCommand))
158 chrome.commands.onCommand.addListener(this.onGotCommand);
159 }
160
161 chrome.tabs.query({active: true}, function(tabs) {
162 if (mode === ChromeVoxMode.CLASSIC) {
163 // Generally, we don't want to inject classic content scripts as it is
164 // done by the extension system at document load. The exception is when
165 // we toggle classic on manually as part of a user command.
166 if (opt_injectClassic)
167 cvox.ChromeVox.injectChromeVoxIntoTabs(tabs);
168 } else {
169 // When in compat mode, if the focus is within the desktop tree proper,
170 // then do not disable content scripts.
171 if (this.currentRange_ &&
172 this.currentRange_.start.node.root.role == RoleType.desktop)
173 return;
174
175 this.disableClassicChromeVox_();
176 }
177 }.bind(this));
178
179 // If switching out of a ChromeVox Next mode, make sure we cancel
180 // the progress loading sound just in case.
181 if ((this.mode_ === ChromeVoxMode.NEXT ||
182 this.mode_ === ChromeVoxMode.FORCE_NEXT) &&
183 this.mode_ != mode) {
184 cvox.ChromeVox.earcons.cancelEarcon(cvox.Earcon.PAGE_START_LOADING);
185 }
186
187 this.mode_ = mode;
188 },
189
190 /**
191 * @override
192 */
193 refreshMode: function(url) {
194 var mode = this.mode_;
195 if (mode != ChromeVoxMode.FORCE_NEXT) {
196 if (this.isWhitelistedForNext_(url))
197 mode = ChromeVoxMode.NEXT;
198 else if (this.isBlacklistedForClassic_(url))
199 mode = ChromeVoxMode.COMPAT;
200 else
201 mode = ChromeVoxMode.CLASSIC;
202 }
203
204 this.setMode(mode);
205 },
206
207 /**
208 * @override
209 */
210 getCurrentRange: function() {
146 return this.currentRange_; 211 return this.currentRange_;
147 }, 212 },
148 213
149 set currentRange(value) { 214 /**
150 if (!value) 215 * @override
216 */
217 setCurrentRange: function(newRange) {
218 if (!newRange)
151 return; 219 return;
152 220
153 this.currentRange_ = value; 221 this.currentRange_ = newRange;
154 222
155 if (this.currentRange_) 223 if (this.currentRange_)
156 this.currentRange_.start.node.makeVisible(); 224 this.currentRange_.start.node.makeVisible();
157 }, 225 },
158 226
227 /** Forces ChromeVox Next to be active for all tabs. */
228 forceChromeVoxNextActive: function() {
229 this.setMode(ChromeVoxMode.FORCE_NEXT);
230 },
231
159 /** 232 /**
160 * Handles ChromeVox Next commands. 233 * Handles ChromeVox Next commands.
161 * @param {string} command 234 * @param {string} command
162 * @param {boolean=} opt_bypassModeCheck Always tries to execute the command 235 * @param {boolean=} opt_bypassModeCheck Always tries to execute the command
163 * regardless of mode. 236 * regardless of mode.
164 * @return {boolean} True if the command should propagate. 237 * @return {boolean} True if the command should propagate.
165 */ 238 */
166 onGotCommand: function(command, opt_bypassModeCheck) { 239 onGotCommand: function(command, opt_bypassModeCheck) {
167 if (!this.currentRange_) 240 if (!this.currentRange_)
168 return true; 241 return true;
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after
324 global.isReadingContinuously = true; 397 global.isReadingContinuously = true;
325 var continueReading = function(prevRange) { 398 var continueReading = function(prevRange) {
326 if (!global.isReadingContinuously || !this.currentRange_) 399 if (!global.isReadingContinuously || !this.currentRange_)
327 return; 400 return;
328 401
329 new Output().withSpeechAndBraille( 402 new Output().withSpeechAndBraille(
330 this.currentRange_, prevRange, Output.EventType.NAVIGATE) 403 this.currentRange_, prevRange, Output.EventType.NAVIGATE)
331 .onSpeechEnd(function() { continueReading(prevRange); }) 404 .onSpeechEnd(function() { continueReading(prevRange); })
332 .go(); 405 .go();
333 prevRange = this.currentRange_; 406 prevRange = this.currentRange_;
334 this.currentRange = 407 this.setCurrentRange(
335 this.currentRange.move(cursors.Unit.NODE, Dir.FORWARD); 408 this.currentRange_.move(cursors.Unit.NODE, Dir.FORWARD));
336 409
337 if (!this.currentRange_ || this.currentRange_.equals(prevRange)) 410 if (!this.currentRange_ || this.currentRange_.equals(prevRange))
338 global.isReadingContinuously = false; 411 global.isReadingContinuously = false;
339 }.bind(this); 412 }.bind(this);
340 413
341 continueReading(null); 414 continueReading(null);
342 return false; 415 return false;
343 case 'showContextMenu': 416 case 'showContextMenu':
344 if (this.currentRange_) { 417 if (this.currentRange_) {
345 var actionNode = this.currentRange_.start.node; 418 var actionNode = this.currentRange_.start.node;
(...skipping 19 matching lines...) Expand all
365 break; 438 break;
366 case 'toggleChromeVoxVersion': 439 case 'toggleChromeVoxVersion':
367 var newMode; 440 var newMode;
368 if (this.mode_ == ChromeVoxMode.FORCE_NEXT) { 441 if (this.mode_ == ChromeVoxMode.FORCE_NEXT) {
369 var inViews = 442 var inViews =
370 this.currentRange_.start.node.root.role == RoleType.desktop; 443 this.currentRange_.start.node.root.role == RoleType.desktop;
371 newMode = inViews ? ChromeVoxMode.COMPAT : ChromeVoxMode.CLASSIC; 444 newMode = inViews ? ChromeVoxMode.COMPAT : ChromeVoxMode.CLASSIC;
372 } else { 445 } else {
373 newMode = ChromeVoxMode.FORCE_NEXT; 446 newMode = ChromeVoxMode.FORCE_NEXT;
374 } 447 }
375 this.setChromeVoxMode(newMode, true); 448 this.setMode(newMode, true);
376 449
377 var isClassic = 450 var isClassic =
378 newMode == ChromeVoxMode.CLASSIC || newMode == ChromeVoxMode.COMPAT; 451 newMode == ChromeVoxMode.CLASSIC || newMode == ChromeVoxMode.COMPAT;
379 452
380 // Leaving unlocalized as 'next' isn't an official name. 453 // Leaving unlocalized as 'next' isn't an official name.
381 cvox.ChromeVox.tts.speak(isClassic ? 454 cvox.ChromeVox.tts.speak(isClassic ?
382 'classic' : 'next', cvox.QueueMode.FLUSH, {doNotInterrupt: true}); 455 'classic' : 'next', cvox.QueueMode.FLUSH, {doNotInterrupt: true});
383 break; 456 break;
384 default: 457 default:
385 return true; 458 return true;
(...skipping 15 matching lines...) Expand all
401 } 474 }
402 475
403 if (current) { 476 if (current) {
404 // TODO(dtseng): Figure out what it means to focus a range. 477 // TODO(dtseng): Figure out what it means to focus a range.
405 var actionNode = current.start.node; 478 var actionNode = current.start.node;
406 if (actionNode.role == RoleType.inlineTextBox) 479 if (actionNode.role == RoleType.inlineTextBox)
407 actionNode = actionNode.parent; 480 actionNode = actionNode.parent;
408 actionNode.focus(); 481 actionNode.focus();
409 482
410 var prevRange = this.currentRange_; 483 var prevRange = this.currentRange_;
411 this.currentRange = current; 484 this.setCurrentRange(current);
412 485
413 new Output().withSpeechAndBraille( 486 new Output().withSpeechAndBraille(
414 this.currentRange_, prevRange, Output.EventType.NAVIGATE) 487 this.currentRange_, prevRange, Output.EventType.NAVIGATE)
415 .go(); 488 .go();
416 } 489 }
417 490
418 return false; 491 return false;
419 }, 492 },
420 493
421 /** 494 /**
422 * Handles key down events. 495 * Handles key down events.
423 * @param {Event} evt The key down event to process. 496 * @param {Event} evt The key down event to process.
424 * @return {boolean} True if the default action should be performed. 497 * @return {boolean} True if the default action should be performed.
425 */ 498 */
426 onKeyDown: function(evt) { 499 onKeyDown: function(evt) {
427 if (this.mode_ != ChromeVoxMode.CLASSIC && 500 if (this.mode_ != ChromeVoxMode.CLASSIC &&
428 !cvox.ChromeVoxKbHandler.basicKeyDownActionsListener(evt)) { 501 !cvox.ChromeVoxKbHandler.basicKeyDownActionsListener(evt)) {
429 evt.preventDefault(); 502 evt.preventDefault();
430 evt.stopPropagation(); 503 evt.stopPropagation();
431 } 504 }
505
506 Output.flushNextSpeechUtterance();
432 }, 507 },
433 508
434 /** 509 /**
435 * Open the options page in a new tab. 510 * Open the options page in a new tab.
436 */ 511 */
437 showOptionsPage: function() { 512 showOptionsPage: function() {
438 var optionsPage = {url: 'chromevox/background/options.html'}; 513 var optionsPage = {url: 'chromevox/background/options.html'};
439 chrome.tabs.create(optionsPage); 514 chrome.tabs.create(optionsPage);
440 }, 515 },
441 516
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
474 // Cast ok since displayPosition is always defined in this case. 549 // Cast ok since displayPosition is always defined in this case.
475 /** @type {number} */ (evt.displayPosition)); 550 /** @type {number} */ (evt.displayPosition));
476 break; 551 break;
477 default: 552 default:
478 return false; 553 return false;
479 } 554 }
480 return true; 555 return true;
481 }, 556 },
482 557
483 /** 558 /**
484 * Refreshes the current mode based on a url.
485 * @param {string} url
486 */
487 refreshMode: function(url) {
488 var mode = this.mode_;
489 if (mode != ChromeVoxMode.FORCE_NEXT) {
490 if (this.isWhitelistedForNext_(url))
491 mode = ChromeVoxMode.NEXT;
492 else if (this.isBlacklistedForClassic_(url))
493 mode = ChromeVoxMode.COMPAT;
494 else
495 mode = ChromeVoxMode.CLASSIC;
496 }
497
498 this.setChromeVoxMode(mode);
499 },
500
501 /**
502 * Called when the automation tree is changed.
503 * @param {chrome.automation.TreeChange} treeChange
504 */
505 onTreeChange: function(treeChange) {
506 if (this.mode_ === ChromeVoxMode.CLASSIC || !cvox.ChromeVox.isActive)
507 return;
508
509 var node = treeChange.target;
510 if (!node.containerLiveStatus)
511 return;
512
513 if (node.containerLiveRelevant.indexOf('additions') >= 0 &&
514 treeChange.type == 'nodeCreated')
515 this.outputLiveRegionChange_(node, null);
516 if (node.containerLiveRelevant.indexOf('text') >= 0 &&
517 treeChange.type == 'nodeChanged')
518 this.outputLiveRegionChange_(node, null);
519 if (node.containerLiveRelevant.indexOf('removals') >= 0 &&
520 treeChange.type == 'nodeRemoved')
521 this.outputLiveRegionChange_(node, '@live_regions_removed');
522 },
523
524 /**
525 * Given a node that needs to be spoken as part of a live region
526 * change and an additional optional format string, output the
527 * live region description.
528 * @param {!chrome.automation.AutomationNode} node The changed node.
529 * @param {?string} opt_prependFormatStr If set, a format string for
530 * cvox2.Output to prepend to the output.
531 * @private
532 */
533 outputLiveRegionChange_: function(node, opt_prependFormatStr) {
534 var range = cursors.Range.fromNode(node);
535 var output = new Output();
536 if (opt_prependFormatStr) {
537 output.format(opt_prependFormatStr);
538 }
539 output.withSpeech(range, null, Output.EventType.NAVIGATE);
540 output.withSpeechCategory(cvox.TtsCategory.LIVE);
541 output.go();
542 },
543
544 /**
545 * Returns true if the url should have Classic running. 559 * Returns true if the url should have Classic running.
546 * @return {boolean} 560 * @return {boolean}
547 * @private 561 * @private
548 */ 562 */
549 shouldEnableClassicForUrl_: function(url) { 563 shouldEnableClassicForUrl_: function(url) {
550 return this.mode_ != ChromeVoxMode.FORCE_NEXT && 564 return this.mode_ != ChromeVoxMode.FORCE_NEXT &&
551 !this.isBlacklistedForClassic_(url) && 565 !this.isBlacklistedForClassic_(url) &&
552 !this.isWhitelistedForNext_(url); 566 !this.isWhitelistedForNext_(url);
553 }, 567 },
554 568
(...skipping 21 matching lines...) Expand all
576 * Disables classic ChromeVox in current web content. 590 * Disables classic ChromeVox in current web content.
577 */ 591 */
578 disableClassicChromeVox_: function() { 592 disableClassicChromeVox_: function() {
579 cvox.ExtensionBridge.send({ 593 cvox.ExtensionBridge.send({
580 message: 'SYSTEM_COMMAND', 594 message: 'SYSTEM_COMMAND',
581 command: 'killChromeVox' 595 command: 'killChromeVox'
582 }); 596 });
583 }, 597 },
584 598
585 /** 599 /**
586 * Sets the current ChromeVox mode.
587 * @param {ChromeVoxMode} mode
588 * @param {boolean=} opt_injectClassic Injects ChromeVox classic into tabs;
589 * defaults to false.
590 */
591 setChromeVoxMode: function(mode, opt_injectClassic) {
592 // Switching key maps potentially affects the key codes that involve
593 // sequencing. Without resetting this list, potentially stale key codes
594 // remain. The key codes themselves get pushed in
595 // cvox.KeySequence.deserialize which gets called by cvox.KeyMap.
596 cvox.ChromeVox.sequenceSwitchKeyCodes = [];
597 if (mode === ChromeVoxMode.CLASSIC || mode === ChromeVoxMode.COMPAT)
598 cvox.ChromeVoxKbHandler.handlerKeyMap = cvox.KeyMap.fromDefaults();
599 else
600 cvox.ChromeVoxKbHandler.handlerKeyMap = cvox.KeyMap.fromNext();
601
602 if (mode == ChromeVoxMode.CLASSIC) {
603 if (chrome.commands &&
604 chrome.commands.onCommand.hasListener(this.onGotCommand))
605 chrome.commands.onCommand.removeListener(this.onGotCommand);
606 } else {
607 if (chrome.commands &&
608 !chrome.commands.onCommand.hasListener(this.onGotCommand))
609 chrome.commands.onCommand.addListener(this.onGotCommand);
610 }
611
612 chrome.tabs.query({active: true}, function(tabs) {
613 if (mode === ChromeVoxMode.CLASSIC) {
614 // Generally, we don't want to inject classic content scripts as it is
615 // done by the extension system at document load. The exception is when
616 // we toggle classic on manually as part of a user command.
617 if (opt_injectClassic)
618 cvox.ChromeVox.injectChromeVoxIntoTabs(tabs);
619 } else {
620 // When in compat mode, if the focus is within the desktop tree proper,
621 // then do not disable content scripts.
622 if (this.currentRange_ &&
623 this.currentRange_.start.node.root.role == RoleType.desktop)
624 return;
625
626 this.disableClassicChromeVox_();
627 }
628 }.bind(this));
629
630 // If switching out of a ChromeVox Next mode, make sure we cancel
631 // the progress loading sound just in case.
632 if ((this.mode_ === ChromeVoxMode.NEXT ||
633 this.mode_ === ChromeVoxMode.FORCE_NEXT) &&
634 this.mode_ != mode) {
635 cvox.ChromeVox.earcons.cancelEarcon(cvox.Earcon.PAGE_START_LOADING);
636 }
637
638 this.mode_ = mode;
639 },
640
641 /**
642 * @param {!Spannable} text 600 * @param {!Spannable} text
643 * @param {number} position 601 * @param {number} position
644 * @private 602 * @private
645 */ 603 */
646 brailleRoutingCommand_: function(text, position) { 604 brailleRoutingCommand_: function(text, position) {
647 var actionNodeSpan = null; 605 var actionNodeSpan = null;
648 var selectionSpan = null; 606 var selectionSpan = null;
649 text.getSpans(position).forEach(function(span) { 607 text.getSpans(position).forEach(function(span) {
650 if (span instanceof Output.SelectionSpan) { 608 if (span instanceof Output.SelectionSpan) {
651 selectionSpan = span; 609 selectionSpan = span;
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
704 return glob.replace(/[.+^$(){}|[\]\\]/g, '\\$&') 662 return glob.replace(/[.+^$(){}|[\]\\]/g, '\\$&')
705 .replace(/\*/g, '.*') 663 .replace(/\*/g, '.*')
706 .replace(/\?/g, '.'); 664 .replace(/\?/g, '.');
707 }).join('|') + ')$'); 665 }).join('|') + ')$');
708 }; 666 };
709 667
710 /** @type {Background} */ 668 /** @type {Background} */
711 global.backgroundObj = new Background(); 669 global.backgroundObj = new Background();
712 670
713 }); // goog.scope 671 }); // goog.scope
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698