blob: f14d8c292422fef028a22d3b9323e89072d80c82 [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.
goog.provide('cvox.TraverseMath');
goog.require('cvox.ChromeVox');
goog.require('cvox.DomUtil');
goog.require('cvox.SemanticTree');
/**
* Initializes the traversal with the provided math node.
*
* @constructor
*/
cvox.TraverseMath = function() {
/**
* The active math <MATH> node. In this context, "active" means that this is
* the math expression the TraverseMath object is navigating.
* @type {Node}
*/
this.activeMath = null;
/**
* The node currently under inspection.
* @type {Node}
*/
this.activeNode = null;
/**
* Dictionary of all LaTeX elements in the page if there are any.
* @type {!Object<!Node>}
* @private
*/
this.allTexs_ = {};
/**
* Dictionary of all MathJaxs elements in the page if there are any.
* @type {!Object<!Node>}
* @private
*/
this.allMathjaxs_ = {};
/**
* Dictionary of all MathJaxs elements that have not yet been translated at
* page load or during MathJax rendering.
* @type {!Object<!Node>}
* @private
*/
this.todoMathjaxs_ = {};
/**
* When traversing a Mathjax node this will contain the internal
* MathML representation of the node.
* @type {Node}
*/
this.activeMathmlHost = null;
/**
* Semantic representation of the current node.
* @type {Node}
*/
this.activeSemanticHost = null;
/**
* List of domain names.
* @type {Array<string>}
*/
this.allDomains = [];
/**
* List of style names.
* @type {Array<string>}
*/
this.allStyles = [];
/**
* Current domain.
* @type {string}
*/
this.domain = 'default';
/**
* Current style.
* @type {string}
*/
this.style = 'short';
/**
* Initialize special objects if necessary.
*/
if (cvox.ChromeVox.mathJax) {
this.initializeMathjaxs();
this.initializeAltMaths();
}
};
goog.addSingletonGetter(cvox.TraverseMath);
/**
* @type {boolean}
* @private
*/
cvox.TraverseMath.setSemantic_ = false;
/**
* Toggles the semantic setting.
* @return {boolean} True if semantic interpretation is switched on. False
* otherwise.
*/
cvox.TraverseMath.toggleSemantic = function() {
return cvox.TraverseMath.setSemantic_ = !cvox.TraverseMath.setSemantic_;
};
/**
* Initializes a traversal of a math expression.
* @param {Node} node A MathML node.
*/
cvox.TraverseMath.prototype.initialize = function(node) {
if (cvox.DomUtil.isMathImg(node)) {
// If a node has a cvoxid attribute we know that it contains a LaTeX
// expression that we have rewritten into its corresponding MathML
// representation, which we can speak and walk.
if (!node.hasAttribute('cvoxid')) {
return;
}
var cvoxid = node.getAttribute('cvoxid');
node = this.allTexs_[cvoxid];
}
if (cvox.DomUtil.isMathJax(node)) {
this.activeMathmlHost = this.allMathjaxs_[node.getAttribute('id')];
}
this.activeMath = this.activeMathmlHost || node;
this.activeNode = this.activeMathmlHost || node;
if (this.activeNode && cvox.TraverseMath.setSemantic_ &&
this.activeNode.nodeType == Node.ELEMENT_NODE) {
this.activeNode =
(new cvox.SemanticTree(/** @type {!Element} */ (this.activeNode))).xml();
}
};
/**
* Adds a mapping of a MathJax node to its MathML representation to the
* dictionary of MathJax elements.
* @param {!Node} mml The MathML node.
* @param {string} id The MathJax node id.
*/
cvox.TraverseMath.prototype.addMathjax = function(mml, id) {
var spanId = cvox.DomUtil.getMathSpanId(id);
if (spanId) {
this.allMathjaxs_[spanId] = mml;
} else {
this.redoMathjaxs(mml, id);
}
};
/**
* Retries to compute MathML representations of MathJax elements, if
* they have not been filled in during rendering.
* @param {!Node} mml The MathML node.
* @param {string} id The MathJax node id.
*/
cvox.TraverseMath.prototype.redoMathjaxs = function(mml, id) {
var fetch = goog.bind(function() {this.addMathjax(mml, id);}, this);
setTimeout(fetch, 500);
};
/**
* Initializes the MathJax to MathML mapping.
* We first try to get all MathJax elements that are already being rendered.
* Secondly, we register a signal to get updated on all elements that are
* rendered or re-rendered later.
*/
cvox.TraverseMath.prototype.initializeMathjaxs = function() {
var callback =
goog.bind(function(mml, id) {
this.addMathjax(mml, id);
}, this);
cvox.ChromeVox.mathJax.isMathjaxActive(
function(bool) {
if (bool) {
cvox.ChromeVox.mathJax.getAllJax(callback);
cvox.ChromeVox.mathJax.registerSignal(callback, 'New Math');
}
});
};
/**
* Initializes the elements in the page that we identify as potentially
* containing tex or asciimath alt text.
*/
cvox.TraverseMath.prototype.initializeAltMaths = function() {
if (!document.querySelector(
cvox.DomUtil.altMathQuerySelector('tex') + ', ' +
cvox.DomUtil.altMathQuerySelector('asciimath'))) {
return;
}
var callback = goog.bind(
function(mml, id) {
this.allTexs_[id] = mml;
}, this);
// Inject a minimalistic version of MathJax into the page.
cvox.ChromeVox.mathJax.injectScripts();
// Once MathJax is injected we harvest all Latex and AsciiMath in alt
// attributes and translate them to MathML expression.
cvox.ChromeVox.mathJax.isMathjaxActive(
function(active) {
if (active) {
cvox.ChromeVox.mathJax.configMediaWiki();
cvox.ChromeVox.mathJax.getAllTexs(callback);
cvox.ChromeVox.mathJax.getAllAsciiMaths(callback);
}
});
};
/**
* Moves to the next leaf node in the current Math expression if it exists.
* @param {boolean} reverse True if reversed. False by default.
* @param {function(!Node):boolean} pred Predicate deciding what a leaf is.
* @return {Node} The next node.
*/
cvox.TraverseMath.prototype.nextLeaf = function(reverse, pred) {
if (this.activeNode && this.activeMath) {
var next = pred(this.activeNode) ?
cvox.DomUtil.directedFindNextNode(
this.activeNode, this.activeMath, reverse, pred) :
cvox.DomUtil.directedFindFirstNode(this.activeNode, reverse, pred);
if (next) {
this.activeNode = next;
}
}
return this.activeNode;
};
// TODO (sorge) Refactor this logic into single walkers.
/**
* Returns a string with the content of the active node.
* @return {string} The active content.
*/
cvox.TraverseMath.prototype.activeContent = function() {
return this.activeNode.textContent;
};
/**
* Moves to the next subtree from a given node in a depth first fashion.
* @param {boolean} reverse True if reversed. False by default.
* @param {function(!Node):boolean} pred Predicate deciding what a subtree is.
* @return {Node} The next subtree.
*/
cvox.TraverseMath.prototype.nextSubtree = function(reverse, pred) {
if (!this.activeNode || !this.activeMath) {
return null;
}
if (!reverse) {
var child = cvox.DomUtil.directedFindFirstNode(
this.activeNode, reverse, pred);
if (child) {
this.activeNode = child;
} else {
var next = cvox.DomUtil.directedFindNextNode(
this.activeNode, this.activeMath, reverse, pred);
if (next) {
this.activeNode = next;
}
}
} else {
if (this.activeNode == this.activeMath) {
var child = cvox.DomUtil.directedFindDeepestNode(
this.activeNode, reverse, pred);
if (child != this.activeNode) {
this.activeNode = child;
return this.activeNode;
}
}
var prev = cvox.DomUtil.directedFindNextNode(
this.activeNode, this.activeMath, reverse, pred, true, true);
if (prev) {
this.activeNode = prev;
}
}
return this.activeNode;
};
/**
* left or right in the math expression.
* Navigation is bounded by the presence of a sibling.
* @param {boolean} r True to move left; false to move right.
* @return {Node} The result.
*/
cvox.TraverseMath.prototype.nextSibling = function(r) {
if (!this.activeNode || !this.activeMath) {
return null;
}
var node = this.activeNode;
node = r ? node.previousSibling : node.nextSibling;
if (!node) {
return null;
}
this.activeNode = node;
return this.activeNode;
};
/**
* Moves up or down the math expression.
* Navigation is bounded by the root math expression.
* @param {boolean} r True to move up; false to move down.
* @return {Node} The result.
*/
cvox.TraverseMath.prototype.nextParentChild = function(r) {
if (!this.activeNode || !this.activeMath) {
return null;
}
if (this.activeNode == this.activeMath && r) {
return null;
}
var node = this.activeNode;
node = r ? node.parentNode : node.firstChild;
if (!node) {
return null;
}
this.activeNode = node;
return this.activeNode;
};
/**
* Adds a list of domains and styles to the existing one.
* @param {Array<string>} domains List of domain names.
* @param {Array<string>} styles List of style names.
*/
cvox.TraverseMath.prototype.addDomainsAndStyles = function(domains, styles) {
this.allDomains.push.apply(
this.allDomains,
domains.filter(
goog.bind(function(x) {return this.allDomains.indexOf(x) < 0;},
this)));
this.allStyles.push.apply(
this.allStyles,
styles.filter(
goog.bind(function(x) {return this.allStyles.indexOf(x) < 0;},
this)));
};
/**
* Gets a list of domains and styles from the symbol and function mappings.
* Depending on the platform they either live in the background page or
* in the android math map.
*/
cvox.TraverseMath.prototype.initDomainsAndStyles = function() {
if (cvox.ChromeVox.host['mathMap']) {
this.addDomainsAndStyles(
cvox.ChromeVox.host['mathMap'].allDomains,
cvox.ChromeVox.host['mathMap'].allStyles);
} else {
cvox.ChromeVox.host.sendToBackgroundPage(
{'target': 'Math',
'action': 'getDomains'});
}
};
/**
* Sets the domain for the TraverseMath object to the next one in the list
* restarting from the first, if necessary.
* @return {string} The name of the newly set domain.
*/
cvox.TraverseMath.prototype.cycleDomain = function() {
this.initDomainsAndStyles();
var index = this.allDomains.indexOf(this.domain);
if (index == -1) {
return this.domain;
}
this.domain = this.allDomains[(++index) % this.allDomains.length];
return this.domain;
};
/**
* Sets the style for the TraverseMath object to the next one in the list
* restarting from the first, if necessary.
* @return {string} The name of the newly set style.
*/
cvox.TraverseMath.prototype.cycleStyle = function() {
this.initDomainsAndStyles();
var index = this.allStyles.indexOf(this.style);
if (index == -1) {
return this.domain;
}
this.style = this.allStyles[(++index) % this.allStyles.length];
return this.style;
};
/**
* Sets the domain for the TraverseMath object.
* @param {string} domain Name of the domain.
* @private
*/
cvox.TraverseMath.prototype.setDomain_ = function(domain) {
if (this.allDomains.indexOf(domain) != -1) {
this.domain = domain;
} else {
this.domain = 'default';
}
};
/**
* Sets the style for the TraverseMath object.
* @param {string} style Name of the style.
* @private
*/
cvox.TraverseMath.prototype.setStyle_ = function(style) {
if (this.allStyles.indexOf(style) != -1) {
this.style = style;
} else {
this.style = 'default';
}
};
/**
* Gets the active node attached to the current document.
* @return {Node} The active node, if it exists.
*/
cvox.TraverseMath.prototype.getAttachedActiveNode = function() {
var node = this.activeNode;
if (!node || node.nodeType != Node.ELEMENT_NODE) {
return null;
}
var id = node.getAttribute('spanID');
return document.getElementById(id);
};