polyfill-service
Version:
A polyfill combinator
460 lines (369 loc) • 8.72 kB
JavaScript
// 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));