UNPKG

polyfill-service

Version:
460 lines (369 loc) 8.72 kB
// URL Polyfill // Draft specification: http://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'; var origURL = global.URL; function URLUtils (url) { var anchor = document.createElement('a'); anchor.href = url; return anchor; } global.URL = 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 () { var doc; // Use another document/base tag/anchor for relative URL resolution, if possible if (document.implementation && document.implementation.createHTMLDocument) { doc = document.implementation.createHTMLDocument(''); } else if (document.implementation && document.implementation.createDocument) { doc = document.implementation.createElement('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; }()); } // 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 var ES5_GET_SET = false; try { ES5_GET_SET = (Object.defineProperties && (function () { var o = {}; Object.defineProperties(o, { p: { 'get': function () { return true; } } }); return o.p; }())); } catch (e) { ES5_GET_SET = false; }; var self = ES5_GET_SET ? this : document.createElement('a'); // NOTE: Doesn't do the encoding/decoding dance function 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 URLSearchParams (url_object, init) { var pairs = []; if (init) { pairs = parse(init); } this._setPairs = function (list) { pairs = list; }; this._updateSteps = function () { updateSteps(); }; var updating = false; function updateSteps () { if (updating) return; updating = true; // TODO: For all associated url objects url_object.search = serialize(pairs); updating = false; } // NOTE: Doesn't do the encoding/decoding dance function 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, '+'); } Object.defineProperties(this, { append: { value: function (name, value) { pairs.push({ name : name, value: value }); updateSteps(); } }, 'delete': { value: function (name) { for (var i = 0; i < pairs.length;) { if (pairs[i].name === name){ pairs.splice(i, 1); } else { ++i; } } updateSteps(); } }, get: { value: function (name) { for (var i = 0; i < pairs.length; ++i) { if (pairs[i].name === name) { return pairs[i].value; } } return null; } }, getAll: { value: function (name) { var result = []; for (var i = 0; i < pairs.length; ++i) { if (pairs[i].name === name) { result.push(pairs[i].value); } } return result; } }, has: { value: function (name) { for (var i = 0; i < pairs.length; ++i) { if (pairs[i].name === name) { return true; } } return false; } }, set: { value: function (name, value) { var found = false; for (var i = 0; i < pairs.length;) { if (pairs[i].name === name) { if (!found) { pairs[i].value = value; found = true; ++i; } else { pairs.splice(i, 1); } } else { ++i; } } if (!found){ pairs.push({name: name, value: value}); } updateSteps(); } }, toString: { value: function () { return serialize(pairs); } } }); } var queryObject = new URLSearchParams( self, instance.search ? instance.search.substring(1) : null); Object.defineProperties(self, { href: { get: function () { return instance.href; }, set: function (v) { instance.href = v; tidy_instance(); update_steps(); } }, origin: { get: function () { if ('origin' in instance) { return instance.origin; } return this.protocol + '//' + this.host; } }, protocol: { get: function () { return instance.protocol; }, set: function (v) { instance.protocol = v; } }, username: { get: function () { return instance.username; }, set: function (v) { instance.username = v; } }, password: { get: function () { return instance.password; }, set: function (v) { instance.password = v; } }, 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; } }, hostname: { get: function () { return instance.hostname; }, set: function (v) { instance.hostname = v; } }, port: { get: function () { return instance.port; }, set: function (v) { instance.port = v; } }, 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; } }, search: { get: function () { return instance.search; }, set: function (v) { if (instance.search !== v) { instance.search = v; tidy_instance(); update_steps(); } } }, searchParams: { get: function () { return queryObject; } // TODO: implement setter }, hash: { get: function () { return instance.hash; }, set: function (v) { instance.hash = v; tidy_instance(); } }, toString: { value: function () { return instance.toString(); } }, valueOf: { value: function () { return instance.valueOf(); } } }); function tidy_instance () { var href = instance.href.replace(/#$|\?$|\?(?=#)/g, ''); if (instance.href !== href){ instance.href = href; } } function update_steps () { queryObject._setPairs(instance.search ? parse(instance.search.substring(1)) : []); queryObject._updateSteps(); } return self; }; if (origURL) { for (var i in origURL) { if (origURL.hasOwnProperty(i)) { global.URL[i] = origURL[i]; } } } }(this));