setInterval pauses in Android Browser / Mobile Safari when screen times out - javascript

I've built a simple JavaScript-based timer for a mobile webapp; for the sake of example:
var a = 0;
setInterval(function() {
console.log('a', a);
a++;
}, 1000);
This runs just fine in both Mobile Safari and Android Browser. It will log to console every second and increment the value of a accordingly. (Okay, Android Browser doesn't have console.log support, but let's assume it does.)
The issue: if the screen times out (i.e. user stopped interacting with the page), the setInterval function pauses. It resumes when the user turns on their screen again. This won't work for me as I need timer to keep running.
The questions: Is there a way to prevent the setInterval function from pausing when the screen times out? If not, is it possible to prevent the screen from timing out? Any other alternatives?
Thanks in advance!

Basically, no. The phone enters a sleep state to save battery when the screen times out. Since you can't see anything anyway, a large number of processing tasks are stopped. Similar things will occur when you change tabs/windows (the page is unloaded from memory). Right now there is no way to request that the device stays on from a web application. Future support in Android for accessing hardware may provide this functionality, but personally I doubt it.
If you need always running support, you'll need to write native applications for both systems (plus on Android it can always run).

You can use the Page Visibility API to detect when the page is hidden or visible. For example, if the user navigates away from the browser and back again or the screen turns off and on.
I used this answer to help create by solution.
You will need to store the time you set your interval. Then when the visibilityChange event listener indicates the document is visible again, you can calculate the amount of time that has passed since you first started the interval and update your data as needed.
In my case I was creating a count down timer in my Angular2 project. My page was running on an iPad and the timer was pausing whenever the screen turned off. So I added the event listener in my ngOnInit(). Then when the screen turned back on I could update my timer to show the correct time left since it was started.
I am using the moment npm package to handle my date time.
The timerInfo object is a class variable that gets updated by the interval callback. self.zone.run() is used to propagate the changes to the DOM so that the updated time gets displayed.
Written in typescript:
private timerInfo:{
days?:number,
hours?:number,
minutes:number,
seconds:number
};
private startTime:Moment = moment();
private timerDuration:number = 20; // in minutes
private timerHandle:any;
ngOnInit() {
this.setVisibilityListener();
}
private setVisibilityListener():void {
var self = this;
var hidden, visibilityState, visibilityChange;
if (typeof document.hidden !== "undefined") {
hidden = "hidden";
visibilityChange = "visibilitychange";
visibilityState = "visibilityState";
}
var document_hidden = document[hidden];
document.addEventListener(visibilityChange, function () {
if (document_hidden != document[hidden]) {
if (document[hidden]) {
// Document hidden
console.log("document hidden");
} else {
// Document shown
console.log("document shown; setCountDownTimer()");
self.setCountDownTimer();
}
document_hidden = document[hidden];
}
});
}
private setCountDownTimer():void {
var self = this;
if (self.startTime) {
var startMoment = moment(self.startTime);
var endMoment = startMoment.add(self.timerDuration, "minutes");
console.log("endMoment: ", endMoment.toISOString());
self.clearTimer();
var eventTime = endMoment.unix();
var currentTime = moment().unix();
var diffTime = eventTime - currentTime;
var duration = moment.duration(diffTime * 1000, 'milliseconds');
var interval = 1000;
// if time to countdown
if (diffTime > 0) {
self.timerHandle = setInterval(() => {
self.zone.run(() => {
var diff = duration.asMilliseconds() - interval;
if (diff < 0) {
self.clearTimer();
self.timerComplete();
} else {
duration = moment.duration(duration.asMilliseconds() - interval, 'milliseconds');
self.timerInfo = {
days: moment.duration(duration).days(),
hours: moment.duration(duration).hours(),
minutes: moment.duration(duration).minutes(),
seconds: moment.duration(duration).seconds()
};
// console.log("timerInfo: ", JSON.stringify(self.timerInfo));
}
});
}, 1000);
} else {
self.timerComplete();
}
}
}
private clearTimer():void {
if (this.timerHandle) {
clearInterval(this.timerHandle);
this.timerHandle = null;
}
}

Related

how to bypass the setTimeout throttle in a hidden tab?

I'm am doing a heavy "scientific" (ie, not displaying data) webgl computation. Webgl can't be put in a worker, and doing a lot of webgl blocks the whole browser so I sliced my computation in chunks, and I compute each chunk in a setTimeout() function (after calling getError() to flush the opengl queue). I leave a bit of time in between the chunks so that the browser has time to flush some UI events from the main UI queue and it makes the whole thing feel a bit less sluggish.
My problem is that when the tab is hidden, the setTimeout gets throttled to a one second period which is way too slow for me.
Is there a better solution than what I did? Obviously requestAnimationFrame() doesn't work, since it's never called back in hidden tabs (and it's too slow in visible).
Is there a non-throttled time event in the hidden state? I tried to use window.postMessage() but it's still too fast and the whole browser feels slow.
here is the current state of my research:
function drawTile(sequenceIndex) {
if (sequenceIndex < sequence.length) {
var x = sequence[sequenceIndex][0];
var y = sequence[sequenceIndex][1];
setTilePos(x, y);
modelStage.render(renderer, modelBuffer);
minkowskiPass.render(renderer, minkowskiBuffer, modelBuffer);
copyPass.quad.position.x = x;
copyPass.quad.position.y = y;
copyPass.render(renderer, null, minkowskiBuffer);
var gl = renderer.getContext();
gl.getError();
sequenceIndex++;
if (document.visibilityState != "hidden") {
setTimeout(function () {
drawTile(sequenceIndex);
}, 10);
} else {
//window.postMessage is not rate limited then the tab is hidden
// we need to slow the computation by an event, otherwise the whole browser is unresponsive.
$(window).one('message', function () {
drawTile(sequenceIndex);
});
window.postMessage('lol', '*');
}
} else
console.timeEnd('computation');
}
console.time('computation');
drawTile(0);
Here's another convoluted workaround for anyone who needs it; you can use the Web Audio API to generate function calls:
var setTimeout2 = (function () {
var samples = 2048;
var fns = [];
var context = new AudioContext();
var source = context.createBufferSource();
var node = context.createScriptProcessor(samples, 1, 1);
// This gets fired every ~46 milliseconds. You can change
// `samples` to another valid value (256, 512, 1024, 2048,
// 4096, 8192, or 16384); then it'll get called every
// `samples / context.sampleRate` seconds (~46 ms for
// `samples == 2048` and `context.sampleRate == 44100`).
node.onaudioprocess = function (e) {
fns = fns.filter(function (fn) {
return !fn(Date.now() - fn.t);
});
};
source.connect(node);
node.connect(context.destination);
window.do_not_garbage_collect = [context, source, node];
return function (fn) {
fn.t = Date.now();
fns.push(fn);
};
}());
// Use like this:
setTimeout2(function (t) {
console.log(t);
// End after 1 second.
if (t > 1000)
return true;
})
Perhaps have a worker thread also run a
postMessage loop and a fraction of the time (every n iterations), either pause or resume the main thread?

Storing JS counting numbers to continue using HTML5 web storage

I'm trying to store my script that counts numbers starting from 23,000 to always continue to appear it's "live" and always counting using Web Storage. I've tried implementing this and so far, I can't seem to get it to work. What would be the best solution to get this to work and function to always count even when refreshing, etc? I've included my JS Fiddle and code below. Any help is kindly appreciated!!
EDIT: To clarify.. I'm trying to have a "live" counter always going no matter what when you go to the page, refresh it, whatever. It's just always going and getting bigger no matter what just like my script does.. However, everytime I refresh it starts back at 23,000.
HTML
<span id="liveNumbers">23,000</span>
JS
if(typeof(Storage)!=="undefined")
{
setInterval(function(){
random = (Math.floor((Math.random()*2)+1));
var plus = Math.random() < 0.5 ? 1 : 1;
random = random * plus;
currentnumber = document.getElementById('liveNumbers');
var curnum = parseInt(currentnumber.innerHTML.replace(",",""));
document.getElementById('liveNumbers').innerHTML =
commaSeparateNumber(curnum + random);
}, 3000);
function commaSeparateNumber(val){
while (/(\d+)(\d{3})/.test(val.toString())){
val = val.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
}
return val;
}
}
else
{
// Sorry! No Web Storage support..
}
Here's my attempt: fiddle
The logic:
On first visit (no localStorage data) the counter is reset to 23000.
Counter runs while page is open.
When closing the page, the current counter value is stored together with the current timestamp (lastSessionEnd).
When user loads the page again, the time that has passed since he closed the page is translated into interval cycles which are passed to the randomRange function and added to the stored counter from the last session.
Here's the code:
if(window.localStorage) {
//configs
var updateInterval = 3000; //ms
function randomRange() {
return Math.floor(Math.random()*3)+1; // [1..3] range
}
var counter = +localStorage.getItem('counter');
if (!counter) { //first load
counter = 23000;
} else { //simulate randomness that would have happened while the user was away from the page
var lastSessionEnd = +localStorage.getItem('lastSessionEnd');
for(var l = Math.floor((getUnixTimeStamp() - lastSessionEnd)*1000/updateInterval); l--;) {
counter += randomRange();
}
}
var liveNumbers = document.getElementById('liveNumbers'); //cache DOM query
function refreshDisplay() {
liveNumbers.innerHTML = commaSeparateNumber(counter);
}
refreshDisplay();
setInterval(function() {
counter += randomRange();
refreshDisplay();
}, updateInterval);
function commaSeparateNumber(val) {
while (/(\d+)(\d{3})/.test(val.toString())){
val = val.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
}
return val;
}
function getUnixTimeStamp() {
return Math.floor(Date.now()/1000);
}
window.addEventListener('beforeunload', function() {
localStorage.setItem('counter', counter);
localStorage.setItem('lastSessionEnd', getUnixTimeStamp());
});
} else {
// Sorry! No Web Storage support..
}
NOTE: this is not perfect, here are the caveats:
As it is done purely in the front-end, it is easily hackable by manipulating the localStorage. Don't use this for important stuff.
As it uses the localStorage API, if the user opens the page in more than one browser (or more than one computer/device), each one will have a different counter. Also, cleaning all personal data will reset the counter.
Finally, there's an interval cycle rounding error, it doesn't account for interrupted interval cycles. E.g. the user closes the page midway through an interval cycle, the next time he opens the page that half-cycle will be discarded and a new one starts. I believe this is a small detail which would take more effort to fix than it's worth, but I'll leave that decision and effort to you.

How to measure FPS & page paint time in Firefox and/or Chrome?

I am working on a performance test for HTML5 canvas & HTML5 SVG projects. As the projects are graphics-focused and require smooth operation, I've chosen refresh rate (fps) and page paint time (ms) as metrics. I am aware of the fact that values calculated from the javascript code are not representative enough, so these values should be taken from the browsers themselves. Therefore, javascript libraries like Mrdoob's stats.js cannot really be used (correct me if I'm wrong). Any info will be appreciated.
What I have found so far:
Mozilla Firefox:
FPS: windows.mozPaintCount
Page Paint Time: ?
Google Chrome:
FPS: built-in FPS-meter (seems to be working only for DOM-elements)
Page Paint Time: built-in PPT-meter (also for DOM-related stuff)
You can use requestAnimationFrame:
(function () {
var active = false;
var frames = 0;
var start;
var frame = function () {
frames = frames + 1;
if (active) {
window.requestAnimationFrame(frame);
}
};
window.FPS = {
start: function () {
active = true;
frames = 0;
start = window.performance.now();
frame();
},
end: function () {
active = false;
var seconds = (window.performance.now() - start) / 1000;
var fps = Math.round(frames / seconds);
alert(fps);
}
};
}());
You can perhaps use window.performance.now(). This new API was intended for things like this where you need sub-ms times.
It will (intend to) give you a high-resolution timestamp in higher resolution than ms but not without drawbacks:
Chrome currently returns milliseconds so no gain there (may be fixed in Canary by now)
Resolution depends on the underlying system
and mozPaintCount is a proprietary call which only works in Firefox.
So no matter how you twist and turn on this you will only get so far.
But you can try something like this:
function startPaintOperation() {
var startTime = 0, endTime = 0;
startTime = performance.now() || new Date().getTime(); // with fallback
... paint
endTime = performance.now() || new Date().getTime();
return endTime - startTime;
}
I don't think you can get much closer than this without using built-in tools of the browser.
See specifications here for more details.
Hope this helps.

JS difference between enter page and refresh page

Is it possible to detect situation when page is entered and when the same page is refreshed
if (entered) alert("hi");
if (refreshed) alert("you've refreshed");
Somehow there are some little differences between page rendering when entered and when refreshed and it would be much easier to detect the case than to debug it for me (if its even possible - maybe some browser optimization stuff is causing it).
This isn't an ideal solution, but if your page can load in under 5 seconds than this will work, and assuming you are not navigation to another page, then returning within 5 seconds.
window.onbeforeunload = function(){
window.sessionStorage.setItem('lastvisit', new Date().getTime());
}
var lastVisit = +window.sessionStorage.getItem('lastvisit');
var isRefresh = (new Date().getTime() - lastVisit) < 5000;
console.log(isRefresh);
There is no perfect way of tracking reloads verses new page loads but this solution works in most situations. Use sessionStorage in combination with an unload event:
(function (win) {
'use strict';
var reloaded = false,
ss = win.sessionStorage,
offset = 1000, // 1 second, may need tweaking if
// your page takes a long time to load/where
// this code is located in your page
now = function () {
return (new Date()).getTime();
},
lastUnload = ss.getItem('lastunload'),
loadStatus = document.getElementById('status');
// sessionStorage returns null if nothing was stored
if (lastUnload !== null) {
// sessionStorage returns a string, +lastUnload
// coerces the string held in lastUnload into an integer
// see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Arithmetic_Operators#-_.28Unary_Negation.29
if (+lastUnload + offset > now()) {
reloaded = true;
}
}
win.addEventListener('unload', function () {
ss.setItem('lastunload', now());
}, false);
if (lastUnload === null) {
loadStatus.innerHTML = 'First visit of session.';
} else if (reloaded) {
loadStatus.innerHTML = 'Page was reloaded.';
} else {
loadStatus.innerHTML = 'Navigated back to page after leaving';
}
}(window));
This code defines a page reload as returning to the page within 1 second of leaving it, so there could be false positives if someone leaves the page and immediately hits the back button but with normal browsing behavior that really shouldn't happen. You can modify the offset variable if you want to give more or less leeway, but 1 second seems to be a good default.
After developing this code I also found this similar answer.
If sessionStorage is available, you can use that.
if (!window.sessionStorage.getItem('visited')) {
//entered
window.sessionStorage.setItem('visited', true);
}
else {
//refreshed
}
More on sessionStorage

Rate limiting to prevent malicious behavior in ExpressJS

Someone made me aware of some flaws in an application I'm working on (mostly within my JavaScript on the front-end), that leaves open the possibility of, say, clicking a ton of buttons at once and sending out a ton of transactional emails. This is clearly not good.
I think one way to handle this in ExpressJS is by using app.all() to count the number of requests that happen within a certain timeframe. I'd store this in the session metadata with timestamps, and if more than X requests happen in Y time, I cut them off for awhile until the limit expires.
Has anyone done this before or have any tips/hints to help me out? Something that's easy to drop in and out of my app is preferable. Thanks!
You could use the Collate object in your webpage.
function Collate(timeout) {
this.timeout = timeout || 1000;
}
Collate.prototype = {
time: 0,
idle: function() {
var t = new Date().getTime();
return (t - this.time > this.timeout && (this.time = t));
},
prefer: function(func) {
this.func = func;
clearTimeout(this.timer);
this.timer = setTimeout(func, this.timeout);
}
};
If you want a function to run once and not run again within the next 1 second.
Like if you want to prevent the user from submitting a form many times, you do this:
var timer = new Collate(3000); //3 seconds
button1.onclick = function() {
if(timer.idle()) {
button1.form.submit();
} else alert("Don't click too quickly!");
}
//or on the form tag
<script>var submitTimer = new Collate(3000);</script>
<form action="post" onsubmit="return submitTimer.idle();">
If you expect an event to fire multiple times and only want to react to the last time it fires.
Like if you want to search after a user has finished typing, you do this:
var timer = new Collate(700); //0.7 seconds
textfield1.onkeyup = function() {
timer.prefer(function() {
autocomplete.search(textfield1.value);
});
};

Categories

Resources