The Ultimate Popup Blocker

Configurable popup blocker that blocks ALL (even user-initiated) popup windows by default. But you can easily open the blocked popup or whitelist a domain, directly from the page.

  1. // ==UserScript==
  2. // @name The Ultimate Popup Blocker
  3. // @description Configurable popup blocker that blocks ALL (even user-initiated) popup windows by default. But you can easily open the blocked popup or whitelist a domain, directly from the page.
  4. // @namespace jakub-g.github.com
  5. // @author http://jakub-g.github.com/
  6. // @version 0.1-20130112
  7. // @userscriptsOrg http://userscripts.org/scripts/show/...
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // @include *
  11. // @exclude http*://*.youtube.com/*
  12. // @exclude http*://mail.google.com/*
  13. // @exclude http*://*.blogspot.tld/*
  14. // @exclude http*://poczta.wp.pl/*
  15. // ==/UserScript==
  16.  
  17. // Why another popup blocker?
  18.  
  19. // Built-in Firefox popup blocker blocks only the popup that were created automatically via
  20. // script on page load etc. When you click on a button on the page, it won't block it.
  21. // However, malicious websites can create JS code that will launch the popups whenever you click
  22. // on any blank space on the page for instance.
  23.  
  24. // In JavaScript, functions are first-order citizens. It means you can store a function in a variable
  25. // and pass it freely. You can also modify the native functions provided by the browser, like 'window.open'.
  26. // Here, we override 'window.open' with our own implementation which doesn't really open the window.
  27. // However the user may want to open a popup - cool, we stored original native window.open function
  28. // in a variable and we will call it!
  29.  
  30. // Of course, we'd like to white list some domains. Greasemonkey offers an option to specify @exclude entries
  31. // in the metadata block of the script. However this means you'll have to edit this text file each time you
  32. // want to whitelist a domain. However, GM also offers a way to set config entries in Firefox storage
  33. // (about:config entries) and we'll make use of it to dynamically add entries to the white list
  34. // right from the page.
  35.  
  36. // The essence of the blocking code is substituting window.open with the function returning FakeWindow,
  37. // which can be a one-liner.
  38. // The majority of the code here is the handling of whitelisting logic and informational logging.
  39.  
  40. // Tests: http://www.popuptest.com/
  41.  
  42. (function() {
  43. // ============================== CONFIG =================================
  44. var bDisplayMessageOnPopupBlocked = true;
  45. var bDisplayOpenPopupLink = true;
  46. var bDisplayWhiteListThisDomainLink = true;
  47. var LOG_ID = "ultimate_popup_blocker"; // HTML ID in the page
  48. // ============================ FUNCTIONS ================================
  49. var global = unsafeWindow; // reference to page's "window" through GreaseMonkey
  50. /*
  51. * Helper to create a button with inner text @text executing onclick @clickCallback,
  52. * appended as a child of @logDiv
  53. */
  54. var putButton = function (logDiv, text, clickCallback, inlineStyle) {
  55. var button = document.createElement("button");
  56. button.innerHTML = text;
  57. button.style.cssText = "text-decoration:none; color:black; cursor:pointer;\
  58. margin: 0 5px; font: 8pt microsoft sans serif; padding: 1px 3px;\
  59. background-color:rgb(212,208,200); border-width:2px; border-style:outset;\
  60. border-color:#eee #555 #555 #eee; color:black;" + inlineStyle;
  61. logDiv.appendChild(button);
  62. button.addEventListener("click", clickCallback);
  63. };
  64. /*
  65. * Helper to create a button (child of @logDiv) which onclick whitelists @domain
  66. * in internal Firefox storage.
  67. */
  68. var putWhitelistButton = function (logDiv, domain) {
  69. putButton(logDiv, domain, function(){
  70. GM_setValue("whitelisted_" + domain, true);
  71. });
  72. };
  73. /*
  74. * Helper to create a text node with @text and append to @logDiv
  75. */
  76. var putText = function (logDiv, text) {
  77. var node = document.createTextNode(text);
  78. logDiv.appendChild(node);
  79. };
  80. /*
  81. * Return logger div, or create it ad-hoc.
  82. */
  83. var getLogDiv = function () {
  84. var logDiv = document.getElementById(LOG_ID);
  85. if(!logDiv){
  86. logDiv = document.createElement("div");
  87. logDiv.setAttribute("id", LOG_ID);
  88. logDiv.style.cssText="position:fixed; top:0; left:0; width:100%;\
  89. padding:5px 5px 5px 29px; font: 8pt microsoft sans serif;\
  90. background-color: linen; color:black; border:1px solid black;\
  91. ";
  92. document.body.appendChild(logDiv);
  93. }
  94. return logDiv;
  95. };
  96. /*
  97. * Get array of domains for which it would make sense to whitelist them.
  98. * Sample valid outputs:
  99. * // localhost -> ['localhost']
  100. * // youtube.com -> ['youtube.com']
  101. * // www.youtube.com -> ['youtube.com', 'www.youtube.com']
  102. * // a.b.c.d -> ['c.d', 'b.c.d', 'a.b.c.d']
  103. */
  104. var getDomainsArray = function(documentDomain){
  105. // e.g. domain = www.google.com, topDomain = google.com
  106. var d1 = documentDomain;
  107. var domainsArr = [];
  108. var lastDot1 = d1.lastIndexOf('.');
  109. if(lastDot1 != -1){
  110. var lastDot2 = d1.lastIndexOf('.', lastDot1-1);
  111. if(lastDot2 != -1 && lastDot2 != lastDot1) {
  112. var d2 = d1.substr(lastDot2 + 1);
  113. domainsArr.push(d2);
  114. var lastDot3 = d1.lastIndexOf('.', lastDot2-1);
  115. if(lastDot3 != -1 && lastDot3 != lastDot2) {
  116. var d3 = d1.substr(lastDot3 + 1);
  117. domainsArr.push(d3);
  118. }
  119. }
  120. }
  121. domainsArr.push(d1);
  122. return domainsArr;
  123. };
  124. /*
  125. * Checks if domain we're currently browsing has been whitelisted by the user
  126. * to display popups.
  127. */
  128. var isCurrentDomainWhiteListed = function() {
  129. var domains = getDomainsArray(document.domain);
  130. var whitelisted = domains.some(function(d){
  131. return GM_getValue("whitelisted_" + d);
  132. }); // if any 'd' in 'domains' was whitelisted, we return true
  133. return whitelisted;
  134. };
  135. /*
  136. * "window.open()" returns Window which might be then used by the originator page
  137. * to focus the popup (annoying splash popup) or blur it to retain focus in the original page
  138. * (pay-by-impressions popup, I don't need it to actually see it).
  139. * We need to return the fake window to not encounter JS runtime error when the popup originator
  140. * page wants to call focus() or blur().
  141. */
  142. var FakeWindow = {
  143. blur: function() {return false;},
  144. focus: function() {return false;}
  145. };
  146. /*
  147. * Storing a reference to real "window.open" method in case we wanted
  148. * to actually open a blocked popup
  149. */
  150. var realWindowOpen = global.open;
  151.  
  152. /*
  153. * This function will be called each time a script wants to open a new window,
  154. * if the blocker is activated.
  155. * We handle the blocking & messaging logic here.
  156. */
  157. var fakeWindowOpen = function(url){
  158. if(!bDisplayMessageOnPopupBlocked){
  159. return FakeWindow;
  160. }
  161. var logDiv = getLogDiv();
  162. logMessage(logDiv, url);
  163. if(bDisplayOpenPopupLink){
  164. displayOpenPopupLink(logDiv, arguments);
  165. }
  166. if(bDisplayWhiteListThisDomainLink) {
  167. displayWhiteListThisDomainLink(logDiv);
  168. }
  169. displayCloseButton(logDiv);
  170. return FakeWindow; // see the doc of FakeWindow
  171. };
  172. var logMessage = function (logDiv, url) {
  173. global.upb_counter = (global.upb_counter || 0);
  174. url = (url[0] == '/') ? document.domain + url : url;
  175. var msg = ["UPB has blocked <b>", ++global.upb_counter, "</b> popup windows, last: <u>", url, "</u>"].join("");
  176. logDiv.innerHTML = msg;
  177. console.log(msg);
  178. logDiv.style.display = "block";
  179. };
  180. var displayOpenPopupLink = function (logDiv, realArguments){
  181. putButton (logDiv, "open the popup", function(){
  182. realWindowOpen.apply(null, realArguments);
  183. });
  184. };
  185. var displayWhiteListThisDomainLink = function(logDiv) {
  186. var domainsArr = getDomainsArray(document.domain);
  187. putText(logDiv, ' whitelist the domain: '); // using 'innerHTML += ' breaks event listeners strangely
  188. putWhitelistButton(logDiv, domainsArr[0]);
  189. if(domainsArr[1]){
  190. putWhitelistButton(logDiv, domainsArr[1]);
  191. }
  192. if(domainsArr[2]){
  193. putWhitelistButton(logDiv, domainsArr[2]);
  194. }
  195. };
  196. var displayCloseButton = function(logDiv) {
  197. putButton (logDiv, "x", function(){
  198. logDiv.style.display = 'none';
  199. }, 'background-color: #a00; color:white; margin:0 32px 0 0; float:right');
  200. };
  201. /*
  202. * Override browser's "window.open" with our own implementation.
  203. */
  204. var activateBlocker = function() {
  205. global.open = fakeWindowOpen;
  206. };
  207. // ============================ LET'S RUN IT ================================
  208. var disabled = isCurrentDomainWhiteListed();
  209. if(disabled){
  210. console.log('[UPB] current domain was found on a white list. UPB disabled.');
  211. }else{
  212. activateBlocker();
  213. }
  214. })();