您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Provides utility functions to get and wait for elements asyncronously that are not yet loaded or available on the page.
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/488161/1332704/wait-for-element.js
// ==UserScript== // @name wait-for-element // @description Provides utility functions to get and wait for elements asyncronously that are not yet loaded or available on the page. // @version 0.0.1 // @namespace owowed.moe // @author owowed <[email protected]> // @require https://update.greasyfork.org/scripts/488160/1332703/make-mutation-observer.js // @license LGPL-3.0 // ==/UserScript== /** * @typedef WaitForElementOptions * @prop {string} [id] - when used, it will select element by ID, and it will use the `document` as the selector parent. * @prop {string | string[]} selector - the selector for the element. If `selector` is `string[]`, then `multiple` option is automatically enabled. Setting `multiple` option to `false` will not have effect at all. * @prop {ParentNode} [parent] - when used, it will instead use `parent` parent element as the selector parent. This option will specify/limit the scope of query selector from `parent` parent element. This may be useful for optimizing selecting element. * @prop {AbortSignal} [abortSignal] - when used, user may able to abort waiting for element by using `AbortSignal#abort()`. * @prop {boolean} [multiple] - when used, `waitForElement*` will act as `ParentNode#querySelectorAll()`. * @prop {number} [timeout] - will set wait for element timeout. Default timeout is 5 seconds. * @prop {boolean} [enableTimeout] - if timeout set by `timeout` reached, `waitForElement*` will throw `WaitForElementTimeoutError`. * @prop {number} [maxTries] - will set how many attempt `waitForElement*` will query select, and if it reached, it will throw `WaitForElementMaximumTriesError`. * @prop {boolean} [ensureDomContentLoaded] - ensure DOM content loaded by listening to `DOMContentLoad` event, and then execute by that, or checking by using `document.readyState`. * @prop {MutationObserverInit} [observerOptions] - set options for `MutationObserver` used in `waitForElement*`. * @prop {(elem: HTMLElement) => boolean} [filter] - filter multiple or single element before being returned. * @prop {(elem: HTMLElement) => HTMLElement} [transform] - transform or modify multiple or single element before being returned. */ /** * @typedef {Element[] | Element | null} WaitForElementReturnValue */ class WaitForElementError extends Error { name = this.constructor.name; } class WaitForElementTimeoutError extends WaitForElementError {} class WaitForElementMaximumTriesError extends WaitForElementError {} /** * Wait for element asyncronously until the element is available on the page. This function immediately accept `parent` as its first parameter. `parent` parameter will specify/limit the scope of query selector. This may be useful for optimizing selecting element. * @param {NonNullable<WaitForElementOptions["parent"]>} parent * @param {WaitForElementOptions["selector"]} selector * @param {WaitForElementOptions} options * @returns {WaitForElementReturnValue} */ function waitForElementByParent(parent, selector, options) { return waitForElementOptions({ selector, parent, ...options }); } /** * Wait for element asyncronously until the element is available on the page. This function immediately accept `selector` as its first parameter. * @param {WaitForElementOptions["selector"]} selector * @param {WaitForElementOptions} options * @returns {WaitForElementReturnValue} */ function waitForElement(selector, options) { return waitForElementOptions({ selector, ...options }); } /** * Wait for element asyncronously until the element is available on the page. * @param {WaitForElementOptions} options * @returns {WaitForElementReturnValue} */ function waitForElementOptions( { id, selector, parent = document.documentElement, abortSignal, // abort controller signal multiple = false, timeout = 5000, enableTimeout = true, maxTries = Number.MAX_SAFE_INTEGER, ensureDomContentLoaded = true, observerOptions = {}, filter, transform, throwError } = {}) { return new Promise((resolve, reject) => { let result, tries = 0; function* applyFilterTransform(result) { if (filter != undefined) { for (let elem of result) { if (filter(elem)) { if (transform) elem = transform(elem); yield elem; } } } else if (transform != undefined) { for (const elem of result) { yield transform(elem); } } } function tryQueryElement(observer) { abortSignal?.throwIfAborted(); if (multiple && id == undefined) { if (Array.isArray(selector)) { result = []; for (const sel of selector) { result = result.concat(Array.from(parent.querySelectorAll(sel))); } } else { result = Array.from(parent.querySelectorAll(selector)); } result = Array.from(applyFilterTransform(result)); } else { if (id) { result = document.getElementById(id); } else if (Array.isArray(selector)) { result = []; function* querySelectorIterator() { for (const sel of selector) { yield parent.querySelector(sel); } } result = Array.from(applyFilterTransform(querySelectorIterator())); } else { result = parent.querySelector(selector); } if (transform) result = transform(result); if (filter != undefined && !filter(result)) { return false; } } if (multiple ? result?.length > 0 : result) { observer?.disconnect(); resolve(result); return result; } tries++; if (tries >= maxTries) { observer?.disconnect(); if (throwError) { reject(new WaitForElementMaximumTriesError(`maximum number of tries (${maxTries}) reached waiting for element "${selector}"`)); } else { resolve(null); } return false; } } function startWaitForElement() { const firstResult = tryQueryElement(); if (firstResult) return; let observer = makeMutationObserver( { target: parent, childList: true, subtree: true, abortSignal, ...observerOptions }, () => tryQueryElement(observer)); let timeoutId = null; if (enableTimeout) { timeoutId = setTimeout(() => { observer.disconnect(); if (throwError) { reject(new WaitForElementTimeoutError(`timeout waiting for element "${selector}"`)); } else { resolve(null); } }, timeout); } abortSignal?.addEventListener("abort", () => { clearTimeout(timeoutId); observer.disconnect(); if (throwError) { reject(new DOMException(abortSignal.reason, "AbortError")); } else { resolve(null); } }); tryQueryElement(); } if (ensureDomContentLoaded && document.readyState == "loading") { document.addEventListener("DOMContentLoaded", () => { startWaitForElement(); }); } else { startWaitForElement(); } }); }