url-polyfill.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. (function(global) {
  2. /**
  3. * Polyfill URLSearchParams
  4. *
  5. * Inspired from : https://github.com/WebReflection/url-search-params/blob/master/src/url-search-params.js
  6. */
  7. var checkIfIteratorIsSupported = function() {
  8. try {
  9. return !!Symbol.iterator;
  10. } catch (error) {
  11. return false;
  12. }
  13. };
  14. var iteratorSupported = checkIfIteratorIsSupported();
  15. var createIterator = function(items) {
  16. var iterator = {
  17. next: function() {
  18. var value = items.shift();
  19. return { done: value === void 0, value: value };
  20. }
  21. };
  22. if (iteratorSupported) {
  23. iterator[Symbol.iterator] = function() {
  24. return iterator;
  25. };
  26. }
  27. return iterator;
  28. };
  29. /**
  30. * Search param name and values should be encoded according to https://url.spec.whatwg.org/#urlencoded-serializing
  31. * encodeURIComponent() produces the same result except encoding spaces as `%20` instead of `+`.
  32. */
  33. var serializeParam = function(value) {
  34. return encodeURIComponent(value).replace(/%20/g, '+');
  35. };
  36. var deserializeParam = function(value) {
  37. return decodeURIComponent(String(value).replace(/\+/g, ' '));
  38. };
  39. var polyfillURLSearchParams = function() {
  40. var URLSearchParams = function(searchString) {
  41. Object.defineProperty(this, '_entries', { writable: true, value: {} });
  42. var typeofSearchString = typeof searchString;
  43. if (typeofSearchString === 'undefined') {
  44. // do nothing
  45. } else if (typeofSearchString === 'string') {
  46. if (searchString !== '') {
  47. this._fromString(searchString);
  48. }
  49. } else if (searchString instanceof URLSearchParams) {
  50. var _this = this;
  51. searchString.forEach(function(value, name) {
  52. _this.append(name, value);
  53. });
  54. } else if ((searchString !== null) && (typeofSearchString === 'object')) {
  55. if (Object.prototype.toString.call(searchString) === '[object Array]') {
  56. for (var i = 0; i < searchString.length; i++) {
  57. var entry = searchString[i];
  58. if ((Object.prototype.toString.call(entry) === '[object Array]') || (entry.length !== 2)) {
  59. this.append(entry[0], entry[1]);
  60. } else {
  61. throw new TypeError('Expected [string, any] as entry at index ' + i + ' of URLSearchParams\'s input');
  62. }
  63. }
  64. } else {
  65. for (var key in searchString) {
  66. if (searchString.hasOwnProperty(key)) {
  67. this.append(key, searchString[key]);
  68. }
  69. }
  70. }
  71. } else {
  72. throw new TypeError('Unsupported input\'s type for URLSearchParams');
  73. }
  74. };
  75. var proto = URLSearchParams.prototype;
  76. proto.append = function(name, value) {
  77. if (name in this._entries) {
  78. this._entries[name].push(String(value));
  79. } else {
  80. this._entries[name] = [String(value)];
  81. }
  82. };
  83. proto.delete = function(name) {
  84. delete this._entries[name];
  85. };
  86. proto.get = function(name) {
  87. return (name in this._entries) ? this._entries[name][0] : null;
  88. };
  89. proto.getAll = function(name) {
  90. return (name in this._entries) ? this._entries[name].slice(0) : [];
  91. };
  92. proto.has = function(name) {
  93. return (name in this._entries);
  94. };
  95. proto.set = function(name, value) {
  96. this._entries[name] = [String(value)];
  97. };
  98. proto.forEach = function(callback, thisArg) {
  99. var entries;
  100. for (var name in this._entries) {
  101. if (this._entries.hasOwnProperty(name)) {
  102. entries = this._entries[name];
  103. for (var i = 0; i < entries.length; i++) {
  104. callback.call(thisArg, entries[i], name, this);
  105. }
  106. }
  107. }
  108. };
  109. proto.keys = function() {
  110. var items = [];
  111. this.forEach(function(value, name) {
  112. items.push(name);
  113. });
  114. return createIterator(items);
  115. };
  116. proto.values = function() {
  117. var items = [];
  118. this.forEach(function(value) {
  119. items.push(value);
  120. });
  121. return createIterator(items);
  122. };
  123. proto.entries = function() {
  124. var items = [];
  125. this.forEach(function(value, name) {
  126. items.push([name, value]);
  127. });
  128. return createIterator(items);
  129. };
  130. if (iteratorSupported) {
  131. proto[Symbol.iterator] = proto.entries;
  132. }
  133. proto.toString = function() {
  134. var searchArray = [];
  135. this.forEach(function(value, name) {
  136. searchArray.push(serializeParam(name) + '=' + serializeParam(value));
  137. });
  138. return searchArray.join('&');
  139. };
  140. global.URLSearchParams = URLSearchParams;
  141. };
  142. var checkIfURLSearchParamsSupported = function() {
  143. try {
  144. var URLSearchParams = global.URLSearchParams;
  145. return (
  146. (new URLSearchParams('?a=1').toString() === 'a=1') &&
  147. (typeof URLSearchParams.prototype.set === 'function') &&
  148. (typeof URLSearchParams.prototype.entries === 'function')
  149. );
  150. } catch (e) {
  151. return false;
  152. }
  153. };
  154. if (!checkIfURLSearchParamsSupported()) {
  155. polyfillURLSearchParams();
  156. }
  157. var proto = global.URLSearchParams.prototype;
  158. if (typeof proto.sort !== 'function') {
  159. proto.sort = function() {
  160. var _this = this;
  161. var items = [];
  162. this.forEach(function(value, name) {
  163. items.push([name, value]);
  164. if (!_this._entries) {
  165. _this.delete(name);
  166. }
  167. });
  168. items.sort(function(a, b) {
  169. if (a[0] < b[0]) {
  170. return -1;
  171. } else if (a[0] > b[0]) {
  172. return +1;
  173. } else {
  174. return 0;
  175. }
  176. });
  177. if (_this._entries) { // force reset because IE keeps keys index
  178. _this._entries = {};
  179. }
  180. for (var i = 0; i < items.length; i++) {
  181. this.append(items[i][0], items[i][1]);
  182. }
  183. };
  184. }
  185. if (typeof proto._fromString !== 'function') {
  186. Object.defineProperty(proto, '_fromString', {
  187. enumerable: false,
  188. configurable: false,
  189. writable: false,
  190. value: function(searchString) {
  191. if (this._entries) {
  192. this._entries = {};
  193. } else {
  194. var keys = [];
  195. this.forEach(function(value, name) {
  196. keys.push(name);
  197. });
  198. for (var i = 0; i < keys.length; i++) {
  199. this.delete(keys[i]);
  200. }
  201. }
  202. searchString = searchString.replace(/^\?/, '');
  203. var attributes = searchString.split('&');
  204. var attribute;
  205. for (var i = 0; i < attributes.length; i++) {
  206. attribute = attributes[i].split('=');
  207. this.append(
  208. deserializeParam(attribute[0]),
  209. (attribute.length > 1) ? deserializeParam(attribute[1]) : ''
  210. );
  211. }
  212. }
  213. });
  214. }
  215. // HTMLAnchorElement
  216. })(
  217. (typeof global !== 'undefined') ? global
  218. : ((typeof window !== 'undefined') ? window
  219. : ((typeof self !== 'undefined') ? self : this))
  220. );
  221. (function(global) {
  222. /**
  223. * Polyfill URL
  224. *
  225. * Inspired from : https://github.com/arv/DOM-URL-Polyfill/blob/master/src/url.js
  226. */
  227. var checkIfURLIsSupported = function() {
  228. try {
  229. var u = new global.URL('b', 'http://a');
  230. u.pathname = 'c d';
  231. return (u.href === 'http://a/c%20d') && u.searchParams;
  232. } catch (e) {
  233. return false;
  234. }
  235. };
  236. var polyfillURL = function() {
  237. var _URL = global.URL;
  238. var URL = function(url, base) {
  239. if (typeof url !== 'string') url = String(url);
  240. if (base && typeof base !== 'string') base = String(base);
  241. // Only create another document if the base is different from current location.
  242. var doc = document, baseElement;
  243. if (base && (global.location === void 0 || base !== global.location.href)) {
  244. base = base.toLowerCase();
  245. doc = document.implementation.createHTMLDocument('');
  246. baseElement = doc.createElement('base');
  247. baseElement.href = base;
  248. doc.head.appendChild(baseElement);
  249. try {
  250. if (baseElement.href.indexOf(base) !== 0) throw new Error(baseElement.href);
  251. } catch (err) {
  252. throw new Error('URL unable to set base ' + base + ' due to ' + err);
  253. }
  254. }
  255. var anchorElement = doc.createElement('a');
  256. anchorElement.href = url;
  257. if (baseElement) {
  258. doc.body.appendChild(anchorElement);
  259. anchorElement.href = anchorElement.href; // force href to refresh
  260. }
  261. var inputElement = doc.createElement('input');
  262. inputElement.type = 'url';
  263. inputElement.value = url;
  264. if (anchorElement.protocol === ':' || !/:/.test(anchorElement.href) || (!inputElement.checkValidity() && !base)) {
  265. throw new TypeError('Invalid URL');
  266. }
  267. Object.defineProperty(this, '_anchorElement', {
  268. value: anchorElement
  269. });
  270. // create a linked searchParams which reflect its changes on URL
  271. var searchParams = new global.URLSearchParams(this.search);
  272. var enableSearchUpdate = true;
  273. var enableSearchParamsUpdate = true;
  274. var _this = this;
  275. ['append', 'delete', 'set'].forEach(function(methodName) {
  276. var method = searchParams[methodName];
  277. searchParams[methodName] = function() {
  278. method.apply(searchParams, arguments);
  279. if (enableSearchUpdate) {
  280. enableSearchParamsUpdate = false;
  281. _this.search = searchParams.toString();
  282. enableSearchParamsUpdate = true;
  283. }
  284. };
  285. });
  286. Object.defineProperty(this, 'searchParams', {
  287. value: searchParams,
  288. enumerable: true
  289. });
  290. var search = void 0;
  291. Object.defineProperty(this, '_updateSearchParams', {
  292. enumerable: false,
  293. configurable: false,
  294. writable: false,
  295. value: function() {
  296. if (this.search !== search) {
  297. search = this.search;
  298. if (enableSearchParamsUpdate) {
  299. enableSearchUpdate = false;
  300. this.searchParams._fromString(this.search);
  301. enableSearchUpdate = true;
  302. }
  303. }
  304. }
  305. });
  306. };
  307. var proto = URL.prototype;
  308. var linkURLWithAnchorAttribute = function(attributeName) {
  309. Object.defineProperty(proto, attributeName, {
  310. get: function() {
  311. return this._anchorElement[attributeName];
  312. },
  313. set: function(value) {
  314. this._anchorElement[attributeName] = value;
  315. },
  316. enumerable: true
  317. });
  318. };
  319. ['hash', 'host', 'hostname', 'port', 'protocol']
  320. .forEach(function(attributeName) {
  321. linkURLWithAnchorAttribute(attributeName);
  322. });
  323. Object.defineProperty(proto, 'search', {
  324. get: function() {
  325. return this._anchorElement['search'];
  326. },
  327. set: function(value) {
  328. this._anchorElement['search'] = value;
  329. this._updateSearchParams();
  330. },
  331. enumerable: true
  332. });
  333. Object.defineProperties(proto, {
  334. 'toString': {
  335. get: function() {
  336. var _this = this;
  337. return function() {
  338. return _this.href;
  339. };
  340. }
  341. },
  342. 'href': {
  343. get: function() {
  344. return this._anchorElement.href.replace(/\?$/, '');
  345. },
  346. set: function(value) {
  347. this._anchorElement.href = value;
  348. this._updateSearchParams();
  349. },
  350. enumerable: true
  351. },
  352. 'pathname': {
  353. get: function() {
  354. return this._anchorElement.pathname.replace(/(^\/?)/, '/');
  355. },
  356. set: function(value) {
  357. this._anchorElement.pathname = value;
  358. },
  359. enumerable: true
  360. },
  361. 'origin': {
  362. get: function() {
  363. // get expected port from protocol
  364. var expectedPort = { 'http:': 80, 'https:': 443, 'ftp:': 21 }[this._anchorElement.protocol];
  365. // add port to origin if, expected port is different than actual port
  366. // and it is not empty f.e http://foo:8080
  367. // 8080 != 80 && 8080 != ''
  368. var addPortToOrigin = this._anchorElement.port != expectedPort &&
  369. this._anchorElement.port !== '';
  370. return this._anchorElement.protocol +
  371. '//' +
  372. this._anchorElement.hostname +
  373. (addPortToOrigin ? (':' + this._anchorElement.port) : '');
  374. },
  375. enumerable: true
  376. },
  377. 'password': { // TODO
  378. get: function() {
  379. return '';
  380. },
  381. set: function(value) {
  382. },
  383. enumerable: true
  384. },
  385. 'username': { // TODO
  386. get: function() {
  387. return '';
  388. },
  389. set: function(value) {
  390. },
  391. enumerable: true
  392. },
  393. });
  394. URL.createObjectURL = function(blob) {
  395. return _URL.createObjectURL.apply(_URL, arguments);
  396. };
  397. URL.revokeObjectURL = function(url) {
  398. return _URL.revokeObjectURL.apply(_URL, arguments);
  399. };
  400. global.URL = URL;
  401. };
  402. if (!checkIfURLIsSupported()) {
  403. polyfillURL();
  404. }
  405. if ((global.location !== void 0) && !('origin' in global.location)) {
  406. var getOrigin = function() {
  407. return global.location.protocol + '//' + global.location.hostname + (global.location.port ? (':' + global.location.port) : '');
  408. };
  409. try {
  410. Object.defineProperty(global.location, 'origin', {
  411. get: getOrigin,
  412. enumerable: true
  413. });
  414. } catch (e) {
  415. setInterval(function() {
  416. global.location.origin = getOrigin();
  417. }, 100);
  418. }
  419. }
  420. })(
  421. (typeof global !== 'undefined') ? global
  422. : ((typeof window !== 'undefined') ? window
  423. : ((typeof self !== 'undefined') ? self : this))
  424. );