// ==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 // @downloadURL none // ==/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) } })();