OLD | NEW |
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'); |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
86 /** @type {!cvox.AbstractEarcons} @private */ | 86 /** @type {!cvox.AbstractEarcons} @private */ |
87 this.classicEarcons_ = cvox.ChromeVox.earcons || new cvox.ClassicEarcons(); | 87 this.classicEarcons_ = cvox.ChromeVox.earcons || new cvox.ClassicEarcons(); |
88 | 88 |
89 /** @type {!cvox.AbstractEarcons} @private */ | 89 /** @type {!cvox.AbstractEarcons} @private */ |
90 this.nextEarcons_ = new NextEarcons(); | 90 this.nextEarcons_ = new NextEarcons(); |
91 | 91 |
92 // Turn cvox.ChromeVox.earcons into a getter that returns either the | 92 // Turn cvox.ChromeVox.earcons into a getter that returns either the |
93 // Next earcons or the Classic earcons depending on the current mode. | 93 // Next earcons or the Classic earcons depending on the current mode. |
94 Object.defineProperty(cvox.ChromeVox, 'earcons', { | 94 Object.defineProperty(cvox.ChromeVox, 'earcons', { |
95 get: (function() { | 95 get: (function() { |
96 if (this.mode === ChromeVoxMode.FORCE_NEXT || | 96 if (this.mode === ChromeVoxMode.FORCE_NEXT || |
97 this.mode === ChromeVoxMode.NEXT) { | 97 this.mode === ChromeVoxMode.NEXT) { |
98 return this.nextEarcons_; | 98 return this.nextEarcons_; |
99 } else { | 99 } else { |
100 return this.classicEarcons_; | 100 return this.classicEarcons_; |
101 } | 101 } |
102 }).bind(this) | 102 }).bind(this) |
103 }); | 103 }); |
104 | 104 |
105 if (cvox.ChromeVox.isChromeOS) { | 105 if (cvox.ChromeVox.isChromeOS) { |
106 Object.defineProperty(cvox.ChromeVox, 'modKeyStr', { | 106 Object.defineProperty(cvox.ChromeVox, 'modKeyStr', { |
107 get: function() { | 107 get: function() { |
108 return (this.mode == ChromeVoxMode.CLASSIC || | 108 return (this.mode == ChromeVoxMode.CLASSIC || |
109 this.mode == ChromeVoxMode.CLASSIC_COMPAT) ? | 109 this.mode == ChromeVoxMode.CLASSIC_COMPAT) ? |
110 'Search+Shift' : 'Search'; | 110 'Search+Shift' : |
| 111 'Search'; |
111 }.bind(this) | 112 }.bind(this) |
112 }); | 113 }); |
113 | 114 |
114 Object.defineProperty(cvox.ChromeVox, 'typingEcho', { | 115 Object.defineProperty(cvox.ChromeVox, 'typingEcho', { |
115 get: function() { | 116 get: function() { |
116 return parseInt(localStorage['typingEcho'], 10); | 117 return parseInt(localStorage['typingEcho'], 10); |
117 }.bind(this), | 118 }.bind(this), |
118 set: function(v) { | 119 set: function(v) { |
119 localStorage['typingEcho'] = v; | 120 localStorage['typingEcho'] = v; |
120 }.bind(this) | 121 }.bind(this) |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
167 this.focusRecoveryMap_ = new WeakMap(); | 168 this.focusRecoveryMap_ = new WeakMap(); |
168 | 169 |
169 chrome.automation.getDesktop(function(desktop) { | 170 chrome.automation.getDesktop(function(desktop) { |
170 /** @type {string} */ | 171 /** @type {string} */ |
171 this.chromeChannel_ = desktop.chromeChannel; | 172 this.chromeChannel_ = desktop.chromeChannel; |
172 }.bind(this)); | 173 }.bind(this)); |
173 | 174 |
174 // Record a metric with the mode we're in on startup. | 175 // Record a metric with the mode we're in on startup. |
175 var useNext = localStorage['useClassic'] != 'true'; | 176 var useNext = localStorage['useClassic'] != 'true'; |
176 chrome.metricsPrivate.recordValue( | 177 chrome.metricsPrivate.recordValue( |
177 { metricName: 'Accessibility.CrosChromeVoxNext', | 178 { |
| 179 metricName: 'Accessibility.CrosChromeVoxNext', |
178 type: chrome.metricsPrivate.MetricTypeType.HISTOGRAM_LINEAR, | 180 type: chrome.metricsPrivate.MetricTypeType.HISTOGRAM_LINEAR, |
179 min: 1, // According to histogram.h, this should be 1 for enums. | 181 min: 1, // According to histogram.h, this should be 1 for enums. |
180 max: 2, // Maximum should be exclusive. | 182 max: 2, // Maximum should be exclusive. |
181 buckets: 3 }, // Number of buckets: 0, 1 and overflowing 2. | 183 buckets: 3 |
| 184 }, // Number of buckets: 0, 1 and overflowing 2. |
182 useNext ? 1 : 0); | 185 useNext ? 1 : 0); |
183 }; | 186 }; |
184 | 187 |
185 /** | 188 /** |
186 * Map from gesture names (AXGesture defined in ui/accessibility/ax_enums.idl) | 189 * Map from gesture names (AXGesture defined in ui/accessibility/ax_enums.idl) |
187 * to commands when in Classic mode. | 190 * to commands when in Classic mode. |
188 * @type {Object<string, string>} | 191 * @type {Object<string, string>} |
189 * @const | 192 * @const |
190 */ | 193 */ |
191 Background.GESTURE_CLASSIC_COMMAND_MAP = { | 194 Background.GESTURE_CLASSIC_COMMAND_MAP = { |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
238 }); | 241 }); |
239 } else { | 242 } else { |
240 target = this.getCurrentRange().start.node; | 243 target = this.getCurrentRange().start.node; |
241 } | 244 } |
242 | 245 |
243 if (!target) | 246 if (!target) |
244 return useNext ? ChromeVoxMode.FORCE_NEXT : ChromeVoxMode.CLASSIC; | 247 return useNext ? ChromeVoxMode.FORCE_NEXT : ChromeVoxMode.CLASSIC; |
245 | 248 |
246 // Closure complains, but clearly, |target| is not null. | 249 // Closure complains, but clearly, |target| is not null. |
247 var topLevelRoot = | 250 var topLevelRoot = |
248 AutomationUtil.getTopLevelRoot(/** @type {!AutomationNode} */(target)); | 251 AutomationUtil.getTopLevelRoot(/** @type {!AutomationNode} */ (target)); |
249 if (!topLevelRoot) | 252 if (!topLevelRoot) |
250 return useNext ? ChromeVoxMode.FORCE_NEXT : | 253 return useNext ? ChromeVoxMode.FORCE_NEXT : ChromeVoxMode.CLASSIC_COMPAT; |
251 ChromeVoxMode.CLASSIC_COMPAT; | |
252 | 254 |
253 var docUrl = topLevelRoot.docUrl || ''; | 255 var docUrl = topLevelRoot.docUrl || ''; |
254 var nextSite = this.isWhitelistedForNext_(docUrl); | 256 var nextSite = this.isWhitelistedForNext_(docUrl); |
255 var classicCompat = | 257 var classicCompat = this.isWhitelistedForClassicCompat_(docUrl); |
256 this.isWhitelistedForClassicCompat_(docUrl); | |
257 if (classicCompat && !useNext) | 258 if (classicCompat && !useNext) |
258 return ChromeVoxMode.CLASSIC_COMPAT; | 259 return ChromeVoxMode.CLASSIC_COMPAT; |
259 else if (nextSite) | 260 else if (nextSite) |
260 return ChromeVoxMode.NEXT; | 261 return ChromeVoxMode.NEXT; |
261 else if (!useNext) | 262 else if (!useNext) |
262 return ChromeVoxMode.CLASSIC; | 263 return ChromeVoxMode.CLASSIC; |
263 else | 264 else |
264 return ChromeVoxMode.FORCE_NEXT; | 265 return ChromeVoxMode.FORCE_NEXT; |
265 }, | 266 }, |
266 | 267 |
(...skipping 16 matching lines...) Expand all Loading... |
283 | 284 |
284 // Classic modes do not use the new focus highlight. | 285 // Classic modes do not use the new focus highlight. |
285 if (newMode == ChromeVoxMode.CLASSIC) | 286 if (newMode == ChromeVoxMode.CLASSIC) |
286 chrome.accessibilityPrivate.setFocusRing([]); | 287 chrome.accessibilityPrivate.setFocusRing([]); |
287 | 288 |
288 // Switch on/off content scripts. | 289 // Switch on/off content scripts. |
289 // note that |this.currentRange_| can *change* because the request is | 290 // note that |this.currentRange_| can *change* because the request is |
290 // async. Save it to ensure we're looking at the currentRange at this moment | 291 // async. Save it to ensure we're looking at the currentRange at this moment |
291 // in time. | 292 // in time. |
292 var cur = this.currentRange_; | 293 var cur = this.currentRange_; |
293 chrome.tabs.query({active: true, | 294 chrome.tabs.query({active: true, lastFocusedWindow: true}, function(tabs) { |
294 lastFocusedWindow: true}, function(tabs) { | |
295 if (newMode == ChromeVoxMode.CLASSIC) { | 295 if (newMode == ChromeVoxMode.CLASSIC) { |
296 // Generally, we don't want to inject classic content scripts as it is | 296 // Generally, we don't want to inject classic content scripts as it is |
297 // done by the extension system at document load. The exception is when | 297 // done by the extension system at document load. The exception is when |
298 // we toggle classic on manually as part of a user command. | 298 // we toggle classic on manually as part of a user command. |
299 if (oldMode == ChromeVoxMode.FORCE_NEXT) { | 299 if (oldMode == ChromeVoxMode.FORCE_NEXT) { |
300 cvox.ChromeVox.injectChromeVoxIntoTabs(tabs); | 300 cvox.ChromeVox.injectChromeVoxIntoTabs(tabs); |
301 } | 301 } |
302 } else if (newMode === ChromeVoxMode.FORCE_NEXT) { | 302 } else if (newMode === ChromeVoxMode.FORCE_NEXT) { |
303 this.disableClassicChromeVox_(); | 303 this.disableClassicChromeVox_(); |
304 } else { | 304 } else { |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
355 chrome.metricsPrivate.recordUserAction( | 355 chrome.metricsPrivate.recordUserAction( |
356 'Accessibility.ChromeVox.ToggleNextOff'); | 356 'Accessibility.ChromeVox.ToggleNextOff'); |
357 } | 357 } |
358 | 358 |
359 localStorage['useClassic'] = !useNext; | 359 localStorage['useClassic'] = !useNext; |
360 if (useNext) | 360 if (useNext) |
361 this.setCurrentRangeToFocus_(); | 361 this.setCurrentRangeToFocus_(); |
362 else | 362 else |
363 this.setCurrentRange(null); | 363 this.setCurrentRange(null); |
364 | 364 |
365 var announce = Msgs.getMsg(useNext ? | 365 var announce = |
366 'switch_to_next' : 'switch_to_classic'); | 366 Msgs.getMsg(useNext ? 'switch_to_next' : 'switch_to_classic'); |
367 cvox.ChromeVox.tts.speak( | 367 cvox.ChromeVox.tts.speak( |
368 announce, cvox.QueueMode.FLUSH, {doNotInterrupt: true}); | 368 announce, cvox.QueueMode.FLUSH, {doNotInterrupt: true}); |
369 | 369 |
370 // If the new mode is Classic, return false now so we don't announce | 370 // If the new mode is Classic, return false now so we don't announce |
371 // anything more. | 371 // anything more. |
372 return useNext; | 372 return useNext; |
373 }, | 373 }, |
374 | 374 |
375 /** | 375 /** |
376 * @override | 376 * @override |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
432 opt_focus = opt_focus === undefined ? true : opt_focus; | 432 opt_focus = opt_focus === undefined ? true : opt_focus; |
433 opt_speechProps = opt_speechProps || {}; | 433 opt_speechProps = opt_speechProps || {}; |
434 var prevRange = this.currentRange_; | 434 var prevRange = this.currentRange_; |
435 if (opt_focus) | 435 if (opt_focus) |
436 this.setFocusToRange_(range, prevRange); | 436 this.setFocusToRange_(range, prevRange); |
437 | 437 |
438 this.setCurrentRange(range); | 438 this.setCurrentRange(range); |
439 | 439 |
440 var o = new Output(); | 440 var o = new Output(); |
441 var selectedRange; | 441 var selectedRange; |
442 if (this.pageSel_ && | 442 if (this.pageSel_ && this.pageSel_.isValid() && range.isValid()) { |
443 this.pageSel_.isValid() && | |
444 range.isValid()) { | |
445 // Compute the direction of the endpoints of each range. | 443 // Compute the direction of the endpoints of each range. |
446 | 444 |
447 // Casts are ok because isValid checks node start and end nodes are | 445 // Casts are ok because isValid checks node start and end nodes are |
448 // non-null; Closure just doesn't eval enough to see it. | 446 // non-null; Closure just doesn't eval enough to see it. |
449 var startDir = | 447 var startDir = AutomationUtil.getDirection( |
450 AutomationUtil.getDirection(this.pageSel_.start.node, | 448 this.pageSel_.start.node, |
451 /** @type {!AutomationNode} */ (range.start.node)); | 449 /** @type {!AutomationNode} */ (range.start.node)); |
452 var endDir = | 450 var endDir = AutomationUtil.getDirection( |
453 AutomationUtil.getDirection(this.pageSel_.end.node, | 451 this.pageSel_.end.node, |
454 /** @type {!AutomationNode} */ (range.end.node)); | 452 /** @type {!AutomationNode} */ (range.end.node)); |
455 | 453 |
456 // Selection across roots isn't supported. | 454 // Selection across roots isn't supported. |
457 var pageRootStart = this.pageSel_.start.node.root; | 455 var pageRootStart = this.pageSel_.start.node.root; |
458 var pageRootEnd = this.pageSel_.end.node.root; | 456 var pageRootEnd = this.pageSel_.end.node.root; |
459 var curRootStart = range.start.node.root; | 457 var curRootStart = range.start.node.root; |
460 var curRootEnd = range.end.node.root; | 458 var curRootEnd = range.end.node.root; |
461 | 459 |
462 // Disallow crossing over the start of the page selection and roots. | 460 // Disallow crossing over the start of the page selection and roots. |
463 if (startDir == Dir.BACKWARD || | 461 if (startDir == Dir.BACKWARD || pageRootStart != pageRootEnd || |
464 pageRootStart != pageRootEnd || | 462 pageRootStart != curRootStart || pageRootEnd != curRootEnd) { |
465 pageRootStart != curRootStart || | |
466 pageRootEnd != curRootEnd) { | |
467 o.format('@end_selection'); | 463 o.format('@end_selection'); |
468 this.pageSel_ = null; | 464 this.pageSel_ = null; |
469 } else { | 465 } else { |
470 // Expand or shrink requires different feedback. | 466 // Expand or shrink requires different feedback. |
471 var msg; | 467 var msg; |
472 if (endDir == Dir.FORWARD && | 468 if (endDir == Dir.FORWARD && |
473 (this.pageSel_.end.node != range.end.node || | 469 (this.pageSel_.end.node != range.end.node || |
474 this.pageSel_.end.index <= range.end.index)) { | 470 this.pageSel_.end.index <= range.end.index)) { |
475 msg = '@selected'; | 471 msg = '@selected'; |
476 } else { | 472 } else { |
477 msg = '@unselected'; | 473 msg = '@unselected'; |
478 selectedRange = prevRange; | 474 selectedRange = prevRange; |
479 } | 475 } |
480 this.pageSel_ = new cursors.Range( | 476 this.pageSel_ = new cursors.Range(this.pageSel_.start, range.end); |
481 this.pageSel_.start, | |
482 range.end | |
483 ); | |
484 if (this.pageSel_) | 477 if (this.pageSel_) |
485 this.pageSel_.select(); | 478 this.pageSel_.select(); |
486 } | 479 } |
487 } else { | 480 } else { |
488 // Ensure we don't select the editable when we first encounter it. | 481 // Ensure we don't select the editable when we first encounter it. |
489 var lca = null; | 482 var lca = null; |
490 if (range.start.node && prevRange.start.node) { | 483 if (range.start.node && prevRange.start.node) { |
491 lca = AutomationUtil.getLeastCommonAncestor(prevRange.start.node, | 484 lca = AutomationUtil.getLeastCommonAncestor( |
492 range.start.node); | 485 prevRange.start.node, range.start.node); |
493 } | 486 } |
494 if (!lca || lca.state[StateType.EDITABLE] || | 487 if (!lca || lca.state[StateType.EDITABLE] || |
495 !range.start.node.state[StateType.EDITABLE]) | 488 !range.start.node.state[StateType.EDITABLE]) |
496 range.select(); | 489 range.select(); |
497 } | 490 } |
498 | 491 |
499 o.withRichSpeechAndBraille( | 492 o.withRichSpeechAndBraille( |
500 selectedRange || range, prevRange, Output.EventType.NAVIGATE) | 493 selectedRange || range, prevRange, Output.EventType.NAVIGATE) |
501 .withQueueMode(cvox.QueueMode.FLUSH); | 494 .withQueueMode(cvox.QueueMode.FLUSH); |
502 | 495 |
503 if (msg) | 496 if (msg) |
504 o.format(msg); | 497 o.format(msg); |
505 | 498 |
506 for (var prop in opt_speechProps) | 499 for (var prop in opt_speechProps) |
507 o.format('!' + prop); | 500 o.format('!' + prop); |
508 | 501 |
509 o.go(); | 502 o.go(); |
510 }, | 503 }, |
511 | 504 |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
566 return true; | 559 return true; |
567 }, | 560 }, |
568 | 561 |
569 /** | 562 /** |
570 * Returns true if the url should have Classic running. | 563 * Returns true if the url should have Classic running. |
571 * @return {boolean} | 564 * @return {boolean} |
572 * @private | 565 * @private |
573 */ | 566 */ |
574 shouldEnableClassicForUrl_: function(url) { | 567 shouldEnableClassicForUrl_: function(url) { |
575 return this.mode != ChromeVoxMode.FORCE_NEXT && | 568 return this.mode != ChromeVoxMode.FORCE_NEXT && |
576 !this.isBlacklistedForClassic_(url) && | 569 !this.isBlacklistedForClassic_(url) && !this.isWhitelistedForNext_(url); |
577 !this.isWhitelistedForNext_(url); | |
578 }, | 570 }, |
579 | 571 |
580 /** | 572 /** |
581 * Compat mode is on if any of the following are true: | 573 * Compat mode is on if any of the following are true: |
582 * 1. a url is blacklisted for Classic. | 574 * 1. a url is blacklisted for Classic. |
583 * 2. the current range is not within web content. | 575 * 2. the current range is not within web content. |
584 * @param {string} url | 576 * @param {string} url |
585 * @return {boolean} | 577 * @return {boolean} |
586 */ | 578 */ |
587 isWhitelistedForClassicCompat_: function(url) { | 579 isWhitelistedForClassicCompat_: function(url) { |
588 return (this.isBlacklistedForClassic_(url) || (this.getCurrentRange() && | 580 return (this.isBlacklistedForClassic_(url) || |
589 !this.getCurrentRange().isWebRange() && | 581 (this.getCurrentRange() && !this.getCurrentRange().isWebRange() && |
590 this.getCurrentRange().start.node.state[StateType.FOCUSED])) || false; | 582 this.getCurrentRange().start.node.state[StateType.FOCUSED])) || |
| 583 false; |
591 }, | 584 }, |
592 | 585 |
593 /** | 586 /** |
594 * @param {string} url | 587 * @param {string} url |
595 * @return {boolean} | 588 * @return {boolean} |
596 * @private | 589 * @private |
597 */ | 590 */ |
598 isBlacklistedForClassic_: function(url) { | 591 isBlacklistedForClassic_: function(url) { |
599 if (this.classicBlacklistRegExp_.test(url)) | 592 if (this.classicBlacklistRegExp_.test(url)) |
600 return true; | 593 return true; |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
671 */ | 664 */ |
672 onMessage_: function(msg, port) { | 665 onMessage_: function(msg, port) { |
673 var target = msg['target']; | 666 var target = msg['target']; |
674 var action = msg['action']; | 667 var action = msg['action']; |
675 | 668 |
676 switch (target) { | 669 switch (target) { |
677 case 'next': | 670 case 'next': |
678 if (action == 'getIsClassicEnabled') { | 671 if (action == 'getIsClassicEnabled') { |
679 var url = msg['url']; | 672 var url = msg['url']; |
680 var isClassicEnabled = this.shouldEnableClassicForUrl_(url); | 673 var isClassicEnabled = this.shouldEnableClassicForUrl_(url); |
681 port.postMessage({ | 674 port.postMessage( |
682 target: 'next', | 675 {target: 'next', isClassicEnabled: isClassicEnabled}); |
683 isClassicEnabled: isClassicEnabled | |
684 }); | |
685 } else if (action == 'enableClassicCompatForUrl') { | 676 } else if (action == 'enableClassicCompatForUrl') { |
686 var url = msg['url']; | 677 var url = msg['url']; |
687 this.classicBlacklist_.add(url); | 678 this.classicBlacklist_.add(url); |
688 if (this.currentRange_ && this.currentRange_.start.node) | 679 if (this.currentRange_ && this.currentRange_.start.node) |
689 this.setCurrentRange(this.currentRange_); | 680 this.setCurrentRange(this.currentRange_); |
690 } else if (action == 'onCommand') { | 681 } else if (action == 'onCommand') { |
691 CommandHandler.onCommand(msg['command']); | 682 CommandHandler.onCommand(msg['command']); |
692 } else if (action == 'flushNextUtterance') { | 683 } else if (action == 'flushNextUtterance') { |
693 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.FLUSH); | 684 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.FLUSH); |
694 } | 685 } |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
734 * @param {string} gesture The gesture to handle, based on the AXGesture enum | 725 * @param {string} gesture The gesture to handle, based on the AXGesture enum |
735 * defined in ui/accessibility/ax_enums.idl | 726 * defined in ui/accessibility/ax_enums.idl |
736 * @return {boolean} True if this gesture was handled. | 727 * @return {boolean} True if this gesture was handled. |
737 * @private | 728 * @private |
738 */ | 729 */ |
739 handleClassicGesture_: function(gesture) { | 730 handleClassicGesture_: function(gesture) { |
740 var command = Background.GESTURE_CLASSIC_COMMAND_MAP[gesture]; | 731 var command = Background.GESTURE_CLASSIC_COMMAND_MAP[gesture]; |
741 if (!command) | 732 if (!command) |
742 return false; | 733 return false; |
743 | 734 |
744 var msg = { | 735 var msg = {'message': 'USER_COMMAND', 'command': command}; |
745 'message': 'USER_COMMAND', | |
746 'command': command | |
747 }; | |
748 cvox.ExtensionBridge.send(msg); | 736 cvox.ExtensionBridge.send(msg); |
749 return true; | 737 return true; |
750 }, | 738 }, |
751 | 739 |
752 /** @private */ | 740 /** @private */ |
753 setCurrentRangeToFocus_: function() { | 741 setCurrentRangeToFocus_: function() { |
754 chrome.automation.getFocus(function(focus) { | 742 chrome.automation.getFocus(function(focus) { |
755 if (focus) | 743 if (focus) |
756 this.setCurrentRange(cursors.Range.fromNode(focus)); | 744 this.setCurrentRange(cursors.Range.fromNode(focus)); |
757 else | 745 else |
758 this.setCurrentRange(null); | 746 this.setCurrentRange(null); |
759 }.bind(this)); | 747 }.bind(this)); |
760 }, | 748 }, |
761 | 749 |
762 /** | 750 /** |
763 * @param {!cursors.Range} range | 751 * @param {!cursors.Range} range |
764 * @param {cursors.Range} prevRange | 752 * @param {cursors.Range} prevRange |
765 * @private | 753 * @private |
766 */ | 754 */ |
767 setFocusToRange_: function(range, prevRange) { | 755 setFocusToRange_: function(range, prevRange) { |
768 var start = range.start.node; | 756 var start = range.start.node; |
769 var end = range.end.node; | 757 var end = range.end.node; |
770 | 758 |
771 // First, see if we've crossed a root. Remove once webview handles focus | 759 // First, see if we've crossed a root. Remove once webview handles focus |
772 // correctly. | 760 // correctly. |
773 if (prevRange && prevRange.start.node && start) { | 761 if (prevRange && prevRange.start.node && start) { |
774 var entered = AutomationUtil.getUniqueAncestors( | 762 var entered = |
775 prevRange.start.node, start); | 763 AutomationUtil.getUniqueAncestors(prevRange.start.node, start); |
776 var embeddedObject = entered.find(function(f) { | 764 var embeddedObject = entered.find(function(f) { |
777 return f.role == RoleType.EMBEDDED_OBJECT; }); | 765 return f.role == RoleType.EMBEDDED_OBJECT; |
| 766 }); |
778 if (embeddedObject && !embeddedObject.state[StateType.FOCUSED]) | 767 if (embeddedObject && !embeddedObject.state[StateType.FOCUSED]) |
779 embeddedObject.focus(); | 768 embeddedObject.focus(); |
780 } | 769 } |
781 | 770 |
782 if (start.state[StateType.FOCUSED] || end.state[StateType.FOCUSED]) | 771 if (start.state[StateType.FOCUSED] || end.state[StateType.FOCUSED]) |
783 return; | 772 return; |
784 | 773 |
785 var isFocusableLinkOrControl = function(node) { | 774 var isFocusableLinkOrControl = function(node) { |
786 return node.state[StateType.FOCUSABLE] && | 775 return node.state[StateType.FOCUSABLE] && |
787 AutomationPredicate.linkOrControl(node); | 776 AutomationPredicate.linkOrControl(node); |
788 }; | 777 }; |
789 | 778 |
790 // Next, try to focus the start or end node. | 779 // Next, try to focus the start or end node. |
791 if (!AutomationPredicate.structuralContainer(start) && | 780 if (!AutomationPredicate.structuralContainer(start) && |
792 start.state[StateType.FOCUSABLE]) { | 781 start.state[StateType.FOCUSABLE]) { |
793 if (!start.state[StateType.FOCUSED]) | 782 if (!start.state[StateType.FOCUSED]) |
794 start.focus(); | 783 start.focus(); |
795 return; | 784 return; |
796 } else if (!AutomationPredicate.structuralContainer(end) && | 785 } else if ( |
| 786 !AutomationPredicate.structuralContainer(end) && |
797 end.state[StateType.FOCUSABLE]) { | 787 end.state[StateType.FOCUSABLE]) { |
798 if (!end.state[StateType.FOCUSED]) | 788 if (!end.state[StateType.FOCUSED]) |
799 end.focus(); | 789 end.focus(); |
800 return; | 790 return; |
801 } | 791 } |
802 | 792 |
803 // If a common ancestor of |start| and |end| is a link, focus that. | 793 // If a common ancestor of |start| and |end| is a link, focus that. |
804 var ancestor = AutomationUtil.getLeastCommonAncestor(start, end); | 794 var ancestor = AutomationUtil.getLeastCommonAncestor(start, end); |
805 while (ancestor && ancestor.root == start.root) { | 795 while (ancestor && ancestor.root == start.root) { |
806 if (isFocusableLinkOrControl(ancestor)) { | 796 if (isFocusableLinkOrControl(ancestor)) { |
(...skipping 13 matching lines...) Expand all Loading... |
820 }; | 810 }; |
821 | 811 |
822 /** | 812 /** |
823 * Converts a list of globs, as used in the extension manifest, to a regular | 813 * Converts a list of globs, as used in the extension manifest, to a regular |
824 * expression that matches if and only if any of the globs in the list matches. | 814 * expression that matches if and only if any of the globs in the list matches. |
825 * @param {!Array<string>} globs | 815 * @param {!Array<string>} globs |
826 * @return {!RegExp} | 816 * @return {!RegExp} |
827 * @private | 817 * @private |
828 */ | 818 */ |
829 Background.globsToRegExp_ = function(globs) { | 819 Background.globsToRegExp_ = function(globs) { |
830 return new RegExp('^(' + globs.map(function(glob) { | 820 return new RegExp( |
831 return glob.replace(/[.+^$(){}|[\]\\]/g, '\\$&') | 821 '^(' + |
832 .replace(/\*/g, '.*') | 822 globs |
833 .replace(/\?/g, '.'); | 823 .map(function(glob) { |
834 }).join('|') + ')$'); | 824 return glob.replace(/[.+^$(){}|[\]\\]/g, '\\$&') |
| 825 .replace(/\*/g, '.*') |
| 826 .replace(/\?/g, '.'); |
| 827 }) |
| 828 .join('|') + |
| 829 ')$'); |
835 }; | 830 }; |
836 | 831 |
837 new Background(); | 832 new Background(); |
838 | 833 |
839 }); // goog.scope | 834 }); // goog.scope |
OLD | NEW |