goog.require('cvox.AriaUtil');
goog.require('cvox.ChromeVox');
goog.require('cvox.DomPredicates');
+goog.require('cvox.Memoize');
goog.require('cvox.NodeState');
goog.require('cvox.XpathUtil');
* it is inside the document view-port as some sites try to communicate with
* screen readers with such elements.
* @param {Node} node The node to determine as visible or not.
- * @param {Object=} opt_options In certain cases, we already have information
+ * @param {{checkAncestors: (boolean|undefined),
+ checkDescendants: (boolean|undefined)}=} opt_options
+ * In certain cases, we already have information
* on the context of the node. To improve performance and avoid redundant
* operations, you may wish to turn certain visibility checks off by
* passing in an options object. The following properties are configurable:
* @return {boolean} True if the node is visible.
*/
cvox.DomUtil.isVisible = function(node, opt_options) {
- opt_options = opt_options || {};
- if (typeof(opt_options.checkAncestors) === 'undefined') {
- opt_options.checkAncestors = true;
- }
- if (typeof(opt_options.checkDescendants) === 'undefined') {
- opt_options.checkDescendants = true;
+ var checkAncestors = true;
+ var checkDescendants = true;
+ if (opt_options) {
+ if (opt_options.checkAncestors !== undefined) {
+ checkAncestors = opt_options.checkAncestors;
+ }
+ if (opt_options.checkDescendants !== undefined) {
+ checkDescendants = opt_options.checkDescendants;
+ }
}
+ // Generate a unique function name based on the arguments, and
+ // memoize the result of the internal visibility computation so that
+ // within the same call stack, we don't need to recompute the visibility
+ // of the same node.
+ var fname = 'isVisible-' + checkAncestors + '-' + checkDescendants;
+ return /** @type {boolean} */ (cvox.Memoize.memoize(
+ cvox.DomUtil.computeIsVisible_.bind(
+ this, node, checkAncestors, checkDescendants), fname, node));
+};
+
+/**
+ * Implementation of |cvox.DomUtil.isVisible|.
+ * @param {Node} node The node to determine as visible or not.
+ * @param {boolean} checkAncestors True if we should check the ancestor chain
+ * for forced invisibility traits of descendants.
+ * @param {boolean} checkDescendants True if we should consider descendants of
+ * the given node for visible elements.
+ * @return {boolean} True if the node is visible.
+ * @private
+ */
+cvox.DomUtil.computeIsVisible_ = function(
+ node, checkAncestors, checkDescendants) {
// If the node is an iframe that we can never inject into, consider it hidden.
if (node.tagName == 'IFRAME' && !node.src) {
return false;
}
// Confirm that no subtree containing node is invisible.
- if (opt_options.checkAncestors &&
+ if (checkAncestors &&
cvox.DomUtil.hasInvisibleAncestor_(node)) {
return false;
}
// If the node's subtree has a visible node, we declare it as visible.
- var recursive = opt_options.checkDescendants;
- if (cvox.DomUtil.hasVisibleNodeSubtree_(node, recursive)) {
+ if (cvox.DomUtil.hasVisibleNodeSubtree_(node, checkDescendants)) {
return true;
}
/**
* Checks the ancestor chain for the given node for invisibility. If an
* ancestor is invisible and this cannot be overriden by a descendant,
- * we return true.
+ * we return true. If the element is not a descendant of the document
+ * element it will return true (invisible).
* @param {Node} node The node to check the ancestor chain for.
* @return {boolean} True if a descendant is invisible.
* @private
if (cvox.DomUtil.isInvisibleStyle(style, true)) {
return true;
}
+ // Once we reach the document element and we haven't found anything
+ // invisible yet, we're done. If we exit the while loop and never found
+ // the document element, the element wasn't part of the DOM and thus it's
+ // invisible.
+ if (ancestor == document.documentElement) {
+ return false;
+ }
}
- return false;
+ return true;
};
* @return {boolean} True if the node has content.
*/
cvox.DomUtil.hasContent = function(node) {
+ // Memoize the result of the internal content computation so that
+ // within the same call stack, we don't need to redo the computation
+ // on the same node twice.
+ return /** @type {boolean} */ (cvox.Memoize.memoize(
+ cvox.DomUtil.computeHasContent_.bind(this), 'hasContent', node));
+};
+
+/**
+ * Internal implementation of |cvox.DomUtil.hasContent|.
+ *
+ * @param {Node} node The node to be checked.
+ * @return {boolean} True if the node has content.
+ * @private
+ */
+cvox.DomUtil.computeHasContent_ = function(node) {
// nodeType:8 == COMMENT_NODE
if (node.nodeType == 8) {
return false;