Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / autofill / ios / browser / resources / suggestion_controller.js
blobabc088af5d10fc23aa210e136faeb15f7c626eef
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.
7 /**
8  * Namespace for this file. It depends on |__gCrWeb| having already been
9  * injected.
10  */
11 __gCrWeb['suggestion'] = {};
13 /**
14  * Returns the first element in |elements| that is later than |elementToCompare|
15  * in tab order.
16  *
17  * @param {Element} elementToCompare The element to start searching forward in
18  *     tab order from.
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
22  *     |elementToCompare|.
23  * @return {Element} the first element in |elements| that is later than
24  *     |elementToCompare| in tab order if there is one; null otherwise.
25  */
26 __gCrWeb.suggestion.getNextElementInTabOrder =
27     function(elementToCompare, elementList) {
28   var elements = [];
29   for (var i = 0; i < elementList.length; ++i) {
30     elements[i] = elementList[i];
31   }
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) {
41       return null;
42     }
43     for (var index = indexToCompare + 1; index < elements.length; ++index) {
44       var element = elements[index];
45       if (__gCrWeb.suggestion.isSequentiallyReachable(element)) {
46         return element;
47       }
48     }
49     return null;
50   }
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);
60   };
61   return __gCrWeb.suggestion.getFormElementAfter(
62       elementToCompare, elements, comparator);
65 /**
66  * Returns the last element in |elements| that is earlier than
67  * |elementToCompare| in tab order.
68  *
69  * @param {Element} elementToCompare The element to start searching backward in
70  *     tab order from.
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.
77  */
78 __gCrWeb.suggestion.getPreviousElementInTabOrder =
79     function(elementToCompare, elementList) {
80   var elements = [];
81   for (var i = 0; i < elementList.length; ++i) {
82     elements[i] = elementList[i];
83   }
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.
90       return null;
91     }
92     for (var index = indexToCompare - 1; index >= 0; --index) {
93       var element = elements[index];
94       if (__gCrWeb.suggestion.isSequentiallyReachable(element)) {
95         return element;
96       }
97     }
98     return null;
99   }
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);
109   };
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
131  *     tree order.
132  * @return {Element} The element that satisfies the conditions given above.
133  */
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) {
139     return null;
140   }
142   var result = null;
143   var resultIndex = -1;
144   for (var index = 0; index < elements.length; ++index) {
145     if (index === indexToCompare) {
146       continue;
147     }
148     var element = elements[index];
149     if (!__gCrWeb.suggestion.isSequentiallyReachable(element)) {
150       continue;
151     }
153     if (comparator(element, index, elementToCompare, indexToCompare)) {
154       if (!result) {
155         result = element;
156         resultIndex = index;
157       } else {
158         if (comparator(result, resultIndex, element, index)) {
159           result = element;
160           resultIndex = index;
161         }
162       }
163     }
164   }
165   return result;
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.
173  */
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.
181   //
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.
188   //
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
191   // navigation.
192   if ((!tabIndex && tabIndex != 0) || tabIndex < 0) {
193     return false;
194   }
195   if (element.type === 'hidden' || element.hasAttribute('disabled')) {
196     return false;
197   }
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') {
206     return false;
207   }
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')) {
219     return false;
220   }
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.
239  */
240 __gCrWeb.suggestion.getTabOrder = function(element) {
241   var tabIndex = element.tabIndex;
242   if (tabIndex === 0) {
243     return Number.MAX_VALUE;
244   }
245   return tabIndex;
249  * Returns the element named |fieldName| in the form specified by |formName|,
250  * if it exists.
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.
255  */
256 __gCrWeb.suggestion.getFormElement = function(formName, fieldName) {
257   var form = __gCrWeb.common.getFormElementFromIdentifier(formName);
258   if (!form)
259     return null;
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.
266  */
267 __gCrWeb.suggestion['selectNextElement'] = function(formName, fieldName) {
268   var currentElement =
269       formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) :
270                  document.activeElement;
271   var nextElement = __gCrWeb.suggestion.getNextElementInTabOrder(currentElement,
272                                                                  document.all);
273   if (nextElement) {
274     nextElement.focus();
275   }
279  * Focuses the previous element in the sequential focus navigation. No
280  * operation if there is no such element.
281  */
282 __gCrWeb.suggestion['selectPreviousElement'] = function(formName, fieldName) {
283   var currentElement =
284       formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) :
285                  document.activeElement;
286   var prevElement = __gCrWeb.suggestion.getPreviousElementInTabOrder(
287       currentElement, document.all);
288   if (prevElement) {
289     prevElement.focus();
290   }
294  * @return {boolean} Whether there is an element in the sequential navigation
295  *     after the currently active element.
296  */
297 __gCrWeb.suggestion['hasNextElement'] = function(formName, fieldName) {
298   var currentElement =
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.
308  */
309 __gCrWeb.suggestion['hasPreviousElement'] = function(formName, fieldName) {
310   var currentElement =
311       formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) :
312                  document.activeElement;
313   return __gCrWeb.suggestion.getPreviousElementInTabOrder(
314              currentElement, document.all) !== null;