- // ==UserScript==
- // @name ElementGetter1.2.0
- // @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;
- }
- }