eskander / Ultra Popup Blocker

// ==UserScript==
// @name         Ultra Popup Blocker
// @description  Configurable popup blocker that blocks all popup windows by default.
// @namespace    https://github.com/eskander
// @author       Eskander
// @version      3.5
// @include      *
// @license      MIT
// @homepage     https://eskander.tn/ultra-popup-blocker/
// @supportURL   https://github.com/Eskander/ultra-popup-blocker/issues/new
// @compatible   firefox Tampermonkey recommended
// @compatible   chrome Tampermonkey recommended
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_openInTab
// @grant        GM_registerMenuCommand
// ==/UserScript==

/* ---------------------------------------------------------------- */

const PERMISSION_DIALOG_ID = 'ultra_popup_blocker'; // HTML ID in the page
const CONTROL_PANEL = 'https://eskander.tn/ultra-popup-blocker/settings.html';

// Reference to page's "window" through GreaseMonkey
const global = unsafeWindow;
global.upb_counter = 0;

// Storing a reference to real "window.open" method in case we wanted it
const realWindowOpen = global.open;

// We need to return the fake window to not encounter JS runtime error when the popup originator
// page wants to call focus() or blur().
const FakeWindow = {
  blur() {
    return false;
  },
  focus() {
    return false;
  },
};

// Timeout before confirmation dialog closes automatically
let timeleft = 15;

/* ---------------------------------------------------------------- */

// Add @domain to local storage
function addDomainToLocalStorage(domain) {
  GM_setValue(`trusted_${domain}`, true);
}

// Remove @domain from local storage
function removeDomainFromLocalStorage(domain) {
  GM_deleteValue(`trusted_${domain}`);
  GM_deleteValue(`${domain}`);
}

// Return true if @domain is trusted
function isDomainTrusted(domain) {
  return GM_getValue(`trusted_${domain}`);
}

// Return an Array of trusted domains
function getTrustedDomains() {
  return GM_listValues();
}

// Open permission manager in new tab
function openControlPanel() {
  GM_openInTab(CONTROL_PANEL, false);
}

// Add a link to permission manager in extensions' popup menu
function attachToExtensionMenu(name, callback) {
  GM_registerMenuCommand(name, callback);
}

// Permission bar; Return permission dialog, or create it if needed.
function getLogDiv() {
  let logDiv = document.getElementById(PERMISSION_DIALOG_ID);
  if (!logDiv) {
    logDiv = document.createElement('div');
    logDiv.setAttribute('id', PERMISSION_DIALOG_ID);
    logDiv.style.cssText = 'position: fixed;\
                            bottom: 0;\
                            left: 0;\
                            z-index: 99999;\
                            width: 100%;\
                            padding: 5px 5px 5px 5px;\
                            font: status-bar;\
                            background-color: black;\
                            color: white;\
                            cursor: help';
    document.body.appendChild(logDiv);
  }
  return logDiv;
}

// Permission bar; Hide dialog
function closeLogDiv(logDiv) {
  const currentLogDiv = logDiv;
  currentLogDiv.style.display = 'none';
}

// Return current top domain. eg: github.com
function getCurrentTopDomain() {
  const hostnameArray = document.location.hostname.split('.');
  const topLevelDomain = hostnameArray[hostnameArray.length - 1];
  const domainName = hostnameArray[hostnameArray.length - 2];
  const currentDomain = `${domainName}.${topLevelDomain}`;
  return currentDomain;
}

// Return true if current domain has been trusted by the user
function isCurrentDomainTrusted() {
  const domain = getCurrentTopDomain();
  return isDomainTrusted(domain);
}

// Permission manager; Create a button to remove domain from permissions list
function removeDomainFromPermissionList() {
  const div = this.parentElement;
  console.log(div);
  const domain = div.innerText.replace('\n\u00D7', '');
  removeDomainFromLocalStorage(domain);
  div.style.display = 'none';
  console.log(`[UPB] Domain removed from trust: ${domain}`);
}

// Permission manager; Add a new domain to permissions list
function addDomainToPermissionList(domain) {
  const domainName = domain.replace('trusted_', '');
  const li = document.createElement('li');
  const t = document.createTextNode(domainName);
  li.appendChild(t);
  document.getElementById('List').appendChild(li);
  // Add a remove button to li
  const span = document.createElement('SPAN');
  const txt = document.createTextNode('\u00D7');
  span.className = 'close';
  span.appendChild(txt);
  span.onclick = removeDomainFromPermissionList;
  li.appendChild(span);
  // Add domain to localStorage
  addDomainToLocalStorage(domainName);
  console.log(`[UPB] Domain added to trust: ${domainName}`);
}

// Permission manager; Button to add a new domain to permissions list
function addNewDomainButton() {
  document.getElementsByClassName('addBtn')[0].addEventListener(
    'click',
    () => {
      const DOMAIN = document.getElementById('Input').value;
      if (DOMAIN !== '') {
        addDomainToPermissionList(DOMAIN);
      }
      document.getElementById('Input').value = '';
    },
  );
}

// Permission bar; Create a button with inner text @text executing onclick
// @clickCallback, appended as a child of @logDiv, with style @inlineStyle.
function createButton(logDiv, text, id, clickCallback, inlineStyle) {
  const button = document.createElement('button');
  button.innerHTML = text;
  button.id = id;
  button.style.cssText = `text-decoration: none;\
                          color: black;\
                          cursor: pointer;\
                          margin: 0 5px;\
                          padding: 1px 3px;\
                          background-color: rgb(255, 255, 255);\
                          border-width: 0px;\
                          border-radius: 5px;\
                          color: black;\
                          ${inlineStyle}`;
  logDiv.appendChild(button);
  button.addEventListener('click', clickCallback);
}

// Permission bar; Create a button (child of @logDiv) which onclick trusts @domain
function createTrustButton(logDiv, domain, a, b, c) {
  createButton(
    logDiv,
    'Always Allow 🗸',
    'upb_trust',
    () => {
      addDomainToLocalStorage(domain);
      realWindowOpen(a, b, c);
      closeLogDiv(logDiv);
      global.open = realWindowOpen;
    },
    '',
  );
}

// Permission bar; Create a button (child of @logDiv) which onclick opens @domain
function createOpenPopupButton(logDiv, a, b, c) {
  createButton(
    logDiv,
    'Allow ↗',
    'upb_open',
    () => {
      realWindowOpen(a, b, c);
      closeLogDiv(logDiv);
    },
    '',
  );
}

// Permission bar; Create a button (child of @logDiv) which onclick hides @logDiv
function createCloseButton(logDiv) {
  createButton(
    logDiv,
    `Deny (${timeleft})`,
    'upb_close',
    () => {
      closeLogDiv(logDiv);
    },
    ' background-color: #a00;\
      color: white;',
  );
}

// Permission bar; Create a button (child of @logDiv) which onclick opens @controlPanel
function createConfigButton(logDiv) {
  createButton(
    logDiv,
    'Config ⚙',
    'upb_config',
    () => {
      openControlPanel();
    },
    ' float: right;\
      margin: 0 10px 0 0;',
  );
}

// Permission bar; Display a permission prompt when a new popup is detected
function createDialogMessage(logDiv, url) {
  const currentLogDiv = logDiv;
  const domain = getCurrentTopDomain();
  let msg;
  let popupUrl;

  global.upb_counter += 1;

  if (global.upb_counter === 1) {
    msg = `<b>[UPB]</b> Allow <b><u>${domain}</u></b> to open a popup ?`;
  } else {
    msg = `<b>[UPB]</b> Allow <b><u>${domain}</u></b> to open a popup ? <b>(${global.upb_counter})</b>`;
  }

  if (url[0] === '/') {
    popupUrl = document.domain + url;
  } else {
    popupUrl = url;
  }

  currentLogDiv.innerHTML = msg;
  currentLogDiv.title = popupUrl;
  console.log(msg);
  currentLogDiv.style.display = 'block';
}

function createTimer(logDiv) {
  console.log(timeleft);
  if (timeleft === 15) {
    const Timer = setInterval(() => {
      document.getElementById('upb_close').innerHTML = `Deny (${timeleft})`;
      timeleft -= 1;
      if (timeleft < 0) {
        clearInterval(Timer);
        closeLogDiv(logDiv);
        timeleft = 15;
      }
      console.log(timeleft);
    }, 1000);
  }
}

// This function will be called each time a script wants to open a new window
function fakeWindowOpen(a, b, c) {
  const domain = getCurrentTopDomain();
  const popupURL = a;
  const logDiv = getLogDiv();
  console.log(a, b, c);
  createDialogMessage(logDiv, popupURL);
  createOpenPopupButton(logDiv, a, b, c);
  createTrustButton(logDiv, domain, a, b, c);
  createCloseButton(logDiv);
  createConfigButton(logDiv);
  createTimer(logDiv);
  return FakeWindow;
}

// Override browser's "window.open" with our own implementation.
function activateBlocker() {
  const TRUSTED = isCurrentDomainTrusted();
  if (!TRUSTED) {
    global.open = fakeWindowOpen;
    console.log('[UPB] Current domain Not trusted.');
  } else {
    console.log('[UPB] Current domain Trusted. UPB disabled.');
  }
}

function activateControlPanel() {
  if (window.location.href === CONTROL_PANEL) {
    // Add listener to the add button
    addNewDomainButton();
    // Show already stored elements in the list
    const storedTrust = getTrustedDomains();
    storedTrust.forEach(addDomainToPermissionList);
    console.log(storedTrust);
  }
}

function activateExtensionMenu() {
  attachToExtensionMenu(
    'Configure popup permissions',
    () => {
      openControlPanel();
    },
  );
}

/* ---------------------------------------------------------------- */

// Add configure link to Tampermonkey's menu
activateExtensionMenu();

// Initiate Control Panel logic
activateControlPanel();

// Start Popup Blocker
activateBlocker();