4

I'm using setInterval (within 3 Tampermonkey scripts) to check three different public websites every few seconds, so I can be alerted when specific text appears. These alerts are for freelance work offers, which can expire within seconds so I have to be quick.

It all works correctly, except when I'm working in a different tab or app, then that after about 6 minutes, setInterval starts to "trigger" for the background tab once per minute instead of once every few seconds.

Any suggestions how to fix this? Is it possible to use Date.now() in some way? Note, I'm a complete beginner, willing to learn but need to keep things as simple as possible.

I've tried reloading the page every 3 minutes using window.location.reload() but that doesn't work. I guess I could create a script to activate and focus the tab every few minutes, but that would interrupt anything I was working on. I tested it with the following barebones script against https://www.google.co.uk/, in case something else in my script was causing a problem, but the same happens:

var i = 0;
setInterval(function() {
    console.log("log: i:" + i);
    i = i+1;
    if(i==15) {
        i = 0;
        window.location.reload();
        console.log("reloaded window");
    }

}, 10000);

After a few minutes, i is incremented only once per minute - even following the window reload.

I've looked at this question

It mentions "workers" but can these be used within tampermonkey on public website I don't own? It also provides a link which suggests a workaround of playing an almost inaudible audio file - but I don't know if playing that within my tampermonkey script would work?

I see there are a number of workarounds here but I'm not sure if I can use any of them.

For example, can MutationObserver be used within a tampermonkey to detect changes in a public website? Even if it can, presumably I'd have to reload the webpage every time I needed to checK? Currently I'm using XMLHttpRequest instead of loading the webpage (far quicker and uses less CPU).

Interestingly, the above link seems to suggest that setInterval and SetTimeout are specifically targetted for throttling, I wonder if that means I could use some other function instead.

I've also seen this but I guess I can only use that for a website I own?

  • This is a performance feature of modern browsers, whereby performance on non-active tabs is scaled back after a period of inactivity. I've not tested workers in Tamper/Greasemonkey, but some research suggests [they work](https://stackoverflow.com/a/18691659/519413) and are likely your best workaround. To be honest - I'd argue it's a better pattern over using timers in the first instance. – Rory McCrossan Nov 05 '22 at 15:40
  • 1
    This is called timer throttling and the documentation says you can reset the depth of the timer to avoid throttling by using window.postMessage + a listener for `message` event. – wOxxOm Nov 05 '22 at 15:52
  • Don't rely on timers to run at the speed you told them to, because that's not what timers do: by spec definintion, they run at intervals _at least_ as long as indicated, and _only_ at least as long as indicated, so an interval set to at least 1000 milliseconds can run with half hour intervals and be spec-conformant. If you need to reliably check several different websites, it's probably time to start writing a real globally active browser extension (thankfully, not actually all that difficult, plenty of good tutorials to be found on the web), or just do as a normal app (node, python, etc). – Mike 'Pomax' Kamermans Nov 05 '22 at 15:55
  • I don't understand. Why are you doing something like this on the front end? Why not just use something like python and beautiful soup to parse the html, and then you can just set the script frequency via cron. And if you need a dedicated computer can just buy a raspberry pi zero for the job. it costs just a few bucks. – john-jones Nov 05 '22 at 15:56
  • I have to admit, I don't really understand the link about workers, or how I might get them working in my environment. Also, the link suggests to me I couldn't use a worker against doccument.innerhtml. It does sound like I might be able to use a worker with XMLHttpRequest (if I could get my head around it), but one of the public websites I need to check generates dynamic content via javascript, and that content isn't available using XMLHttpRequest, it's only available in innerHTML once the page has fully loaded and run all its scripts. – Adrian Wood Nov 05 '22 at 16:02
  • john-jones - not heard of beautiful soup! Would this be able to access the html from the website's servers with me logged in somehow? What about content that is not part of the html, but which they deliver via javascript? – Adrian Wood Nov 05 '22 at 16:08
  • WOxxOm - can you provide a link about how to reset throttling via window.postMessage? - – Adrian Wood Nov 05 '22 at 16:09
  • Here's a random example, seems fine: [link](https://medium.com/@adithyaviswam/overcoming-browser-throttling-of-setinterval-executions-45387853a826). – wOxxOm Nov 05 '22 at 17:10
  • I dont know how it could work with logins. I also dont know how it could tackle information delievered via js. – john-jones Nov 05 '22 at 19:38

1 Answers1

2

I can think of a few options.

  • Instead of having three scripts, use a single script, and run that single script on every site (with // @match *://*/*). Then, with that single script, set the interval. Whenever the interval callback runs, use Tampermonkey's GM_setValue and GM_getValue for cross-domain storage to coordinate executions - a callback will first check with getValue whether the last check was more than 3 minutes ago. If so, it calls setValue with the current date and performs the check. This way, even if the script is running on 100 different tabs (some in active tabs, some in background tabs), it'll still run the check once every few minutes.

    To perform the check, use Tampermonkey's GM_xmlHttpRequest to get across same-origin restrictions; make a request to the three sites, and parse them into documents using DOMParser so you can programatically search through them for the elements you're looking for.

  • Perform the checks from a backend app instead of from a browser - for example, with Node and Puppeteer, which won't have throttling issues. To have the results be communicated with you, you could either have a userscript or websocket with your local webserver, or you could integrate the webserver into an Electron app instead. This is the approach I'd prefer, I think it's the most robust.

  • Use workers, which has worked for some

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Thanks - interesting idea about running the script on multiple tabs. Is there any way to make that run a script on a different tab, because one of the websites derives its content by dynamically loading it through java - so xmlHttpRequest doesn't include that content. – Adrian Wood Nov 05 '22 at 16:51
  • I probably do need to consider a separate app (presumably you mean an app running on my PC rather than on the website server which I have no access to)? Again though, the backend app presumably wouldn't be able to access dynamic content. Do you have any tutorial links to help me as a complete beginner? – Adrian Wood Nov 05 '22 at 16:52
  • See my comments against the post above about workers - again, is there anything you can point me to that might help me figure out how to do this? Is it possible to use a worker instead of setInterval just to provide a timer, rather than checking innerHTML or the response form xmlHttpRequest? – Adrian Wood Nov 05 '22 at 16:53
  • (1) As was mentioned by someone else, sending a message to the window might be able to reactivate the tab if you really wanted to go that route (I wouldn't) (2) Yes, it could access dynamic content: https://stackoverflow.com/questions/54109078/puppeteer-wait-for-page-dom-updates-respond-to-new-items-that-are-added-after (3) If it was me I'd strongly prefer to use a separate app rather than a userscript, it'd be much more reliable – CertainPerformance Nov 05 '22 at 16:59
  • Sorry for the delayed update, and thanks everyone for you help. For now, I've successfully deployed an almost silent "beep" sound every 30 seconds to keep setTimeout and setInterval being throttled. I like the idea of perhaps using node.js, BUT I've not gone there yet, partly because I wouldn't know how to login outside of the login form provided by the website. I haven't tried posting a message, but will give that a go. – Adrian Wood Dec 05 '22 at 18:39