// ==UserScript== // @name 网页广告拦截器 // @namespace http://tampermonkey.net/ // @version 5.6.0 // @author DeepSeek&Gemini // @description 一个手机端浏览器能用的强大的广告拦截器 // @match *://*/* // @license MIT // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // @grant GM_notification // @grant GM_listValues // @grant GM_xmlhttpRequest // @run-at document-start // ==/UserScript== (function () { 'use strict'; (function () { try { var s = document.createElement('style'); s.setAttribute('data-adblock-hide-style', 'true'); s.textContent = '.adblock-universal-hidden{display:none!important;visibility:hidden!important;opacity:0!important;pointer-events:none!important;position:absolute!important;left:-9999px!important;top:-9999px!important;width:0!important;height:0!important;overflow:hidden!important;z-index:-999!important;}'; (document.documentElement || document).appendChild(s); } catch (e) { } })(); const DEFAULT_MODULE_STATE = { removeInlineScripts: false, removeExternalScripts: false, interceptThirdParty: false, blockDynamicScripts: false, manageCSP: false, scriptBlacklistMode: true, }; const DEFAULT_CONFIG_STATE = { inlineScriptStrictMode: false, dynamicScriptStrictMode: false, thirdPartyStrictMode: false, thirdPartyStrictMethod: false, spoofUAEnabled: false, simplePlatformSpoof: false, residualCleanupEnabled: false, cssUniversalHideEnabled: false, iframeUIFix: false, redirectBlockerEnabled: false, builtinBlacklistEnabled: true, redirectBlockerCompatibilityMode: false, }; const DEFAULT_CSP_RULES_TEMPLATE = [ { id: 1, name: '只允许同源外部脚本', rule: "script-src 'self'", enabled: false }, { id: 2, name: '只允许同源外部样式', rule: "style-src 'self'", enabled: false }, { id: 3, name: '只允许同源图片', rule: "img-src 'self'", enabled: false }, { id: 4, name: '禁止所有框架', rule: "frame-src 'none'", enabled: false }, { id: 5, name: '禁止所有媒体', rule: "media-src 'none'", enabled: false }, { id: 6, name: '禁止所有对象与嵌入', rule: "object-src 'none'", enabled: false }, ]; const BUILTIN_BLACKLIST_KEYWORDS = [ 'hm.baidu.com', 'tongji()', 'push.js', '{return void 0!==b[a]?b[a]:a}).join("")}', '${scripts[randomIndex]}', '${scripts[Math.random()', 'https://" + Date.parse(new Date())+', 'https://" + (new Date().getDate()) + ', 'https://{randomstr}.', 'new Function(t)()', 'new Function(b)()', 'new Function(c)()', 'new Function(t);', 'new Function(b);', 'new Function(c);', "new Function('d',e)", 'new Function(document[', 'new Function(function(p,a,c,k,e,d)', 'function a(a){', 'function b(b){', 'function c(c){', 'function updateCarousel()', 'Math.floor(2147483648 * Math.random());', 'Math.floor(Math.random()*url.length)', 'Math.floor(Math.random() * urls.length)', "new Date()['getTime']()", 'newDate=new window', 'Math.floor(((new Date()).getTime()', '&&navigator[', '=navigator;', 'navigator.platform){setTimeout(function', 'disableDebugger', 'blockDeveloperTools', '["Date"]())[\'getTime\']()', '\');', '<\\/\' + \'s\' + \'c\' + \'ri\' + \'pt\' + \'>\');', '(\'#htmlContenthtml\').html', 'D.createElement(\'span\');', 'window.$m(', '{win:false,mac:false,xll:false}', 'function|getDate', 'parseInt(Math[\'random\']' ]; let currentConfig = { ...DEFAULT_CONFIG_STATE, modules: { ...DEFAULT_MODULE_STATE }, cspRules: DEFAULT_CSP_RULES_TEMPLATE.map((rule) => ({ ...rule })), whitelist: new Set(), keywordWhitelist: new Set(), scriptBlacklist: new Set(), thirdPartyWhitelist: [], removedBuiltinKeywords: new Set(), }; const ConfigUpdater = { _saveTimer: null, _debounceDelay: 150, set(key, value) { const keys = key.split('.'); let obj = currentConfig; for (let i = 0; i < keys.length - 1; i++) { obj = obj[keys[i]]; } obj[keys[keys.length - 1]] = value; this.scheduleSave(); }, scheduleSave() { if (this._saveTimer) clearTimeout(this._saveTimer); this._saveTimer = setTimeout(() => { StorageManager.saveConfig(); this._saveTimer = null; }, this._debounceDelay); }, saveNow() { if (this._saveTimer) { clearTimeout(this._saveTimer); this._saveTimer = null; } StorageManager.saveConfig(); }, }; const StorageManager = { getConfigKey(domain) { return `adblock_unified_config_${domain}`; }, loadConfig() { const hostname = location.hostname; const key = this.getConfigKey(hostname); try { const saved = GM_getValue(key, null); if (saved) { const data = JSON.parse(saved); if (data.modules) Object.assign(currentConfig.modules, data.modules); if (data.cspRules) currentConfig.cspRules = data.cspRules.map((r) => ({ ...r })); if (Array.isArray(data.whitelist)) currentConfig.whitelist = new Set(data.whitelist); if (Array.isArray(data.keywordWhitelist)) currentConfig.keywordWhitelist = new Set(data.keywordWhitelist); if (Array.isArray(data.scriptBlacklist)) currentConfig.scriptBlacklist = new Set(data.scriptBlacklist); if (Array.isArray(data.thirdPartyWhitelist)) currentConfig.thirdPartyWhitelist = data.thirdPartyWhitelist; if (Array.isArray(data.removedBuiltinKeywords)) currentConfig.removedBuiltinKeywords = new Set( data.removedBuiltinKeywords ); Object.keys(DEFAULT_CONFIG_STATE).forEach((configKey) => { if (data[configKey] !== undefined) { currentConfig[configKey] = data[configKey]; } else if (currentConfig[configKey] === undefined) { currentConfig[configKey] = DEFAULT_CONFIG_STATE[configKey]; } }); } } catch (e) { console.warn( '[广告拦截器] 配置加载失败,已回退默认值:', e.message || e ); try { GM_deleteValue(key); } catch (e2) { } } const legacySpoof = GM_getValue('spoofUAEnabled', null); if (legacySpoof !== null) { currentConfig.spoofUAEnabled = legacySpoof; GM_deleteValue('spoofUAEnabled'); this.saveConfig(); } }, saveConfig() { const hostname = location.hostname; const key = this.getConfigKey(hostname); const toStore = { modules: currentConfig.modules, cspRules: currentConfig.cspRules, whitelist: Array.from(currentConfig.whitelist), keywordWhitelist: Array.from(currentConfig.keywordWhitelist), scriptBlacklist: Array.from(currentConfig.scriptBlacklist), thirdPartyWhitelist: currentConfig.thirdPartyWhitelist, removedBuiltinKeywords: Array.from(currentConfig.removedBuiltinKeywords), }; Object.keys(DEFAULT_CONFIG_STATE).forEach((k) => { toStore[k] = currentConfig[k]; }); GM_setValue(key, JSON.stringify(toStore)); }, resetAllSettings() { currentConfig.modules = { ...DEFAULT_MODULE_STATE }; currentConfig.cspRules = DEFAULT_CSP_RULES_TEMPLATE.map((rule) => ({ ...rule, })); currentConfig.whitelist.clear(); currentConfig.keywordWhitelist.clear(); currentConfig.scriptBlacklist.clear(); currentConfig.thirdPartyWhitelist = []; currentConfig.removedBuiltinKeywords.clear(); Object.keys(DEFAULT_CONFIG_STATE).forEach((k) => { currentConfig[k] = DEFAULT_CONFIG_STATE[k]; }); this.saveConfig(); }, }; StorageManager.loadConfig(); const CONFIG = { Z_INDEX: 2147483640, CACHE_TTL: 300000, BATCH_SIZE: 20, LOG_MAX: 50, LOG_IDENTIFIER_TTL: 300000, STRONG_BLOCK_TIMEOUT: 600000, DEBOUNCE_WAIT: 100, THROTTLE_LIMIT: 100, RESIDUAL_MIN_WIDTH: 30, RESIDUAL_MIN_HEIGHT: 30, CONTENT_PREVIEW_LENGTH: 200, LOG_DETAIL_LENGTH: 500, TOOLTIP_GAP: 12, ZINDEX_HIDE_THRESHOLD: 600, FIXED_HEIGHT_THRESHOLD: 50, CONTAINER_MIN_VISIBLE: 10, LAZY_PLACEHOLDER_MAX_LEN: 200, OVERLAY_MIN_SIZE: 30, DYNAMIC_URL_SHORT_TTL: 30000, INVALID_HOSTNAME_TTL: 30000, DATA_URL_TTL: 60000, IDLE_SCAN_INTERVAL: 3000, BATCH_SIZE_CLEANUP: 30, PAGE_SIZE: 20, CACHE_CAPACITY_URL: 1000, CACHE_CAPACITY_SMALL: 500, ANCHOR_CLICK_THROTTLE: 16, URL_DYNAMIC_PATTERNS: [ /\?.*[tT]=/, /\?.*timestamp/, /\?.*rand/, /\?.*rnd/, /\?.*[0-9]{13,}/, /\?.*\d{10,}/, /\d{10,}\./, /[0-9a-f]{32,}\./, ], }; const _globals = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; const _document = _globals.document; const _location = _globals.location; const _MutationObserver = _globals.MutationObserver; const _Element = _globals.Element; const _Node = _globals.Node; const _setTimeout = _globals.setTimeout; const _clearTimeout = _globals.clearTimeout; const _requestAnimationFrame = _globals.requestAnimationFrame; const _cancelAnimationFrame = _globals.cancelAnimationFrame; const _XMLHttpRequest = _globals.XMLHttpRequest; const _fetch = _globals.fetch; const _Proxy = _globals.Proxy; const _Set = _globals.Set; const _Map = _globals.Map; const _IntersectionObserver = _globals.IntersectionObserver; const _AbortController = _globals.AbortController; const _requestIdleCallback = _globals.requestIdleCallback || function (cb) { return _setTimeout(() => cb({ timeRemaining: () => 50, didTimeout: false }), 1); }; const _cancelIdleCallback = _globals.cancelIdleCallback || _clearTimeout; function escapeHtml(unsafe) { if (unsafe == null) return ''; return String(unsafe) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/`/g, '`') .replace(/\//g, '\\/'); } function generateContentHash(str) { if (typeof str !== 'string') return ''; let hash = 0x811c9dc5; for (let i = 0; i < str.length; i++) { hash ^= str.charCodeAt(i); hash = (hash * 0x01000193) >>> 0; } return 'hash_' + (hash >>> 0).toString(36); } class LRUCache { constructor(capacity, defaultTTL) { this.capacity = capacity || 100; this.defaultTTL = defaultTTL || 0; this.cache = new _Map(); } get(key) { if (!this.cache.has(key)) return null; const entry = this.cache.get(key); const ttl = entry.ttl !== undefined ? entry.ttl : this.defaultTTL; if (ttl > 0 && Date.now() - entry.timestamp > ttl) { this.cache.delete(key); return null; } this.cache.delete(key); this.cache.set(key, entry); return entry.value; } set(key, value, ttl) { const finalTTL = ttl !== undefined ? ttl : this.defaultTTL; const entry = { value: value, timestamp: Date.now(), ttl: finalTTL }; if (this.cache.has(key)) { this.cache.delete(key); } else if (this.cache.size >= this.capacity) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } this.cache.set(key, entry); } has(key) { const entry = this.cache.get(key); if (!entry) return false; const ttl = entry.ttl !== undefined ? entry.ttl : this.defaultTTL; if (ttl > 0 && Date.now() - entry.timestamp > ttl) { this.cache.delete(key); return false; } return true; } delete(key) { return this.cache.delete(key); } clear() { this.cache.clear(); } get size() { return this.cache.size; } } const PUBLIC_SUFFIX_LIST = new Set([ 'co.uk', 'org.uk', 'me.uk', 'ltd.uk', 'plc.uk', 'net.uk', 'sch.uk', 'ac.uk', 'gov.uk', 'com.cn', 'net.cn', 'org.cn', 'gov.cn', 'edu.cn', 'co.jp', 'ne.jp', 'or.jp', 'go.jp', 'ac.jp', 'ad.jp', 'com.au', 'net.au', 'org.au', 'edu.au', 'gov.au', 'asn.au', 'id.au', 'co.nz', 'org.nz', 'net.nz', 'edu.nz', 'govt.nz', 'com.hk', 'org.hk', 'edu.hk', 'gov.hk', 'net.hk', 'com.tw', 'net.tw', 'org.tw', 'edu.tw', 'gov.tw', 'idv.tw', 'game.tw', 'ebiz.tw', 'club.tw' ]); const COMMON_SLD_HEURISTIC = ['com', 'co', 'org', 'net', 'edu', 'gov', 'ac', 'go']; class URLResolutionCache { constructor() { this.hostnameCache = new LRUCache(CONFIG.CACHE_CAPACITY_URL, CONFIG.CACHE_TTL); this.domainCache = new LRUCache(CONFIG.CACHE_CAPACITY_URL, CONFIG.CACHE_TTL); this.absoluteUrlCache = new LRUCache(CONFIG.CACHE_CAPACITY_URL, CONFIG.CACHE_TTL); this.thirdPartyCache = new LRUCache(CONFIG.CACHE_CAPACITY_URL, CONFIG.CACHE_TTL); this.whitelistCache = new LRUCache(CONFIG.CACHE_CAPACITY_SMALL, CONFIG.CACHE_TTL); this.urlCheckCache = new LRUCache(CONFIG.CACHE_CAPACITY_SMALL, CONFIG.CACHE_TTL); } isDynamicURL(url) { if (!url || typeof url !== 'string') return false; const cacheKey = 'dynamic_' + url; if (this.urlCheckCache.has(cacheKey)) return this.urlCheckCache.get(cacheKey); const result = CONFIG.URL_DYNAMIC_PATTERNS.some(function (pattern) { return pattern.test(url); }); this.urlCheckCache.set(cacheKey, result, CONFIG.CACHE_TTL); return result; } getHostname(url) { if (!url || typeof url !== 'string') return null; const cacheKey = 'hostname_' + url; if (this.hostnameCache.has(cacheKey)) return this.hostnameCache.get(cacheKey); if ( url.startsWith('data:') || url.startsWith('blob:') || url.startsWith('about:blank') ) { this.hostnameCache.set(cacheKey, null, CONFIG.DATA_URL_TTL); return null; } try { const hostname = new URL(url, _location.href).hostname; this.hostnameCache.set(cacheKey, hostname, CONFIG.CACHE_TTL); return hostname; } catch (e) { this.hostnameCache.set(cacheKey, null, CONFIG.INVALID_HOSTNAME_TTL); return null; } } isIPv4(hostname) { return /^(\d{1,3}\.){3}\d{1,3}$/.test(hostname); } getDomain(hostname) { if (!hostname) return null; const cacheKey = 'domain_' + hostname; if (this.domainCache.has(cacheKey)) return this.domainCache.get(cacheKey); if (this.isIPv4(hostname)) { this.domainCache.set(cacheKey, hostname, CONFIG.CACHE_TTL); return hostname; } const parts = hostname.split('.'); let domain = hostname; for (let i = 1; i <= parts.length; i++) { const candidate = parts.slice(-i).join('.'); if (PUBLIC_SUFFIX_LIST.has(candidate)) { if (i + 1 <= parts.length) { domain = parts.slice(-(i + 1)).join('.'); } else { domain = candidate; } break; } } if (domain === hostname && parts.length > 2) { const tld = parts[parts.length - 1]; const sld = parts[parts.length - 2]; const isCountryCode = /^[a-z]{2}$/i.test(tld); if (isCountryCode && COMMON_SLD_HEURISTIC.indexOf(sld) !== -1) { domain = parts.slice(-3).join('.'); } else { domain = parts.slice(-2).join('.'); } } this.domainCache.set(cacheKey, domain, CONFIG.CACHE_TTL); return domain; } getAbsoluteURL(url) { if (!url) return ''; const cacheKey = 'absolute_' + url + '_' + _location.href; if (this.absoluteUrlCache.has(cacheKey)) return this.absoluteUrlCache.get(cacheKey); try { const absoluteUrl = new URL(url, _location.href).href; const ttl = this.isDynamicURL(url) ? CONFIG.DYNAMIC_URL_SHORT_TTL : CONFIG.CACHE_TTL; this.absoluteUrlCache.set(cacheKey, absoluteUrl, ttl); return absoluteUrl; } catch (e) { this.absoluteUrlCache.set(cacheKey, url, CONFIG.INVALID_HOSTNAME_TTL); return url; } } isThirdPartyHost(resourceHostname, currentHost, blockParentSubDomains) { if (!resourceHostname) return false; const cacheKey = 'thirdparty_' + resourceHostname + '_' + currentHost + '_' + blockParentSubDomains; if (this.thirdPartyCache.has(cacheKey)) return this.thirdPartyCache.get(cacheKey); let isThirdParty = false; if (!currentHost || !resourceHostname) { isThirdParty = false; } else if (resourceHostname === currentHost) { isThirdParty = false; } else if (blockParentSubDomains) { isThirdParty = true; } else { const currentDomain = this.getDomain(currentHost); const resourceDomain = this.getDomain(resourceHostname); isThirdParty = currentDomain !== resourceDomain; } this.thirdPartyCache.set(cacheKey, isThirdParty, CONFIG.CACHE_TTL); return isThirdParty; } isWhitelisted(url, thirdPartyWhitelist) { if (!url || !thirdPartyWhitelist) return false; const cacheKey = 'whitelist_' + url + '_' + _location.hostname; if (this.whitelistCache.has(cacheKey)) return this.whitelistCache.get(cacheKey); let isWhitelisted = false; for (const pattern of thirdPartyWhitelist) { if (!pattern) continue; try { if (pattern.includes('://')) { if (url.includes(pattern)) { isWhitelisted = true; break; } } else { const urlHost = new URL(url, _location.href).hostname; let patternHost = pattern.startsWith('*.') ? pattern.substring(2) : pattern; const escapeRegExp = function (str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }; patternHost = escapeRegExp(patternHost); const regex = new RegExp('(^|\\.)' + patternHost + '$'); if (regex.test(urlHost)) { isWhitelisted = true; break; } } } catch (e) { if (url.includes(pattern)) { isWhitelisted = true; break; } } } this.whitelistCache.set(cacheKey, isWhitelisted, CONFIG.CACHE_TTL); return isWhitelisted; } clear() { this.hostnameCache.clear(); this.domainCache.clear(); this.absoluteUrlCache.clear(); this.thirdPartyCache.clear(); this.whitelistCache.clear(); this.urlCheckCache.clear(); } } const urlCache = new URLResolutionCache(); const ProcessedElementsCache = { _processedElements: new WeakSet(), isProcessed(element) { if (!Utils || !Utils.isElement) return false; if (!Utils.isElement(element)) return false; return this._processedElements.has(element); }, markAsProcessed(element) { if (!Utils || !Utils.isElement) return; if (!Utils.isElement(element)) return; this._processedElements.add(element); }, clear() { this._processedElements = new WeakSet(); }, }; const MODULE_NAMES = { removeInlineScripts: '移除内嵌脚本', removeExternalScripts: '移除外联脚本', blockDynamicScripts: '拦截动态脚本', interceptThirdParty: '拦截第三方资源', manageCSP: 'CSP策略管理', scriptBlacklistMode: '脚本黑名单模式', }; const Utils = { truncateString: function (str, maxLength) { maxLength = maxLength || CONFIG.CONTENT_PREVIEW_LENGTH; if (typeof str !== 'string') return ''; if (str.length <= maxLength) return str; return str.slice(0, maxLength) + '...'; }, getCurrentHostname: function () { return _location.hostname; }, isElement: function (el) { return el instanceof _Element; }, getScriptContentPreview: function (scriptElement) { if (!scriptElement || scriptElement.tagName !== 'SCRIPT') return ''; return this.truncateString( scriptElement.textContent, CONFIG.CONTENT_PREVIEW_LENGTH ); }, getIframeSrcPreview: function (iframeElement) { if (!iframeElement || iframeElement.tagName !== 'IFRAME') return ''; return this.truncateString(iframeElement.src, CONFIG.CONTENT_PREVIEW_LENGTH); }, getResourceHostname: function (url) { return urlCache.getHostname(url); }, getDomain: function (hostname) { return urlCache.getDomain(hostname); }, isThirdPartyHost: function (resourceHostname, currentHost, blockParentSubDomains) { return urlCache.isThirdPartyHost( resourceHostname, currentHost, blockParentSubDomains ); }, isThirdParty: function (url, blockParentSubDomains) { blockParentSubDomains = blockParentSubDomains !== undefined ? blockParentSubDomains : true; if (!url) return false; const cacheKey = 'isThirdParty_' + url + '_' + _location.hostname + '_' + blockParentSubDomains; if (urlCache.urlCheckCache.has(cacheKey)) return urlCache.urlCheckCache.get(cacheKey); try { const urlObj = new URL(url, _location.href); const hostname = urlObj.hostname; if (!hostname || url.startsWith('data:') || url.startsWith('blob:')) { urlCache.urlCheckCache.set(cacheKey, false, CONFIG.CACHE_TTL); return false; } const result = urlCache.isThirdPartyHost( hostname, this.getCurrentHostname(), blockParentSubDomains ); urlCache.urlCheckCache.set(cacheKey, result, CONFIG.CACHE_TTL); return result; } catch (e) { urlCache.urlCheckCache.set(cacheKey, false, CONFIG.CACHE_TTL); return false; } }, shouldInterceptByModule: function (element, moduleKey) { if (!currentConfig.modules[moduleKey]) return false; if ( ProcessedElementsCache.isProcessed(element) || this.isParentProcessed(element) ) return false; if (Whitelisting.isElementWhitelisted(element)) return false; return true; }, getBlockParentSubDomainsSetting: function () { return !!currentConfig.thirdPartyStrictMode; }, isUIElement: function (el) { return ( el && el.classList && (el.classList.contains('mask') || el.classList.contains('panel') || el.id === 'ad-blocker-settings-container') ); }, isPanelElement: function (el) { return el && el.classList && el.classList.contains('panel'); }, isPanelClick: function (event) { const path = event.composedPath(); return path.some(function (el) { return Utils.isUIElement(el); }); }, isContainerEmpty: function (container, ignoreProcessedFlag) { ignoreProcessedFlag = ignoreProcessedFlag !== undefined ? ignoreProcessedFlag : true; if (!this.isElement(container)) return false; if ( container.children.length === 0 && container.textContent.trim() === '' ) { return this.isSuspiciousAdContainer(container); } let hasVisibleContent = false; for (const child of container.childNodes) { if (child.nodeType === _Node.TEXT_NODE && child.textContent.trim().length > 0) { hasVisibleContent = true; break; } if (child.nodeType === _Node.ELEMENT_NODE) { const el = child; if (ignoreProcessedFlag && ProcessedElementsCache.isProcessed(el)) continue; const style = getComputedStyle(el); if ( style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' ) { if ( el.tagName === 'IMG' || el.tagName === 'VIDEO' || el.tagName === 'CANVAS' || (el.textContent && el.textContent.trim().length > 0) ) { hasVisibleContent = true; break; } if ( style.opacity !== '0' && style.visibility !== 'hidden' && el.offsetWidth > 0 && el.offsetHeight > 0 && el.tagName !== 'BR' && el.tagName !== 'HR' ) { hasVisibleContent = true; break; } } } } return !hasVisibleContent; }, isSuspiciousAdContainer: function (container) { if (!this.isElement(container)) return false; const style = getComputedStyle(container); const hasVisualStyling = (style.backgroundColor !== 'rgba(0, 0, 0, 0)' && style.backgroundColor !== 'transparent') || style.backgroundImage !== 'none' || style.borderWidth !== '0px' || style.boxShadow !== 'none'; const hasSuspiciousPosition = style.position === 'absolute' || style.position === 'fixed' || style.position === 'sticky'; const hasSuspiciousSize = parseFloat(style.width) > CONFIG.RESIDUAL_MIN_WIDTH && parseFloat(style.height) > CONFIG.RESIDUAL_MIN_HEIGHT; const hasSpacing = style.paddingTop !== '0px' || style.paddingBottom !== '0px' || style.marginTop !== '0px' || style.marginBottom !== '0px'; const relativeWithStyle = style.position === 'relative' && hasVisualStyling && (hasSuspiciousSize || hasSpacing); const isVisible = style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'; return ( isVisible && (hasSuspiciousPosition || hasSuspiciousSize || hasSpacing || relativeWithStyle) ); }, throttle: function (func, limit) { limit = limit || CONFIG.THROTTLE_LIMIT; let inThrottle; return function () { const args = arguments; const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; _setTimeout(function () { inThrottle = false; }, limit); } }; }, isParentProcessed: function (element) { let parent = element.parentElement; while (parent) { if (ProcessedElementsCache.isProcessed(parent)) return true; parent = parent.parentElement; } return false; }, getActiveBlacklistSet: function () { const set = new Set(currentConfig.scriptBlacklist); if (currentConfig.builtinBlacklistEnabled) { BUILTIN_BLACKLIST_KEYWORDS.forEach(function (k) { if (!currentConfig.removedBuiltinKeywords.has(k)) set.add(k); }); } return set; }, getActiveBlacklistArray: function () { return Array.from(this.getActiveBlacklistSet()); }, getContentIdentifier: function (element, reasonType) { if (!element && !reasonType) return null; if (element && this.isElement(element)) { const tagName = element.tagName; const src = element.src || element.getAttribute('data-src') || element.href || element.action || ''; if (tagName === 'SCRIPT') return element.src ? 'SCRIPT_SRC: ' + this.truncateString(element.src, CONFIG.LOG_DETAIL_LENGTH) : 'SCRIPT_CONTENT: ' + this.truncateString(element.textContent, CONFIG.LOG_DETAIL_LENGTH); if (tagName === 'IFRAME') return 'IFRAME_SRC: ' + this.truncateString(element.src, CONFIG.LOG_DETAIL_LENGTH); if (tagName === 'IMG') return src ? 'IMG_SRC: ' + this.truncateString(src, CONFIG.LOG_DETAIL_LENGTH) : null; if (tagName === 'A') return src ? 'A_HREF: ' + this.truncateString(src, CONFIG.LOG_DETAIL_LENGTH) : null; if (tagName === 'LINK' && element.rel === 'stylesheet' && element.href) return 'CSS_HREF: ' + this.truncateString(element.href, CONFIG.LOG_DETAIL_LENGTH); if (tagName === 'STYLE') return 'STYLE_CONTENT: ' + this.truncateString(element.textContent, CONFIG.LOG_DETAIL_LENGTH); if (tagName === 'EMBED') return src ? 'EMBED_SRC: ' + this.truncateString(src, CONFIG.LOG_DETAIL_LENGTH) : null; if (tagName === 'OBJECT') return src ? 'OBJECT_DATA: ' + this.truncateString(src, CONFIG.LOG_DETAIL_LENGTH) : null; return null; } else if (reasonType && typeof reasonType.detail === 'string') { if (reasonType.detail.startsWith('SRC:')) return ( (reasonType.type || 'INTERCEPTED') + '_SRC: ' + this.truncateString(reasonType.detail.substring(4).trim(), CONFIG.LOG_DETAIL_LENGTH) ); if (reasonType.detail.startsWith('URL:')) return ( (reasonType.type || 'INTERCEPTED') + '_URL: ' + this.truncateString(reasonType.detail.substring(5).trim(), CONFIG.LOG_DETAIL_LENGTH) ); if (reasonType.type === 'EVAL') return 'EVAL_HASH: ' + generateContentHash(reasonType.detail); if (reasonType.type === 'FUNCTION_CONSTRUCTOR') return 'FUNCTION_HASH: ' + generateContentHash(reasonType.detail); if (reasonType.type === 'DOCUMENT_WRITE') return 'DOCUMENT_WRITE_HASH: ' + generateContentHash(reasonType.detail); if (reasonType.type === 'SETTIMEOUT') return 'SETTIMEOUT_HASH: ' + generateContentHash(reasonType.detail); if (reasonType.type === 'SETINTERVAL') return 'SETINTERVAL_HASH: ' + generateContentHash(reasonType.detail); if (reasonType.type === 'REQUESTANIMATIONFRAME') return 'REQUESTANIMATIONFRAME_HASH: ' + generateContentHash(reasonType.detail); if (reasonType.type === 'THIRD_PARTY') { const urlMatch = reasonType.detail.match(/(https?:\/\/[^\s]+)/); if (urlMatch) return 'THIRD_PARTY_URL: ' + this.truncateString(urlMatch[1], CONFIG.LOG_DETAIL_LENGTH); return 'THIRD_PARTY_DETAIL: ' + this.truncateString(reasonType.detail, CONFIG.LOG_DETAIL_LENGTH); } if (reasonType.type === 'SCRIPT_BLACKLIST') return 'BLACKLIST: ' + this.truncateString(reasonType.detail, CONFIG.LOG_DETAIL_LENGTH); if (reasonType.type === '内联事件') return 'INLINE_EVENT: ' + reasonType.detail; if (reasonType.type === 'javascript URL') return 'JAVASCRIPT_URL: ' + reasonType.detail; return 'LOG_DETAIL: ' + this.truncateString(reasonType.detail, CONFIG.LOG_DETAIL_LENGTH); } return null; }, }; function shouldBlockResource(url) { if (!url) return false; if (urlCache.isWhitelisted(url, currentConfig.thirdPartyWhitelist)) return false; for (const keyword of currentConfig.keywordWhitelist) { if (url.includes(keyword)) return false; } return Utils.isThirdParty(url, Utils.getBlockParentSubDomainsSetting()); } function parseLogContext(element, reason) { let elementIdentifier = '[未知元素]'; let interceptedContent = '[无法获取内容]'; let contentIdentifier = null; let resourceDomain = ''; const hashTypes = [ 'EVAL', 'FUNCTION_CONSTRUCTOR', 'DOCUMENT_WRITE', 'SETTIMEOUT', 'SETINTERVAL', 'REQUESTANIMATIONFRAME', ]; if (reason && typeof reason.detail === 'string') { const isSpecificType = [ '内联事件', 'javascript URL', ...hashTypes, 'THIRD_PARTY', 'SCRIPT_BLACKLIST', 'THIRD_PARTY_SCAN', ].includes(reason.type); if (isSpecificType || !Utils.isElement(element)) { interceptedContent = Utils.truncateString(reason.detail, CONFIG.LOG_DETAIL_LENGTH); elementIdentifier = reason.type ? '[' + reason.type + ']' : '[未知类型]'; if (hashTypes.includes(reason.type)) { contentIdentifier = Utils.getContentIdentifier(null, { type: reason.type, detail: reason.rawDetail || reason.detail, }); } else { contentIdentifier = Utils.getContentIdentifier(null, reason); } if (reason.detail.includes('://')) { try { const urlMatch = reason.detail.match(/https?:\/\/[^\s]+/); if (urlMatch) resourceDomain = Utils.getResourceHostname(urlMatch[0]) || ''; } catch (e) { } } if (isSpecificType) return { elementIdentifier, interceptedContent, contentIdentifier, resourceDomain }; } } if (Utils.isElement(element)) { const tagName = element.tagName; const id = element.id ? '#' + element.id : ''; const className = element.className ? '.' + element.className.split(/\s+/).join('.') : ''; elementIdentifier = tagName + id + className; contentIdentifier = Utils.getContentIdentifier(element); const tagHandlers = { SCRIPT: () => { if (element.src) { interceptedContent = '外联脚本: ' + Utils.truncateString(element.src, CONFIG.LOG_DETAIL_LENGTH); resourceDomain = Utils.getResourceHostname(element.src) || ''; } else { interceptedContent = '内嵌脚本: ' + Utils.getScriptContentPreview(element); } }, IFRAME: () => { interceptedContent = Utils.getIframeSrcPreview(element); if (element.src) resourceDomain = Utils.getResourceHostname(element.src) || ''; }, IMG: () => { const src = element.src || element.dataset.src || element.getAttribute('data-src') || ''; interceptedContent = Utils.truncateString(src, CONFIG.LOG_DETAIL_LENGTH); if (src) resourceDomain = Utils.getResourceHostname(src) || ''; }, A: () => { interceptedContent = Utils.truncateString(element.href || '', CONFIG.LOG_DETAIL_LENGTH); if (element.href) resourceDomain = Utils.getResourceHostname(element.href) || ''; }, LINK: () => { interceptedContent = Utils.truncateString(element.href || '', CONFIG.LOG_DETAIL_LENGTH); if (element.href) resourceDomain = Utils.getResourceHostname(element.href) || ''; }, STYLE: () => { interceptedContent = Utils.truncateString(element.textContent, CONFIG.LOG_DETAIL_LENGTH); }, EMBED: () => { interceptedContent = Utils.truncateString(element.src || '', CONFIG.LOG_DETAIL_LENGTH); if (element.src) resourceDomain = Utils.getResourceHostname(element.src) || ''; }, OBJECT: () => { interceptedContent = Utils.truncateString(element.data || '', CONFIG.LOG_DETAIL_LENGTH); if (element.data) resourceDomain = Utils.getResourceHostname(element.data) || ''; }, }; if (tagHandlers[tagName]) tagHandlers[tagName](); else interceptedContent = Utils.truncateString(element.outerHTML, CONFIG.LOG_DETAIL_LENGTH); } return { elementIdentifier, interceptedContent, contentIdentifier, resourceDomain }; } const LogManager = { logs: [], maxLogs: CONFIG.LOG_MAX, loggedContentIdentifiers: new LRUCache(CONFIG.LOG_MAX, CONFIG.LOG_IDENTIFIER_TTL), add: function (moduleKey, element, reason) { const anyModuleEnabled = Object.values(currentConfig.modules).some(function (v) { return v === true; }); if (!anyModuleEnabled) return; if (!Utils.isElement(element) && element !== null && typeof reason !== 'object') return; if ( Whitelisting.isElementWhitelisted(element) || Whitelisting.isReasonWhitelisted(reason) ) return; const ctx = parseLogContext(element, reason); const { elementIdentifier, interceptedContent, contentIdentifier, resourceDomain } = ctx; if (!contentIdentifier) return; const existingIndex = this.logs.findIndex(function (l) { return l.contentIdentifier === contentIdentifier; }); if (existingIndex !== -1) { this.logs.splice(existingIndex, 1); } else if (this.loggedContentIdentifiers.has(contentIdentifier)) { return; } this.logs.push({ id: this.logs.length + 1, moduleKey: moduleKey, module: MODULE_NAMES[moduleKey] || moduleKey, element: elementIdentifier, content: interceptedContent, domain: resourceDomain, timestamp: Date.now(), contentIdentifier: contentIdentifier, }); this.loggedContentIdentifiers.set(contentIdentifier, true); if (this.logs.length > this.maxLogs) { const removed = this.logs.shift(); this.loggedContentIdentifiers.delete(removed.contentIdentifier); } }, clearLoggedIdentifiers: function () { this.loggedContentIdentifiers.clear(); }, }; const Whitelisting = { isElementWhitelisted: function (element) { if (!element || !Utils.isElement(element)) return false; const ci = Utils.getContentIdentifier(element); if (ci && currentConfig.whitelist.has(ci)) return true; if (currentConfig.modules.interceptThirdParty) { const hostname = Utils.getResourceHostname( element.src || element.href || element.action || element.data || '' ); if (hostname) { const ru = element.src || element.href || element.action || element.data || ''; if (urlCache.isWhitelisted(ru, currentConfig.thirdPartyWhitelist)) return true; } } if (currentConfig.keywordWhitelist.size > 0) { const sc = element.textContent || ''; const src = element.src || element.href || element.action || element.data || ''; for (const kw of currentConfig.keywordWhitelist) { if (!kw) continue; if (sc.includes(kw) || src.includes(kw)) return true; } } return false; }, isReasonWhitelisted: function (reason) { if (!reason || typeof reason.detail !== 'string') return false; let ci; if ( reason.type === 'EVAL' || reason.type === 'FUNCTION_CONSTRUCTOR' || reason.type === 'DOCUMENT_WRITE' || reason.type === 'SETTIMEOUT' || reason.type === 'SETINTERVAL' || reason.type === 'REQUESTANIMATIONFRAME' ) { ci = Utils.getContentIdentifier(null, { type: reason.type, detail: reason.rawDetail || reason.detail, }); } else { ci = Utils.getContentIdentifier(null, reason); } if (ci && currentConfig.whitelist.has(ci)) return true; if (currentConfig.modules.interceptThirdParty) { const um = reason.detail.match(/https?:\/\/[^\s]+/); if (um) { if (urlCache.isWhitelisted(um[0], currentConfig.thirdPartyWhitelist)) return true; } } if (currentConfig.keywordWhitelist.size > 0) { for (const kw of currentConfig.keywordWhitelist) { if (!kw) continue; if (reason.detail.includes(kw)) return true; } } return false; }, isCodeWhitelisted: function (code, type) { if (typeof code !== 'string' || code.trim() === '') return false; if (currentConfig.keywordWhitelist.size > 0) { for (const kw of currentConfig.keywordWhitelist) { if (!kw) continue; if (code.includes(kw)) return true; } } const ci = Utils.getContentIdentifier(null, { type: type, detail: code }); if (ci && currentConfig.whitelist.has(ci)) return true; return false; }, add: function (contentIdentifier) { if (!contentIdentifier || contentIdentifier.trim() === '') return; currentConfig.whitelist.add(contentIdentifier); ConfigUpdater.saveNow(); }, addKeyword: function (keyword) { if (!keyword || keyword.trim() === '') return; currentConfig.keywordWhitelist.add(keyword.trim()); ConfigUpdater.saveNow(); }, removeKeywordsMatchingDomain: function (domain) { let changed = false; const toRemove = []; for (const kw of currentConfig.keywordWhitelist) { if (domain.includes(kw)) toRemove.push(kw); } toRemove.forEach(function (k) { currentConfig.keywordWhitelist.delete(k); changed = true; }); if (changed) ConfigUpdater.saveNow(); }, clearAllWhitelists: function () { currentConfig.whitelist.clear(); currentConfig.keywordWhitelist.clear(); currentConfig.thirdPartyWhitelist = []; ConfigUpdater.saveNow(); }, }; function getLogWhitelistStatus(log) { if (currentConfig.whitelist.has(log.contentIdentifier)) return 'whitelisted'; const kws = Array.from(currentConfig.keywordWhitelist); if ( kws.some(function (kw) { return (log.content && log.content.includes(kw)) || (log.domain && log.domain.includes(kw)); }) ) return 'keyword-whitelisted'; if (log.domain && urlCache.isWhitelisted(log.domain, currentConfig.thirdPartyWhitelist)) return 'whitelisted'; return ''; } function isThirdPartyUrl(url) { if (!url || typeof url !== 'string') return false; if (url.startsWith('#') || url.startsWith('javascript:')) return false; try { const targetUrl = new URL(url, _location.href); const currentHost = _location.hostname; if (targetUrl.hostname === currentHost) return false; return urlCache.isThirdPartyHost( targetUrl.hostname, currentHost, !!currentConfig.thirdPartyStrictMode ); } catch (e) { return false; } } if ( _document.designMode === 'on' || _document.documentElement.style.pointerEvents === 'none' ) { _document.designMode = 'off'; _document.documentElement.style.pointerEvents = ''; } let activePanels = new _Set(); let strongBlockingEnabled = false; let blockingTimer = null; const _beforeunloadHandler = function (e) { e.preventDefault(); e.returnValue = '系统可能不会保存您所做的更改。'; return e.returnValue; }; const _errorHandler = function (e) { if (strongBlockingEnabled) { disableStrongBlocking(); activePanels.clear(); } }; function enableStrongBlocking() { if (strongBlockingEnabled) return; _globals.addEventListener('beforeunload', _beforeunloadHandler); _globals.addEventListener('error', _errorHandler, true); strongBlockingEnabled = true; if (blockingTimer) _clearTimeout(blockingTimer); blockingTimer = _setTimeout(function () { if (activePanels.size > 0) { activePanels.clear(); disableStrongBlocking(); } }, CONFIG.STRONG_BLOCK_TIMEOUT); } function disableStrongBlocking() { if (!strongBlockingEnabled) return; _globals.removeEventListener('beforeunload', _beforeunloadHandler); _globals.removeEventListener('error', _errorHandler, true); strongBlockingEnabled = false; if (blockingTimer) { _clearTimeout(blockingTimer); blockingTimer = null; } if (currentConfig.modules.blockDynamicScripts) DynamicScriptInterceptor.enable(); } function setupNavigationBlocking(panelId) { activePanels.add(panelId); if (activePanels.size === 1) { enableStrongBlocking(); if (currentConfig.modules.blockDynamicScripts) DynamicScriptInterceptor.disable(); } } function teardownNavigationBlocking(panelId) { activePanels.delete(panelId); if (activePanels.size === 0) { disableStrongBlocking(); if (currentConfig.modules.blockDynamicScripts) DynamicScriptInterceptor.enable(); } } const WriteHookManager = { hooks: [], originalWrite: null, originalWriteln: null, _installed: false, init: function () { if (this._installed) return; this.originalWrite = _document.write; this.originalWriteln = _document.writeln; this._installed = true; const self = this; try { _document.write = function () { const a = arguments; for (const hook of self.hooks) { const result = hook(a, 'write'); if (result === false) return; } return self.originalWrite.apply(this, a); }; _document.writeln = function () { const a = arguments; for (const hook of self.hooks) { const result = hook(a, 'writeln'); if (result === false) return; } return self.originalWriteln.apply(this, a); }; } catch (e) { } }, addHook: function (hookFn) { if (!this.hooks.includes(hookFn)) this.hooks.push(hookFn); }, removeHook: function (hookFn) { const idx = this.hooks.indexOf(hookFn); if (idx !== -1) this.hooks.splice(idx, 1); }, }; const RedirectBlocker = { _initialized: false, _writeTimingHook: null, _savedDescriptors: [], _savedValues: {}, _savedObservers: [], _savedEventHandlers: [], _failedHooks: [], _pendingFailureNotification: null, _checkAndBlockUrl: function (url) { return isThirdPartyUrl(url); }, _compat: function () { return currentConfig.redirectBlockerCompatibilityMode !== false; }, _applyDefine: function (obj, prop, descriptor) { try { var orig = Object.getOwnPropertyDescriptor(obj, prop); Object.defineProperty(obj, prop, descriptor); this._savedDescriptors.push({ obj: obj, prop: prop, desc: orig }); return true; } catch (e) { this._failedHooks.push(prop); return false; } }, getPendingFailureNotification: function () { var msg = this._pendingFailureNotification; this._pendingFailureNotification = null; return msg; }, init: function () { if (this._initialized) return; if (!currentConfig.redirectBlockerEnabled) return; this._initialized = true; this._failedHooks = []; this._savedDescriptors = []; this._savedValues = {}; this._savedObservers = []; this._savedEventHandlers = []; var _c = this._compat(); var self = this; try { var d = Object.getOwnPropertyDescriptor(Location.prototype, 'href'); if (d && d.set) { var origHrefSet = d.set; self._applyDefine(Location.prototype, 'href', { get: d.get, set: function (v) { if (self._checkAndBlockUrl(v)) return; origHrefSet.call(this, v); }, enumerable: true, configurable: _c, }); } } catch (e) { } try { var origAssign = Location.prototype.assign; self._applyDefine(Location.prototype, 'assign', { value: function (u) { if (self._checkAndBlockUrl(u)) return; return origAssign.call(this, u); }, writable: _c, configurable: _c, }); } catch (e) { } try { var origReplace = Location.prototype.replace; self._applyDefine(Location.prototype, 'replace', { value: function (u) { if (self._checkAndBlockUrl(u)) return; return origReplace.call(this, u); }, writable: _c, configurable: _c, }); } catch (e) { } try { var origOpen = window.open; self._applyDefine(window, 'open', { value: function () { if (self._checkAndBlockUrl(arguments[0])) return null; return origOpen.apply(this, arguments); }, writable: _c, configurable: _c, }); } catch (e) { } try { var origPushState = history.pushState; var origReplaceState = history.replaceState; self._applyDefine(history, 'pushState', { value: function () { var url = arguments[2]; if (url) { if ( typeof url === 'string' && (url.charAt(0) === '/' || url.charAt(0) === '#' || url.startsWith(_location.origin)) ) { if (typeof resetAllCachesAndStates === 'function') resetAllCachesAndStates(); return origPushState.apply(this, arguments); } if (self._checkAndBlockUrl(url)) return; } if (typeof resetAllCachesAndStates === 'function') resetAllCachesAndStates(); return origPushState.apply(this, arguments); }, writable: _c, configurable: _c, }); self._applyDefine(history, 'replaceState', { value: function () { var url = arguments[2]; if (url) { if ( typeof url === 'string' && (url.charAt(0) === '/' || url.charAt(0) === '#' || url.startsWith(_location.origin)) ) { if (typeof resetAllCachesAndStates === 'function') resetAllCachesAndStates(); return origReplaceState.apply(this, arguments); } if (self._checkAndBlockUrl(url)) return; } if (typeof resetAllCachesAndStates === 'function') resetAllCachesAndStates(); return origReplaceState.apply(this, arguments); }, writable: _c, configurable: _c, }); } catch (e) { } var popstateHandler = function () { if (typeof resetAllCachesAndStates === 'function') resetAllCachesAndStates(); }; _globals.addEventListener('popstate', popstateHandler, { capture: true }); self._savedEventHandlers.push({ target: _globals, type: 'popstate', handler: popstateHandler, useCapture: true }); try { var dDomain = Object.getOwnPropertyDescriptor(Document.prototype, 'domain'); if (dDomain && dDomain.set) { self._applyDefine(Document.prototype, 'domain', { get: dDomain.get, set: function () { return; }, enumerable: true, configurable: _c, }); } } catch (e) { } try { if (navigator.serviceWorker) { var origSWRegister = navigator.serviceWorker.register; self._applyDefine(navigator.serviceWorker, 'register', { value: function (scriptUrl, options) { try { var currentHost = _location.hostname; var scriptHost = ''; try { var urlObj = new URL(scriptUrl, _location.href); scriptHost = urlObj.hostname; } catch (e) { scriptHost = currentHost; } if (scriptHost !== currentHost && isThirdPartyUrl(scriptUrl)) { LogManager.add('redirectBlocker', null, { type: 'SERVICE_WORKER', detail: '拦截第三方 SW: ' + Utils.truncateString(scriptUrl, CONFIG.LOG_DETAIL_LENGTH), }); return Promise.reject(new Error('Third-party Service Worker blocked by adblocker')); } return origSWRegister.call(this, scriptUrl, options); } catch (e) { return Promise.reject(e); } }, writable: _c, configurable: _c, }); } } catch (e) { } try { var origSubmit = HTMLFormElement.prototype.submit; self._applyDefine(HTMLFormElement.prototype, 'submit', { value: function () { var a = this.getAttribute('action'); if (a && self._checkAndBlockUrl(a)) return; return origSubmit.call(this); }, writable: _c, configurable: _c, }); } catch (e) { } try { var fp = HTMLFormElement.prototype; var ad = Object.getOwnPropertyDescriptor(fp, 'action'); if (ad && ad.set && ad.configurable) { var origActionSet = ad.set; self._applyDefine(fp, 'action', { configurable: true, enumerable: true, get: ad.get, set: function (v) { if (!v || v.trim() === '') { origActionSet.call(this, v); return; } if (self._checkAndBlockUrl(v)) return; origActionSet.call(this, v); }, }); } } catch (e) { } var formSubmitHandler = function (e) { var f = e.target; if (f && f.tagName === 'FORM') { var a = f.getAttribute('action'); if (a && self._checkAndBlockUrl(a)) { e.preventDefault(); e.stopImmediatePropagation(); return false; } } }; document.addEventListener('submit', formSubmitHandler, true); self._savedEventHandlers.push({ target: document, type: 'submit', handler: formSubmitHandler, useCapture: true }); var anchorClickLastCheck = 0; var anchorClickHandler = function (e) { var now = Date.now(); if (now - anchorClickLastCheck < CONFIG.ANCHOR_CLICK_THROTTLE) return; anchorClickLastCheck = now; var t = e.target; if (t.tagName !== 'A') t = t.closest('a'); if (!t || !t.href) return; var h = t.getAttribute('href') || t.href; if (!h || h.charAt(0) === '#' || h.charAt(0) === 'j') { if (h.startsWith('#') || h.startsWith('javascript:')) return; } if (self._checkAndBlockUrl(h)) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); return false; } }; document.addEventListener('click', anchorClickHandler, true); self._savedEventHandlers.push({ target: document, type: 'click', handler: anchorClickHandler, useCapture: true }); try { var origAnchorClick = HTMLAnchorElement.prototype.click; self._applyDefine(HTMLAnchorElement.prototype, 'click', { value: function () { var h = this.getAttribute('href') || this.href; if (self._checkAndBlockUrl(h)) return; return origAnchorClick.call(this); }, writable: _c, configurable: _c, }); } catch (e) { } try { if (window.navigation && window.navigation.navigate) { var origNavigate = window.navigation.navigate; self._applyDefine(window.navigation, 'navigate', { value: function (u) { if (self._checkAndBlockUrl(u)) { return { committed: Promise.reject(), finished: Promise.reject() }; } return origNavigate.apply(this, arguments); }, writable: _c, configurable: _c, }); } } catch (e) { } WriteHookManager.init(); this._writeTimingHook = function (args, type) { if (_document.readyState !== 'loading') return false; }; WriteHookManager.addHook(this._writeTimingHook); try { self._savedValues.setTimeout = window.setTimeout; window.setTimeout = function (cb, d) { var a = Array.prototype.slice.call(arguments, 2); if (typeof cb === 'function') { if (/(?:location\s*\.\s*href|location\s*=|window\s*\.\s*open)\s*=/i.test(cb.toString())) return -1; } return self._savedValues.setTimeout.call(this, cb, d, a); }; } catch (e) { } try { var ip = HTMLIFrameElement.prototype; var iframeSrcDesc = Object.getOwnPropertyDescriptor(ip, 'src'); if (iframeSrcDesc && iframeSrcDesc.set && iframeSrcDesc.configurable) { var origIframeSrcSet = iframeSrcDesc.set; self._applyDefine(ip, 'src', { configurable: true, enumerable: true, get: iframeSrcDesc.get, set: function (v) { if (self._checkAndBlockUrl(v)) return; origIframeSrcSet.call(this, v); }, }); } } catch (e) { } try { var fp2 = HTMLFrameElement.prototype; var frameSrcDesc = Object.getOwnPropertyDescriptor(fp2, 'src'); if (frameSrcDesc && frameSrcDesc.set && frameSrcDesc.configurable) { var origFrameSrcSet = frameSrcDesc.set; self._applyDefine(fp2, 'src', { configurable: true, enumerable: true, get: frameSrcDesc.get, set: function (v) { if (self._checkAndBlockUrl(v)) return; origFrameSrcSet.call(this, v); }, }); } } catch (e) { } try { var baseObserver = new _MutationObserver(function (ms) { for (const m of ms) { if (m.type === 'childList') { m.addedNodes.forEach(function (n) { if (n.nodeType === _Node.ELEMENT_NODE && n.tagName === 'BASE') { var h = n.getAttribute('href'); if (h && self._checkAndBlockUrl(h)) n.removeAttribute('href'); } }); } else if (m.type === 'attributes' && m.attributeName === 'href' && m.target.tagName === 'BASE') { var h2 = m.target.getAttribute('href'); if (h2 && self._checkAndBlockUrl(h2)) m.target.removeAttribute('href'); } } }); baseObserver.observe(_document.documentElement || _document, { childList: true, subtree: true, attributes: true, attributeFilter: ['href'] }); self._savedObservers.push(baseObserver); } catch (e) { } try { var ap = HTMLAreaElement.prototype; var areaHrefDesc = Object.getOwnPropertyDescriptor(ap, 'href'); if (areaHrefDesc && areaHrefDesc.set && areaHrefDesc.configurable) { var origAreaHrefSet = areaHrefDesc.set; self._applyDefine(ap, 'href', { configurable: true, enumerable: true, get: areaHrefDesc.get, set: function (v) { if (self._checkAndBlockUrl(v)) return; origAreaHrefSet.call(this, v); }, }); } } catch (e) { } try { var ep = HTMLEmbedElement.prototype; var embedSrcDesc = Object.getOwnPropertyDescriptor(ep, 'src'); if (embedSrcDesc && embedSrcDesc.set && embedSrcDesc.configurable) { var origEmbedSrcSet = embedSrcDesc.set; self._applyDefine(ep, 'src', { configurable: true, enumerable: true, get: embedSrcDesc.get, set: function (v) { if (self._checkAndBlockUrl(v)) return; origEmbedSrcSet.call(this, v); }, }); } } catch (e) { } try { var op = HTMLObjectElement.prototype; var objDataDesc = Object.getOwnPropertyDescriptor(op, 'data'); if (objDataDesc && objDataDesc.set && objDataDesc.configurable) { var origObjDataSet = objDataDesc.set; self._applyDefine(op, 'data', { configurable: true, enumerable: true, get: objDataDesc.get, set: function (v) { if (self._checkAndBlockUrl(v)) return; origObjDataSet.call(this, v); }, }); } } catch (e) { } try { var metaObserver = new _MutationObserver(function (ms) { for (const m of ms) { if (!m.addedNodes) continue; m.addedNodes.forEach(function (n) { if (!(n instanceof Element)) return; if (n.tagName === 'META') { if (/refresh/i.test(n.getAttribute('http-equiv')) && /url\s*=/i.test(n.getAttribute('content') || '')) n.remove(); } else { if ( n.tagName === 'LINK' && (n.rel === 'prefetch' || n.rel === 'prerender' || n.rel === 'preload') ) { var h = n.getAttribute('href'); if (h && self._checkAndBlockUrl(h)) n.remove(); } if (n.querySelectorAll) n.querySelectorAll('meta[http-equiv="refresh"]').forEach(function (m2) { m2.remove(); }); } }); } }); metaObserver.observe(_document.documentElement || _document, { childList: true, subtree: true }); self._savedObservers.push(metaObserver); } catch (e) { } if (this._failedHooks.length > 0) { this._pendingFailureNotification = this._failedHooks.slice(); } }, disable: function () { if (!this._initialized) return true; var allRestored = true; for (var i = this._savedDescriptors.length - 1; i >= 0; i--) { var r = this._savedDescriptors[i]; try { if (r.desc) Object.defineProperty(r.obj, r.prop, r.desc); } catch (e) { allRestored = false; } } this._savedDescriptors = []; if (this._savedValues.setTimeout) { try { window.setTimeout = this._savedValues.setTimeout; } catch (e) { allRestored = false; } this._savedValues.setTimeout = null; } if (this._writeTimingHook) { WriteHookManager.removeHook(this._writeTimingHook); this._writeTimingHook = null; } for (var j = 0; j < this._savedObservers.length; j++) { try { this._savedObservers[j].disconnect(); } catch (e) { } } this._savedObservers = []; for (var k = 0; k < this._savedEventHandlers.length; k++) { var eh = this._savedEventHandlers[k]; try { eh.target.removeEventListener(eh.type, eh.handler, eh.useCapture); } catch (e) { } } this._savedEventHandlers = []; this._initialized = false; return allRestored; }, }; RedirectBlocker.init(); const DynamicScriptInterceptor = { _enabled: false, _dynamicWriteHook: null, _monitoringDepth: 0, _currentMonitorSource: null, originalEval: null, originalFunction: null, originalFunctionConstructorDescriptor: null, originalSetTimeout: null, originalSetInterval: null, originalClearTimeout: null, originalClearInterval: null, originalRequestAnimationFrame: null, originalCancelAnimationFrame: null, originalDocumentWrite: null, originalDocumentWriteln: null, originalWorker: null, originalSharedWorker: null, init: function () { if (currentConfig.modules.blockDynamicScripts) this.enable(); }, _detectObfuscation: function (code) { if (typeof code !== 'string' || code.length < 10) return false; if (/String\s*\.\s*fromCharCode/i.test(code)) return true; if (/\batob\s*\(/i.test(code)) return true; if (/\[\s*\d+\s*\]\s*\.\s*join/i.test(code)) return true; if (/\bcharCodeAt\b/i.test(code) && /\btoString\b/i.test(code)) return true; if (/\bdecodeURIComponent\s*\(/i.test(code) && /\b(?:replace|split|substr|substring|slice)\b/i.test(code)) return true; if (/(?:\\x[0-9a-fA-F]{2}){3,}/i.test(code)) return true; if (/(?:\\u[0-9a-fA-F]{4}){2,}/i.test(code)) return true; if (/\\x3[cC]|\\x3[eE]|\\x2[fF]/i.test(code) && /\b(?:document|location|window)\b/i.test(code)) return true; if (/\bparseInt\s*\(\s*['"][0-9a-fA-F]/i.test(code)) return true; var brkCount = (code.match(/\[/g) || []).length; var dotCount = (code.match(/\./g) || []).length; var concatCount = (code.match(/\+/g) || []).length; if (brkCount > dotCount * 2 && brkCount > 3) return true; if (concatCount > 5 && /\b(?:write|open|href|assign|replace)\b/i.test(code)) return true; return false; }, _detectIndirectCalls: function (code) { if (typeof code !== 'string') return false; var stripped = code.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, ''); stripped = stripped.replace(/(?:^|\s|;|\})\s*(?:var|let|const|function|return|if|else|for|while|try|catch|throw|switch|case|break|continue|new|typeof|instanceof|in|of|void|delete|this|true|false|null|undefined|do)\b/g, ' '); stripped = stripped.trim(); if (!stripped) return false; var lines = stripped.split(/[;\n]/).filter(function (l) { return l.trim().length > 0; }); if (lines.length === 0) return false; var onlySimpleCalls = true; var referencedNames = []; for (var i = 0; i < lines.length; i++) { var line = lines[i].trim(); if (line === '{' || line === '}' || line.length === 0) continue; var callMatch = line.match(/^\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/); if (callMatch) { referencedNames.push(callMatch[1]); } else { onlySimpleCalls = false; break; } } if (onlySimpleCalls && referencedNames.length > 0 && referencedNames.length <= 5) { var suspiciousNames = ['trigger', 'init', 'start', 'run', 'exec', 'fire', 'do', 'go', 'load', 'inject', 'insert', 'append', 'render', 'show', 'display', 'create', 'build', 'setup', 'prepare', 'launch', 'handle', 'process']; for (var j = 0; j < referencedNames.length; j++) { for (var k = 0; k < suspiciousNames.length; k++) { if (referencedNames[j].toLowerCase().indexOf(suspiciousNames[k]) !== -1) return true; } } } return false; }, _wrapForStrictMonitoring: function (fn, type) { var self = this; var sourceStr = ''; try { sourceStr = fn.toString(); } catch (e) { sourceStr = '[wrapped]'; } var wrapped = function () { self._monitoringDepth++; self._currentMonitorSource = sourceStr; try { return fn.apply(this, arguments); } catch (e) { throw e; } finally { self._monitoringDepth--; if (self._monitoringDepth <= 0) { self._monitoringDepth = 0; self._currentMonitorSource = null; } } }; try { Object.defineProperty(wrapped, 'name', { value: fn.name || '', configurable: true }); } catch (e) { } try { Object.defineProperty(wrapped, 'length', { value: fn.length, configurable: true }); } catch (e) { } return wrapped; }, enable: function () { if (this._enabled) return; this._enabled = true; this._monitoringDepth = 0; this._currentMonitorSource = null; this.originalEval = _globals.eval; this.originalFunction = _globals.Function; this.originalSetTimeout = _globals.setTimeout; this.originalSetInterval = _globals.setInterval; this.originalClearTimeout = _globals.clearTimeout; this.originalClearInterval = _globals.clearInterval; this.originalRequestAnimationFrame = _globals.requestAnimationFrame; this.originalCancelAnimationFrame = _globals.cancelAnimationFrame; this.originalDocumentWrite = WriteHookManager.originalWrite; this.originalDocumentWriteln = WriteHookManager.originalWriteln; this.originalWorker = _globals.Worker || null; this.originalSharedWorker = _globals.SharedWorker || null; const self = this; var strictModePatterns = [ /document\s*\.\s*write(?:ln)?\s*\(/i, /document\s*\[\s*['"][^'"]*?write[^'"]*?['"]\s*\]\s*\(/i, /location\s*\.\s*(?:assign|replace)\s*\(/i, /location\s*\[\s*['"][^'"]*?(?:assign|replace)[^'"]*?['"]\s*\]\s*\(/i, /location\s*(?:\.\s*href|\[\s*['"][^'"]*?href[^'"]*?['"]\s*\])\s*=/i, /(?:window|self|top|parent|frames|globalThis)\s*\.\s*open\s*\(/i, /(?:window|self|top|parent|frames|globalThis)\s*\[\s*['"][^'"]*?open[^'"]*?['"]\s*\]\s*\(/i, /\w+\s*\[\s*['"][^'"]*?(?:write|writeln)[^'"]*?['"]\s*\]\s*\(/i, /\w+\s*\[\s*['"][^'"]*?open[^'"]*?['"]\s*\]\s*\(/i, /\w+\s*\[\s*['"][^'"]*?(?:assign|replace|href)[^'"]*?['"]\s*\]\s*[\(=]/i, /(?:document|location|window|self|top|parent)\s*\[\s*\w+\s*\]\s*\(/i, /['"][wW][rR][iI][tT][eE]['"].*?\+/i, /['"](?:open|href|assign|replace)['"].*?\+/i, ]; var checkAndBlock = function (code, type) { if (typeof code !== 'string' || code.trim() === '') return null; if (Whitelisting.isCodeWhitelisted(code, type)) return 'allow'; var ci = Utils.getContentIdentifier(null, { type: type, detail: code }); if (ci && !currentConfig.whitelist.has(ci)) { LogManager.add('blockDynamicScripts', null, { type: type, detail: Utils.truncateString(code, CONFIG.LOG_DETAIL_LENGTH), rawDetail: code, }); return 'block'; } return null; }; var checkCallback = function (callback, type) { if (typeof callback === 'string') { return checkAndBlock(callback, type) === 'block' ? { blocked: true } : { blocked: false }; } else if (typeof callback === 'function') { var fs = callback.toString(); if (fs.includes('eval') || fs.includes('Function')) { return checkAndBlock(fs, type) === 'block' ? { blocked: true } : { blocked: false }; } if (currentConfig.dynamicScriptStrictMode) { var patternMatched = false; for (var si = 0; si < strictModePatterns.length; si++) { if (strictModePatterns[si].test(fs)) { var result = checkAndBlock(fs, type); if (result === 'block') return { blocked: true }; patternMatched = true; break; } } if (!patternMatched) { if (self._detectObfuscation(fs)) { checkAndBlock(fs, type + '_OBFUSCATED'); return { blocked: false, needsMonitoring: true }; } if (self._detectIndirectCalls(fs)) { return { blocked: false, needsMonitoring: true }; } } } return { blocked: false }; } return { blocked: false }; }; try { _globals.eval = function (code) { if (typeof code === 'string') { var result = checkAndBlock(code, 'EVAL'); if (result === 'block') return undefined; } return self.originalEval.call(this, code); }; } catch (e) { } try { this.originalFunctionConstructorDescriptor = Object.getOwnPropertyDescriptor( Function.prototype, 'constructor' ); _globals.Function = new _Proxy(this.originalFunction, { construct: function (target, args, newTarget) { var code = args.length > 0 ? String(args[args.length - 1]) : ''; if (typeof code === 'string') { var result = checkAndBlock(code, 'FUNCTION_CONSTRUCTOR'); if (result === 'block') return Reflect.construct(target, ['return;'], newTarget || target); } return Reflect.construct(target, args, newTarget || target); }, apply: function (target, thisArg, args) { var code = args.length > 0 ? String(args[args.length - 1]) : ''; if (typeof code === 'string') { var result = checkAndBlock(code, 'FUNCTION_CONSTRUCTOR'); if (result === 'block') return Reflect.apply(target, thisArg, ['return;']); } return Reflect.apply(target, thisArg, args); }, }); try { Object.defineProperty(Function.prototype, 'constructor', { get: function () { return _globals.Function; }, configurable: true, }); } catch (e) { } } catch (e) { } try { _globals.setTimeout = function (cb, d) { var a = Array.prototype.slice.call(arguments, 2); var r = checkCallback(cb, 'SETTIMEOUT'); if (r.blocked) return -1; if (r.needsMonitoring && typeof cb === 'function') { cb = self._wrapForStrictMonitoring(cb, 'SETTIMEOUT'); } return self.originalSetTimeout.apply(this, [cb, d].concat(a)); }; _globals.setInterval = function (cb, d) { var a = Array.prototype.slice.call(arguments, 2); var r = checkCallback(cb, 'SETINTERVAL'); if (r.blocked) return -1; if (r.needsMonitoring && typeof cb === 'function') { cb = self._wrapForStrictMonitoring(cb, 'SETINTERVAL'); } return self.originalSetInterval.apply(this, [cb, d].concat(a)); }; _globals.clearTimeout = function (id) { if (id === -1) return; return self.originalClearTimeout.call(this, id); }; _globals.clearInterval = function (id) { if (id === -1) return; return self.originalClearInterval.call(this, id); }; _globals.requestAnimationFrame = function (cb) { var r = checkCallback(cb, 'REQUESTANIMATIONFRAME'); if (r.blocked) return -1; if (r.needsMonitoring && typeof cb === 'function') { cb = self._wrapForStrictMonitoring(cb, 'REQUESTANIMATIONFRAME'); } return self.originalRequestAnimationFrame.call(this, cb); }; _globals.cancelAnimationFrame = function (id) { if (id === -1) return; return self.originalCancelAnimationFrame.call(this, id); }; } catch (e) { } if (this.originalWorker) { try { _globals.Worker = new _Proxy(this.originalWorker, { construct: function (target, args, newTarget) { var url = typeof args[0] === 'string' ? args[0] : ''; if (url && shouldBlockResource(url)) { LogManager.add('blockDynamicScripts', null, { type: 'THIRD_PARTY', detail: 'WORKER: ' + Utils.truncateString(url, CONFIG.LOG_DETAIL_LENGTH), }); return Reflect.construct(target, ['data:text/javascript,', args[1]], newTarget); } return Reflect.construct(target, args, newTarget); }, }); } catch (e) { } } if (this.originalSharedWorker) { try { _globals.SharedWorker = new _Proxy(this.originalSharedWorker, { construct: function (target, args, newTarget) { var url = typeof args[0] === 'string' ? args[0] : ''; if (url && shouldBlockResource(url)) { LogManager.add('blockDynamicScripts', null, { type: 'THIRD_PARTY', detail: 'SHARED_WORKER: ' + Utils.truncateString(url, CONFIG.LOG_DETAIL_LENGTH), }); return Reflect.construct(target, ['data:text/javascript,', args[1]], newTarget); } return Reflect.construct(target, args, newTarget); }, }); } catch (e) { } } WriteHookManager.init(); this._dynamicWriteHook = function (args, type) { var c = args.join(''); if (typeof c === 'string') { var result = checkAndBlock(c, 'DOCUMENT_WRITE'); if (result === 'block') return false; } }; WriteHookManager.addHook(this._dynamicWriteHook); }, disable: function () { if (!this._enabled) return; this._enabled = false; this._monitoringDepth = 0; this._currentMonitorSource = null; if (this._dynamicWriteHook) { WriteHookManager.removeHook(this._dynamicWriteHook); this._dynamicWriteHook = null; } try { _globals.eval = this.originalEval; } catch (e) { } try { _globals.Function = this.originalFunction; if (this.originalFunctionConstructorDescriptor) Object.defineProperty(Function.prototype, 'constructor', this.originalFunctionConstructorDescriptor); else try { delete Function.prototype.constructor; } catch (e) { } } catch (e) { } try { _globals.setTimeout = this.originalSetTimeout; } catch (e) { } try { _globals.setInterval = this.originalSetInterval; } catch (e) { } try { _globals.clearTimeout = this.originalClearTimeout; } catch (e) { } try { _globals.clearInterval = this.originalClearInterval; } catch (e) { } try { _globals.requestAnimationFrame = this.originalRequestAnimationFrame; } catch (e) { } try { _globals.cancelAnimationFrame = this.originalCancelAnimationFrame; } catch (e) { } if (this.originalWorker) { try { _globals.Worker = this.originalWorker; } catch (e) { } } if (this.originalSharedWorker) { try { _globals.SharedWorker = this.originalSharedWorker; } catch (e) { } } this.originalEval = this.originalFunction = this.originalSetTimeout = this.originalSetInterval = null; this.originalClearTimeout = this.originalClearInterval = this.originalRequestAnimationFrame = this.originalCancelAnimationFrame = null; this.originalDocumentWrite = this.originalDocumentWriteln = null; this.originalWorker = this.originalSharedWorker = null; }, }; if (currentConfig.modules.blockDynamicScripts) DynamicScriptInterceptor.enable(); const ResidualCleaner = { _scanTimer: null, _dedupSet: null, _pendingNodes: [], _maxCleanupDepth: 5, observer: null, _isLazyImage: function (el) { if (el.tagName !== 'IMG') return false; if (el.loading === 'lazy') return true; var lazyAttrs = [ 'data-src', 'data-original', 'data-echo', 'data-lazy-src', 'data-bg', 'data-srcset', 'data-lazy', 'data-ll-status', 'data-islazy', ]; for (var i = 0; i < lazyAttrs.length; i++) { if (el.hasAttribute(lazyAttrs[i])) return true; } if (el.classList) { if (el.classList.contains('lazyload') || el.classList.contains('lazy')) return true; } if (el.srcset && el.srcset.trim()) return true; if (el.parentElement && el.parentElement.tagName === 'PICTURE') return true; return false; }, init: function () { var any = Object.values(currentConfig.modules).some(function (v) { return v === true; }); if (!any || !currentConfig.residualCleanupEnabled) { this.stop(); return; } this.setupMutationObserver(); this.initialScan(); this.startPeriodicScan(); }, stop: function () { if (this.observer) { this.observer.disconnect(); this.observer = null; } if (this._scanTimer !== null) { _cancelIdleCallback(this._scanTimer); this._scanTimer = null; } this._dedupSet = ProcessedElementsCache; this._pendingNodes = []; }, getScanSelectors: function () { var selectors = ['div', 'span', 'section', 'aside']; if (currentConfig.modules.interceptThirdParty) { selectors.push('iframe'); selectors.push('object'); selectors.push('embed'); } return selectors.join(', '); }, setupMutationObserver: function () { var self = this; this.observer = new _MutationObserver(function (ms) { for (const m of ms) { if (m.type === 'childList') { if (m.addedNodes.length > 0) { for (const n of m.addedNodes) { if (n.nodeType === _Node.ELEMENT_NODE && Utils.isElement(n)) { self._pendingNodes.push(n); self.checkAndCleanup(n); } } } if (m.removedNodes.length > 0 && m.target && Utils.isElement(m.target)) self.checkAndCleanup(m.target); } } }); this.observer.observe(_document.documentElement, { childList: true, subtree: true, attributes: false }); }, isTransparentOverlay: function (el) { if (!el || el.tagName !== 'A' || ProcessedElementsCache.isProcessed(el)) return false; if (el.offsetWidth < CONFIG.OVERLAY_MIN_SIZE || el.offsetHeight < CONFIG.OVERLAY_MIN_SIZE) return false; var s = getComputedStyle(el); if (s.position !== 'absolute' && s.position !== 'fixed') return false; var z = parseInt(s.zIndex); if (isNaN(z) || z < 100) return false; return !Array.from(el.childNodes).some(function (c) { if (c.nodeType === _Node.TEXT_NODE && c.textContent.trim().length > 0) return true; if (c.nodeType === _Node.ELEMENT_NODE && c.tagName === 'IMG' && c.offsetWidth > 5 && c.offsetHeight > 5) return true; return false; }); }, cleanEmptyParents: function (parent, depth) { depth = depth || 0; if (depth > this._maxCleanupDepth) return; if ( !parent || parent === _document.body || parent === _document.documentElement || !Utils.isElement(parent) || ProcessedElementsCache.isProcessed(parent) ) return; if (this.isStrictEmpty(parent) && Utils.isSuspiciousAdContainer(parent)) { ProcessedElementsCache.markAsProcessed(parent); var np = parent.parentElement; parent.remove(); ProcessedElementsCache.markAsProcessed(parent); this.cleanEmptyParents(np, depth + 1); } }, isStrictEmpty: function (container) { if (!Utils.isElement(container)) return false; if (container.getAttribute && container.getAttribute('data-adblock-safe') === 'true') return false; var hasReal = false; for (var i = 0; i < container.childNodes.length; i++) { var child = container.childNodes[i]; if (child.nodeType === _Node.ELEMENT_NODE && ProcessedElementsCache.isProcessed(child)) continue; if (child.nodeType === _Node.TEXT_NODE) { if (child.textContent.trim().length > 1) { hasReal = true; break; } } else if (child.nodeType === _Node.ELEMENT_NODE) { var el = child; if (el.offsetWidth <= 0 && el.offsetHeight <= 0) continue; var inlineStyle = el.style; if (inlineStyle.display === 'none' || inlineStyle.visibility === 'hidden' || inlineStyle.opacity === '0') continue; var s = getComputedStyle(el); if (s.display === 'none' || s.visibility === 'hidden' || s.opacity === '0') continue; if (el.tagName === 'IMG') { if (this._isLazyImage(el)) { hasReal = true; break; } var imgSrc = el.getAttribute('src') || el.getAttribute('data-src') || ''; var isDataPlaceholder = imgSrc.startsWith('data:') && imgSrc.length < CONFIG.LAZY_PLACEHOLDER_MAX_LEN; if (imgSrc && !isDataPlaceholder) { hasReal = true; break; } if ((el.naturalWidth <= 5 && el.naturalHeight <= 5) || (el.offsetWidth <= 5 && el.offsetHeight <= 5)) continue; } if (el.offsetWidth > CONFIG.CONTAINER_MIN_VISIBLE && el.offsetHeight > CONFIG.CONTAINER_MIN_VISIBLE) { if (el.tagName === 'A') { var hasLink = false; for (var j = 0; j < el.childNodes.length; j++) { var c = el.childNodes[j]; if (c.nodeType === _Node.TEXT_NODE && c.textContent.trim().length > 0) { hasLink = true; break; } if (c.nodeType === _Node.ELEMENT_NODE) { if (c.tagName === 'IMG' && (c.naturalWidth > 5 || this._isLazyImage(c))) { hasLink = true; break; } if (c.offsetWidth > CONFIG.CONTAINER_MIN_VISIBLE && c.offsetHeight > CONFIG.CONTAINER_MIN_VISIBLE && c.tagName !== 'BR' && c.tagName !== 'HR' && c.tagName !== 'A') { hasLink = true; break; } } } if (!hasLink) continue; } hasReal = true; break; } } } return !hasReal; }, checkAndCleanup: function (element) { if (!element || !Utils.isElement(element) || ProcessedElementsCache.isProcessed(element) || element === _document.body || element === _document.documentElement) return; if (this.isTransparentOverlay(element)) { ProcessedElementsCache.markAsProcessed(element); var p = element.parentElement; element.remove(); ProcessedElementsCache.markAsProcessed(element); this.cleanEmptyParents(p); return; } var w = element.offsetWidth; var h = element.offsetHeight; if (w > 0 && w < CONFIG.RESIDUAL_MIN_WIDTH && h > 0 && h < CONFIG.RESIDUAL_MIN_HEIGHT) return; if (!currentConfig.residualCleanupEnabled) return; if (this.isStrictEmpty(element)) { var s = getComputedStyle(element); var isFH = s.height !== 'auto' && parseFloat(s.height) > CONFIG.FIXED_HEIGHT_THRESHOLD; var isHZ = parseInt(s.zIndex) > CONFIG.ZINDEX_HIDE_THRESHOLD; var isAF = s.position === 'absolute' || s.position === 'fixed'; if (isFH || isAF || isHZ || Utils.isSuspiciousAdContainer(element)) { ProcessedElementsCache.markAsProcessed(element); this.cleanupContainer(element, isAF || isHZ); } } }, cleanupContainer: function (container, forceHide) { forceHide = forceHide || false; if (!container || !Utils.isElement(container) || !container.isConnected) return; if (forceHide) { container.style.setProperty('display', 'none', 'important'); container.style.setProperty('visibility', 'hidden', 'important'); container.style.setProperty('pointer-events', 'none', 'important'); container.style.setProperty('height', '0px', 'important'); container.style.setProperty('overflow', 'hidden', 'important'); var p = container.parentElement; while (p && p !== _document.body && p !== _document.documentElement) { if (!Utils.isElement(p)) break; p.style.setProperty('pointer-events', 'none', 'important'); if (Array.from(p.childNodes).some(function (c) { return ((c.nodeType === _Node.ELEMENT_NODE && !ProcessedElementsCache.isProcessed(c)) || (c.nodeType === _Node.TEXT_NODE && c.textContent.trim().length > 0)); })) break; p = p.parentElement; } ProcessedElementsCache.markAsProcessed(container); } else { var p2 = container.parentElement; container.remove(); ProcessedElementsCache.markAsProcessed(container); this.cleanEmptyParents(p2); } }, initialScan: function () { var self = this; var links = _document.querySelectorAll('a'); for (var i = 0; i < links.length; i++) { if (this.isTransparentOverlay(links[i])) { ProcessedElementsCache.markAsProcessed(links[i]); var p = links[i].parentElement; links[i].remove(); ProcessedElementsCache.markAsProcessed(links[i]); this.cleanEmptyParents(p); } } var els = Array.from(_document.querySelectorAll(this.getScanSelectors())); var toR = []; var toH = []; for (const el of els) { if (!el || !Utils.isElement(el) || ProcessedElementsCache.isProcessed(el) || el === _document.body || el === _document.documentElement) continue; var w = el.offsetWidth; var h = el.offsetHeight; if (w > 0 && w < CONFIG.RESIDUAL_MIN_WIDTH && h > 0 && h < CONFIG.RESIDUAL_MIN_HEIGHT) continue; if (this.isStrictEmpty(el)) { var s = getComputedStyle(el); var isFH = s.height !== 'auto' && parseFloat(s.height) > CONFIG.FIXED_HEIGHT_THRESHOLD; var isHZ = parseInt(s.zIndex) > CONFIG.ZINDEX_HIDE_THRESHOLD; var isAF = s.position === 'absolute' || s.position === 'fixed'; if (isFH || isAF || isHZ || Utils.isSuspiciousAdContainer(el)) { ProcessedElementsCache.markAsProcessed(el); if (isAF || isHZ) toH.push(el); else toR.push(el); } } } toR.forEach(function (el) { var p = el.parentElement; ProcessedElementsCache.markAsProcessed(el); el.remove(); self.cleanEmptyParents(p); }); toH.forEach(function (el) { ProcessedElementsCache.markAsProcessed(el); el.style.setProperty('display', 'none', 'important'); el.style.setProperty('height', '0px', 'important'); el.style.setProperty('overflow', 'hidden', 'important'); }); }, startPeriodicScan: function () { var self = this; var last = 0; var loop = function (dl) { if (!currentConfig.residualCleanupEnabled) return; var now = Date.now(); if (now - last > CONFIG.IDLE_SCAN_INTERVAL && dl.timeRemaining() > 5) { var cnt = 0; var batch = self._pendingNodes.splice(0, CONFIG.BATCH_SIZE_CLEANUP); for (var i = 0; i < batch.length && dl.timeRemaining() > 5 && cnt < CONFIG.BATCH_SIZE_CLEANUP; i++) { var el = batch[i]; if (!el || !Utils.isElement(el) || !el.isConnected) continue; if (self.isTransparentOverlay(el)) { ProcessedElementsCache.markAsProcessed(el); var p = el.parentElement; el.remove(); ProcessedElementsCache.markAsProcessed(el); self.cleanEmptyParents(p); cnt++; } else { self.checkAndCleanup(el); cnt++; } } if (self._pendingNodes.length > 500) self._pendingNodes = self._pendingNodes.slice(-200); if (cnt > 0) last = now; } self._scanTimer = _requestIdleCallback(loop); }; this._scanTimer = _requestIdleCallback(loop); }, }; const CSSUniversalHider = { _enabled: false, _observer: null, _intersectionObserver: null, _processedSet: new WeakSet(), _hideClass: 'adblock-universal-hidden', _styleElement: null, init: function () { currentConfig.cssUniversalHideEnabled ? this.enable() : this.disable(); }, enable: function () { if (this._enabled) return; this._enabled = true; this.injectStyle(); var self = this; this._intersectionObserver = new _IntersectionObserver(function (es) { es.forEach(function (e) { if (e.isIntersecting) { var el = e.target; self._intersectionObserver.unobserve(el); if (self.shouldHideElement(el)) self.hideElement(el); } }); }, { threshold: 0 }); this._observer = new _MutationObserver(function (ms) { for (const m of ms) { if (m.type === 'childList' && m.addedNodes.length > 0) { for (const n of m.addedNodes) { if (n.nodeType === 1) self.observeElement(n); } } } }); this._observer.observe(_document.documentElement, { childList: true, subtree: true }); this.scanAndHide(); }, disable: function () { if (!this._enabled) return; this._enabled = false; if (this._observer) { this._observer.disconnect(); this._observer = null; } if (this._intersectionObserver) { this._intersectionObserver.disconnect(); this._intersectionObserver = null; } if (this._styleElement && this._styleElement.parentNode) { this._styleElement.parentNode.removeChild(this._styleElement); this._styleElement = null; } var _hideCls = this._hideClass; try { _document.querySelectorAll('.' + _hideCls).forEach(function (el) { el.classList.remove(_hideCls); }); } catch (e) { } this._processedSet = new WeakSet(); }, injectStyle: function () { if (this._styleElement) return; var existing = _document.querySelector('style[data-adblock-hide-style]'); if (existing) { this._styleElement = existing; return; } var s = _document.createElement('style'); s.setAttribute('data-adblock-hide-style', 'true'); s.textContent = '.' + this._hideClass + ' { display: none !important; visibility: hidden !important; opacity: 0 !important; pointer-events: none !important; position: absolute !important; left: -9999px !important; top: -9999px !important; width: 0 !important; height: 0 !important; overflow: hidden !important; z-index: -999 !important; }'; _document.head.appendChild(s); this._styleElement = s; }, observeElement: function (el) { if (!el || el.nodeType !== 1 || (el.getAttribute && el.getAttribute('data-adblock-safe') === 'true') || Utils.isUIElement(el) || this._processedSet.has(el)) return; this._intersectionObserver.observe(el); }, shouldHideElement: function (el) { if (!el || el.nodeType !== 1 || (el.getAttribute && el.getAttribute('data-adblock-safe') === 'true') || Utils.isUIElement(el) || this._processedSet.has(el)) return false; try { var s = window.getComputedStyle(el); var z = parseInt(s.zIndex); if (isNaN(z)) z = 0; var pos = s.position; if (z > CONFIG.ZINDEX_HIDE_THRESHOLD && pos !== 'static') return true; } catch (e) { } return false; }, hideElement: function (el) { if (this.shouldHideElement(el)) { el.classList.add(this._hideClass); this._processedSet.add(el); } }, scanAndHide: function () { var self = this; if (!this._enabled) return; try { _document.querySelectorAll('*').forEach(function (el) { self.observeElement(el); }); } catch (e) { } }, }; const ResourceCanceller = { cancelResourceLoading: function (element) { if (!Utils.isElement(element) || ProcessedElementsCache.isProcessed(element)) return; var t = element.tagName; if (t === 'IMG') { element.removeAttribute('src'); element.removeAttribute('srcset'); element.removeAttribute('data-src'); } else if (t === 'IFRAME') { element.removeAttribute('src'); element.style.display = 'none'; } else if (t === 'SCRIPT') element.removeAttribute('src'); else if (t === 'LINK' && element.rel === 'stylesheet') element.removeAttribute('href'); else if (t === 'STYLE') element.textContent = ''; else if (t === 'EMBED') element.removeAttribute('src'); else if (t === 'OBJECT') element.removeAttribute('data'); var p = element.parentElement; if (p && p !== _document.body && p !== _document.documentElement && Utils.isContainerEmpty(p, true) && Utils.isSuspiciousAdContainer(p)) ResidualCleaner.checkAndCleanup(p); if (element.parentNode) element.parentNode.removeChild(element); ProcessedElementsCache.markAsProcessed(element); }, }; const TAG_HANDLERS = { SCRIPT: { srcAttr: 'src', inlineContent: true, check: function (el, mk, reason) { var ci = Utils.getContentIdentifier(el); if (ci && currentConfig.whitelist.has(ci)) return false; LogManager.add(mk, el, reason); if (mk === 'removeExternalScripts' || mk === 'scriptBlacklistMode') ResourceCanceller.cancelResourceLoading(el); else if (mk === 'removeInlineScripts' && !el.src) el.remove(); ProcessedElementsCache.markAsProcessed(el); return true; }, }, LINK: { srcAttr: 'href', check: function (el, mk) { if (mk !== 'interceptThirdParty') return false; var u = el.href; if (u && el.rel === 'stylesheet' && shouldBlockResource(u)) { LogManager.add(mk, el, { type: 'THIRD_PARTY', detail: 'LINK: ' + Utils.truncateString(u, CONFIG.LOG_DETAIL_LENGTH) }); ResourceCanceller.cancelResourceLoading(el); ProcessedElementsCache.markAsProcessed(el); return true; } return false; }, }, EMBED: { srcAttr: 'src', check: function (el, mk) { if (mk !== 'interceptThirdParty') return false; var u = el.src; if (u && shouldBlockResource(u)) { LogManager.add(mk, el, { type: 'THIRD_PARTY', detail: 'EMBED: ' + Utils.truncateString(u, CONFIG.LOG_DETAIL_LENGTH) }); ResourceCanceller.cancelResourceLoading(el); ProcessedElementsCache.markAsProcessed(el); return true; } return false; }, }, OBJECT: { srcAttr: 'data', check: function (el, mk) { if (mk !== 'interceptThirdParty') return false; var u = el.data; if (u && shouldBlockResource(u)) { LogManager.add(mk, el, { type: 'THIRD_PARTY', detail: 'OBJECT: ' + Utils.truncateString(u, CONFIG.LOG_DETAIL_LENGTH) }); ResourceCanceller.cancelResourceLoading(el); ProcessedElementsCache.markAsProcessed(el); return true; } return false; }, }, A: { srcAttr: 'href', check: function (el, mk) { if (mk !== 'interceptThirdParty') return false; var u = el.href; if (u && shouldBlockResource(u)) { LogManager.add(mk, el, { type: 'THIRD_PARTY', detail: 'A: ' + Utils.truncateString(u, CONFIG.LOG_DETAIL_LENGTH) }); el.href = 'javascript:void(0)'; el.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); }, true); ProcessedElementsCache.markAsProcessed(el); return true; } return false; }, }, }; class BaseModule { constructor(mk) { this.moduleKey = mk; this.enabled = false; this.observer = null; } init() { currentConfig.modules[this.moduleKey] ? this.enable() : this.disable(); } enable() { if (this.enabled) return; this.enabled = true; this.onEnable(); } disable() { if (!this.enabled) return; this.enabled = false; this.onDisable(); } onEnable() { } onDisable() { if (this.observer) { this.observer.disconnect(); this.observer = null; } } checkElement(el) { if (!Utils.shouldInterceptByModule(el, this.moduleKey)) return false; return this._checkElement(el); } _checkElement(el) { throw new Error('_checkElement must be implemented'); } } class RemoveInlineScriptsModule extends BaseModule { constructor() { super('removeInlineScripts'); this.attributeObserver = null; } onEnable() { var self = this; _document.querySelectorAll('script:not([src])').forEach(function (s) { self.checkElement(s); }); if (currentConfig.inlineScriptStrictMode) _document.querySelectorAll('*').forEach(function (el) { self.sanitizeInlineEventAttributes(el); }); this.observer = new _MutationObserver(function (ms) { for (const m of ms) { for (const n of m.addedNodes) { if (n.nodeType === 1) { if (currentConfig.inlineScriptStrictMode) self.sanitizeInlineEventAttributes(n); if (n.tagName === 'SCRIPT' && !n.src) self.checkElement(n); } } } }); this.observer.observe(_document.documentElement, { childList: true, subtree: true }); if (currentConfig.inlineScriptStrictMode) { this.attributeObserver = new _MutationObserver(function (ms) { for (const m of ms) { if (m.type !== 'attributes' || m.target.nodeType !== 1) continue; var an = m.attributeName; var t = m.target; if (an && an.toLowerCase().startsWith('on')) { var v = t.getAttribute(an); if (v && typeof v === 'string' && v.trim() !== '') { var r = { type: '内联事件', detail: '属性: ' + an + '="' + v + '"' }; if (!Whitelisting.isReasonWhitelisted(r)) { LogManager.add(self.moduleKey, t, r); t.removeAttribute(an); ProcessedElementsCache.markAsProcessed(t); } } } else if (['href', 'src', 'action', 'data', 'formaction'].indexOf(an) !== -1) { var v2 = t.getAttribute(an); if (v2 && typeof v2 === 'string' && v2.trim().toLowerCase().startsWith('javascript:')) { var r2 = { type: 'javascript URL', detail: an + '="' + v2 + '"' }; if (!Whitelisting.isReasonWhitelisted(r2)) { LogManager.add(self.moduleKey, t, r2); t.removeAttribute(an); ProcessedElementsCache.markAsProcessed(t); } } } } }); this.attributeObserver.observe(_document.documentElement, { attributes: true, subtree: true }); } } onDisable() { super.onDisable(); if (this.attributeObserver) { this.attributeObserver.disconnect(); this.attributeObserver = null; } } _checkElement(el) { if (el.tagName === 'SCRIPT' && !el.src) return TAG_HANDLERS.SCRIPT.check(el, this.moduleKey, { type: '内嵌脚本移除', detail: '内容: ' + Utils.truncateString(el.textContent, CONFIG.LOG_DETAIL_LENGTH) }); return false; } sanitizeInlineEventAttributes(el) { if (!Utils.isElement(el) || ProcessedElementsCache.isProcessed(el)) return false; var mod = false; if (el.attributes) { for (var i = el.attributes.length - 1; i >= 0; i--) { var a = el.attributes[i]; if (a.name.toLowerCase().startsWith('on') && typeof a.value === 'string' && a.value.trim() !== '') { var r = { type: '内联事件', detail: '属性: ' + a.name + '="' + a.value + '"' }; if (!Whitelisting.isReasonWhitelisted(r)) { LogManager.add(this.moduleKey, el, r); el.removeAttribute(a.name); mod = true; } } } } ['href', 'src', 'action', 'data', 'formaction'].forEach(function (attr) { var v = el.getAttribute(attr); if (v && typeof v === 'string' && v.trim().toLowerCase().startsWith('javascript:')) { var r = { type: 'javascript URL', detail: attr + '="' + v + '"' }; if (!Whitelisting.isReasonWhitelisted(r)) { LogManager.add(el.tagName === 'SCRIPT' ? 'removeInlineScripts' : 'removeInlineScripts', el, r); el.removeAttribute(attr); mod = true; } } }); if (mod) ProcessedElementsCache.markAsProcessed(el); return mod; } updateStrictMode() { if (this.enabled) { this.onDisable(); this.onEnable(); } } } class RemoveExternalScriptsModule extends BaseModule { constructor() { super('removeExternalScripts'); } onEnable() { var self = this; _document.querySelectorAll('script[src]').forEach(function (s) { self.checkElement(s); }); this.observer = new _MutationObserver(function (ms) { for (const m of ms) { if (m.type === 'childList') { for (const n of m.addedNodes) { if (n.nodeType === 1) { if (n.tagName === 'SCRIPT' && n.src) self.checkElement(n); if (n.querySelectorAll) n.querySelectorAll('script[src]').forEach(function (s) { self.checkElement(s); }); } } } else if (m.type === 'attributes' && m.attributeName === 'src') { var el = m.target; if (el.tagName === 'SCRIPT' && el.src) self.checkElement(el); } } }); this.observer.observe(_document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['src'] }); } _checkElement(el) { if (el.tagName === 'SCRIPT' && el.src) return TAG_HANDLERS.SCRIPT.check(el, this.moduleKey, { type: '外联脚本移除', detail: 'SRC: ' + Utils.truncateString(el.src, CONFIG.LOG_DETAIL_LENGTH) }); return false; } } class ScriptBlacklistModeModule extends BaseModule { constructor() { super('scriptBlacklistMode'); this.activeBlacklistSet = null; this.lastBlacklistHash = ''; } updateActiveBlacklistSet() { var h = Array.from(currentConfig.scriptBlacklist).sort().join('|') + '|' + currentConfig.builtinBlacklistEnabled + '|' + Array.from(currentConfig.removedBuiltinKeywords).sort().join('|'); if (h !== this.lastBlacklistHash) { this.lastBlacklistHash = h; this.activeBlacklistSet = Utils.getActiveBlacklistSet(); } } onEnable() { var self = this; this.updateActiveBlacklistSet(); _document.querySelectorAll('script').forEach(function (s) { self.checkElement(s); }); this.observer = new _MutationObserver(function (ms) { for (const m of ms) { for (const n of m.addedNodes) { if (n.nodeType === 1 && n.tagName === 'SCRIPT') self.checkElement(n); } } }); this.observer.observe(_document.documentElement, { childList: true, subtree: true }); } _checkElement(el) { if (el.tagName !== 'SCRIPT') return false; this.updateActiveBlacklistSet(); var bl = this.activeBlacklistSet; if (!bl || bl.size === 0) return false; var content = el.src || el.textContent || ''; if (!content) return false; var matched = false, mk = ''; if (!this._blacklistRegex || this._lastBlacklistHash !== this.lastBlacklistHash) { this._lastBlacklistHash = this.lastBlacklistHash; var keywords = Array.from(bl).filter(function (k) { return k && k.length > 0; }); if (keywords.length === 0) { this._blacklistRegex = null; } else { var escaped = keywords.map(function (k) { return k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }); try { this._blacklistRegex = new RegExp(escaped.join('|'), 'i'); } catch (e) { this._blacklistRegex = null; } } } if (this._blacklistRegex) { var match = content.match(this._blacklistRegex); if (match) { matched = true; mk = match[0]; } } if (matched) { LogManager.add(this.moduleKey, el, { type: 'SCRIPT_BLACKLIST', detail: '命中关键词: ' + mk + ' - ' + (el.src ? 'SRC: ' + Utils.truncateString(el.src, CONFIG.LOG_DETAIL_LENGTH) : '内嵌: ' + Utils.truncateString(el.textContent, CONFIG.LOG_DETAIL_LENGTH)) }); ResourceCanceller.cancelResourceLoading(el); ProcessedElementsCache.markAsProcessed(el); return true; } return false; } } class HTMLSanitizer { constructor(mk) { this.moduleKey = mk; } filterHTMLString(html) { if (typeof html !== 'string' || !html.includes('<')) return html; var qr = /<(?:script|link|img|iframe|embed|object|a|form|base)[^>]*(?:src|href|data|action)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi; var need = false; var m; while ((m = qr.exec(html)) !== null) { if (m[1] || m[2] || m[3]) { var u = m[1] || m[2] || m[3]; if (u && shouldBlockResource(u)) { need = true; break; } } } if (!need) return html; try { var doc = new _globals.DOMParser().parseFromString(html, 'text/html'); var self = this; doc.querySelectorAll('script,link,img,iframe,embed,object,a,form,base').forEach(function (el) { var t = el.tagName.toLowerCase(); var a; if (['script', 'img', 'iframe', 'embed'].indexOf(t) !== -1) a = 'src'; else if (['link', 'a', 'base'].indexOf(t) !== -1) a = 'href'; else if (t === 'object') a = 'data'; else if (t === 'form') a = 'action'; if (!a) return; var u = el.getAttribute(a); if (!u) return; if (t === 'link') { var r = el.getAttribute('rel'); if (!r || !r.includes('stylesheet')) return; } if (shouldBlockResource(u)) { LogManager.add(self.moduleKey, null, { type: 'THIRD_PARTY_HTML_INJECTION', detail: '阻止: ' + t.toUpperCase() + ' ' + Utils.truncateString(u, CONFIG.LOG_DETAIL_LENGTH) }); if (t === 'img') { el.setAttribute(a, 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'); el.removeAttribute('srcset'); el.removeAttribute('data-src'); } else if (t === 'a' || t === 'form') el.removeAttribute(a); else el.remove(); } }); return doc.body.innerHTML; } catch (e) { return html; } } } class NetworkInterceptor { constructor(mk) { this.moduleKey = mk; this.restoredFns = []; } setupNetworkInterception() { var self = this; try { var origFetch = _fetch; _globals.fetch = new _Proxy(_fetch, { apply: function (t, ta, a) { var u = typeof a[0] === 'string' ? a[0] : a[0] ? a[0].url : undefined; if (u && shouldBlockResource(u)) { LogManager.add(self.moduleKey, null, { type: 'THIRD_PARTY', detail: 'FETCH: ' + Utils.truncateString(u, CONFIG.LOG_DETAIL_LENGTH) }); return Promise.resolve(new Response('', { status: 0, statusText: 'Blocked by AdBlocker', headers: { 'X-AdBlock-Blocked': 'true' } })); } return Reflect.apply(t, ta, a); }, }); this.restoredFns.push(function () { try { _globals.fetch = origFetch; } catch (e) { } }); } catch (e) { } try { var oo = _XMLHttpRequest.prototype.open; var os = _XMLHttpRequest.prototype.send; _XMLHttpRequest.prototype.open = new _Proxy(oo, { apply: function (t, ta, a) { if (a[1] && shouldBlockResource(a[1])) { LogManager.add(self.moduleKey, null, { type: 'THIRD_PARTY', detail: 'XHR: ' + Utils.truncateString(a[1], CONFIG.LOG_DETAIL_LENGTH) }); ta._adblockBlocked = true; ta._adblockBlockedUrl = a[1]; try { Reflect.apply(t, ta, [a[0] || 'GET', 'about:blank', a[2] !== false]); } catch (e) { } return; } ta._adblockBlocked = false; ta._adblockBlockedUrl = null; return Reflect.apply(t, ta, a); }, }); _XMLHttpRequest.prototype.send = new _Proxy(os, { apply: function (t, ta, a) { if (ta._adblockBlocked) { _setTimeout(function () { try { Object.defineProperty(ta, 'readyState', { value: 4, writable: false, configurable: true }); Object.defineProperty(ta, 'status', { value: 0, writable: false, configurable: true }); Object.defineProperty(ta, 'statusText', { value: 'Blocked', writable: false, configurable: true }); Object.defineProperty(ta, 'responseText', { value: '', writable: false, configurable: true }); Object.defineProperty(ta, 'response', { value: '', writable: false, configurable: true }); if (typeof ta.onreadystatechange === 'function') ta.onreadystatechange(); if (typeof ta.onload === 'function') ta.onload(); if (typeof ta.addEventListener === 'function') { var evt = { type: 'load', target: ta, currentTarget: ta }; ta.dispatchEvent(evt); } } catch (e) { } }, 0); return; } return Reflect.apply(t, ta, a); }, }); this.restoredFns.push(function () { try { _XMLHttpRequest.prototype.open = oo; } catch (e) { } try { _XMLHttpRequest.prototype.send = os; } catch (e) { } }); } catch (e) { } } stopInterception() { this.restoredFns.forEach(function (fn) { try { fn(); } catch (e) { } }); this.restoredFns = []; } } class ImageInterceptor { constructor(mk) { this.moduleKey = mk; this.originalImage = null; this.trackingPatterns = [ /pixel/i, /beacon/i, /track/i, /analytics/i, /__utm/i, /gtag/i, /\bga\.(?:js|gif|png)\b/i, /ping/i, /\/collect\b/i, /\/stat(istic)?s?\b/i, /\/log(ger)?\b/i, /\/metric(s)?\b/i, /\/monitor(ing)?\b/i, /\/report(ing)?\b/i, /\/hit(s)?\b/i, /\/event(s)?\b/i, /\/counter\b/i, /\/pageview\b/i, /\/activity\b/i, /\/telemetry\b/i, /\/rum\b/i, /\/error_report/i, /\/imp(ression)?s?\b/i, /\/click(s|track)?\b/i, /\/conv(ersion)?s?\b/i, /\/ref(erral)?(s)?\b/i, /\b(?:1x1|spacer|blank|px)\.(?:gif|png|jpe?g)\b/i, /\b(?:tr|trk|ct)\.(?:gif|png|jpe?g)\b/i, /\btag\.(?:js|gif|png)\b/i, /\.(?:gif|png|jpe?g)\?.*(?:id|uid|sid|pid|session|visitor|ref|source|campaign)=/i, /[?&](?:utm_source|utm_medium|utm_campaign|utm_term|utm_content)=/i, /[?&](?:gclid|fbclid|msclkid|dclid|mc_eid)=/i, /[?&](?:ref|source|medium|campaign|aff_id|click_id|sid|track_id|tid)=/i, /[?&]tid=UA-/i, /[?&]tid=G-/i, /[?&]callback=/i, /\bfingerprint/i, /\bclient_id/i, ]; } init() { var self = this; try { this.originalImage = _globals.Image; var OriginalImage = this.originalImage; var trackingPatterns = this.trackingPatterns; var ProxyConstructor = _Proxy; var isTrackingPixel = function (src) { if (!src || typeof src !== 'string') return false; for (var i = 0; i < trackingPatterns.length; i++) { if (trackingPatterns[i].test(src)) return true; } return false; }; var WrappedImage = function () { var img; if (arguments.length === 2) img = new OriginalImage(arguments[0], arguments[1]); else if (arguments.length === 1) img = new OriginalImage(arguments[0]); else img = new OriginalImage(); var srcDescriptor = Object.getOwnPropertyDescriptor(OriginalImage.prototype, 'src'); if (srcDescriptor && srcDescriptor.set) { var originalSrcSet = srcDescriptor.set; Object.defineProperty(img, 'src', { get: function () { return srcDescriptor.get.call(this); }, set: function (v) { if (v && typeof v === 'string') { if (isTrackingPixel(v) || shouldBlockResource(v)) { LogManager.add(self.moduleKey, null, { type: 'TRACKING_PIXEL', detail: '拦截追踪像素: ' + Utils.truncateString(v, CONFIG.LOG_DETAIL_LENGTH) }); return; } } return originalSrcSet.call(this, v); }, configurable: true, enumerable: true, }); } return img; }; WrappedImage.prototype = OriginalImage.prototype; _globals.Image = WrappedImage; } catch (e) { } } stop() { if (this.originalImage) { try { _globals.Image = this.originalImage; } catch (e) { } this.originalImage = null; } } } class DOMPrototypeHooker { constructor(mk, hs) { this.moduleKey = mk; this.htmlSanitizer = hs; this.restoredFns = []; this.observer = null; } setupProxyInterception() { var self = this; var lc = function (el, attr, url, tag) { LogManager.add(self.moduleKey, el, { type: 'THIRD_PARTY', detail: tag + ': ' + Utils.truncateString(url, CONFIG.LOG_DETAIL_LENGTH) }); ResourceCanceller.cancelResourceLoading(el); ProcessedElementsCache.markAsProcessed(el); }; try { var osa = _Element.prototype.setAttribute; _Element.prototype.setAttribute = new _Proxy(osa, { apply: function (t, ta, a) { var handler = TAG_HANDLERS[ta.tagName]; if (handler && shouldBlockResource(a[1])) { var attrName = a[0] && typeof a[0] === 'string' ? a[0].toLowerCase() : a[0]; if ((handler.srcAttr && handler.srcAttr === attrName) || (handler.dataSrcAttr && handler.dataSrcAttr === attrName)) { lc(ta, a[0], a[1], ta.tagName); return; } } return Reflect.apply(t, ta, a); }, }); this.restoredFns.push(function () { try { _Element.prototype.setAttribute = osa; } catch (e) { } }); } catch (e) { } for (var tag in TAG_HANDLERS) { var proto = _globals['HTML' + tag + 'Element'] ? _globals['HTML' + tag + 'Element'].prototype : undefined; if (!proto) continue; var sa = TAG_HANDLERS[tag].srcAttr; try { var desc = Object.getOwnPropertyDescriptor(proto, sa); if (desc && desc.set && desc.configurable !== false) { var os = desc.set; Object.defineProperty(proto, sa, { set: new _Proxy(os, { apply: function (t, ta, a) { if (shouldBlockResource(a[0])) { lc(ta, sa, a[0], tag); return; } return Reflect.apply(t, ta, a); }, }), get: desc.get, configurable: true, enumerable: desc.enumerable !== undefined ? desc.enumerable : true, }); this.restoredFns.push((function (p, prop, d) { return function () { try { Object.defineProperty(p, prop, d); } catch (e) { } }; })(proto, sa, desc)); } } catch (e) { } } } setupHTMLInterception() { var self = this; try { var ihd = Object.getOwnPropertyDescriptor(_Element.prototype, 'innerHTML'); if (ihd && ihd.set) { var os = ihd.set; Object.defineProperty(_Element.prototype, 'innerHTML', { set: function (v) { return os.call(this, self.htmlSanitizer.filterHTMLString(v)); }, get: ihd.get, configurable: false, enumerable: true, }); this.restoredFns.push(function () { try { Object.defineProperty(_Element.prototype, 'innerHTML', ihd); } catch (e) { } }); } } catch (e) { } try { var oi = _Element.prototype.insertAdjacentHTML; _Element.prototype.insertAdjacentHTML = new _Proxy(oi, { apply: function (t, ta, a) { return Reflect.apply(t, ta, [a[0], self.htmlSanitizer.filterHTMLString(a[1])]); } }); this.restoredFns.push(function () { try { _Element.prototype.insertAdjacentHTML = oi; } catch (e) { } }); } catch (e) { } } setupStrictDOMInterception() { var self = this; var check = function (el) { if (!el || !Utils.isElement(el)) return false; var t = el.tagName; if (TAG_HANDLERS[t]) { var h = TAG_HANDLERS[t]; var u = el[h.srcAttr] || el.getAttribute(h.srcAttr) || (h.dataSrcAttr && el.getAttribute(h.dataSrcAttr)); if (u && shouldBlockResource(u)) { LogManager.add(self.moduleKey, el, { type: 'THIRD_PARTY', detail: '严格拦截: ' + t + ': ' + Utils.truncateString(u, CONFIG.LOG_DETAIL_LENGTH) }); ResourceCanceller.cancelResourceLoading(el); ProcessedElementsCache.markAsProcessed(el); return true; } } return false; }; try { var oa = _Node.prototype.appendChild; var newAppendChild = function (child) { if (child && Utils.isElement(child)) { if (check(child)) return child; } return oa.call(this, child); }; _Node.prototype.appendChild = newAppendChild; this.restoredFns.push(function () { if (_Node.prototype.appendChild === newAppendChild) { _Node.prototype.appendChild = oa; } }); } catch (e) { } try { var oi = _Node.prototype.insertBefore; var newInsertBefore = function (newNode, referenceNode) { if (newNode && Utils.isElement(newNode)) { if (check(newNode)) return newNode; } return oi.call(this, newNode, referenceNode); }; _Node.prototype.insertBefore = newInsertBefore; this.restoredFns.push(function () { if (_Node.prototype.insertBefore === newInsertBefore) { _Node.prototype.insertBefore = oi; } }); } catch (e) { } } patchAttachShadow() { var self = this; var proto = _Element.prototype; try { if (proto.attachShadow && !proto._adblockOriginalAttachShadow) { proto._adblockOriginalAttachShadow = proto.attachShadow; proto.attachShadow = function (init) { var shadow = proto._adblockOriginalAttachShadow.call(this, init); self.observeShadowRoot(shadow); var ihd = Object.getOwnPropertyDescriptor(shadow, 'innerHTML'); if (ihd && ihd.set && ihd.configurable) { Object.defineProperty(shadow, 'innerHTML', { get: typeof ihd.get === 'function' ? function () { return ihd.get.call(this); } : undefined, set: function (v) { ihd.set.call(this, self.htmlSanitizer.filterHTMLString(v)); }, configurable: true, }); } return shadow; }; this.restoredFns.push(function () { if (proto._adblockOriginalAttachShadow) { proto.attachShadow = proto._adblockOriginalAttachShadow; delete proto._adblockOriginalAttachShadow; } }); } } catch (e) { } } observeShadowRoot(sr) { if (!sr || sr._adblockObserved) return; sr._adblockObserved = true; var self = this; var obs = new _MutationObserver(function (ms) { for (const m of ms) { for (const n of m.addedNodes) { if (n.nodeType !== 1) continue; var t = n.tagName; if (TAG_HANDLERS[t]) { var h = TAG_HANDLERS[t]; var u = n[h.srcAttr] || n.getAttribute(h.srcAttr) || (h.dataSrcAttr && n.getAttribute(h.dataSrcAttr)); if (u && shouldBlockResource(u)) { LogManager.add(self.moduleKey, n, { type: 'THIRD_PARTY', detail: 'Shadow DOM ' + t + ': ' + Utils.truncateString(u, CONFIG.LOG_DETAIL_LENGTH) }); ResourceCanceller.cancelResourceLoading(n); ProcessedElementsCache.markAsProcessed(n); } } if (n.shadowRoot) self.observeShadowRoot(n.shadowRoot); } } }); obs.observe(sr, { childList: true, subtree: true }); this.restoredFns.push(function () { obs.disconnect(); delete sr._adblockObserved; }); } stopInterception() { this.restoredFns.forEach(function (fn) { try { fn(); } catch (e) { } }); this.restoredFns = []; if (this.observer) { this.observer.disconnect(); this.observer = null; } } } class ThirdPartyInterceptionModule extends BaseModule { constructor() { super('interceptThirdParty'); this.htmlSanitizer = new HTMLSanitizer(this.moduleKey); this.networkInterceptor = new NetworkInterceptor(this.moduleKey); this.domHooker = new DOMPrototypeHooker(this.moduleKey, this.htmlSanitizer); this.imageInterceptor = new ImageInterceptor(this.moduleKey); } onEnable() { this.stopInterception(); this.domHooker.setupProxyInterception(); this.networkInterceptor.setupNetworkInterception(); this.setupMutationFallback(); this.domHooker.setupHTMLInterception(); this.domHooker.patchAttachShadow(); if (currentConfig.thirdPartyStrictMethod) this.domHooker.setupStrictDOMInterception(); this.imageInterceptor.init(); } onDisable() { this.stopInterception(); this.imageInterceptor.stop(); } stopInterception() { this.networkInterceptor.stopInterception(); this.domHooker.stopInterception(); if (this.observer) { this.observer.disconnect(); this.observer = null; } } setupMutationFallback() { var self = this; this.observer = new _MutationObserver(function (ms) { for (const m of ms) { for (const n of m.addedNodes) { if (n.nodeType !== 1) continue; var t = n.tagName; if (TAG_HANDLERS[t]) { var h = TAG_HANDLERS[t]; var u = n[h.srcAttr] || n.getAttribute(h.srcAttr) || (h.dataSrcAttr && n.getAttribute(h.dataSrcAttr)); if (u && shouldBlockResource(u)) { LogManager.add(self.moduleKey, n, { type: 'THIRD_PARTY', detail: t + ': ' + Utils.truncateString(u, CONFIG.LOG_DETAIL_LENGTH) }); ResourceCanceller.cancelResourceLoading(n); ProcessedElementsCache.markAsProcessed(n); } } if (n.shadowRoot) self.domHooker.observeShadowRoot(n.shadowRoot); } } }); this.observer.observe(_document.documentElement, { childList: true, subtree: true }); } updateStrictMode() { urlCache.clear(); } updateStrictMethod() { if (this.enabled) { this.onDisable(); this.onEnable(); } } _checkElement(el) { var t = el.tagName; if (!TAG_HANDLERS[t]) return false; var h = TAG_HANDLERS[t]; var u = el[h.srcAttr] || el.getAttribute(h.srcAttr) || (h.dataSrcAttr && el.getAttribute(h.dataSrcAttr)); if (u && shouldBlockResource(u)) { LogManager.add(this.moduleKey, el, { type: 'THIRD_PARTY', detail: t + ': ' + Utils.truncateString(u, CONFIG.LOG_DETAIL_LENGTH) }); ResourceCanceller.cancelResourceLoading(el); ProcessedElementsCache.markAsProcessed(el); return true; } return false; } } const CSPModule = { init: function () { if (currentConfig.modules.manageCSP) this.applyCSP(); }, applyCSP: function () { if (!currentConfig.modules.manageCSP) return; var ex = _document.querySelector('meta[http-equiv="Content-Security-Policy"]'); if (ex) ex.remove(); var er = currentConfig.cspRules.filter(function (r) { return r.enabled; }); if (er.length === 0) return; var dirs = {}; for (const r of er) { var p = r.rule.split(/\s+/); var d = p[0]; var v = p.slice(1); if (!dirs[d]) dirs[d] = new _Set(); v.forEach(function (x) { dirs[d].add(x); }); } var ps = ''; for (var d2 in dirs) { if (dirs.hasOwnProperty(d2)) ps += d2 + ' ' + Array.from(dirs[d2]).join(' ') + '; '; } ps = ps.trim(); if (!ps) return; var inject = function () { if (_document.head) { if (!_document.querySelector('meta[http-equiv="Content-Security-Policy"]')) { var m = _document.createElement('meta'); m.httpEquiv = 'Content-Security-Policy'; m.content = ps; _document.head.appendChild(m); } } else { new _MutationObserver(function () { this.disconnect(); if (_document.head) { if (!_document.querySelector('meta[http-equiv="Content-Security-Policy"]')) { var m2 = _document.createElement('meta'); m2.httpEquiv = 'Content-Security-Policy'; m2.content = ps; _document.head.appendChild(m2); } } }).observe(_document.documentElement, { childList: true, subtree: true }); } }; inject(); }, updateRule: function (id, enabled) { var r = currentConfig.cspRules.find(function (x) { return x.id === id; }); if (r) r.enabled = enabled; }, }; const UI_CSS = ` :root { --ab-z-index: ${CONFIG.Z_INDEX}; } .mask { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0); backdrop-filter: blur(0px); z-index: var(--ab-z-index); display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; pointer-events: auto; animation: fade-in 0.3s forwards; } .panel { background: #fff; border-radius: 20px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); padding: 16px 12px; display: flex; flex-direction: column; gap: 10px; width: 94vw; max-width: 500px; font-family: system-ui, -apple-system, sans-serif; box-sizing: border-box; position: relative; transform: scale(0.9); opacity: 0; animation: scale-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; max-height: 85vh; overflow-y: auto; } .title { margin: 0 0 6px 0; font-size: 16px; font-weight: 700; color: #1a1a1a; text-align: center; word-break: break-all; line-height: 1.3; padding: 0 8px; } .btn-group { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; } .btn-group button { flex: 1 0 calc(50% - 8px); min-width: 100px; } button { border: none; border-radius: 10px; padding: 10px 8px; cursor: pointer; font-size: 13px; font-weight: 600; transition: all 0.2s; background: #f0f2f5; color: #444; display: flex; align-items: center; justify-content: center; min-height: 40px; touch-action: manipulation; } button:hover { background: #e4e6e9; transform: translateY(-1px); } button:active { transform: scale(0.95); } button.primary { background: #007AFF; color: #fff; } button.primary:hover { background: #0063cc; box-shadow: 0 4px 12px rgba(0, 122, 255, 0.3); } button.danger { background: #ff4d4f; color: #fff; } button.danger:hover { background: #d9363e; box-shadow: 0 4px 12px rgba(255, 77, 79, 0.3); } textarea { width: 100%; height: 140px; border: 1px solid #ddd; border-radius: 10px; padding: 10px; font-family: monospace; font-size: 12px; resize: none; box-sizing: border-box; outline: none; line-height: 1.4; } textarea:focus { border-color: #007AFF; box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1); } select { width: 100%; padding: 8px; border-radius: 10px; border: 1px solid #ddd; outline: none; font-size: 13px; } .module-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 8px; } .module-switch { display: flex; align-items: center; justify-content: space-between; padding: 6px 10px; background: #f8f9fa; border-radius: 10px; border: 1px solid #eee; } .switch-label { font-size: 13px; font-weight: 600; color: #333; display: flex; align-items: center; gap: 6px; } .info-icon { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; background: #007AFF; color: white; border-radius: 50%; font-size: 12px; font-weight: bold; cursor: pointer; margin-left: 4px; position: relative; } .info-icon:hover { background: #0056b3; } .info-tooltip { position: fixed; background: rgba(0, 0, 0, 0.9); color: #fff; padding: 12px 16px; border-radius: 10px; font-size: 13px; max-width: 280px; min-width: 200px; line-height: 1.5; white-space: pre-line; word-break: break-word; z-index: calc(var(--ab-z-index) + 100); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); pointer-events: auto; } .info-tooltip::before { content: ''; position: absolute; width: 0; height: 0; border: 8px solid transparent; } .info-tooltip.tooltip-bottom::before { top: -16px; left: 50%; transform: translateX(-50%); border-bottom-color: rgba(0, 0, 0, 0.9); } .info-tooltip.tooltip-top::before { bottom: -16px; left: 50%; transform: translateX(-50%); border-top-color: rgba(0, 0, 0, 0.9); } .switch { position: relative; width: 40px; height: 24px; flex-shrink: 0; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 24px; touch-action: manipulation; } .slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .slider { background-color: #007AFF; } input:checked + .slider:before { transform: translateX(16px); } .sub-panel { max-height: 45vh; overflow-y: auto; background: #f9f9f9; padding: 12px; border-radius: 10px; border: 1px solid #eee; } .log-entry { margin-bottom: 10px; padding: 10px; background: #fff; border-radius: 8px; border-left: 4px solid #007AFF; font-size: 12px; position: relative; min-height: 60px; } .log-module { color: #007AFF; font-weight: bold; margin-bottom: 3px; font-size: 13px; } .log-content { color: #666; word-break: break-word; font-size: 11px; max-height: 180px; overflow-y: auto; white-space: normal; line-height: 1.4; padding-right: 45px; } .whitelist-btn { position: absolute; top: 8px; right: 8px; background: #34C759; color: #fff; border: none; border-radius: 5px; padding: 3px 8px; font-size: 10px; cursor: pointer; z-index: 1; } .csp-rule { display: flex; align-items: center; justify-content: space-between; padding: 10px; background: #fff; border-radius: 8px; margin-bottom: 6px; } .csp-name { font-size: 12px; color: #333; max-width: 70%; } .whitelist-item { display: flex; align-items: center; justify-content: space-between; flex-wrap: nowrap; width: 100%; padding: 8px; background: #fff; border-radius: 6px; margin-bottom: 5px; } .whitelist-text { font-size: 11px; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; min-width: 0; } .whitelist-item button { flex-shrink: 0; margin-left: 8px; } .panel::-webkit-scrollbar { width: 6px; } .panel::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; } .panel::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; } .log-entry.whitelisted { background-color: #e8f5e8; } .log-entry.keyword-whitelisted { background-color: #e0f0ff; } .log-entry.blacklisted { background-color: #ffe0e0; } .toast-container { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); z-index: calc(var(--ab-z-index) + 7); pointer-events: none; display: flex; flex-direction: column; align-items: center; gap: 8px; } .toast { background: rgba(0, 0, 0, 0.85); color: #fff; padding: 12px 20px; border-radius: 10px; font-size: 14px; max-width: 85vw; text-align: center; line-height: 1.5; opacity: 0; transform: translateY(10px); animation: toast-in 0.3s ease forwards; white-space: pre-line; word-break: break-word; } .confirm-msg { text-align: center; font-size: 14px; color: #555; padding: 16px 8px; line-height: 1.6; white-space: pre-line; } @keyframes toast-in { to { opacity: 1; transform: translateY(0); } } @keyframes toast-out { to { opacity: 0; transform: translateY(10px); } } @media (max-width: 375px) { .panel { padding: 12px 8px; border-radius: 14px; } .title { font-size: 15px; } .switch-label { font-size: 12px; } .module-grid { grid-template-columns: 1fr; } .btn-group button { min-height: 36px; font-size: 12px; } .log-content { font-size: 10px; } .whitelist-text { font-size: 10px; } .csp-name { font-size: 11px; } } @media (orientation: landscape) and (max-height: 500px) { .btn-group button { flex: 1 0 100%; min-width: 80px; } .module-grid { grid-template-columns: repeat(3, 1fr); } .panel { max-height: 95vh; padding: 12px 10px; } .sub-panel { max-height: 50vh; } } @media (orientation: landscape) and (max-height: 350px) { .module-switch { padding: 4px 8px; } .switch-label { font-size: 11px; } .btn-group { gap: 4px; } .btn-group button { padding: 6px 8px; min-height: 32px; font-size: 11px; } .sub-panel { max-height: 40vh; padding: 8px; } } @media (prefers-color-scheme: dark) { .panel { background: #1c1c1e; color: #fff; } .title { color: #fff; } button { background: #2c2c2e; color: #ccc; } button:hover { background: #3a3a3c; } textarea { background: #2c2c2e; border-color: #444; color: #eee; } select { background: #2c2c2e; border-color: #444; color: #eee; } .module-switch { background: #2c2c2e; border-color: #444; } .switch-label { color: #eee; } .sub-panel { background: #2c2c2e; border-color: #444; } .log-entry { background: #1c1c1e; } .csp-rule { background: #1c1c1e; } .whitelist-item { background: #1c1c1e; } .whitelist-text { color: #eee; } .panel::-webkit-scrollbar-track { background: #2c2c2e; } .panel::-webkit-scrollbar-thumb { background: #555; } .log-entry.whitelisted { background-color: #2a4a2a; } .log-entry.keyword-whitelisted { background-color: #2a3a5a; } .log-entry.blacklisted { background-color: #5a2a2a; } .toast { background: rgba(255, 255, 255, 0.9); color: #1c1c1e; } .confirm-msg { color: #ddd; } .info-tooltip { background: rgba(255, 255, 255, 0.95); color: #1c1c1e; } .info-tooltip.tooltip-bottom::before { border-bottom-color: rgba(255, 255, 255, 0.95); } .info-tooltip.tooltip-top::before { border-top-color: rgba(255, 255, 255, 0.95); } } @keyframes fade-in { to { background: rgba(0, 0, 0, 0.3); backdrop-filter: blur(8px); } } @keyframes scale-in { to { transform: scale(1); opacity: 1; } } `; let panelIdCounter = 0; class PanelManager { constructor() { this.shadowRoot = null; this.settingsContainer = null; this.toastContainer = null; this.isAnimating = false; this.currentController = null; this.activeTooltip = null; } ensureShadow() { if (this.shadowRoot) return; this.settingsContainer = _document.createElement('div'); this.settingsContainer.id = 'ad-blocker-settings-container'; this.settingsContainer.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;z-index:' + (CONFIG.Z_INDEX + 7) + ';pointer-events:none;'; this.settingsContainer.setAttribute('data-adblock-safe', 'true'); _document.documentElement.appendChild(this.settingsContainer); this.shadowRoot = this.settingsContainer.attachShadow({ mode: 'closed' }); var style = _document.createElement('style'); style.textContent = UI_CSS; this.shadowRoot.appendChild(style); this.toastContainer = _document.createElement('div'); this.toastContainer.className = 'toast-container'; this.shadowRoot.appendChild(this.toastContainer); ProcessedElementsCache.markAsProcessed(this.settingsContainer); this.checkAndShowPendingFailures(); } checkAndShowPendingFailures() { var failedHooks = RedirectBlocker.getPendingFailureNotification(); if (failedHooks && failedHooks.length > 0) { this.showToast('⚠️ 部分跳转拦截功能未生效:\n' + failedHooks.join(', ') + '\n可能与其他脚本冲突,建议刷新或检查兼容模式'); } } showToast(message, duration) { duration = duration || 3500; this.ensureShadow(); if (!this.shadowRoot || !this.toastContainer) return; var t = _document.createElement('div'); t.className = 'toast'; t.textContent = message; this.toastContainer.appendChild(t); _setTimeout(function () { t.style.animation = 'toast-out 0.3s ease forwards'; _setTimeout(function () { if (t.parentNode) t.parentNode.removeChild(t); }, 300); }, duration); } showInfoTooltip(infoIcon, message) { this.closeInfoTooltip(); this.ensureShadow(); if (!this.shadowRoot) return; var tooltip = _document.createElement('div'); tooltip.className = 'info-tooltip'; tooltip.textContent = message; this.shadowRoot.appendChild(tooltip); this.activeTooltip = tooltip; var iconRect = infoIcon.getBoundingClientRect(); var tooltipRect = tooltip.getBoundingClientRect(); var viewportWidth = window.innerWidth; var viewportHeight = window.innerHeight; var tooltipWidth = tooltipRect.width || 280; var tooltipHeight = tooltipRect.height || 100; var left = iconRect.left + iconRect.width / 2 - tooltipWidth / 2; var top = iconRect.bottom + CONFIG.TOOLTIP_GAP; if (left < 10) left = 10; if (left + tooltipWidth > viewportWidth - 10) left = viewportWidth - tooltipWidth - 10; if (top + tooltipHeight > viewportHeight - 10) { top = iconRect.top - tooltipHeight - CONFIG.TOOLTIP_GAP; tooltip.classList.add('tooltip-top'); } else { tooltip.classList.add('tooltip-bottom'); } tooltip.style.left = left + 'px'; tooltip.style.top = top + 'px'; var self = this; var closeHandler = function (e) { if (e.target !== tooltip && e.target !== infoIcon && !tooltip.contains(e.target)) { self.closeInfoTooltip(); _document.removeEventListener('click', closeHandler, true); } }; _setTimeout(function () { _document.addEventListener('click', closeHandler, true); }, 10); } closeInfoTooltip() { if (this.activeTooltip) { if (this.activeTooltip.parentNode) { this.activeTooltip.parentNode.removeChild(this.activeTooltip); } this.activeTooltip = null; } } showConfirm(message, onConfirm) { var panelManager = this; this.createPanel({ title: '⚠️ 操作确认', contentHtml: '
' + escapeHtml(message) + '
', buttons: [ { id: 'confirmOk', text: '确认执行', class: 'danger', onclick: function () { panelManager.closeCurrentMask(); if (onConfirm) onConfirm(); } }, { id: 'confirmNo', text: '取消', onclick: function (e, m) { if (m && m._closePanel) m._closePanel(); } }, ], hideBackButton: true, onBack: function () { }, }); } closeCurrentMask() { var m = this.shadowRoot ? this.shadowRoot.querySelector('.mask') : null; if (!m) return; if (this.currentController) { this.currentController.abort(); this.currentController = null; } var pid = m.getAttribute('data-panel-id'); if (pid) teardownNavigationBlocking(pid); m.style.transition = 'none'; m.remove(); this.isAnimating = false; this.closeInfoTooltip(); } createPanel(options) { if (this.isAnimating) return null; var title = options.title; var contentHtml = options.contentHtml; var onClose = options.onClose; var onBack = options.onBack; var buttons = options.buttons || []; var hideBackButton = options.hideBackButton || false; var isRootPanel = options.isRootPanel || false; this.ensureShadow(); this.closeCurrentMask(); var panelId = 'panel_' + ++panelIdCounter; setupNavigationBlocking(panelId); var controller = _AbortController ? new _AbortController() : null; var signal = controller ? controller.signal : undefined; this.currentController = controller; var self = this; var mask = _document.createElement('div'); mask.className = 'mask'; mask.setAttribute('data-adblock-safe', 'true'); mask.setAttribute('data-panel-id', panelId); var btnHtml = buttons.map(function (b) { return (''); }).join(''); var backHtml = hideBackButton ? '' : ''; mask.innerHTML = '
' + escapeHtml(title) + '
' + contentHtml + '
' + btnHtml + backHtml + '
'; var closePanel = function (anim) { if (anim === undefined) anim = true; if (!mask.parentNode) return; teardownNavigationBlocking(panelId); var doRemove = function () { if (controller) controller.abort(); mask.remove(); self.isAnimating = false; if (self.currentController === controller) self.currentController = null; if (onClose) onClose(); }; if (anim) { self.isAnimating = true; var onTE = function () { mask.removeEventListener('transitionend', onTE); doRemove(); }; mask.addEventListener('transitionend', onTE); mask.style.opacity = '0'; _setTimeout(function () { if (mask.parentNode) { mask.removeEventListener('transitionend', onTE); doRemove(); } }, 500); } else doRemove(); }; mask._closePanel = closePanel; var handleBack = function () { if (isRootPanel) return; closePanel(); if (onBack) onBack(); else self.showSettings(); }; mask.addEventListener('click', function (e) { var path = e.composedPath(); var isPanel = path.some(function (el) { return Utils.isPanelElement(el); }); if (!isPanel && e.target === mask) { handleBack(); return; } if (isPanel) { if (!e.target.closest('button, input, textarea, select, label, .info-icon, .whitelist-btn')) { handleBack(); return; } } var button = e.target.closest('button'); if (!button) return; var action = button.dataset.action || button.dataset.id; if (action === 'backBtn') { handleBack(); return; } var matched = buttons.find(function (b) { return b.id === action; }); if (matched && matched.onclick) { matched.onclick(e, mask); return; } self.handlePanelAction(action, button.dataset.index, button, mask, e); }, { signal: signal }); mask.addEventListener('change', function (e) { var t = e.target; if (t.matches('.module-toggle')) { var k = t.dataset.key; currentConfig.modules[k] = t.checked; ConfigUpdater.saveNow(); resetAllCachesAndStates(); if (k === 'removeInlineScripts' && inlineScriptsModule) inlineScriptsModule.init(); if (k === 'interceptThirdParty' && thirdPartyModule) thirdPartyModule.init(); if (k === 'blockDynamicScripts') { t.checked ? DynamicScriptInterceptor.enable() : DynamicScriptInterceptor.disable(); } if (k === 'redirectBlockerEnabled') { if (t.checked) { RedirectBlocker.init(); var pendingFailures = RedirectBlocker.getPendingFailureNotification(); if (pendingFailures && pendingFailures.length > 0) { self.showToast('⚠️ 部分跳转拦截功能未生效:\n' + pendingFailures.join(', ') + '\n建议刷新或检查兼容模式'); } } else { var restored = RedirectBlocker.disable(); if (!restored) self.showToast('部分原生方法可能已被页面锁定无法还原,建议刷新页面'); } } if (strongBlockingEnabled) disableStrongBlocking(); var any = Object.values(currentConfig.modules).some(function (v) { return v === true; }); if (currentConfig.residualCleanupEnabled && any) ResidualCleaner.init(); else ResidualCleaner.stop(); } else if (t.matches('.csp-toggle')) { CSPModule.updateRule(parseInt(t.dataset.id), t.checked); currentConfig.modules.manageCSP = currentConfig.cspRules.some(function (r) { return r.enabled; }); ConfigUpdater.saveNow(); _location.reload(); } else if (t.matches('.advanced-toggle')) { var k2 = t.dataset.key; if (k2 === 'spoofUAEnabled') { if (t.checked) { if (currentConfig.simplePlatformSpoof) { currentConfig.simplePlatformSpoof = false; var simpleSpoofToggle = mask.querySelector('.advanced-toggle[data-key="simplePlatformSpoof"]'); if (simpleSpoofToggle) simpleSpoofToggle.checked = false; self.showToast('UA模拟(GM请求)已开启,自动关闭"苹果设备模拟"'); } } } else if (k2 === 'simplePlatformSpoof') { if (t.checked) { if (currentConfig.spoofUAEnabled) { currentConfig.spoofUAEnabled = false; var uaSpoofToggle = mask.querySelector('.advanced-toggle[data-key="spoofUAEnabled"]'); if (uaSpoofToggle) uaSpoofToggle.checked = false; self.showToast('苹果设备模拟已开启,自动关闭"UA模拟(GM请求)"'); } } } currentConfig[k2] = t.checked; ConfigUpdater.saveNow(); if (k2 === 'inlineScriptStrictMode' && inlineScriptsModule) inlineScriptsModule.updateStrictMode(); if (k2 === 'thirdPartyStrictMode' && thirdPartyModule) thirdPartyModule.updateStrictMode(); if (k2 === 'thirdPartyStrictMethod' && thirdPartyModule) thirdPartyModule.updateStrictMethod(); if (k2 === 'residualCleanupEnabled') { var any2 = Object.values(currentConfig.modules).some(function (v) { return v === true; }); if (currentConfig.residualCleanupEnabled && any2) ResidualCleaner.init(); else ResidualCleaner.stop(); } if (k2 === 'cssUniversalHideEnabled') CSSUniversalHider.init(); if (k2 === 'builtinBlacklistEnabled' && t.checked) currentConfig.removedBuiltinKeywords.clear(); if (k2 === 'redirectBlockerCompatibilityMode') { self.showToast('跳转拦截兼容已' + (t.checked ? '开启' : '关闭') + ',刷新页面后生效'); } } }, { signal: signal }); this.shadowRoot.appendChild(mask); mask.style.pointerEvents = 'auto'; mask.querySelector('.panel').style.pointerEvents = 'auto'; return mask; } renderLogItem(log, index) { var status = getLogWhitelistStatus(log); var logEntryClass = 'log-entry' + (status ? ' ' + status : ''); var isWL = status !== ''; var domainHint = log.domain ? '
域名: ' + escapeHtml(log.domain) + '
' : ''; return ('
' + escapeHtml(log.module) + ' - ' + escapeHtml(log.element) + '
' + escapeHtml(log.content) + '
' + domainHint + '
'); } handlePanelAction(action, index, button, mask, event) { var self = this; switch (action) { case 'deleteDiaryItem': { var arr = Array.from(currentConfig.whitelist); var idx = parseInt(index); if (!isNaN(idx) && idx >= 0 && idx < arr.length && arr[idx]) { currentConfig.whitelist.delete(arr[idx]); ConfigUpdater.saveNow(); resetAllCachesAndStates(); self.showDiaryWhitelistPanel(); } break; } case 'deleteKeywordItem': { var arr2 = Array.from(currentConfig.keywordWhitelist); var idx2 = parseInt(index); if (!isNaN(idx2) && idx2 >= 0 && idx2 < arr2.length && arr2[idx2]) { currentConfig.keywordWhitelist.delete(arr2[idx2]); ConfigUpdater.saveNow(); resetAllCachesAndStates(); self.showKeywordWhitelistPanel(); } break; } case 'deleteBlacklistItem': { var arr3 = Utils.getActiveBlacklistArray(); var idx3 = parseInt(index); if (!isNaN(idx3) && idx3 >= 0 && idx3 < arr3.length && arr3[idx3]) { if (BUILTIN_BLACKLIST_KEYWORDS.indexOf(arr3[idx3]) !== -1) currentConfig.removedBuiltinKeywords.add(arr3[idx3]); else currentConfig.scriptBlacklist.delete(arr3[idx3]); ConfigUpdater.saveNow(); resetAllCachesAndStates(); self.showScriptBlacklistPanel(); } break; } case 'deleteThirdPartyItem': { var wl = currentConfig.thirdPartyWhitelist; var idx4 = parseInt(index); if (!isNaN(idx4) && idx4 >= 0 && idx4 < wl.length && wl[idx4]) { var removedItem = wl[idx4]; wl.splice(idx4, 1); if (removedItem) Whitelisting.removeKeywordsMatchingDomain(removedItem); ConfigUpdater.saveNow(); resetAllCachesAndStates(); self.showThirdPartyPanel(); } break; } case 'whitelistLog': { var logs = LogManager.logs; var idx5 = parseInt(index); if (!isNaN(idx5) && idx5 >= 0 && idx5 < logs.length && logs[idx5] && logs[idx5].contentIdentifier) { var le = logs[idx5]; var isTPD = currentConfig.modules.interceptThirdParty && le.domain; if (isTPD && le.domain) { if (currentConfig.thirdPartyWhitelist.indexOf(le.domain) === -1) currentConfig.thirdPartyWhitelist.push(le.domain); logs.forEach(function (e) { if (e.domain === le.domain && currentConfig.thirdPartyWhitelist.indexOf(e.domain) === -1) currentConfig.thirdPartyWhitelist.push(e.domain); }); ConfigUpdater.saveNow(); mask.querySelectorAll('.log-entry').forEach(function (div) { var btn = div.querySelector('.whitelist-btn'); if (!btn) return; var bi = parseInt(btn.dataset.index); if (!isNaN(bi) && logs[bi] && logs[bi].domain === le.domain) { div.classList.add('whitelisted'); btn.disabled = true; btn.textContent = '已加白'; btn.style.backgroundColor = '#999'; } }); } else { if (!currentConfig.whitelist.has(le.contentIdentifier)) currentConfig.whitelist.add(le.contentIdentifier); ConfigUpdater.saveNow(); var ld = button.closest('.log-entry'); if (ld) { ld.classList.add('whitelisted'); button.disabled = true; button.textContent = '已加白'; button.style.backgroundColor = '#999'; } } resetAllCachesAndStates(); } break; } case 'addToBlacklist': { var enc = button.dataset.content; if (enc) { currentConfig.scriptBlacklist.add(decodeURIComponent(enc)); ConfigUpdater.saveNow(); var ld2 = button.closest('.log-entry'); if (ld2) { var tb = ld2.querySelector('button[data-action="addToBlacklist"]'); if (tb) { tb.disabled = true; tb.textContent = '已加黑'; tb.style.backgroundColor = '#999'; } ld2.classList.add('blacklisted'); } } break; } case 'addKeywordWhitelist': { var inp = mask.querySelector('#keywordWhitelistInput'); var kw = inp.value.trim(); if (kw) { Whitelisting.addKeyword(kw); inp.value = ''; self.showLogsPanel(); } break; } case 'addWhitelist': { var inp2 = mask.querySelector('#newWhitelist'); var v = inp2.value.trim(); if (v) { v = v.replace(/^https?:\/\//, ''); try { v = new URL('http://' + v).hostname; } catch (e) { } if (currentConfig.thirdPartyWhitelist.indexOf(v) === -1) currentConfig.thirdPartyWhitelist.push(v); ConfigUpdater.saveNow(); inp2.value = ''; resetAllCachesAndStates(); self.showThirdPartyPanel(); } break; } case 'addBlacklist': { var inp3 = mask.querySelector('#newBlacklistKeyword'); var kw2 = inp3.value.trim(); if (kw2) { currentConfig.scriptBlacklist.add(kw2); ConfigUpdater.saveNow(); inp3.value = ''; resetAllCachesAndStates(); self.showScriptBlacklistPanel(); } break; } case 'addBlacklistFromDomain': { var d = decodeURIComponent(button.dataset.domain); if (d) { currentConfig.scriptBlacklist.add(d); ConfigUpdater.saveNow(); var ld3 = button.closest('.log-entry'); if (ld3) { var tb2 = ld3.querySelector('button[data-action="addBlacklistFromDomain"]'); if (tb2) { tb2.disabled = true; tb2.textContent = '域名已加黑'; tb2.style.backgroundColor = '#999'; } ld3.classList.add('blacklisted'); } } break; } case 'clearAllDiary': { self.showConfirm('确定清空所有日记白名单吗?', function () { currentConfig.whitelist.clear(); ConfigUpdater.saveNow(); resetAllCachesAndStates(); self.showDiaryWhitelistPanel(); }); break; } case 'clearAllKeyword': { self.showConfirm('确定清空所有关键词白名单吗?', function () { currentConfig.keywordWhitelist.clear(); ConfigUpdater.saveNow(); resetAllCachesAndStates(); self.showKeywordWhitelistPanel(); }); break; } case 'clearAllBlacklist': { self.showConfirm('确定清空所有脚本黑名单吗?', function () { currentConfig.scriptBlacklist.clear(); currentConfig.removedBuiltinKeywords.clear(); ConfigUpdater.saveNow(); resetAllCachesAndStates(); self.showScriptBlacklistPanel(); }); break; } case 'clearAllThirdParty': { self.showConfirm('确定清空所有第三方白名单吗?', function () { currentConfig.thirdPartyWhitelist = []; ConfigUpdater.saveNow(); resetAllCachesAndStates(); self.showThirdPartyPanel(); }); break; } case 'showScriptList': { self.showScriptListPanel(); break; } case 'enableCSP': { currentConfig.modules.manageCSP = true; ConfigUpdater.saveNow(); _location.reload(); break; } case 'disableCSP': { currentConfig.modules.manageCSP = false; ConfigUpdater.saveNow(); _location.reload(); break; } case 'allOn': { currentConfig.cspRules.forEach(function (r) { r.enabled = true; }); currentConfig.modules.manageCSP = true; ConfigUpdater.saveNow(); _location.reload(); break; } case 'allOff': { currentConfig.cspRules.forEach(function (r) { r.enabled = false; }); currentConfig.modules.manageCSP = false; ConfigUpdater.saveNow(); _location.reload(); break; } case 'closePanel': { if (mask._closePanel) mask._closePanel(); break; } case 'showAdvancedSettings': { self.showAdvancedSettingsPanel(); break; } case 'loadMoreLogs': { self.loadMoreLogs(mask); break; } case 'loadMoreScripts': { self.loadMoreScripts(mask); break; } } } showAdvancedSettingsPanel() { this.closeCurrentMask(); var self = this; var c = currentConfig; var infoMap = { redirectBlocker: '启用后,尝试拦截所有跳转到第三方、非同源域名的行为。\n\n若关闭"拦截第三方资源-严格模式",则允许同主域的子域名跳转。', redirectCompat: '需要配合同源跳转拦截功能,开启:允许其他用户脚本(如网页翻译、重定向净化等)劫持相同的浏览器API。如果其他脚本后加载,可能覆盖本脚本的拦截功能。\n\n关闭:锁定API劫持,确保跳转拦截始终生效。如果同时安装了翻译/重定向净化等脚本,它们可能报错失效。', inlineStrict: '严格模式:额外拦截内联事件(如onclick)和javascript:URL,适用于拦截点击弹窗、悬浮广告等。\n宽松模式(默认):仅移除内嵌脚本内容。', dynamicScriptStrict: '严格模式(增强版v5.7):\n\n[静态分析层] 增强的正则模式,覆盖以下写法:\n• 点号语法:document.write()\n• 方括号语法:document["write"]()、document[\'write\']()\n• 全局别名:self.open()、top.open()、parent.open()\n• 变量别名+方括号:d["write"]()、loc["assign"]()\n• 动态属性访问:document[variable]()\n• 字符串拼接构造:\'wri\'+\'te\'\n\n[混淆检测层] 自动识别以下混淆手段:\n• String.fromCharCode / atob 编码\n• 数组join拼接\n• charCodeAt+toString转换\n• 十六进制/Unicode转义\n• decodeURIComponent+replace链\n• 方括号密集的动态属性访问\n\n[间接调用检测层] 识别回调中仅调用其他函数的短闭包,标记为可疑并启用运行时监控。\n\n[运行时监控层] 对被标记的可疑回调,在执行期间启用深度监控。即使静态分析无法检测的隐藏逻辑,在运行时调用 document.write / location.assign / window.open 等API时仍会被 WriteHookManager 和 RedirectBlocker 拦截。\n\n宽松模式(默认):仅检测eval()和new Function()。\n\n注意:开启严格模式可能误拦正常功能,建议配合拦截日志使用,将误拦项加入白名单。', thirdPartyStrict: '严格模式:拦截所有第三方资源(包括子域名和兄弟域名)。\n宽松模式(默认):只拦截完全无关的第三方域名(主域名不同)。', thirdPartyMethod: '严格劫持方式:在宽松模式基础上,额外劫持 appendChild 方法,拦截动态创建的第三方资源。', residualCleanup: '清空残留容器:在浏览器空闲时清理有明显广告样式且内容为空的容器。', cssUniversalHide: 'CSS通用隐藏:动态检测高z-index且非静态定位的广告元素并隐藏。可能误伤正常元素。', iframeUIFix: '当在某些视频网站打开设置面板时,如果视频播放器和网页都弹出了控制面板,开启此选项可能修复该问题。', builtinBlacklist: '启用后,脚本黑名单模式将使用内置的广告特征关键词列表进行拦截。关闭则不使用内置列表。', spoofUA: '使用GM_xmlhttpRequest方法模拟苹果设备,尝试绕过服务端检测', }; this.createPanel({ title: '高级设置中心', contentHtml: `
同源跳转拦截 !
跳转拦截兼容 !
清空残留容器 !
CSS通用隐藏 !
内置黑名单 !
移除内嵌脚本-严格模式 !
拦截动态脚本-严格模式 !
拦截第三方资源-严格模式 !
拦截第三方-严格劫持方式 !
禁止在嵌套框架中显示面板 !
UA模拟(GM请求) !
`, buttons: [], onBack: function () { self.showSettings(); }, }); var me = this.shadowRoot.querySelector('.mask:last-child'); if (me) { var panelEl = me.querySelector('.panel'); if (panelEl) { panelEl.addEventListener('click', function (e) { var infoIcon = e.target.closest('.info-icon'); if (infoIcon && infoIcon.dataset.info) { e.stopPropagation(); var message = infoMap[infoIcon.dataset.info] || '点击查看详情'; self.showInfoTooltip(infoIcon, message); } }, { signal: me._closePanel ? undefined : undefined }); } } } showSettings() { this.closeCurrentMask(); var self = this; var ms = Object.keys(MODULE_NAMES).map(function (k) { return ('
' + escapeHtml(MODULE_NAMES[k]) + '
'); }).join(''); this.createPanel({ title: '🛡️广告拦截设置', contentHtml: '
功能模块:
' + ms + '
', buttons: [ { id: 'viewLogs', text: '拦截日志 (' + LogManager.logs.length + ')', onclick: function () { self.showLogsPanel(); } }, { id: 'manageCSP', text: 'CSP策略管理', onclick: function () { self.showCSPPanel(); } }, { id: 'manageDiaryWhitelist', text: '日记白名单', onclick: function () { self.showDiaryWhitelistPanel(); } }, { id: 'manageThirdParty', text: '第三方白名单', onclick: function () { self.showThirdPartyPanel(); } }, { id: 'manageKeywordWhitelist', text: '关键词白名单', onclick: function () { self.showKeywordWhitelistPanel(); } }, { id: 'manageScriptBlacklist', text: '脚本黑名单', onclick: function () { self.showScriptBlacklistPanel(); } }, { id: 'showAdvancedSettings', text: '高级设置中心', onclick: function () { self.showAdvancedSettingsPanel(); } }, { id: 'closePanel', text: '返回网页', onclick: function (e, m) { if (m._closePanel) m._closePanel(); } }, ], hideBackButton: true, isRootPanel: true, }); } showDiaryWhitelistPanel() { this.closeCurrentMask(); var self = this; var wl = Array.from(currentConfig.whitelist); var prefixMap = { 'INLINE_EVENT: ': '内联事件: ', 'JAVASCRIPT_URL: ': 'JS URL: ', 'SCRIPT_CONTENT: ': '脚本内容: ', 'SCRIPT_SRC: ': '脚本SRC: ', 'EVAL_HASH: ': 'Eval哈希: ', 'FUNCTION_HASH: ': 'Function哈希: ', 'DOCUMENT_WRITE_HASH: ': 'document.write哈希: ', 'SETTIMEOUT_HASH: ': 'setTimeout哈希: ', 'SETINTERVAL_HASH: ': 'setInterval哈希: ', 'REQUESTANIMATIONFRAME_HASH: ': 'RAF哈希: ' }; var html = wl.length > 0 ? wl.map(function (item, i) { var display = item; for (var prefix in prefixMap) { if (prefixMap.hasOwnProperty(prefix) && item.startsWith(prefix)) { display = prefixMap[prefix] + item.substring(prefix.length); break; } } return ('
' + escapeHtml(display) + '
'); }).join('') : '
日记白名单为空
'; this.createPanel({ title: '日记白名单 (' + wl.length + '项)', contentHtml: '
查看和管理拦截日记中添加的白名单条目
' + html + '
', buttons: [], onBack: function () { self.showSettings(); } }); } showKeywordWhitelistPanel() { this.closeCurrentMask(); var self = this; var kws = Array.from(currentConfig.keywordWhitelist); var html = kws.length > 0 ? kws.map(function (kw, i) { return ('
' + escapeHtml(kw) + '
'); }).join('') : '
关键词白名单为空
'; this.createPanel({ title: '关键词白名单 (' + kws.length + '项)', contentHtml: '
查看和管理通过关键词添加的白名单条目
' + html + '
', buttons: [], onBack: function () { self.showSettings(); } }); } showScriptBlacklistPanel() { this.closeCurrentMask(); var self = this; var bl = Utils.getActiveBlacklistArray(); var html = bl.length > 0 ? bl.map(function (kw, i) { return ('
' + escapeHtml(kw) + '
'); }).join('') : '
脚本黑名单为空
'; this.createPanel({ title: '脚本黑名单 (' + bl.length + '项)', contentHtml: '
脚本黑名单模式将拦截内嵌/外联脚本中匹配这些关键词的资源
' + html + '
', buttons: [], onBack: function () { self.showSettings(); } }); _setTimeout(function () { var me = self.shadowRoot && self.shadowRoot.querySelector('.mask:last-child'); if (!me) return; var inp = me.querySelector('#newBlacklistKeyword'); if (inp) { inp.focus(); inp.addEventListener('keypress', function (e) { if (e.key === 'Enter') { var b = me.querySelector('[data-id="addBlacklist"]'); if (b) b.click(); } }); } }, 0); } showScriptListPanel() { this.closeCurrentMask(); var self = this; var scripts = Array.from(_document.scripts); var items = scripts.map(function (s, i) { return { index: i + 1, isExternal: !!s.src, content: Utils.truncateString(s.src ? s.src : s.textContent, CONFIG.LOG_DETAIL_LENGTH), script: s }; }); var bls = Utils.getActiveBlacklistSet(); var PS = CONFIG.PAGE_SIZE; var initial = items.slice(0, PS); var renderItem = function (item) { var sContent = item.isExternal ? item.script.src : item.script.textContent; var isBL = bls.has(sContent); var domainHint = '', extractBtn = '', isDBL = false; if (item.isExternal && item.script.src) { try { var d = new URL(item.script.src, _location.href).hostname; if (d) { domainHint = '
域名: ' + escapeHtml(d) + '
'; isDBL = bls.has(d); extractBtn = ''; } } catch (e) { } } var cls = 'log-entry' + (isBL || isDBL ? ' blacklisted' : ''); return ('
脚本 #' + item.index + ' - ' + (item.isExternal ? '外联' : '内嵌') + '
' + escapeHtml(item.content) + '
' + domainHint + extractBtn + '
'); }; this.createPanel({ title: '当前网页脚本列表 (共' + scripts.length + '个)', contentHtml: '
点击「加黑」将整个脚本内容添加到脚本黑名单;「加黑域名」将脚本域名加入黑名单
' + initial.map(renderItem).join('') + '
' + (items.length > PS ? '' : ''), buttons: [], onBack: function () { self.showScriptBlacklistPanel(); } }); } loadMoreScripts(mask) { var scripts = Array.from(_document.scripts); var bls = Utils.getActiveBlacklistSet(); var PS = CONFIG.PAGE_SIZE; var container = mask.querySelector('[data-panel="scriptList"]'); var btn = mask.querySelector('[data-id="loadMoreScripts"]'); var cur = container.children.length; if (cur >= scripts.length) { if (btn) btn.remove(); return; } var batch = scripts.slice(cur, cur + PS); var frag = _document.createDocumentFragment(); var tmp = _document.createElement('div'); batch.forEach(function (s, idx) { var ri = cur + idx + 1; var isExt = !!s.src; var content = Utils.truncateString(isExt ? s.src : s.textContent, CONFIG.LOG_DETAIL_LENGTH); var sContent = isExt ? s.src : s.textContent; var isBL = bls.has(sContent); var domainHint = '', extractBtn = '', isDBL = false; if (isExt && s.src) { try { var d = new URL(s.src, _location.href).hostname; if (d) { domainHint = '
域名: ' + escapeHtml(d) + '
'; isDBL = bls.has(d); extractBtn = ''; } } catch (e) { } } var cls = 'log-entry' + (isBL || isDBL ? ' blacklisted' : ''); tmp.innerHTML = '
脚本 #' + ri + ' - ' + (isExt ? '外联' : '内嵌') + '
' + escapeHtml(content) + '
' + domainHint + extractBtn + '
'; while (tmp.firstChild) frag.appendChild(tmp.firstChild); }); container.appendChild(frag); var nc = container.children.length; if (nc >= scripts.length) { if (btn) btn.remove(); } else if (btn) { btn.textContent = '加载更多 (' + nc + '/' + scripts.length + ')'; } } showLogsPanel() { this.closeCurrentMask(); var self = this; var logs = LogManager.logs; var PS = CONFIG.PAGE_SIZE; var initial = logs.slice(0, PS); this.createPanel({ title: '拦截日志 (' + logs.length + '条)', contentHtml: '
关键词加白(添加包含关键词的脚本到白名单):
' + initial.map(function (l, i) { return self.renderLogItem(l, i); }).join('') + '
' + (logs.length > PS ? '' : ''), buttons: [], onBack: function () { self.showSettings(); }, }); _setTimeout(function () { var me = self.shadowRoot && self.shadowRoot.querySelector('.mask:last-child'); if (!me) return; var inp = me.querySelector('#keywordWhitelistInput'); if (inp) { inp.focus(); inp.addEventListener('keypress', function (e) { if (e.key === 'Enter') { var b = me.querySelector('[data-id="addKeywordWhitelist"]'); if (b) b.click(); } }); } }, 0); } loadMoreLogs(mask) { var self = this; var logs = LogManager.logs; var PS = CONFIG.PAGE_SIZE; var container = mask.querySelector('[data-panel="logList"]'); var btn = mask.querySelector('[data-id="loadMoreLogs"]'); var cur = container.children.length; if (cur >= logs.length) { if (btn) btn.remove(); return; } var batch = logs.slice(cur, cur + PS); var frag = _document.createDocumentFragment(); var tmp = _document.createElement('div'); batch.forEach(function (l, i) { tmp.innerHTML = self.renderLogItem(l, cur + i); while (tmp.firstChild) frag.appendChild(tmp.firstChild); }); container.appendChild(frag); var nc = container.children.length; if (nc >= logs.length) { if (btn) btn.remove(); } else if (btn) { btn.textContent = '加载更多 (' + nc + '/' + logs.length + ')'; } } showCSPPanel() { this.closeCurrentMask(); var self = this; var rulesHtml = currentConfig.cspRules.map(function (r) { return ('
' + escapeHtml(r.name) + '
'); }).join(''); this.createPanel({ title: 'CSP策略管理', contentHtml: '
当前状态: ' + (currentConfig.modules.manageCSP ? '✅已启用' : '❌已禁用') + '
' + rulesHtml + '
', buttons: [], onBack: function () { self.showSettings(); }, }); } showThirdPartyPanel() { this.closeCurrentMask(); var self = this; var wl = currentConfig.thirdPartyWhitelist; var html = wl.length > 0 ? wl.map(function (item, i) { return ('
' + escapeHtml(item) + '
'); }).join('') : '
白名单为空
'; this.createPanel({ title: '第三方白名单 (' + wl.length + '项)', contentHtml: '
已拦截的第三方域名可以添加到白名单中
' + html + '
', buttons: [], onBack: function () { self.showSettings(); }, }); _setTimeout(function () { var me = self.shadowRoot && self.shadowRoot.querySelector('.mask:last-child'); if (!me) return; var inp = me.querySelector('#newWhitelist'); if (inp) { inp.focus(); inp.addEventListener('keypress', function (e) { if (e.key === 'Enter') { var b = me.querySelector('[data-id="addWhitelist"]'); if (b) b.click(); } }); } }, 0); } } class CentralScheduler { constructor(modules) { this.modules = modules; this.elementCheckCache = new WeakSet(); this.urlCheckCache = new LRUCache(CONFIG.CACHE_CAPACITY_SMALL, CONFIG.CACHE_TTL); } shouldProcessElement(el) { return ( Utils.isElement(el) && !this.elementCheckCache.has(el) && !ProcessedElementsCache.isProcessed(el) && !Utils.isParentProcessed(el) && el.getAttribute('data-adblock-safe') !== 'true' ); } processElement(el) { if (!this.shouldProcessElement(el)) return false; this.elementCheckCache.add(el); for (var i = 0; i < this.modules.length; i++) { var m = this.modules[i]; if (m.enabled && m.checkElement(el)) { ProcessedElementsCache.markAsProcessed(el); return true; } } return false; } clearCache() { this.elementCheckCache = new WeakSet(); this.urlCheckCache.clear(); urlCache.clear(); } } let inlineScriptsModule = null; let thirdPartyModule = null; let centralScheduler = null; let menuCommandsRegistered = false; function registerAllMenuCommands() { if (menuCommandsRegistered) return; menuCommandsRegistered = true; var ensureUI = function (callback) { if (!UIController.uiInitialized) UIController.init(); callback(); }; GM_registerMenuCommand('1 ⚙️ 广告拦截设置面板', function () { ensureUI(function () { UIController.panelManager.showSettings(); }); }); GM_registerMenuCommand( '2 🍎 苹果设备模拟 ' + (currentConfig.simplePlatformSpoof ? '✅ 已启用' : '❌ 已禁用'), function () { currentConfig.simplePlatformSpoof = !currentConfig.simplePlatformSpoof; if (currentConfig.simplePlatformSpoof) { currentConfig.spoofUAEnabled = false; } ConfigUpdater.saveNow(); GM_notification({ text: '平台模拟已' + (currentConfig.simplePlatformSpoof ? '启用' : '禁用') + ',刷新页面生效', title: '广告拦截器' }); _location.reload(); } ); GM_registerMenuCommand('3 🗑️ 清空所有白名单', function () { ensureUI(function () { UIController.panelManager.showConfirm('确定清空当前域名的所有白名单(包括日记白名单、第三方白名单、关键词白名单等)吗?', function () { Whitelisting.clearAllWhitelists(); resetAllCachesAndStates(); GM_notification({ text: '所有白名单已清空', title: '广告拦截器' }); _location.reload(); }); }); }); GM_registerMenuCommand('4 🔄 当前域名重置所有设置', function () { ensureUI(function () { UIController.panelManager.showConfirm('确定重置当前域名的所有设置吗?\n\n这将清空当前域名的所有白名单并关闭所有模块,恢复到初始状态。', function () { StorageManager.resetAllSettings(); resetAllCachesAndStates(); GM_notification({ text: '当前域名设置已重置', title: '广告拦截器' }); _location.reload(); }); }); }); GM_registerMenuCommand('5 ⚠️ 全局重置所有设置', function () { ensureUI(function () { UIController.panelManager.showConfirm('警告:此操作将清除所有域名的广告拦截配置!\n\n确定要全局重置吗?', function () { try { for (var k of GM_listValues()) { if (k.startsWith('adblock_unified_config_')) GM_deleteValue(k); } } catch (e) { } GM_notification({ text: '全局设置已重置,正在刷新页面...', title: '广告拦截器' }); _location.reload(); }); }); }); } registerAllMenuCommands(); const UIController = { uiInitialized: false, modulesInitialized: false, mutationObserver: null, batchProcessingQueue: [], batchSize: CONFIG.BATCH_SIZE, isProcessingBatch: false, lastProcessTime: 0, emaFrameTime: 16, panelManager: null, modules: [], init: function () { if (this.uiInitialized) return; this.uiInitialized = true; this.modulesInitialized = true; this.applyInitialModuleStates(); this.panelManager = new PanelManager(); this.createModules(); this.applyModuleSettings(); this.setupObservers(); this.setupResourceScan(); var any = Object.values(currentConfig.modules).some(function (v) { return v === true; }); if (currentConfig.residualCleanupEnabled && any) ResidualCleaner.init(); else ResidualCleaner.stop(); CSSUniversalHider.init(); }, applyInitialModuleStates: function () { Object.keys(DEFAULT_MODULE_STATE).forEach(function (k) { if (currentConfig.modules[k] === undefined) currentConfig.modules[k] = DEFAULT_MODULE_STATE[k]; }); }, createModules: function () { inlineScriptsModule = new RemoveInlineScriptsModule(); thirdPartyModule = new ThirdPartyInterceptionModule(); this.modules = [ inlineScriptsModule, new RemoveExternalScriptsModule(), new ScriptBlacklistModeModule(), thirdPartyModule, ]; }, applyModuleSettings: function () { this.modules.forEach(function (m) { m.init(); }); DynamicScriptInterceptor.init(); CSPModule.init(); centralScheduler = new CentralScheduler(this.modules); }, setupObservers: function () { var self = this; if (!this.modules.some(function (m) { return m.enabled; }) || !currentConfig.modules.blockDynamicScripts) return; this.mutationObserver = new _MutationObserver( Utils.throttle(function (ms) { for (var i = 0; i < ms.length; i++) { var m = ms[i]; for (var j = 0; j < m.addedNodes.length; j++) { var n = m.addedNodes[j]; if (n.nodeType === _Node.ELEMENT_NODE && !ProcessedElementsCache.isProcessed(n) && !Utils.isParentProcessed(n)) self.addToBatchProcessingQueue(n); } } }, CONFIG.THROTTLE_LIMIT) ); this.mutationObserver.observe(_document.documentElement, { childList: true, subtree: true }); this.processExistingElementsBatch(); }, setupResourceScan: function () { var self = this; if (currentConfig.modules.interceptThirdParty) { _document.addEventListener('DOMContentLoaded', function () { _setTimeout(function () { self.scanExistingResources(); }, 1000); }); } }, scanExistingResources: function () { try { _document .querySelectorAll('script[src], iframe[src], img[src], img[data-src], embed[src], object[data], link[href]') .forEach(function (el) { if (ProcessedElementsCache.isProcessed(el) || Utils.isParentProcessed(el)) return; var t = el.tagName; var u; if (t === 'SCRIPT') u = el.src; else if (t === 'IFRAME') u = el.src; else if (t === 'IMG') u = el.src || el.getAttribute('data-src'); else if (t === 'EMBED') u = el.src; else if (t === 'OBJECT') u = el.data; else if (t === 'LINK') u = el.href; if (u && shouldBlockResource(u)) { var ci = Utils.getContentIdentifier(el); if (ci && !currentConfig.whitelist.has(ci)) { LogManager.add('interceptThirdParty', el, { type: 'THIRD_PARTY_SCAN', detail: '扫描发现: ' + t + ': ' + Utils.truncateString(u, CONFIG.LOG_DETAIL_LENGTH) }); } } }); } catch (e) { } }, addToBatchProcessingQueue: function (el) { if (el.getAttribute && el.getAttribute('data-adblock-safe') === 'true') { ProcessedElementsCache.markAsProcessed(el); return; } this.batchProcessingQueue.push(el); if (!this.isProcessingBatch) this.processBatch(); }, processBatch: function () { var self = this; if (!centralScheduler) return; this.isProcessingBatch = true; var bs = this.batchSize; var alpha = 0.2; var chunk = function () { var now = performance.now(); if (self.lastProcessTime > 0) { self.emaFrameTime = alpha * (now - self.lastProcessTime) + (1 - alpha) * self.emaFrameTime; bs = self.emaFrameTime > 16 ? Math.max(5, Math.round(bs * 0.9)) : Math.min(50, Math.round(bs * 1.1)); } self.lastProcessTime = now; var batch = self.batchProcessingQueue.splice(0, bs); for (var i = 0; i < batch.length; i++) { centralScheduler.processElement(batch[i]); } if (self.batchProcessingQueue.length > 0) _requestAnimationFrame(chunk); else self.isProcessingBatch = false; }; _requestAnimationFrame(chunk); }, processExistingElementsBatch: function () { var self = this; var els = Array.from( _document.querySelectorAll('script, iframe, img, a[href], style, link[rel="preload"], link[rel="prefetch"], embed, object, link[href]') ); var process = function () { var batch = els.splice(0, self.batchSize * 2); for (var i = 0; i < batch.length; i++) { var el = batch[i]; if (!ProcessedElementsCache.isProcessed(el) && !Utils.isParentProcessed(el)) self.addToBatchProcessingQueue(el); } if (els.length > 0) _requestIdleCallback(process); }; _requestIdleCallback(process); }, }; const _UA_SPOOF_KEY = '__adblock_ua_spoof_done__'; const _DESKTOP_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; function initUASpoof() { if (!currentConfig.spoofUAEnabled) return false; if (typeof GM_xmlhttpRequest === 'undefined') return false; if (_globals[_UA_SPOOF_KEY]) return false; try { Object.defineProperty(navigator, 'platform', { get: function () { return 'MacIntel'; }, configurable: true, enumerable: true }); Object.defineProperty(navigator, 'userAgent', { get: function () { return _DESKTOP_UA; }, configurable: true, enumerable: true }); } catch (e) { } _globals[_UA_SPOOF_KEY] = true; try { GM_xmlhttpRequest({ method: 'GET', url: _location.href, headers: { 'User-Agent': _DESKTOP_UA }, anonymous: true, timeout: 10000, onload: function (response) { if (response.status >= 200 && response.status < 400 && response.responseText) { try { _document.open(); _document.write(response.responseText); _document.close(); if (suppressUIForIframe()) initModulesWithoutUI(); else UIController.init(); return; } catch (e) { } } delete _globals[_UA_SPOOF_KEY]; if (suppressUIForIframe()) initModulesWithoutUI(); else UIController.init(); }, onerror: function () { delete _globals[_UA_SPOOF_KEY]; if (suppressUIForIframe()) initModulesWithoutUI(); else UIController.init(); }, ontimeout: function () { delete _globals[_UA_SPOOF_KEY]; if (suppressUIForIframe()) initModulesWithoutUI(); else UIController.init(); }, }); return true; } catch (e) { delete _globals[_UA_SPOOF_KEY]; return false; } } function applySimplePlatformSpoof() { if (!currentConfig.simplePlatformSpoof) return; try { Object.defineProperty(navigator, 'platform', { get: function () { return 'MacIntel'; }, configurable: true, enumerable: true }); } catch (e) { } } applySimplePlatformSpoof(); function suppressUIForIframe() { if (!currentConfig.iframeUIFix) return false; try { return _globals.self !== _globals.top; } catch (e) { return true; } } function initModulesWithoutUI() { if (!UIController.modulesInitialized) { UIController.modulesInitialized = true; UIController.applyInitialModuleStates(); UIController.createModules(); UIController.applyModuleSettings(); UIController.setupObservers(); UIController.setupResourceScan(); var any = Object.values(currentConfig.modules).some(function (v) { return v === true; }); if (currentConfig.residualCleanupEnabled && any) ResidualCleaner.init(); CSSUniversalHider.init(); } } function resetAllCachesAndStates() { if (centralScheduler) centralScheduler.clearCache(); ProcessedElementsCache.clear(); LogManager.clearLoggedIdentifiers(); urlCache.clear(); } function safeInit() { if (_document.documentElement) { if (currentConfig.spoofUAEnabled && initUASpoof()) { return true; } if (suppressUIForIframe()) initModulesWithoutUI(); else UIController.init(); return true; } return false; } if (!safeInit()) { var mo = new _MutationObserver(function () { if (safeInit()) mo.disconnect(); }); mo.observe(_document, { childList: true }); _document.addEventListener('DOMContentLoaded', function () { if (!safeInit()) _setTimeout(safeInit, 100); }, { once: true }); _setTimeout(function () { if (!safeInit()) { if (suppressUIForIframe()) initModulesWithoutUI(); else UIController.init(); } }, 5000); } })();