Prevents specified DOM elements from being destroyed by page state changes
Skrip ini tidak untuk dipasang secara langsung. Ini adalah pustaka skrip lain untuk disertakan dengan direktif meta // @require https://update.greasyfork.org/scripts/584836/1863342/Anti-UI%20Purge%20Library.js
// ==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);