Greasy Fork

Greasy Fork is available in English.

Ultra Popup Blocker (Enhanced Edition)

A popup blocker, re-engineered with an elegant UI and powerful, modern protections.

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Ultra Popup Blocker (Enhanced Edition)
// @description  A popup blocker, re-engineered with an elegant UI and powerful, modern protections.
// @namespace    https://github.com/1Tdd
// @author       1Tdd (Original by Eskander)
// @version      5.0
// @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: '500px', 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 }
        
        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 to new storage system..."); 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.", t) } catch (e) { console.error("[UPB] Migration failed. GM.listValues() is likely blocked. Starting with a fresh index.", 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 || [] } }
        const STYLES = { glassEffect: `background-color: rgba(28, 28, 30, 0.75) !important; -webkit-backdrop-filter: blur(20px) !important; backdrop-filter: blur(20px) !important; border: 1px solid rgba(255, 255, 255, 0.1) !important; box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37) !important;`, notificationBar: `position: fixed !important; bottom: 20px !important; left: 50% !important; transform: translateX(-50%) !important; z-index: 2147483646 !important; width: auto !important; max-width: 90% !important; padding: 10px !important; border-radius: 16px !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;`, 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: 20px !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;`, modalHeader: `padding: 16px !important; text-align: center !important; font-size: 18px !important; font-weight: 600 !important; border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; background-color: rgba(255, 255, 255, 0.05) !important;`, modalContent: `padding: 20px !important; display: flex !important; justify-content: space-between !important; gap: 20px !important;`, modalFooter: `padding: 10px 20px !important; text-align: center !important; border-top: 1px solid rgba(255, 255, 255, 0.1) !important; background-color: rgba(0, 0, 0, 0.1) !important;`, button: `display: inline-flex !important; align-items: center !important; justify-content: center !important; gap: 8px !important; padding: 8px 16px !important; border-radius: 9999px !important; border: none !important; font-size: 14px !important; font-weight: 500 !important; cursor: pointer !important; transition: transform 0.1s ease, background-color 0.2s ease !important; line-height: 1.5 !important;`, buttonColors: { allow: "background-color: #30D158 !important; color: black !important;", trust: "background-color: #0A84FF !important; color: white !important;", deny: "background-color: #FF453A !important; color: white !important;", denyTemp: "background-color: #5856D6 !important; color: white !important;", config: "background-color: rgba(118, 118, 128, 0.24) !important; color: white !important;", neutral: "background-color: rgba(118, 118, 128, 0.24) !important; color: white !important;" }, inputField: `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;`, 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: 8px !important; min-height: 100px !important;`, listItem: `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;`, removeButton: `width: 20px !important; height: 20px !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;` };
        class UIComponents { static createButton(e, t, n) { const o = document.createElement("button"), 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.style.cssText = STYLES.button + (STYLES.buttonColors[t] || STYLES.buttonColors.neutral), o.addEventListener("click", n), o.addEventListener("mousedown", () => o.style.transform = "scale(0.95)"), o.addEventListener("mouseup", () => o.style.transform = "scale(1)"), o.addEventListener("mouseleave", () => o.style.transform = "scale(1)"), o } static createNotificationBar() { const e = document.createElement("div"); return e.id = "upb-notification-bar", e.style.cssText = STYLES.notificationBar + STYLES.glassEffect, e } static createModalElement() { const e = document.createElement("div"); return e.id = "upb-config-modal", e.style.cssText = STYLES.modal + STYLES.glassEffect, e } static updateDenyButtonText(e, t) { e && (e.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.id = "upb-toast-notification", 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() { return !document.body || !this.element && document.body.contains(this.element) || (this.element = UIComponents.createNotificationBar(), document.body.appendChild(this.element)), 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(), redirectShield.arm()) : 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("img"); t.src = CONSTANTS.LOGO_SVG_URL, t.style.cssText = "width: 20px; height: 20px; flex-shrink: 0;", this.element.appendChild(t); const n = e || "", o = n.length > CONSTANTS.TRUNCATE_LENGTH ? `${n.substring(0,CONSTANTS.TRUNCATE_LENGTH)}..` : n, i = document.createElement("span"); i.appendChild(document.createTextNode("Blocked popup to ")); const s = document.createElement("a"); s.href = n, s.target = "_blank", s.style.cssText = "color:#64D2FF; text-decoration: none; font-weight: 500;", s.textContent = o || "an unspecified destination", i.appendChild(s), this.element.appendChild(i) } async addButtons(e) { const t = await DomainManager.getCurrentTopDomain(), n = document.createElement("div"); n.style.cssText = "display: flex; gap: 8px; border-left: 1px solid rgba(255, 255, 255, 0.1); padding-left: 15px;", 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("div"); 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() { document.body ? (this.element && this.element.focus(), this.element = this.createElement(), this.refreshAllLists(), document.body.appendChild(this.element)) : window.addEventListener("DOMContentLoaded", () => this.show()) } createElement() { const e = UIComponents.createModalElement(), t = document.createElement("div"); t.style.cssText = STYLES.modalHeader + " display: flex; align-items: center; justify-content: center; gap: 10px;"; 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.style.cssText = STYLES.modalContent, i.appendChild(this.createListSection("Allowed Websites", "allow")), i.appendChild(this.createListSection("Denied Websites", "deny")), e.appendChild(i); const s = document.createElement("div"); return s.style.cssText = STYLES.modalFooter, s.appendChild(UIComponents.createButton("Close", "neutral", () => this.hide())), e.appendChild(s), this.setupInputHandlers(e), e } createListSection(e, t) { const n = document.createElement("div"); n.style.width = "48%"; 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.id = `upb-input-${t}`, s.placeholder = "e.g., example.com", s.style.cssText = STYLES.inputField; const a = UIComponents.createButton("Add", "trust", () => this.handleAdd(t)); i.appendChild(s), i.appendChild(a); const c = document.createElement("ul"); return c.id = `upb-list-${t}`, c.style.cssText = STYLES.list, n.appendChild(o), n.appendChild(i), n.appendChild(c), n } setupInputHandlers(e) { e.querySelector("#upb-input-allow").onkeydown = t => { t.key === "Enter" && this.handleAdd("allow") }, e.querySelector("#upb-input-deny").onkeydown = t => { t.key === "Enter" && this.handleAdd("deny") } } async handleAdd(e) { const t = this.element.querySelector(`#upb-input-${e}`), n = DomainManager.parseAndValidateDomain(t.value); n ? (t.value = "", e === "allow" ? await DomainManager.addAllowedDomain(n) : await DomainManager.addDeniedDomain(n)) : alert("Invalid domain format. Please enter a valid domain.") } async refreshAllLists() { this.element && (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.style.cssText = "padding: 10px; color: #8E8E93; text-align: center;", o.textContent = "No websites in this list.", e.appendChild(o) } else t.sort().forEach(o => { const i = document.createElement("li"); i.style.cssText = STYLES.listItem; const s = document.createElement("span"); s.textContent = o; const a = document.createElement("div"); a.style.cssText = STYLES.removeButton, 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 void(global.open !== realWindowOpen && (global.open = realWindowOpen), document.write = global._realDocumentWrite, document.writeln = global._realDocumentWriteln) }
                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) }
})();