// ==UserScript== // @name ElementGetter // @author cxxjackie // @version 1.2.0 // @supportURL https://bbs.tampermonkey.net.cn/thread-2726-1-1.html // ==/UserScript== class ElementGetter { #jQuery; #window; #matches; #MutationObs; #listeners; #addObserver(target, callback) { const observer = new this.#MutationObs(mutations => { for (const mutation of mutations) { if (mutation.type === 'attributes') { callback(mutation.target); if (observer.canceled) return; } for (const node of mutation.addedNodes) { if (node instanceof Element) callback(node); if (observer.canceled) return; } } }); observer.canceled = false; observer.observe(target, {childList: true, subtree: true, attributes: true}); return () => { observer.canceled = true; observer.disconnect(); }; } #addFilter(target, filter) { let listener = this.#listeners.get(target); if (!listener) { listener = { filters: new Set(), remove: this.#addObserver(target, el => { listener.filters.forEach(f => f(el)); }) }; this.#listeners.set(target, listener); } listener.filters.add(filter); } #removeFilter(target, filter) { const listener = this.#listeners.get(target); if (!listener) return; listener.filters.delete(filter); if (!listener.filters.size) { listener.remove(); this.#listeners.delete(target); } } #query(all, selector, parent, includeParent) { const $ = this.#jQuery; if ($) { let jNodes = includeParent ? $(parent) : $([]); jNodes = jNodes.add([...parent.querySelectorAll('*')]).filter(selector); if (all) { return $.map(jNodes, el => $(el)); } else { return jNodes.length ? $(jNodes.get(0)) : null; } } else { const checkParent = includeParent && this.#matches.call(parent, selector); if (all) { const result = checkParent ? [parent] : []; result.push(...parent.querySelectorAll(selector)); return result; } else { return checkParent ? parent : parent.querySelector(selector); } } } #getOne(selector, parent, timeout) { return new Promise(resolve => { const node = this.#query(false, selector, parent, false); if (node) return resolve(node); let timer; const filter = el => { const node = this.#query(false, selector, el, true); if (node) { this.#removeFilter(parent, filter); timer && clearTimeout(timer); resolve(node); } }; this.#addFilter(parent, filter); if (timeout > 0) { timer = setTimeout(() => { this.#removeFilter(parent, filter); resolve(null); }, timeout); } }); } #getList(selectorList, parent, timeout) { return Promise.all(selectorList.map(selector => this.#getOne(selector, parent, timeout))); } constructor(jQuery) { this.#jQuery = jQuery && jQuery.fn && jQuery.fn.jquery ? jQuery : null; this.#window = window.unsafeWindow || document.defaultView || window; const elProto = this.#window.Element.prototype; this.#matches = elProto.matches || elProto.matchesSelector || elProto.mozMatchesSelector || elProto.oMatchesSelector || elProto.webkitMatchesSelector; this.#MutationObs = this.#window.MutationObserver || this.#window.WebkitMutationObserver || this.#window.MozMutationObserver; this.#listeners = new WeakMap(); } get(selector, ...args) { const parent = typeof args[0] !== 'number' && args.shift() || this.#window.document; const timeout = args[0] || 0; if (Array.isArray(selector)) { return this.#getList(selector, parent, timeout); } else { return this.#getOne(selector, parent, timeout); } } each(selector, ...args) { const parent = typeof args[0] !== 'function' && args.shift() || this.#window.document; const callback = args[0]; const refs = new WeakSet(); const nodes = this.#query(true, selector, parent, false); for (const node of nodes) { refs.add(this.#jQuery ? node.get(0) : node); if (callback(node, false) === false) return; } const filter = el => { const nodes = this.#query(true, selector, el, true); for (const node of nodes) { const _el = this.#jQuery ? node.get(0) : node; if (!refs.has(_el)) { refs.add(_el); if (callback(node, true) === false) { return this.#removeFilter(parent, filter); } } } }; this.#addFilter(parent, filter); } create(domString, parent) { const template = this.#window.document.createElement('template'); template.innerHTML = domString; const node = template.content.firstElementChild || template.content.firstChild; parent ? parent.appendChild(node) : node.remove(); return node; } }