OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // <include src="assert.js"> | 5 // <include src="assert.js"> |
6 | 6 |
7 /** | 7 /** |
8 * Alias for document.getElementById. Found elements must be HTMLElements. | 8 * Alias for document.getElementById. Found elements must be HTMLElements. |
9 * @param {string} id The ID of the element to find. | 9 * @param {string} id The ID of the element to find. |
10 * @return {HTMLElement} The found element or null if not found. | 10 * @return {HTMLElement} The found element or null if not found. |
(...skipping 22 matching lines...) Expand all Loading... |
33 * @param {string} msg The text to be pronounced. | 33 * @param {string} msg The text to be pronounced. |
34 */ | 34 */ |
35 function announceAccessibleMessage(msg) { | 35 function announceAccessibleMessage(msg) { |
36 var element = document.createElement('div'); | 36 var element = document.createElement('div'); |
37 element.setAttribute('aria-live', 'polite'); | 37 element.setAttribute('aria-live', 'polite'); |
38 element.style.position = 'fixed'; | 38 element.style.position = 'fixed'; |
39 element.style.left = '-9999px'; | 39 element.style.left = '-9999px'; |
40 element.style.height = '0px'; | 40 element.style.height = '0px'; |
41 element.innerText = msg; | 41 element.innerText = msg; |
42 document.body.appendChild(element); | 42 document.body.appendChild(element); |
43 window.setTimeout(function() { | 43 window.setTimeout(function() { document.body.removeChild(element); }, 0); |
44 document.body.removeChild(element); | |
45 }, 0); | |
46 } | 44 } |
47 | 45 |
48 /** | 46 /** |
49 * Generates a CSS url string. | 47 * Generates a CSS url string. |
50 * @param {string} s The URL to generate the CSS url for. | 48 * @param {string} s The URL to generate the CSS url for. |
51 * @return {string} The CSS url string. | 49 * @return {string} The CSS url string. |
52 */ | 50 */ |
53 function url(s) { | 51 function url(s) { |
54 // http://www.w3.org/TR/css3-values/#uris | 52 // http://www.w3.org/TR/css3-values/#uris |
55 // Parentheses, commas, whitespace characters, single quotes (') and double | 53 // Parentheses, commas, whitespace characters, single quotes (') and double |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
99 | 97 |
100 return location.origin + location.pathname + newQuery + location.hash; | 98 return location.origin + location.pathname + newQuery + location.hash; |
101 } | 99 } |
102 | 100 |
103 /** | 101 /** |
104 * @param {Node} el A node to search for ancestors with |className|. | 102 * @param {Node} el A node to search for ancestors with |className|. |
105 * @param {string} className A class to search for. | 103 * @param {string} className A class to search for. |
106 * @return {Element} A node with class of |className| or null if none is found. | 104 * @return {Element} A node with class of |className| or null if none is found. |
107 */ | 105 */ |
108 function findAncestorByClass(el, className) { | 106 function findAncestorByClass(el, className) { |
109 return /** @type {Element} */(findAncestor(el, function(el) { | 107 return /** @type {Element} */ (findAncestor(el, function(el) { |
110 return el.classList && el.classList.contains(className); | 108 return el.classList && el.classList.contains(className); |
111 })); | 109 })); |
112 } | 110 } |
113 | 111 |
114 /** | 112 /** |
115 * Return the first ancestor for which the {@code predicate} returns true. | 113 * Return the first ancestor for which the {@code predicate} returns true. |
116 * @param {Node} node The node to check. | 114 * @param {Node} node The node to check. |
117 * @param {function(Node):boolean} predicate The function that tests the | 115 * @param {function(Node):boolean} predicate The function that tests the |
118 * nodes. | 116 * nodes. |
119 * @return {Node} The found ancestor or null if not found. | 117 * @return {Node} The found ancestor or null if not found. |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
159 }; | 157 }; |
160 } | 158 } |
161 | 159 |
162 /** | 160 /** |
163 * TODO(dbeam): DO NOT USE. THIS IS DEPRECATED. Use an action-link instead. | 161 * TODO(dbeam): DO NOT USE. THIS IS DEPRECATED. Use an action-link instead. |
164 * Call this to stop clicks on <a href="#"> links from scrolling to the top of | 162 * Call this to stop clicks on <a href="#"> links from scrolling to the top of |
165 * the page (and possibly showing a # in the link). | 163 * the page (and possibly showing a # in the link). |
166 */ | 164 */ |
167 function preventDefaultOnPoundLinkClicks() { | 165 function preventDefaultOnPoundLinkClicks() { |
168 document.addEventListener('click', function(e) { | 166 document.addEventListener('click', function(e) { |
169 var anchor = findAncestor(/** @type {Node} */(e.target), function(el) { | 167 var anchor = findAncestor(/** @type {Node} */ (e.target), function(el) { |
170 return el.tagName == 'A'; | 168 return el.tagName == 'A'; |
171 }); | 169 }); |
172 // Use getAttribute() to prevent URL normalization. | 170 // Use getAttribute() to prevent URL normalization. |
173 if (anchor && anchor.getAttribute('href') == '#') | 171 if (anchor && anchor.getAttribute('href') == '#') |
174 e.preventDefault(); | 172 e.preventDefault(); |
175 }); | 173 }); |
176 } | 174 } |
177 | 175 |
178 /** | 176 /** |
179 * Check the directionality of the page. | 177 * Check the directionality of the page. |
180 * @return {boolean} True if Chrome is running an RTL UI. | 178 * @return {boolean} True if Chrome is running an RTL UI. |
181 */ | 179 */ |
182 function isRTL() { | 180 function isRTL() { |
183 return document.documentElement.dir == 'rtl'; | 181 return document.documentElement.dir == 'rtl'; |
184 } | 182 } |
185 | 183 |
186 /** | 184 /** |
187 * Get an element that's known to exist by its ID. We use this instead of just | 185 * Get an element that's known to exist by its ID. We use this instead of just |
188 * calling getElementById and not checking the result because this lets us | 186 * calling getElementById and not checking the result because this lets us |
189 * satisfy the JSCompiler type system. | 187 * satisfy the JSCompiler type system. |
190 * @param {string} id The identifier name. | 188 * @param {string} id The identifier name. |
191 * @return {!HTMLElement} the Element. | 189 * @return {!HTMLElement} the Element. |
192 */ | 190 */ |
193 function getRequiredElement(id) { | 191 function getRequiredElement(id) { |
194 return assertInstanceof($(id), HTMLElement, | 192 return assertInstanceof( |
195 'Missing required element: ' + id); | 193 $(id), HTMLElement, 'Missing required element: ' + id); |
196 } | 194 } |
197 | 195 |
198 /** | 196 /** |
199 * Query an element that's known to exist by a selector. We use this instead of | 197 * Query an element that's known to exist by a selector. We use this instead of |
200 * just calling querySelector and not checking the result because this lets us | 198 * just calling querySelector and not checking the result because this lets us |
201 * satisfy the JSCompiler type system. | 199 * satisfy the JSCompiler type system. |
202 * @param {string} selectors CSS selectors to query the element. | 200 * @param {string} selectors CSS selectors to query the element. |
203 * @param {(!Document|!DocumentFragment|!Element)=} opt_context An optional | 201 * @param {(!Document|!DocumentFragment|!Element)=} opt_context An optional |
204 * context object for querySelector. | 202 * context object for querySelector. |
205 * @return {!HTMLElement} the Element. | 203 * @return {!HTMLElement} the Element. |
206 */ | 204 */ |
207 function queryRequiredElement(selectors, opt_context) { | 205 function queryRequiredElement(selectors, opt_context) { |
208 var element = (opt_context || document).querySelector(selectors); | 206 var element = (opt_context || document).querySelector(selectors); |
209 return assertInstanceof(element, HTMLElement, | 207 return assertInstanceof( |
210 'Missing required element: ' + selectors); | 208 element, HTMLElement, 'Missing required element: ' + selectors); |
211 } | 209 } |
212 | 210 |
213 // Handle click on a link. If the link points to a chrome: or file: url, then | 211 // Handle click on a link. If the link points to a chrome: or file: url, then |
214 // call into the browser to do the navigation. | 212 // call into the browser to do the navigation. |
215 ['click', 'auxclick'].forEach(function(eventName) { | 213 ['click', 'auxclick'].forEach(function(eventName) { |
216 document.addEventListener(eventName, function(e) { | 214 document.addEventListener(eventName, function(e) { |
217 if (e.button > 1) | 215 if (e.button > 1) |
218 return; // Ignore buttons other than left and middle. | 216 return; // Ignore buttons other than left and middle. |
219 if (e.defaultPrevented) | 217 if (e.defaultPrevented) |
220 return; | 218 return; |
221 | 219 |
222 var eventPath = e.path; | 220 var eventPath = e.path; |
223 var anchor = null; | 221 var anchor = null; |
224 if (eventPath) { | 222 if (eventPath) { |
225 for (var i = 0; i < eventPath.length; i++) { | 223 for (var i = 0; i < eventPath.length; i++) { |
226 var element = eventPath[i]; | 224 var element = eventPath[i]; |
227 if (element.tagName === 'A' && element.href) { | 225 if (element.tagName === 'A' && element.href) { |
228 anchor = element; | 226 anchor = element; |
229 break; | 227 break; |
230 } | 228 } |
231 } | 229 } |
232 } | 230 } |
233 | 231 |
234 // Fallback if Event.path is not available. | 232 // Fallback if Event.path is not available. |
235 var el = e.target; | 233 var el = e.target; |
236 if (!anchor && el.nodeType == Node.ELEMENT_NODE && | 234 if (!anchor && el.nodeType == Node.ELEMENT_NODE && |
237 el.webkitMatchesSelector('A, A *')) { | 235 el.webkitMatchesSelector('A, A *')) { |
238 while (el.tagName != 'A') { | 236 while (el.tagName != 'A') { |
239 el = el.parentElement; | 237 el = el.parentElement; |
240 } | 238 } |
241 anchor = el; | 239 anchor = el; |
242 } | 240 } |
243 | 241 |
244 if (!anchor) | 242 if (!anchor) |
245 return; | 243 return; |
246 | 244 |
247 anchor = /** @type {!HTMLAnchorElement} */(anchor); | 245 anchor = /** @type {!HTMLAnchorElement} */ (anchor); |
248 if ((anchor.protocol == 'file:' || anchor.protocol == 'about:') && | 246 if ((anchor.protocol == 'file:' || anchor.protocol == 'about:') && |
249 (e.button == 0 || e.button == 1)) { | 247 (e.button == 0 || e.button == 1)) { |
250 chrome.send('navigateToUrl', [ | 248 chrome.send('navigateToUrl', [ |
251 anchor.href, | 249 anchor.href, anchor.target, e.button, e.altKey, e.ctrlKey, e.metaKey, |
252 anchor.target, | |
253 e.button, | |
254 e.altKey, | |
255 e.ctrlKey, | |
256 e.metaKey, | |
257 e.shiftKey | 250 e.shiftKey |
258 ]); | 251 ]); |
259 e.preventDefault(); | 252 e.preventDefault(); |
260 } | 253 } |
261 }); | 254 }); |
262 }); | 255 }); |
263 | 256 |
264 /** | 257 /** |
265 * Creates a new URL which is the old URL with a GET param of key=value. | 258 * Creates a new URL which is the old URL with a GET param of key=value. |
266 * @param {string} url The base URL. There is not sanity checking on the URL so | 259 * @param {string} url The base URL. There is not sanity checking on the URL so |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
358 doc.documentElement.scrollLeft = doc.body.scrollLeft = value; | 351 doc.documentElement.scrollLeft = doc.body.scrollLeft = value; |
359 } | 352 } |
360 | 353 |
361 /** | 354 /** |
362 * Replaces '&', '<', '>', '"', and ''' characters with their HTML encoding. | 355 * Replaces '&', '<', '>', '"', and ''' characters with their HTML encoding. |
363 * @param {string} original The original string. | 356 * @param {string} original The original string. |
364 * @return {string} The string with all the characters mentioned above replaced. | 357 * @return {string} The string with all the characters mentioned above replaced. |
365 */ | 358 */ |
366 function HTMLEscape(original) { | 359 function HTMLEscape(original) { |
367 return original.replace(/&/g, '&') | 360 return original.replace(/&/g, '&') |
368 .replace(/</g, '<') | 361 .replace(/</g, '<') |
369 .replace(/>/g, '>') | 362 .replace(/>/g, '>') |
370 .replace(/"/g, '"') | 363 .replace(/"/g, '"') |
371 .replace(/'/g, '''); | 364 .replace(/'/g, '''); |
372 } | 365 } |
373 | 366 |
374 /** | 367 /** |
375 * Shortens the provided string (if necessary) to a string of length at most | 368 * Shortens the provided string (if necessary) to a string of length at most |
376 * |maxLength|. | 369 * |maxLength|. |
377 * @param {string} original The original string. | 370 * @param {string} original The original string. |
378 * @param {number} maxLength The maximum length allowed for the string. | 371 * @param {number} maxLength The maximum length allowed for the string. |
379 * @return {string} The original string if its length does not exceed | 372 * @return {string} The original string if its length does not exceed |
380 * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...' | 373 * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...' |
381 * appended. | 374 * appended. |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
421 } | 414 } |
422 | 415 |
423 // <if expr="is_ios"> | 416 // <if expr="is_ios"> |
424 // Polyfill 'key' in KeyboardEvent for iOS. | 417 // Polyfill 'key' in KeyboardEvent for iOS. |
425 // This function is not intended to be complete but should | 418 // This function is not intended to be complete but should |
426 // be sufficient enough to have iOS work correctly while | 419 // be sufficient enough to have iOS work correctly while |
427 // it does not support key yet. | 420 // it does not support key yet. |
428 if (!('key' in KeyboardEvent.prototype)) { | 421 if (!('key' in KeyboardEvent.prototype)) { |
429 Object.defineProperty(KeyboardEvent.prototype, 'key', { | 422 Object.defineProperty(KeyboardEvent.prototype, 'key', { |
430 /** @this {KeyboardEvent} */ | 423 /** @this {KeyboardEvent} */ |
431 get: function () { | 424 get: function() { |
432 // 0-9 | 425 // 0-9 |
433 if (this.keyCode >= 0x30 && this.keyCode <= 0x39) | 426 if (this.keyCode >= 0x30 && this.keyCode <= 0x39) |
434 return String.fromCharCode(this.keyCode); | 427 return String.fromCharCode(this.keyCode); |
435 | 428 |
436 // A-Z | 429 // A-Z |
437 if (this.keyCode >= 0x41 && this.keyCode <= 0x5a) { | 430 if (this.keyCode >= 0x41 && this.keyCode <= 0x5a) { |
438 var result = String.fromCharCode(this.keyCode).toLowerCase(); | 431 var result = String.fromCharCode(this.keyCode).toLowerCase(); |
439 if (this.shiftKey) | 432 if (this.shiftKey) |
440 result = result.toUpperCase(); | 433 result = result.toUpperCase(); |
441 return result; | 434 return result; |
442 } | 435 } |
443 | 436 |
444 // Special characters | 437 // Special characters |
445 switch(this.keyCode) { | 438 switch (this.keyCode) { |
446 case 0x08: return 'Backspace'; | 439 case 0x08: |
447 case 0x09: return 'Tab'; | 440 return 'Backspace'; |
448 case 0x0d: return 'Enter'; | 441 case 0x09: |
449 case 0x10: return 'Shift'; | 442 return 'Tab'; |
450 case 0x11: return 'Control'; | 443 case 0x0d: |
451 case 0x12: return 'Alt'; | 444 return 'Enter'; |
452 case 0x1b: return 'Escape'; | 445 case 0x10: |
453 case 0x20: return ' '; | 446 return 'Shift'; |
454 case 0x21: return 'PageUp'; | 447 case 0x11: |
455 case 0x22: return 'PageDown'; | 448 return 'Control'; |
456 case 0x23: return 'End'; | 449 case 0x12: |
457 case 0x24: return 'Home'; | 450 return 'Alt'; |
458 case 0x25: return 'ArrowLeft'; | 451 case 0x1b: |
459 case 0x26: return 'ArrowUp'; | 452 return 'Escape'; |
460 case 0x27: return 'ArrowRight'; | 453 case 0x20: |
461 case 0x28: return 'ArrowDown'; | 454 return ' '; |
462 case 0x2d: return 'Insert'; | 455 case 0x21: |
463 case 0x2e: return 'Delete'; | 456 return 'PageUp'; |
464 case 0x5b: return 'Meta'; | 457 case 0x22: |
465 case 0x70: return 'F1'; | 458 return 'PageDown'; |
466 case 0x71: return 'F2'; | 459 case 0x23: |
467 case 0x72: return 'F3'; | 460 return 'End'; |
468 case 0x73: return 'F4'; | 461 case 0x24: |
469 case 0x74: return 'F5'; | 462 return 'Home'; |
470 case 0x75: return 'F6'; | 463 case 0x25: |
471 case 0x76: return 'F7'; | 464 return 'ArrowLeft'; |
472 case 0x77: return 'F8'; | 465 case 0x26: |
473 case 0x78: return 'F9'; | 466 return 'ArrowUp'; |
474 case 0x79: return 'F10'; | 467 case 0x27: |
475 case 0x7a: return 'F11'; | 468 return 'ArrowRight'; |
476 case 0x7b: return 'F12'; | 469 case 0x28: |
477 case 0xbb: return '='; | 470 return 'ArrowDown'; |
478 case 0xbd: return '-'; | 471 case 0x2d: |
479 case 0xdb: return '['; | 472 return 'Insert'; |
480 case 0xdd: return ']'; | 473 case 0x2e: |
| 474 return 'Delete'; |
| 475 case 0x5b: |
| 476 return 'Meta'; |
| 477 case 0x70: |
| 478 return 'F1'; |
| 479 case 0x71: |
| 480 return 'F2'; |
| 481 case 0x72: |
| 482 return 'F3'; |
| 483 case 0x73: |
| 484 return 'F4'; |
| 485 case 0x74: |
| 486 return 'F5'; |
| 487 case 0x75: |
| 488 return 'F6'; |
| 489 case 0x76: |
| 490 return 'F7'; |
| 491 case 0x77: |
| 492 return 'F8'; |
| 493 case 0x78: |
| 494 return 'F9'; |
| 495 case 0x79: |
| 496 return 'F10'; |
| 497 case 0x7a: |
| 498 return 'F11'; |
| 499 case 0x7b: |
| 500 return 'F12'; |
| 501 case 0xbb: |
| 502 return '='; |
| 503 case 0xbd: |
| 504 return '-'; |
| 505 case 0xdb: |
| 506 return '['; |
| 507 case 0xdd: |
| 508 return ']'; |
481 } | 509 } |
482 return 'Unidentified'; | 510 return 'Unidentified'; |
483 } | 511 } |
484 }); | 512 }); |
485 } else { | 513 } else { |
486 window.console.log("KeyboardEvent.Key polyfill not required"); | 514 window.console.log('KeyboardEvent.Key polyfill not required'); |
487 } | 515 } |
488 // </if> /* is_ios */ | 516 // </if> /* is_ios */ |
489 | 517 |
490 /** | 518 /** |
491 * Helper to convert callback-based define() API to a promise-based API. | 519 * Helper to convert callback-based define() API to a promise-based API. |
492 * @suppress {undefinedVars} | 520 * @suppress {undefinedVars} |
493 * @param {!Array<string>} moduleNames | 521 * @param {!Array<string>} moduleNames |
494 * @return {!Promise} | 522 * @return {!Promise} |
495 */ | 523 */ |
496 function importModules(moduleNames) { | 524 function importModules(moduleNames) { |
497 return new Promise(function(resolve) { | 525 return new Promise(function(resolve) { |
498 define(moduleNames, function() { | 526 define(moduleNames, function() { resolve(Array.from(arguments)); }); |
499 resolve(Array.from(arguments)); | |
500 }); | |
501 }); | 527 }); |
502 } | 528 } |
503 | 529 |
504 /** | 530 /** |
505 * @param {!Event} e | 531 * @param {!Event} e |
506 * @return {boolean} Whether a modifier key was down when processing |e|. | 532 * @return {boolean} Whether a modifier key was down when processing |e|. |
507 */ | 533 */ |
508 function hasKeyModifiers(e) { | 534 function hasKeyModifiers(e) { |
509 return !!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey); | 535 return !!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey); |
510 } | 536 } |
OLD | NEW |