// ==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);
} });
}
})();