OLD | NEW |
(Empty) | |
| 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 |
| 3 // found in the LICENSE file. |
| 4 |
| 5 // <include src="assert.js"> |
| 6 |
| 7 /** |
| 8 * Alias for document.getElementById. Found elements must be HTMLElements. |
| 9 * @param {string} id The ID of the element to find. |
| 10 * @return {HTMLElement} The found element or null if not found. |
| 11 */ |
| 12 function $(id) { |
| 13 var el = document.getElementById(id); |
| 14 return el ? assertInstanceof(el, HTMLElement) : null; |
| 15 } |
| 16 |
| 17 // TODO(devlin): This should return SVGElement, but closure compiler is missing |
| 18 // those externs. |
| 19 /** |
| 20 * Alias for document.getElementById. Found elements must be SVGElements. |
| 21 * @param {string} id The ID of the element to find. |
| 22 * @return {Element} The found element or null if not found. |
| 23 */ |
| 24 function getSVGElement(id) { |
| 25 var el = document.getElementById(id); |
| 26 return el ? assertInstanceof(el, Element) : null; |
| 27 } |
| 28 |
| 29 /** |
| 30 * Add an accessible message to the page that will be announced to |
| 31 * users who have spoken feedback on, but will be invisible to all |
| 32 * other users. It's removed right away so it doesn't clutter the DOM. |
| 33 * @param {string} msg The text to be pronounced. |
| 34 */ |
| 35 function announceAccessibleMessage(msg) { |
| 36 var element = document.createElement('div'); |
| 37 element.setAttribute('aria-live', 'polite'); |
| 38 element.style.position = 'relative'; |
| 39 element.style.left = '-9999px'; |
| 40 element.style.height = '0px'; |
| 41 element.innerText = msg; |
| 42 document.body.appendChild(element); |
| 43 window.setTimeout(function() { |
| 44 document.body.removeChild(element); |
| 45 }, 0); |
| 46 } |
| 47 |
| 48 /** |
| 49 * Generates a CSS url string. |
| 50 * @param {string} s The URL to generate the CSS url for. |
| 51 * @return {string} The CSS url string. |
| 52 */ |
| 53 function url(s) { |
| 54 // http://www.w3.org/TR/css3-values/#uris |
| 55 // Parentheses, commas, whitespace characters, single quotes (') and double |
| 56 // quotes (") appearing in a URI must be escaped with a backslash |
| 57 var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1'); |
| 58 // WebKit has a bug when it comes to URLs that end with \ |
| 59 // https://bugs.webkit.org/show_bug.cgi?id=28885 |
| 60 if (/\\\\$/.test(s2)) { |
| 61 // Add a space to work around the WebKit bug. |
| 62 s2 += ' '; |
| 63 } |
| 64 return 'url("' + s2 + '")'; |
| 65 } |
| 66 |
| 67 /** |
| 68 * Parses query parameters from Location. |
| 69 * @param {Location} location The URL to generate the CSS url for. |
| 70 * @return {Object} Dictionary containing name value pairs for URL |
| 71 */ |
| 72 function parseQueryParams(location) { |
| 73 var params = {}; |
| 74 var query = unescape(location.search.substring(1)); |
| 75 var vars = query.split('&'); |
| 76 for (var i = 0; i < vars.length; i++) { |
| 77 var pair = vars[i].split('='); |
| 78 params[pair[0]] = pair[1]; |
| 79 } |
| 80 return params; |
| 81 } |
| 82 |
| 83 /** |
| 84 * Creates a new URL by appending or replacing the given query key and value. |
| 85 * Not supporting URL with username and password. |
| 86 * @param {Location} location The original URL. |
| 87 * @param {string} key The query parameter name. |
| 88 * @param {string} value The query parameter value. |
| 89 * @return {string} The constructed new URL. |
| 90 */ |
| 91 function setQueryParam(location, key, value) { |
| 92 var query = parseQueryParams(location); |
| 93 query[encodeURIComponent(key)] = encodeURIComponent(value); |
| 94 |
| 95 var newQuery = ''; |
| 96 for (var q in query) { |
| 97 newQuery += (newQuery ? '&' : '?') + q + '=' + query[q]; |
| 98 } |
| 99 |
| 100 return location.origin + location.pathname + newQuery + location.hash; |
| 101 } |
| 102 |
| 103 /** |
| 104 * @param {Node} el A node to search for ancestors with |className|. |
| 105 * @param {string} className A class to search for. |
| 106 * @return {Element} A node with class of |className| or null if none is found. |
| 107 */ |
| 108 function findAncestorByClass(el, className) { |
| 109 return /** @type {Element} */(findAncestor(el, function(el) { |
| 110 return el.classList && el.classList.contains(className); |
| 111 })); |
| 112 } |
| 113 |
| 114 /** |
| 115 * Return the first ancestor for which the {@code predicate} returns true. |
| 116 * @param {Node} node The node to check. |
| 117 * @param {function(Node):boolean} predicate The function that tests the |
| 118 * nodes. |
| 119 * @return {Node} The found ancestor or null if not found. |
| 120 */ |
| 121 function findAncestor(node, predicate) { |
| 122 var last = false; |
| 123 while (node != null && !(last = predicate(node))) { |
| 124 node = node.parentNode; |
| 125 } |
| 126 return last ? node : null; |
| 127 } |
| 128 |
| 129 function swapDomNodes(a, b) { |
| 130 var afterA = a.nextSibling; |
| 131 if (afterA == b) { |
| 132 swapDomNodes(b, a); |
| 133 return; |
| 134 } |
| 135 var aParent = a.parentNode; |
| 136 b.parentNode.replaceChild(a, b); |
| 137 aParent.insertBefore(b, afterA); |
| 138 } |
| 139 |
| 140 /** |
| 141 * Disables text selection and dragging, with optional whitelist callbacks. |
| 142 * @param {function(Event):boolean=} opt_allowSelectStart Unless this function |
| 143 * is defined and returns true, the onselectionstart event will be |
| 144 * surpressed. |
| 145 * @param {function(Event):boolean=} opt_allowDragStart Unless this function |
| 146 * is defined and returns true, the ondragstart event will be surpressed. |
| 147 */ |
| 148 function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) { |
| 149 // Disable text selection. |
| 150 document.onselectstart = function(e) { |
| 151 if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e))) |
| 152 e.preventDefault(); |
| 153 }; |
| 154 |
| 155 // Disable dragging. |
| 156 document.ondragstart = function(e) { |
| 157 if (!(opt_allowDragStart && opt_allowDragStart.call(this, e))) |
| 158 e.preventDefault(); |
| 159 }; |
| 160 } |
| 161 |
| 162 /** |
| 163 * 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 |
| 165 * the page (and possibly showing a # in the link). |
| 166 */ |
| 167 function preventDefaultOnPoundLinkClicks() { |
| 168 document.addEventListener('click', function(e) { |
| 169 var anchor = findAncestor(/** @type {Node} */(e.target), function(el) { |
| 170 return el.tagName == 'A'; |
| 171 }); |
| 172 // Use getAttribute() to prevent URL normalization. |
| 173 if (anchor && anchor.getAttribute('href') == '#') |
| 174 e.preventDefault(); |
| 175 }); |
| 176 } |
| 177 |
| 178 /** |
| 179 * Check the directionality of the page. |
| 180 * @return {boolean} True if Chrome is running an RTL UI. |
| 181 */ |
| 182 function isRTL() { |
| 183 return document.documentElement.dir == 'rtl'; |
| 184 } |
| 185 |
| 186 /** |
| 187 * 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 |
| 189 * satisfy the JSCompiler type system. |
| 190 * @param {string} id The identifier name. |
| 191 * @return {!HTMLElement} the Element. |
| 192 */ |
| 193 function getRequiredElement(id) { |
| 194 return assertInstanceof($(id), HTMLElement, |
| 195 'Missing required element: ' + id); |
| 196 } |
| 197 |
| 198 /** |
| 199 * 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 |
| 201 * satisfy the JSCompiler type system. |
| 202 * @param {string} selectors CSS selectors to query the element. |
| 203 * @param {(!Document|!DocumentFragment|!Element)=} opt_context An optional |
| 204 * context object for querySelector. |
| 205 * @return {!HTMLElement} the Element. |
| 206 */ |
| 207 function queryRequiredElement(selectors, opt_context) { |
| 208 var element = (opt_context || document).querySelector(selectors); |
| 209 return assertInstanceof(element, HTMLElement, |
| 210 'Missing required element: ' + selectors); |
| 211 } |
| 212 |
| 213 // 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. |
| 215 document.addEventListener('click', function(e) { |
| 216 if (e.defaultPrevented) |
| 217 return; |
| 218 |
| 219 var el = e.target; |
| 220 if (el.nodeType == Node.ELEMENT_NODE && |
| 221 el.webkitMatchesSelector('A, A *')) { |
| 222 while (el.tagName != 'A') { |
| 223 el = el.parentElement; |
| 224 } |
| 225 |
| 226 if ((el.protocol == 'file:' || el.protocol == 'about:') && |
| 227 (e.button == 0 || e.button == 1)) { |
| 228 chrome.send('navigateToUrl', [ |
| 229 el.href, |
| 230 el.target, |
| 231 e.button, |
| 232 e.altKey, |
| 233 e.ctrlKey, |
| 234 e.metaKey, |
| 235 e.shiftKey |
| 236 ]); |
| 237 e.preventDefault(); |
| 238 } |
| 239 } |
| 240 }); |
| 241 |
| 242 /** |
| 243 * Creates a new URL which is the old URL with a GET param of key=value. |
| 244 * @param {string} url The base URL. There is not sanity checking on the URL so |
| 245 * it must be passed in a proper format. |
| 246 * @param {string} key The key of the param. |
| 247 * @param {string} value The value of the param. |
| 248 * @return {string} The new URL. |
| 249 */ |
| 250 function appendParam(url, key, value) { |
| 251 var param = encodeURIComponent(key) + '=' + encodeURIComponent(value); |
| 252 |
| 253 if (url.indexOf('?') == -1) |
| 254 return url + '?' + param; |
| 255 return url + '&' + param; |
| 256 } |
| 257 |
| 258 /** |
| 259 * Creates an element of a specified type with a specified class name. |
| 260 * @param {string} type The node type. |
| 261 * @param {string} className The class name to use. |
| 262 * @return {Element} The created element. |
| 263 */ |
| 264 function createElementWithClassName(type, className) { |
| 265 var elm = document.createElement(type); |
| 266 elm.className = className; |
| 267 return elm; |
| 268 } |
| 269 |
| 270 /** |
| 271 * webkitTransitionEnd does not always fire (e.g. when animation is aborted |
| 272 * or when no paint happens during the animation). This function sets up |
| 273 * a timer and emulate the event if it is not fired when the timer expires. |
| 274 * @param {!HTMLElement} el The element to watch for webkitTransitionEnd. |
| 275 * @param {number=} opt_timeOut The maximum wait time in milliseconds for the |
| 276 * webkitTransitionEnd to happen. If not specified, it is fetched from |el| |
| 277 * using the transitionDuration style value. |
| 278 */ |
| 279 function ensureTransitionEndEvent(el, opt_timeOut) { |
| 280 if (opt_timeOut === undefined) { |
| 281 var style = getComputedStyle(el); |
| 282 opt_timeOut = parseFloat(style.transitionDuration) * 1000; |
| 283 |
| 284 // Give an additional 50ms buffer for the animation to complete. |
| 285 opt_timeOut += 50; |
| 286 } |
| 287 |
| 288 var fired = false; |
| 289 el.addEventListener('webkitTransitionEnd', function f(e) { |
| 290 el.removeEventListener('webkitTransitionEnd', f); |
| 291 fired = true; |
| 292 }); |
| 293 window.setTimeout(function() { |
| 294 if (!fired) |
| 295 cr.dispatchSimpleEvent(el, 'webkitTransitionEnd', true); |
| 296 }, opt_timeOut); |
| 297 } |
| 298 |
| 299 /** |
| 300 * Alias for document.scrollTop getter. |
| 301 * @param {!HTMLDocument} doc The document node where information will be |
| 302 * queried from. |
| 303 * @return {number} The Y document scroll offset. |
| 304 */ |
| 305 function scrollTopForDocument(doc) { |
| 306 return doc.documentElement.scrollTop || doc.body.scrollTop; |
| 307 } |
| 308 |
| 309 /** |
| 310 * Alias for document.scrollTop setter. |
| 311 * @param {!HTMLDocument} doc The document node where information will be |
| 312 * queried from. |
| 313 * @param {number} value The target Y scroll offset. |
| 314 */ |
| 315 function setScrollTopForDocument(doc, value) { |
| 316 doc.documentElement.scrollTop = doc.body.scrollTop = value; |
| 317 } |
| 318 |
| 319 /** |
| 320 * Alias for document.scrollLeft getter. |
| 321 * @param {!HTMLDocument} doc The document node where information will be |
| 322 * queried from. |
| 323 * @return {number} The X document scroll offset. |
| 324 */ |
| 325 function scrollLeftForDocument(doc) { |
| 326 return doc.documentElement.scrollLeft || doc.body.scrollLeft; |
| 327 } |
| 328 |
| 329 /** |
| 330 * Alias for document.scrollLeft setter. |
| 331 * @param {!HTMLDocument} doc The document node where information will be |
| 332 * queried from. |
| 333 * @param {number} value The target X scroll offset. |
| 334 */ |
| 335 function setScrollLeftForDocument(doc, value) { |
| 336 doc.documentElement.scrollLeft = doc.body.scrollLeft = value; |
| 337 } |
| 338 |
| 339 /** |
| 340 * Replaces '&', '<', '>', '"', and ''' characters with their HTML encoding. |
| 341 * @param {string} original The original string. |
| 342 * @return {string} The string with all the characters mentioned above replaced. |
| 343 */ |
| 344 function HTMLEscape(original) { |
| 345 return original.replace(/&/g, '&') |
| 346 .replace(/</g, '<') |
| 347 .replace(/>/g, '>') |
| 348 .replace(/"/g, '"') |
| 349 .replace(/'/g, '''); |
| 350 } |
| 351 |
| 352 /** |
| 353 * Shortens the provided string (if necessary) to a string of length at most |
| 354 * |maxLength|. |
| 355 * @param {string} original The original string. |
| 356 * @param {number} maxLength The maximum length allowed for the string. |
| 357 * @return {string} The original string if its length does not exceed |
| 358 * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...' |
| 359 * appended. |
| 360 */ |
| 361 function elide(original, maxLength) { |
| 362 if (original.length <= maxLength) |
| 363 return original; |
| 364 return original.substring(0, maxLength - 1) + '\u2026'; |
| 365 } |
| 366 |
| 367 /** |
| 368 * Quote a string so it can be used in a regular expression. |
| 369 * @param {string} str The source string. |
| 370 * @return {string} The escaped string. |
| 371 */ |
| 372 function quoteString(str) { |
| 373 return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1'); |
| 374 } |
OLD | NEW |