blob: caf421e38519b6de61582333660dda4d683decbe [file] [log] [blame]
// 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;
}