group_util.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 Some utilities for defining what groups are.
7 */
8
9
10goog.provide('cvox.GroupUtil');
11
12goog.require('cvox.AriaUtil');
13goog.require('cvox.DomUtil');
14
15
16/**
17 * If a node contains more characters than this, it should not be treated
18 * as a leaf node by the smart navigation algorithm.
19 *
20 * This number was determined by looking at the average number of
21 * characters in a paragraph:
22 * http://www.fullondesign.co.uk/design/usability/
23 * 285-how-many-characters-per-a-page-is-normal.htm
24 * and then trying it out on a few popular websites (CNN, BBC,
25 * Google Search, etc.) and making sure it made sense.
26 * @type {number}
27 * @private
28 * @const
29 */
30cvox.GroupUtil.MAX_CHARCOUNT_ = 1500;
31
32
33/**
34 * If a node contains any of these elements, it should not be treated
35 * as a leaf node by the smart navigation algorithm.
36 * @type {string}
37 * @private
38 * @const
39 */
40cvox.GroupUtil.BREAKOUT_SELECTOR_ = 'blockquote,' +
41    'button,' +
42    'code,' +
43    'form,' +
44    'frame,' +
45    'h1,' +
46    'h2,' +
47    'h3,' +
48    'h4,' +
49    'h5,' +
50    'h6,' +
51    'hr,' +
52    'iframe,' +
53    'input,' +
54    'object,' +
55    'ol,' +
56    'p,' +
57    'pre,' +
58    'select,' +
59    'table,' +
60    'tr,' +
61    'ul,' +
62    'math,' +
63  // This takes care of MathJax expressions.
64    'span.math,' +
65// TODO (sorge) Do we want to group all math or only display math?
66//    '[mode="display"],' +
67    // Aria widget roles
68    '[role~="alert ' +
69    'alertdialog ' +
70    'button ' +
71    'checkbox ' +
72    'combobox ' +
73    'dialog ' +
74    'log ' +
75    'marquee ' +
76    'menubar ' +
77    'progressbar ' +
78    'radio ' +
79    'radiogroup ' +
80    'scrollbar ' +
81    'slider ' +
82    'spinbutton ' +
83    'status ' +
84    'tab ' +
85    'tabpanel ' +
86    'textbox ' +
87    'toolbar ' +
88    'tooltip ' +
89    'treeitem ' +
90    // Aria structure roles
91    'article ' +
92    'document ' +
93    'group ' +
94    'heading ' +
95    'img ' +
96    'list ' +
97    'math ' +
98    'region ' +
99    'row ' +
100    'separator"]';
101
102
103/**
104 * Returns true if this is a leaf node for groups.
105 * true for a node => true for all child nodes
106 * true if node has no children
107 * @param {!Node} node The node to check.
108 * @return {boolean} true if this is at the "leaf node" level or lower
109 * for this granularity.
110 */
111cvox.GroupUtil.isLeafNode = function(node) {
112  // TODO (stoarca): Write test to make sure that this function satisfies
113  // the restriction given above.
114  if (node.tagName == 'LABEL') {
115    return cvox.DomUtil.isLeafNode(node);
116  }
117  if (cvox.DomUtil.isLeafNode(node)) {
118    return true;
119  }
120
121  if (!cvox.DomUtil.isSemanticElt(node)) {
122    var breakingNodes = node.querySelectorAll(
123        cvox.GroupUtil.BREAKOUT_SELECTOR_);
124
125    for (var i = 0; i < breakingNodes.length; ++i) {
126      if (cvox.DomUtil.hasContent(breakingNodes[i])) {
127        return false;
128      }
129    }
130  }
131
132  if (cvox.AriaUtil.isCompositeControl(node) &&
133      !cvox.DomUtil.isFocusable(node)) {
134    return false;
135  }
136
137  var content = cvox.DomUtil.collapseWhitespace(
138      cvox.DomUtil.getValue(node) + ' ' +
139      cvox.DomUtil.getName(node));
140  if (content.length > cvox.GroupUtil.MAX_CHARCOUNT_) {
141    return false;
142  }
143
144  if (content.replace(/\s/g, '') === '') {
145    // Text only contains whitespace
146    return false;
147  }
148
149  return true;
150};
151