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

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: Fix tests 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 this.mode_ = mode;
180 },
181
182 /**
183 * @override
184 */
185 refreshMode: function(url) {
186 var mode = this.mode_;
187 if (mode != ChromeVoxMode.FORCE_NEXT) {
188 if (this.isWhitelistedForNext_(url))
189 mode = ChromeVoxMode.NEXT;
190 else if (this.isBlacklistedForClassic_(url))
191 mode = ChromeVoxMode.COMPAT;
192 else
193 mode = ChromeVoxMode.CLASSIC;
194 }
195
196 this.setMode(mode);
197 },
198
199 /**
200 * @override
201 */
202 getCurrentRange: function() {
146 return this.currentRange_; 203 return this.currentRange_;
147 }, 204 },
148 205
149 set currentRange(value) { 206 /**
150 if (!value) 207 * @override
208 */
209 setCurrentRange: function(newRange) {
210 if (!newRange)
151 return; 211 return;
152 212
153 this.currentRange_ = value; 213 this.currentRange_ = newRange;
154 214
155 if (this.currentRange_) 215 if (this.currentRange_)
156 this.currentRange_.start.node.makeVisible(); 216 this.currentRange_.start.node.makeVisible();
157 }, 217 },
158 218
219 /** Forces ChromeVox Next to be active for all tabs. */
220 forceChromeVoxNextActive: function() {
221 this.setMode(ChromeVoxMode.FORCE_NEXT);
222 },
223
159 /** 224 /**
160 * Handles ChromeVox Next commands. 225 * Handles ChromeVox Next commands.
161 * @param {string} command 226 * @param {string} command
162 * @param {boolean=} opt_bypassModeCheck Always tries to execute the command 227 * @param {boolean=} opt_bypassModeCheck Always tries to execute the command
163 * regardless of mode. 228 * regardless of mode.
164 * @return {boolean} True if the command should propagate. 229 * @return {boolean} True if the command should propagate.
165 */ 230 */
166 onGotCommand: function(command, opt_bypassModeCheck) { 231 onGotCommand: function(command, opt_bypassModeCheck) {
167 if (!this.currentRange_) 232 if (!this.currentRange_)
168 return true; 233 return true;
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after
324 global.isReadingContinuously = true; 389 global.isReadingContinuously = true;
325 var continueReading = function(prevRange) { 390 var continueReading = function(prevRange) {
326 if (!global.isReadingContinuously || !this.currentRange_) 391 if (!global.isReadingContinuously || !this.currentRange_)
327 return; 392 return;
328 393
329 new Output().withSpeechAndBraille( 394 new Output().withSpeechAndBraille(
330 this.currentRange_, prevRange, Output.EventType.NAVIGATE) 395 this.currentRange_, prevRange, Output.EventType.NAVIGATE)
331 .onSpeechEnd(function() { continueReading(prevRange); }) 396 .onSpeechEnd(function() { continueReading(prevRange); })
332 .go(); 397 .go();
333 prevRange = this.currentRange_; 398 prevRange = this.currentRange_;
334 this.currentRange = 399 this.setCurrentRange(
335 this.currentRange.move(cursors.Unit.NODE, Dir.FORWARD); 400 this.currentRange_.move(cursors.Unit.NODE, Dir.FORWARD));
336 401
337 if (!this.currentRange_ || this.currentRange_.equals(prevRange)) 402 if (!this.currentRange_ || this.currentRange_.equals(prevRange))
338 global.isReadingContinuously = false; 403 global.isReadingContinuously = false;
339 }.bind(this); 404 }.bind(this);
340 405
341 continueReading(null); 406 continueReading(null);
342 return false; 407 return false;
343 case 'showContextMenu': 408 case 'showContextMenu':
344 if (this.currentRange_) { 409 if (this.currentRange_) {
345 var actionNode = this.currentRange_.start.node; 410 var actionNode = this.currentRange_.start.node;
(...skipping 19 matching lines...) Expand all
365 break; 430 break;
366 case 'toggleChromeVoxVersion': 431 case 'toggleChromeVoxVersion':
367 var newMode; 432 var newMode;
368 if (this.mode_ == ChromeVoxMode.FORCE_NEXT) { 433 if (this.mode_ == ChromeVoxMode.FORCE_NEXT) {
369 var inViews = 434 var inViews =
370 this.currentRange_.start.node.root.role == RoleType.desktop; 435 this.currentRange_.start.node.root.role == RoleType.desktop;
371 newMode = inViews ? ChromeVoxMode.COMPAT : ChromeVoxMode.CLASSIC; 436 newMode = inViews ? ChromeVoxMode.COMPAT : ChromeVoxMode.CLASSIC;
372 } else { 437 } else {
373 newMode = ChromeVoxMode.FORCE_NEXT; 438 newMode = ChromeVoxMode.FORCE_NEXT;
374 } 439 }
375 this.setChromeVoxMode(newMode, true); 440 this.setMode(newMode, true);
376 441
377 var isClassic = 442 var isClassic =
378 newMode == ChromeVoxMode.CLASSIC || newMode == ChromeVoxMode.COMPAT; 443 newMode == ChromeVoxMode.CLASSIC || newMode == ChromeVoxMode.COMPAT;
379 444
380 // Leaving unlocalized as 'next' isn't an official name. 445 // Leaving unlocalized as 'next' isn't an official name.
381 cvox.ChromeVox.tts.speak(isClassic ? 446 cvox.ChromeVox.tts.speak(isClassic ?
382 'classic' : 'next', cvox.QueueMode.FLUSH, {doNotInterrupt: true}); 447 'classic' : 'next', cvox.QueueMode.FLUSH, {doNotInterrupt: true});
383 break; 448 break;
384 default: 449 default:
385 return true; 450 return true;
(...skipping 15 matching lines...) Expand all
401 } 466 }
402 467
403 if (current) { 468 if (current) {
404 // TODO(dtseng): Figure out what it means to focus a range. 469 // TODO(dtseng): Figure out what it means to focus a range.
405 var actionNode = current.start.node; 470 var actionNode = current.start.node;
406 if (actionNode.role == RoleType.inlineTextBox) 471 if (actionNode.role == RoleType.inlineTextBox)
407 actionNode = actionNode.parent; 472 actionNode = actionNode.parent;
408 actionNode.focus(); 473 actionNode.focus();
409 474
410 var prevRange = this.currentRange_; 475 var prevRange = this.currentRange_;
411 this.currentRange = current; 476 this.setCurrentRange(current);
412 477
413 new Output().withSpeechAndBraille( 478 new Output().withSpeechAndBraille(
414 this.currentRange_, prevRange, Output.EventType.NAVIGATE) 479 this.currentRange_, prevRange, Output.EventType.NAVIGATE)
415 .go(); 480 .go();
416 } 481 }
417 482
418 return false; 483 return false;
419 }, 484 },
420 485
421 /** 486 /**
422 * Handles key down events. 487 * Handles key down events.
423 * @param {Event} evt The key down event to process. 488 * @param {Event} evt The key down event to process.
424 * @return {boolean} True if the default action should be performed. 489 * @return {boolean} True if the default action should be performed.
425 */ 490 */
426 onKeyDown: function(evt) { 491 onKeyDown: function(evt) {
427 if (this.mode_ != ChromeVoxMode.CLASSIC && 492 if (this.mode_ != ChromeVoxMode.CLASSIC &&
428 !cvox.ChromeVoxKbHandler.basicKeyDownActionsListener(evt)) { 493 !cvox.ChromeVoxKbHandler.basicKeyDownActionsListener(evt)) {
429 evt.preventDefault(); 494 evt.preventDefault();
430 evt.stopPropagation(); 495 evt.stopPropagation();
431 } 496 }
497
498 Output.flushNextSpeechUtterance();
432 }, 499 },
433 500
434 /** 501 /**
435 * Open the options page in a new tab. 502 * Open the options page in a new tab.
436 */ 503 */
437 showOptionsPage: function() { 504 showOptionsPage: function() {
438 var optionsPage = {url: 'chromevox/background/options.html'}; 505 var optionsPage = {url: 'chromevox/background/options.html'};
439 chrome.tabs.create(optionsPage); 506 chrome.tabs.create(optionsPage);
440 }, 507 },
441 508
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
474 // Cast ok since displayPosition is always defined in this case. 541 // Cast ok since displayPosition is always defined in this case.
475 /** @type {number} */ (evt.displayPosition)); 542 /** @type {number} */ (evt.displayPosition));
476 break; 543 break;
477 default: 544 default:
478 return false; 545 return false;
479 } 546 }
480 return true; 547 return true;
481 }, 548 },
482 549
483 /** 550 /**
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. 551 * Returns true if the url should have Classic running.
546 * @return {boolean} 552 * @return {boolean}
547 * @private 553 * @private
548 */ 554 */
549 shouldEnableClassicForUrl_: function(url) { 555 shouldEnableClassicForUrl_: function(url) {
550 return this.mode_ != ChromeVoxMode.FORCE_NEXT && 556 return this.mode_ != ChromeVoxMode.FORCE_NEXT &&
551 !this.isBlacklistedForClassic_(url) && 557 !this.isBlacklistedForClassic_(url) &&
552 !this.isWhitelistedForNext_(url); 558 !this.isWhitelistedForNext_(url);
553 }, 559 },
554 560
(...skipping 21 matching lines...) Expand all
576 * Disables classic ChromeVox in current web content. 582 * Disables classic ChromeVox in current web content.
577 */ 583 */
578 disableClassicChromeVox_: function() { 584 disableClassicChromeVox_: function() {
579 cvox.ExtensionBridge.send({ 585 cvox.ExtensionBridge.send({
580 message: 'SYSTEM_COMMAND', 586 message: 'SYSTEM_COMMAND',
581 command: 'killChromeVox' 587 command: 'killChromeVox'
582 }); 588 });
583 }, 589 },
584 590
585 /** 591 /**
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 this.mode_ = mode;
631 },
632
633 /**
634 * @param {!Spannable} text 592 * @param {!Spannable} text
635 * @param {number} position 593 * @param {number} position
636 * @private 594 * @private
637 */ 595 */
638 brailleRoutingCommand_: function(text, position) { 596 brailleRoutingCommand_: function(text, position) {
639 var actionNodeSpan = null; 597 var actionNodeSpan = null;
640 var selectionSpan = null; 598 var selectionSpan = null;
641 text.getSpans(position).forEach(function(span) { 599 text.getSpans(position).forEach(function(span) {
642 if (span instanceof Output.SelectionSpan) { 600 if (span instanceof Output.SelectionSpan) {
643 selectionSpan = span; 601 selectionSpan = span;
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
696 return glob.replace(/[.+^$(){}|[\]\\]/g, '\\$&') 654 return glob.replace(/[.+^$(){}|[\]\\]/g, '\\$&')
697 .replace(/\*/g, '.*') 655 .replace(/\*/g, '.*')
698 .replace(/\?/g, '.'); 656 .replace(/\?/g, '.');
699 }).join('|') + ')$'); 657 }).join('|') + ')$');
700 }; 658 };
701 659
702 /** @type {Background} */ 660 /** @type {Background} */
703 global.backgroundObj = new Background(); 661 global.backgroundObj = new Background();
704 662
705 }); // goog.scope 663 }); // goog.scope
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698