- // ==UserScript==
- // @name Background tab setTimeout muffler
- // @namespace BSP
- // @description Defers setTimeout calls if no user input has been received for 15 seconds. Useful for stopping background tabs from continuing to use CPU cycles.
- // @include http://*
- // @include https://*
- // @exclude http://*.google.tld/*
- // @exclude https://*.google.tld/*
- // @exclude http://irc.lc/*
- // @exclude https://irc.lc/*
- // @exclude http://www.newsblur.com/*
- // @exclude https://www.newsblur.com/*
- // @exclude http://steamcommunity.com/id*
- // @exclude https://www.duolingo.com/*
- // @version 1
- // @run-at document-start
- // ==/UserScript==
-
- var setInterval_old = unsafeWindow.setInterval;
- var setTimeout_old = unsafeWindow.setTimeout;
- var clearInterval_old = unsafeWindow.clearInterval;
- var clearTimeout_old = unsafeWindow.clearTimeout;
- var startDate = new Date;
-
- //QueuedTimer format: {ID: [startTime, repeat, func, delay, args]}
- var queuedTimers = null;
- //ActiveTimer format: {ID: [repeat, func, delay, args, browserTimerID]}
- var activeTimers = {};
- var nextID = 1000000;
- var lastUserInteraction = Date.now();
-
- function debugLog() {
- false && console && console.log && console.log(Array.slice(arguments, 0));
- }
-
- function muffleTimers() {
- return (Date.now() - lastUserInteraction) > 15000;
- }
-
- function addTimer(ID, repeat, func, delay, args, isNew) {
- if(typeof func != "function" && typeof func != "string") {
- debugLog("addTimer", "invalid func", arguments);
- throw Error("setInterval/setTimeout: invalid func");
- }
- delay = +(delay || 0); //coerce to number
- if(isNaN(delay)) { //check for NaN (results from non-number strings/objects, etc.)
- debugLog("addTimer", "invalid delay", arguments);
- throw Error("setInterval/setTimeout: invalid delay");
- }
-
- //if(Object.keys(queuedTimers || {}).length + Object.keys(activeTimers).length > 500 && new Date - startDate > 15000) {
- // debugLog("addTimer", "More than 100 timers!? Fuck that!", arguments);
- // throw Error("STFU PLZ");
- //}
-
- if(muffleTimers()) {
- if(!queuedTimers) queuedTimers = {};
- isNew && debugLog("addTimer", "Deferred", [ID, repeat, delay, func]);
- queuedTimers[ID] = [Date.now(), repeat, func, delay, args];
- } else {
- var browserTimerID = setTimeout_old(execTimer, delay, ID, repeat, func, delay, args);
- isNew && debugLog("addTimer", "Scheduled", [ID, repeat, delay, func]);
- activeTimers[ID] = [repeat, func, delay, args, browserTimerID];
-
- }
- return ID;
- }
-
- function execTimer(ID, repeat, func, delay, args) {
- delete activeTimers[ID];
- debugLog("execTimer", "Executing", ID, repeat);
-
- //Re-add to the list so it can be cleared while in progress
- if(repeat) {
- addTimer(ID, repeat, func, delay, args, false);
- }
-
- try {
- if(typeof func == "string") {
- (function() { unsafeWindow.eval(func); }).apply(unsafeWindow);
- } else {
- func.apply(unsafeWindow, args);
- }
- } catch(ex) { /* Silence it like JS normally does */ debugLog("execTimer", "Error", arguments, ex); }
-
- }
-
- function onUserInteraction(event) {
- lastUserInteraction = Date.now();
- //If any timers are queued, restart them
- if(queuedTimers) {
- debugLog("onUserInteraction", "Processing deferred timers", arguments, queuedTimers);
- try {
- for(var ID in queuedTimers) {
- //QueuedTimer format: {ID: [startTime, repeat, func, delay, args]}
- var timer = queuedTimers[ID];
- var delay = Math.max(0, timer[0] + timer[3] - Date.now());
-
- var browserTimerID = setTimeout_old(execTimer, delay, ID, timer[1], timer[2], timer[3], timer[4]);
- //ActiveTimer format: {ID: [repeat, func, delay, args, browserTimerID]}
- activeTimers[ID] = [timer[1], timer[2], timer[3], timer[4], browserTimerID];
- delete queuedTimers[ID];
- }
- } catch(ex) {
- debugLog("onUserInteraction", "Error", ex);
- }
- queuedTimers = null;
- }
- removeEventListener("mousemove", onUserInteraction);
- removeEventListener("keydown", onUserInteraction);
-
- setTimeout_old(function() {
- addEventListener("mousemove", onUserInteraction);
- addEventListener("keydown", onUserInteraction);
- }, 5000);
- }
-
- addEventListener("mousemove", onUserInteraction);
- addEventListener("keydown", onUserInteraction);
-
-
- unsafeWindow.setInterval = function setInterval() {
- var func = arguments[0];
- var delay = arguments[1];
- var args = Array.slice(arguments, 2);
- return addTimer(nextID++, true, func, delay, args, true);
- };
-
-
- unsafeWindow.setTimeout = function setTimeout() {
- var func = arguments[0];
- var delay = arguments[1];
- var args = Array.slice(arguments, 2);
- return addTimer(nextID++, false, func, delay, args, true);
- };
-
- function clearTimer(ID) {
- if(queuedTimers && typeof(queuedTimers[ID]) != "undefined") {
- debugLog("clearTimer", "Clearing deferred timer", arguments);
- delete queuedTimers[ID];
- }
- if(typeof(activeTimers[ID]) != "undefined") {
- debugLog("clearTimer", "Clearing active timer", arguments);
- clearTimeout(activeTimers[ID][4]);
- delete activeTimers[ID];
- }
- };
-
- unsafeWindow.clearTimeout = clearTimer;
- unsafeWindow.clearInterval = clearTimer;