blob: 2c011d49cabde71b4d4e526fa346129a9116298e [file] [log] [blame]
// 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);
};