Greasy Fork is available in English.

polyfill

Microsoft Edge、Firefox、Opera、Google Chrome向けのpolyfillです。

Version au 22/01/2017. Voir la dernière version.

Ce script ne doit pas être installé directement. C'est une librairie destinée à être incluse dans d'autres scripts avec la méta-directive // @require https://update.greasyfork.org/scripts/17895/170891/polyfill.js

// ==UserScript==
// @name        polyfill
// @description Microsoft Edge、Firefox、Opera、Google Chrome向けのpolyfillです。
// @version     1.6.0
// @license     Mozilla Public License Version 2.0 (MPL 2.0); https://www.mozilla.org/MPL/2.0/
// @compatible  Edge
// @compatible  Firefox
// @compatible  Opera
// @compatible  Chrome
// @author      100の人
// @homepage    https://greasyfork.org/scripts/17895
// ==/UserScript==

(function () {
'use strict';

///////////////////////////////////////////////////////////////////////////////
//////// For Microsoft Edge, Firefox, Opera, and Google Chrome         ////////
///////////////////////////////////////////////////////////////////////////////

if (typeof URLSearchParams === 'undefined') {
	// Microsoft Edge
	let privateFields = new WeakMap();
	let pte = function (object) {
		let fields = {};
		if (privateFields.has(object)) {
			// クラス、またはインスタンス
			fields = privateFields.get(object);
		} else {
			if (privateFields.has(object.constructor.prototype)) {
				// インスタンス
				Object.assign(fields, privateFields.get(object.constructor.prototype));
				for (let key in fields) {
					if (typeof fields[key] === 'function') {
						// インスタンスメソッド
						fields[key] = fields[key].bind(object);
					}
				}
			}
			privateFields.set(object, fields);
		}
		return fields;
	};

	/**
	 * @param {string} input
	 * @returns {(string[])[]} List of name-value pairs where both name and value hold a string.
	 * @see [application/x-www-form-urlencoded – URL Standard]{@link https://url.spec.whatwg.org/#concept-urlencoded-string-parser}
	 * @memberof URL
	 * @function
	 * @access private
	 */
	let parseXWWWFormUrlencoded = input => input.split('&').filter(bytes => bytes !== '').map(
		bytes => bytes.replace(/\+/g, ' ').split(/([^=]*)=?(.*)/).slice(1, 3).map(decodeURIComponent)
	);

	/**
	 * @param {string} input - A byte sequence.
	 * @returns {string}
	 * @see [application/x-www-form-urlencoded – URL Standard]{@link https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer}
	 * @memberof URL
	 * @function
	 * @access private
	 */
	let serializeXWWWFormUrlencodedByte
		= input => encodeURIComponent(input).replace(/%20/g, '+').replace(/[!~'()]+/g, escape);

	/**
	 * @param {(string[])[]} pairs - List of name-value pairs pairs.
	 * @returns {string}
	 * @see [application/x-www-form-urlencoded – URL Standard]{@link https://url.spec.whatwg.org/#concept-urlencoded-serializer}
	 * @memberof URL
	 * @function
	 * @access private
	 */
	let serializeXWWWFormUrlencodedString = function (pairs) {
		return pairs
			.map(pair => serializeXWWWFormUrlencodedByte(pair[0]) + '=' + serializeXWWWFormUrlencodedByte(pair[1]))
			.join('&');
	};

	Object.defineProperty(window, 'URLSearchParams', {
		writable: true,
		enumerable: false,
		configurable: true,

		/**
		 * A URLSearchParams object has an associated list of name-value pairs, which is initially empty.
		 * @see [URLSearchParams Interface – URL Standard]{@link https://url.spec.whatwg.org/#interface-urlsearchparams}
		 */
		value: class {
			/**
			 * @param {((string[])[]|Object.<string>|string)} [init]
			 */
			constructor(init = '')
			{
				/**
				 * A URLSearchParams object has an associated list of name-value pairs, which is initially empty.
				 * @member {(string[])[]}
				 * @access private
				 */
				pte(this).list = typeof init === 'object' && init !== null
					? (Symbol.iterator in init
						? Array.from(init).map(function (item) {
							let pair = Array.from(item);
							if (pair.length !== 2) {
								throw new TypeError(
									'URLSearchParams require name/value tuples when being initialized by a sequence.'
								);
							}
							return [String(pair[0]), String(pair[1])];
						})
						: Object.getOwnPropertyNames(init).map(key => [String(key), String(init[key])]))
					: parseXWWWFormUrlencoded(String(init).replace(/^\?/, ''));

				/**
				 * A URLSearchParams object has an associated url object, which is initially null.
				 * @member {?(URL|HTMLAnchorElement|HTMLAreaElement)}
				 * @access private
				 */
				pte(this).urlObject = null;
			}

			/**
			 * Append a new name-value pair whose name is name and value is value, to the list of name-value pairs.
			 * @param {string} name
			 * @param {string} value
			 */
			append(name, value)
			{
				if (arguments.length < 2) {
					throw new TypeError(`Failed to execute 'append' on 'URLSearchParams': 2 argument required, but only ${arguments.length} present.`);
				}
				pte(this).list.push([String(name), String(value)]);
				pte(this).update();
			}

			/**
			 * Remove all name-value pairs whose name is name.
			 * @param {string} name
			 */
			delete(name)
			{
				if (arguments.length < 1) {
					throw new TypeError(`Failed to execute 'delete' on 'URLSearchParams': 1 argument required, but only ${arguments.length} present.`);
				}
				for (let i = 0, l = pte(this).list.length; i < l; i++) {
					if (pte(this).list[i][0] === name) {
						pte(this).list.splice(i, 1);
						i--;
						l--;
					}
				}
				pte(this).update();
			}

			/**
			 * Return the value of the first name-value pair whose name is name, and null if there is no such pair.
			 * @param {string} name
			 * @returns {?string}
			 */
			get(name)
			{
				if (arguments.length < 1) {
					throw new TypeError(`Failed to execute 'get' on 'URLSearchParams': 1 argument required, but only ${arguments.length} present.`);
				}
				for (let pair of pte(this).list) {
					if (pair[0] === name) {
						return pair[1];
					}
				}
				return null;
			}

			/**
			 * Return the values of all name-value pairs whose name is name, in list order,
			 * and the empty sequence otherwise.
			 * @param {string} name
			 * @returns {string[]}
			 */
			getAll(name)
			{
				if (arguments.length < 1) {
					throw new TypeError(`Failed to execute 'getAll' on 'URLSearchParams': 1 argument required, but only ${arguments.length} present.`);
				}
				let values = [];
				for (let pair of pte(this).list) {
					if (pair[0] === name) {
						values.push(pair[1]);
					}
				}
				return values;
			}

			/**
			 * If there are any name-value pairs whose name is name,
			 * set the value of the first such name-value pair to value and remove the others.
			 * Otherwise,
			 * append a new name-value pair whose name is name and value is value, to the list of name-value pairs.
			 * @param {string} name
			 * @param {string} value
			 */
			set(name, value)
			{
				if (arguments.length < 2) {
					throw new TypeError(`Failed to execute 'set' on 'URLSearchParams': 1 argument required, but only ${arguments.length} present.`);
				}
				let flag;
				for (let i = 0, l = pte(this).list.length; i < l; i++) {
					if (pte(this).list[i][0] === name) {
						if (flag) {
							pte(this).list.splice(i, 1);
							i--;
							l--;
						} else {
							pte(this).list[i][1] = String(value);
							flag = true;
						}
					}
				}
				if (!flag) {
					pte(this).list.push([String(name), String(value)]);
				}
				pte(this).update();
			}

			/**
			 * Return true if there is a name-value pair whose name is name, and false otherwise.
			 * @param {string} name
			 * @returns {boolean}
			 */
			has(name)
			{
				if (arguments.length < 1) {
					throw new TypeError(`Failed to execute 'name' on 'URLSearchParams': 1 argument required, but only ${arguments.length} present.`);
				}
				return pte(this).list.some(pair => pair[0] === name);
			}

			/**
			 * Return the serialization of the URLSearchParams object's associated list of name-value pairs.
			 * @returns {string}
			 */
			toString()
			{
				return serializeXWWWFormUrlencodedString(pte(this).list);
			}

			/**
			 * The value pairs to iterate over
			 *     are the list name-value pairs with the key being the name and the value the value.
			 * @returns {Iterator.<string[]>}
			 */
			* [Symbol.iterator]()
			{
				for (let [key, value] of pte(this).list) {
					yield [key, value];
				}
			}

			/**
			 * @param {Function} callback
			 * @param {*} thisArg
			 */
			forEach(callback, thisArg = null)
			{
				if (typeof callback !== 'function') {
					throw new TypeError(`${callback} is not a function`);
				}
				for (let [key, value] of pte(this).list) {
					callback.call(thisArg, value, key);
				}
			}

			/**
			 * @returns {Iterator.<string[]>}
			 */
			* entries()
			{
				for (let [key, value] of pte(this).list) {
					yield [key, value];
				}
			}

			/**
			 * @returns {Iterator.<string>}
			 */
			* keys()
			{
				for (let [key] of pte(this).list) {
					yield key;
				}
			}

			/**
			 * @returns {Iterator.<string>}
			 */
			* values()
			{
				for (let [, value] of pte(this).list) {
					yield value;
				}
			}
		},
	});

	// Symbol.iterator 以外の各メソッドを列挙可能に
	// 4.6.7. Operations <https://www.w3.org/TR/WebIDL-1/#es-operations>
	// 4.6.8. Common iterator behavior <https://www.w3.org/TR/WebIDL-1/#es-iterators>
	let props = {};
	for (let methodName of Object.getOwnPropertyNames(URLSearchParams.prototype)) {
		props[methodName] = { enumerable: true };
	}
	Object.defineProperties(URLSearchParams.prototype, props);

	/**
	 * @see [URLSearchParams Interface – URL Standard]{@link https://url.spec.whatwg.org/#concept-urlsearchparams-update}
	 * @access private
	 * @memberof URLSearchParams#
	 */
	pte(URLSearchParams.prototype).update = function () {
		let urlObject = pte(this).urlObject;
		if (urlObject) {
			urlObject.search = serializeXWWWFormUrlencodedString(pte(this).list);
		}
	};

	/*globals URL :true*/
	URL = new Proxy(URL, {
		construct(URL, argumentsList) {
			let url = Reflect.construct(URL, argumentsList);
			let queryObject = new URLSearchParams(url.search);
			pte(queryObject).urlObject = url;
			/**
			 * A URL object has a query object (a URLSearchParams object).
			 * @member {URLSearchParams}
			 * @access private
			 */
			pte(url).queryObject = queryObject;
			return url;
		},
	});

	/**
	 * @see [The search attribute – URL members – URL Standard]{@link https://url.spec.whatwg.org/#dom-url-search}
	 * @access private
	 * @memberof URL#
	 */
	pte(URL.prototype).updateQueryObject = function () {
		let list = pte(pte(this).queryObject).list;
		list.splice(0, list.length, ...parseXWWWFormUrlencoded(this.search.replace('?', '')));
	};

	Object.defineProperties(URL.prototype, {
		href: {
			set: new Proxy(Object.getOwnPropertyDescriptor(URL.prototype, 'href').set, {
				apply(setter, url, argumentsList) {
					Reflect.apply(setter, url, argumentsList);
					pte(url).updateQueryObject();
				},
			}),
		},
		search: {
			set: new Proxy(Object.getOwnPropertyDescriptor(URL.prototype, 'search').set, {
				apply(setter, url, argumentsList) {
					Reflect.apply(setter, url, argumentsList);
					pte(url).updateQueryObject();
				},
			}),
		},
		/**
		 * @member {URLSearchParams} URL#searchParams
		 * @readonly
		 */
		searchParams: {
			enumerable: true,
			configurable: true,
			get() {
				return pte(this).queryObject;
			},
		},
	});
} else if (!new URLSearchParams({key: ''}).has('key')) {
	// Firefox, Opera, and Google Chrome
	/*globals URLSearchParams: true */
	URLSearchParams = new Proxy(URLSearchParams, {
		construct(URLSearchParams, argumentsList) {
			if (argumentsList.length > 0) {
				if (typeof argumentsList[0] === 'object' && argumentsList[0] !== null) {
					let params = new URLSearchParams();
					if (Symbol.iterator in argumentsList[0]) {
						for (let item of argumentsList[0]) {
							let pair = Array.from(item);
							if (pair.length !== 2) {
								throw new TypeError(
									'URLSearchParams require name/value tuples when being initialized by a sequence.'
								);
							}
							params.append(pair[0], pair[1]);
						}
					} else {
						for (let key of Object.getOwnPropertyNames(argumentsList[0])) {
							params.append(key, argumentsList[0][key]);
						}
					}
					return params;
				}
				argumentsList[0] = String(argumentsList[0]).replace(/^\?/, '');
			}
			return Reflect.construct(URLSearchParams, argumentsList);
		},
	});
}

if (!('createFor' in URL)) {
	/**
	 * 分をミリ秒に変換するときの乗数。
	 * @constant {number}
	 */
	const MINUTES_TO_MILISECONDS = 60 * 1000;

	/**
	 * Blob URL を自動破棄するまでのミリ秒数。
	 * @constant {number}
	 */
	const MAX_LIFETIME = 10 * MINUTES_TO_MILISECONDS;

	/**
	 * Blob URLを生成し、{@link MAX_LIFETIME}ミリ秒後に破棄します。
	 * @see [File API]{@link https://www.w3.org/TR/FileAPI/#dfn-createFor}
	 * @see [Bug 1062917 - Implement URL.createFor]{@link https://bugzilla.mozilla.org/show_bug.cgi?id=1062917}
	 * @see [Issue 608460 - chromium - Consider implementing URL.createFor() - Monorail]{@link https://bugs.chromium.org/p/chromium/issues/detail?id=608460}
	 * @param {Blob} blob
	 * @returns {string} Blob URL。
	 */
	URL.createFor = function (blob) {
		let url = this.createObjectURL(blob);
		window.setTimeout(() => this.revokeObjectURL(url), MAX_LIFETIME);
		return url;
	};
}

///////////////////////////////////////////////////////////////////////////////
//////// For Microsoft Edge and Firefox 45 ESR                         ////////
///////////////////////////////////////////////////////////////////////////////

if (!('prepend' in document)) {
	/**
	 * 複数のインターフェースに[Unscopable]拡張属性を伴うメンバーを実装します。
	 * @param {Function[]} interfaces
	 * @param {Object.<Function>} members
	 */
	let implementUnscopableMembers = function (interfaces, members) {
		for (let intrfc of interfaces) {
			Object.assign(intrfc.prototype, members);
			if (Symbol.unscopables) {
				let object = {};
				for (let memberName of Object.keys(members)) {
					object[memberName] = true;
				}
				if (intrfc.prototype[Symbol.unscopables]) {
					Object.assign(intrfc.prototype[Symbol.unscopables], object);
				} else {
					intrfc.prototype[Symbol.unscopables] = object;
				}
			}
		}
	};

	/**
	 * @see [DOM Standard]{@link https://dom.spec.whatwg.org/#converting-nodes-into-a-node}
	 * @param {(Node|string)[]} nodes
	 * @returns {(Node|DocumentFragment)}
	 */
	let convertNodesIntoNode = function (nodes) {
		for (let i = 0, l = nodes.length; i < l; i++) {
			if (!(nodes[i] instanceof Node)) {
				nodes[i] = new Text(nodes[i]);
			}
		}
		if (nodes.length === 1) {
			return nodes[0];
		}
		let fragment = new DocumentFragment();
		for (let node of nodes) {
			fragment.appendChild(node);
		}
		return fragment;
	};

	// https://dom.spec.whatwg.org/#interface-parentnode
	implementUnscopableMembers([Document, DocumentFragment, Element], {
		/**
		 * Inserts nodes before the first child of node, while replacing strings in nodes with equivalent Text nodes.
		 * @see [DOM Standard]{@link https://dom.spec.whatwg.org/#dom-parentnode-prepend}
		 * @param {...(Node|string)} nodes
		 * @throws {DOMException} Throws a HierarchyRequestError if the constraints of the node tree are violated.
		 */
		prepend(...nodes) {
			this.insertBefore(convertNodesIntoNode(nodes), this.firstChild);
		},

		/**
		 * Inserts nodes after the last child of node, while replacing strings in nodes with equivalent Text nodes.
		 * @see [DOM Standard]{@link https://dom.spec.whatwg.org/#dom-parentnode-append}
		 * @param {...(Node|string)} nodes
		 * @throws {DOMException} Throws a HierarchyRequestError if the constraints of the node tree are violated.
		 */
		append(...nodes) {
			this.appendChild(convertNodesIntoNode(nodes));
		},
	});

	// https://dom.spec.whatwg.org/#interface-childnode
	implementUnscopableMembers([DocumentType, Element, CharacterData], {
		/**
		 * Inserts nodes just before node, while replacing strings in nodes with equivalent Text nodes.
		 * @see [DOM Standard]{@link https://dom.spec.whatwg.org/#dom-childnode-before}
		 * @param {...(Node|string)} nodes
		 * @throws {DOMException} Throws a HierarchyRequestError if the constraints of the node tree are violated.
		 */
		before(...nodes) {
			let parent = this.parentNode;
			if (!parent) {
				return;
			}
			let viablePreviousSibling;
			while ((viablePreviousSibling = this.previousSibling) && nodes.includes(viablePreviousSibling)) {
			}
			parent.insertBefore(
				convertNodesIntoNode(nodes),
				viablePreviousSibling ? viablePreviousSibling.nextSibling : parent.firstChild
			);
		},

		/**
		 * Inserts nodes just after node, while replacing strings in nodes with equivalent Text nodes.
		 * @see [DOM Standard]{@link https://dom.spec.whatwg.org/#dom-childnode-after}
		 * @param {...(Node|string)} nodes
		 * @throws {DOMException} Throws a HierarchyRequestError if the constraints of the node tree are violated.
		 */
		after(...nodes) {
			let parent = this.parentNode;
			if (!parent) {
				return;
			}
			let viableNextSibling;
			while ((viableNextSibling = this.nextSibling) && nodes.includes(viableNextSibling)) {
			}
			parent.insertBefore(convertNodesIntoNode(nodes), viableNextSibling);
		},

		/**
		 * Replaces node with nodes, while replacing strings in nodes with equivalent Text nodes.
		 * @see [DOM Standard]{@link https://dom.spec.whatwg.org/#dom-childnode-replacewith}
		 * @param {...(Node|string)} nodes
		 * @throws {DOMException} Throws a HierarchyRequestError if the constraints of the node tree are violated.
		 */
		replaceWith(...nodes) {
			let parent = this.parentNode;
			if (!parent) {
				return;
			}
			let viableNextSibling;
			while ((viableNextSibling = this.nextSibling) && nodes.includes(viableNextSibling)) {
			}
			let node = convertNodesIntoNode(nodes);
			if (this.parentNode === parent) {
				parent.replaceChild(node, this);
			} else {
				parent.insertBefore(node, viableNextSibling);
			}
		},
	});
}

///////////////////////////////////////////////////////////////////////////////
//////// For Microsoft Edge                                            ////////
///////////////////////////////////////////////////////////////////////////////

// 開発者ツールのコンソールから、スタックトレースなどを確認できるようにします
if (typeof chrome !== 'undefined' && !('runtime' in chrome)) {
	window.addEventListener('error', function (event) {
		if (event.error) {
			console.debug(event.error);
		}
	});
}

if (!(Symbol.iterator in NodeList.prototype)) {
	Object.assign(NodeList.prototype, {
		/**
		 * @see [Issue #5998615 NodeList should be iterable — Microsoft Edge Development]{@link https://developer.microsoft.com/microsoft-edge/platform/issues/5998615/}
		 * @returns {Iterator.<(number|Node)[]>}
		 */
		*[Symbol.iterator]()
		{
			for (let i = 0, l = this.length; i < l; i++) {
				yield this[i];
			}
		},
		/**
		 * @param {Function} callback
		 * @param {*} thisArg
		 * @function
		 */
		forEach: Array.prototype.forEach,
		/**
		 * @returns {Iterator.<(number|Node)[]>}
		 * @function
		 */
		* entries()
		{
			for (let i = 0, l = this.length; i < l; i++) {
				yield [i, this[i]];
			}
		},
		/**
		 * @returns {Iterator.<number>}
		 * @function
		 */
		* keys()
		{
			for (let i = 0, l = this.length; i < l; i++) {
				yield i;
			}
		},
		/**
		 * @returns {Iterator.<Node>}
		 * @function
		 */
		*values()
		{
			for (let i = 0, l = this.length; i < l; i++) {
				yield this[i];
			}
		},
	});
	Object.defineProperty(NodeList.prototype, Symbol.iterator, {enumerable: false});
}

try {
	new Text();
} catch (exception) {
	/*globals Text: true */
	Text = new Proxy(Text, {
		construct(Text, argumentsList)
		{
			return document.createTextNode(0 in argumentsList ? argumentsList[0] : '');
		},
	});
}

try {
	new Range();
} catch (exception) {
	/*globals Range: true */
	Range = new Proxy(Range, {
		construct(Range, argumentsList)
		{
			return document.createRange();
		},
	});
}

try {
	new DocumentFragment();
} catch (exception) {
	/*globals DocumentFragment: true */
	/**
	 * @see [Issue #9628204 Unable to call DocumentFragment as a constructor — Microsoft Edge Development]{@link https://developer.microsoft.com/microsoft-edge/platform/issues/9628204/}
	 */
	DocumentFragment = new Proxy(DocumentFragment, {
		construct(DocumentFragment, argumentsList)
		{
			return document.createDocumentFragment();
		},
	});
}

if (!('firstElementChild' in new DocumentFragment())) {
	/**
	 * @see [Issue #10060579 Document Fragment does not support children property — Microsoft Edge Development]{@link https://developer.microsoft.com/microsoft-edge/platform/issues/10060579/}
	 */
	Object.defineProperties(DocumentFragment.prototype, {
		firstElementChild: {
			get()
			{
				return Array.from(this.childNodes).find(node => node.nodeType === Node.ELEMENT_NODE);
			},
			enumerable: true,
			configurable: true,
		},
		lastElementChild: {
			get()
			{
				return Array.from(this.childNodes).reverse().find(node => node.nodeType === Node.ELEMENT_NODE);
			},
			enumerable: true,
			configurable: true,
		},
	});
}

/* eslint-disable */
/**
 * @see [Issue #10320716 Edge Browser missing function Element.closest(selector) — Microsoft Edge Development]{https://developer.microsoft.com/microsoft-edge/platform/issues/10320716/}
 * @see [Polyfill — Element.closest() — Web API インターフェイス | MDN]{@link https://developer.mozilla.org/docs/Web/API/Element/closest#Specification}
 * @license CC0-1.0
 */
if (window.Element && !Element.prototype.closest) {
    Element.prototype.closest =
    function(s) {
        var matches = (this.document || this.ownerDocument).querySelectorAll(s),
            i,
            el = this;
        do {
            i = matches.length;
            while (--i >= 0 && matches.item(i) !== el) {};
        } while ((i < 0) && (el = el.parentElement));
        return el;
    };
}
/* eslint-enable */

if (!(document.getElementsByName('') instanceof NodeList)) {
	Document.prototype.getElementsByName = new Proxy(Document.prototype.getElementsByName, {
		apply(getElementsByName, doc, argumentsList)
		{
			let htmlCollection = Reflect.apply(getElementsByName, doc, argumentsList);
			Object.defineProperty(htmlCollection, Symbol.iterator, {
				writable: true,
				enumerable: false,
				configurable: true,
				value: function * () {
					for (let i = 0, l = this.length; i < l; i++) {
						yield this[i];
					}
				},
			});
			return htmlCollection;
		},
	});
}

///////////////////////////////////////////////////////////////////////////////
//////// For Firefox                                                   ////////
///////////////////////////////////////////////////////////////////////////////

// 6.4 Time Zone Names | ECMAScript® 2016 Internationalization API Specification
// http://www.ecma-international.org/ecma-402/3.0/index.html#sec-time-zone-names
// Bug 837961 – Add support for IANA time zone names to internationalization API
// <https://bugzilla.mozilla.org/show_bug.cgi?id=837961>
// メモ:JavaScript で システム時刻から別のタイムゾーンの時刻へ変換 — ねじろぐ @drillbits
// <http://d.hatena.ne.jp/drillbits/20100127/javascript_timezone_system>
try {
	new Intl.DateTimeFormat('ja', {timeZone: 'Asia/Tokyo'});
} catch (exception) {
	/**
	 * 大文字化したタイムゾーン名とタイムゾーンオフセットの組。
	 * @constant {Object.<number>}
	 */
	const TIMEZONE_OFFSETS = {
		'ASIA/TOKYO': -540,
	};

	/**
	 * 分をミリ秒に変換するときの乗数。
	 * @constant {number}
	 */
	const MINUTES_TO_MILLISECONDS = 60 * 1000;

	Date.prototype.toLocaleString = new Proxy(Date.prototype.toLocaleString, {
		apply: function (target, thisArg, argumentsList) {
			if (typeof argumentsList[1] === 'object' && argumentsList[1] !== null) {
				let timeZone = String(argumentsList[1].timeZone).toUpperCase();
				if (TIMEZONE_OFFSETS.hasOwnProperty(timeZone)) {
					let timeZoneOffset = TIMEZONE_OFFSETS[timeZone];
					if (thisArg.getTimezoneOffset() === timeZoneOffset) {
						delete argumentsList[1].timeZone;
					} else {
						thisArg = new Date(thisArg.getTime() - timeZoneOffset * MINUTES_TO_MILLISECONDS);
						argumentsList[1].timeZone = 'UTC';
					}
				}
			}
			return target.apply(thisArg, argumentsList);
		},
	});
}

///////////////////////////////////////////////////////////////////////////////
//////// For Firefox 45 ESR                                            ////////
///////////////////////////////////////////////////////////////////////////////

try {
	new CustomEvent('', {detail: {}});
} catch (exception) {
	/*globals CustomEvent: true, cloneInto: true */
	CustomEvent = new Proxy(CustomEvent, { construct: function (Target, args) {
		if (args.length >= 2
			&& typeof args[1] === 'object' && typeof args[1].detail === 'object' && args[1].detail !== null) {
			args[1].detail = cloneInto(args[1].detail, window, {
				cloneFunctions: true,
				wrapReflectors: true,
			});
		}
		return new Target(...args);
	} });
}

})();