Anti-UI Purge Library

Prevents specified DOM elements from being destroyed by page state changes

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/584836/1863342/Anti-UI%20Purge%20Library.js

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Anti-UI Purge Library
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Prevents specified DOM elements from being destroyed by page state changes
// @author       Gemini
// @grant        none
// ==/UserScript==

(function(win) {
    'use strict';

    const protectedIDs = new Set();
    const protectedElements = new Map();

    // ==========================================
    // 1. NATIVE METHOD HOOKING (Proactive Defense)
    // ==========================================
    
    // Hook removeChild to intercept direct removal attempts
    const originalRemoveChild = Node.prototype.removeChild;
    Node.prototype.removeChild = function(child) {
        if (child && child.id && protectedIDs.has(child.id)) {
            console.warn(`[Anti-Purge] Blocked attempt to remove protected element: #${child.id}`);
            return child; // Pretend it was removed successfully
        }
        return originalRemoveChild.apply(this, arguments);
    };

    // Hook replaceChild
    const originalReplaceChild = Node.prototype.replaceChild;
    Node.prototype.replaceChild = function(newChild, oldChild) {
        if (oldChild && oldChild.id && protectedIDs.has(oldChild.id)) {
            console.warn(`[Anti-Purge] Blocked attempt to replace protected element: #${oldChild.id}`);
            // Insert the new child, but keep the old protected child
            this.insertBefore(newChild, oldChild);
            return oldChild;
        }
        return originalReplaceChild.apply(this, arguments);
    };

    // Hook innerHTML to catch blanket wipes (e.g., document.body.innerHTML = '')
    const originalInnerHTML = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
    Object.defineProperty(Element.prototype, 'innerHTML', {
        set: function(value) {
            // Find if any protected elements are currently inside this container
            const elementsToSave = [];
            protectedIDs.forEach(id => {
                const el = this.querySelector(`#${id}`);
                if (el) elementsToSave.push(el);
            });

            // Allow the wipe
            originalInnerHTML.set.call(this, value);

            // Re-append the protected elements immediately
            elementsToSave.forEach(el => {
                this.appendChild(el);
                console.warn(`[Anti-Purge] Restored #${el.id} after innerHTML wipe.`);
            });
        },
        get: originalInnerHTML.get
    });

    // ==========================================
    // 2. MUTATION OBSERVER (Reactive Fallback)
    // ==========================================
    
    const observer = new MutationObserver((mutations) => {
        mutations.forEach(mutation => {
            mutation.removedNodes.forEach(node => {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    // Check if the node itself is protected
                    if (protectedIDs.has(node.id)) {
                        document.body.appendChild(protectedElements.get(node.id));
                        console.warn(`[Anti-Purge] Observer restored orphaned element: #${node.id}`);
                    } 
                    // Check if the removed node *contained* our protected element
                    else {
                        protectedIDs.forEach(id => {
                            if (node.querySelector(`#${id}`)) {
                                document.body.appendChild(protectedElements.get(id));
                                console.warn(`[Anti-Purge] Observer restored element #${id} from purged parent container.`);
                            }
                        });
                    }
                }
            });
        });
    });

    win.addEventListener('DOMContentLoaded', () => {
        observer.observe(document.body, { childList: true, subtree: true });
    });

    // ==========================================
    // 3. EXPORTED API
    // ==========================================
    
    win.AntiPurge = {
        /**
         * Registers an element to be protected from DOM deletion.
         * @param {HTMLElement} element - The DOM node to protect. Must have a unique ID.
         */
        protect: function(element) {
            if (!element.id) {
                console.error('[Anti-Purge] Element must have an ID to be protected.');
                return;
            }
            protectedIDs.add(element.id);
            protectedElements.set(element.id, element);
            console.log(`[Anti-Purge] Now protecting: #${element.id}`);
        },

        /**
         * Unregisters an element, allowing normal deletion.
         * @param {string} id - The ID of the element to release.
         */
        release: function(id) {
            protectedIDs.delete(id);
            protectedElements.delete(id);
            console.log(`[Anti-Purge] Released protection for: #${id}`);
        }
    };

})(typeof unsafeWindow !== 'undefined' ? unsafeWindow : window);