Double-Tap Hide, Stealth Engine, Site-Wide Freeze Mode, Hardened Blocker, Daily Auto-Clearing Logs, Single-Use Freeze Approvals
// ==UserScript==
// @name Hide Web Elements Pro
// @version 3.29
// @description Double-Tap Hide, Stealth Engine, Site-Wide Freeze Mode, Hardened Blocker, Daily Auto-Clearing Logs, Single-Use Freeze Approvals
// @author KTZ
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant unsafeWindow
// @run-at document-start
// @license MIT
// @namespace https://greasyfork.org/users/1620673
// ==/UserScript==
(function() {
'use strict';
const UI_HOST_ID = 'hider-ui-root';
const isTop = (window.self === window.top);
const isMobile = /Android|iPhone|iPad/i.test(navigator.userAgent);
const btnSize = isMobile ? '50px' : '44px';
const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
// Pre-compiled Regexes for high-performance DOM parsing
const REG_NUMERIC = /^\d+$/;
const REG_LONG_ID = /.{15,}/;
const REG_HOVER = /^hover:/i;
const REG_RANDOM_CLASS = /^[a-zA-Z0-9]{10,}$/;
// In-Memory Fast Cache Engine (Avoids repeated GM_getValue IPC calls)
const CACHE = {
blockedDomainsList: [],
blockedDomainsSet: new Set(),
isFrozen: false,
autoNo: false,
logs: null,
siteData: null,
linkData: null
};
// Auto-clears blocked logs every new calendar day
function checkAndClearDailyLogs() {
const today = new Date().toDateString();
const lastClearDay = GM_getValue('hider_last_log_clear_day', '');
if (lastClearDay !== today) {
CACHE.logs = [];
GM_setValue('hider_global_logs', []);
GM_setValue('hider_last_log_clear_day', today);
}
}
function syncCacheFromStorage() {
CACHE.blockedDomainsList = GM_getValue('hider_blocked_domains', []);
CACHE.blockedDomainsSet = new Set(CACHE.blockedDomainsList.map(cleanDomain).filter(Boolean));
CACHE.isFrozen = GM_getValue('hider_freeze_global', false);
CACHE.autoNo = GM_getValue('hider_autono_global', false);
checkAndClearDailyLogs();
}
syncCacheFromStorage();
let isSelecting = false;
let isFrozen = CACHE.isFrozen;
let currentScope = 'site';
let previewElement = null;
let userApprovedNavigation = false;
let cachedCssString = null;
let lastUrl = window.location.href;
// --- Helper: Fast Domain Cleaning & O(1) Set Lookups ---
function cleanDomain(url) {
if (!url || typeof url !== 'string') return '';
try {
const lower = url.trim().toLowerCase();
const noProtocol = lower.includes('://') ? lower.split('://')[1] : lower;
return noProtocol.split('/')[0].split('?')[0].split(':')[0].replace(/^www\./, '');
} catch(e) {
return '';
}
}
function isDomainBlocked(targetUrl) {
if (!targetUrl || CACHE.blockedDomainsSet.size === 0 || typeof targetUrl !== 'string') return false;
const targetDomain = cleanDomain(targetUrl);
if (!targetDomain) return false;
// Fast O(1) Direct Set Match
if (CACHE.blockedDomainsSet.has(targetDomain)) return true;
// Fallback Subdomain & Query String Inclusion Check
const lowerUrl = targetUrl.toLowerCase();
for (const bDomain of CACHE.blockedDomainsSet) {
if (targetDomain.endsWith('.' + bDomain) || lowerUrl.includes(bDomain)) {
return true;
}
}
return false;
}
// --- Optimized Log Engine (Daily Reset + Debounced Storage Sync) ---
let logSaveTimer = null;
function logBlockedAttempt(url, triggerType) {
checkAndClearDailyLogs();
if (!CACHE.logs) {
const rawLogs = GM_getValue('hider_global_logs', []);
CACHE.logs = Array.isArray(rawLogs) ? rawLogs : [];
}
CACHE.logs.unshift({
url: url || 'about:blank',
time: new Date().toLocaleTimeString(),
type: triggerType || 'Popup'
});
if (CACHE.logs.length > 50) CACHE.logs.pop();
if (!logSaveTimer) {
logSaveTimer = setTimeout(() => {
logSaveTimer = null;
GM_setValue('hider_global_logs', CACHE.logs);
}, 1000);
}
}
// --- Layout-Thrash Free Video Exposing Engine ---
function forceExposeAndPlayVideos() {
try {
const videos = document.getElementsByTagName('video');
if (videos.length === 0) return;
for (let i = 0; i < videos.length; i++) {
const v = videos[i];
v.style.setProperty('display', 'block', 'important');
v.style.setProperty('visibility', 'visible', 'important');
v.style.setProperty('opacity', '1', 'important');
v.style.setProperty('pointer-events', 'auto', 'important');
let parent = v.parentElement;
let depth = 0;
while (parent && depth < 3 && parent !== document.body) {
parent.style.setProperty('display', 'block', 'important');
parent.style.setProperty('visibility', 'visible', 'important');
parent.style.setProperty('opacity', '1', 'important');
parent = parent.parentElement;
depth++;
}
const p = v.play();
if (p && typeof p.catch === 'function') p.catch(() => {});
}
} catch(e) {}
}
function triggerVideoResumeChain() {
forceExposeAndPlayVideos();
setTimeout(forceExposeAndPlayVideos, 150);
setTimeout(forceExposeAndPlayVideos, 400);
setTimeout(forceExposeAndPlayVideos, 800);
broadcastToFrames(window, { type: 'HIDER_RESUME_VIDEOS' });
}
function simulateAdWindowSuccess() {
try {
window.dispatchEvent(new Event('blur'));
document.dispatchEvent(new Event('visibilitychange'));
if (typeof window.onblur === 'function') {
try { window.onblur(); } catch(e) {}
}
setTimeout(() => {
window.dispatchEvent(new Event('focus'));
if (typeof window.onfocus === 'function') {
try { window.onfocus(); } catch(e) {}
}
}, 60);
triggerVideoResumeChain();
} catch(e) {}
}
function createDummyWindow() {
const dummyTarget = {
closed: false,
focus: function() {},
blur: function() {},
close: function() { this.closed = true; },
postMessage: function() {},
location: {
href: 'about:blank',
replace: function(u) { if (isDomainBlocked(u)) logBlockedAttempt(u, 'Blocked Dummy Replace'); },
assign: function(u) { if (isDomainBlocked(u)) logBlockedAttempt(u, 'Blocked Dummy Assign'); }
},
document: { write: function() {}, close: function() {}, body: {} },
opener: win
};
return new Proxy(dummyTarget, {
get(target, prop) {
if (prop in target) return target[prop];
return function() {};
},
set(target, prop, value) {
if (prop === 'location' && typeof value === 'string' && isDomainBlocked(value)) {
logBlockedAttempt(value, 'Blocked Dummy Location Set');
return true;
}
target[prop] = value;
return true;
}
});
}
// --- HARDENED INTERCEPTORS (PROTOTYPE LEVEL) ---
// 1. Trap Location Redirects
try {
const locProto = win.Location ? win.Location.prototype : Object.getPrototypeOf(win.location);
if (locProto) {
const origAssign = locProto.assign;
locProto.assign = function(url) {
if (isDomainBlocked(url)) {
logBlockedAttempt(url, 'Hard Blocked Redirect (assign)');
showToast('⛔ Blocked redirect');
simulateAdWindowSuccess();
return;
}
return origAssign.apply(this, arguments);
};
const origReplace = locProto.replace;
locProto.replace = function(url) {
if (isDomainBlocked(url)) {
logBlockedAttempt(url, 'Hard Blocked Redirect (replace)');
showToast('⛔ Blocked redirect');
simulateAdWindowSuccess();
return;
}
return origReplace.apply(this, arguments);
};
const hrefDesc = Object.getOwnPropertyDescriptor(locProto, 'href');
if (hrefDesc && hrefDesc.set) {
const origHrefSet = hrefDesc.set;
Object.defineProperty(locProto, 'href', {
set: function(url) {
if (isDomainBlocked(url)) {
logBlockedAttempt(url, 'Hard Blocked Redirect (href)');
showToast('⛔ Blocked redirect');
simulateAdWindowSuccess();
return;
}
origHrefSet.call(this, url);
}
});
}
}
} catch(e) {}
// 2. Trap Programmatic Script Clicks
try {
const origAnchorClick = HTMLAnchorElement.prototype.click;
HTMLAnchorElement.prototype.click = function() {
if (isDomainBlocked(this.href)) {
logBlockedAttempt(this.href, 'Hard Blocked Script Click');
showToast('⛔ Blocked link click');
simulateAdWindowSuccess();
return;
}
return origAnchorClick.apply(this, arguments);
};
} catch(e) {}
// 3. Trap window.open
try {
const origOpen = win.open;
win.open = function(url, target, features) {
if (isDomainBlocked(url)) {
logBlockedAttempt(url || 'about:blank', 'Hard Blocked (win.open)');
simulateAdWindowSuccess();
showToast('⛔ Hard Blocked Popup');
return createDummyWindow();
}
if (!url || url === 'about:blank' || url === '') {
const realWin = origOpen.apply(win, arguments);
if (!realWin) return createDummyWindow();
try {
return new Proxy(realWin, {
get(t, p) {
if (p === 'location') {
return new Proxy(t.location, {
set(locTarget, locProp, locVal) {
if ((locProp === 'href' || locProp === 'assign') && isDomainBlocked(locVal)) {
logBlockedAttempt(locVal, 'Hard Blocked Popup Navigation');
showToast('⛔ Blocked popup navigation');
try { t.close(); } catch(err){}
return true;
}
locTarget[locProp] = locVal;
return true;
}
});
}
const val = t[p];
return typeof val === 'function' ? val.bind(t) : val;
},
set(t, p, v) {
if (p === 'location' && isDomainBlocked(v)) {
logBlockedAttempt(v, 'Hard Blocked Popup Location');
showToast('⛔ Blocked popup navigation');
try { t.close(); } catch(err){}
return true;
}
t[p] = v;
return true;
}
});
} catch(e) {
return realWin;
}
}
if (isFrozen && !userApprovedNavigation) {
showFreezePrompt(url || 'about:blank', 'win.open()', () => {
userApprovedNavigation = true;
origOpen.call(win, url, target, features);
setTimeout(() => { userApprovedNavigation = false; }, 300);
}, () => {
simulateAdWindowSuccess();
});
return createDummyWindow();
}
return origOpen.apply(win, arguments);
};
} catch (err) {}
// --- Cross-Frame Communication ---
function broadcastToFrames(winObj, msg) {
try {
for (let i = 0; i < winObj.frames.length; i++) {
winObj.frames[i].postMessage(msg, '*');
broadcastToFrames(winObj.frames[i], msg);
}
} catch(e) {}
}
function broadcastState() {
if (!isTop) return;
broadcastToFrames(window, {
type: 'HIDER_SYNC_STATE',
isSelecting,
currentScope,
isFrozen
});
}
window.addEventListener('message', (e) => {
if (!e.data) return;
if (e.data.type === 'HIDER_REQUEST_STATE' && isTop) {
broadcastState();
return;
}
if (e.data.type === 'HIDER_SYNC_STATE') {
isSelecting = e.data.isSelecting;
currentScope = e.data.currentScope;
isFrozen = e.data.isFrozen;
const btnSelect = document.getElementById('btn-select');
const btnScope = document.getElementById('btn-scope');
if (btnSelect) btnSelect.classList.toggle('active', isSelecting);
if (btnScope) btnScope.classList.toggle('active', isSelecting);
if (!isSelecting && previewElement) {
previewElement.classList.remove('hider-preview-highlight');
previewElement = null;
}
}
if (e.data.type === 'HIDER_RESUME_VIDEOS') {
forceExposeAndPlayVideos();
setTimeout(forceExposeAndPlayVideos, 200);
setTimeout(forceExposeAndPlayVideos, 500);
}
});
if (!isTop) {
try { window.top.postMessage({ type: 'HIDER_REQUEST_STATE' }, '*'); } catch(e) {}
}
// --- Optimized Element Selector Engine ---
function getExactSelector(el) {
if (!el || el.nodeType !== Node.ELEMENT_NODE) return '';
const path = [];
let current = el;
while (current && current.nodeType === Node.ELEMENT_NODE && current.tagName.toLowerCase() !== 'body' && current.tagName.toLowerCase() !== 'html') {
let selector = current.tagName.toLowerCase();
if (current.id && typeof current.id === 'string' && !REG_NUMERIC.test(current.id) && !REG_LONG_ID.test(current.id)) {
selector = `#${CSS.escape(current.id)}`;
path.unshift(selector);
break;
}
let hasGoodClass = false;
if (current.className && typeof current.className === 'string') {
const classes = current.className.trim().split(/\s+/).filter(c => {
return c.length > 2 && !REG_RANDOM_CLASS.test(c) && !REG_HOVER.test(c);
});
if (classes.length > 0) {
selector += `.${CSS.escape(classes[0])}`;
if (classes[1]) selector += `.${CSS.escape(classes[1])}`;
hasGoodClass = true;
}
}
if (!hasGoodClass) {
let index = 1, sibling = current.previousElementSibling;
while (sibling) { index++; sibling = sibling.previousElementSibling; }
selector += `:nth-child(${index})`;
}
path.unshift(selector);
if (hasGoodClass && path.length >= 2) break;
current = current.parentElement;
}
if (path.length > 0 && !path[0].startsWith('#') && !path[0].includes('.')) {
path.unshift('body');
}
return path.join(' > ');
}
// --- UI Toast & Modal Prompts ---
function showToast(msg) {
const existing = document.getElementById('hider-toast');
if (existing) existing.remove();
const toast = document.createElement('div');
toast.id = 'hider-toast';
toast.style.cssText = `
position: fixed; bottom: 25px; left: 50%; transform: translateX(-50%);
background: rgba(15, 23, 42, 0.88); backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 20px;
padding: 8px 18px; color: white; font-family: system-ui, -apple-system, sans-serif;
font-size: 12px; font-weight: 600; z-index: 2147483647;
box-shadow: 0 4px 20px rgba(0,0,0,0.5); pointer-events: none;
transition: opacity 0.3s ease; opacity: 1;
`;
toast.textContent = msg;
(document.body || document.documentElement).appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 300);
}, 2000);
}
function showFreezePrompt(url, triggerType, onConfirm, onDeny) {
if (CACHE.autoNo) {
logBlockedAttempt(url, triggerType + ' (Auto Block)');
if (onDeny) onDeny();
simulateAdWindowSuccess();
return;
}
const existing = document.getElementById('hider-freeze-prompt');
if (existing) existing.remove();
const promptEl = document.createElement('div');
promptEl.id = 'hider-freeze-prompt';
promptEl.style.cssText = `
position: fixed; top: 20px; left: 50%; transform: translateX(-50%);
background: rgba(15, 23, 42, 0.92); backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 14px;
padding: 14px 18px; color: white; font-family: system-ui, -apple-system, sans-serif;
font-size: 13px; z-index: 2147483647; display: flex; flex-direction: column;
align-items: center; gap: 8px; box-shadow: 0 10px 30px rgba(0,0,0,0.5);
pointer-events: auto; max-width: 90vw; width: 330px; text-align: center;
`;
const displayUrl = url ? (url.length > 45 ? url.substring(0, 42) + '...' : url) : 'another page';
promptEl.innerHTML = `
<div style="font-weight: 700; color: #f43f5e; font-size: 11px; letter-spacing: 0.5px; text-transform: uppercase;">❄️ Site-Wide Navigation Resistance</div>
<div style="font-size: 12px; color: #f3f4f6;">Do you want to proceed to this link?</div>
<div style="font-size: 11px; color: #9ca3af; word-break: break-all; max-height: 36px; overflow: hidden; opacity: 0.8;">${displayUrl}</div>
<div style="display: flex; align-items: center; gap: 6px; margin: 2px 0;">
<input type="checkbox" id="hider-freeze-remember" style="cursor: pointer; accent-color: #8b5cf6;">
<label for="hider-freeze-remember" style="font-size: 11px; color: #cbd5e1; cursor: pointer; user-select: none;">Remember "No" choice (Auto-Block Ads)</label>
</div>
<div style="display: flex; gap: 8px; width: 100%; margin-top: 4px;">
<button id="hider-freeze-yes" style="flex: 1; padding: 7px 12px; background: #10b981; border: none; border-radius: 8px; color: white; font-weight: bold; cursor: pointer; font-size: 12px;">Yes</button>
<button id="hider-freeze-no" style="flex: 1; padding: 7px 12px; background: #4b5563; border: none; border-radius: 8px; color: white; font-weight: bold; cursor: pointer; font-size: 12px;">No</button>
</div>
`;
(document.body || document.documentElement).appendChild(promptEl);
document.getElementById('hider-freeze-yes').onclick = () => {
promptEl.remove();
if (onConfirm) onConfirm();
};
document.getElementById('hider-freeze-no').onclick = () => {
logBlockedAttempt(url, triggerType + ' (User)');
if (document.getElementById('hider-freeze-remember').checked) {
GM_setValue('hider_autono_global', true);
CACHE.autoNo = true;
}
promptEl.remove();
if (onDeny) onDeny();
simulateAdWindowSuccess();
};
}
function injectCoreStyles() {
if (document.getElementById('hider-core-styles')) return;
const s = document.createElement('style');
s.id = 'hider-core-styles';
s.textContent = `
@keyframes hider-preview-pulse {
0% { outline: 3px dotted #ef4444; outline-offset: -3px; box-shadow: inset 0 0 0 rgba(239, 68, 68, 0.1); }
50% { outline: 3px dotted #ef4444; outline-offset: 2px; box-shadow: inset 0 0 15px rgba(239, 68, 68, 0.4); }
100% { outline: 3px dotted #ef4444; outline-offset: -3px; box-shadow: inset 0 0 0 rgba(239, 68, 68, 0.1); }
}
.hider-preview-highlight {
animation: hider-preview-pulse 1s infinite !important;
cursor: pointer !important;
border-radius: 2px !important;
}
`;
(document.head || document.documentElement).appendChild(s);
}
function injectUI() {
if (!isTop || document.getElementById(UI_HOST_ID)) return;
const mountPoint = document.body || document.documentElement;
if (!mountPoint) return;
const host = document.createElement('div');
host.id = UI_HOST_ID;
host.style.cssText = 'position:fixed; top:0; right:0; bottom:0; width:0; z-index:2147483647; pointer-events:none;';
mountPoint.appendChild(host);
const s = document.createElement('style');
s.textContent = `
.hider-btn {
position: fixed; right: 0; width: ${btnSize}; height: ${btnSize};
background: rgba(255, 255, 255, 0.2); backdrop-filter: blur(15px);
border: 1px solid rgba(255,255,255,0.3); border-radius: 12px 0 0 12px;
color: white; display: flex; align-items: center; justify-content: center;
cursor: pointer; font-size: 10px; font-weight: 700; text-transform: uppercase;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: auto;
transform: translateX(calc(${btnSize} - 15px));
}
@media (hover: hover) {
.hider-btn:hover { transform: translateX(0); width: 70px; background: rgba(255,255,255,0.4); }
}
#btn-select { top: 20px; }
#btn-select.active { background: #3b82f6 !important; transform: translateX(0) !important; width: 70px !important; }
#btn-scope { top: calc(20px + ${btnSize} + 10px); }
#btn-scope.scope-link { background: #10b981 !important; }
#btn-scope.active { transform: translateX(0) !important; width: 70px !important; }
#btn-scope.temp-expand { transform: translateX(0) !important; width: 70px !important; }
#btn-freeze { top: calc(20px + (${btnSize} * 2) + 20px); background: rgba(139, 92, 246, 0.4); }
#btn-freeze.is-frozen { background: #8b5cf6 !important; }
#btn-freeze.temp-expand { transform: translateX(0) !important; width: 70px !important; }
#btn-manage { top: calc(20px + (${btnSize} * 3) + 30px); background: rgba(239, 68, 68, 0.4); }
#btn-manage.active { background: rgba(239, 68, 68, 0.8) !important; transform: translateX(0) !important; width: 70px !important; }
#hider-panel {
position: fixed; right: 10px; top: 100px; width: 320px; max-height: 75vh;
background: rgba(0,0,0,0.88); backdrop-filter: blur(20px); border-radius: 16px;
padding: 15px; color: white; display: none; flex-direction: column; overflow-y: auto; pointer-events: auto;
}
.panel-header { font-weight:bold; margin-bottom:12px; border-bottom: 2px solid #555; padding-bottom: 8px; display: flex; justify-content: space-between; align-items: center; }
.section-title { font-size: 11px; color: #aaa; text-transform: uppercase; margin: 10px 0 5px 0; letter-spacing: 1px; display: flex; justify-content: space-between; align-items: center; }
.list-item { font-size: 11px; margin-bottom: 6px; border-bottom: 1px solid #333; padding-bottom: 4px; display: flex; justify-content: space-between; align-items: center; }
.list-item span { word-break: break-all; padding-right: 8px; }
.hider-btn-small { cursor: pointer; border: none; border-radius: 4px; padding: 3px 7px; color: white; font-weight: bold; background: #ff4444; font-size: 10px; }
.hider-btn-small:hover { background: #cc0000; }
#close-p { background: #555; padding: 5px 9px; } #close-p:hover { background: #777; }
`;
(document.head || document.documentElement).appendChild(s);
host.innerHTML = `
<div class="hider-btn" id="btn-select">Hide</div>
<div class="hider-btn" id="btn-scope">SITE</div>
<div class="hider-btn ${isFrozen ? 'is-frozen' : ''}" id="btn-freeze">FREEZE</div>
<div class="hider-btn" id="btn-manage">List</div>
<div id="hider-panel">
<div class="panel-header">
<span>Site-Wide Hider & Blocker</span>
<button class="hider-btn-small" id="close-p">X</button>
</div>
<div class="section-title">Block Target Domain (Site-Wide)</div>
<div style="display: flex; gap: 6px; margin-bottom: 8px;">
<input type="text" id="manual-domain-input" placeholder="e.g. badsite.com or adsserver.net" style="flex: 1; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); border-radius: 6px; color: white; padding: 5px 8px; font-size: 11px; outline: none;">
<button class="hider-btn-small" id="add-domain-btn" style="background: #3b82f6; width: 30px; font-size: 14px; font-weight: bold;">+</button>
</div>
<div class="section-title" style="margin-top: 10px;">Manually Blocked Websites</div>
<div id="list-blocked-domains"></div>
<div class="section-title" style="margin-top: 10px;">Site-Wide Freeze Memory</div>
<div id="freeze-memory-container" class="list-item"></div>
<div class="section-title" style="margin-top: 10px;">
<span>Blocked Redirect Log (Daily)</span>
<button class="hider-btn-small" id="clear-log-btn" style="background:#6b7280;">Clear</button>
</div>
<div id="list-blocked-log" style="max-height: 120px; overflow-y: auto; background: rgba(255,255,255,0.05); border-radius: 6px; padding: 5px;"></div>
<div class="section-title" style="margin-top: 10px;">Hidden Site-Wide (${window.location.hostname})</div>
<div id="list-site"></div>
<div class="section-title" style="margin-top: 10px;">Hidden This Page Only</div>
<div id="list-link"></div>
</div>
`;
const addBlockedDomain = () => {
const input = document.getElementById('manual-domain-input');
const domain = cleanDomain(input.value);
if (!domain) return;
if (!CACHE.blockedDomainsSet.has(domain)) {
CACHE.blockedDomainsList.push(domain);
CACHE.blockedDomainsSet.add(domain);
GM_setValue('hider_blocked_domains', CACHE.blockedDomainsList);
showToast(`🚫 Force-blocked website: ${domain}`);
}
input.value = '';
renderList();
};
document.getElementById('add-domain-btn').onclick = addBlockedDomain;
document.getElementById('manual-domain-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') addBlockedDomain();
});
document.getElementById('btn-select').onclick = (e) => {
isSelecting = !isSelecting;
const btnScope = document.getElementById('btn-scope');
const btnManage = document.getElementById('btn-manage');
const panel = document.getElementById('hider-panel');
if (isSelecting) {
e.target.classList.add('active');
if (btnScope) btnScope.classList.add('active');
panel.style.display = 'none';
btnManage.classList.remove('active');
} else {
e.target.classList.remove('active');
if (btnScope) btnScope.classList.remove('active');
if (previewElement) {
previewElement.classList.remove('hider-preview-highlight');
previewElement = null;
}
}
broadcastState();
};
const btnScope = document.getElementById('btn-scope');
btnScope.onclick = () => {
currentScope = currentScope === 'site' ? 'link' : 'site';
btnScope.textContent = currentScope === 'site' ? 'SITE' : 'LINK';
btnScope.classList.toggle('scope-link', currentScope === 'link');
btnScope.classList.add('temp-expand');
showToast(`Scope set to: ${currentScope.toUpperCase()}`);
broadcastState();
setTimeout(() => {
btnScope.classList.remove('temp-expand');
btnScope.blur();
}, 1000);
};
const btnFreeze = document.getElementById('btn-freeze');
btnFreeze.onclick = () => {
isFrozen = !isFrozen;
CACHE.isFrozen = isFrozen;
GM_setValue('hider_freeze_global', isFrozen);
btnFreeze.classList.toggle('is-frozen', isFrozen);
btnFreeze.classList.add('temp-expand');
showToast(isFrozen ? '❄️ Global Freeze Mode: ON' : '🔥 Global Freeze Mode: OFF');
broadcastState();
setTimeout(() => {
btnFreeze.classList.remove('temp-expand');
btnFreeze.blur();
}, 1000);
};
document.getElementById('btn-manage').onclick = (e) => {
const panel = document.getElementById('hider-panel');
const btnSelect = document.getElementById('btn-select');
const btnScope = document.getElementById('btn-scope');
if (panel.style.display === 'flex') {
panel.style.display = 'none';
e.target.classList.remove('active');
} else {
panel.style.display = 'flex';
e.target.classList.add('active');
isSelecting = false;
btnSelect.classList.remove('active');
if (btnScope) btnScope.classList.remove('active');
if (previewElement) {
previewElement.classList.remove('hider-preview-highlight');
previewElement = null;
}
broadcastState();
renderList();
}
};
document.getElementById('close-p').onclick = () => {
document.getElementById('hider-panel').style.display = 'none';
document.getElementById('btn-manage').classList.remove('active');
};
document.getElementById('clear-log-btn').onclick = () => {
CACHE.logs = [];
GM_setValue('hider_global_logs', []);
renderList();
};
}
// --- Dynamic CSS Engine ---
function updateStyles() {
let styleEl = document.getElementById('hider-dynamic-styles');
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = 'hider-dynamic-styles';
(document.head || document.documentElement).appendChild(styleEl);
}
const siteData = GM_getValue('hider_site_' + window.location.hostname, []);
const linkData = GM_getValue('hider_link_' + window.location.href, []);
const combined = [...new Set([...siteData, ...linkData])];
const hideRules = combined
.filter(sel => sel && sel.trim() !== '')
.map(sel => `${sel} {
position: absolute !important;
top: -99999px !important;
left: -99999px !important;
width: 1px !important;
height: 1px !important;
opacity: 0.0001 !important;
pointer-events: none !important;
clip: rect(1px, 1px, 1px, 1px) !important;
clip-path: inset(50%) !important;
overflow: hidden !important;
max-width: 0px !important;
max-height: 0px !important;
margin: 0 !important;
padding: 0 !important;
}`)
.join('\n');
if (cachedCssString === hideRules && styleEl.textContent === hideRules) return;
cachedCssString = hideRules;
styleEl.textContent = hideRules;
}
function restoreSelector(selector) {
if (!selector) return;
try {
const els = document.querySelectorAll(selector);
const props = ['display', 'position', 'top', 'left', 'width', 'height', 'opacity', 'pointer-events', 'clip', 'clip-path', 'overflow', 'max-width', 'max-height', 'margin', 'padding'];
els.forEach(el => {
props.forEach(p => el.style.removeProperty(p));
});
} catch (err) {}
}
function renderList() {
checkAndClearDailyLogs();
const hostKey = window.location.hostname;
const siteKey = 'hider_site_' + hostKey;
const linkKey = 'hider_link_' + window.location.href;
const siteData = GM_getValue(siteKey, []);
const linkData = GM_getValue(linkKey, []);
if (!CACHE.logs) {
const raw = GM_getValue('hider_global_logs', []);
CACHE.logs = Array.isArray(raw) ? raw : [];
}
const cMemory = document.getElementById('freeze-memory-container');
const cDomains = document.getElementById('list-blocked-domains');
const cSite = document.getElementById('list-site');
const cLink = document.getElementById('list-link');
const cLog = document.getElementById('list-blocked-log');
if(!cSite || !cLink) return;
if (cDomains) {
cDomains.innerHTML = '';
if (CACHE.blockedDomainsList.length === 0) {
cDomains.innerHTML = `<div style="font-size:10px; color:#777;">None</div>`;
} else {
CACHE.blockedDomainsList.forEach((domain, i) => {
const div = document.createElement('div'); div.className = 'list-item';
div.innerHTML = `<span>${domain}</span> <button class="hider-btn-small" data-i="${i}">X</button>`;
div.querySelector('button').onclick = (e) => {
const removed = CACHE.blockedDomainsList.splice(e.target.dataset.i, 1)[0];
CACHE.blockedDomainsSet.delete(cleanDomain(removed));
GM_setValue('hider_blocked_domains', CACHE.blockedDomainsList);
renderList();
};
cDomains.appendChild(div);
});
}
}
if (cMemory) {
if (CACHE.autoNo) {
cMemory.innerHTML = `<span style="color:#10b981;">Always Auto-Block Ads</span> <button class="hider-btn-small" id="reset-mem">Reset</button>`;
} else {
cMemory.innerHTML = `<span style="color:#777;">Ask Every Time</span>`;
}
const resetBtn = document.getElementById('reset-mem');
if (resetBtn) {
resetBtn.onclick = () => {
GM_setValue('hider_autono_global', false);
CACHE.autoNo = false;
renderList();
};
}
}
if (cLog) {
cLog.innerHTML = '';
if (CACHE.logs.length === 0) {
cLog.innerHTML = `<div style="font-size:10px; color:#777; text-align:center;">No popups logged today</div>`;
} else {
CACHE.logs.forEach(item => {
const row = document.createElement('div');
row.style.cssText = 'font-size: 10px; border-bottom: 1px solid rgba(255,255,255,0.1); padding: 3px 0; display: flex; flex-direction: column;';
row.innerHTML = `
<div style="display:flex; justify-content:space-between; color:#a7f3d0; font-weight:bold;">
<span>${item.type}</span>
<span style="color:#9ca3af;">${item.time}</span>
</div>
<div style="color:#d1d5db; word-break:break-all; opacity:0.8;">${item.url}</div>
`;
cLog.appendChild(row);
});
}
}
cSite.innerHTML = ''; cLink.innerHTML = '';
if (siteData.length === 0) cSite.innerHTML = `<div style="font-size:10px; color:#777;">None</div>`;
siteData.forEach((sel, i) => {
const div = document.createElement('div'); div.className = 'list-item';
div.innerHTML = `<span>${sel.substring(0, 25)}...</span> <button class="hider-btn-small" data-i="${i}">X</button>`;
div.querySelector('button').onclick = (e) => {
const removed = siteData.splice(e.target.dataset.i, 1)[0];
GM_setValue(siteKey, siteData);
restoreSelector(removed);
updateStyles();
renderList();
};
cSite.appendChild(div);
});
if (linkData.length === 0) cLink.innerHTML = `<div style="font-size:10px; color:#777;">None</div>`;
linkData.forEach((sel, i) => {
const div = document.createElement('div'); div.className = 'list-item';
div.innerHTML = `<span>${sel.substring(0, 25)}...</span> <button class="hider-btn-small" data-i="${i}">X</button>`;
div.querySelector('button').onclick = (e) => {
const removed = linkData.splice(e.target.dataset.i, 1)[0];
GM_setValue(linkKey, linkData);
restoreSelector(removed);
updateStyles();
renderList();
};
cLink.appendChild(div);
});
}
// --- Navigation Intercept Handlers ---
function handleNavigationClick(e) {
if (isSelecting) return;
if (e.target.closest('#' + UI_HOST_ID) || e.target.closest('#hider-freeze-prompt')) return;
const link = e.target.closest('a[href], area[href]');
if (link) {
const href = link.getAttribute('href');
if (href && !href.startsWith('#') && !href.startsWith('javascript:')) {
const targetUrl = link.href;
if (isDomainBlocked(targetUrl)) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
logBlockedAttempt(targetUrl, 'Hard Blocked Website Click');
simulateAdWindowSuccess();
showToast('⛔ Force Blocked: Website in List');
return;
}
if (!isFrozen) return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const isNewTab = link.target === '_blank' || e.ctrlKey || e.metaKey || e.button === 1;
showFreezePrompt(targetUrl, 'Link Click', () => {
userApprovedNavigation = true;
if (isNewTab) {
win.open(targetUrl, '_blank');
} else {
win.location.href = targetUrl;
}
setTimeout(() => { userApprovedNavigation = false; }, 300);
}, () => {
simulateAdWindowSuccess();
});
}
}
}
// Capture Listener for Navigation & Selection
window.addEventListener('click', (e) => {
if (e.target.closest('#' + UI_HOST_ID) || e.target.closest('#hider-freeze-prompt')) return;
handleNavigationClick(e);
if (!isSelecting) return;
e.preventDefault();
e.stopPropagation();
if (previewElement === e.target) {
previewElement.classList.remove('hider-preview-highlight');
const selector = getExactSelector(e.target);
if (!selector) {
previewElement = null;
return;
}
if (currentScope === 'site') {
const key = 'hider_site_' + window.location.hostname;
const s = GM_getValue(key, []);
if (!s.includes(selector)) { s.push(selector); GM_setValue(key, s); }
} else {
const key = 'hider_link_' + window.location.href;
const s = GM_getValue(key, []);
if (!s.includes(selector)) { s.push(selector); GM_setValue(key, s); }
}
updateStyles();
previewElement = null;
} else {
if (previewElement) {
previewElement.classList.remove('hider-preview-highlight');
}
previewElement = e.target;
previewElement.classList.add('hider-preview-highlight');
}
}, true);
window.addEventListener('auxclick', handleNavigationClick, true);
window.addEventListener('submit', (e) => {
if (e.target.closest('#' + UI_HOST_ID) || e.target.closest('#hider-freeze-prompt')) return;
const form = e.target;
const action = form.action || window.location.href;
if (isDomainBlocked(action)) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
logBlockedAttempt(action, 'Hard Blocked Form Submission');
simulateAdWindowSuccess();
showToast('⛔ Force Blocked: Form Action');
return;
}
if (!isFrozen) return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
showFreezePrompt(action, 'Form Submit', () => {
userApprovedNavigation = true;
form.submit();
setTimeout(() => { userApprovedNavigation = false; }, 300);
}, () => {
simulateAdWindowSuccess();
});
}, true);
win.addEventListener('beforeunload', (e) => {
if (isFrozen && !userApprovedNavigation) {
e.preventDefault();
e.returnValue = 'Page navigation is currently frozen site-wide.';
return e.returnValue;
}
}, true);
// Context Menu Unlocker
function enableContextMenus() {
const blockEvents = ['contextmenu', 'selectstart', 'copy', 'paste', 'dragstart'];
blockEvents.forEach(evt => {
document.addEventListener(evt, (e) => {
e.stopPropagation();
}, true);
});
const cssUnlock = document.createElement('style');
cssUnlock.textContent = `* { -webkit-touch-callout: default !important; }`;
(document.head || document.documentElement).appendChild(cssUnlock);
}
// Lightweight Native Hook SPA Navigation Tracker
const checkUrlChange = () => {
if (window.location.href !== lastUrl) {
lastUrl = window.location.href;
updateStyles();
const panel = document.getElementById('hider-panel');
if (panel && panel.style.display === 'flex') renderList();
}
};
const origPushState = history.pushState;
if (origPushState) {
history.pushState = function() {
origPushState.apply(this, arguments);
checkUrlChange();
};
}
const origReplaceState = history.replaceState;
if (origReplaceState) {
history.replaceState = function() {
origReplaceState.apply(this, arguments);
checkUrlChange();
};
}
window.addEventListener('popstate', checkUrlChange, { passive: true });
window.addEventListener('hashchange', checkUrlChange, { passive: true });
// Initialization Handler & Extremely Lightweight DOM Observer
function initUI() {
enableContextMenus();
injectCoreStyles();
injectUI();
updateStyles();
// Optimized MutationObserver (Debounced & Lightweight, No Deep Tree Traversal)
let debouncedTimer = null;
const observer = new MutationObserver(() => {
if (debouncedTimer) return;
debouncedTimer = setTimeout(() => {
debouncedTimer = null;
if (isTop && !document.getElementById(UI_HOST_ID)) injectUI();
if (!document.getElementById('hider-dynamic-styles')) updateStyles();
if (!document.getElementById('hider-core-styles')) injectCoreStyles();
}, 1000);
});
if (document.documentElement) {
observer.observe(document.documentElement, { childList: true });
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initUI);
} else {
initUI();
}
})();