| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /* |
| * A mock text-to-speech engine for tests. |
| * This class has functions and callbacks necessary for accessibility extensions |
| * (Select-to-Speak, ChromeVox) to function. It keeps track of the utterances |
| * currently being spoken, and whether TTS should be speaking or is stopped. |
| * @constructor |
| */ |
| var MockTts = function() { |
| /** |
| * @type {Array<string>} |
| * @private |
| */ |
| this.pendingUtterances_ = []; |
| |
| /** |
| * @type {boolean} |
| * @private |
| */ |
| this.currentlySpeaking_ = false; |
| |
| /** |
| * A list of callbacks to call each time speech is requested. |
| * These are stored such that the last one should be called |
| * first. Each should only be used once. |
| * @type {Array<function(string)>} |
| * @private |
| */ |
| this.speechCallbackStack_ = []; |
| |
| /** |
| * Options object for speech. |
| * @type {onEvent: !function({type: string, charIndex: number})} |
| * @private |
| */ |
| this.options_ = null; |
| |
| /** |
| * Whether TTS events will be queued instead of sent immediately. |
| * @private {boolean} |
| */ |
| this.waitToSendEvents_ = false; |
| |
| /** |
| * TTS engine events waiting to be sent. Each entry in the array is an array |
| * of two elements: [options, event], where |options| is the TTS options |
| * during the time of that event, and |event| is the event isself. |
| * @private {!Array<!Array<!Object>>} |
| */ |
| this.pendingEvents_ = []; |
| |
| /** |
| * @enum {string} |
| * @see https://developer.chrome.com/extensions/tts#type-EventType |
| */ |
| this.EventType = chrome.tts.EventType; |
| |
| this.onVoicesChanged = { |
| addListener: listener => {}, |
| removeListener: listener => {}, |
| }; |
| }; |
| |
| MockTts.prototype = { |
| // Functions based on methods in |
| // https://developer.chrome.com/extensions/tts |
| speak(utterance, options) { |
| this.pendingUtterances_.push(utterance); |
| this.currentlySpeaking_ = true; |
| if (options && options.onEvent) { |
| this.options_ = options; |
| this.sendEvent({type: this.EventType.START, charIndex: 0}); |
| } |
| if (this.speechCallbackStack_.length > 0) { |
| this.speechCallbackStack_.pop()(utterance); |
| } |
| }, |
| finishPendingUtterance() { |
| this.pendingUtterances_ = []; |
| this.currentlySpeaking_ = false; |
| if (this.options_) { |
| this.sendEvent({type: this.EventType.END}); |
| } |
| }, |
| /** |
| * Mock the speaking process of TTS, and simulate a word end event. |
| * @param {number} nextStartIndex The start char index of the word to be |
| * spoken. |
| */ |
| speakUntilCharIndex(nextStartIndex) { |
| this.currentlySpeaking_ = true; |
| if (this.options_) { |
| this.sendEvent({type: this.EventType.WORD, charIndex: nextStartIndex}); |
| } |
| }, |
| stop() { |
| this.pendingUtterances_ = []; |
| this.currentlySpeaking_ = false; |
| if (this.options_) { |
| this.sendEvent({type: this.EventType.INTERRUPTED}); |
| this.options_ = null; |
| } |
| }, |
| getVoices(callback) { |
| callback([{ |
| voiceName: 'English US', |
| lang: 'en-US', |
| eventTypes: [ |
| this.EventType.START, |
| this.EventTypeEND, |
| this.EventType.WORD, |
| this.EventType.CANCELLED, |
| ], |
| }]); |
| }, |
| isSpeaking(callback) { |
| callback(this.currentlySpeaking_); |
| }, |
| // Functions for testing |
| currentlySpeaking() { |
| return this.currentlySpeaking_; |
| }, |
| pendingUtterances() { |
| return this.pendingUtterances_; |
| }, |
| setOnSpeechCallbacks(callbacks) { |
| this.speechCallbackStack_ = callbacks.reverse(); |
| }, |
| getOptions() { |
| return this.options_; |
| }, |
| sendEvent(event) { |
| if (!this.options_) { |
| return; |
| } |
| if (this.waitToSendEvents_) { |
| // Queue event if set to wait on events. |
| this.pendingEvents_.push([this.options_, event]); |
| return; |
| } |
| this.options_.onEvent(event); |
| }, |
| setWaitToSendEvents(waitToSendEvents) { |
| this.waitToSendEvents_ = waitToSendEvents; |
| }, |
| sendPendingEvents() { |
| while (this.pendingEvents_.length > 0) { |
| const [options, event] = this.pendingEvents_.pop(); |
| options.onEvent(event); |
| } |
| }, |
| }; |