node_breadcrumb.js revision cedac228d2dd51db4b79ea1e72c7f249408ee061
1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5/** 6 * @fileoverview Responsible for tagging nodes used by ChromeVox. 7 */ 8 9goog.provide('cvox.NodeBreadcrumb'); 10 11goog.require('cvox.ChromeVox'); 12 13 14 15/** 16 * Responsible for tagging nodes and tracking those nodes. 17 * @constructor 18 */ 19cvox.NodeBreadcrumb = function() { 20 /** 21 * Counter to be incremented each time HistoryEvent tries to tag a previously 22 * untagged node. 23 * @type {number} 24 */ 25 this.cvTagCounter_ = 0; 26}; 27goog.addSingletonGetter(cvox.NodeBreadcrumb); 28 29/** 30 * The attribute to mark nodes that have been touched, and in what order. 31 * @type {string} 32 * @const 33 * NOTE: not private because tester is using this 34 */ 35cvox.NodeBreadcrumb.TOUCHED_TAG = 'chromevoxtag'; 36 37/** 38 * The attribute to mark nodes needed to replicate results with. 39 * @type {string} 40 * @const 41 * @private 42 */ 43cvox.NodeBreadcrumb.NEEDED_TAG_ = 'chromevoxneeded'; 44 45 46/** 47 * Tags the current node. 48 * @return {number} The tag number. 49 */ 50cvox.NodeBreadcrumb.prototype.tagCurrentNode = function() { 51 var cvTag; 52 var currentNode = cvox.ChromeVox.navigationManager.getCurrentNode(); 53 while (currentNode && !currentNode.hasAttribute) { 54 currentNode = currentNode.parentNode; 55 } 56 if (!currentNode) { 57 cvTag = -1; 58 } else if (currentNode.hasAttribute(cvox.NodeBreadcrumb.TOUCHED_TAG)) { 59 cvTag = currentNode.getAttribute(cvox.NodeBreadcrumb.TOUCHED_TAG); 60 } else { 61 cvTag = this.cvTagCounter_; 62 currentNode.setAttribute(cvox.NodeBreadcrumb.TOUCHED_TAG, cvTag); 63 this.cvTagCounter_++; 64 } 65 return cvTag; 66}; 67 68 69/** 70 * Marks all elements that need to be in the test case, starting at the 71 * elements that have been tagged. 72 * @param {Node} node Root of the subtree which to mark. 73 * @private 74 */ 75cvox.NodeBreadcrumb.prototype.smartStart_ = function(node) { 76 for (var i = 0; i < node.children.length; ++i) { 77 var child = node.children[i]; 78 this.smartStart_(child); 79 if (child.getAttribute && 80 !goog.isNull(child.getAttribute(cvox.NodeBreadcrumb.TOUCHED_TAG))) { 81 this.setNeeded_(child); 82 } 83 } 84}; 85 86 87/** 88 * Recursively marks all elements that need to be in the test case. 89 * Note: modifies the node passed in. 90 * @param {Node} node The node to mark. 91 * @private 92 */ 93cvox.NodeBreadcrumb.prototype.setNeeded_ = function(node) { 94 if (!node) { 95 return; 96 } 97 98 if (node.getAttribute && 99 goog.isNull(node.getAttribute(cvox.NodeBreadcrumb.NEEDED_TAG_))) { 100 node.setAttribute(cvox.NodeBreadcrumb.NEEDED_TAG_, true); 101 102 // only the parent needs to be added 103 // if the siblings are needed, then some ancestor 104 // would have had chromevoxtag set, in which case 105 // we copy the whole subtree of that ancestor anyways 106 if (node.nodeName !== 'body') { 107 this.setNeeded_(node.parentElement); 108 } 109 } 110}; 111 112 113/** 114 * Clones the part of the dom that is needed to recreate the test case. 115 * The nodes must have been marked first by calling smartStart_. 116 * @param {Node|Text} node The root of the subtree to clone. 117 * @return {Node|Text} The cloned subtree. 118 * @private 119 */ 120cvox.NodeBreadcrumb.prototype.smartClone_ = function(node) { 121 var skipattrs = {}; 122 skipattrs[cvox.NodeBreadcrumb.TOUCHED_TAG] = true; 123 skipattrs[cvox.NodeBreadcrumb.NEEDED_TAG_] = true; 124 125 if (node.getAttribute && node.getAttribute(cvox.NodeBreadcrumb.TOUCHED_TAG)) { 126 return cvox.DomUtil.deepClone(node, skipattrs); 127 } 128 129 var ret = cvox.DomUtil.shallowChildlessClone(node, skipattrs); 130 131 for (var i = 0; i < node.childNodes.length; ++i) { 132 var child = node.childNodes[i]; 133 if (child.getAttribute && 134 !goog.isNull(child.getAttribute(cvox.NodeBreadcrumb.NEEDED_TAG_))) { 135 ret.appendChild(this.smartClone_(child)); 136 } 137 } 138 return ret; 139}; 140 141 142/** 143 * Returns a sting containing the html needed to replicate the test. 144 * @return {Node} The subset of the dom that was walked. 145 */ 146cvox.NodeBreadcrumb.prototype.dumpWalkedDom = function() { 147 this.smartStart_(document.body); 148 return this.smartClone_(document.body); 149}; 150 151 152/** 153 * Retrieves the ChromeVox tag for the current node. 154 * 155 * @return {number} The ChromeVox tag or -1 if there is an error. 156 */ 157cvox.NodeBreadcrumb.getCurrentNodeTag = function() { 158 var currentNode = cvox.ChromeVox.navigationManager.getCurrentNode(); 159 while (currentNode && !currentNode.hasAttribute) { 160 currentNode = currentNode.parentNode; 161 } 162 if (currentNode && currentNode.hasAttribute(cvox.NodeBreadcrumb.TOUCHED_TAG)) { 163 return currentNode.getAttribute(cvox.NodeBreadcrumb.TOUCHED_TAG); 164 } else { 165 return -1; 166 } 167}; 168