Hold to new tab

Hold mouse button over link to open it in new tab. Requires to release mouse to take effect due to popup restrictions.

  1. // ==UserScript==
  2. // @name Hold to new tab
  3. // @description Hold mouse button over link to open it in new tab. Requires to release mouse to take effect due to popup restrictions.
  4. // @namespace util
  5. // @include *
  6. // @version 2016.2.15.18.05
  7. // @grant none
  8. // @author Jakub Mareda aka MXXIV
  9. // @run-at document-start
  10. // ==/UserScript==
  11.  
  12. // Timeout after mousedown when the new tab timeout should be started
  13. // this timeout is to prevent flickering when clicking links normally
  14. var startCoundownTimeout = null;
  15. var startCountdownTime = 100; //140;
  16.  
  17. // the actual countdown before displaying the link
  18. var countdownTimeout = null;
  19. var countdownTime = 500;
  20.  
  21. // Time when the mouse was pressed down
  22. var mouseDownTime = -1;
  23.  
  24. // Target link
  25. var link = null;
  26. // progress bar object, initialized later
  27. var progressBar;
  28.  
  29. function mouseDown(e) {
  30. // only left button
  31. if(e.button != 0)
  32. return;
  33. var el = e.target;
  34. //console.log("Mousedown");
  35. while(el!=null) {
  36. if(el.tagName && el.tagName.toLowerCase()=="a") {
  37. link = el;
  38. //console.log("... link!");
  39. startPreCountdown(e.clientX, e.clientY);
  40. return;
  41. }
  42. el = el.parentNode;
  43. }
  44. //console.log("... not a link.");
  45. }
  46. // Counts but doesn't display anything yet
  47. function startPreCountdown(x,y) {
  48. mouseDownTime = performance.now();
  49. // add cancel listeners
  50. link.addEventListener("mouseout", failIfLeftButton);
  51. link.addEventListener("mouseup", failIfLeftButton);
  52. window.addEventListener("onunload", stopFail);
  53. // Don't use delay if delay is off
  54. if(startCountdownTime>0) {
  55. startCountdownTimeout = setTimeout(startCountdown, startCountdownTime, x, y);
  56. }
  57. else {
  58. startCountdown(x, y);
  59. }
  60. }
  61. // Starts displaying the loader
  62. function startCountdown(x,y) {
  63. progressBar.x = x;
  64. progressBar.y = y;
  65. progressBar.start();
  66.  
  67. countdownTimeout = setTimeout(stopExec, countdownTime);
  68. }
  69. // Stop and do the action
  70. function stopExec() {
  71. //console.log("Open in new tab: "+link.href);
  72. if(link.target!="_blank") {
  73. if(link.target)
  74. link.setAttribute("oldtarget", link.target);
  75. link.target = "_blank";
  76. removeBlankOnClick(link);
  77. }
  78. //link.dispatchEvent(new MouseEvent("click"));
  79. stop();
  80. }
  81. // stop and do nothing
  82. function stopFail() {
  83. //console.log("No action on "+link.href);
  84. stop();
  85. }
  86.  
  87. function failIfLeftButton(mouseEvent) {
  88. if(mouseEvent.button == 0)
  89. stopFail();
  90. }
  91.  
  92. function stop() {
  93. progressBar.stop();
  94. clearTimeout(countdownTimeout);
  95. countdownTimeout = null;
  96. clearTimeout(startCountdownTimeout);
  97. startCountdownTimeout = null;
  98. // clear listeners
  99. link.removeEventListener("mouseout", failIfLeftButton);
  100. link.removeEventListener("mouseup", failIfLeftButton);
  101. window.removeEventListener("onunload", stopFail);
  102. }
  103.  
  104. function calculateTimePercentage() {
  105. var dt = performance.now()-mouseDownTime;
  106. return (dt/(startCountdownTime+countdownTime))*100;
  107. }
  108.  
  109.  
  110. function Renderer(callback) {
  111. this.percents = 0;
  112. this.x = 0;
  113. this.y = 0;
  114. this.getPercents = callback;
  115. this.colorFull = "rgba(86,125,255,0.8)";
  116. this.colorEmpty = "rgba(86,125,255,0.2)";
  117. this.rendering = false;
  118. // cached callback
  119. this.renderLoop = this.renderLoop.bind(this);
  120. }
  121. Renderer.prototype = {
  122. init: function() {
  123. var div = this.div;
  124. if(!div) {
  125. var div = this.div = document.createElement("div");
  126. div.style.position = "fixed";
  127. div.style.width = "40px";
  128. div.style.height = "6px";
  129. div.style.fontSize = "0px";
  130. div.innerHTML = "This div is used to render progress bar for New tab userscript.";
  131. div.style.display = "none";
  132. document.body.appendChild(div);
  133. }
  134. return div;
  135. },
  136. render: function() {
  137. var div = this.div?this.div:this.init();
  138. div.style.top = (this.y-8)+"px";
  139. // hardcoded half width
  140. div.style.left = (this.x-20)+"px";
  141. var c = this.colorFull,
  142. c2 = this.colorEmpty;
  143. var perc = Math.round(this.percents)+"";
  144. div.style.background = "linear-gradient(to right, "+c+" 0%,"+c+" "+perc+"%,"+c2+" "+perc+"%,"+c2+" 100%)";
  145. },
  146. renderLoop: function() {
  147. if(this.rendering) {
  148. if(this.getPercents)
  149. this.percents = this.getPercents();
  150. this.render();
  151. requestAnimationFrame(this.renderLoop);
  152. }
  153. },
  154. start: function() {
  155. this.rendering = true;
  156. (this.div ? this.div:this.init()).style.display = "block";
  157. this.renderLoop();
  158. },
  159. stop: function() {
  160. this.rendering = false;
  161. if(this.div)
  162. this.div.style.display = "none";
  163. }
  164. }
  165.  
  166. progressBar = new Renderer(calculateTimePercentage);
  167.  
  168. window.addEventListener("mousedown", mouseDown);
  169.  
  170. function cancelEvent(e) {
  171. e.preventDefault();
  172. console.log("Cancel ", e.type);
  173. window.removeEventListener(e.type, cancelEvent);
  174. return false;
  175. }
  176. function cancelNext(evtName) {
  177. window.addEventListener(evtName, cancelEvent);
  178. }
  179. /** remove target="_blank" - or set it to original value **/
  180. function removeBlank(e) {
  181. // it must happen AFTER the click event does what it should
  182. setTimeout((()=>{
  183. if(this.hasAttribute("oldtarget")) {
  184. this.setAttribute("target", this.getAttribute("oldtarget"));
  185. this.removeAttribute("oldtarget");
  186. }
  187. else {
  188. this.removeAttribute("target");
  189. }
  190. }).bind(this), 0);
  191. this.removeEventListener(e.type, removeBlank);
  192. }
  193. function removeBlankOnClick(link) {
  194. link.addEventListener("click", removeBlank);
  195. }