OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 // Installs suggestion management functions on the |__gCrWeb| object. |
| 6 |
| 7 /** |
| 8 * Namespace for this file. It depends on |__gCrWeb| having already been |
| 9 * injected. |
| 10 */ |
| 11 __gCrWeb['suggestion'] = {}; |
| 12 |
| 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 } |
| 51 |
| 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); |
| 63 }; |
| 64 |
| 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 } |
| 84 |
| 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 (elementToCompare === 0 || elementToCompare === -1) { |
| 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 } |
| 100 |
| 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 }; |
| 110 |
| 111 return __gCrWeb.suggestion.getFormElementAfter( |
| 112 elementToCompare, elements, comparator); |
| 113 }; |
| 114 |
| 115 /** |
| 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. |
| 123 * |
| 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} comparator A function that returns a boolean, given an |
| 128 * Element |element1|, an integer that represents |element1|'s position in |
| 129 * DOM tree order, an Element |element2| and an integer that represents |
| 130 * |element2|'s position in DOM tree order. |
| 131 * @return {Element} The element that satisfies the conditions given above. |
| 132 */ |
| 133 __gCrWeb.suggestion.getFormElementAfter = |
| 134 function(elementToCompare, elements, comparator) { |
| 135 // Computes the index |indexToCompare| of |elementToCompare| in |element|. |
| 136 var indexToCompare = elements.indexOf(elementToCompare); |
| 137 if (indexToCompare === -1) { |
| 138 return null; |
| 139 } |
| 140 |
| 141 var result = null; |
| 142 var resultIndex = -1; |
| 143 for (var index = 0; index < elements.length; ++index) { |
| 144 if (index === indexToCompare) { |
| 145 continue; |
| 146 } |
| 147 var element = elements[index]; |
| 148 if (!__gCrWeb.suggestion.isSequentiallyReachable(element)) { |
| 149 continue; |
| 150 } |
| 151 |
| 152 if (comparator(element, index, elementToCompare, indexToCompare)) { |
| 153 if (!result) { |
| 154 result = element; |
| 155 resultIndex = index; |
| 156 } else { |
| 157 if (comparator(result, resultIndex, element, index)) { |
| 158 result = element; |
| 159 resultIndex = index; |
| 160 } |
| 161 } |
| 162 } |
| 163 } |
| 164 return result; |
| 165 }; |
| 166 |
| 167 /** |
| 168 * Returns if an element is reachable in sequential navigation. |
| 169 * |
| 170 * @param {Element} element The element that is to be examined. |
| 171 * @return {boolean} Whether an element is reachable in sequential navigation. |
| 172 */ |
| 173 __gCrWeb.suggestion.isSequentiallyReachable = function(element) { |
| 174 var tabIndex = element.tabIndex; |
| 175 // It is proposed in W3C that if tabIndex is omitted or parsing the value |
| 176 // returns an error, the user agent should follow platform conventions to |
| 177 // determine whether the element can be reached using sequential focus |
| 178 // navigation, and if so, what its relative order should be. No document is |
| 179 // found on the platform conventions in this case on iOS. |
| 180 // |
| 181 // There is a list of elements for which the tabIndex focus flags are |
| 182 // suggested to be set in this case in W3C proposal. It is observed that in |
| 183 // UIWebview parsing the tabIndex of an element in this list returns 0 if it |
| 184 // is omitted or it is set to be an invalid value, undefined, null or NaN. So |
| 185 // here it is assumed that all the elements that have invalid tabIndex is |
| 186 // not reachable in sequential focus navigation. |
| 187 // |
| 188 // It is proposed in W3C that if tabIndex is a negative integer, the user |
| 189 // agent should not allow the element to be reached using sequential focus |
| 190 // navigation. |
| 191 if ((!tabIndex && tabIndex != 0) || tabIndex < 0) { |
| 192 return false; |
| 193 } |
| 194 if (element.type === 'hidden' || element.hasAttribute('disabled')) { |
| 195 return false; |
| 196 } |
| 197 |
| 198 // false is returned if |element| is neither an input nor a select. Note based |
| 199 // on this condition, false is returned for an iframe (as Mobile Safari does |
| 200 // not navigate to elements in an iframe, there is no need to recursively |
| 201 // check if there is a reachable element in an iframe). |
| 202 if (element.tagName !== 'INPUT' && |
| 203 element.tagName !== 'SELECT' && |
| 204 element.tagName !== 'TEXTAREA') { |
| 205 return false; |
| 206 } |
| 207 |
| 208 // The following elements are skipped when navigating using 'Prev' and "Next' |
| 209 // buttons in Mobile Safari. |
| 210 if (element.tagName === 'INPUT' && |
| 211 (element.type === 'submit' || |
| 212 element.type === 'reset' || |
| 213 element.type === 'image' || |
| 214 element.type === 'button' || |
| 215 element.type === 'range' || |
| 216 element.type === 'radio' || |
| 217 element.type === 'checkbox')) { |
| 218 return false; |
| 219 } |
| 220 |
| 221 // Expensive, final check that the element is not concealed. |
| 222 return __gCrWeb['common'].isElementVisible(element); |
| 223 }; |
| 224 |
| 225 /** |
| 226 * It is proposed in W3C an element that has a tabIndex greater than zero should |
| 227 * be placed before any focusable element whose tabIndex is equal to zero in |
| 228 * sequential focus navigation order. Here a value adjusted from tabIndex that |
| 229 * reflects this order is returned. That is, given |element1| and |element2|, |
| 230 * if |__gCrWeb.suggestion.getTabOrder(element1) > |
| 231 * __gCrWeb.suggestion.getTabOrder(element2)|, then |element1| is after |
| 232 * |element2| in sequential navigation. |
| 233 * |
| 234 * @param {Element} element The element of which the sequential navigation order |
| 235 * information is returned. |
| 236 * @return {Number} An adjusted value that reflect |element|'s position in the |
| 237 * sequential navigation. |
| 238 */ |
| 239 __gCrWeb.suggestion.getTabOrder = function(element) { |
| 240 var tabIndex = element.tabIndex; |
| 241 if (tabIndex === 0) { |
| 242 return Number.MAX_VALUE; |
| 243 } |
| 244 return tabIndex; |
| 245 }; |
| 246 |
| 247 /** |
| 248 * Returns the element named |fieldName| in the form specified by |formName|, |
| 249 * if it exists. |
| 250 * |
| 251 * @param {string} formName The name of the form containing the element. |
| 252 * @param {string} fieldName The name of the field containing the element. |
| 253 * @return {Element} The element if found, otherwise null. |
| 254 */ |
| 255 __gCrWeb.suggestion.getFormElement = function(formName, fieldName) { |
| 256 var form = __gCrWeb.common.getFormElementFromIdentifier(formName); |
| 257 if (!form) |
| 258 return null; |
| 259 return __gCrWeb.getElementByNameWithParent(form, fieldName); |
| 260 }; |
| 261 |
| 262 /** |
| 263 * Focuses the next element in the sequential focus navigation. No operation |
| 264 * if there is no such element. |
| 265 */ |
| 266 __gCrWeb.suggestion['selectNextElement'] = function(formName, fieldName) { |
| 267 var currentElement = |
| 268 formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) : |
| 269 document.activeElement; |
| 270 var nextElement = __gCrWeb.suggestion.getNextElementInTabOrder(currentElement, |
| 271 document.all); |
| 272 if (nextElement) { |
| 273 nextElement.focus(); |
| 274 } |
| 275 }; |
| 276 |
| 277 /** |
| 278 * Focuses the previous element in the sequential focus navigation. No |
| 279 * operation if there is no such element. |
| 280 */ |
| 281 __gCrWeb.suggestion['selectPreviousElement'] = function(formName, fieldName) { |
| 282 var currentElement = |
| 283 formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) : |
| 284 document.activeElement; |
| 285 var prevElement = __gCrWeb.suggestion.getPreviousElementInTabOrder( |
| 286 currentElement, document.all); |
| 287 if (prevElement) { |
| 288 prevElement.focus(); |
| 289 } |
| 290 }; |
| 291 |
| 292 /** |
| 293 * @return {boolean} Whether there is an element in the sequential navigation |
| 294 * after the currently active element. |
| 295 */ |
| 296 __gCrWeb.suggestion['hasNextElement'] = function(formName, fieldName) { |
| 297 var currentElement = |
| 298 formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) : |
| 299 document.activeElement; |
| 300 return __gCrWeb.suggestion.getNextElementInTabOrder(currentElement, |
| 301 document.all) !== null; |
| 302 }; |
| 303 |
| 304 /** |
| 305 * @return {boolean} Whether there is an element in the sequential navigation |
| 306 * before the currently active element. |
| 307 */ |
| 308 __gCrWeb.suggestion['hasPreviousElement'] = function(formName, fieldName) { |
| 309 var currentElement = |
| 310 formName ? __gCrWeb.suggestion.getFormElement(formName, fieldName) : |
| 311 document.activeElement; |
| 312 return __gCrWeb.suggestion.getPreviousElementInTabOrder( |
| 313 currentElement, document.all) !== null; |
| 314 }; |
OLD | NEW |