| 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 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 54 this.currentRange_ = null; | 54 this.currentRange_ = null; |
| 55 | 55 |
| 56 /** | 56 /** |
| 57 * Which variant of ChromeVox is active. | 57 * Which variant of ChromeVox is active. |
| 58 * @type {ChromeVoxMode} | 58 * @type {ChromeVoxMode} |
| 59 * @private | 59 * @private |
| 60 */ | 60 */ |
| 61 this.mode_ = ChromeVoxMode.CLASSIC; | 61 this.mode_ = ChromeVoxMode.CLASSIC; |
| 62 | 62 |
| 63 /** @type {!ClassicCompatibility} @private */ | 63 /** @type {!ClassicCompatibility} @private */ |
| 64 this.compat_ = new ClassicCompatibility(this.mode_ === ChromeVoxMode.COMPAT); | 64 this.compat_ = new ClassicCompatibility(); |
| 65 | 65 |
| 66 // Manually bind all functions to |this|. | 66 // Manually bind all functions to |this|. |
| 67 for (var func in this) { | 67 for (var func in this) { |
| 68 if (typeof(this[func]) == 'function') | 68 if (typeof(this[func]) == 'function') |
| 69 this[func] = this[func].bind(this); | 69 this[func] = this[func].bind(this); |
| 70 } | 70 } |
| 71 | 71 |
| 72 /** | 72 /** |
| 73 * Maps an automation event to its listener. | 73 * Maps an automation event to its listener. |
| 74 * @type {!Object<EventType, function(Object) : void>} | 74 * @type {!Object<EventType, function(Object) : void>} |
| 75 */ | 75 */ |
| 76 this.listeners_ = { | 76 this.listeners_ = { |
| 77 alert: this.onEventDefault, | 77 alert: this.onEventDefault, |
| 78 focus: this.onEventDefault, | 78 focus: this.onEventDefault, |
| 79 hover: this.onEventDefault, | 79 hover: this.onEventDefault, |
| 80 loadComplete: this.onLoadComplete, | 80 loadComplete: this.onLoadComplete, |
| 81 menuStart: this.onEventDefault, | 81 menuStart: this.onEventDefault, |
| 82 menuEnd: this.onEventDefault, | 82 menuEnd: this.onEventDefault, |
| 83 menuListValueChanged: this.onEventDefault, | 83 menuListValueChanged: this.onEventDefault, |
| 84 textChanged: this.onTextOrTextSelectionChanged, | 84 textChanged: this.onTextOrTextSelectionChanged, |
| 85 textSelectionChanged: this.onTextOrTextSelectionChanged, | 85 textSelectionChanged: this.onTextOrTextSelectionChanged, |
| 86 valueChanged: this.onValueChanged | 86 valueChanged: this.onValueChanged |
| 87 }; | 87 }; |
| 88 | 88 |
| 89 // Register listeners for ... | 89 // Register listeners for ... |
| 90 // Desktop. | |
| 91 chrome.automation.getDesktop(this.onGotDesktop); | 90 chrome.automation.getDesktop(this.onGotDesktop); |
| 91 chrome.commands.onCommand.addListener(this.onGotCommand); |
| 92 }; | 92 }; |
| 93 | 93 |
| 94 Background.prototype = { | 94 Background.prototype = { |
| 95 /** Forces ChromeVox Next to be active for all tabs. */ | 95 /** Forces ChromeVox Next to be active for all tabs. */ |
| 96 forceChromeVoxNextActive: function() { | 96 forceChromeVoxNextActive: function() { |
| 97 this.setChromeVoxMode(ChromeVoxMode.FORCE_NEXT); | 97 this.setChromeVoxMode(ChromeVoxMode.FORCE_NEXT); |
| 98 }, | 98 }, |
| 99 | 99 |
| 100 /** | 100 /** |
| 101 * Handles all setup once a new automation tree appears. | 101 * Handles all setup once a new automation tree appears. |
| (...skipping 18 matching lines...) Expand all Loading... |
| 120 type: chrome.automation.EventType.loadComplete}); | 120 type: chrome.automation.EventType.loadComplete}); |
| 121 } | 121 } |
| 122 } | 122 } |
| 123 }, | 123 }, |
| 124 | 124 |
| 125 /** | 125 /** |
| 126 * Handles chrome.commands.onCommand. | 126 * Handles chrome.commands.onCommand. |
| 127 * @param {string} command | 127 * @param {string} command |
| 128 * @param {boolean=} opt_skipCompat Whether to skip compatibility checks. | 128 * @param {boolean=} opt_skipCompat Whether to skip compatibility checks. |
| 129 */ | 129 */ |
| 130 onGotCommand: function(command, opt_skipCompat) { | 130 onGotCommand: function(command, opt_skipCompat) { |
| 131 if (this.mode_ === ChromeVoxMode.CLASSIC) { |
| 132 if (this.compat_.onGotClassicCommand(command)) |
| 133 return; |
| 134 } |
| 135 |
| 131 if (!this.currentRange_) | 136 if (!this.currentRange_) |
| 132 return; | 137 return; |
| 133 | 138 |
| 134 if (!opt_skipCompat) { | 139 if (!opt_skipCompat && this.mode_ === ChromeVoxMode.COMPAT) { |
| 135 if (this.compat_.onGotCommand(command)) | 140 if (this.compat_.onGotCommand(command)) |
| 136 return; | 141 return; |
| 137 } | 142 } |
| 138 | 143 |
| 139 if (this.mode_ === ChromeVoxMode.CLASSIC) | |
| 140 return; | |
| 141 | |
| 142 var current = this.currentRange_; | 144 var current = this.currentRange_; |
| 143 var dir = Dir.FORWARD; | 145 var dir = Dir.FORWARD; |
| 144 var pred = null; | 146 var pred = null; |
| 145 var predErrorMsg = undefined; | 147 var predErrorMsg = undefined; |
| 146 switch (command) { | 148 switch (command) { |
| 147 case 'nextCharacter': | 149 case 'nextCharacter': |
| 148 current = current.move(cursors.Unit.CHARACTER, Dir.FORWARD); | 150 current = current.move(cursors.Unit.CHARACTER, Dir.FORWARD); |
| 149 break; | 151 break; |
| 150 case 'previousCharacter': | 152 case 'previousCharacter': |
| 151 current = current.move(cursors.Unit.CHARACTER, Dir.BACKWARD); | 153 current = current.move(cursors.Unit.CHARACTER, Dir.BACKWARD); |
| (...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 321 current = cursors.Range.fromNode(node); | 323 current = cursors.Range.fromNode(node); |
| 322 } else { | 324 } else { |
| 323 cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg(predErrorMsg), | 325 cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg(predErrorMsg), |
| 324 cvox.QueueMode.FLUSH); | 326 cvox.QueueMode.FLUSH); |
| 325 return; | 327 return; |
| 326 } | 328 } |
| 327 } | 329 } |
| 328 | 330 |
| 329 if (current) { | 331 if (current) { |
| 330 // TODO(dtseng): Figure out what it means to focus a range. | 332 // TODO(dtseng): Figure out what it means to focus a range. |
| 331 current.getStart().getNode().focus(); | 333 var actionNode = current.getStart().getNode(); |
| 334 if (actionNode.role == chrome.automation.RoleType.inlineTextBox) |
| 335 actionNode = actionNode.parent; |
| 336 actionNode.focus(); |
| 332 | 337 |
| 333 var prevRange = this.currentRange_; | 338 var prevRange = this.currentRange_; |
| 334 this.currentRange_ = current; | 339 this.currentRange_ = current; |
| 340 |
| 335 new Output().withSpeechAndBraille( | 341 new Output().withSpeechAndBraille( |
| 336 this.currentRange_, prevRange, Output.EventType.NAVIGATE) | 342 this.currentRange_, prevRange, Output.EventType.NAVIGATE) |
| 337 .go(); | 343 .go(); |
| 338 } | 344 } |
| 339 }, | 345 }, |
| 340 | 346 |
| 341 /** | 347 /** |
| 342 * Provides all feedback once ChromeVox's focus changes. | 348 * Provides all feedback once ChromeVox's focus changes. |
| 343 * @param {Object} evt | 349 * @param {Object} evt |
| 344 */ | 350 */ |
| 345 onEventDefault: function(evt) { | 351 onEventDefault: function(evt) { |
| 346 var node = evt.target; | 352 var node = evt.target; |
| 347 | 353 |
| 348 if (!node) | 354 if (!node) |
| 349 return; | 355 return; |
| 350 | 356 |
| 351 var prevRange = this.currentRange_; | 357 var prevRange = this.currentRange_; |
| 352 | 358 |
| 353 this.currentRange_ = cursors.Range.fromNode(node); | 359 this.currentRange_ = cursors.Range.fromNode(node); |
| 354 | 360 |
| 355 // Check to see if we've crossed roots. Only care about focused roots. | 361 // Check to see if we've crossed roots. Continue if we've crossed roots or |
| 356 if (!prevRange || | 362 // are not within web content. |
| 363 if (node.root.role == 'desktop' || |
| 364 !prevRange || |
| 357 (prevRange.getStart().getNode().root != node.root && | 365 (prevRange.getStart().getNode().root != node.root && |
| 358 node.root.focused)) | 366 node.root.focused)) |
| 359 this.setupChromeVoxVariants_(node.root.docUrl || ''); | 367 this.setupChromeVoxVariants_(node.root.docUrl || ''); |
| 360 | 368 |
| 361 // Don't process nodes inside of web content if ChromeVox Next is inactive. | 369 // Don't process nodes inside of web content if ChromeVox Next is inactive. |
| 362 if (node.root.role != chrome.automation.RoleType.desktop && | 370 if (node.root.role != chrome.automation.RoleType.desktop && |
| 363 this.mode_ === ChromeVoxMode.CLASSIC) { | 371 this.mode_ === ChromeVoxMode.CLASSIC) { |
| 364 chrome.accessibilityPrivate.setFocusRing([]); | 372 chrome.accessibilityPrivate.setFocusRing([]); |
| 365 return; | 373 return; |
| 366 } | 374 } |
| 367 | 375 |
| 376 // Don't output if focused node hasn't changed. |
| 377 if (prevRange && |
| 378 evt.type == 'focus' && |
| 379 this.currentRange_.equals(prevRange)) |
| 380 return; |
| 381 |
| 368 new Output().withSpeechAndBraille( | 382 new Output().withSpeechAndBraille( |
| 369 this.currentRange_, prevRange, evt.type) | 383 this.currentRange_, prevRange, evt.type) |
| 370 .go(); | 384 .go(); |
| 371 }, | 385 }, |
| 372 | 386 |
| 373 /** | 387 /** |
| 374 * Provides all feedback once a load complete event fires. | 388 * Provides all feedback once a load complete event fires. |
| 375 * @param {Object} evt | 389 * @param {Object} evt |
| 376 */ | 390 */ |
| 377 onLoadComplete: function(evt) { | 391 onLoadComplete: function(evt) { |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 505 } | 519 } |
| 506 output.withSpeech(range, null, Output.EventType.NAVIGATE); | 520 output.withSpeech(range, null, Output.EventType.NAVIGATE); |
| 507 output.go(); | 521 output.go(); |
| 508 }, | 522 }, |
| 509 | 523 |
| 510 /** | 524 /** |
| 511 * @return {boolean} | 525 * @return {boolean} |
| 512 * @private | 526 * @private |
| 513 */ | 527 */ |
| 514 isWhitelistedForCompat_: function(url) { | 528 isWhitelistedForCompat_: function(url) { |
| 515 return url.indexOf('chrome://md-settings') != -1; | 529 return url.indexOf('chrome://md-settings') != -1 || url === ''; |
| 516 }, | 530 }, |
| 517 | 531 |
| 518 /** | 532 /** |
| 519 * @private | 533 * @private |
| 520 * @param {string} url | 534 * @param {string} url |
| 521 * @return {boolean} Whether the given |url| is whitelisted. | 535 * @return {boolean} Whether the given |url| is whitelisted. |
| 522 */ | 536 */ |
| 523 isWhitelistedForNext_: function(url) { | 537 isWhitelistedForNext_: function(url) { |
| 524 return this.whitelist_.some(function(item) { | 538 return this.whitelist_.some(function(item) { |
| 525 return url.indexOf(item) != -1; | 539 return url.indexOf(item) != -1; |
| 526 }.bind(this)); | 540 }.bind(this)); |
| 527 }, | 541 }, |
| 528 | 542 |
| 529 /** | 543 /** |
| 530 * Setup ChromeVox variants. | 544 * Setup ChromeVox variants. |
| 531 * @param {string} url | 545 * @param {string} url |
| 532 * @private | 546 * @private |
| 533 */ | 547 */ |
| 534 setupChromeVoxVariants_: function(url) { | 548 setupChromeVoxVariants_: function(url) { |
| 535 this.compat_.active = this.isWhitelistedForCompat_(url); | |
| 536 var mode = this.mode_; | 549 var mode = this.mode_; |
| 537 if (this.compat_.active) | 550 if (mode != ChromeVoxMode.FORCE_NEXT) { |
| 538 mode = ChromeVoxMode.COMPAT; | 551 if (this.isWhitelistedForCompat_(url)) |
| 539 else if (this.isWhitelistedForNext_(url)) | 552 mode = ChromeVoxMode.COMPAT; |
| 540 mode = ChromeVoxMode.NEXT; | 553 else if (this.isWhitelistedForNext_(url)) |
| 541 else if (mode != ChromeVoxMode.FORCE_NEXT) | 554 mode = ChromeVoxMode.NEXT; |
| 542 mode = ChromeVoxMode.CLASSIC; | 555 else |
| 556 mode = ChromeVoxMode.CLASSIC; |
| 557 } |
| 543 | 558 |
| 544 this.setChromeVoxMode(mode); | 559 this.setChromeVoxMode(mode); |
| 545 }, | 560 }, |
| 546 | 561 |
| 547 /** | 562 /** |
| 548 * Disables classic ChromeVox. | 563 * Disables classic ChromeVox. |
| 549 * @param {number} tabId The tab where ChromeVox classic is running in. | 564 * @param {number} tabId The tab where ChromeVox classic is running in. |
| 550 */ | 565 */ |
| 551 disableClassicChromeVox_: function(tabId) { | 566 disableClassicChromeVox_: function(tabId) { |
| 552 chrome.tabs.executeScript( | 567 chrome.tabs.executeScript( |
| 553 tabId, | 568 tabId, |
| 554 {'code': 'try { window.disableChromeVox(); } catch(e) { }\n', | 569 {'code': 'try { window.disableChromeVox(); } catch(e) { }\n', |
| 555 'allFrames': true}); | 570 'allFrames': true}); |
| 556 }, | 571 }, |
| 557 | 572 |
| 558 /** | 573 /** |
| 559 * Sets the current ChromeVox mode. | 574 * Sets the current ChromeVox mode. |
| 560 * @param {ChromeVoxMode} mode | 575 * @param {ChromeVoxMode} mode |
| 561 */ | 576 */ |
| 562 setChromeVoxMode: function(mode) { | 577 setChromeVoxMode: function(mode) { |
| 563 if (mode === ChromeVoxMode.NEXT || | |
| 564 mode === ChromeVoxMode.COMPAT || | |
| 565 mode === ChromeVoxMode.FORCE_NEXT) { | |
| 566 if (!chrome.commands.onCommand.hasListener(this.onGotCommand)) | |
| 567 chrome.commands.onCommand.addListener(this.onGotCommand); | |
| 568 } else { | |
| 569 if (chrome.commands.onCommand.hasListener(this.onGotCommand)) | |
| 570 chrome.commands.onCommand.removeListener(this.onGotCommand); | |
| 571 } | |
| 572 | |
| 573 chrome.tabs.query({active: true}, function(tabs) { | 578 chrome.tabs.query({active: true}, function(tabs) { |
| 574 if (mode === ChromeVoxMode.CLASSIC) { | 579 if (mode === ChromeVoxMode.CLASSIC) { |
| 575 // This case should do nothing because Classic gets injected by the | 580 // This case should do nothing because Classic gets injected by the |
| 576 // extension system via our manifest. Once ChromeVox Next is enabled | 581 // extension system via our manifest. Once ChromeVox Next is enabled |
| 577 // for tabs, re-enable. | 582 // for tabs, re-enable. |
| 578 // cvox.ChromeVox.injectChromeVoxIntoTabs(tabs); | 583 // cvox.ChromeVox.injectChromeVoxIntoTabs(tabs); |
| 579 } else { | 584 } else { |
| 580 tabs.forEach(function(tab) { | 585 tabs.forEach(function(tab) { |
| 581 this.disableClassicChromeVox_(tab.id); | 586 this.disableClassicChromeVox_(tab.id); |
| 582 }.bind(this)); | 587 }.bind(this)); |
| 583 } | 588 } |
| 584 }.bind(this)); | 589 }.bind(this)); |
| 585 | 590 |
| 586 this.compat_.active = mode === ChromeVoxMode.COMPAT; | |
| 587 this.mode_ = mode; | 591 this.mode_ = mode; |
| 588 } | 592 } |
| 589 }; | 593 }; |
| 590 | 594 |
| 591 /** @type {Background} */ | 595 /** @type {Background} */ |
| 592 global.backgroundObj = new Background(); | 596 global.backgroundObj = new Background(); |
| 593 | 597 |
| 594 }); // goog.scope | 598 }); // goog.scope |
| OLD | NEW |