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 * The global object. 9 * @type {!Object} 10 * @const 11 */ 12var global = this; 13 14/** 15 * Alias for document.getElementById. 16 * @param {string} id The ID of the element to find. 17 * @return {HTMLElement} The found element or null if not found. 18 */ 19function $(id) { 20 return document.getElementById(id); 21} 22 23/** 24 * Calls chrome.send with a callback and restores the original afterwards. 25 * @param {string} name The name of the message to send. 26 * @param {!Array} params The parameters to send. 27 * @param {string} callbackName The name of the function that the backend calls. 28 * @param {!Function} callback The function to call. 29 */ 30function chromeSend(name, params, callbackName, callback) { 31 var old = global[callbackName]; 32 global[callbackName] = function() { 33 // restore 34 global[callbackName] = old; 35 36 var args = Array.prototype.slice.call(arguments); 37 return callback.apply(global, args); 38 }; 39 chrome.send(name, params); 40} 41 42/** 43 * Returns the scale factors supported by this platform. 44 * @return {array} The supported scale factors. 45 */ 46function getSupportedScaleFactors() { 47 var supportedScaleFactors = []; 48 if (cr.isMac || cr.isChromeOS) { 49 supportedScaleFactors.push(1); 50 supportedScaleFactors.push(2); 51 } else { 52 // Windows must be restarted to display at a different scale factor. 53 supportedScaleFactors.push(window.devicePixelRatio); 54 } 55 return supportedScaleFactors; 56} 57 58/** 59 * Generates a CSS url string. 60 * @param {string} s The URL to generate the CSS url for. 61 * @return {string} The CSS url string. 62 */ 63function url(s) { 64 // http://www.w3.org/TR/css3-values/#uris 65 // Parentheses, commas, whitespace characters, single quotes (') and double 66 // quotes (") appearing in a URI must be escaped with a backslash 67 var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1'); 68 // WebKit has a bug when it comes to URLs that end with \ 69 // https://bugs.webkit.org/show_bug.cgi?id=28885 70 if (/\\\\$/.test(s2)) { 71 // Add a space to work around the WebKit bug. 72 s2 += ' '; 73 } 74 return 'url("' + s2 + '")'; 75} 76 77/** 78 * Generates a CSS -webkit-image-set for a chrome:// url. 79 * An entry in the image set is added for each of getSupportedScaleFactors(). 80 * The scale-factor-specific url is generated by replacing the first instance of 81 * 'scalefactor' in |path| with the numeric scale factor. 82 * @param {string} path The URL to generate an image set for. 83 * 'scalefactor' should be a substring of |path|. 84 * @return {string} The CSS -webkit-image-set. 85 */ 86function imageset(path) { 87 var supportedScaleFactors = getSupportedScaleFactors(); 88 89 var replaceStartIndex = path.indexOf('scalefactor'); 90 if (replaceStartIndex < 0) 91 return url(path); 92 93 var s = ''; 94 for (var i = 0; i < supportedScaleFactors.length; ++i) { 95 var scaleFactor = supportedScaleFactors[i]; 96 var pathWithScaleFactor = path.substr(0, replaceStartIndex) + scaleFactor + 97 path.substr(replaceStartIndex + 'scalefactor'.length); 98 99 s += url(pathWithScaleFactor) + ' ' + scaleFactor + 'x'; 100 101 if (i != supportedScaleFactors.length - 1) 102 s += ', '; 103 } 104 return '-webkit-image-set(' + s + ')'; 105} 106 107/** 108 * Parses query parameters from Location. 109 * @param {string} location The URL to generate the CSS url for. 110 * @return {object} Dictionary containing name value pairs for URL 111 */ 112function parseQueryParams(location) { 113 var params = {}; 114 var query = unescape(location.search.substring(1)); 115 var vars = query.split('&'); 116 for (var i = 0; i < vars.length; i++) { 117 var pair = vars[i].split('='); 118 params[pair[0]] = pair[1]; 119 } 120 return params; 121} 122 123/** 124 * Creates a new URL by appending or replacing the given query key and value. 125 * Not supporting URL with username and password. 126 * @param {object} location The original URL. 127 * @param {string} key The query parameter name. 128 * @param {string} value The query parameter value. 129 * @return {string} The constructed new URL. 130 */ 131function setQueryParam(location, key, value) { 132 var query = parseQueryParams(location); 133 query[encodeURIComponent(key)] = encodeURIComponent(value); 134 135 var newQuery = ''; 136 for (var q in query) { 137 newQuery += (newQuery ? '&' : '?') + q + '=' + query[q]; 138 } 139 140 return location.origin + location.pathname + newQuery + location.hash; 141} 142 143function findAncestorByClass(el, className) { 144 return findAncestor(el, function(el) { 145 if (el.classList) 146 return el.classList.contains(className); 147 return null; 148 }); 149} 150 151/** 152 * Return the first ancestor for which the {@code predicate} returns true. 153 * @param {Node} node The node to check. 154 * @param {function(Node) : boolean} predicate The function that tests the 155 * nodes. 156 * @return {Node} The found ancestor or null if not found. 157 */ 158function findAncestor(node, predicate) { 159 var last = false; 160 while (node != null && !(last = predicate(node))) { 161 node = node.parentNode; 162 } 163 return last ? node : null; 164} 165 166function swapDomNodes(a, b) { 167 var afterA = a.nextSibling; 168 if (afterA == b) { 169 swapDomNodes(b, a); 170 return; 171 } 172 var aParent = a.parentNode; 173 b.parentNode.replaceChild(a, b); 174 aParent.insertBefore(b, afterA); 175} 176 177/** 178 * Disables text selection and dragging, with optional whitelist callbacks. 179 * @param {function(Event):boolean=} opt_allowSelectStart Unless this function 180 * is defined and returns true, the onselectionstart event will be 181 * surpressed. 182 * @param {function(Event):boolean=} opt_allowDragStart Unless this function 183 * is defined and returns true, the ondragstart event will be surpressed. 184 */ 185function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) { 186 // Disable text selection. 187 document.onselectstart = function(e) { 188 if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e))) 189 e.preventDefault(); 190 }; 191 192 // Disable dragging. 193 document.ondragstart = function(e) { 194 if (!(opt_allowDragStart && opt_allowDragStart.call(this, e))) 195 e.preventDefault(); 196 }; 197} 198 199/** 200 * Call this to stop clicks on <a href="#"> links from scrolling to the top of 201 * the page (and possibly showing a # in the link). 202 */ 203function preventDefaultOnPoundLinkClicks() { 204 document.addEventListener('click', function(e) { 205 var anchor = findAncestor(e.target, function(el) { 206 return el.tagName == 'A'; 207 }); 208 // Use getAttribute() to prevent URL normalization. 209 if (anchor && anchor.getAttribute('href') == '#') 210 e.preventDefault(); 211 }); 212} 213 214/** 215 * Check the directionality of the page. 216 * @return {boolean} True if Chrome is running an RTL UI. 217 */ 218function isRTL() { 219 return document.documentElement.dir == 'rtl'; 220} 221 222/** 223 * Get an element that's known to exist by its ID. We use this instead of just 224 * calling getElementById and not checking the result because this lets us 225 * satisfy the JSCompiler type system. 226 * @param {string} id The identifier name. 227 * @return {!Element} the Element. 228 */ 229function getRequiredElement(id) { 230 var element = $(id); 231 assert(element, 'Missing required element: ' + id); 232 return element; 233} 234 235// Handle click on a link. If the link points to a chrome: or file: url, then 236// call into the browser to do the navigation. 237document.addEventListener('click', function(e) { 238 if (e.defaultPrevented) 239 return; 240 241 var el = e.target; 242 if (el.nodeType == Node.ELEMENT_NODE && 243 el.webkitMatchesSelector('A, A *')) { 244 while (el.tagName != 'A') { 245 el = el.parentElement; 246 } 247 248 if ((el.protocol == 'file:' || el.protocol == 'about:') && 249 (e.button == 0 || e.button == 1)) { 250 chrome.send('navigateToUrl', [ 251 el.href, 252 el.target, 253 e.button, 254 e.altKey, 255 e.ctrlKey, 256 e.metaKey, 257 e.shiftKey 258 ]); 259 e.preventDefault(); 260 } 261 } 262}); 263 264/** 265 * 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 267 * it must be passed in a proper format. 268 * @param {string} key The key of the param. 269 * @param {string} value The value of the param. 270 * @return {string} The new URL. 271 */ 272function appendParam(url, key, value) { 273 var param = encodeURIComponent(key) + '=' + encodeURIComponent(value); 274 275 if (url.indexOf('?') == -1) 276 return url + '?' + param; 277 return url + '&' + param; 278} 279 280/** 281 * Creates a CSS -webkit-image-set for a favicon request. 282 * @param {string} url The url for the favicon. 283 * @param {number=} opt_size Optional preferred size of the favicon. 284 * @param {string=} opt_type Optional type of favicon to request. Valid values 285 * are 'favicon' and 'touch-icon'. Default is 'favicon'. 286 * @return {string} -webkit-image-set for the favicon. 287 */ 288function getFaviconImageSet(url, opt_size, opt_type) { 289 var size = opt_size || 16; 290 var type = opt_type || 'favicon'; 291 return imageset( 292 'chrome://' + type + '/size/' + size + '@scalefactorx/' + url); 293} 294 295/** 296 * Creates a new URL for a favicon request for the current device pixel ratio. 297 * The URL must be updated when the user moves the browser to a screen with a 298 * different device pixel ratio. Use getFaviconImageSet() for the updating to 299 * occur automatically. 300 * @param {string} url The url for the favicon. 301 * @param {number=} opt_size Optional preferred size of the favicon. 302 * @param {string=} opt_type Optional type of favicon to request. Valid values 303 * are 'favicon' and 'touch-icon'. Default is 'favicon'. 304 * @return {string} Updated URL for the favicon. 305 */ 306function getFaviconUrlForCurrentDevicePixelRatio(url, opt_size, opt_type) { 307 var size = opt_size || 16; 308 var type = opt_type || 'favicon'; 309 return 'chrome://' + type + '/size/' + size + '@' + 310 window.devicePixelRatio + 'x/' + url; 311} 312 313/** 314 * Creates an element of a specified type with a specified class name. 315 * @param {string} type The node type. 316 * @param {string} className The class name to use. 317 * @return {Element} The created element. 318 */ 319function createElementWithClassName(type, className) { 320 var elm = document.createElement(type); 321 elm.className = className; 322 return elm; 323} 324 325/** 326 * webkitTransitionEnd does not always fire (e.g. when animation is aborted 327 * or when no paint happens during the animation). This function sets up 328 * a timer and emulate the event if it is not fired when the timer expires. 329 * @param {!HTMLElement} el The element to watch for webkitTransitionEnd. 330 * @param {number} timeOut The maximum wait time in milliseconds for the 331 * webkitTransitionEnd to happen. 332 */ 333function ensureTransitionEndEvent(el, timeOut) { 334 var fired = false; 335 el.addEventListener('webkitTransitionEnd', function f(e) { 336 el.removeEventListener('webkitTransitionEnd', f); 337 fired = true; 338 }); 339 window.setTimeout(function() { 340 if (!fired) 341 cr.dispatchSimpleEvent(el, 'webkitTransitionEnd'); 342 }, timeOut); 343} 344 345/** 346 * Alias for document.scrollTop getter. 347 * @param {!HTMLDocument} doc The document node where information will be 348 * queried from. 349 * @return {number} The Y document scroll offset. 350 */ 351function scrollTopForDocument(doc) { 352 return doc.documentElement.scrollTop || doc.body.scrollTop; 353} 354 355/** 356 * Alias for document.scrollTop setter. 357 * @param {!HTMLDocument} doc The document node where information will be 358 * queried from. 359 * @param {number} value The target Y scroll offset. 360 */ 361function setScrollTopForDocument(doc, value) { 362 doc.documentElement.scrollTop = doc.body.scrollTop = value; 363} 364 365/** 366 * Alias for document.scrollLeft getter. 367 * @param {!HTMLDocument} doc The document node where information will be 368 * queried from. 369 * @return {number} The X document scroll offset. 370 */ 371function scrollLeftForDocument(doc) { 372 return doc.documentElement.scrollLeft || doc.body.scrollLeft; 373} 374 375/** 376 * Alias for document.scrollLeft setter. 377 * @param {!HTMLDocument} doc The document node where information will be 378 * queried from. 379 * @param {number} value The target X scroll offset. 380 */ 381function setScrollLeftForDocument(doc, value) { 382 doc.documentElement.scrollLeft = doc.body.scrollLeft = value; 383} 384