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

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: Rebase 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
127 /**
128 * Live region events received in fewer than this many milliseconds will
129 * queue, otherwise they'll be output with a category flush.
130 * @type {number}
131 * @const
132 */
David Tseng 2015/11/23 23:23:57 Move this as well?
dmazzoni 2015/11/30 22:00:47 Done.
133 Background.LIVE_REGION_QUEUE_TIME_MS = 500;
Peter Lundblad 2015/11/24 11:04:30 Move to LiveRegionHandler?
dmazzoni 2015/11/30 22:00:46 Done.
134
133 Background.prototype = { 135 Background.prototype = {
134 /** Forces ChromeVox Next to be active for all tabs. */ 136 __proto__: ChromeVoxState.prototype,
135 forceChromeVoxNextActive: function() {
136 this.setChromeVoxMode(ChromeVoxMode.FORCE_NEXT);
137 },
138 137
139 /** @type {ChromeVoxMode} */ 138 /**
140 get mode() { 139 * @override
140 */
141 getMode: function() {
141 return this.mode_; 142 return this.mode_;
142 }, 143 },
143 144
144 /** @type {cursors.Range} */ 145 /**
145 get currentRange() { 146 * @override
147 */
148 setMode: function(mode, opt_injectClassic) {
149 // Switching key maps potentially affects the key codes that involve
150 // sequencing. Without resetting this list, potentially stale key codes
151 // remain. The key codes themselves get pushed in
152 // cvox.KeySequence.deserialize which gets called by cvox.KeyMap.
153 cvox.ChromeVox.sequenceSwitchKeyCodes = [];
154 if (mode === ChromeVoxMode.CLASSIC || mode === ChromeVoxMode.COMPAT)
155 cvox.ChromeVoxKbHandler.handlerKeyMap = cvox.KeyMap.fromDefaults();
156 else
157 cvox.ChromeVoxKbHandler.handlerKeyMap = cvox.KeyMap.fromNext();
158
159 if (mode == ChromeVoxMode.CLASSIC) {
160 if (chrome.commands &&
161 chrome.commands.onCommand.hasListener(this.onGotCommand))
162 chrome.commands.onCommand.removeListener(this.onGotCommand);
163 } else {
164 if (chrome.commands &&
165 !chrome.commands.onCommand.hasListener(this.onGotCommand))
166 chrome.commands.onCommand.addListener(this.onGotCommand);
167 }
168
169 chrome.tabs.query({active: true}, function(tabs) {
170 if (mode === ChromeVoxMode.CLASSIC) {
171 // Generally, we don't want to inject classic content scripts as it is
172 // done by the extension system at document load. The exception is when
173 // we toggle classic on manually as part of a user command.
174 if (opt_injectClassic)
175 cvox.ChromeVox.injectChromeVoxIntoTabs(tabs);
176 } else {
177 // When in compat mode, if the focus is within the desktop tree proper,
178 // then do not disable content scripts.
179 if (this.currentRange_ &&
180 this.currentRange_.start.node.root.role == RoleType.desktop)
181 return;
182
183 this.disableClassicChromeVox_();
184 }
185 }.bind(this));
186
187 this.mode_ = mode;
188 },
189
190 /**
191 * @override
192 */
193 getCurrentRange: function() {
146 return this.currentRange_; 194 return this.currentRange_;
147 }, 195 },
148 196
149 set currentRange(value) { 197 /**
150 if (!value) 198 * @override
199 */
200 setCurrentRange: function(newRange) {
201 if (!newRange)
151 return; 202 return;
152 203
153 this.currentRange_ = value; 204 this.currentRange_ = newRange;
154 205
155 if (this.currentRange_) 206 if (this.currentRange_)
156 this.currentRange_.start.node.makeVisible(); 207 this.currentRange_.start.node.makeVisible();
157 }, 208 },
158 209
210 /** Forces ChromeVox Next to be active for all tabs. */
211 forceChromeVoxNextActive: function() {
212 this.setMode(ChromeVoxMode.FORCE_NEXT);
213 },
214
159 /** 215 /**
160 * Handles ChromeVox Next commands. 216 * Handles ChromeVox Next commands.
161 * @param {string} command 217 * @param {string} command
162 * @param {boolean=} opt_bypassModeCheck Always tries to execute the command 218 * @param {boolean=} opt_bypassModeCheck Always tries to execute the command
163 * regardless of mode. 219 * regardless of mode.
164 * @return {boolean} True if the command should propagate. 220 * @return {boolean} True if the command should propagate.
165 */ 221 */
166 onGotCommand: function(command, opt_bypassModeCheck) { 222 onGotCommand: function(command, opt_bypassModeCheck) {
167 if (!this.currentRange_) 223 if (!this.currentRange_)
168 return true; 224 return true;
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after
324 global.isReadingContinuously = true; 380 global.isReadingContinuously = true;
325 var continueReading = function(prevRange) { 381 var continueReading = function(prevRange) {
326 if (!global.isReadingContinuously || !this.currentRange_) 382 if (!global.isReadingContinuously || !this.currentRange_)
327 return; 383 return;
328 384
329 new Output().withSpeechAndBraille( 385 new Output().withSpeechAndBraille(
330 this.currentRange_, prevRange, Output.EventType.NAVIGATE) 386 this.currentRange_, prevRange, Output.EventType.NAVIGATE)
331 .onSpeechEnd(function() { continueReading(prevRange); }) 387 .onSpeechEnd(function() { continueReading(prevRange); })
332 .go(); 388 .go();
333 prevRange = this.currentRange_; 389 prevRange = this.currentRange_;
334 this.currentRange = 390 this.setCurrentRange(
335 this.currentRange.move(cursors.Unit.NODE, Dir.FORWARD); 391 this.currentRange_.move(cursors.Unit.NODE, Dir.FORWARD));
336 392
337 if (!this.currentRange_ || this.currentRange_.equals(prevRange)) 393 if (!this.currentRange_ || this.currentRange_.equals(prevRange))
338 global.isReadingContinuously = false; 394 global.isReadingContinuously = false;
339 }.bind(this); 395 }.bind(this);
340 396
341 continueReading(null); 397 continueReading(null);
342 return false; 398 return false;
343 case 'showContextMenu': 399 case 'showContextMenu':
344 if (this.currentRange_) { 400 if (this.currentRange_) {
345 var actionNode = this.currentRange_.start.node; 401 var actionNode = this.currentRange_.start.node;
(...skipping 19 matching lines...) Expand all
365 break; 421 break;
366 case 'toggleChromeVoxVersion': 422 case 'toggleChromeVoxVersion':
367 var newMode; 423 var newMode;
368 if (this.mode_ == ChromeVoxMode.FORCE_NEXT) { 424 if (this.mode_ == ChromeVoxMode.FORCE_NEXT) {
369 var inViews = 425 var inViews =
370 this.currentRange_.start.node.root.role == RoleType.desktop; 426 this.currentRange_.start.node.root.role == RoleType.desktop;
371 newMode = inViews ? ChromeVoxMode.COMPAT : ChromeVoxMode.CLASSIC; 427 newMode = inViews ? ChromeVoxMode.COMPAT : ChromeVoxMode.CLASSIC;
372 } else { 428 } else {
373 newMode = ChromeVoxMode.FORCE_NEXT; 429 newMode = ChromeVoxMode.FORCE_NEXT;
374 } 430 }
375 this.setChromeVoxMode(newMode, true); 431 this.setMode(newMode, true);
376 432
377 var isClassic = 433 var isClassic =
378 newMode == ChromeVoxMode.CLASSIC || newMode == ChromeVoxMode.COMPAT; 434 newMode == ChromeVoxMode.CLASSIC || newMode == ChromeVoxMode.COMPAT;
379 435
380 // Leaving unlocalized as 'next' isn't an official name. 436 // Leaving unlocalized as 'next' isn't an official name.
381 cvox.ChromeVox.tts.speak(isClassic ? 437 cvox.ChromeVox.tts.speak(isClassic ?
382 'classic' : 'next', cvox.QueueMode.FLUSH, {doNotInterrupt: true}); 438 'classic' : 'next', cvox.QueueMode.FLUSH, {doNotInterrupt: true});
383 break; 439 break;
384 default: 440 default:
385 return true; 441 return true;
(...skipping 15 matching lines...) Expand all
401 } 457 }
402 458
403 if (current) { 459 if (current) {
404 // TODO(dtseng): Figure out what it means to focus a range. 460 // TODO(dtseng): Figure out what it means to focus a range.
405 var actionNode = current.start.node; 461 var actionNode = current.start.node;
406 if (actionNode.role == RoleType.inlineTextBox) 462 if (actionNode.role == RoleType.inlineTextBox)
407 actionNode = actionNode.parent; 463 actionNode = actionNode.parent;
408 actionNode.focus(); 464 actionNode.focus();
409 465
410 var prevRange = this.currentRange_; 466 var prevRange = this.currentRange_;
411 this.currentRange = current; 467 this.setCurrentRange(current);
412 468
413 new Output().withSpeechAndBraille( 469 new Output().withSpeechAndBraille(
414 this.currentRange_, prevRange, Output.EventType.NAVIGATE) 470 this.currentRange_, prevRange, Output.EventType.NAVIGATE)
415 .go(); 471 .go();
416 } 472 }
417 473
418 return false; 474 return false;
419 }, 475 },
420 476
421 /** 477 /**
422 * Handles key down events. 478 * Handles key down events.
423 * @param {Event} evt The key down event to process. 479 * @param {Event} evt The key down event to process.
424 * @return {boolean} True if the default action should be performed. 480 * @return {boolean} True if the default action should be performed.
425 */ 481 */
426 onKeyDown: function(evt) { 482 onKeyDown: function(evt) {
427 if (this.mode_ != ChromeVoxMode.CLASSIC && 483 if (this.mode_ != ChromeVoxMode.CLASSIC &&
428 !cvox.ChromeVoxKbHandler.basicKeyDownActionsListener(evt)) { 484 !cvox.ChromeVoxKbHandler.basicKeyDownActionsListener(evt)) {
429 evt.preventDefault(); 485 evt.preventDefault();
430 evt.stopPropagation(); 486 evt.stopPropagation();
431 } 487 }
488
489 Output.flushNextSpeechUtterance();
432 }, 490 },
433 491
434 /** 492 /**
435 * Open the options page in a new tab. 493 * Open the options page in a new tab.
436 */ 494 */
437 showOptionsPage: function() { 495 showOptionsPage: function() {
438 var optionsPage = {url: 'chromevox/background/options.html'}; 496 var optionsPage = {url: 'chromevox/background/options.html'};
439 chrome.tabs.create(optionsPage); 497 chrome.tabs.create(optionsPage);
440 }, 498 },
441 499
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
488 var mode = this.mode_; 546 var mode = this.mode_;
489 if (mode != ChromeVoxMode.FORCE_NEXT) { 547 if (mode != ChromeVoxMode.FORCE_NEXT) {
490 if (this.isWhitelistedForNext_(url)) 548 if (this.isWhitelistedForNext_(url))
491 mode = ChromeVoxMode.NEXT; 549 mode = ChromeVoxMode.NEXT;
492 else if (this.isBlacklistedForClassic_(url)) 550 else if (this.isBlacklistedForClassic_(url))
493 mode = ChromeVoxMode.COMPAT; 551 mode = ChromeVoxMode.COMPAT;
494 else 552 else
495 mode = ChromeVoxMode.CLASSIC; 553 mode = ChromeVoxMode.CLASSIC;
496 } 554 }
497 555
498 this.setChromeVoxMode(mode); 556 this.setMode(mode);
499 }, 557 },
500 558
501 /** 559 /**
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. 560 * Returns true if the url should have Classic running.
546 * @return {boolean} 561 * @return {boolean}
547 * @private 562 * @private
548 */ 563 */
549 shouldEnableClassicForUrl_: function(url) { 564 shouldEnableClassicForUrl_: function(url) {
550 return this.mode_ != ChromeVoxMode.FORCE_NEXT && 565 return this.mode_ != ChromeVoxMode.FORCE_NEXT &&
551 !this.isBlacklistedForClassic_(url) && 566 !this.isBlacklistedForClassic_(url) &&
552 !this.isWhitelistedForNext_(url); 567 !this.isWhitelistedForNext_(url);
553 }, 568 },
554 569
(...skipping 21 matching lines...) Expand all
576 * Disables classic ChromeVox in current web content. 591 * Disables classic ChromeVox in current web content.
577 */ 592 */
578 disableClassicChromeVox_: function() { 593 disableClassicChromeVox_: function() {
579 cvox.ExtensionBridge.send({ 594 cvox.ExtensionBridge.send({
580 message: 'SYSTEM_COMMAND', 595 message: 'SYSTEM_COMMAND',
581 command: 'killChromeVox' 596 command: 'killChromeVox'
582 }); 597 });
583 }, 598 },
584 599
585 /** 600 /**
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 601 * @param {!Spannable} text
635 * @param {number} position 602 * @param {number} position
636 * @private 603 * @private
637 */ 604 */
638 brailleRoutingCommand_: function(text, position) { 605 brailleRoutingCommand_: function(text, position) {
639 var actionNodeSpan = null; 606 var actionNodeSpan = null;
640 var selectionSpan = null; 607 var selectionSpan = null;
641 text.getSpans(position).forEach(function(span) { 608 text.getSpans(position).forEach(function(span) {
642 if (span instanceof Output.SelectionSpan) { 609 if (span instanceof Output.SelectionSpan) {
643 selectionSpan = span; 610 selectionSpan = span;
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
696 return glob.replace(/[.+^$(){}|[\]\\]/g, '\\$&') 663 return glob.replace(/[.+^$(){}|[\]\\]/g, '\\$&')
697 .replace(/\*/g, '.*') 664 .replace(/\*/g, '.*')
698 .replace(/\?/g, '.'); 665 .replace(/\?/g, '.');
699 }).join('|') + ')$'); 666 }).join('|') + ')$');
700 }; 667 };
701 668
702 /** @type {Background} */ 669 /** @type {Background} */
703 global.backgroundObj = new Background(); 670 global.backgroundObj = new Background();
704 671
705 }); // goog.scope 672 }); // goog.scope
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698