Greasy Fork

ElementGetter库

目前为 2022-08-04 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.cloud/scripts/448895/1077494/ElementGetter%E5%BA%93.js

// ==UserScript==
// @name         ElementGetter库
// @author       cxxjackie
// @version      1.1.1
// @namespace    http://tampermonkey.net/
// @description  库
// @icon
// @grant        none
// @license MIT
// ==/UserScript==


(function() {
    'use strict';

    // Your code here...
})();

class ElementGetter {
    #jQuery;
    #window;
    #matchesSelector;
    #mutationObserver;
    #addListener;
    #listeners;
    #addObserver(target, callback) {
        const observer = new this.#mutationObserver(mutations => {
            for (const mutation of mutations) {
                for (const addedNode of mutation.addedNodes) {
                    if (observer.canceled) return;
                    callback(addedNode);
                }
            }
        });
        observer.canceled = false;
        observer.observe(target, { childList: true, subtree: true });
        return function() {
            observer.canceled = true;
            observer.disconnect();
        };
    }
    #addEvent(target, callback) {
        const listener = e => callback(e.target);
        target.addEventListener('DOMNodeInserted', listener);
        return function() {
            target.removeEventListener('DOMNodeInserted', listener);
        };
    }
    #addFilter(target, filter) {
        if (this.#listeners.has(target)) {
            const listener = this.#listeners.get(target);
            listener.filters.push(filter);
        } else {
            const removeFunc = this.#addListener(target, node => {
                if (node instanceof Element) {
                    listener.filters.forEach(f => f(node));
                }
            });
            const listener = {
                filters: [filter],
                remove: removeFunc
            };
            this.#listeners.set(target, listener);
        }
    }
    #removeFilter(target, filter) {
        if (!this.#listeners.has(target)) return;
        const listener = this.#listeners.get(target);
        const index = listener.filters.indexOf(filter);
        listener.filters.splice(index, 1);
        if (listener.filters.length === 0) {
            listener.remove();
            this.#listeners.delete(target);
        }
    }
    #query(selector, parent, includeParent) {
        const $ = this.#jQuery;
        if ($) {
            let jNodes = $(parent.querySelectorAll('*'));
            if (includeParent) jNodes = jNodes.add(parent);
            jNodes = jNodes.filter(selector);
            return jNodes.length > 0 ? $(jNodes.get(0)) : null;
        } else {
            if (includeParent && this.#matchesSelector.call(parent, selector)) {
                return parent;
            }
            return parent.querySelector(selector);
        }
    }
    #queryAll(selector, parent, includeParent) {
        const $ = this.#jQuery;
        let result = [];
        if ($) {
            let jNodes = $(parent.querySelectorAll('*'));
            if (includeParent) jNodes = jNodes.add(parent);
            jNodes = jNodes.filter(selector);
            jNodes.each((i, elm) => result.push($(elm)));
        } else {
            if (includeParent && this.#matchesSelector.call(parent, selector)) {
                result.push(parent);
            }
            result.push(...parent.querySelectorAll(selector));
        }
        return result;
    }
    #getOne(selector, parent, timeout) {
        return new Promise(resolve => {
            const result = this.#query(selector, parent, false);
            if (result) return resolve(result);
            let timer;
            const filter = node => {
                const result = this.#query(selector, node, true);
                if (result) {
                    this.#removeFilter(parent, filter);
                    timer && clearTimeout(timer);
                    resolve(result);
                }
            };
            this.#addFilter(parent, filter);
            if (timeout > 0) {
                timer = setTimeout(() => {
                    this.#removeFilter(parent, filter);
                    resolve(null);
                }, timeout);
            }
        });
    }
    #getList(selectorList, parent, timeout) {
        const promiseList = [];
        for (const selector of selectorList) {
            promiseList.push(this.#getOne(selector, parent, timeout));
        }
        return Promise.all(promiseList);
    }
    constructor(jQuery) {
        this.#jQuery = jQuery && jQuery.fn && jQuery.fn.jquery ? jQuery : null;
        this.#window = window.unsafeWindow || document.defaultView || window;
        const elmProto = this.#window.Element.prototype;
        this.#matchesSelector = elmProto.matches
            || elmProto.matchesSelector
            || elmProto.webkitMatchesSelector
            || elmProto.msMatchesSelector
            || elmProto.mozMatchesSelector;
        this.#mutationObserver = this.#window.MutationObserver
            || this.#window.WebkitMutationObserver
            || this.#window.MozMutationObserver;
        this.#addListener = this.#mutationObserver ? this.#addObserver : this.#addEvent;
        this.#listeners = new WeakMap();
    }
    get(selector, ...args) {
        const parent = typeof args[0] !== 'number' && args.shift() || this.#window.document;
        const timeout = args[0] || 0;
        if (typeof selector === 'string' || selector instanceof String) {
            return this.#getOne(selector, parent, timeout);
        } else if (selector instanceof Array) {
            return this.#getList(selector, parent, timeout);
        } else {
            return Promise.resolve(null);
        }
    }
    each(selector, ...args) {
        const parent = typeof args[0] !== 'function' && args.shift() || this.#window.document;
        const callback = args[0];
        let removed = false;
        const handle = {
            remove: () => {
                removed = true;
            }
        };
        setTimeout(() => {
            const elms = this.#queryAll(selector, parent, false);
            for (const elm of elms) {
                if (callback(elm, false) === false || removed) return;
            }
            const refs = new WeakSet();
            const removeFunc = this.#addListener(parent, node => {
                if (node instanceof Element) {
                    const elms = this.#queryAll(selector, node, true);
                    for (const elm of elms) {
                        const _node = this.#jQuery ? elm.get(0) : elm;
                        if (!refs.has(_node)) {
                            refs.add(_node);
                            if (callback(elm, false) === false) handle.remove();
                            if (removed) return;
                        }
                    }
                }
            });
            handle.remove = () => {
                removed = true;
                removeFunc();
            };
        }, 0);
        return handle;
    }
    remove(handle) {
        handle && handle.remove();
    }
    create(domString) {
        const template = this.#window.document.createElement('template');
        template.innerHTML = domString;
        const node = template.content.firstElementChild || template.content.firstChild;
        node.remove();
        return node;
    }
}