// Copyright (c) 2012 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. <include src="assert.js"> /** * The global object. * @type {!Object} * @const */ var global = this; /** * Alias for document.getElementById. * @param {string} id The ID of the element to find. * @return {HTMLElement} The found element or null if not found. */ function $(id) { return document.getElementById(id); } /** * Calls chrome.send with a callback and restores the original afterwards. * @param {string} name The name of the message to send. * @param {!Array} params The parameters to send. * @param {string} callbackName The name of the function that the backend calls. * @param {!Function} callback The function to call. */ function chromeSend(name, params, callbackName, callback) { var old = global[callbackName]; global[callbackName] = function() { // restore global[callbackName] = old; var args = Array.prototype.slice.call(arguments); return callback.apply(global, args); }; chrome.send(name, params); } /** * Returns the scale factors supported by this platform. * @return {array} The supported scale factors. */ function getSupportedScaleFactors() { var supportedScaleFactors = []; if (cr.isMac || cr.isChromeOS) { supportedScaleFactors.push(1); supportedScaleFactors.push(2); } else { // Windows must be restarted to display at a different scale factor. supportedScaleFactors.push(window.devicePixelRatio); } return supportedScaleFactors; } /** * Generates a CSS url string. * @param {string} s The URL to generate the CSS url for. * @return {string} The CSS url string. */ function url(s) { // http://www.w3.org/TR/css3-values/#uris // Parentheses, commas, whitespace characters, single quotes (') and double // quotes (") appearing in a URI must be escaped with a backslash var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1'); // WebKit has a bug when it comes to URLs that end with \ // https://bugs.webkit.org/show_bug.cgi?id=28885 if (/\\\\$/.test(s2)) { // Add a space to work around the WebKit bug. s2 += ' '; } return 'url("' + s2 + '")'; } /** * Generates a CSS -webkit-image-set for a chrome:// url. * An entry in the image set is added for each of getSupportedScaleFactors(). * The scale-factor-specific url is generated by replacing the first instance of * 'scalefactor' in |path| with the numeric scale factor. * @param {string} path The URL to generate an image set for. * 'scalefactor' should be a substring of |path|. * @return {string} The CSS -webkit-image-set. */ function imageset(path) { var supportedScaleFactors = getSupportedScaleFactors(); var replaceStartIndex = path.indexOf('scalefactor'); if (replaceStartIndex < 0) return url(path); var s = ''; for (var i = 0; i < supportedScaleFactors.length; ++i) { var scaleFactor = supportedScaleFactors[i]; var pathWithScaleFactor = path.substr(0, replaceStartIndex) + scaleFactor + path.substr(replaceStartIndex + 'scalefactor'.length); s += url(pathWithScaleFactor) + ' ' + scaleFactor + 'x'; if (i != supportedScaleFactors.length - 1) s += ', '; } return '-webkit-image-set(' + s + ')'; } /** * Parses query parameters from Location. * @param {string} location The URL to generate the CSS url for. * @return {object} Dictionary containing name value pairs for URL */ function parseQueryParams(location) { var params = {}; var query = unescape(location.search.substring(1)); var vars = query.split('&'); for (var i = 0; i < vars.length; i++) { var pair = vars[i].split('='); params[pair[0]] = pair[1]; } return params; } /** * Creates a new URL by appending or replacing the given query key and value. * Not supporting URL with username and password. * @param {object} location The original URL. * @param {string} key The query parameter name. * @param {string} value The query parameter value. * @return {string} The constructed new URL. */ function setQueryParam(location, key, value) { var query = parseQueryParams(location); query[encodeURIComponent(key)] = encodeURIComponent(value); var newQuery = ''; for (var q in query) { newQuery += (newQuery ? '&' : '?') + q + '=' + query[q]; } return location.origin + location.pathname + newQuery + location.hash; } function findAncestorByClass(el, className) { return findAncestor(el, function(el) { if (el.classList) return el.classList.contains(className); return null; }); } /** * Return the first ancestor for which the {@code predicate} returns true. * @param {Node} node The node to check. * @param {function(Node) : boolean} predicate The function that tests the * nodes. * @return {Node} The found ancestor or null if not found. */ function findAncestor(node, predicate) { var last = false; while (node != null && !(last = predicate(node))) { node = node.parentNode; } return last ? node : null; } function swapDomNodes(a, b) { var afterA = a.nextSibling; if (afterA == b) { swapDomNodes(b, a); return; } var aParent = a.parentNode; b.parentNode.replaceChild(a, b); aParent.insertBefore(b, afterA); } /** * Disables text selection and dragging, with optional whitelist callbacks. * @param {function(Event):boolean=} opt_allowSelectStart Unless this function * is defined and returns true, the onselectionstart event will be * surpressed. * @param {function(Event):boolean=} opt_allowDragStart Unless this function * is defined and returns true, the ondragstart event will be surpressed. */ function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) { // Disable text selection. document.onselectstart = function(e) { if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e))) e.preventDefault(); }; // Disable dragging. document.ondragstart = function(e) { if (!(opt_allowDragStart && opt_allowDragStart.call(this, e))) e.preventDefault(); }; } /** * Call this to stop clicks on <a href="#"> links from scrolling to the top of * the page (and possibly showing a # in the link). */ function preventDefaultOnPoundLinkClicks() { document.addEventListener('click', function(e) { var anchor = findAncestor(e.target, function(el) { return el.tagName == 'A'; }); // Use getAttribute() to prevent URL normalization. if (anchor && anchor.getAttribute('href') == '#') e.preventDefault(); }); } /** * Check the directionality of the page. * @return {boolean} True if Chrome is running an RTL UI. */ function isRTL() { return document.documentElement.dir == 'rtl'; } /** * Get an element that's known to exist by its ID. We use this instead of just * calling getElementById and not checking the result because this lets us * satisfy the JSCompiler type system. * @param {string} id The identifier name. * @return {!Element} the Element. */ function getRequiredElement(id) { var element = $(id); assert(element, 'Missing required element: ' + id); return element; } // Handle click on a link. If the link points to a chrome: or file: url, then // call into the browser to do the navigation. document.addEventListener('click', function(e) { if (e.defaultPrevented) return; var el = e.target; if (el.nodeType == Node.ELEMENT_NODE && el.webkitMatchesSelector('A, A *')) { while (el.tagName != 'A') { el = el.parentElement; } if ((el.protocol == 'file:' || el.protocol == 'about:') && (e.button == 0 || e.button == 1)) { chrome.send('navigateToUrl', [ el.href, el.target, e.button, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey ]); e.preventDefault(); } } }); /** * Creates a new URL which is the old URL with a GET param of key=value. * @param {string} url The base URL. There is not sanity checking on the URL so * it must be passed in a proper format. * @param {string} key The key of the param. * @param {string} value The value of the param. * @return {string} The new URL. */ function appendParam(url, key, value) { var param = encodeURIComponent(key) + '=' + encodeURIComponent(value); if (url.indexOf('?') == -1) return url + '?' + param; return url + '&' + param; } /** * Creates a CSS -webkit-image-set for a favicon request. * @param {string} url The url for the favicon. * @param {number=} opt_size Optional preferred size of the favicon. * @param {string=} opt_type Optional type of favicon to request. Valid values * are 'favicon' and 'touch-icon'. Default is 'favicon'. * @return {string} -webkit-image-set for the favicon. */ function getFaviconImageSet(url, opt_size, opt_type) { var size = opt_size || 16; var type = opt_type || 'favicon'; return imageset( 'chrome://' + type + '/size/' + size + '@scalefactorx/' + url); } /** * Creates a new URL for a favicon request for the current device pixel ratio. * The URL must be updated when the user moves the browser to a screen with a * different device pixel ratio. Use getFaviconImageSet() for the updating to * occur automatically. * @param {string} url The url for the favicon. * @param {number=} opt_size Optional preferred size of the favicon. * @param {string=} opt_type Optional type of favicon to request. Valid values * are 'favicon' and 'touch-icon'. Default is 'favicon'. * @return {string} Updated URL for the favicon. */ function getFaviconUrlForCurrentDevicePixelRatio(url, opt_size, opt_type) { var size = opt_size || 16; var type = opt_type || 'favicon'; return 'chrome://' + type + '/size/' + size + '@' + window.devicePixelRatio + 'x/' + url; } /** * Creates an element of a specified type with a specified class name. * @param {string} type The node type. * @param {string} className The class name to use. * @return {Element} The created element. */ function createElementWithClassName(type, className) { var elm = document.createElement(type); elm.className = className; return elm; } /** * webkitTransitionEnd does not always fire (e.g. when animation is aborted * or when no paint happens during the animation). This function sets up * a timer and emulate the event if it is not fired when the timer expires. * @param {!HTMLElement} el The element to watch for webkitTransitionEnd. * @param {number} timeOut The maximum wait time in milliseconds for the * webkitTransitionEnd to happen. */ function ensureTransitionEndEvent(el, timeOut) { var fired = false; el.addEventListener('webkitTransitionEnd', function f(e) { el.removeEventListener('webkitTransitionEnd', f); fired = true; }); window.setTimeout(function() { if (!fired) cr.dispatchSimpleEvent(el, 'webkitTransitionEnd'); }, timeOut); } /** * Alias for document.scrollTop getter. * @param {!HTMLDocument} doc The document node where information will be * queried from. * @return {number} The Y document scroll offset. */ function scrollTopForDocument(doc) { return doc.documentElement.scrollTop || doc.body.scrollTop; } /** * Alias for document.scrollTop setter. * @param {!HTMLDocument} doc The document node where information will be * queried from. * @param {number} value The target Y scroll offset. */ function setScrollTopForDocument(doc, value) { doc.documentElement.scrollTop = doc.body.scrollTop = value; } /** * Alias for document.scrollLeft getter. * @param {!HTMLDocument} doc The document node where information will be * queried from. * @return {number} The X document scroll offset. */ function scrollLeftForDocument(doc) { return doc.documentElement.scrollLeft || doc.body.scrollLeft; } /** * Alias for document.scrollLeft setter. * @param {!HTMLDocument} doc The document node where information will be * queried from. * @param {number} value The target X scroll offset. */ function setScrollLeftForDocument(doc, value) { doc.documentElement.scrollLeft = doc.body.scrollLeft = value; }