1 // Copyright 2013 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.
5 // Installs suggestion management functions on the |__gCrWeb| object.
8 * Namespace for this file. It depends on |__gCrWeb| having already been
11 __gCrWeb['suggestion'] = {};
14 * Returns the first element in |elements| that is later than |elementToCompare|
17 * @param {Element} elementToCompare The element to start searching forward in
19 * @param {NodeList} elementList Elements in which the first element that is
20 * later than |elementToCompare| in tab order is to be returned if there is
21 * one; |elements| should be sorted in DOM tree order and should contain
23 * @return {Element} the first element in |elements| that is later than
24 * |elementToCompare| in tab order if there is one; null otherwise.
26 __gCrWeb.suggestion.getNextElementInTabOrder =
27 function(elementToCompare, elementList) {
29 for (var i = 0; i < elementList.length; ++i) {
30 elements[i] = elementList[i];
32 // There is no defined behavior if the element is not reachable. Here the
33 // next reachable element in DOM tree order is returned. (This is what is
34 // observed in Mobile Safari and Chrome Desktop, if |elementToCompare| is not
35 // the last element in DOM tree order).
36 // TODO(chenyu): investigate and simulate Mobile Safari's behavior when
37 // |elementToCompare| is the last one in DOM tree order.
38 if (!__gCrWeb.suggestion.isSequentiallyReachable(elementToCompare)) {
39 var indexToCompare = elements.indexOf(elementToCompare);
40 if (indexToCompare === elements.length - 1 || indexToCompare === -1) {
43 for (var index = indexToCompare + 1; index < elements.length; ++index) {
44 var element = elements[index];
45 if (__gCrWeb.suggestion.isSequentiallyReachable(element)) {
52 // Returns true iff |element1| that has DOM tree position |index1| is after
53 // |element2| that has DOM tree position |index2| in tab order. It is assumed
54 // |index1 !== index2|.
55 var comparator = function(element1, index1, element2, index2) {
56 var tabOrder1 = __gCrWeb.suggestion.getTabOrder(element1);
57 var tabOrder2 = __gCrWeb.suggestion.getTabOrder(element2);
58 return tabOrder1 > tabOrder2 ||
59 (tabOrder1 === tabOrder2 && index1 > index2);
61 return __gCrWeb.suggestion.getFormElementAfter(
62 elementToCompare, elements, comparator);
66 * Returns the last element in |elements| that is earlier than
67 * |elementToCompare| in tab order.
69 * @param {Element} elementToCompare The element to start searching backward in
71 * @param {NodeList} elementList Elements in which the last element that is
72 * earlier than |elementToCompare| in tab order is to be returned if
73 * there is one; |elements| should be sorted in DOM tree order and it should
74 * contain |elementToCompare|.
75 * @return {Element} the last element in |elements| that is earlier than
76 * |elementToCompare| in tab order if there is one; null otherwise.
78 __gCrWeb.suggestion.getPreviousElementInTabOrder =
79 function(elementToCompare, elementList) {
81 for (var i = 0; i < elementList.length; ++i) {
82 elements[i] = elementList[i];
85 // There is no defined behavior if the element is not reachable. Here the
86 // previous reachable element in DOM tree order is returned.
87 if (!__gCrWeb.suggestion.isSequentiallyReachable(elementToCompare)) {
88 var indexToCompare = elements.indexOf(elementToCompare);
89 if (indexToCompare <= 0) { // Ignore if first or no element is found.
92 for (var index = indexToCompare - 1; index >= 0; --index) {
93 var element = elements[index];
94 if (__gCrWeb.suggestion.isSequentiallyReachable(element)) {
101 // Returns true iff |element1| that has DOM tree position |index1| is before
102 // |element2| that has DOM tree position |index2| in tab order. It is assumed
103 // |index1 !== index2|.
104 var comparator = function(element1, index1, element2, index2) {
105 var tabOrder1 = __gCrWeb.suggestion.getTabOrder(element1);
106 var tabOrder2 = __gCrWeb.suggestion.getTabOrder(element2);
107 return tabOrder1 < tabOrder2 ||
108 (tabOrder1 === tabOrder2 && index1 < index2);
111 return __gCrWeb.suggestion.getFormElementAfter(
112 elementToCompare, elements, comparator);
116 * Given an element |elementToCompare|, such as
117 * |__gCrWeb.suggestion.isSequentiallyReachable(elementToCompare)|, and a list
118 * of |elements| which are sorted in DOM tree order and contains
119 * |elementToCompare|, this method returns the next element in |elements| after
120 * |elementToCompare| in the order defined by |comparator|, where an element is
121 * said to be 'after' anotherElement if and only if
122 * comparator(element, indexOfElement, anotherElement, anotherIndex) is true.
124 * @param {Element} elementToCompare The element to be compared.
125 * @param {Array<Element>} elements Elements to compare; |elements| should be
126 * sorted in DOM tree order and it should contain |elementToCompare|.
127 * @param {function(Element, number, Element, number):boolean} comparator A
128 * function that returns a boolean, given an Element |element1|, an integer
129 * that represents |element1|'s position in DOM tree order, an Element
130 * |element2| and an integer that represents |element2|'s position in DOM
132 * @return {Element} The element that satisfies the conditions given above.
134 __gCrWeb.suggestion.getFormElementAfter =
135 function(elementToCompare, elements, comparator) {
136 // Computes the index |indexToCompare| of |elementToCompare| in |element|.
137 var indexToCompare = elements.indexOf(elementToCompare);
138 if (indexToCompare === -1) {
143 var resultIndex = -1;
144 for (var index = 0; index < elements.length; ++index) {
145 if (index === indexToCompare) {
148 var element = elements[index];
149 if (!__gCrWeb.suggestion.isSequentiallyReachable(element)) {
153 if (comparator(element, index, elementToCompare, indexToCompare)) {
158 if (comparator(result, resultIndex, element, index)) {
169 * Returns if an element is reachable in sequential navigation.
171 * @param {Element} element The element that is to be examined.
172 * @return {boolean} Whether an element is reachable in sequential navigation.
174 __gCrWeb.suggestion.isSequentiallyReachable = function(element) {
175 var tabIndex = element.tabIndex;
176 // It is proposed in W3C that if tabIndex is omitted or parsing the value
177 // returns an error, the user agent should follow platform conventions to
178 // determine whether the element can be reached using sequential focus
179 // navigation, and if so, what its relative order should be. No document is
180 // found on the platform conventions in this case on iOS.
182 // There is a list of elements for which the tabIndex focus flags are
183 // suggested to be set in this case in W3C proposal. It is observed that in
184 // UIWebview parsing the tabIndex of an element in this list returns 0 if it
185 // is omitted or it is set to be an invalid value, undefined, null or NaN. So
186 // here it is assumed that all the elements that have invalid tabIndex is
187 // not reachable in sequential focus navigation.
189 // It is proposed in W3C that if tabIndex is a negative integer, the user
190 // agent should not allow the element to be reached using sequential focus
192 if ((!tabIndex && tabIndex != 0) || tabIndex < 0) {
195 if (element.type === 'hidden' || element.hasAttribute('disabled')) {
199 // false is returned if |element| is neither an input nor a select. Note based
200 // on this condition, false is returned for an iframe (as Mobile Safari does
201 // not navigate to elements in an iframe, there is no need to recursively
202 // check if there is a reachable element in an iframe).
203 if (element.tagName !== 'INPUT' &&
204 element.tagName !== 'SELECT' &&
205 element.tagName !== 'TEXTAREA') {
209 // The following elements are skipped when navigating using 'Prev' and "Next'
210 // buttons in Mobile Safari.
211 if (element.tagName === 'INPUT' &&
212 (element.type === 'submit' ||
213 element.type === 'reset' ||
214 element.type === 'image' ||
215 element.type === 'button' ||
216 element.type === 'range' ||
217 element.type === 'radio' ||
218 element.type === 'checkbox')) {
222 // Expensive, final check that the element is not concealed.
223 return __gCrWeb['common'].isElementVisible(element);
227 * It is proposed in W3C an element that has a tabIndex greater than zero should
228 * be placed before any focusable element whose tabIndex is equal to zero in
229 * sequential focus navigation order. Here a value adjusted from tabIndex that
230 * reflects this order is returned. That is, given |element1| and |element2|,
231 * if |__gCrWeb.suggestion.getTabOrder(element1) >
232 * __gCrWeb.suggestion.getTabOrder(element2)|, then |element1| is after
233 * |element2| in sequential navigation.
235 * @param {Element} element The element of which the sequential navigation order
236 * information is returned.
237 * @return {number} An adjusted value that reflect |element|'s position in the
238 * sequential navigation.
240 __gCrWeb.suggestion.getTabOrder = function(element) {
241 var tabIndex = element.tabIndex;
242 if (tabIndex === 0) {
243 return Number.MAX_VALUE;
249 * Returns the element named |fieldName| in the form specified by |formName|,
252 * @param {string} formName The name of the form containing the element.
253 * @param {string} fieldName The name of the field containing the element.
254 * @return {Element} The element if found, otherwise null.
256 __gCrWeb.suggestion.getFormElement = function(formName, fieldName) {
257 var form = __gCrWeb.common.getFormElementFromIdentifier(formName);
260 return __gCrWeb.getElementByNameWithParent(form, fieldName);
264 * Focuses the next element in the sequential focus navigation. No operation
265 * if there is no such element.
267 __gCrWeb.suggestion['selectNextElement'] = function(formName, fieldName) {
269 formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) :
270 document.activeElement;
271 var nextElement = __gCrWeb.suggestion.getNextElementInTabOrder(currentElement,
279 * Focuses the previous element in the sequential focus navigation. No
280 * operation if there is no such element.
282 __gCrWeb.suggestion['selectPreviousElement'] = function(formName, fieldName) {
284 formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) :
285 document.activeElement;
286 var prevElement = __gCrWeb.suggestion.getPreviousElementInTabOrder(
287 currentElement, document.all);
294 * @return {boolean} Whether there is an element in the sequential navigation
295 * after the currently active element.
297 __gCrWeb.suggestion['hasNextElement'] = function(formName, fieldName) {
299 formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) :
300 document.activeElement;
301 return __gCrWeb.suggestion.getNextElementInTabOrder(currentElement,
302 document.all) !== null;
306 * @return {boolean} Whether there is an element in the sequential navigation
307 * before the currently active element.
309 __gCrWeb.suggestion['hasPreviousElement'] = function(formName, fieldName) {
311 formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) :
312 document.activeElement;
313 return __gCrWeb.suggestion.getPreviousElementInTabOrder(
314 currentElement, document.all) !== null;