Prompt On New Tab

Display a confirmation dialog when the site wants to open a new tab, so that user has the chance to cancel or allow it to open in a new or current tab. This script won't work if the user opens a link in a new tab using web browser's "Open in a new tab", "Open in background tab", or similar which are web browser internal or browser extension features.

  1. // ==UserScript==
  2. // @name Prompt On New Tab
  3. // @namespace https://greasyfork.org/en/users/85671-jcunews
  4. // @version 1.2.19
  5. // @license GNU AGPLv3
  6. // @author jcunews
  7. // @description Display a confirmation dialog when the site wants to open a new tab, so that user has the chance to cancel or allow it to open in a new or current tab. This script won't work if the user opens a link in a new tab using web browser's "Open in a new tab", "Open in background tab", or similar which are web browser internal or browser extension features.
  8. // @match *://*/*
  9. // @grant none
  10. // @run-at document-start
  11. // ==/UserScript==
  12.  
  13. /*
  14. The rejectList and allowList contains the list of source-target rules. The main purpose of these lists
  15. is to provide an automated action on whether opening a new tab is allowed or not. Any matching
  16. rejectList rule will reject the new tab to open without prompting the user. Any matching allowList rule
  17. will allow the new tab to open without prompting the user.
  18.  
  19. rejectList has higher priority than allowList. Each source-target rule is an array of two values:
  20. *SourceURL* then *TargetURL*. *SourceURL* denotes the current tab's URL, while *TargetURL* denotes the
  21. new tab's URL.
  22.  
  23. Both source and target URL values can be either a string type or a regular expression object. Each will
  24. be compared against the whole URL.
  25.  
  26. If string type is used, it must match the whole URL instead of part of it. The comparison is done
  27. without case sensitivity (i.e. character case is ignored). A `*` wildcard can be used to match any one
  28. or more characters. e.g.:
  29.  
  30. - `"*"` will match any URL.
  31.  
  32. - `"*://www.site.com/*"` will match against `http://www.site.com/` including
  33. `http://www.site.com/home`.
  34.  
  35. - `"www.site.com"` will never match against `http://www.site.com/`.
  36.  
  37. - `"*www.site.com*"` will match against `http://www.site.com/` but also against
  38. `http://www.proxy.com/?url=http://www.site.com/`.
  39.  
  40. - `"*://* /*www.site.com*"` (without the space in the middle) will match against
  41. `http://www.proxy.com/?url=http://www.site.com/` but not against `http://www.site.com/`.
  42.  
  43. - `"*url=*www.site.com*"` will match against `http://www.proxy.com/?url=http://www.site.com/`
  44. but also against `http://www.proxy.com/?url=http://www.other.com/&alt=http://www.site.com/`.
  45.  
  46. If regular expression object is used, it may match only part of the whole URL, depending on the regular
  47. expression pattern itself. The comparison is done with or without case sensitivity, depending on the
  48. regular expression flags.
  49.  
  50. A source-target rule will match if the source and target URLs matches.
  51. */
  52.  
  53. ((open_, submit_, wael, ele) => {
  54.  
  55. //===== CONFIGURATION BEGIN =====
  56.  
  57. var rejectList = [
  58. ["*", "*://*.doubleclick.net/*"],
  59. ["*", /^https?:\/\/[^.]+\.adservices?\.com\//i],
  60. ["*://site.com/*", /^.*?:\/\/site\.com\/(offer|popup)/i]
  61. ];
  62.  
  63. var allowList = [
  64. ["*://www.bing.com/*", "*"],
  65. ["*://www.google.*/*", "*://*.google.*/*"]
  66. ];
  67.  
  68. //If promptToOpenInCurrentTab is enabled, when the confirmation dialog is shown and the user chose
  69. //Cancel, an additional confirmation dialog will be shown to confirm whether the URL should be
  70. //opened in current tab or not.
  71. var promptToOpenInCurrentTab = true;
  72.  
  73. //===== CONFIGURATION END =====
  74.  
  75. [rejectList, allowList].forEach(list => {
  76. list.forEach(pair => {
  77. pair.forEach((str, i) => {
  78. if (("string" === typeof str) || (str instanceof String)) {
  79. pair[i] = new RegExp("^" + str.replace(/([(){}\[\]\\^$.+?|])/g,
  80. "\\$1").replace(/([*])/g, ".*?") + "$", "i");
  81. }
  82. });
  83. });
  84. });
  85.  
  86. function checkUrl(target, curUrl) {
  87. function checkUrlPair(pair) {
  88. return pair[0].test(curUrl) && pair[1].test(target);
  89. }
  90.  
  91. curUrl = location.href;
  92. if (rejectList.some(checkUrlPair)) {
  93. return -1;
  94. } else if (allowList.some(checkUrlPair)) {
  95. return 1;
  96. } else return 0;
  97. }
  98.  
  99. function dummy(){}
  100.  
  101. function doWindow(w, z) {
  102. try {
  103. if (w.name) this.push(w.name);
  104. (w.contentWindow || w).document.querySelectorAll("iframe,frame").forEach(doWindow, this);
  105. } catch(z) {}
  106. }
  107.  
  108. function isExistingFrameName(name, a, w, p, z) {
  109. if (!name) return true;
  110. a = ["_parent", "_self", "_top"];
  111. if (top !== window) {
  112. try {
  113. top.name;
  114. w = top;
  115. } catch(z) {
  116. w = window;
  117. while ((w = w.parent) && (w !== p)) {
  118. try {
  119. w.name;
  120. } catch(z) {
  121. w = null;
  122. }
  123. p = w;
  124. }
  125. w = w || p;
  126. }
  127. } else w = window;
  128. if (w) doWindow.call(a, w);
  129. return a.includes(name);
  130. }
  131.  
  132. open_ = window.open;
  133. window.open = function(url, name) {
  134. var loc = {};
  135. if (isExistingFrameName(name)) {
  136. return open_.apply(this, arguments);
  137. } else switch (checkUrl(url)) {
  138. case 1:
  139. return open_.apply(this, arguments);
  140. case 0:
  141. if (confirm("This site wants to open a new tab.\nDo you want to allow it?\n\nURL:\n" + url)) {
  142. return open_.apply(this, arguments);
  143. } else if (
  144. promptToOpenInCurrentTab &&
  145. confirm("URL:\n" + url + "\n\nDo you want to open it in current tab instead?")
  146. ) {
  147. name = "_top";
  148. return open_.apply(this, arguments);
  149. }
  150. }
  151. return {
  152. document: {
  153. close: dummy,
  154. location: loc,
  155. open: dummy,
  156. write: dummy
  157. },
  158. location: loc
  159. };
  160. };
  161.  
  162. function reject(ev) {
  163. if (!ev || !ev.preventDefault) return;
  164. ev.preventDefault();
  165. ev.stopPropagation();
  166. ev.stopImmediatePropagation();
  167. }
  168.  
  169. function actionCheckUrl(ele, url, msg, ev) {
  170. switch (checkUrl(url)) {
  171. case 0:
  172. if (!confirm(msg + "\nDo you want to allow it?\n\nURL:\n" + url)) {
  173. if (
  174. promptToOpenInCurrentTab &&
  175. confirm("URL:\n" + url + "\n\nDo you want to open it in current tab instead?")
  176. ) {
  177. ele.target = "_top";
  178. break;
  179. }
  180. reject(ev);
  181. return false;
  182. } else break;
  183. case -1:
  184. reject(ev);
  185. return false;
  186. }
  187. return true;
  188. }
  189.  
  190. function onFormSubmit(ev) {
  191. if ((/^https?:/).test(this.action) && !isExistingFrameName(this.target) &&
  192. !actionCheckUrl(this, this.action, "This site wants to submit a form in a new tab.")) return;
  193. return submit_.apply(this, arguments);
  194. }
  195. submit_ = HTMLFormElement.prototype.submit;
  196. HTMLFormElement.prototype.submit = onFormSubmit;
  197.  
  198. function windowSubmit(ev){
  199. if (
  200. !ev.defaultPrevented &&
  201. (/^https?:/).test(ev.target.action) && !isExistingFrameName(ev.target.target)
  202. ) {
  203. return actionCheckUrl(ev.target, ev.target.action,
  204. "This site wants to submit a form in a new tab.", ev);
  205. }
  206. }
  207. addEventListener("submit", windowSubmit);
  208.  
  209. function onAnchorClick(ev) {
  210. if ((/^(?:f|ht)tps?:/).test(this.href) && !isExistingFrameName(this.target)) {
  211. return actionCheckUrl(this, this.href, "This site wants to open a new tab.", ev);
  212. }
  213. return;
  214. }
  215.  
  216. function windowClick(ev, a){
  217. if (ev.button || !(a = ev.target) || ev.defaultPrevented) return;
  218. if (a.tagName === "A") {
  219. return onAnchorClick.call(a, ev);
  220. } else while (a = a.parentNode) {
  221. if (a.tagName === "A") return onAnchorClick.call(a, ev);
  222. }
  223. }
  224. addEventListener("click", windowClick);
  225.  
  226. wael = window.addEventListener;
  227. window.addEventListener = function(type, fn) {
  228. var res = wael.apply(this, arguments);
  229. if (type === "click") {
  230. removeEventListener("click", windowClick);
  231. wael("click", windowClick);
  232. } else if (type === "submit") {
  233. removeEventListener("click", windowSubmit);
  234. wael("submit", windowSubmit);
  235. }
  236. return res;
  237. };
  238.  
  239. })();