Index: components/autofill/ios/browser/resources/suggestion_controller.js |
diff --git a/components/autofill/ios/browser/resources/suggestion_controller.js b/components/autofill/ios/browser/resources/suggestion_controller.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..1c4ab63082c3329af1c18ee2f94bbc783687d5ce |
--- /dev/null |
+++ b/components/autofill/ios/browser/resources/suggestion_controller.js |
@@ -0,0 +1,314 @@ |
+// Copyright 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+// Installs suggestion management functions on the |__gCrWeb| object. |
+ |
+/** |
+ * Namespace for this file. It depends on |__gCrWeb| having already been |
+ * injected. |
+ */ |
+__gCrWeb['suggestion'] = {}; |
+ |
+/** |
+ * Returns the first element in |elements| that is later than |elementToCompare| |
+ * in tab order. |
+ * |
+ * @param {Element} elementToCompare The element to start searching forward in |
+ * tab order from. |
+ * @param {NodeList} elementList Elements in which the first element that is |
+ * later than |elementToCompare| in tab order is to be returned if there is |
+ * one; |elements| should be sorted in DOM tree order and should contain |
+ * |elementToCompare|. |
+ * @return {Element} the first element in |elements| that is later than |
+ * |elementToCompare| in tab order if there is one; null otherwise. |
+ */ |
+__gCrWeb.suggestion.getNextElementInTabOrder = |
+ function(elementToCompare, elementList) { |
+ var elements = []; |
+ for (var i = 0; i < elementList.length; ++i) { |
+ elements[i] = elementList[i]; |
+ } |
+ // There is no defined behavior if the element is not reachable. Here the |
+ // next reachable element in DOM tree order is returned. (This is what is |
+ // observed in Mobile Safari and Chrome Desktop, if |elementToCompare| is not |
+ // the last element in DOM tree order). |
+ // TODO(chenyu): investigate and simulate Mobile Safari's behavior when |
+ // |elementToCompare| is the last one in DOM tree order. |
+ if (!__gCrWeb.suggestion.isSequentiallyReachable(elementToCompare)) { |
+ var indexToCompare = elements.indexOf(elementToCompare); |
+ if (indexToCompare === elements.length - 1 || indexToCompare === -1) { |
+ return null; |
+ } |
+ for (var index = indexToCompare + 1; index < elements.length; ++index) { |
+ var element = elements[index]; |
+ if (__gCrWeb.suggestion.isSequentiallyReachable(element)) { |
+ return element; |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ // Returns true iff |element1| that has DOM tree position |index1| is after |
+ // |element2| that has DOM tree position |index2| in tab order. It is assumed |
+ // |index1 !== index2|. |
+ var comparator = function(element1, index1, element2, index2) { |
+ var tabOrder1 = __gCrWeb.suggestion.getTabOrder(element1); |
+ var tabOrder2 = __gCrWeb.suggestion.getTabOrder(element2); |
+ return tabOrder1 > tabOrder2 || |
+ (tabOrder1 === tabOrder2 && index1 > index2); |
+ }; |
+ return __gCrWeb.suggestion.getFormElementAfter( |
+ elementToCompare, elements, comparator); |
+}; |
+ |
+/** |
+ * Returns the last element in |elements| that is earlier than |
+ * |elementToCompare| in tab order. |
+ * |
+ * @param {Element} elementToCompare The element to start searching backward in |
+ * tab order from. |
+ * @param {NodeList} elementList Elements in which the last element that is |
+ * earlier than |elementToCompare| in tab order is to be returned if |
+ * there is one; |elements| should be sorted in DOM tree order and it should |
+ * contain |elementToCompare|. |
+ * @return {Element} the last element in |elements| that is earlier than |
+ * |elementToCompare| in tab order if there is one; null otherwise. |
+ */ |
+__gCrWeb.suggestion.getPreviousElementInTabOrder = |
+ function(elementToCompare, elementList) { |
+ var elements = []; |
+ for (var i = 0; i < elementList.length; ++i) { |
+ elements[i] = elementList[i]; |
+ } |
+ |
+ // There is no defined behavior if the element is not reachable. Here the |
+ // previous reachable element in DOM tree order is returned. |
+ if (!__gCrWeb.suggestion.isSequentiallyReachable(elementToCompare)) { |
+ var indexToCompare = elements.indexOf(elementToCompare); |
+ if (elementToCompare === 0 || elementToCompare === -1) { |
+ return null; |
+ } |
+ for (var index = indexToCompare - 1; index >= 0; --index) { |
+ var element = elements[index]; |
+ if (__gCrWeb.suggestion.isSequentiallyReachable(element)) { |
+ return element; |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ // Returns true iff |element1| that has DOM tree position |index1| is before |
+ // |element2| that has DOM tree position |index2| in tab order. It is assumed |
+ // |index1 !== index2|. |
+ var comparator = function(element1, index1, element2, index2) { |
+ var tabOrder1 = __gCrWeb.suggestion.getTabOrder(element1); |
+ var tabOrder2 = __gCrWeb.suggestion.getTabOrder(element2); |
+ return tabOrder1 < tabOrder2 || |
+ (tabOrder1 === tabOrder2 && index1 < index2); |
+ }; |
+ |
+ return __gCrWeb.suggestion.getFormElementAfter( |
+ elementToCompare, elements, comparator); |
+}; |
+ |
+/** |
+ * Given an element |elementToCompare|, such as |
+ * |__gCrWeb.suggestion.isSequentiallyReachable(elementToCompare)|, and a list |
+ * of |elements| which are sorted in DOM tree order and contains |
+ * |elementToCompare|, this method returns the next element in |elements| after |
+ * |elementToCompare| in the order defined by |comparator|, where an element is |
+ * said to be 'after' anotherElement if and only if |
+ * comparator(element, indexOfElement, anotherElement, anotherIndex) is true. |
+ * |
+ * @param {Element} elementToCompare The element to be compared. |
+ * @param {Array.<Element>} elements Elements to compare; |elements| should be |
+ * sorted in DOM tree order and it should contain |elementToCompare|. |
+ * @param {function} comparator A function that returns a boolean, given an |
+ * Element |element1|, an integer that represents |element1|'s position in |
+ * DOM tree order, an Element |element2| and an integer that represents |
+ * |element2|'s position in DOM tree order. |
+ * @return {Element} The element that satisfies the conditions given above. |
+ */ |
+__gCrWeb.suggestion.getFormElementAfter = |
+ function(elementToCompare, elements, comparator) { |
+ // Computes the index |indexToCompare| of |elementToCompare| in |element|. |
+ var indexToCompare = elements.indexOf(elementToCompare); |
+ if (indexToCompare === -1) { |
+ return null; |
+ } |
+ |
+ var result = null; |
+ var resultIndex = -1; |
+ for (var index = 0; index < elements.length; ++index) { |
+ if (index === indexToCompare) { |
+ continue; |
+ } |
+ var element = elements[index]; |
+ if (!__gCrWeb.suggestion.isSequentiallyReachable(element)) { |
+ continue; |
+ } |
+ |
+ if (comparator(element, index, elementToCompare, indexToCompare)) { |
+ if (!result) { |
+ result = element; |
+ resultIndex = index; |
+ } else { |
+ if (comparator(result, resultIndex, element, index)) { |
+ result = element; |
+ resultIndex = index; |
+ } |
+ } |
+ } |
+ } |
+ return result; |
+}; |
+ |
+/** |
+ * Returns if an element is reachable in sequential navigation. |
+ * |
+ * @param {Element} element The element that is to be examined. |
+ * @return {boolean} Whether an element is reachable in sequential navigation. |
+ */ |
+__gCrWeb.suggestion.isSequentiallyReachable = function(element) { |
+ var tabIndex = element.tabIndex; |
+ // It is proposed in W3C that if tabIndex is omitted or parsing the value |
+ // returns an error, the user agent should follow platform conventions to |
+ // determine whether the element can be reached using sequential focus |
+ // navigation, and if so, what its relative order should be. No document is |
+ // found on the platform conventions in this case on iOS. |
+ // |
+ // There is a list of elements for which the tabIndex focus flags are |
+ // suggested to be set in this case in W3C proposal. It is observed that in |
+ // UIWebview parsing the tabIndex of an element in this list returns 0 if it |
+ // is omitted or it is set to be an invalid value, undefined, null or NaN. So |
+ // here it is assumed that all the elements that have invalid tabIndex is |
+ // not reachable in sequential focus navigation. |
+ // |
+ // It is proposed in W3C that if tabIndex is a negative integer, the user |
+ // agent should not allow the element to be reached using sequential focus |
+ // navigation. |
+ if ((!tabIndex && tabIndex != 0) || tabIndex < 0) { |
+ return false; |
+ } |
+ if (element.type === 'hidden' || element.hasAttribute('disabled')) { |
+ return false; |
+ } |
+ |
+ // false is returned if |element| is neither an input nor a select. Note based |
+ // on this condition, false is returned for an iframe (as Mobile Safari does |
+ // not navigate to elements in an iframe, there is no need to recursively |
+ // check if there is a reachable element in an iframe). |
+ if (element.tagName !== 'INPUT' && |
+ element.tagName !== 'SELECT' && |
+ element.tagName !== 'TEXTAREA') { |
+ return false; |
+ } |
+ |
+ // The following elements are skipped when navigating using 'Prev' and "Next' |
+ // buttons in Mobile Safari. |
+ if (element.tagName === 'INPUT' && |
+ (element.type === 'submit' || |
+ element.type === 'reset' || |
+ element.type === 'image' || |
+ element.type === 'button' || |
+ element.type === 'range' || |
+ element.type === 'radio' || |
+ element.type === 'checkbox')) { |
+ return false; |
+ } |
+ |
+ // Expensive, final check that the element is not concealed. |
+ return __gCrWeb['common'].isElementVisible(element); |
+}; |
+ |
+/** |
+ * It is proposed in W3C an element that has a tabIndex greater than zero should |
+ * be placed before any focusable element whose tabIndex is equal to zero in |
+ * sequential focus navigation order. Here a value adjusted from tabIndex that |
+ * reflects this order is returned. That is, given |element1| and |element2|, |
+ * if |__gCrWeb.suggestion.getTabOrder(element1) > |
+ * __gCrWeb.suggestion.getTabOrder(element2)|, then |element1| is after |
+ * |element2| in sequential navigation. |
+ * |
+ * @param {Element} element The element of which the sequential navigation order |
+ * information is returned. |
+ * @return {Number} An adjusted value that reflect |element|'s position in the |
+ * sequential navigation. |
+ */ |
+__gCrWeb.suggestion.getTabOrder = function(element) { |
+ var tabIndex = element.tabIndex; |
+ if (tabIndex === 0) { |
+ return Number.MAX_VALUE; |
+ } |
+ return tabIndex; |
+}; |
+ |
+/** |
+ * Returns the element named |fieldName| in the form specified by |formName|, |
+ * if it exists. |
+ * |
+ * @param {string} formName The name of the form containing the element. |
+ * @param {string} fieldName The name of the field containing the element. |
+ * @return {Element} The element if found, otherwise null. |
+ */ |
+__gCrWeb.suggestion.getFormElement = function(formName, fieldName) { |
+ var form = __gCrWeb.common.getFormElementFromIdentifier(formName); |
+ if (!form) |
+ return null; |
+ return __gCrWeb.getElementByNameWithParent(form, fieldName); |
+}; |
+ |
+/** |
+ * Focuses the next element in the sequential focus navigation. No operation |
+ * if there is no such element. |
+ */ |
+__gCrWeb.suggestion['selectNextElement'] = function(formName, fieldName) { |
+ var currentElement = |
+ formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) : |
+ document.activeElement; |
+ var nextElement = __gCrWeb.suggestion.getNextElementInTabOrder(currentElement, |
+ document.all); |
+ if (nextElement) { |
+ nextElement.focus(); |
+ } |
+}; |
+ |
+/** |
+ * Focuses the previous element in the sequential focus navigation. No |
+ * operation if there is no such element. |
+ */ |
+__gCrWeb.suggestion['selectPreviousElement'] = function(formName, fieldName) { |
+ var currentElement = |
+ formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) : |
+ document.activeElement; |
+ var prevElement = __gCrWeb.suggestion.getPreviousElementInTabOrder( |
+ currentElement, document.all); |
+ if (prevElement) { |
+ prevElement.focus(); |
+ } |
+}; |
+ |
+/** |
+ * @return {boolean} Whether there is an element in the sequential navigation |
+ * after the currently active element. |
+ */ |
+__gCrWeb.suggestion['hasNextElement'] = function(formName, fieldName) { |
+ var currentElement = |
+ formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) : |
+ document.activeElement; |
+ return __gCrWeb.suggestion.getNextElementInTabOrder(currentElement, |
+ document.all) !== null; |
+}; |
+ |
+/** |
+ * @return {boolean} Whether there is an element in the sequential navigation |
+ * before the currently active element. |
+ */ |
+__gCrWeb.suggestion['hasPreviousElement'] = function(formName, fieldName) { |
+ var currentElement = |
+ formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) : |
+ document.activeElement; |
+ return __gCrWeb.suggestion.getPreviousElementInTabOrder( |
+ currentElement, document.all) !== null; |
+}; |