NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @exclude *
//
// ==UserLibrary==
// @name DOM Waiter
// @description DOM helper to wait HTML elements
// @copyright 2020, Sergey Zaborovsky (seryiza.xyz)
// @license MIT
// @version 0.1.5
// ==/UserLibrary==
//
// ==OpenUserJs==
// @author Seryiza
// ==/OpenUserJs==
// ==/UserScript==
/* jshint esversion: 6 */
/**
* Print any errors thrown by a function to log, and rethrow them. Useful for
* asynchronous callbacks.
*/
function logCallback(cb) {
return function (...args) {
try {
return cb(...args);
}
catch (e) {
console.error(e);
throw e;
}
};
}
/**
* Default finder of HTML elements.
*
* @param {string} selector
* @returns {HTMLElement[]}
*/
function findElementsFromDocument(selector, source = undefined) {
if (source) return [...source].filter(el => el.matches(selector));
return Array.from(document.querySelectorAll(selector));
}
/**
* Check element by comparation of text content.
*
* @param {string} expectingText
* @param {HTMLElement} element
*/
function checkElementByTextContent(expectingText, element) {
return element.textContent === expectingText;
}
/**
* Get checker function from multiple types.
*
* @param {function|string|undefined} checker
*/
function getCheckerFunction(checker) {
if (checker instanceof Function) {
return checker;
}
if (typeof checker === 'string') {
return checkElementByTextContent.bind(null, checker);
}
return function () {
return true;
}
}
/**
* Create wait function.
*
* @param {function} elementsFinder
*/
function Waiter(findElementsFn = findElementsFromDocument) {
return function (selector, checker) {
const checkerFn = getCheckerFunction(checker);
const select = (source = undefined) => {
const elements = findElementsFn(selector);
if (elements.length === 0) {
return;
}
const checkedElements = elements.filter(checkerFn);
if (checkedElements.length === 0) {
return;
}
return (checkedElements.length > 1) ? checkedElements : checkedElements[0];
};
const selected = select();
if (selected) {
return Promise.resolve(selected);
}
return new Promise(logCallback((resolve) => {
const observer = new MutationObserver(logCallback((mods) => {
for (const mod of mods) {
const selected = select(mod.addedNodes);
if (selected) {
observer.disconnect();
resolve(selected);
return;
}
}
}));
// Document because document.body may not yet be instantiated
observer.observe(document, {
childList: true,
subtree: true,
});
}));
}
}
window.Waiter = Waiter;