Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
iOS6 webkit timer bug workaround
(function (window) {
// This library re-implements setTimeout, setInterval, clearTimeout, clearInterval for iOS6.
// iOS6 suffers from a bug that kills timers that are created while a page is scrolling.
// This library fixes that problem by recreating timers after scrolling finishes (with interval correction).
// This code is released in the public domain. Do with it what you want, without limitations. I do not promise
// that it works, or that I will provide support (don't sue me).
// Author: rkorving@wizcorp.jp
var timeouts = {};
var intervals = {};
var orgSetTimeout = window.setTimeout;
var orgSetInterval = window.setInterval;
var orgClearTimeout = window.clearTimeout;
var orgClearInterval = window.clearInterval;
function createTimer(set, map, args) {
var id, cb = args[0], repeat = (set === orgSetInterval);
function callback() {
if (cb) {
cb.apply(window, arguments);
if (!repeat) {
delete map[id];
cb = null;
}
}
}
args[0] = callback;
id = set.apply(window, args);
map[id] = { args: args, created: Date.now(), cb: cb, id: id };
return id;
}
function resetTimer(set, clear, map, virtualId, correctInterval) {
var timer = map[virtualId];
if (!timer) {
return;
}
var repeat = (set === orgSetInterval);
// cleanup
clear(timer.id);
// reduce the interval (arg 1 in the args array)
if (!repeat) {
var interval = timer.args[1];
var reduction = Date.now() - timer.created;
if (reduction < 0) {
reduction = 0;
}
interval -= reduction;
if (interval < 0) {
interval = 0;
}
timer.args[1] = interval;
}
// recreate
function callback() {
if (timer.cb) {
timer.cb.apply(window, arguments);
if (!repeat) {
delete map[virtualId];
timer.cb = null;
}
}
}
timer.args[0] = callback;
timer.created = Date.now();
timer.id = set.apply(window, timer.args);
}
window.setTimeout = function () {
return createTimer(orgSetTimeout, timeouts, arguments);
};
window.setInterval = function () {
return createTimer(orgSetInterval, intervals, arguments);
};
window.clearTimeout = function (id) {
var timer = timeouts[id];
if (timer) {
delete timeouts[id];
orgClearTimeout(timer.id);
}
};
window.clearInterval = function (id) {
var timer = intervals[id];
if (timer) {
delete intervals[id];
orgClearInterval(timer.id);
}
};
window.addEventListener('scroll', function () {
// recreate the timers using adjusted intervals
// we cannot know how long the scroll-freeze lasted, so we cannot take that into account
var virtualId;
for (virtualId in timeouts) {
resetTimer(orgSetTimeout, orgClearTimeout, timeouts, virtualId);
}
for (virtualId in intervals) {
resetTimer(orgSetInterval, orgClearInterval, intervals, virtualId);
}
});
}(window));
@niksy
Copy link

niksy commented Jun 2, 2013

Although the bug is fixed in iOS 6.1, is anyone willing to document it on https://github.com/scottjehl/Device-Bugs for posterity? @pixelscript, can yours Radar issue description be used as Device Bugs issue description?

@dsproject
Copy link

not work for me ;(

@betesh
Copy link

betesh commented Sep 2, 2015

I was getting cb.apply is not a function error. It's because native setTimeout allows you to pass a String, which gets eval'd when the timeout expires. This code doesn't handle that case. I had to make this change to function callback():

        if (cb) {
            if ("string" == typeof cb) {
                if ("()" == cb.substring(cb.length - 2, cb.length)) {
                    cb = cb.substring(0, cb.length - 2)
                }
                cb = eval(cb);
            }
            cb.apply(window, arguments);
                           // etc.

@yelhouti
Copy link

yelhouti commented Jun 1, 2016

this cods for some reason broke Hammer.js press event, in case any one has the same problem...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment