Ultra Popup Blocker

Configurable popup blocker that blocks all popup windows by default.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Ultra Popup Blocker
// @description  Configurable popup blocker that blocks all popup windows by default.
// @namespace    eskander.github.io
// @author       Eskander
// @version      4.1
// @include      *
// @license      MIT
// @homepage     https://github.com/Eskander/ultra-popup-blocker
// @supportURL   https://github.com/Eskander/ultra-popup-blocker/issues/new
// @compatible   firefox Tampermonkey / Violentmonkey
// @compatible   chrome Tampermonkey / Violentmonkey
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.deleteValue
// @grant        GM.listValues
// @grant        GM.registerMenuCommand
// ==/UserScript==

/* Constants and Globals */
const CONSTANTS = {
  TIMEOUT_SECONDS: 15,
  TRUNCATE_LENGTH: 50,
  MODAL_WIDTH: '400px'
}

const STYLES = {
  modal: `
    position: fixed !important;
    top: 50% !important;
    left: 50% !important;
    transform: translate(-50%, -50%) !important;
    background-color: #ffffff !important;
    color: #000000 !important;
    width: ${CONSTANTS.MODAL_WIDTH} !important;
    border: 1px solid #000000 !important;
    z-index: 2147483647 !important;
    box-shadow: 0 2px 10px rgba(0,0,0,0.5) !important;
    margin: 0 !important;
    padding: 0 !important;
    font-family: Arial !important;
    font-size: 14px !important;
    line-height: 1.5 !important;
    box-sizing: border-box !important;
  `,
  modalHeader: `
    background-color: #000000 !important;
    padding: 30px 40px !important;
    color: #ffffff !important;
    text-align: center !important;
    margin: 0 !important;
    font-size: inherit !important;
    line-height: inherit !important;
  `,
  modalFooter: `
    background-color: #000000 !important;
    padding: 5px 40px !important;
    color: #ffffff !important;
    text-align: center !important;
    margin: 0 !important;
  `,
  button: `
    margin-right: 20px !important;
    padding: 5px !important;
    cursor: pointer !important;
    font-family: inherit !important;
    font-size: inherit !important;
    line-height: inherit !important;
    border: 1px solid #000000 !important;
    background: #ffffff !important;
    color: #000000 !important;
    border-radius: 3px !important;
  `,
  notificationBar: `
    position: fixed !important;
    bottom: 0 !important;
    left: 0 !important;
    z-index: 2147483646 !important;
    width: 100% !important;
    padding: 5px !important;
    font-family: Arial !important;
    font-size: 14px !important;
    line-height: 1.5 !important;
    background-color: #000000 !important;
    color: #ffffff !important;
    display: none !important;
    margin: 0 !important;
    box-sizing: border-box !important;
  `,
  listItem: `
    padding: 12px 8px 12px 40px !important;
    font-size: 18px !important;
    background-color: #ffffff !important;
    color: #000000 !important;
    border-bottom: 1px solid #000000 !important;
    position: relative !important;
    transition: 0.2s !important;
    margin: 0 !important;
  `,
  removeButton: `
    cursor: pointer !important;
    position: absolute !important;
    right: 0 !important;
    top: 0 !important;
    padding: 12px 16px !important;
    background: transparent !important;
    border: none !important;
    color: #000000 !important;
  `
}

// Reference to page's window through GreaseMonkey
const global = unsafeWindow
global.upbCounter = 0

// Store reference to original window.open
const realWindowOpen = global.open

// Fake window object to prevent JS errors
const FakeWindow = {
  blur: () => false,
  focus: () => false
}

/* Domain Management */
class DomainManager {
  static async getCurrentTopDomain () {
    const [domainName, topLevelDomain] = document.location.hostname.split('.').slice(-2)
    return `${domainName}.${topLevelDomain}`
  }

  static async isCurrentDomainTrusted () {
    const domain = await this.getCurrentTopDomain()
    return await GM.getValue(domain)
  }

  static async addTrustedDomain (domain) {
    await GM.setValue(domain, true)
  }

  static async removeTrustedDomain (domain) {
    await GM.deleteValue(domain)
  }

  static async getTrustedDomains () {
    return await GM.listValues()
  }
}

/* UI Components */
class UIComponents {
  static createButton (text, id, clickHandler, color) {
    const button = document.createElement('button')
    button.id = `upb-${id}`
    button.innerHTML = text
    button.style.cssText = `${STYLES.button} color: ${color} !important;`
    button.addEventListener('click', clickHandler)
    return button
  }

  static createNotificationBar () {
    const bar = document.createElement('div')
    bar.id = 'upb-notification-bar'
    bar.style.cssText = STYLES.notificationBar
    return bar
  }

  static createModalElement () {
    const modal = document.createElement('div')
    modal.id = 'upb-trusted-domains-modal'
    modal.style.cssText = STYLES.modal
    return modal
  }

  static updateDenyButtonText (button, timeLeft) {
    if (button) {
      button.innerHTML = `🔴 Deny (${timeLeft})`
    }
  }
}

/* Notification Bar */
class NotificationBar {
  constructor () {
    // Don't create the element in constructor
    this.element = null
    this.timeLeft = CONSTANTS.TIMEOUT_SECONDS
    this.denyTimeoutId = null
    this.denyButton = null
  }

  createElement () {
    if (!this.element) {
      this.element = UIComponents.createNotificationBar()
      document.body.appendChild(this.element)
    }
    return this.element
  }

  show (url) {
    if (!this.element) {
      this.createElement()
    }
    this.element.style.display = 'block'
    this.setMessage(url)
    this.addButtons(url)
    this.startDenyTimeout()
  }

  hide () {
    if (this.element) {
      this.element.style.display = 'none'
      if (this.element.parentNode) {
        this.element.parentNode.removeChild(this.element)
      }
      this.element = null
    }
    global.upbCounter = 0
    this.clearDenyTimeout()
  }

  clearDenyTimeout () {
    if (this.denyTimeoutId) {
      clearInterval(this.denyTimeoutId)
      this.denyTimeoutId = null
    }
  }

  setMessage (url) {
    const truncatedUrl = url.length > CONSTANTS.TRUNCATE_LENGTH
      ? `${url.substring(0, CONSTANTS.TRUNCATE_LENGTH)}..`
      : url

    this.element.innerHTML = `
      Ultra Popup Blocker: This site is attempting to open <b>${global.upbCounter}</b> popup(s).
      <a href="${url}" style="color:yellow;">${truncatedUrl}</a>
    `
  }

  async addButtons (url) {
    const currentDomain = await DomainManager.getCurrentTopDomain()

    // Allow Once
    this.element.appendChild(
      UIComponents.createButton('🟢 Allow Once', 'allow', () => {
        realWindowOpen(url)
        this.hide()
      }, 'green')
    )

    // Always Allow
    this.element.appendChild(
      UIComponents.createButton('🔵 Always Allow', 'trust', async () => {
        await DomainManager.addTrustedDomain(currentDomain)
        realWindowOpen(url)
        this.hide()
        global.open = realWindowOpen
      }, 'blue')
    )

    // Deny
    this.denyButton = UIComponents.createButton('🔴 Deny (15)', 'deny', () => {
      this.hide()
      PopupBlocker.initialize()
    }, 'red')
    this.element.appendChild(this.denyButton)

    // Config
    const configButton = UIComponents.createButton('🟠 Config', 'config', () => {
      new TrustedDomainsModal().show()
    }, 'orange')
    configButton.style.float = 'right'
    this.element.appendChild(configButton)
  }

  startDenyTimeout () {
    this.timeLeft = CONSTANTS.TIMEOUT_SECONDS
    this.clearDenyTimeout()

    // Initial update
    UIComponents.updateDenyButtonText(this.denyButton, this.timeLeft)

    this.denyTimeoutId = setInterval(() => {
      this.timeLeft--
      UIComponents.updateDenyButtonText(this.denyButton, this.timeLeft)

      if (this.timeLeft <= 0) {
        this.clearDenyTimeout()
        this.hide()
        PopupBlocker.initialize()
      }
    }, 1000)
  }

  resetTimeout () {
    if (this.element && this.element.style.display === 'block') {
      this.startDenyTimeout()
    }
  }
}

/* Trusted Domains Modal */
class TrustedDomainsModal {
  constructor () {
    this.element = document.getElementById('upb-trusted-domains-modal') || this.createElement()
  }

  createElement () {
    const modal = UIComponents.createModalElement()

    const header = document.createElement('div')
    header.style.cssText = STYLES.modalHeader
    header.innerHTML = `
      <h2 style="color:white !important;">Ultra Popup Blocker</h2>
      <h3 style="color:white !important;text-align:left;margin-top:10px;">Trusted websites:</h3>
    `
    modal.appendChild(header)

    const footer = document.createElement('div')
    footer.style.cssText = STYLES.modalFooter

    const closeButton = document.createElement('button')
    closeButton.innerText = 'Close'
    closeButton.style.cssText = `
      background-color: #000000;
      color: #ffffff;
      border: none;
      padding: 10px;
      cursor: pointer;
    `
    closeButton.onclick = () => this.hide()

    footer.appendChild(closeButton)
    modal.appendChild(footer)

    document.body.appendChild(modal)
    return modal
  }

  show () {
    this.refreshDomainsList()
    this.element.style.display = 'block'
  }

  hide () {
    this.element.style.display = 'none'
  }

  async refreshDomainsList () {
    const existingList = document.getElementById('upb-domains-list')
    if (existingList) existingList.remove()

    const list = document.createElement('ul')
    list.id = 'upb-domains-list'
    list.style.cssText = 'margin:0;padding:0;list-style-type:none;'

    const trustedDomains = await DomainManager.getTrustedDomains()

    if (trustedDomains.length === 0) {
      const message = document.createElement('p')
      message.style.padding = '20px'
      message.innerText = 'No allowed websites'
      list.appendChild(message)
    } else {
      for (const domain of trustedDomains) {
        await this.addDomainListItem(list, domain)
      }
    }

    this.element.insertBefore(list, this.element.querySelector('div:last-child'))
  }

  async addDomainListItem (list, domain) {
    const item = document.createElement('li')
    item.style.cssText = STYLES.listItem
    item.innerText = domain

    item.addEventListener('mouseover', () => {
      item.style.backgroundColor = '#ddd'
    })
    item.addEventListener('mouseout', () => {
      item.style.backgroundColor = 'white'
    })

    const removeButton = document.createElement('span')
    removeButton.style.cssText = STYLES.removeButton
    removeButton.innerText = '×'

    removeButton.addEventListener('mouseover', () => {
      removeButton.style.backgroundColor = '#f44336'
      removeButton.style.color = 'white'
    })
    removeButton.addEventListener('mouseout', () => {
      removeButton.style.backgroundColor = 'transparent'
      removeButton.style.color = 'black'
    })
    removeButton.addEventListener('click', async () => {
      await DomainManager.removeTrustedDomain(domain)
      item.remove()
      PopupBlocker.initialize()
    })

    item.appendChild(removeButton)
    list.appendChild(item)
  }
}

/* Popup Blocker */
class PopupBlocker {
  static async initialize () {
    if (global.open !== realWindowOpen) return

    if (await DomainManager.isCurrentDomainTrusted()) {
      const domain = await DomainManager.getCurrentTopDomain()
      console.log(`[UPB] Trusted domain: ${domain}`)
      global.open = realWindowOpen
      return
    }

    const notificationBar = new NotificationBar()

    global.open = (url, target, features) => {
      global.upbCounter++
      console.log(`[UPB] Popup blocked: ${url}`)
      notificationBar.show(url)
      return FakeWindow
    }
  }
}

/* Initialize */
window.addEventListener('load', () => PopupBlocker.initialize())
GM.registerMenuCommand('Ultra Popup Blocker: Trusted domains', () => new TrustedDomainsModal().show())