15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * The global object.
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @type {!Object}
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @const
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)var global = this;
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Alias for document.getElementById.
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} id The ID of the element to find.
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {HTMLElement} The found element or null if not found.
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function $(id) {
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return document.getElementById(id);
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Calls chrome.send with a callback and restores the original afterwards.
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} name The name of the message to send.
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {!Array} params The parameters to send.
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} callbackName The name of the function that the backend calls.
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {!Function} callback The function to call.
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function chromeSend(name, params, callbackName, callback) {
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var old = global[callbackName];
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  global[callbackName] = function() {
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // restore
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    global[callbackName] = old;
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var args = Array.prototype.slice.call(arguments);
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return callback.apply(global, args);
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  chrome.send(name, params);
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Generates a CSS url string.
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} s The URL to generate the CSS url for.
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {string} The CSS url string.
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function url(s) {
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // http://www.w3.org/TR/css3-values/#uris
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Parentheses, commas, whitespace characters, single quotes (') and double
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // quotes (") appearing in a URI must be escaped with a backslash
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // WebKit has a bug when it comes to URLs that end with \
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // https://bugs.webkit.org/show_bug.cgi?id=28885
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (/\\\\$/.test(s2)) {
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Add a space to work around the WebKit bug.
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    s2 += ' ';
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 'url("' + s2 + '")';
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Parses query parameters from Location.
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} location The URL to generate the CSS url for.
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {object} Dictionary containing name value pairs for URL
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function parseQueryParams(location) {
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var params = {};
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var query = unescape(location.search.substring(1));
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var vars = query.split('&');
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var i = 0; i < vars.length; i++) {
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var pair = vars[i].split('=');
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    params[pair[0]] = pair[1];
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return params;
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function findAncestorByClass(el, className) {
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return findAncestor(el, function(el) {
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (el.classList)
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return el.classList.contains(className);
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return null;
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  });
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Return the first ancestor for which the {@code predicate} returns true.
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Node} node The node to check.
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(Node) : boolean} predicate The function that tests the
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     nodes.
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {Node} The found ancestor or null if not found.
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function findAncestor(node, predicate) {
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var last = false;
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while (node != null && !(last = predicate(node))) {
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    node = node.parentNode;
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return last ? node : null;
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function swapDomNodes(a, b) {
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var afterA = a.nextSibling;
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (afterA == b) {
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    swapDomNodes(b, a);
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var aParent = a.parentNode;
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  b.parentNode.replaceChild(a, b);
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  aParent.insertBefore(b, afterA);
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Disables text selection and dragging, with optional whitelist callbacks.
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *    is defined and returns true, the onselectionstart event will be
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *    surpressed.
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(Event):boolean=} opt_allowDragStart Unless this function
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *    is defined and returns true, the ondragstart event will be surpressed.
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) {
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Disable text selection.
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  document.onselectstart = function(e) {
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e)))
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      e.preventDefault();
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Disable dragging.
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  document.ondragstart = function(e) {
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!(opt_allowDragStart && opt_allowDragStart.call(this, e)))
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      e.preventDefault();
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Call this to stop clicks on <a href="#"> links from scrolling to the top of
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the page (and possibly showing a # in the link).
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function preventDefaultOnPoundLinkClicks() {
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  document.addEventListener('click', function(e) {
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var anchor = findAncestor(e.target, function(el) {
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return el.tagName == 'A';
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    });
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Use getAttribute() to prevent URL normalization.
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (anchor && anchor.getAttribute('href') == '#')
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      e.preventDefault();
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  });
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Check the directionality of the page.
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {boolean} True if Chrome is running an RTL UI.
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function isRTL() {
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return document.documentElement.dir == 'rtl';
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Simple common assertion API
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {*} condition The condition to test.  Note that this may be used to
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     test whether a value is defined or not, and we don't want to force a
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     cast to Boolean.
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string=} opt_message A message to use in any error.
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function assert(condition, opt_message) {
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  'use strict';
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!condition) {
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var msg = 'Assertion failed';
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (opt_message)
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      msg = msg + ': ' + opt_message;
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    throw new Error(msg);
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Get an element that's known to exist by its ID. We use this instead of just
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * calling getElementById and not checking the result because this lets us
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * satisfy the JSCompiler type system.
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} id The identifier name.
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {!Element} the Element.
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function getRequiredElement(id) {
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var element = $(id);
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  assert(element, 'Missing required element: ' + id);
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return element;
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Handle click on a link. If the link points to a chrome: or file: url, then
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// call into the browser to do the navigation.
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)document.addEventListener('click', function(e) {
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Allow preventDefault to work.
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!e.returnValue)
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var el = e.target;
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (el.nodeType == Node.ELEMENT_NODE &&
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      el.webkitMatchesSelector('A, A *')) {
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    while (el.tagName != 'A') {
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      el = el.parentElement;
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ((el.protocol == 'file:' || el.protocol == 'about:') &&
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (e.button == 0 || e.button == 1)) {
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      chrome.send('navigateToUrl', [
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        el.href,
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        el.target,
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        e.button,
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        e.altKey,
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        e.ctrlKey,
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        e.metaKey,
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        e.shiftKey
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ]);
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      e.preventDefault();
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)});
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Creates a new URL which is the old URL with a GET param of key=value.
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} url The base URL. There is not sanity checking on the URL so
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     it must be passed in a proper format.
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} key The key of the param.
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} value The value of the param.
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {string} The new URL.
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function appendParam(url, key, value) {
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (url.indexOf('?') == -1)
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return url + '?' + param;
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return url + '&' + param;
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
229