ElementGetter1.2.0

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/450726/1089539/ElementGetter120.js

  1. // ==UserScript==
  2. // @name ElementGetter1.2.0
  3. // @author cxxjackie
  4. // @version 1.2.0
  5. // @supportURL https://bbs.tampermonkey.net.cn/thread-2726-1-1.html
  6. // ==/UserScript==
  7.  
  8. class ElementGetter {
  9. #jQuery;
  10. #window;
  11. #matches;
  12. #MutationObs;
  13. #listeners;
  14. #addObserver(target, callback) {
  15. const observer = new this.#MutationObs(mutations => {
  16. for (const mutation of mutations) {
  17. if (mutation.type === 'attributes') {
  18. callback(mutation.target);
  19. if (observer.canceled) return;
  20. }
  21. for (const node of mutation.addedNodes) {
  22. if (node instanceof Element) callback(node);
  23. if (observer.canceled) return;
  24. }
  25. }
  26. });
  27. observer.canceled = false;
  28. observer.observe(target, {childList: true, subtree: true, attributes: true});
  29. return () => {
  30. observer.canceled = true;
  31. observer.disconnect();
  32. };
  33. }
  34. #addFilter(target, filter) {
  35. let listener = this.#listeners.get(target);
  36. if (!listener) {
  37. listener = {
  38. filters: new Set(),
  39. remove: this.#addObserver(target, el => {
  40. listener.filters.forEach(f => f(el));
  41. })
  42. };
  43. this.#listeners.set(target, listener);
  44. }
  45. listener.filters.add(filter);
  46. }
  47. #removeFilter(target, filter) {
  48. const listener = this.#listeners.get(target);
  49. if (!listener) return;
  50. listener.filters.delete(filter);
  51. if (!listener.filters.size) {
  52. listener.remove();
  53. this.#listeners.delete(target);
  54. }
  55. }
  56. #query(all, selector, parent, includeParent) {
  57. const $ = this.#jQuery;
  58. if ($) {
  59. let jNodes = includeParent ? $(parent) : $([]);
  60. jNodes = jNodes.add([...parent.querySelectorAll('*')]).filter(selector);
  61. if (all) {
  62. return $.map(jNodes, el => $(el));
  63. } else {
  64. return jNodes.length ? $(jNodes.get(0)) : null;
  65. }
  66. } else {
  67. const checkParent = includeParent && this.#matches.call(parent, selector);
  68. if (all) {
  69. const result = checkParent ? [parent] : [];
  70. result.push(...parent.querySelectorAll(selector));
  71. return result;
  72. } else {
  73. return checkParent ? parent : parent.querySelector(selector);
  74. }
  75. }
  76. }
  77. #getOne(selector, parent, timeout) {
  78. return new Promise(resolve => {
  79. const node = this.#query(false, selector, parent, false);
  80. if (node) return resolve(node);
  81. let timer;
  82. const filter = el => {
  83. const node = this.#query(false, selector, el, true);
  84. if (node) {
  85. this.#removeFilter(parent, filter);
  86. timer && clearTimeout(timer);
  87. resolve(node);
  88. }
  89. };
  90. this.#addFilter(parent, filter);
  91. if (timeout > 0) {
  92. timer = setTimeout(() => {
  93. this.#removeFilter(parent, filter);
  94. resolve(null);
  95. }, timeout);
  96. }
  97. });
  98. }
  99. #getList(selectorList, parent, timeout) {
  100. return Promise.all(selectorList.map(selector => this.#getOne(selector, parent, timeout)));
  101. }
  102. constructor(jQuery) {
  103. this.#jQuery = jQuery && jQuery.fn && jQuery.fn.jquery ? jQuery : null;
  104. this.#window = window.unsafeWindow || document.defaultView || window;
  105. const elProto = this.#window.Element.prototype;
  106. this.#matches = elProto.matches
  107. || elProto.matchesSelector
  108. || elProto.mozMatchesSelector
  109. || elProto.oMatchesSelector
  110. || elProto.webkitMatchesSelector;
  111. this.#MutationObs = this.#window.MutationObserver
  112. || this.#window.WebkitMutationObserver
  113. || this.#window.MozMutationObserver;
  114. this.#listeners = new WeakMap();
  115. }
  116. get(selector, ...args) {
  117. const parent = typeof args[0] !== 'number' && args.shift() || this.#window.document;
  118. const timeout = args[0] || 0;
  119. if (Array.isArray(selector)) {
  120. return this.#getList(selector, parent, timeout);
  121. } else {
  122. return this.#getOne(selector, parent, timeout);
  123. }
  124. }
  125. each(selector, ...args) {
  126. const parent = typeof args[0] !== 'function' && args.shift() || this.#window.document;
  127. const callback = args[0];
  128. const refs = new WeakSet();
  129. const nodes = this.#query(true, selector, parent, false);
  130. for (const node of nodes) {
  131. refs.add(this.#jQuery ? node.get(0) : node);
  132. if (callback(node, false) === false) return;
  133. }
  134. const filter = el => {
  135. const nodes = this.#query(true, selector, el, true);
  136. for (const node of nodes) {
  137. const _el = this.#jQuery ? node.get(0) : node;
  138. if (!refs.has(_el)) {
  139. refs.add(_el);
  140. if (callback(node, true) === false) {
  141. return this.#removeFilter(parent, filter);
  142. }
  143. }
  144. }
  145. };
  146. this.#addFilter(parent, filter);
  147. }
  148. create(domString, parent) {
  149. const template = this.#window.document.createElement('template');
  150. template.innerHTML = domString;
  151. const node = template.content.firstElementChild || template.content.firstChild;
  152. parent ? parent.appendChild(node) : node.remove();
  153. return node;
  154. }
  155. }