| // 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; |
| }; |