I write extension for Chrome. And I need run delayed tasks when background page inactive. Cause setTimeout not working in background tabs, I try emulate setTimeout with setInterval, like code below (located in content script):
window.timings = [];
function set_timeout(func, time){
var now = new Date() / 1;
window.timings.push({
func: func,
time: time + now
});
}
function tick(){
var now = new Date() / 1;
window.timings = window.timings.filter(function(delay_obj){
if (now > delay_obj.time){
delay_obj.func.call();
return false;
} else {
return true;
}
});
}
$(function() {
setInterval(tick, 1000);
// some code
});
And it don't work when set_interval call in delay function:
set_timeout(function(){
console.log('func1');
}, 2000);
set_timeout(function(){
console.log('func2');
set_timeout(function(){
console.log('func3');
}, 3000);
}, 3000);
Output:
func1
func2
Why func3 not displayed?
You're apparently using an event page declared with "persistent": false in manifest.json, it is unloaded after 15 seconds of inactivity. The linked documentation says to use chrome.alarms API.
For a delay less than 15 seconds since the last chrome event:
Use setTimeout or setInterval.
For a delay of 15-60 seconds since the last chrome event:
Don't use the event page, switch to "persistent": true in manifest.json.
For a delay of 60 seconds or more:
manifest.json:
"permissions": ["alarms"],
background script:
chrome.alarms.create("MyInterval1", {when: Date.now() + 1 * 60e3});
chrome.alarms.onAlarm.addListener(function(alarm) {
if (alarm.name == "MyInterval1") {
console.log("Yay!");
chrome.alarms.create("MyInterval1", {when: Date.now() + 1 * 60e3});
}
});
Also note:
Other asynchronous HTML5 APIs like notifications and geolocation will not complete if the event page shuts down. Instead, use equivalent extension APIs, like notifications.
If your extension uses, extension.getBackgroundPage, switch to runtime.getBackgroundPage instead. The newer method is asynchronous so that it can start the event page if necessary before returning it.
Note that in a published extension the interval between the next alarm and the last fired alarm is at least 1 minute even if you specify a smaller value like 15 seconds (15*1000).
Source: https://developer.chrome.com/docs/extensions/reference/alarms/#method-create
In order to reduce the load on the user's machine, Chrome limits
alarms to at most once every 1 minute but may delay them an arbitrary
amount more. That is, setting delayInMinutes or periodInMinutes to
less than 1 will not be honored and will cause a warning. when can
be set to less than 1 minute after "now" without warning but won't
actually cause the alarm to fire for at least 1 minute.
To help you debug your app or extension, when you've loaded it
unpacked, there's no limit to how often the alarm can fire.
Related
I am currently trying to get a repeating sound effect, which is getting slower over time with setTimeout() in sync with an animation. As soon as I get it in sync it will work and stay in sync for the time I am working on the program. But now when I was away for about 1 1/2 hours and run the program again exactly as I left it, the sound is no longer in sync with the animation. The same thing happend to me with the same program yesterday when I was away for some time and overnight.
So I was thinking that setTimeout() is somehow working with the current time and will work differently at different times. Can someone confirm this?
Here is my code for reference.
The timeout function:
const timeoutRollSound = (time = 0, index = 0) => {
setTimeout(() => {
const audioClick = new Audio(
"foo/bar.wav"
);
audioClick.play();
index++;
timeoutRollSound(0.05 * Math.pow(index, 2) + 3 * index - 50, index)
}, time);
};
The animation:
$(".itemToAnimate").animate(
{ right: endpoint },
{
duration: 10000,
easing: "easeOutQuint",
}
);
I had this issue in Java years ago. Here's what's going on.
When you set a timeout (like you are doing) you are actually saying "I don't want this function to execute before X milliseconds". So the timeout function may be ready to run, but JavaScript or the browser might be doing something else.
setInterval might work better. But the other thing you can do is include the difference between when the code was eligible to be run and the time it was actually run at, like:
setTimeout(() => {
const audioClick = new Audio(
"foo/bar.wav"
);
audioClick.play();
index++;
timeoutRollSound(0.05 * Math.pow(index, 2) + 3 * index - 50, index)
timeoutRollSound.last = Date.now();
}, time - ((Date.now - timeoutRollSound.last) );
This reminds me of an issue I was having with another JS library and could be related. If you put the tab in browser to the background, the execution will be suspended. From what I'm getting from your code, you rely on the fact that the recursion setTimeout will run constantly which could be the source of your issues.
This could be the issue you are having, take a look: Chrome: timeouts/interval suspended in background tabs?
I've got a countdown on my website done with setTimeout() but I realize that with Internet Explorer 11 (and older I guess) when the user right clicks anywhere on the browser, the countdown just stops because the "context menu" from the browser is open. The countdown starts back when the context menu is close.
The solution of blocking the rightclick during this countdown is not an usable solution in my case.
I've also check the requestAnimationFrame() method, but if the user minimize the browser, the countdown also stops.
The countdown is not lasting forever, it can last from 3 minutes to 10 seconds.
Any workaround/ideas ?
Thanks
You can't rely on setTimeout or setInterval to track time. They will approximately run when they are meant to but it is dependent on what else is taking up processing time and some blocking events like you have discovered. You should think of them as a way to update your clock but maintain the clock state as a differential between the time you started and the time your timer function fires. Because the timing of when the function fires isn't exact I try to set the interval lower than what I want. If I want it to tick every second then I set the interval to a half or quarter second to ensure that I make up for misses quicker.
Here is a rough example:
function countdown(duration) {
const start = Date.now();
const interval = window.setInterval(function() {
const remaining = duration - (Date.now() - start);
if (remaining <= 0) {
console.log(0);
window.clearInterval(interval);
return;
}
console.log(remaining);
}, 500);
}
countdown(5000);
Is there any way to create an alarm for which it guarantees to be triggered at a provided time? Chrome does provide the chrome.alarms API which can be used to create alarms, however the system may delay the alarm indefinitely:
chrome.alarms.create({
when: startTime,
periodInMinutes: 60 // is not actually triggered every hour
});
The same thing goes with setTimeout:
function update(){
console.log(new Date());
setTimeout(update, 1 * 60 * 1000); // also isn't triggered every minute
}
Just after 10 minutes of the computer being idle, the update loop starts being delayed.
I'm looking for a way to guarantee an alarm going off at a defined time (given the computer is on and the extension is loaded, of course).
I have a jQuery slider on my site and the code going to the next slide is in a function called nextImage. I used setInterval to run my function on a timer, and it does exactly what I want: it runs my slides on a timer. BUT, if I go to the site in Chrome, switch to another tab and return, the slider runs through the slides continuously until it 'catches up'. Does anyone know of a way to fix this. The following is my code.
setInterval(function() {
nextImage();
}, 8000);
How to detect when a tab is focused or not in Chrome with Javascript?
window.addEventListener('focus', function() {
document.title = 'focused';
},false);
window.addEventListener('blur', function() {
document.title = 'not focused';
},false);
To apply to your situation:
var autopager;
function startAutopager() {
autopager = window.setInterval(nextImage, 8000);
}
function stopAutopager() {
window.clearInterval(autopager);
}
window.addEventListener('focus', startAutopager);
window.addEventListener('blur', stopAutopager);
Note that in the latest version of Chromium, there is either a bug or a 'feature' which is making this less reliable, requiring that the user has clicked at least once anywhere in the window. See linked question above for details.
I post an answer here: How can I make setInterval also work when a tab is inactive in Chrome?
Just do this:
setInterval(function() {
$("#your-image-container").stop(true,true);
nextImage();
}, 1000);
inactive browser tabs buffer some of the setInterval or setTimeout functions.
stop(true,true) - will stop all buffered events and execute immadietly only last animation.
The window.setTimeout() method now clamps to send no more than one timeout per second in inactive tabs. In addition, it now clamps nested timeouts to the smallest value allowed by the HTML5 specification: 4 ms (instead of the 10 ms it used to clamp to).
A few ideas comes to mind:
Idea #1
You can make it so that a short burst is idempotent. For example, you could say:
function now() {
return (new Date()).getTime();
}
var autopagerInterval = 8000;
function startAutopager() {
var startImage = getCurrentImageNumber();
var startTime = now();
var autopager = setInterval(
function() {
var timeSinceStart = now() - startTime();
var targetImage = getCurrentImageNumber + Math.ceil(timeSinceStart/autopagerInterval);
if (getCurrentImageNumber() != targetImage)
setImageNumber(targetImage); // trigger animation, etc.
},
autopagerInterval
);
return autopager;
}
This way even if the function runs 1000 times, it will still run in only a few milliseconds and animate only once.
note: If the user leaves the page and comes back, it will have scrolled. This is probably not what the original poster wants, but I leave this solution up since it is sometimes what you want.
Idea #2
Another way to add idempotence (while still keeping your nextImage() function and not having it scroll to the bottom of the page) would be to have the function set a mutex lock which disappears after a second (cleared by another timeout). Thus even if the setInterval function was called 1000 times, only the first instance would run and the others would do nothing.
var locked = false;
var autopager = window.setInterval(function(){
if (!locked) {
locked = true;
window.setTimeout(function(){
locked=false;
}, 1000);
nextImage();
}
}, 8000);
edit: this may not work, see below
Idea #3
I tried the following test:
function f() {
console.log((new Date()) + window.focus());
window.setTimeout(f, 1000);
}
f();
It seems to indicate that the function is being called every second. This is odd... but I think this means that the callbacks are being called, but that the page renderer refuses to update the page in any graphical way while the tab is unfocused, delaying all operations until the user returns, but operations keep piling up.
Also the window.focus() function doesn't say if the window has focus; it GIVES focus to the window, and is thus irrelevant.
What we want is probably this: How to detect when a tab is focused or not in Chrome with Javascript? -- you can unset your interval when the window loses focus (blur), and reset it when it gains focus.
I don't know exactly what is going on in your function nextImage(), but I had a similar issue. I was using animate() with setInterval() on a jQuery image slider that I created, and I was experiencing the same thing as you when I switched to a different tab and back again. In my case the animate() function was being queued, so once the window regained focus the slider would go crazy. To fix this I just stopped the animate() function from queuing.
There are a couple ways you can do this. the easiest is with .stop(), but this issue and ways to fix it are documented in the jQuery docs. Check this page near the bottom under the heading additional notes: http://api.jquery.com/animate/
I had faced similar issue, somehow this code below works fine for me.
var t1= window.setInterval('autoScroll()', 8000);
window.addEventListener('focus', function() {
focused = true;
window.clearInterval(t1);
t1 = window.setInterval('autoScroll()', 8000);
},false);
window.addEventListener('blur', function() {
focused = false;
window.clearInterval(t1);
},false)
function autoScroll()
{
if ( running == true){
if ( focused = true){
forwardSlide();
}
}
else {
running = true;
}
}
If you are using Soh Tanaka's image slider then just add this...to solve your Google Chrome issue:
$(".image_reel").stop(true, true).fadeOut(300).animate({ left: -image_reelPosition}, 500 ).fadeIn(300);
Take note of the .stop() function. Ignore the fading in and out stuff, that's what I used on my version
Thanks
Seconding the comment by jgerstle to use page visibility events instead, see https://www.w3.org/TR/page-visibility/#example-1-visibility-aware-video-playback for more around subscribing to 'visibilitychange' for hidden/visible states.
This seems to be more useful than focus/blur these days as it covers visible-but-not-selected windows if concerned also about multi-window operating systems.
I am not too familiar with the specifics of every javascript implementation on each browser. I do know however that using setTimeout, the method passed in gets called on a separate thread. So would using a setTimeout recursively inside of a method cause its stack to grow indefinitely until it causes a Stack Overflow? Or would it create a separate callstack and destroy the current frame once it goes out of focus? Here is the code that I'm wondering about.
function pollServer()
{
$.getJSON("poll.php", {}, function(data){
window.setTimeout(pollServer, 1000);
});
}
window.setTimeout(pollServer, 0);
I want to poll the server every second or so, but do not want to waste CPU cycles with a 'blocking loop' - also I do not want to set a timelimit on how long a user can access a page either before their browser dies.
EDIT
Using firebug, I set a few breakpoints and by viewing the "Script -> Stack" panel saw that the call stack is literally just "pollServer" and it doesn't grow per call. This is good - however, do any other implementations of JS act differently?
I am not sure if it would create a stack overflow, but I suggest you use setInterval if the period is constant.
This is how prototype implements its PeriodicalExecuter.
// Taken from Prototype (www.prototypejs.org)
var PeriodicalExecuter = Class.create({
initialize: function(callback, frequency) {
this.callback = callback;
this.frequency = frequency;
this.currentlyExecuting = false;
this.registerCallback();
},
registerCallback: function() {
this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},
execute: function() {
this.callback(this);
},
stop: function() {
if (!this.timer) return;
clearInterval(this.timer);
this.timer = null;
},
onTimerEvent: function() {
if (!this.currentlyExecuting) {
try {
this.currentlyExecuting = true;
this.execute();
} finally {
this.currentlyExecuting = false;
}
}
}
});
setTimeout executes sometime later in the future in the event pump loop. Functions passed to setTimeout are not continuations.
If you stop and think about it, what useful purpose or evidencec is there that the call stack is shared by the timeout function.
If they were shared what stack would be shared from the setter to the timeout function ?
Given the setter can do a few returns and pop some frames - what would be passed ?
Does the timeout function block the original thread ?
Does the statement after the setTimeout function execute after the timeout executes ?
Once you answer those questions it clearly becomes evident the answerr is NO.
setTimeout does not grow the callstack, because it returns immediately. As for whether your code will run indefinitely in any browser, I'm not sure, but it seems likely.
take a look at the jQuery "SmartUpdater" plugin.
http://plugins.jquery.com/project/smartupdater
Following features are available:
stop() - to stop updating.
restart() - to start updating after pause with resetting time interval to minTimeout.
continue() - to start updating after pause without resetting time interval.
status attribute - shows current status ( running | stopping | undefined )
updates only if new data is different from the old one.
multiplies time interval each time when data is not changed.
handle ajax failures by stopping to request data after "maxFailedRequests".