「@run-at document-start」で動作するGreasemonkeyスクリプト向けの待機処理を行います。
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/17896/112958/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E8%B5%B7%E5%8B%95.js
// ==UserScript==
// @name スクリプトの起動
// @version 1.0.1
// @description 「@run-at document-start」で動作するGreasemonkeyスクリプト向けの待機処理を行います。
// @require https://greasyfork.org/scripts/17895/code/polyfill.js?version=112591
// @license Mozilla Public License Version 2.0 (MPL 2.0); https://www.mozilla.org/MPL/2.0/
// @compatible Firefox
// @compatible Opera 36以降。また、「@require https://greasyfork.org/scripts/17895/code/polyfill.js?version=112591」を先に設定する必要あり
// @compatible Chrome 「@require https://greasyfork.org/scripts/17895/code/polyfill.js?version=112591」を先に設定する必要あり
// @run-at document-start
// @author 100の人
// @homepage https://greasyfork.org/scripts/17896
// ==/UserScript==
(function () {
'use strict';
/**
* {@link checkExistingTarget}で{@link startMain}を実行する間隔 (ミリ秒)。
* @type {number}
*/
const INTERVAL = 10;
/**
* {@link checkExistingTarget}で{@link startMain}を実行する回数。
* @type {number}
*/
const LIMIT = 500;
/**
* 挿入された要素の親が、目印となる要素の親か否かを返すコールバック関数。
* @callback isTargetParent
* @param {(Document|Element)} parent
* @returns {boolean}
*/
/**
* 挿入された要素が、目印となる要素か否かを返すコールバック関数。
* @callback isTarget
* @param {(DocumentType|Element)} target
* @returns {boolean}
*/
/**
* 目印となる要素が文書に存在するか否かを返すコールバック関数。
* @callback existsTarget
* @returns {boolean}
*/
/**
* 目印となる要素が挿入された直後に関数を実行します。
* @param {Function} main - 実行する関数。
* @param {isTargetParent} isTargetParent
* @param {isTarget} isTarget
* @param {existsTarget} existsTarget
* @param {Object} [callbacksForFirefox]
* @param {isTargetParent} [callbacksForFirefox.isTargetParent] - Firefoxにおける{@link isTargetParent}。
* @param {isTarget} [callbacksForFirefox.isTarget] - Firefoxにおける{@link isTarget}。
* @param {number} [timeoutSinceStopParsingDocument=0] - DOM構築完了後に監視を続けるミリ秒数。
*/
window.startScript = function (
main,
isTargetParent,
isTarget,
existsTarget,
callbacksForFirefox = {},
timeoutSinceStopParsingDocument = 0
) {
/**
* 実行済みなら真。
* @type {boolean}
*/
let alreadyCalled = false;
let observer;
// 目印となる要素が既に存在していれば、即実行
startMain();
if (alreadyCalled) {
return;
}
// FirefoxのMutationObserverは、HTMLのDOM構築に関して要素をまとめて挿入したと見なすため、isTargetParent、isTargetを変更
if (typeof MozSettingsEvent !== 'undefined') {
isTargetParent = callbacksForFirefox.isTargetParent || isTargetParent;
isTarget = callbacksForFirefox.isTarget || isTarget;
}
observer = new MutationObserver(mutationCallback);
observer.observe(document, {
childList: true,
subtree: true,
});
if (document.readyState === 'complete') {
// DOMの構築が完了していれば
onDOMContentLoaded();
} else {
document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
}
/**
* {@link startMain}を実行し、スクリプトが開始されていなければさらに{@link timeoutSinceStopParsingDocument}ミリ秒待機し、
* スクリプトが開始されていなければ{@link stopObserving}を実行します。
*/
function onDOMContentLoaded() {
startMain();
if (timeoutSinceStopParsingDocument === 0) {
if (!alreadyCalled) {
stopObserving();
}
} else {
window.setTimeout(function () {
if (!alreadyCalled) {
stopObserving();
}
}, timeoutSinceStopParsingDocument);
}
}
/**
* 目印となる要素が挿入されたら、監視を停止し、{@link checkExistingTarget}を実行します。
* @param {MutationRecord[]} mutations - A list of MutationRecord objects.
* @param {MutationObserver} observer - The constructed MutationObserver object.
*/
function mutationCallback(mutations, observer) {
for (let mutation of mutations) {
let target = mutation.target;
if (target.nodeType === Node.ELEMENT_NODE && isTargetParent(target)) {
// 子が追加されたノードが要素で、かつその要素についてisTargetParentが真を返せば
for (let addedNode of mutation.addedNodes) {
if (addedNode.nodeType === Node.ELEMENT_NODE && isTarget(addedNode)) {
// 追加された子が要素で、かつその要素についてisTargetが真を返せば
observer.disconnect();
checkExistingTarget(0);
return;
}
}
}
}
}
/**
* {@link startMain}を実行し、スクリプトが開始されていなければ再度実行します。
* @param {number} count - {@link startMain}を実行した回数。
*/
function checkExistingTarget(count) {
startMain();
if (!alreadyCalled && count < LIMIT) {
window.setTimeout(checkExistingTarget, INTERVAL, count + 1);
}
}
/**
* 目印となる要素が存在するか確認し、存在すれば{@link stopObserving}を実行しスクリプトを開始します。
*/
function startMain() {
if (!alreadyCalled && existsTarget()) {
stopObserving();
main();
}
}
/**
* 監視を停止します。
*/
function stopObserving() {
alreadyCalled = true;
if (typeof observer !== 'undefined') {
observer.disconnect();
}
document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
}
}
})();