Ultra Popup Blocker (Enhanced Edition)

A sleek, modern popup blocker with an Apple-inspired glassmorphism UI and advanced redirect protection.

当前为 2025-06-30 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Ultra Popup Blocker (Enhanced Edition)
// @description  A sleek, modern popup blocker with an Apple-inspired glassmorphism UI and advanced redirect protection.
// @namespace    https://github.com/1Tdd
// @author       1Tdd (Original by Eskander)
// @version      5.1
// @include      *
// @license      MIT
// @homepage     https://github.com/1Tdd/ultra-popup-blocker
// @supportURL   https://github.com/1Tdd/ultra-popup-blocker/issues/new
// @icon         data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhdXJvcmEtZ3JhZGllbnQiIHgxPSIwJSIgeTE9IjEwMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiM1ODU2RDYiLz48c3RvcCBvZmZzZXQ9IjUwJSIgc3R5bGU9InN0b3AtY29sb3I6I0ZGMkQ1NSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3R5bGU9InN0b3AtY29sb3I6I0ZGOTgwQSIvPjwvbGluZWFyR3JhZGllbnQ+PG1hc2sgaWQ9InRleHQtbWFzayI+PHJlY3Qgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIGZpbGw9IndoaXRlIiAvPjx0ZXh0IHg9IjUwJSIgeT0iNTMlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0iLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0Rm9udCwgJ1NlZ29lIFVJJywgUm9ib3RvLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjQwIiBmb250LXdlaWdodD0iYm9sZCIgZmlsbD0iYmxhY2siPlVQQjwvdGV4dD48L21hc2s+PC9kZWZzPjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiByeD0iMjIiIGZpbGw9IiMwMDAwMDAiIC8+PHJlY3QgeD0iMTAiIHk9IjEwIiB3aWR0aD0iODAiIGhlaWdodD0iODAiIHJ4PSIyMiIgZmlsbD0idXJsKCNhdXJvcmEtZ3JhZGllbnQpIiBtYXNrPSJ1cmwoI3RleHQtbWFzaykiIC8+PC9zdmc+
// @compatible   firefox Tampermonkey / Violentmonkey
// @compatible   chrome Tampermonkey / Violentmonkey
// @run-at       document-start
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.deleteValue
// @grant        GM.listValues
// @grant        GM.registerMenuCommand
// ==/UserScript==

(function() {
    'use strict';
    try {
        const CONSTANTS = { TIMEOUT_SECONDS: 15, TRUNCATE_LENGTH: 50, MODAL_WIDTH: '550px', TOAST_TIMEOUT_SECONDS: 3, DEBOUNCE_MS: 250, MODAL_Z_INDEX_THRESHOLD: 1000, LOGO_SVG_URL: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhdXJvcmEtZ3JhZGllbnQiIHgxPSIwJSIgeTE9IjEwMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiM1ODU2RDYiLz48c3RvcCBvZmZzZXQ9IjUwJSIgc3R5bGU9InN0b3AtY29sb3I6I0ZGMkQ1NSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3R5bGU9InN0b3AtY29sb3I6I0ZGOTgwQSIvPjwvbGluZWFyR3JhZGllbnQ+PG1hc2sgaWQ9InRleHQtbWFzayI+PHJlY3Qgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIGZpbGw9IndoaXRlIiAvPjx0ZXh0IHg9IjUwJSIgeT0iNTMlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0iLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0Rm9udCwgJ1NlZ29lIFVJJywgUm9ib3RvLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjQwIiBmb250LXdlaWdodD0iYm9sZCIgZmlsbD0iYmxhY2siPlVQQjwvdGV4dD48L21hc2s+PC9kZWZzPjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiByeD0iMjIiIGZpbGw9IiMwMDAwMDAiIC8+PHJlY3QgeD0iMTAiIHk9IjEwIiB3aWR0aD0iODAiIGhlaWdodD0iODAiIHJ4PSIyMiIgZmlsbD0idXJsKCNhdXJvcmEtZ3JhZGllbnQpIiBtYXNrPSJ1cmwoI3RleHQtbWFzaykiIC8+PC9zdmc+" };
        const global = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
        const realWindowOpen = global.open;
        const FakeWindow = (() => { const p = { get: (t, r) => r === "closed" ? !0 : new Proxy(function() {}, p), set: () => !0, apply: () => void 0 }; return new Proxy(function() {}, p) })();
        if (typeof global._realDocumentWrite === "undefined") { global._realDocumentWrite = document.write; global._realDocumentWriteln = document.writeln }

        const fullStyles = `
            .upb-base-button { display: inline-flex !important; align-items: center !important; justify-content: center !important; gap: 8px !important; padding: 10px 20px !important; border-radius: 9999px !important; border: 1px solid rgba(255,255,255,0.1) !important; font-size: 14px !important; font-weight: 600 !important; cursor: pointer !important; transition: all 0.2s ease-out !important; line-height: 1.2 !important; }
            .upb-base-button:hover { filter: brightness(1.1); transform: translateY(-1px); }
            .upb-base-button:active { transform: scale(0.96); filter: brightness(0.95); transition-duration: 0.1s; }
            .upb-button--allow { background-image: linear-gradient(to right, #24D169, #23C15D) !important; color: #003D11 !important; border: none !important; }
            .upb-button--trust { background-image: linear-gradient(to right, #0B84FF, #3DA0FF) !important; color: white !important; border: none !important; }
            .upb-button--deny { background-image: linear-gradient(to right, #FF3B30, #FF453A) !important; color: white !important; border: none !important; }
            .upb-button--denyTemp { background-image: linear-gradient(to right, #5856D6, #6B69D6) !important; color: white !important; border: none !important; }
            .upb-button--config, .upb-button--neutral { background-color: rgba(118, 118, 128, 0.3) !important; color: white !important; border-color: rgba(255,255,255,0.15) !important;}
            .upb-button--config:hover, .upb-button--neutral:hover { background-color: rgba(118, 118, 128, 0.5) !important; }
            #upb-notification-bar { position: fixed !important; bottom: 20px !important; left: 50% !important; transform: translateX(-50%) !important; z-index: 2147483646 !important; width: auto !important; max-width: 95% !important; padding: 12px !important; border-radius: 20px !important; display: none; align-items: center !important; gap: 15px !important; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif !important; font-size: 14px !important; color: #F5F5F7 !important; background-color: rgba(28, 28, 30, 0.7) !important; -webkit-backdrop-filter: blur(25px) !important; backdrop-filter: blur(25px) !important; border: 1px solid rgba(255, 255, 255, 0.15) !important; box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.4), inset 0 0 0 1px rgba(255, 255, 255, 0.15); }
            #upb-config-modal { position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: ${CONSTANTS.MODAL_WIDTH} !important; z-index: 2147483647 !important; border-radius: 24px !important; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif !important; color: #F5F5F7 !important; overflow: hidden !important; background-color: rgba(28, 28, 30, 0.7) !important; -webkit-backdrop-filter: blur(25px) !important; backdrop-filter: blur(25px) !important; border: 1px solid rgba(255, 255, 255, 0.15) !important; box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.4), inset 0 0 0 1px rgba(255, 255, 255, 0.15); }
            #upb-modal-header { padding: 16px !important; text-align: center !important; font-size: 18px !important; font-weight: 600 !important; border-bottom: 1px solid rgba(255, 255, 255, 0.15) !important; background-color: rgba(255, 255, 255, 0.05) !important; display: flex; align-items: center; justify-content: center; gap: 10px; }
            #upb-modal-content { padding: 20px !important; display: flex !important; justify-content: space-between !important; gap: 20px !important; }
            .upb-list-section { width: 48%; }
            #upb-modal-footer { padding: 10px 20px !important; text-align: center !important; border-top: 1px solid rgba(255, 255, 255, 0.15) !important; background-color: rgba(0, 0, 0, 0.1) !important; }
            .upb-input { width: 100% !important; padding: 10px !important; background-color: rgba(118, 118, 128, 0.24) !important; border: 1px solid rgba(118, 118, 128, 0.32) !important; border-radius: 8px !important; color: #F5F5F7 !important; font-size: 14px !important; box-sizing: border-box !important; }
            .upb-list { margin: 0 !important; padding: 0 !important; list-style-type: none !important; max-height: 250px !important; overflow-y: auto !important; background-color: rgba(118, 118, 128, 0.12) !important; border-radius: 12px !important; border: 1px solid rgba(255,255,255,0.08) !important; }
            .upb-list-item { display: flex !important; align-items: center !important; justify-content: space-between !important; padding: 10px 12px !important; border-bottom: 1px solid rgba(118, 118, 128, 0.12) !important; }
            .upb-remove-button { width: 22px !important; height: 22px !important; border-radius: 50% !important; background-color: rgba(118, 118, 128, 0.24) !important; color: #F5F5F7 !important; font-weight: bold !important; cursor: pointer !important; display: flex !important; align-items: center !important; justify-content: center !important; padding-bottom: 2px !important; transition: all 0.2s ease-out; }
            .upb-remove-button:hover { background-color: #FF453A !important; }
            .upb-button-container { display: flex; gap: 8px; border-left: 1px solid rgba(255, 255, 255, 0.15); padding-left: 15px; }
            .upb-info-section { display: flex; align-items: center; gap: 15px; text-shadow: 0 1px 2px rgba(0,0,0,0.2); }
            @media (max-width: 600px) { #upb-notification-bar { flex-direction: column !important; text-align: center; } .upb-button-container { border-left: none !important; padding-left: 0 !important; border-top: 1px solid rgba(255, 255, 255, 0.15) !important; padding-top: 10px !important; flex-wrap: wrap; justify-content: center; } #upb-config-modal { width: 95vw !important; max-height: 85vh !important; display: flex !important; flex-direction: column !important; } #upb-modal-content { flex-direction: column !important; gap: 15px !important; overflow-y: auto; padding: 15px !important; } .upb-list-section { width: 100% !important; } }
        `;
        if (!document.getElementById("upb-styles")) { const styleSheet = document.createElement("style"); styleSheet.id = "upb-styles"; styleSheet.textContent = fullStyles; document.head.appendChild(styleSheet); }
        
        class EventManager { constructor() { this.events = {} } on(e, t) { this.events[e] = this.events[e] || [], this.events[e].push(t) } off(e, t) { this.events[e] && (this.events[e] = this.events[e].filter(n => n !== t)) } emit(e, t) { this.events[e] && this.events[e].forEach(n => n(t)) } }
        const events = new EventManager;
        class DomainManager { static PREFIX_ALLOW = "allow_"; static PREFIX_DENY = "deny_"; static INDEX_KEY = "upb_domain_index"; static MIGRATED_KEY = "upb_migrated_v9"; static async getIndex() { let e = await GM.getValue(this.INDEX_KEY); return e && typeof e.allowed != "undefined" || (e = { allowed: [], denied: [] }), e } static async saveIndex(e) { await GM.setValue(this.INDEX_KEY, e) } static async runMigration() { if (await GM.getValue(this.MIGRATED_KEY)) return; console.log("[UPB] Running one-time migration..."); try { const e = await GM.listValues(), t = { allowed: [], denied: [] }; for (const n of e) n.startsWith(this.PREFIX_ALLOW) ? t.allowed.push(n.substring(this.PREFIX_ALLOW.length)) : n.startsWith(this.PREFIX_DENY) && t.denied.push(n.substring(this.PREFIX_DENY.length)); await this.saveIndex(t), console.log("[UPB] Migration successful.") } catch (e) { console.error("[UPB] Migration failed.", e), await this.saveIndex({ allowed: [], denied: [] }) } await GM.setValue(this.MIGRATED_KEY, !0) } static parseAndValidateDomain(e) { try { if (!e || typeof e != "string") return null; let t = e.includes("//") ? new URL(e).hostname : e; if (t = t.trim().toLowerCase(), t.startsWith("www.") && (t = t.substring(4)), !t || !t.includes(".")) return null; const n = t.split("."); if (n.length < 2 || n.some(s => s.length === 0)) return null; const o = ["co.uk", "com.au", "com.br", "gov.uk", "ac.uk", "co.jp", "co.in"], i = n.slice(-2).join("."); return o.includes(i) && n.length > 2 ? n.slice(-3).join(".") : n.slice(-2).join(".") } catch (t) { return null } } static async getDomainState(e) { return e ? await GM.getValue(this.PREFIX_ALLOW + e) ? "allow" : await GM.getValue(this.PREFIX_DENY + e) ? "deny" : "ask" : "ask" } static async getCurrentDomainState() { const e = await this.getCurrentTopDomain(); return this.getDomainState(e) } static async getCurrentTopDomain() { return this.parseAndValidateDomain(location.hostname) } static async addAllowedDomain(e) { const t = await this.getIndex(); t.allowed.includes(e) || t.allowed.push(e), t.denied = t.denied.filter(n => n !== e), await this.saveIndex(t), await GM.setValue(this.PREFIX_ALLOW + e, !0), await GM.deleteValue(this.PREFIX_DENY + e), events.emit("domainListChanged") } static async addDeniedDomain(e) { const t = await this.getIndex(); t.denied.includes(e) || t.denied.push(e), t.allowed = t.allowed.filter(n => n !== e), await this.saveIndex(t), await GM.setValue(this.PREFIX_DENY + e, !0), await GM.deleteValue(this.PREFIX_ALLOW + e), events.emit("domainListChanged") } static async removeAllowedDomain(e) { const t = await this.getIndex(); t.allowed = t.allowed.filter(n => n !== e), await this.saveIndex(t), await GM.deleteValue(this.PREFIX_ALLOW + e), events.emit("domainListChanged") } static async removeDeniedDomain(e) { const t = await this.getIndex(); t.denied = t.denied.filter(n => n !== e), await this.saveIndex(t), await GM.deleteValue(this.PREFIX_DENY + e), events.emit("domainListChanged") } static async getAllowedDomains() { const e = await this.getIndex(); return e.allowed || [] } static async getDeniedDomains() { const e = await this.getIndex(); return e.denied || [] } }
        class UIComponents { static createButton(e, t, n) { const o = document.createElement("button"); o.className = "upb-base-button upb-button--" + t; const i = e.split(/ (.*)/s), s = i[0], a = i[1] || "", c = document.createElement("span"); c.textContent = s, a || (c.style.margin = "0"), o.appendChild(c); if (a) { const l = document.createElement("span"); l.textContent = a, o.appendChild(l) } return o.addEventListener("click", n), o } static updateDenyButtonText(e, t) { if (e) { const n = e.querySelector("span:last-child"); n && (n.textContent = `Deny (${t})`) } } }
        class ToastNotification { constructor() { this.element = null, this.timeoutId = null } show(e) { this.element && this.hide(!0), document.body ? (this.element = document.createElement("div"), this.element.style.cssText = `position: fixed !important; bottom: 20px !important; right: 20px !important; background-color: rgba(28, 28, 30, 0.75) !important; -webkit-backdrop-filter: blur(10px) !important; backdrop-filter: blur(10px) !important; border: 1px solid rgba(255, 255, 255, 0.1) !important; color: white !important; padding: 10px 20px !important; border-radius: 9999px !important; z-index: 2147483647 !important; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif !important; font-size: 14px !important; box-shadow: 0 4px 20px rgba(0,0,0,0.4) !important; opacity: 0 !important; transform: translateY(10px) !important; transition: all 0.3s ease-in-out !important;`, this.element.textContent = e, document.body.appendChild(this.element), setTimeout(() => { this.element && (this.element.style.opacity = "1", this.element.style.transform = "translateY(0)") }, 10), this.timeoutId = setTimeout(() => this.hide(), 1e3 * CONSTANTS.TOAST_TIMEOUT_SECONDS)) : window.addEventListener("DOMContentLoaded", () => this.show(e)) } hide(e = !1) { !this.element || (clearTimeout(this.timeoutId), this.timeoutId = null, e ? (this.element.parentNode && this.element.parentNode.removeChild(this.element), this.element = null) : (this.element.style.opacity = "0", this.element.style.transform = "translateY(10px)", setTimeout(() => { this.element && this.element.parentNode && this.element.parentNode.removeChild(this.element), this.element = null }, 300))) } }
        class RedirectShield { constructor() { this.isAllowed = !1, this.handler = e => { if (!this.isAllowed) { e.preventDefault(), e.returnValue = ""; return "" } } } arm() { window.addEventListener("beforeunload", this.handler, !0) } disarm() { window.removeEventListener("beforeunload", this.handler, !0) } allowOnce(e) { this.isAllowed = !0, e(), setTimeout(() => { this.isAllowed = !1 }, 100) } }
        class NotificationBar { constructor() { this.element = null, this.timeLeft = CONSTANTS.TIMEOUT_SECONDS, this.denyTimeoutId = null, this.denyButton = null, this.currentUrl = null } createElement() { let e = document.getElementById("upb-notification-bar"); return e || (e = document.createElement("div"), e.id = "upb-notification-bar", document.body.appendChild(e)), this.element = e, this.element } show(e) { this.element && this.element.style.display === "flex" && this.clearDenyTimeout(), this.currentUrl = e, document.body ? this.createElement() && (this.element.style.display = "flex", this.setMessage(e), this.addButtons(e), this.startDenyTimeout()) : window.addEventListener("DOMContentLoaded", () => this.show(e)) } hide() { redirectShield.disarm(), this.element && (this.clearDenyTimeout(), this.element.parentNode && this.element.parentNode.removeChild(this.element), this.element = null) } clearDenyTimeout() { this.denyTimeoutId && (clearInterval(this.denyTimeoutId), this.denyTimeoutId = null) } setMessage(e) { for (; this.element.firstChild;) this.element.removeChild(this.element.firstChild); const t = document.createElement("div"); t.className = "upb-info-section"; const n = document.createElement("img"); n.src = CONSTANTS.LOGO_SVG_URL, n.style.cssText = "width: 20px; height: 20px; flex-shrink: 0;", t.appendChild(n); const o = e || "", i = o.length > CONSTANTS.TRUNCATE_LENGTH ? `${o.substring(0,CONSTANTS.TRUNCATE_LENGTH)}..` : o, s = document.createElement("span"); s.appendChild(document.createTextNode("Blocked popup to ")); const a = document.createElement("a"); a.href = o, a.target = "_blank", a.style.cssText = "color:#64D2FF; text-decoration: none; font-weight: 500;", a.textContent = i || "an unspecified destination", s.appendChild(a), t.appendChild(s), this.element.appendChild(t) } async addButtons(e) { const t = await DomainManager.getCurrentTopDomain(), n = document.createElement("div"); n.className = "upb-button-container"; n.appendChild(UIComponents.createButton("✅ Allow Once", "allow", () => { this.hide(), redirectShield.allowOnce(() => { realWindowOpen(e) }) })), n.appendChild(UIComponents.createButton("💙 Always Allow", "trust", async () => { this.hide(), await DomainManager.addAllowedDomain(t) })), n.appendChild(UIComponents.createButton("❌ Always Deny", "deny", async () => { confirm(`Are you sure you want to permanently block popups from ${t}?`) && (this.hide(), await DomainManager.addDeniedDomain(t)) })), this.denyButton = UIComponents.createButton(`🚫 Deny (${this.timeLeft})`, "denyTemp", () => this.hide()), n.appendChild(this.denyButton), n.appendChild(UIComponents.createButton("⚙️ Config", "config", () => configModal.show())); const o = this.element.querySelector(".upb-button-container"); o && o.remove(), this.element.appendChild(n) } startDenyTimeout() { this.timeLeft = CONSTANTS.TIMEOUT_SECONDS, this.clearDenyTimeout(), UIComponents.updateDenyButtonText(this.denyButton, this.timeLeft), this.denyTimeoutId = setInterval(() => { this.timeLeft--, UIComponents.updateDenyButtonText(this.denyButton, this.timeLeft), this.timeLeft <= 0 && this.hide() }, 1e3) } }
        class ConfigModal { constructor() { this.element = null, this.refreshListener = () => { this.element && this.refreshAllLists() }, events.on("domainListChanged", this.refreshListener) } destroy() { events.off("domainListChanged", this.refreshListener), this.hide() } hide() { this.element && (this.element.remove(), this.element = null) } show() { if (!document.body) { window.addEventListener("DOMContentLoaded", () => this.show()); return } this.element && this.hide(); this.element = this.createElement(), document.body.appendChild(this.element), this.refreshAllLists() } createElement() { const e = document.createElement("div"); e.id = "upb-config-modal"; const t = document.createElement("div"); t.id = "upb-modal-header"; const n = document.createElement("img"); n.src = CONSTANTS.LOGO_SVG_URL, n.style.cssText = "width: 24px; height: 24px;"; const o = document.createElement("span"); o.textContent = "Ultra Popup Blocker", t.appendChild(n), t.appendChild(o), e.appendChild(t); const i = document.createElement("div"); i.id = "upb-modal-content", i.appendChild(this.createListSection("Allowed Websites", "allow")), i.appendChild(this.createListSection("Denied Websites", "deny")), e.appendChild(i); const s = document.createElement("div"); return s.id = "upb-modal-footer", s.appendChild(UIComponents.createButton("Close", "neutral", () => this.hide())), e.appendChild(s), e } createListSection(e, t) { const n = document.createElement("div"); n.className = "upb-list-section"; const o = document.createElement("h3"); o.style.cssText = "margin-top: 0; margin-bottom: 10px; text-align: center; font-weight: 500;", o.textContent = e; const i = document.createElement("div"); i.style.cssText = "display: flex; gap: 8px; margin-bottom: 10px;"; const s = document.createElement("input"); s.type = "text", s.className = "upb-input", s.placeholder = "e.g., example.com"; const a = UIComponents.createButton("Add", "trust", () => this.handleAdd(t, s)); i.appendChild(s), i.appendChild(a); const c = document.createElement("ul"); return c.className = "upb-list", c.classList.add(`upb-list--${t}`), n.appendChild(o), n.appendChild(i), n.appendChild(c), s.onkeydown = e => { e.key === "Enter" && this.handleAdd(t, s) }, n } handleAdd(e, t) { const n = DomainManager.parseAndValidateDomain(t.value); n ? (t.value = "", e === "allow" ? DomainManager.addAllowedDomain(n) : DomainManager.addDeniedDomain(n)) : alert("Invalid domain format. Please enter a valid domain.") } async refreshAllLists() { if (!this.element) return; await this.populateList(this.element.querySelector(".upb-list--allow"), await DomainManager.getAllowedDomains(), "allow"); await this.populateList(this.element.querySelector(".upb-list--deny"), await DomainManager.getDeniedDomains(), "deny") } populateList(e, t, n) { for (; e.firstChild;) e.removeChild(e.firstChild); if (t.length === 0) { const o = document.createElement("li"); o.textContent = "No websites in this list.", o.style.cssText = "padding: 10px; color: #8E8E93; text-align: center;", e.appendChild(o) } else t.sort().forEach(o => { const i = document.createElement("li"); i.className = "upb-list-item"; const s = document.createElement("span"); s.textContent = o; const a = document.createElement("div"); a.className = "upb-remove-button", a.textContent = "×", a.onclick = async () => { n === "allow" ? await DomainManager.removeAllowedDomain(o) : await DomainManager.removeDeniedDomain(o) }, i.appendChild(s), i.appendChild(a), e.appendChild(i) }) } }
        class ModalBlocker { static isModal(e) { if (!(e instanceof HTMLElement) || !document.body.contains(e) || e.offsetParent === null || getComputedStyle(e).visibility === "hidden") return !1; const t = getComputedStyle(e), n = parseInt(t.zIndex, 10) || 0; if (n < 1e3) return !1; const o = e.getBoundingClientRect(); if (o.width <= 0 || o.height <= 0) return !1; const i = window.innerWidth, s = window.innerHeight; return o.width > .8 * i && o.height > .8 * s || o.width > 300 && o.height > 200 && t.position === "fixed" || t.backgroundColor.startsWith("rgba") && parseFloat(t.backgroundColor.split(",")[3]) > .1 } static neutralize(e, t) { console.log("[UPB] Neutralizing suspected modal:", e), e.style.setProperty("display", "none", "important"), document.body.style.setProperty("overflow", "auto", "important"), document.documentElement.style.setProperty("overflow", "auto", "important"), t.show("🛡️ Modal popup hidden.") } static scan(e, t) { if (sessionStorage.getItem("upb_modal_block_disabled") === "true") return; for (const n of e) if (n.nodeType === Node.ELEMENT_NODE) { if (n.closest("#upb-notification-bar, #upb-config-modal, #upb-toast-notification")) continue; if (this.isModal(n)) this.neutralize(n, t); else { const o = n.querySelectorAll("div, form"); for (const i of o) this.isModal(i) && this.neutralize(i, t) } } } }
        class PopupBlocker {
            static async initialize() {
                if (global._upbListenerCleaner) { global._upbListenerCleaner(), global._upbListenerCleaner = null }
                const domainState = await DomainManager.getCurrentDomainState();
                if (global._upb_toast || (global._upb_toast = new ToastNotification), domainState === "allow") { return redirectShield.disarm(), void(global.open !== realWindowOpen && (global.open = realWindowOpen), document.write = global._realDocumentWrite, document.writeln = global._realDocumentWriteln) }
                
                redirectShield.arm();

                if (domainState === "deny") { const e = global._upb_toast; global.open = () => (e.show("🚫 Popup blocked on a denied site."), FakeWindow); const t = n => { let o = !1; if (n.type === "click") { const i = n.target.closest("a"); if (i && i.href) { const s = document.querySelector('base[target="_blank"]'); o = i.target === "_blank" || s && i.target !== "_self" } } else if (n.type === "submit") { const i = n.target.closest("form"); i && i.target === "_blank" && (o = !0) } o && (n.preventDefault(), n.stopPropagation(), n.stopImmediatePropagation(), e.show("🚫 Popup blocked on a denied site.")) }; return void(window.addEventListener("click", t, !0), window.addEventListener("submit", t, !0), global._upbListenerCleaner = () => { window.removeEventListener("click", t, !0), window.removeEventListener("submit", t, !0) }) }
                
                const notificationBar = new NotificationBar;
                global.open = (url) => { notificationBar.show(url); return FakeWindow; };
                const clickBlocker = e => { if (e.target.closest("#upb-notification-bar, #upb-toast-notification")) return; const t = e.target.closest("a"); if (t && t.href) { const n = document.querySelector('base[target="_blank"]'), o = t.target === "_blank" || n && t.target !== "_self"; o && (e.preventDefault(), e.stopPropagation(), e.stopImmediatePropagation(), notificationBar.show(t.href)) } };
                const submitListener = e => { const t = e.target.closest("form"); t && t.target === "_blank" && (e.preventDefault(), notificationBar.show(t.action || location.href)) };
                window.addEventListener("click", clickBlocker, !0), window.addEventListener("submit", submitListener, !0), global._upbListenerCleaner = () => { window.removeEventListener("click", clickBlocker, !0), window.removeEventListener("submit", submitListener, !0) }
            }
        }

        const configModal = new ConfigModal;
        const redirectShield = new RedirectShield;
        function debounce(e, t) { let n; return function(...o) { const i = this; clearTimeout(n), n = setTimeout(() => e.apply(i, o), t) } }
        const reinitializeDebounced = debounce(PopupBlocker.initialize, CONSTANTS.DEBOUNCE_MS);
        const observer = new MutationObserver(mutations => { const addedNodes = mutations.flatMap(m => Array.from(m.addedNodes)); if (addedNodes.length > 0) { sessionStorage.getItem("upb_modal_block_disabled") !== "true" && ModalBlocker.scan(addedNodes, global._upb_toast || new ToastNotification), reinitializeDebounced() } });
        function startObserver() { document.body ? observer.observe(document.body, { childList: !0, subtree: !0 }) : window.addEventListener("DOMContentLoaded", () => { observer.observe(document.body, { childList: !0, subtree: !0 }) }, { once: !0 }) }
        
        GM.registerMenuCommand("Ultra Popup Blocker: Configure", () => configModal.show());
        GM.registerMenuCommand("Disable Modal Blocker (1 Tab)", () => { sessionStorage.setItem("upb_modal_block_disabled", "true"); const e = new ToastNotification; e.show("Modal blocker disabled for this tab.") });
        events.on("domainListChanged", PopupBlocker.initialize);
        
        (async () => {
            await DomainManager.runMigration();
            await PopupBlocker.initialize();
            startObserver();
        })()
    } catch (e) { console.error("[UPB] A critical error occurred. Please report this on GitHub.", e) }
})();