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