简体   繁体   中英

WebKit setInterval and system time change

I have encountered following issue when creating simple task: displaying html clock by using WebKit engine. Additional requirement were to handle system time change and that it should work on Windows. I have used setInterval to achive this but it seems to freeze browser after I change system time backward. For me it looks like WebKit issue. It is easy to reproduce on safari by running this simple code:

<p id="date"></p>
setInterval(SetTime, 1000);
function SetTime() {
document.getElementById('date').textContent=new Date();
}

After that I have made another approach with recursive setTimeout call. Same effect.

(function loop() {
document.getElementById('date').textContent=new Date();
setTimeout(loop, 1000);
})();

Any ideas why is that happening and how to go around this?

This is almost definitely an issue with WebKit.

The Problem

When you use setTimeout , you create a 'timer' with two properties:

  • A timestamp , set to now + the specified delay
  • A callback to fire once once the system time is greater than the timestamp.

You can imagine a naïve implementation of setTimeout looking something like this:

var timers = [];
function setTimeout(callback, delay) {
    var id = timers.length;
    timers[id] = {
        callback: callback,
        timestamp: Date.now() + delay
    }
    return id;
}

This would simply create a timer and add it to a list. Then, on each tick, the JS runtime would check these timers and execute the callbacks for those that have fired:

var now = Date.now();
for(var id in timers) {
    var timer = timers[id];
    if(timer && timer.timestamp < now) {
        callback();
        delete timers[id];
    }
}

Given this implementation, imagine now changing the system time (ie Date.now() in the examples above) to a value in the past -- the timer's timestamp will still be set relative to the previous system time (ie in the future).

The same issue exists with setInterval , which (assuming sane code in WebKit) will be implemented using setTimeout .

Unfortunately, this means that any invocation of setTimeout or setInterval is going to suffer.

The Solution

As an alternative, you can use the lovely window.requestAnimationFrame method to perform a callback on each tick. I haven't tested this at at all, but it should continue to fire on each tick, regardless of the system time.

As a bonus, each time it fires the callback, you get passed the current timestamp as a parameter. By keeping track of the previous timestamp passed to your callback, you could easily detect backwards system time changes:

var lastTimestamp;
var onNextFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;

var checkForPast = function(timestamp) {
    if(lastTimestamp && timestamp < lastTimestamp) {
        console.error('System time moved into the past! I should probably handle this');
    }
    lastTimestamp = timestamp;
    onNextFrame(checkForPast);
};

onNextFrame(checkForPast);

Conclusions

This might not be great news for you, but you should probably rewrite your entire application to use requestAnimationFrame anyway - it seems much more suited to your needs:

var onNextFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
var dateElem = document.getElementById('date');

var updateDate = function(timestamp) {
    dateElem.textContent = new Date();
    onNextFrame(updateDate);
};
onNextFrame(updateDate);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM