Greasy Fork is available in English.

waitForKeyElementsInDocument

NOT TESTED YET waitForKeyElements version with possibility to work on other DOM Documents. Uses ES6, no JQuery or other frameworks required.

Ce script ne doit pas être installé directement. C'est une librairie destinée à être incluse dans d'autres scripts avec la méta-directive // @require https://greasyfork.org/scripts/369404-waitforkeyelementsindocument/code/waitForKeyElementsInDocument.js?version=606324

// ==UserScript==
// @name            waitForKeyElementsInDocument
// @namespace       https://greasyfork.org/users/190012-zerk
// @version         0.1.4
// @description     NOT TESTED YET waitForKeyElements version with possibility to work on other DOM Documents. Uses ES6, no JQuery or other frameworks required.
// @author          zerk
// @icon            https://www.greasespot.net/favicon.ico
// @include         http*://*/*
// @grant           unsafeWindow
// ==/UserScript==

// code based on https://greasyfork.org/scripts/38889-waitforkeyelements-2018-userscript-library/code

/**
 * Delay
 * @constant
 */
const delay = 100;

/**
 * Unique identifier
 * @constant
 */
const uuid = `wfke_${(GM_info.script.name).replace(/[^a-zA-Z0-9_.]/gi, '').toLowerCase()}_${Math.random().toString(36).substring(2, 15)}`;

/**
 * Request id of requestAnimationFrame
 * @global
 */
let requestId = unsafeWindow.requestId || null;
unsafeWindow.requestId = requestId;

/**
 * Selector dictionary
 * @global
 */
const selectorMap = unsafeWindow.selectorMap || {};
unsafeWindow.selectorMap = selectorMap;

/**
 * didStartLookup
 * @global
 */
let didStartLookup = false;
unsafeWindow.didStartLookup = didStartLookup;

/**
 * Has started lookup
 * @returns {Boolean} - Yes/No
 */
let hasStartedLookup = () => unsafeWindow.didStartLookup;

/**
 * Stop lookup
 */
let stopLookup = () => {
    window.cancelAnimationFrame(unsafeWindow.requestId);
    unsafeWindow.requestId = null;
    delete unsafeWindow.requestId;
};

/**
 * Register selector
 * @param {String} selector - CSS selector of elements to search / monitor ('.comment')
 * @param {function} callback - Callback executed on element detection (called with element as argument)
 * @param {Boolean=} findOnce - Stop lookup after the last currently available element has been found
 * @param {String=} targetSelector - CSS selector of nested element to limit search to ('#footer')
 */
let registerSelector = (selector, callback, findOnce, checkerFunction, targetSelector) => {
    unsafeWindow.selectorMap[selector] = {
        callback,
        findOnce,
        checkerFunction,
        targetSelector
    };

    // DEBUG
    console.debug('[waitForKeyElements]', `[${selector}]`, 'selector added');
};

/**
 * Register selector
 * @param {String} selector - CSS selector
 * @returns {Boolean} - Yes/No
 */
let hasRegisteredSelector = (selector) => unsafeWindow.selectorMap[selector];

/**
 * Unregister selector
 * @param {String} selector - CSS selector
 */
let unregisterSelector = (selector) => {
    unsafeWindow.selectorMap[selector] = null;
    delete unsafeWindow.selectorMap[selector];

    // DEBUG
    // console.log('[waitForKeyElements]', `[${selector}]`, 'selector complete');

    // Last selector? Cancel requestAnimationFrame
    if (Object.keys(unsafeWindow.selectorMap).length === 0) {
        stopLookup();

        // DEBUG
        console.debug('[waitForKeyElements]', 'all selectors complete');
    }
};

let waitForKeyElementsInDocument = (selector, callback, findOnce = false, checkerFunction = null, targetSelector = '', doc = window.document) => {
    // Init
    let elementList;
    let didFindNewElement = false;
    
    // Find elements
    if (Boolean(targetSelector)) {
        elementList = doc.querySelector(targetSelector).querySelectorAll(selector) || [];
    } else {
        elementList = doc.querySelectorAll(selector) || [];
    }

    // Is element new?
    if (elementList.length > 0) {
        elementList.forEach((element) => {
            if(checkerFunction && checkerFunction(element)) {             
                // Add <data> attribute to newly discovered nodes
                if (element.dataset[uuid] !== 'found') {
                    element.dataset[uuid] = 'found';
                    didFindNewElement = true;

                    // Callback
                    callback(element);

                    // DEBUG
                    console.debug('[waitForKeyElements]', `[${selector}"]`, 'element found');
                }
            }
        });
    }

    // Register selector
    if (!hasRegisteredSelector(selector)) {
        registerSelector(selector, callback, findOnce, checkerFunction, targetSelector);
    }

    // Running once? Check if element found
    if (hasRegisteredSelector(selector) && findOnce && didFindNewElement) {
        unregisterSelector(selector);
    } else {
        if (!hasStartedLookup()) {
            unsafeWindow.didStartLookup = true;
            let lastTime = Date.now();
            let lookupLoop = () => {
                if (Date.now() >= (lastTime + delay)) {
                    lastTime = Date.now();

                    // Loop through registered selectors
                    Object.keys(unsafeWindow.selectorMap).forEach((selector) => {
                        waitForKeyElementsInDocument(selector, unsafeWindow.selectorMap[selector].callback, unsafeWindow.selectorMap[selector].findOnce, unsafeWindow.selectorMap[selector].checkerFunction, unsafeWindow.selectorMap[selector].targetSelector);
                    });

                    // Verbose
                    // console.debug('[waitForKeyElements]', lastTime, `[${selector}]`, 'lookup cycle complete');
                }

                unsafeWindow.requestId = window.requestAnimationFrame(lookupLoop);
            };
            lookupLoop();
        }
    }
};