text: // URL Polyfill
  // Draft specification: https://url.spec.whatwg.org
  
  // Notes:
  // - Primarily useful for parsing URLs and modifying query parameters
  // - Should work in IE8+ and everything more modern
  
  (function (global) {
    'use strict';
  
    function isSequence(o) {
      if (!o) return false;
      if ('Symbol' in global && 'iterator' in global.Symbol &&
          typeof o[Symbol.iterator] === 'function') return true;
      if (Array.isArray(o)) return true;
      return false;
    }
  
    function toArray(iter) {
      return ('from' in Array) ? Array.from(iter) : Array.prototype.slice.call(iter);
    }
  
    (function() {
  
      // Browsers may have:
      // * No global URL object
      // * URL with static methods only - may have a dummy constructor
      // * URL with members except searchParams
      // * Full URL API support
      var origURL = global.URL;
      var nativeURL;
      try {
        if (origURL) {
          nativeURL = new global.URL('http://example.com');
          if ('searchParams' in nativeURL)
            return;
          if (!('href' in nativeURL))
            nativeURL = undefined;
        }
      } catch (_) {}
  
      // NOTE: Doesn't do the encoding/decoding dance
      function urlencoded_serialize(pairs) {
        var output = '', first = true;
        pairs.forEach(function (pair) {
          var name = encodeURIComponent(pair.name);
          var value = encodeURIComponent(pair.value);
          if (!first) output += '&';
          output += name + '=' + value;
          first = false;
        });
        return output.replace(/%20/g, '+');
      }
  
      // NOTE: Doesn't do the encoding/decoding dance
      function urlencoded_parse(input, isindex) {
        var sequences = input.split('&');
        if (isindex && sequences[0].indexOf('=') === -1)
          sequences[0] = '=' + sequences[0];
        var pairs = [];
        sequences.forEach(function (bytes) {
          if (bytes.length === 0) return;
          var index = bytes.indexOf('=');
          if (index !== -1) {
            var name = bytes.substring(0, index);
            var value = bytes.substring(index + 1);
          } else {
            name = bytes;
            value = '';
          }
          name = name.replace(/\+/g, ' ');
          value = value.replace(/\+/g, ' ');
          pairs.push({ name: name, value: value });
        });
        var output = [];
        pairs.forEach(function (pair) {
          output.push({
            name: decodeURIComponent(pair.name),
            value: decodeURIComponent(pair.value)
          });
        });
        return output;
      }
  
      function URLUtils(url) {
        if (nativeURL)
          return new origURL(url);
        var anchor = document.createElement('a');
        anchor.href = url;
        return anchor;
      }
  
      function URLSearchParams(init) {
        var $this = this;
        this._list = [];
  
        if (init === undefined || init === null) {
          // no-op
        } else if (init instanceof URLSearchParams) {
          // In ES6 init would be a sequence, but special case for ES5.
          this._list = urlencoded_parse(String(init));
        } else if (typeof init === 'object' && isSequence(init)) {
          toArray(init).forEach(function(e) {
            if (!isSequence(e)) throw TypeError();
            var nv = toArray(e);
            if (nv.length !== 2) throw TypeError();
            $this._list.push({name: String(nv[0]), value: String(nv[1])});
          });
        } else if (typeof init === 'object' && init) {
          Object.keys(init).forEach(function(key) {
            $this._list.push({name: String(key), value: String(init[key])});
          });
        } else {
          init = String(init);
          if (init.substring(0, 1) === '?')
            init = init.substring(1);
          this._list = urlencoded_parse(init);
        }
  
        this._url_object = null;
        this._setList = function (list) { if (!updating) $this._list = list; };
  
        var updating = false;
        this._update_steps = function() {
          if (updating) return;
          updating = true;
  
          if (!$this._url_object) return;
  
          // Partial workaround for IE issue with 'about:'
          if ($this._url_object.protocol === 'about:' &&
              $this._url_object.pathname.indexOf('?') !== -1) {
            $this._url_object.pathname = $this._url_object.pathname.split('?')[0];
          }
  
          $this._url_object.search = urlencoded_serialize($this._list);
  
          updating = false;
        };
      }
  
  
      Object.defineProperties(URLSearchParams.prototype, {
        append: {
          value: function (name, value) {
            this._list.push({ name: name, value: value });
            this._update_steps();
          }, writable: true, enumerable: true, configurable: true
        },
  
        'delete': {
          value: function (name) {
            for (var i = 0; i < this._list.length;) {
              if (this._list[i].name === name)
                this._list.splice(i, 1);
              else
                ++i;
            }
            this._update_steps();
          }, writable: true, enumerable: true, configurable: true
        },
  
        get: {
          value: function (name) {
            for (var i = 0; i < this._list.length; ++i) {
              if (this._list[i].name === name)
                return this._list[i].value;
            }
            return null;
          }, writable: true, enumerable: true, configurable: true
        },
  
        getAll: {
          value: function (name) {
            var result = [];
            for (var i = 0; i < this._list.length; ++i) {
              if (this._list[i].name === name)
                result.push(this._list[i].value);
            }
            return result;
          }, writable: true, enumerable: true, configurable: true
        },
  
        has: {
          value: function (name) {
            for (var i = 0; i < this._list.length; ++i) {
              if (this._list[i].name === name)
                return true;
            }
            return false;
          }, writable: true, enumerable: true, configurable: true
        },
  
        set: {
          value: function (name, value) {
            var found = false;
            for (var i = 0; i < this._list.length;) {
              if (this._list[i].name === name) {
                if (!found) {
                  this._list[i].value = value;
                  found = true;
                  ++i;
                } else {
                  this._list.splice(i, 1);
                }
              } else {
                ++i;
              }
            }
  
            if (!found)
              this._list.push({ name: name, value: value });
  
            this._update_steps();
          }, writable: true, enumerable: true, configurable: true
        },
  
        entries: {
          value: function() { return new Iterator(this._list, 'key+value'); },
          writable: true, enumerable: true, configurable: true
        },
  
        keys: {
          value: function() { return new Iterator(this._list, 'key'); },
          writable: true, enumerable: true, configurable: true
        },
  
        values: {
          value: function() { return new Iterator(this._list, 'value'); },
          writable: true, enumerable: true, configurable: true
        },
  
        forEach: {
          value: function(callback) {
            var thisArg = (arguments.length > 1) ? arguments[1] : undefined;
            this._list.forEach(function(pair, index) {
              callback.call(thisArg, pair.value, pair.name);
            });
  
          }, writable: true, enumerable: true, configurable: true
        },
  
        toString: {
          value: function () {
            return urlencoded_serialize(this._list);
          }, writable: true, enumerable: false, configurable: true
        }
      });
  
      function Iterator(source, kind) {
        var index = 0;
        this['next'] = function() {
          if (index >= source.length)
            return {done: true, value: undefined};
          var pair = source[index++];
          return {done: false, value:
                  kind === 'key' ? pair.name :
                  kind === 'value' ? pair.value :
                  [pair.name, pair.value]};
        };
      }
  
      if ('Symbol' in global && 'iterator' in global.Symbol) {
        Object.defineProperty(URLSearchParams.prototype, global.Symbol.iterator, {
          value: URLSearchParams.prototype.entries,
          writable: true, enumerable: true, configurable: true});
        Object.defineProperty(Iterator.prototype, global.Symbol.iterator, {
          value: function() { return this; },
          writable: true, enumerable: true, configurable: true});
      }
  
      function URL(url, base) {
        if (!(this instanceof global.URL))
          throw new TypeError("Failed to construct 'URL': Please use the 'new' operator.");
  
        if (base) {
          url = (function () {
            if (nativeURL) return new origURL(url, base).href;
            var iframe;
            try {
              var doc;
              // Use another document/base tag/anchor for relative URL resolution, if possible
              if (Object.prototype.toString.call(window.operamini) === "[object OperaMini]") {
                iframe = document.createElement('iframe');
                iframe.style.display = 'none';
                document.documentElement.appendChild(iframe);
                doc = iframe.contentWindow.document;
              } else if (document.implementation && document.implementation.createHTMLDocument) {
                doc = document.implementation.createHTMLDocument('');
              } else if (document.implementation && document.implementation.createDocument) {
                doc = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
                doc.documentElement.appendChild(doc.createElement('head'));
                doc.documentElement.appendChild(doc.createElement('body'));
              } else if (window.ActiveXObject) {
                doc = new window.ActiveXObject('htmlfile');
                doc.write('<head><\/head><body><\/body>');
                doc.close();
              }
  
              if (!doc) throw Error('base not supported');
  
              var baseTag = doc.createElement('base');
              baseTag.href = base;
              doc.getElementsByTagName('head')[0].appendChild(baseTag);
              var anchor = doc.createElement('a');
              anchor.href = url;
              return anchor.href;
            } finally {
              if (iframe)
                iframe.parentNode.removeChild(iframe);
            }
          }());
        }
  
        // An inner object implementing URLUtils (either a native URL
        // object or an HTMLAnchorElement instance) is used to perform the
        // URL algorithms. With full ES5 getter/setter support, return a
        // regular object For IE8's limited getter/setter support, a
        // different HTMLAnchorElement is returned with properties
        // overridden
  
        var instance = URLUtils(url || '');
  
        // Detect for ES5 getter/setter support
        // (an Object.defineProperties polyfill that doesn't support getters/setters may throw)
        var ES5_GET_SET = (function() {
          if (!('defineProperties' in Object)) return false;
          try {
            var obj = {};
            Object.defineProperties(obj, { prop: { 'get': function () { return true; } } });
            return obj.prop;
          } catch (_) {
            return false;
          }
        })();
  
        var self = ES5_GET_SET ? this : document.createElement('a');
  
  
  
        var query_object = new URLSearchParams(
          instance.search ? instance.search.substring(1) : null);
        query_object._url_object = self;
  
        Object.defineProperties(self, {
          href: {
            get: function () { return instance.href; },
            set: function (v) { instance.href = v; tidy_instance(); update_steps(); },
            enumerable: true, configurable: true
          },
          origin: {
            get: function () {
              if ('origin' in instance) return instance.origin;
              return this.protocol + '//' + this.host;
            },
            enumerable: true, configurable: true
          },
          protocol: {
            get: function () { return instance.protocol; },
            set: function (v) { instance.protocol = v; },
            enumerable: true, configurable: true
          },
          username: {
            get: function () { return instance.username; },
            set: function (v) { instance.username = v; },
            enumerable: true, configurable: true
          },
          password: {
            get: function () { return instance.password; },
            set: function (v) { instance.password = v; },
            enumerable: true, configurable: true
          },
          host: {
            get: function () {
              // IE returns default port in |host|
              var re = {'http:': /:80$/, 'https:': /:443$/, 'ftp:': /:21$/}[instance.protocol];
              return re ? instance.host.replace(re, '') : instance.host;
            },
            set: function (v) { instance.host = v; },
            enumerable: true, configurable: true
          },
          hostname: {
            get: function () { return instance.hostname; },
            set: function (v) { instance.hostname = v; },
            enumerable: true, configurable: true
          },
          port: {
            get: function () { return instance.port; },
            set: function (v) { instance.port = v; },
            enumerable: true, configurable: true
          },
          pathname: {
            get: function () {
              // IE does not include leading '/' in |pathname|
              if (instance.pathname.charAt(0) !== '/') return '/' + instance.pathname;
              return instance.pathname;
            },
            set: function (v) { instance.pathname = v; },
            enumerable: true, configurable: true
          },
          search: {
            get: function () { return instance.search; },
            set: function (v) {
              if (instance.search === v) return;
              instance.search = v; tidy_instance(); update_steps();
            },
            enumerable: true, configurable: true
          },
          searchParams: {
            get: function () { return query_object; },
            enumerable: true, configurable: true
          },
          hash: {
            get: function () { return instance.hash; },
            set: function (v) { instance.hash = v; tidy_instance(); },
            enumerable: true, configurable: true
          },
          toString: {
            value: function() { return instance.toString(); },
            enumerable: false, configurable: true
          },
          valueOf: {
            value: function() { return instance.valueOf(); },
            enumerable: false, configurable: true
          }
        });
  
        function tidy_instance() {
          var href = instance.href.replace(/#$|\?$|\?(?=#)/g, '');
          if (instance.href !== href)
            instance.href = href;
        }
  
        function update_steps() {
          query_object._setList(instance.search ? urlencoded_parse(instance.search.substring(1)) : []);
          query_object._update_steps();
        };
  
        return self;
      }
  
      if (origURL) {
        for (var i in origURL) {
          if (origURL.hasOwnProperty(i) && typeof origURL[i] === 'function')
            URL[i] = origURL[i];
        }
      }
  
      global.URL = URL;
      global.URLSearchParams = URLSearchParams;
    }());
  
    // Patch native URLSearchParams constructor to handle sequences/records
    // if necessary.
    (function() {
      if (new global.URLSearchParams([['a', 1]]).get('a') === '1' &&
          new global.URLSearchParams({a: 1}).get('a') === '1')
        return;
      var orig = global.URLSearchParams;
      global.URLSearchParams = function(init) {
        if (init && typeof init === 'object' && isSequence(init)) {
          var o = new orig();
          toArray(init).forEach(function(e) {
            if (!isSequence(e)) throw TypeError();
            var nv = toArray(e);
            if (nv.length !== 2) throw TypeError();
            o.append(nv[0], nv[1]);
          });
          return o;
        } else if (init && typeof init === 'object') {
          o = new orig();
          Object.keys(init).forEach(function(key) {
            o.set(key, init[key]);
          });
          return o;
        } else {
          return new orig(init);
        }
      };
    }());
  
  }(self));
  