简体   繁体   English

WebKit setInterval和系统时间更改

[英]WebKit setInterval and system time change

I have encountered following issue when creating simple task: displaying html clock by using WebKit engine. 创建简单任务时遇到以下问题:使用WebKit引擎显示html时钟。 Additional requirement were to handle system time change and that it should work on Windows. 附加要求是处理系统时间更改,并且该更改应在Windows上运行。 I have used setInterval to achive this but it seems to freeze browser after I change system time backward. 我已经使用setInterval达到了这个目的,但是在向后更改系统时间后,它似乎冻结了浏览器。 For me it looks like WebKit issue. 对我来说,它看起来像WebKit问题。 It is easy to reproduce on safari by running this simple code: 通过运行以下简单代码,可以轻松地在safari上重现代码:

<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. 之后,我使用了递归setTimeout调用的另一种方法。 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. WebKit几乎绝对是一个问题。

The Problem 问题

When you use setTimeout , you create a 'timer' with two properties: 使用setTimeout ,将创建具有两个属性的“计时器”:

  • 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: 您可以想象一个简单的setTimeout实现看起来像这样:

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: 然后,在每个刻度上,JS运行时将检查这些计时器并执行已触发的回调:

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). 有了这种实现,想象一下现在将系统时间(即上述示例中的Date.now() )更改为过去的值-计时器的时间戳仍将相对于先前的系统时间(即将来)进行设置。

The same issue exists with setInterval , which (assuming sane code in WebKit) will be implemented using setTimeout . setInterval存在相同的问题(假设WebKit中使用合理的代码)将使用setTimeout实现。

Unfortunately, this means that any invocation of setTimeout or setInterval is going to suffer. 不幸的是,这意味着setTimeoutsetInterval 任何调用都会受到影响。

The Solution 解决方案

As an alternative, you can use the lovely window.requestAnimationFrame method to perform a callback on each tick. 或者,您可以使用可爱的window.requestAnimationFrame方法在每个刻度上执行回调。 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: 对于您来说,这可能不是个好消息,但是您可能应该重写整个应用程序以使用requestAnimationFrame似乎更适合您的需求:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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