| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview Implentation of ChromeVox's public API. |
| * |
| */ |
| |
| goog.provide('cvox.ApiImplementation'); |
| goog.provide('cvox.ApiImplementation.Math'); |
| |
| goog.require('cvox.ApiUtil'); |
| goog.require('cvox.AriaUtil'); |
| goog.require('cvox.BuildInfo'); |
| goog.require('cvox.ChromeVox'); |
| goog.require('cvox.ChromeVoxJSON'); |
| goog.require('cvox.DomUtil'); |
| goog.require('cvox.ScriptInstaller'); |
| |
| /** |
| * @constructor |
| */ |
| cvox.ApiImplementation = function() { |
| }; |
| |
| /** |
| * The URL to the script loader. |
| * @type {string} |
| */ |
| cvox.ApiImplementation.siteSpecificScriptLoader; |
| |
| /** |
| * The URL base for the site-specific scripts. |
| * @type {string} |
| */ |
| cvox.ApiImplementation.siteSpecificScriptBase; |
| |
| /** |
| * Inject the API into the page and set up communication with it. |
| * @param {function()=} opt_onload A function called when the script is loaded. |
| */ |
| cvox.ApiImplementation.init = function(opt_onload) { |
| window.addEventListener('message', cvox.ApiImplementation.portSetup, true); |
| var scripts = new Array(); |
| scripts.push(cvox.ChromeVox.host.getFileSrc( |
| 'chromevox/injected/api_util.js')); |
| scripts.push(cvox.ChromeVox.host.getApiSrc()); |
| scripts.push(cvox.ApiImplementation.siteSpecificScriptLoader); |
| |
| var apiScript = cvox.ScriptInstaller.installScript(scripts, |
| 'cvoxapi', opt_onload, cvox.ApiImplementation.siteSpecificScriptBase); |
| |
| if (!apiScript) { |
| // If the API script is already installed, just re-enable it. |
| window.location.href = 'javascript:cvox.Api.internalEnable();'; |
| } |
| }; |
| |
| /** |
| * This method is called when the content script receives a message from |
| * the page. |
| * @param {Event} event The DOM event with the message data. |
| * @return {boolean} True if default event processing should continue. |
| */ |
| cvox.ApiImplementation.portSetup = function(event) { |
| if (event.data == 'cvox.PortSetup') { |
| cvox.ApiImplementation.port = event.ports[0]; |
| cvox.ApiImplementation.port.onmessage = function(event) { |
| cvox.ApiImplementation.dispatchApiMessage( |
| cvox.ChromeVoxJSON.parse(event.data)); |
| }; |
| |
| // Stop propagation since it was our message. |
| event.stopPropagation(); |
| return false; |
| } |
| return true; |
| }; |
| |
| /** |
| * Call the appropriate API function given a message from the page. |
| * @param {*} message The message. |
| */ |
| cvox.ApiImplementation.dispatchApiMessage = function(message) { |
| var method; |
| switch (message['cmd']) { |
| case 'speak': method = cvox.ApiImplementation.speak; break; |
| case 'speakNodeRef': method = cvox.ApiImplementation.speakNodeRef; break; |
| case 'stop': method = cvox.ApiImplementation.stop; break; |
| case 'playEarcon': method = cvox.ApiImplementation.playEarcon; break; |
| case 'syncToNodeRef': method = cvox.ApiImplementation.syncToNodeRef; break; |
| case 'clickNodeRef': method = cvox.ApiImplementation.clickNodeRef; break; |
| case 'getBuild': method = cvox.ApiImplementation.getBuild; break; |
| case 'getVersion': method = cvox.ApiImplementation.getVersion; break; |
| case 'getCurrentNode': method = cvox.ApiImplementation.getCurrentNode; |
| break; |
| case 'getCvoxModKeys': method = cvox.ApiImplementation.getCvoxModKeys; |
| break; |
| case 'isKeyShortcut': method = cvox.ApiImplementation.isKeyShortcut; break; |
| case 'setKeyEcho': method = cvox.ApiImplementation.setKeyEcho; break; |
| case 'Math.defineRule': |
| method = cvox.ApiImplementation.Math.defineRule; break; |
| break; |
| } |
| if (!method) { |
| throw 'Unknown API call: ' + message['cmd']; |
| } |
| |
| method.apply(cvox.ApiImplementation, message['args']); |
| }; |
| |
| /** |
| * Sets endCallback in properties to call callbackId's function. |
| * @param {Object} properties Speech properties to use for this utterance. |
| * @param {number} callbackId The callback Id. |
| * @private |
| */ |
| function setupEndCallback_(properties, callbackId) { |
| var endCallback = function() { |
| cvox.ApiImplementation.port.postMessage(cvox.ChromeVoxJSON.stringify( |
| { |
| 'id': callbackId |
| })); |
| }; |
| if (properties) { |
| properties['endCallback'] = endCallback; |
| } |
| } |
| |
| /** |
| * Speaks the given string using the specified queueMode and properties. |
| * |
| * @param {number} callbackId The callback Id. |
| * @param {string} textString The string of text to be spoken. |
| * @param {number=} queueMode Valid modes are 0 for flush; 1 for queue. |
| * @param {Object=} properties Speech properties to use for this utterance. |
| */ |
| cvox.ApiImplementation.speak = function( |
| callbackId, textString, queueMode, properties) { |
| if (cvox.ChromeVox.isActive) { |
| if (!properties) { |
| properties = {}; |
| } |
| setupEndCallback_(properties, callbackId); |
| cvox.ChromeVox.tts.speak(textString, queueMode, properties); |
| } |
| }; |
| |
| /** |
| * Speaks the given node. |
| * |
| * @param {Node} node The node that ChromeVox should be synced to. |
| * @param {number=} queueMode Valid modes are 0 for flush; 1 for queue. |
| * @param {Object=} properties Speech properties to use for this utterance. |
| */ |
| cvox.ApiImplementation.speakNode = function(node, queueMode, properties) { |
| if (cvox.ChromeVox.isActive) { |
| cvox.ChromeVox.tts.speak( |
| cvox.DomUtil.getName(node), |
| queueMode, |
| properties); |
| } |
| }; |
| |
| /** |
| * Speaks the given node. |
| * |
| * @param {number} callbackId The callback Id. |
| * @param {Object} nodeRef A serializable reference to a node. |
| * @param {number=} queueMode Valid modes are 0 for flush; 1 for queue. |
| * @param {Object=} properties Speech properties to use for this utterance. |
| */ |
| cvox.ApiImplementation.speakNodeRef = function( |
| callbackId, nodeRef, queueMode, properties) { |
| var node = cvox.ApiUtils.getNodeFromRef(nodeRef); |
| if (!properties) { |
| properties = {}; |
| } |
| setupEndCallback_(properties, callbackId); |
| cvox.ApiImplementation.speakNode(node, queueMode, properties); |
| }; |
| |
| /** |
| * Stops speech. |
| */ |
| cvox.ApiImplementation.stop = function() { |
| if (cvox.ChromeVox.isActive) { |
| cvox.ChromeVox.tts.stop(); |
| } |
| }; |
| |
| /** |
| * Plays the specified earcon sound. |
| * |
| * @param {string} earcon An earcon name. |
| * Valid names are: |
| * ALERT_MODAL |
| * ALERT_NONMODAL |
| * BULLET |
| * BUSY_PROGRESS_LOOP |
| * BUSY_WORKING_LOOP |
| * BUTTON |
| * CHECK_OFF |
| * CHECK_ON |
| * COLLAPSED |
| * EDITABLE_TEXT |
| * ELLIPSIS |
| * EXPANDED |
| * FONT_CHANGE |
| * INVALID_KEYPRESS |
| * LINK |
| * LISTBOX |
| * LIST_ITEM |
| * NEW_MAIL |
| * OBJECT_CLOSE |
| * OBJECT_DELETE |
| * OBJECT_DESELECT |
| * OBJECT_OPEN |
| * OBJECT_SELECT |
| * PARAGRAPH_BREAK |
| * SEARCH_HIT |
| * SEARCH_MISS |
| * SECTION |
| * TASK_SUCCESS |
| * WRAP |
| * WRAP_EDGE |
| * This list may expand over time. |
| */ |
| cvox.ApiImplementation.playEarcon = function(earcon) { |
| if (cvox.ChromeVox.isActive) { |
| cvox.ChromeVox.earcons.playEarconByName(earcon); |
| } |
| }; |
| |
| /** |
| * Synchronizes ChromeVox's internal cursor to a node by id. |
| * |
| * @param {Object} nodeRef A serializable reference to a node. |
| * @param {boolean=} speakNode If true, speaks out the node. |
| */ |
| cvox.ApiImplementation.syncToNodeRef = function(nodeRef, speakNode) { |
| var node = cvox.ApiUtils.getNodeFromRef(nodeRef); |
| cvox.ApiImplementation.syncToNode(node, speakNode); |
| }; |
| |
| /** |
| * Synchronizes ChromeVox's internal cursor to the targetNode. |
| * Note that this will NOT trigger reading unless given the optional argument; |
| * it is for setting the internal ChromeVox cursor so that when the user resumes |
| * reading, they will be starting from a reasonable position. |
| * |
| * @param {Node} targetNode The node that ChromeVox should be synced to. |
| * @param {boolean=} opt_speakNode If true, speaks out the node. |
| * @param {number=} opt_queueMode The queue mode to use for speaking. |
| */ |
| cvox.ApiImplementation.syncToNode = function( |
| targetNode, opt_speakNode, opt_queueMode) { |
| if (!cvox.ChromeVox.isActive) { |
| return; |
| } |
| |
| if (opt_queueMode == undefined) { |
| opt_queueMode = cvox.AbstractTts.QUEUE_MODE_CATEGORY_FLUSH; |
| } |
| |
| cvox.ChromeVox.navigationManager.updateSelToArbitraryNode(targetNode, true); |
| cvox.ChromeVox.navigationManager.updateIndicator(); |
| |
| if (opt_speakNode == undefined) { |
| opt_speakNode = false; |
| } |
| |
| // Don't speak anything if the node is hidden or invisible. |
| if (cvox.AriaUtil.isHiddenRecursive(targetNode)) { |
| opt_speakNode = false; |
| } |
| |
| if (opt_speakNode) { |
| cvox.ChromeVox.navigationManager.speakDescriptionArray( |
| cvox.ApiImplementation.getDesc_(targetNode), |
| opt_queueMode, |
| null, |
| null, |
| 'nav'); |
| } |
| |
| cvox.ChromeVox.navigationManager.getBraille().write(); |
| |
| cvox.ChromeVox.navigationManager.updatePosition(targetNode); |
| }; |
| |
| /** |
| * Get the current node that ChromeVox is on. |
| * @param {number} callbackId The callback Id. |
| */ |
| cvox.ApiImplementation.getCurrentNode = function(callbackId) { |
| var currentNode = cvox.ChromeVox.navigationManager.getCurrentNode(); |
| cvox.ApiImplementation.port.postMessage(cvox.ChromeVoxJSON.stringify( |
| { |
| 'id': callbackId, |
| 'currentNode': cvox.ApiUtils.makeNodeReference(currentNode) |
| })); |
| }; |
| |
| /** |
| * Gets the predefined description set on a node by an api call, if such |
| * a call was made. Otherwise returns the description that the NavigationManager |
| * would speak. |
| * @param {Node} node The node for which to get the description. |
| * @return {Array.<cvox.NavDescription>} The description array. |
| * @private |
| */ |
| cvox.ApiImplementation.getDesc_ = function(node) { |
| if (!node.hasAttribute('cvoxnodedesc')) { |
| return cvox.ChromeVox.navigationManager.getDescription(); |
| } |
| |
| var preDesc = cvox.ChromeVoxJSON.parse(node.getAttribute('cvoxnodedesc')); |
| var currentDesc = new Array(); |
| for (var i = 0; i < preDesc.length; ++i) { |
| var inDesc = preDesc[i]; |
| // TODO: this can probably be replaced with just NavDescription(inDesc) |
| // need test case to ensure this change will work |
| currentDesc.push(new cvox.NavDescription({ |
| context: inDesc.context, |
| text: inDesc.text, |
| userValue: inDesc.userValue, |
| annotation: inDesc.annotation |
| })); |
| } |
| return currentDesc; |
| }; |
| |
| /** |
| * Simulate a click on an element. |
| * |
| * @param {Object} nodeRef A serializable reference to a node. |
| * @param {boolean} shiftKey Specifies if shift is held down. |
| */ |
| cvox.ApiImplementation.clickNodeRef = function(nodeRef, shiftKey) { |
| cvox.DomUtil.clickElem( |
| cvox.ApiUtils.getNodeFromRef(nodeRef), shiftKey, false); |
| }; |
| |
| /** |
| * Get the ChromeVox build info string. |
| * @param {number} callbackId The callback Id. |
| */ |
| cvox.ApiImplementation.getBuild = function(callbackId) { |
| cvox.ApiImplementation.port.postMessage(cvox.ChromeVoxJSON.stringify( |
| { |
| 'id': callbackId, |
| 'build': cvox.BuildInfo.build |
| })); |
| }; |
| |
| /** |
| * Get the ChromeVox version. |
| * @param {number} callbackId The callback Id. |
| */ |
| cvox.ApiImplementation.getVersion = function(callbackId) { |
| var version = cvox.ChromeVox.version; |
| if (version == null) { |
| window.setTimeout(function() { |
| cvox.ApiImplementation.getVersion(callbackId); |
| }, 1000); |
| return; |
| } |
| cvox.ApiImplementation.port.postMessage(cvox.ChromeVoxJSON.stringify( |
| { |
| 'id': callbackId, |
| 'version': version |
| })); |
| }; |
| |
| /** |
| * Get the ChromeVox modifier keys. |
| * @param {number} callbackId The callback Id. |
| */ |
| cvox.ApiImplementation.getCvoxModKeys = function(callbackId) { |
| cvox.ApiImplementation.port.postMessage(cvox.ChromeVoxJSON.stringify( |
| { |
| 'id': callbackId, |
| 'keyCodes': cvox.KeyUtil.cvoxModKeyCodes() |
| })); |
| }; |
| |
| /** |
| * Return if the keyEvent has a key binding. |
| * @param {number} callbackId The callback Id. |
| * @param {Event} keyEvent A key event. |
| */ |
| cvox.ApiImplementation.isKeyShortcut = function(callbackId, keyEvent) { |
| var keySeq = cvox.KeyUtil.keyEventToKeySequence(keyEvent); |
| cvox.ApiImplementation.port.postMessage(cvox.ChromeVoxJSON.stringify( |
| { |
| 'id': callbackId, |
| 'isHandled': cvox.ChromeVoxKbHandler.handlerKeyMap.hasKey(keySeq) |
| })); |
| }; |
| |
| /** |
| * Set key echoing on key press. |
| * @param {boolean} keyEcho Whether key echoing should be on or off. |
| */ |
| cvox.ApiImplementation.setKeyEcho = function(keyEcho) { |
| var msg = cvox.ChromeVox.keyEcho; |
| msg[document.location.href] = keyEcho; |
| cvox.ChromeVox.host.sendToBackgroundPage({ |
| 'target': 'Prefs', |
| 'action': 'setPref', |
| 'pref': 'keyEcho', |
| 'value': JSON.stringify(msg) |
| }); |
| }; |
| |
| /** |
| * @constructor |
| */ |
| cvox.ApiImplementation.Math = function() {}; |
| |
| /** |
| * Defines a math speech rule. |
| * @param {string} name Rule name. |
| * @param {string} dynamic Dynamic constraint annotation. In the case of a |
| * math rule it consists of a domain.style string. |
| * @param {string} action An action of rule components. |
| * @param {string} prec XPath or custom function constraining match. |
| * @param {...string} constraints Additional constraints. |
| */ |
| cvox.ApiImplementation.Math.defineRule = |
| function(name, dynamic, action, prec, constraints) { |
| var mathStore = cvox.MathmlStore.getInstance(); |
| var constraintList = Array.prototype.slice.call(arguments, 4); |
| var args = [name, dynamic, action, prec].concat(constraintList); |
| |
| mathStore.defineRule.apply(mathStore, args); |
| }; |