Greasy Fork is available in English.

Basic Functions (For userscripts)

Useful functions for myself

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greatest.deepsurf.us/scripts/456034/1597683/Basic%20Functions%20%28For%20userscripts%29.js

  1. // ==UserScript==
  2. // @name Basic Functions (For userscripts)
  3. // @name:zh-CN 常用函数(用户脚本)
  4. // @name:en Basic Functions (For userscripts)
  5. // @namespace PY-DNG Userscripts
  6. // @version 1.10.4
  7. // @description Useful functions for myself
  8. // @description:zh-CN 自用函数
  9. // @description:en Useful functions for myself
  10. // @author PY-DNG
  11. // @license GPL-3.0-or-later
  12. // ==/UserScript==
  13.  
  14. /* eslint-disable no-multi-spaces */
  15. /* eslint-disable no-return-assign */
  16.  
  17. // Note: version 0.8.2.1 is modified just the license and it's not uploaded to GF yet 23-11-26 15:03
  18. // Note: version 0.8.3.1 is added just the description of parseArgs and has not uploaded to GF yet 24-02-03 18:55
  19.  
  20. let [
  21. // Console & Debug
  22. LogLevel, DoLog, Err, Assert,
  23.  
  24. // DOM
  25. $, $All, $CrE, $AEL, $$CrE, addStyle, detectDom, destroyEvent,
  26.  
  27. // Data
  28. copyProp, copyProps, parseArgs, escJsStr, replaceText,
  29.  
  30. // Environment & Browser
  31. getUrlArgv, dl_browser, dl_GM,
  32.  
  33. // Logic & Task
  34. AsyncManager, queueTask, FunctionLoader, loadFuncs, require, isLoaded, default_pool
  35. ] = (function() {
  36. const [LogLevel, DoLog] = (function() {
  37. /**
  38. * level defination for DoLog function, bigger ones has higher possibility to be printed in console
  39. * @typedef {Object} LogLevel
  40. * @property {0} None - 0
  41. * @property {1} Error - 1
  42. * @property {2} Success - 2
  43. * @property {3} Warning - 3
  44. * @property {4} Info - 4
  45. */
  46. /** @type {LogLevel} */
  47. const LogLevel = {
  48. None: 0,
  49. Error: 1,
  50. Success: 2,
  51. Warning: 3,
  52. Info: 4,
  53. };
  54.  
  55. return [LogLevel, DoLog];
  56.  
  57. /**
  58. * @overload
  59. * @param {String} content - log content
  60. */
  61. /**
  62. * @overload
  63. * @param {Number} level - level specified in LogLevel object
  64. * @param {String} content - log content
  65. */
  66. /**
  67. * Logger with level and logger function specification
  68. * @overload
  69. * @param {Number} level - level specified in LogLevel object
  70. * @param {String} content - log content
  71. * @param {String} logger - which log function to use (in window.console[logger])
  72. */
  73. function DoLog() {
  74. // Get window
  75. const win = (typeof(unsafeWindow) === 'object' && unsafeWindow !== null) ? unsafeWindow : window;
  76.  
  77. const LogLevelMap = {};
  78. LogLevelMap[LogLevel.None] = {
  79. prefix: '',
  80. color: 'color:#ffffff'
  81. }
  82. LogLevelMap[LogLevel.Error] = {
  83. prefix: '[Error]',
  84. color: 'color:#ff0000'
  85. }
  86. LogLevelMap[LogLevel.Success] = {
  87. prefix: '[Success]',
  88. color: 'color:#00aa00'
  89. }
  90. LogLevelMap[LogLevel.Warning] = {
  91. prefix: '[Warning]',
  92. color: 'color:#ffa500'
  93. }
  94. LogLevelMap[LogLevel.Info] = {
  95. prefix: '[Info]',
  96. color: 'color:#888888'
  97. }
  98. LogLevelMap[LogLevel.Elements] = {
  99. prefix: '[Elements]',
  100. color: 'color:#000000'
  101. }
  102.  
  103. // Current log level
  104. DoLog.logLevel = (win.isPY_DNG && win.userscriptDebugging) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
  105.  
  106. // Log counter
  107. DoLog.logCount === undefined && (DoLog.logCount = 0);
  108.  
  109. // Get args
  110. let [level, logContent, logger] = parseArgs([...arguments], [
  111. [2],
  112. [1,2],
  113. [1,2,3]
  114. ], [LogLevel.Info, 'DoLog initialized.', 'log']);
  115.  
  116. let msg = '%c' + LogLevelMap[level].prefix + (typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + (LogLevelMap[level].prefix ? ' ' : '');
  117. let subst = LogLevelMap[level].color;
  118.  
  119. switch (typeof(logContent)) {
  120. case 'string':
  121. msg += '%s';
  122. break;
  123. case 'number':
  124. msg += '%d';
  125. break;
  126. default:
  127. msg += '%o';
  128. break;
  129. }
  130.  
  131. // Log when log level permits
  132. if (level <= DoLog.logLevel) {
  133. // Log to console when log level permits
  134. if (level <= DoLog.logLevel) {
  135. if (++DoLog.logCount > 512) {
  136. console.clear();
  137. DoLog.logCount = 0;
  138. }
  139. console[logger](msg, subst, logContent);
  140. }
  141. }
  142. }
  143. }) ();
  144.  
  145. /**
  146. * Throw an error
  147. * @param {String} msg - the error message
  148. * @param {typeof Error} [ErrorConstructor=Error] - which error constructor to use, defaulting to Error()
  149. */
  150. function Err(msg, ErrorConstructor=Error) {
  151. throw new ErrorConstructor((typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + msg);
  152. }
  153.  
  154. /**
  155. * Assert given condition is true-like, otherwise throws given error
  156. * @param {*} condition
  157. * @param {string} errmsg
  158. * @param {typeof Error} [ErrorConstructor=Error]
  159. */
  160. function Assert(condition, errmsg, ErrorConstructor=Error) {
  161. condition || Err(errmsg, ErrorConstructor);
  162. }
  163.  
  164. /**
  165. * Convenient function to querySelector
  166. * @overload
  167. * @param {Element|Document|DocumentFragment} [root] - which target to call querySelector on
  168. * @param {string} selector - querySelector selector
  169. * @returns {Element|null}
  170. */
  171. function $() {
  172. switch(arguments.length) {
  173. case 2:
  174. return arguments[0].querySelector(arguments[1]);
  175. default:
  176. return document.querySelector(arguments[0]);
  177. }
  178. }
  179. /**
  180. * Convenient function to querySelectorAll
  181. * @overload
  182. * @param {Element|Document|DocumentFragment} [root] - which target to call querySelectorAll on
  183. * @param {string} selector - querySelectorAll selector
  184. * @returns {NodeList}
  185. */
  186. function $All() {
  187. switch(arguments.length) {
  188. case 2:
  189. return arguments[0].querySelectorAll(arguments[1]);
  190. break;
  191. default:
  192. return document.querySelectorAll(arguments[0]);
  193. }
  194. }
  195. /**
  196. * Convenient function to querySelectorAll
  197. * @overload
  198. * @param {Document} [root] - which document to call createElement on
  199. * @param {string} tagName
  200. * @returns {HTMLElement}
  201. */
  202. function $CrE() {
  203. switch(arguments.length) {
  204. case 2:
  205. return arguments[0].createElement(arguments[1]);
  206. break;
  207. default:
  208. return document.createElement(arguments[0]);
  209. }
  210. }
  211. /**
  212. * Convenient function to addEventListener
  213. * @overload
  214. * @param {EventTarget} target - which target to call addEventListener on
  215. * @param {string} type
  216. * @param {EventListenerOrEventListenerObject | null} callback
  217. * @param {AddEventListenerOptions | boolean} [options]
  218. */
  219. function $AEL(...args) {
  220. /** @type {EventTarget} */
  221. const target = args.shift();
  222. return target.addEventListener.apply(target, args);
  223. }
  224. /**
  225. * @typedef {[type: string, callback: EventListenerOrEventListenerObject | null, options: AddEventListenerOptions | boolean]} $AEL_Arguments
  226. */
  227. /**
  228. * @typedef {Object} $$CrE_Options
  229. * @property {string} tagName
  230. * @property {object} [props] - properties set by `element[prop] = value;`
  231. * @property {object} [attrs] - attributes set by `element.setAttribute(attr, value);`
  232. * @property {string | string[]} [classes] - class names to be set
  233. * @property {object} [styles] - styles set by `element[style_name] = style_value;`
  234. * @property {$AEL_Arguments[]} [listeners] - event listeners added by `$AEL(element, ...listener);`
  235. */
  236. /**
  237. * @overload
  238. * @param {$$CrE_Options} options
  239. * @returns {HTMLElement}
  240. */
  241. /**
  242. * Create configorated element
  243. * @overload
  244. * @param {string} tagName
  245. * @param {object} [props] - properties set by `element[prop] = value;`
  246. * @param {object} [attrs] - attributes set by `element.setAttribute(attr, value);`
  247. * @param {string | string[]} [classes] - class names to be set
  248. * @param {object} [styles] - styles set by `element[style_name] = style_value;`
  249. * @param {$AEL_Arguments[]} [listeners] - event listeners added by `$AEL(element, ...listener);`
  250. * @returns {HTMLElement}
  251. */
  252. function $$CrE() {
  253. const [tagName, props, attrs, classes, styles, listeners] = parseArgs([...arguments], [
  254. function(args, defaultValues) {
  255. const arg = args[0];
  256. return {
  257. 'string': () => [arg, ...defaultValues.filter((arg, i) => i > 0)],
  258. 'object': () => ['tagName', 'props', 'attrs', 'classes', 'styles', 'listeners'].map((prop, i) => arg.hasOwnProperty(prop) ? arg[prop] : defaultValues[i])
  259. }[typeof arg]();
  260. },
  261. [1,2],
  262. [1,2,3],
  263. [1,2,3,4],
  264. [1,2,3,4,5]
  265. ], ['div', {}, {}, [], {}, []]);
  266. const elm = $CrE(tagName);
  267. for (const [name, val] of Object.entries(props)) {
  268. elm[name] = val;
  269. }
  270. for (const [name, val] of Object.entries(attrs)) {
  271. elm.setAttribute(name, val);
  272. }
  273. for (const cls of Array.isArray(classes) ? classes : [classes]) {
  274. elm.classList.add(cls);
  275. }
  276. for (const [name, val] of Object.entries(styles)) {
  277. elm.style[name] = val;
  278. }
  279. for (const listener of listeners) {
  280. $AEL(elm, ...listener);
  281. }
  282. return elm;
  283. }
  284.  
  285. /**
  286. * @overload
  287. * @param {string} css - css content
  288. * @returns {HTMLStyleElement}
  289. */
  290. /**
  291. * @overload
  292. * @param {string} css - css content
  293. * @param {string} id - `id` attribute for <style> element
  294. * @returns {HTMLStyleElement}
  295. */
  296. /**
  297. * Append a style text to document(<head>) with a <style> element \
  298. * removes existing <style> elements with same id if id provided, so style updates can be done by using one same id
  299. *
  300. * Uses `GM_addElement` if `GM_addElement` exists and param `id` not specified. (`GM_addElement` uses id attribute, so specifing id manually when using `GM_addElement` takes no effect) \
  301. * In another case `GM_addStyle` instead of `GM_addElement` exists, and both `id` and `parentElement` not specified, `GM_addStyle` will be used. \
  302. * `document.createElement('style')` will be used otherwise.
  303. * @overload
  304. * @param {HTMLElement} parentElement - parent element to place <style> element
  305. * @param {string} css - css content
  306. * @param {string} id - `id` attribute for <style> element
  307. * @returns {HTMLStyleElement}
  308. */
  309. function addStyle() {
  310. // Get arguments
  311. const [parentElement, css, id] = parseArgs([...arguments], [
  312. [2],
  313. [2,3],
  314. [1,2,3]
  315. ], [null, '', null]);
  316.  
  317. if (typeof GM_addElement === 'function' && id === null) {
  318. return GM_addElement(parentElement, 'style', { textContent: css });
  319. } else if (typeof GM_addStyle === 'function' && parentElement === null && id === null) {
  320. return GM_addStyle(css);
  321. } else {
  322. // Make <style>
  323. const style = $CrE('style');
  324. style.innerHTML = css;
  325. id !== null && (style.id = id);
  326. id !== null && Array.from($All(`style#${id}`)).forEach(elm => elm.remove());
  327.  
  328. // Append to parentElement
  329. (parentElement ?? document.head).appendChild(style);
  330. return style;
  331. }
  332. }
  333.  
  334. /**
  335. * @typedef {Object} detectDom_options
  336. * @property {Node} root - root target to observe on
  337. * @property {string | string[]} [selector] - selector(s) to observe for, be aware that in options object it is named selector, but is named selectors in param
  338. * @property {boolean} [attributes] - whether to observe existing elements' attribute changes
  339. * @property {function} [callback] - if provided, use callback instead of Promise when selector element found
  340. */
  341. /**
  342. * @overload
  343. * @param {string | string[]} selector - selector(s) to observe for, be aware that in options object it is named selector, but is named selectors in param
  344. * @returns {Promise<HTMLElement>}
  345. */
  346. /**
  347. * @overload
  348. * @param {detectDom_options} options
  349. * @returns {MutationObserver}
  350. */
  351. /**
  352. * Get callback / resolve promise when specific dom/element appearce in document \
  353. * uses MutationObserver for implementation \
  354. * This behavior is different from versions that equals to or older than 0.8.4.2, so be careful when using it.
  355. * @overload
  356. * @param {Node} root - root target to observe on
  357. * @param {string | string[]} [selectors] - selector(s) to observe for
  358. * @param {boolean} [attributes] - whether to observe existing elements' attribute changes
  359. * @param {function} [callback] - if provided, use callback instead of Promise when selector element found
  360. * @returns {MutationObserver}
  361. */
  362. function detectDom() {
  363. let [selectors, root, attributes, callback] = parseArgs([...arguments], [
  364. function(args, defaultValues) {
  365. const arg = args[0];
  366. return {
  367. 'string': () => [arg, ...defaultValues.filter((arg, i) => i > 0)],
  368. 'object': () => ['selector', 'root', 'attributes', 'callback'].map((prop, i) => arg.hasOwnProperty(prop) ? arg[prop] : defaultValues[i])
  369. }[typeof arg]();
  370. },
  371. [2,1],
  372. [2,1,3],
  373. [2,1,3,4],
  374. ], [[''], document, false, null]);
  375. !Array.isArray(selectors) && (selectors = [selectors]);
  376.  
  377. if (select(root, selectors)) {
  378. for (const elm of selectAll(root, selectors)) {
  379. if (callback) {
  380. setTimeout(callback.bind(null, elm));
  381. } else {
  382. return Promise.resolve(elm);
  383. }
  384. }
  385. }
  386.  
  387. const observer = new MutationObserver(mCallback);
  388. observer.observe(root, {
  389. childList: true,
  390. subtree: true,
  391. attributes,
  392. });
  393.  
  394. let isPromise = !callback;
  395. return callback ? observer : new Promise((resolve, reject) => callback = resolve);
  396.  
  397. function mCallback(mutationList, observer) {
  398. const addedNodes = mutationList.reduce((an, mutation) => {
  399. switch (mutation.type) {
  400. case 'childList':
  401. an.push(...mutation.addedNodes);
  402. break;
  403. case 'attributes':
  404. an.push(mutation.target);
  405. break;
  406. }
  407. return an;
  408. }, []);
  409. const addedSelectorNodes = addedNodes.reduce((nodes, anode) => {
  410. if (anode.matches && match(anode, selectors)) {
  411. nodes.add(anode);
  412. }
  413. const childMatches = anode.querySelectorAll ? selectAll(anode, selectors) : [];
  414. for (const cm of childMatches) {
  415. nodes.add(cm);
  416. }
  417. return nodes;
  418. }, new Set());
  419. for (const node of addedSelectorNodes) {
  420. callback(node);
  421. isPromise && observer.disconnect();
  422. }
  423. }
  424.  
  425. function selectAll(elm, selectors) {
  426. !Array.isArray(selectors) && (selectors = [selectors]);
  427. return selectors.map(selector => [...$All(elm, selector)]).reduce((all, arr) => {
  428. all.push(...arr);
  429. return all;
  430. }, []);
  431. }
  432.  
  433. function select(elm, selectors) {
  434. const all = selectAll(elm, selectors);
  435. return all.length ? all[0] : null;
  436. }
  437.  
  438. function match(elm, selectors) {
  439. return !!elm.matches && selectors.some(selector => elm.matches(selector));
  440. }
  441. }
  442.  
  443. /**
  444. * Just stopPropagation and preventDefault
  445. * @param {Event} e
  446. */
  447. function destroyEvent(e) {
  448. if (!e) {return false;};
  449. if (!e instanceof Event) {return false;};
  450. e.stopPropagation();
  451. e.preventDefault();
  452. }
  453.  
  454. /**
  455. * copy property value from obj1 to obj2 if exists
  456. * @param {object} obj1
  457. * @param {object} obj2
  458. * @param {string|Symbol} prop
  459. */
  460. function copyProp(obj1, obj2, prop) {obj1.hasOwnProperty(prop) && (obj2[prop] = obj1[prop]);}
  461. /**
  462. * copy property values from obj1 to obj2 if exists
  463. * @param {object} obj1
  464. * @param {object} obj2
  465. * @param {string|Symbol} [props] - properties to copy, copy all enumerable properties if not specified
  466. */
  467. function copyProps(obj1, obj2, props) {(props ?? Object.keys(obj1)).forEach((prop) => (copyProp(obj1, obj2, prop)));}
  468.  
  469. /**
  470. * Argument parser with sorting and defaultValue support \
  471. * See use cases in other functions
  472. * @param {Array} args - original arguments' value to be parsed
  473. * @param {(number[]|function)[]} rules - rules to sort arguments or custom function to parse arguments
  474. * @param {Array} defaultValues - default values for arguments not provided a value
  475. * @returns {Array}
  476. */
  477. function parseArgs(args, rules, defaultValues=[]) {
  478. // args and rules should be array, but not just iterable (string is also iterable)
  479. if (!Array.isArray(args) || !Array.isArray(rules)) {
  480. throw new TypeError('parseArgs: args and rules should be array')
  481. }
  482.  
  483. // fill rules[0]
  484. (!Array.isArray(rules[0]) || rules[0].length === 1) && rules.splice(0, 0, []);
  485.  
  486. // max arguments length
  487. const count = rules.length - 1;
  488.  
  489. // args.length must <= count
  490. if (args.length > count) {
  491. throw new TypeError(`parseArgs: args has more elements(${args.length}) longer than ruless'(${count})`);
  492. }
  493.  
  494. // rules[i].length should be === i if rules[i] is an array, otherwise it should be a function
  495. for (let i = 1; i <= count; i++) {
  496. const rule = rules[i];
  497. if (Array.isArray(rule)) {
  498. if (rule.length !== i) {
  499. throw new TypeError(`parseArgs: rules[${i}](${rule}) should have ${i} numbers, but given ${rules[i].length}`);
  500. }
  501. if (!rule.every((num) => (typeof num === 'number' && num <= count))) {
  502. throw new TypeError(`parseArgs: rules[${i}](${rule}) should contain numbers smaller than count(${count}) only`);
  503. }
  504. } else if (typeof rule !== 'function') {
  505. throw new TypeError(`parseArgs: rules[${i}](${rule}) should be an array or a function.`)
  506. }
  507. }
  508.  
  509. // Parse
  510. const rule = rules[args.length];
  511. let parsed;
  512. if (Array.isArray(rule)) {
  513. parsed = [...defaultValues];
  514. for (let i = 0; i < rule.length; i++) {
  515. parsed[rule[i]-1] = args[i];
  516. }
  517. } else {
  518. parsed = rule(args, defaultValues);
  519. }
  520. return parsed;
  521. }
  522.  
  523. /**
  524. * escape str into javascript written format
  525. * @param {string} str
  526. * @param {string} [quote]
  527. * @returns
  528. */
  529. function escJsStr(str, quote='"') {
  530. str = str.replaceAll('\\', '\\\\').replaceAll(quote, '\\' + quote).replaceAll('\t', '\\t');
  531. str = quote === '`' ? str.replaceAll(/(\$\{[^\}]*\})/g, '\\$1') : str.replaceAll('\r', '\\r').replaceAll('\n', '\\n');
  532. return quote + str + quote;
  533. }
  534. /**
  535. * Replace given text with no mismatching of replacing replaced text
  536. *
  537. * e.g. replaceText('aaaabbbbccccdddd', {'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e'}) === 'bbbbccccddddeeee' \
  538. * replaceText('abcdAABBAA', {'BB': 'AA', 'AAAAAA': 'This is a trap!'}) === 'abcdAAAAAA' \
  539. * replaceText('abcd{AAAA}BB}', {'{AAAA}': '{BB', '{BBBB}': 'This is a trap!'}) === 'abcd{BBBB}' \
  540. * replaceText('abcd', {}) === 'abcd'
  541. *
  542. * **Note**: \
  543. * replaceText will replace in sort of replacer's iterating sort \
  544. * e.g. currently replaceText('abcdAABBAA', {'BBAA': 'TEXT', 'AABB': 'TEXT'}) === 'abcdAATEXT' \
  545. * but remember: (As MDN Web Doc said,) Although the keys of an ordinary Object are ordered now, this was \
  546. * not always the case, and the order is complex. As a result, it's best not to rely on property order. \
  547. * So, don't expect replaceText will treat replacer key-values in any specific sort. Use replaceText to \
  548. * replace irrelevance replacer keys only.
  549. * @param {string} text
  550. * @param {object} replacer
  551. * @returns {string}
  552. */
  553. function replaceText(text, replacer) {
  554. if (Object.entries(replacer).length === 0) {return text;}
  555. const [models, targets] = Object.entries(replacer);
  556. const len = models.length;
  557. let text_arr = [{text: text, replacable: true}];
  558. for (const [model, target] of Object.entries(replacer)) {
  559. text_arr = replace(text_arr, model, target);
  560. }
  561. return text_arr.map((text_obj) => (text_obj.text)).join('');
  562.  
  563. function replace(text_arr, model, target) {
  564. const result_arr = [];
  565. for (const text_obj of text_arr) {
  566. if (text_obj.replacable) {
  567. const splited = text_obj.text.split(model);
  568. for (const part of splited) {
  569. result_arr.push({text: part, replacable: true});
  570. result_arr.push({text: target, replacable: false});
  571. }
  572. result_arr.pop();
  573. } else {
  574. result_arr.push(text_obj);
  575. }
  576. }
  577. return result_arr;
  578. }
  579. }
  580.  
  581. /**
  582. * @typedef {Object} getUrlArgv_options
  583. * @property {string} name
  584. * @property {string} [url]
  585. * @property {string} [defaultValue]
  586. * @property {function} [dealFunc] - function that inputs original getUrlArgv result and outputs final return value
  587. */
  588. /**
  589. * @overload
  590. * @param {Object} getUrlArgv_options
  591. * @returns
  592. */
  593. /**
  594. * Get a url argument from location.href
  595. * @param {string} name
  596. * @param {string} [url]
  597. * @param {string} [defaultValue]
  598. * @param {function} [dealFunc] - function that inputs original getUrlArgv result and outputs final return value
  599. */
  600. function getUrlArgv() {
  601. const [name, url, defaultValue, dealFunc] = parseArgs([...arguments], [
  602. function(args, defaultValues) {
  603. const arg = args[0];
  604. return {
  605. 'string': () => [arg, ...defaultValues.filter((arg, i) => i > 0)],
  606. 'object': () => ['name', 'url', 'defaultValue', 'dealFunc'].map((prop, i) => arg.hasOwnProperty(prop) ? arg[prop] : defaultValues[i])
  607. }[typeof arg]();
  608. },
  609. [2,1],
  610. [2,1,3],
  611. [2,1,3,4]
  612. ], [null, location.href, null, a => a]);
  613.  
  614. if (name === null) { return null; }
  615.  
  616. const search = new URL(url).search;
  617. const objSearch = new URLSearchParams(search);
  618. const raw = objSearch.has(name) ? objSearch.get(name) : defaultValue;
  619. const argv = dealFunc(raw);
  620.  
  621. return argv;
  622. }
  623.  
  624. /**
  625. * download file from given url by simulating <a download="..." href=""></a> clicks \
  626. * a common use case is to download Blob objects as file from `URL.createObjectURL`
  627. * @param {string} url
  628. * @param {string} filename
  629. */
  630. function dl_browser(url, filename) {
  631. const a = document.createElement('a');
  632. a.href = url;
  633. a.download = filename;
  634. a.click();
  635. }
  636.  
  637. /**
  638. * File download function \
  639. * details looks like the detail of GM_xmlhttpRequest \
  640. * onload function will be called after file saved to disk
  641. * @param {object} details
  642. */
  643. function dl_GM(details) {
  644. if (!details.url || !details.name) {return false;};
  645.  
  646. // Configure request object
  647. const requestObj = {
  648. url: details.url,
  649. responseType: 'blob',
  650. onload: function(e) {
  651. // Save file
  652. dl_browser(URL.createObjectURL(e.response), details.name);
  653.  
  654. // onload callback
  655. details.onload ? details.onload(e) : function() {};
  656. }
  657. }
  658. if (details.onloadstart ) {requestObj.onloadstart = details.onloadstart;};
  659. if (details.onprogress ) {requestObj.onprogress = details.onprogress;};
  660. if (details.onerror ) {requestObj.onerror = details.onerror;};
  661. if (details.onabort ) {requestObj.onabort = details.onabort;};
  662. if (details.onreadystatechange) {requestObj.onreadystatechange = details.onreadystatechange;};
  663. if (details.ontimeout ) {requestObj.ontimeout = details.ontimeout;};
  664.  
  665. // Send request
  666. Assert(typeof GM_xmlhttpRequest === 'function', 'GM_xmlhttpRequest should be provided in order to use dl_GM', TypeError);
  667. GM_xmlhttpRequest(requestObj);
  668. }
  669.  
  670. /**
  671. * Manager to manager async tasks \
  672. * This was written when I haven't learnt Promise, so for fluent promise users, just ignore it:)
  673. *
  674. * # Usage
  675. * ```javascript
  676. * // This simulates a async task, it can be a XMLHttpRequest, some file reading, or so on...
  677. * function someAsyncTask(callback, duration) {
  678. * const result = Math.random();
  679. * setTimeout(() => callback(result), duration);
  680. * }
  681. *
  682. * // Do 10 async tasks, and log all results when all async tasks finished
  683. * const AM = new AsyncManager();
  684. * const results = [];
  685. * AM.onfinish = function() {
  686. * console.log('All tasks finished!');
  687. * console.log(results);
  688. * }
  689. *
  690. * for (let i = 0; i < 10; i++) {
  691. * AM.add();
  692. * const duration = (Math.random() * 5 + 5) * 1000;
  693. * const index = i;
  694. * someAsyncTask(result => {
  695. * console.log(`Task ${index} finished after ${duration}ms!`);
  696. * results[index] = result;
  697. * }, duration);
  698. * console.log(`Task ${index} started!`);
  699. * }
  700. *
  701. * // Set AM.finishEvent to true after all tasks added, allowing AsyncManager to call onfinish callback
  702. * ```
  703. * @constructor
  704. */
  705. function AsyncManager() {
  706. const AM = this;
  707.  
  708. // Ongoing tasks count
  709. this.taskCount = 0;
  710.  
  711. // Whether generate finish events
  712. let finishEvent = false;
  713. Object.defineProperty(this, 'finishEvent', {
  714. configurable: true,
  715. enumerable: true,
  716. get: () => (finishEvent),
  717. set: (b) => {
  718. finishEvent = b;
  719. b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
  720. }
  721. });
  722.  
  723. // Add one task
  724. this.add = () => (++AM.taskCount);
  725.  
  726. // Finish one task
  727. this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
  728. }
  729.  
  730. /**
  731. * Put tasks in specific queue and order their execution \
  732. * Set `queueTask[queueId].max`, `queueTask[queueId].sleep` to custom queue's max ongoing tasks and sleep time between tasks
  733. * @param {function} task - task function to run
  734. * @param {string | Symbol} queueId - identifier to specify a target queue. if provided, given task will be added into specified queue.
  735. * @returns
  736. */
  737. function queueTask(task, queueId='default') {
  738. init();
  739.  
  740. return new Promise((resolve, reject) => {
  741. queueTask.hasOwnProperty(queueId) || (queueTask[queueId] = { tasks: [], ongoing: 0 });
  742. queueTask[queueId].tasks.push({task, resolve, reject});
  743. checkTask(queueId);
  744. });
  745.  
  746. function init() {
  747. if (!queueTask[queueId]?.initialized) {
  748. queueTask[queueId] = {
  749. // defaults
  750. tasks: [],
  751. ongoing: 0,
  752. max: 3,
  753. sleep: 500,
  754.  
  755. // user's pre-sets
  756. ...(queueTask[queueId] || {}),
  757.  
  758. // initialized flag
  759. initialized: true
  760. }
  761. };
  762. }
  763.  
  764. function checkTask() {
  765. const queue = queueTask[queueId];
  766. setTimeout(() => {
  767. if (queue.ongoing < queue.max && queue.tasks.length) {
  768. const task = queue.tasks.shift();
  769. queue.ongoing++;
  770. setTimeout(
  771. () => task.task().then(v => {
  772. queue.ongoing--;
  773. task.resolve(v);
  774. checkTask(queueId);
  775. }).catch(e => {
  776. queue.ongoing--;
  777. task.reject(e);
  778. checkTask(queueId);
  779. }),
  780. queue.sleep
  781. );
  782. }
  783. });
  784. }
  785. }
  786.  
  787. const [FunctionLoader, loadFuncs, require, isLoaded, default_pool] = (function() {
  788. /**
  789. * 一般用作函数对象oFunc的加载条件,检测当前环境是否适合/需要该oFunc加载
  790. * @typedef {Object} checker_func
  791. * @property {string} type - checker's identifier
  792. * @property {function} func - actual internal judgement implementation
  793. */
  794. /**
  795. * 一般用作函数对象oFunc的加载条件,检测当前环境是否适合/需要该oFunc加载
  796. * @typedef {Object} checker
  797. * @property {string} type - checker's identifier
  798. * @property {*} value - param that goes into checker function
  799. */
  800. /**
  801. * 需要使用的substorage名称
  802. * @typedef {"GM_setValue" | "GM_getValue" | "GM_listValues" | "GM_deleteValue"} substorage_value
  803. */
  804. /**
  805. * 可以传入params的字符串名称
  806. * @typedef {'oFunc' | substorage_value} param
  807. */
  808. /**
  809. * 被加载函数对象的func函数
  810. * @callback oFuncBody
  811. * @param {oFunc} oFunc
  812. * @returns {*|Promise<*>}
  813. */
  814. /**
  815. * 被加载执行的函数对象
  816. * @typedef {Object} oFunc
  817. * @property {string} id - 每次load(每个FuncPool实例)内唯一的标识符
  818. * @property {boolean} [disabled] - 为真值时,无论checkers还是detectDom等任何其他条件通过或未通过,均不执行此函数对象;默认为false
  819. * @property {checker[]|checker} [checkers] - oFunc执行的条件
  820. * @property {string[]|string} [detectDom] - 如果提供,开始checker检查前会首先等待其中所有css选择器对应的元素在document中出现
  821. * @property {string[]|string} [dependencies] - 如果提供,应为其他函数对象的id或者id列表;开始checker检查前会首先等待其中所有指定的函数对象加载完毕
  822. * @property {boolean} [readonly] - 指定该函数的返回值是否应该被Proxy保护为不可修改对象
  823. * @property {param[]|param} params - 可选,指定传入oFunc.func的参数列表;可以为参数本身或其组成的数组
  824. * 参数可以为 字符串 或是 其他类型,如果是字符串就传入对应的FunctionLoader提供的内置值(见下),如果是其他类型则按照原样传入
  825. * - "oFunc":
  826. * 函数对象本身
  827. * - "GM_setValue", "GM_getValue", "GM_listValues", "GM_deleteValue":
  828. * 和脚本管理器提供的函数一致,但是读取和写入的对象是以oFunc.id为键的子空间
  829. * 比如,GM_getValue("prop") 就相当于调用脚本管理器提供的的 GM_getValue(oFunc.id)["prop"]
  830. * @property {oFuncBody} func - 实际实现了功能的函数
  831. * @property {boolean} [STOP] - [调试用] 指定不执行此函数对象
  832. */
  833.  
  834. const registered_checkers = {
  835. switch: value => value,
  836. url: value => location.href === value,
  837. path: value => location.pathname === value,
  838. regurl: value => !!location.href.match(value),
  839. regpath: value => !!location.pathname.match(value),
  840. starturl: value => location.href.startsWith(value),
  841. startpath: value => location.pathname.startsWith(value),
  842. func: value => value()
  843. };
  844.  
  845. class FuncPool extends EventTarget {
  846. static #STILL_LOADING = Symbol('oFunc still loading');
  847. static FunctionNotFound = Symbol('Function not found');
  848. static FunctionNotLoaded = Symbol('Function not loaded');
  849. static CheckerNotPass = Symbol('Function checker does not pass');
  850. static ErrorWhileLoad = Symbol('Error caught when function loading');
  851.  
  852. /** @typedef {symbol|*} return_value */
  853. /** @type {Map<oFunc, return_value>} */
  854. #oFuncs = new Map();
  855.  
  856. #GM_funcs;
  857.  
  858. /** @typedef {{error: Error, oFunc: oFunc}} load_error */
  859. /** @type {load_error[]} */
  860. errors;
  861.  
  862. /** @type {boolean} */
  863. catch_errors;
  864.  
  865. /**
  866. * 创建新函数池
  867. * @param {Object} [details={}] - 可选,默认为{}空对象
  868. * @param {function} [details.GM_getValue] - 可选,读取脚本存储的函数;如果提供,使用提供的值,否则使用上下文中的值
  869. * @param {function} [details.GM_setValue] - 可选,写入脚本存储的函数;如果提供,使用提供的值,否则使用上下文中的值
  870. * @param {function} [details.GM_deleteValue] - 可选,删除脚本存储的函数;如果提供,使用提供的值,否则使用上下文中的值
  871. * @param {function} [details.GM_listValues] - 可选,列出脚本存储的函数;如果提供,使用提供的值,否则使用上下文中的值
  872. * @param {oFunc | oFunc[]} [details.oFuncs] - 可选,需要立即加载的函数对象
  873. * @param {boolean} [details.catch_errors=false] - 可选,是否自动捕获错误,默认为false
  874. * @returns {FuncPool}
  875. */
  876. constructor({
  877. GM_getValue: _GM_getValue = typeof GM_getValue === 'function' ? GM_getValue : null,
  878. GM_setValue: _GM_setValue = typeof GM_setValue === 'function' ? GM_setValue : null,
  879. GM_deleteValue: _GM_deleteValue = typeof GM_deleteValue === 'function' ? GM_deleteValue : null,
  880. GM_listValues: _GM_listValues = typeof GM_listValues === 'function' ? GM_listValues : null,
  881. oFuncs = [],
  882. catch_errors = false,
  883. } = {}) {
  884. super();
  885. this.#GM_funcs = {
  886. GM_getValue: _GM_getValue,
  887. GM_setValue: _GM_setValue,
  888. GM_deleteValue: _GM_deleteValue,
  889. GM_listValues: _GM_listValues
  890. };
  891. this.errors = [];
  892. this.catch_errors = catch_errors;
  893. this.load(oFuncs);
  894. }
  895.  
  896. /**
  897. * 加载提供的一个或多个函数对象,并将其加入到函数池中 \
  898. * 异步函数,当所有传入的函数对象都彻底load完毕/checkers确定不加载时resolve
  899. * @param {oFunc[]|oFunc} [oFuncs] - 可选,需要加载的函数对象或其数组,不提供时默认为空数组
  900. */
  901. async load(oFuncs=[]) {
  902. oFuncs = Array.isArray(oFuncs) ? oFuncs : [oFuncs];
  903. await Promise.all(oFuncs.map(oFunc => this.#load(oFunc)));
  904. }
  905.  
  906. /**
  907. * 加载一个函数对象,并将其加入到函数池中 \
  908. * 当id重复时,直接报错RedeclarationError \
  909. * 异步函数,当彻底load完毕/checkers确定不加载时resolve \
  910. * 当加载完毕时,广播load事件;如果全部加载完毕,还广播all_load事件
  911. * @todo 当checker确定不加载时,广播什么事件?后续all_load是否仍然触发?
  912. * @param {oFunc} oFunc
  913. * @returns {Promise<boolean>} 本次调用是否成功执行了加载并顺利加载完毕
  914. */
  915. async #load(oFunc) {
  916. const that = this;
  917.  
  918. // disabled的函数对象,不执行
  919. if (oFunc.disabled) {
  920. return false;
  921. }
  922.  
  923. // 已经在函数池中的函数对象,不重复load
  924. if (this.#oFuncs.has(oFunc)) {
  925. return false;
  926. }
  927.  
  928. // 检查有无重复id
  929. for (const o of this.#oFuncs.keys()) {
  930. if (o.id === oFunc.id) {
  931. throw new RedeclarationError(`Attempts to load oFunc with id already in use: ${oFunc.id}`);
  932. }
  933. }
  934.  
  935. // 设置当前返回值为STILL_LOADING
  936. this.#oFuncs.set(oFunc, FuncPool.#STILL_LOADING);
  937.  
  938. // 加载依赖
  939. const dependencies = Array.isArray(oFunc.dependencies) ? oFunc.dependencies : ( oFunc.dependencies ? [oFunc.dependencies] : [] );
  940. await Promise.all(dependencies.map(id => this.require(id, true)));
  941.  
  942. // 检测checkers加载条件
  943. const checkers = Array.isArray(oFunc.checkers) ? oFunc.checkers : ( oFunc.checkers ? [oFunc.checkers] : [] );
  944. if (!testCheckers(checkers, oFunc)) {
  945. this.#oFuncs.set(oFunc, FuncPool.CheckerNotPass);
  946. return false;
  947. }
  948.  
  949. // 检测detectDOM中css选择器指定的元素出现
  950. const selectors = Array.isArray(oFunc.detectDom) ? oFunc.detectDom : ( oFunc.detectDom ? [oFunc.detectDom] : [] );
  951. await Promise.all(selectors.map(selector => detectDom(selector)));
  952.  
  953. // 处理substorage
  954. const substorage = this.#MakeSubStorage(oFunc.id);
  955.  
  956. // 处理函数参数
  957. const builtins = {
  958. oFunc,
  959. ...substorage
  960. };
  961. const params = oFunc.params ? (Array.isArray(oFunc.params) ? oFunc.params : [oFunc.params]) : [];
  962. const args = params.map(param => typeof param === 'string' ? builtins[param] : param);
  963.  
  964. // 执行函数对象
  965. let raw_return_value, return_value;
  966. try {
  967. raw_return_value = oFunc.func(...args);
  968. return_value = await Promise.resolve(raw_return_value);
  969. } catch (error) {
  970. // 当出现错误时
  971. if (this.catch_errors) {
  972. // 错误捕获已开启:广播错误事件,储存错误信息,设置错误状态,返回false
  973. const load_error = { error, oFunc };
  974. this.#broadcast('error', load_error);
  975. this.errors.push(load_error);
  976. this.#oFuncs.set(oFunc, FuncPool.ErrorWhileLoad);
  977. return false;
  978. } else {
  979. // 错误捕获未开启:直接抛出错误
  980. throw error;
  981. }
  982. }
  983.  
  984. // 设置返回值
  985. this.#oFuncs.set(oFunc, return_value);
  986.  
  987. // 广播事件
  988. this.#broadcast('load', { oFunc, id: oFunc.id, return_value });
  989. Array.from(this.#oFuncs.values()).every(v => v !== FuncPool.#STILL_LOADING) &&
  990. this.#broadcast('all_load');
  991. return true;
  992. }
  993.  
  994. /**
  995. * 获取指定函数对象的返回值
  996. * 如果wait=false:
  997. * - 如果指定的函数对象不存在,返回FuncPool.FunctionNotFound
  998. * - 如果指定的函数对象存在但尚未加载,返回FuncPool.FunctionNotLoaded
  999. *
  1000. * 如果wait=false:
  1001. * - 如果指定的函数对象不存在,返回FuncPool.FunctionNotFound
  1002. * - 如果指定的函数对象存在但尚未加载,返回FuncPool.FunctionNotLoaded
  1003. *
  1004. * 如果函数对象指定了readonly为真值,则返回前用Proxy包装返回值,使其不可修改
  1005. * @template {boolean} B
  1006. * @param {string} id - 函数对象的id
  1007. * @param {B} [wait=false] - 是否等待加载完毕
  1008. * @returns {B extends true ? Promise : *}
  1009. */
  1010. require(id, wait=false) {
  1011. if (wait) {
  1012. // wait模式,返回Promise,resolve为返回值
  1013. // 加载完毕就会resolve为函数对象返回值的promise
  1014. return new Promise((resolve, reject) => {
  1015. const return_value = this.require(id, false);
  1016. const not_available = [
  1017. FuncPool.FunctionNotLoaded,
  1018. FuncPool.FunctionNotFound
  1019. ];
  1020. if (!not_available.includes(return_value)) {
  1021. resolve(return_value);
  1022. } else {
  1023. $AEL(this, 'load', e => e.detail.oFunc.id === id && resolve(e.detail.return_value));
  1024. }
  1025. });
  1026. } else {
  1027. // 非wait模式,有返回值直接返回,无返回值返回对应Symbol标志
  1028. for (const [oFunc, return_value] of this.#oFuncs.entries()) {
  1029. if (oFunc.id === id) {
  1030. if (return_value === FuncPool.#STILL_LOADING) {
  1031. return FuncPool.FunctionNotLoaded;
  1032. } else {
  1033. return oFunc.readonly ? FuncPool.#MakeReadonlyObj(return_value) : return_value;
  1034. }
  1035. }
  1036. }
  1037. return FuncPool.FunctionNotFound;
  1038. }
  1039. }
  1040.  
  1041. isLoaded(id) {
  1042. for (const [oFunc, return_value] of this.#oFuncs.entries()) {
  1043. if (oFunc.id === id) {
  1044. if (return_value === FuncPool.#STILL_LOADING) {
  1045. return false;
  1046. } else {
  1047. return true;
  1048. }
  1049. }
  1050. return false;
  1051. }
  1052. }
  1053.  
  1054. /**
  1055. * 调用this.dispatchEvent分发自定义事件
  1056. * 同时对可分发的事件名称进行限制
  1057. * @param {'load' | 'all_load' | 'error'} evt_name
  1058. * @param {*} [detail]
  1059. */
  1060. #broadcast(evt_name, detail) {
  1061. return this.dispatchEvent(new CustomEvent(evt_name, { detail }));
  1062. }
  1063.  
  1064. get GM_funcs() {
  1065. return { ...this.#GM_funcs };
  1066. }
  1067.  
  1068. /**
  1069. * 以Proxy包装value,使其属性只读 \
  1070. * 如果传入的不是object,则直接返回value \
  1071. * @param {Object} val
  1072. * @returns {Proxy}
  1073. */
  1074. static #MakeReadonlyObj(val) {
  1075. return isObject(val) ? new Proxy(val, {
  1076. get: function(target, property, receiver) {
  1077. return FuncPool.#MakeReadonlyObj(target[property]);
  1078. },
  1079. set: function(target, property, value, receiver) {},
  1080. has: function(target, prop) {},
  1081. setPrototypeOf(target, newProto) {
  1082. return false;
  1083. },
  1084. defineProperty(target, property, descriptor) {
  1085. return true;
  1086. },
  1087. deleteProperty(target, property) {
  1088. return false;
  1089. },
  1090. preventExtensions(target) {
  1091. return false;
  1092. }
  1093. }) : val;
  1094.  
  1095. function isObject(value) {
  1096. return ['object', 'function'].includes(typeof value) && value !== null;
  1097. }
  1098. }
  1099.  
  1100. /**
  1101. * 创建适用于子功能函数的 GM_setValue, GM_getValue, GM_deleteValue 和 GM_listValues \
  1102. * 调用返回的`GM_setValue(str, val)`相当于对脚本管理器提供的GM*函数进行如下调用:
  1103. * ``` javascript
  1104. * const obj = GM_getValue(key, {});
  1105. * if (typeof obj !== 'object' or obj === null) { throw new TypeError(''); }
  1106. * obj[str] = val;
  1107. * GM_setValue(key, obj);
  1108. * ```
  1109. * @param {string} key - 实际调用用户脚本管理器的GM*函数时提供的key,一般是子功能函数id
  1110. * @returns {{ GM_setValue: function, GM_getValue: function, GM_deleteValue: function, GM_listValues: function }}
  1111. */
  1112. #MakeSubStorage(key) {
  1113. const GM_funcs = this.#GM_funcs;
  1114. return {
  1115. GM_setValue(name, val) {
  1116. checkGrant(['GM_setValue', 'GM_getValue'], 'GM_setValue');
  1117. const obj = GM_funcs.GM_getValue(key, {});
  1118. Assert(isObject(obj), `FunctionLoader: storage item of key ${name} should be an object`, TypeError);
  1119. obj[name] = val;
  1120. GM_funcs.GM_setValue(key, obj);
  1121. },
  1122. GM_getValue(name, default_value=null) {
  1123. checkGrant(['GM_getValue'], 'GM_getValue');
  1124. const obj = GM_funcs.GM_getValue(key, {});
  1125. return obj.hasOwnProperty(name) ? obj[name] : default_value;
  1126. },
  1127. GM_deleteValue(name) {
  1128. checkGrant(['GM_setValue', 'GM_getValue'], 'GM_deleteValue');
  1129. const obj = GM_funcs.GM_getValue(key, {});
  1130. delete obj[name];
  1131. GM_funcs.GM_setValue(key, obj);
  1132. },
  1133. GM_listValues() {
  1134. checkGrant(['GM_getValue'], 'GM_listValues');
  1135. const obj = GM_funcs.GM_getValue(key, {});
  1136. return Object.keys(obj);
  1137. }
  1138. };
  1139.  
  1140. /**
  1141. * 检查指定的GM_*函数是否存在,不存在就抛出错误
  1142. * @param {string|string[]} funcnames
  1143. * @param {string} calling - 正在调用的GM_函数的名字,输出错误信息时用
  1144. */
  1145. function checkGrant(funcnames, calling) {
  1146. Array.isArray(funcnames) || (funcnames = [funcnames]);
  1147. for (const funcname of funcnames) {
  1148. Assert(GM_funcs[funcname], `FunctionLoader: @grant ${funcname} in userscript metadata before using ${calling}`, TypeError);
  1149. }
  1150. }
  1151.  
  1152. function isObject(val) {
  1153. return typeof val === 'object' && val !== null;
  1154. }
  1155. }
  1156. }
  1157. class RedeclarationError extends TypeError {}
  1158. class CircularDependencyError extends ReferenceError {}
  1159.  
  1160.  
  1161. // 预置的函数池
  1162. const default_pool = new FuncPool();
  1163.  
  1164. /**
  1165. * 在预置的函数池中加载函数对象或其数组
  1166. * @param {oFunc[]|oFunc} oFuncs - 需要执行的函数对象
  1167. * @returns {FuncPool}
  1168. */
  1169. function loadFuncs(oFuncs) {
  1170. default_pool.load(oFuncs);
  1171. return default_pool;
  1172. }
  1173.  
  1174. /**
  1175. * 在预置的函数池中获取函数对象的返回值
  1176. * @type {typeof FuncPool.prototype.require}
  1177. */
  1178. function require(id, wait) {
  1179. return default_pool.require(id, wait);
  1180. }
  1181.  
  1182. /**
  1183. * 在预置的函数池中检查指定函数对象是否已经加载完毕(有返回值可用)
  1184. * @param {string} id - 函数对象的字符串id
  1185. * @returns {boolean}
  1186. */
  1187. function isLoaded(id) {
  1188. return default_pool.isLoaded(id);
  1189. }
  1190.  
  1191. /**
  1192. * 测试给定checker是否检测通过 \
  1193. * 给定多个checker时,checkers之间是 或 关系,有一个checker通过即算作整体通过 \
  1194. * 注意此函数设计和旧版testChecker的设计不同,旧版中一个checker可以有多个值,还可通过checker.all指定多值之间的关系为 与 还是 或
  1195. * @param {checker[]|checker} [checkers] - 需要检测的checkers
  1196. * @param {oFunc|*} [this_value] - 如提供,将用作checkers运行时的this值;一般而言为checkers所属的函数对象
  1197. * @returns {boolean}
  1198. */
  1199. function testCheckers(checkers=[], this_value=null) {
  1200. checkers = Array.isArray(checkers) ? checkers : [checkers];
  1201. return checkers.length === 0 || checkers.some(checker => !!registered_checkers[checker.type]?.call(this_value, checker.value));
  1202. }
  1203.  
  1204. /**
  1205. * 注册新checker \
  1206. * 如果给定type已经被其他checker占用,则会报错RedeclarationError \
  1207. * @param {string} type - checker类名
  1208. * @param {function} func - checker implementation
  1209. */
  1210. function registerChecker(type, func) {
  1211. if (registered_checkers.hasOwnProperty(type)) {
  1212. throw RedeclarationError(`Attempts to register checker with type already in use: ${type}`);
  1213. }
  1214. registered_checkers[type] = func;
  1215. }
  1216.  
  1217. const FunctionLoader = {
  1218. FuncPool,
  1219. testCheckers,
  1220. registerChecker,
  1221. get checkers() {
  1222. return Object.assign({}, registered_checkers);
  1223. },
  1224. Error: {
  1225. RedeclarationError,
  1226. CircularDependencyError
  1227. }
  1228. };
  1229. return [FunctionLoader, loadFuncs, require, isLoaded, default_pool];
  1230. }) ();
  1231.  
  1232. return [
  1233. // Console & Debug
  1234. LogLevel, DoLog, Err, Assert,
  1235.  
  1236. // DOM
  1237. $, $All, $CrE, $AEL, $$CrE, addStyle, detectDom, destroyEvent,
  1238.  
  1239. // Data
  1240. copyProp, copyProps, parseArgs, escJsStr, replaceText,
  1241.  
  1242. // Environment & Browser
  1243. getUrlArgv, dl_browser, dl_GM,
  1244.  
  1245. // Logic & Task
  1246. AsyncManager, queueTask, FunctionLoader, loadFuncs, require, isLoaded, default_pool
  1247. ];
  1248. }) ();