Create a gist now

Instantly share code, notes, and snippets.

Embed
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));
@davidbrewer

This comment has been minimized.

Show comment
Hide comment
@davidbrewer

davidbrewer Oct 19, 2012

This worked perfectly to fix a set of aggravating problems I was struggling with today. I would certainly not have figured out the problem by now if it wasn't for this gist and your comment on another web page linking here. Thank you so much for sharing this!

This worked perfectly to fix a set of aggravating problems I was struggling with today. I would certainly not have figured out the problem by now if it wasn't for this gist and your comment on another web page linking here. Thank you so much for sharing this!

@chapster11

This comment has been minimized.

Show comment
Hide comment
@chapster11

chapster11 Oct 22, 2012

Does it solve this issue in other ios systems. I tried using it in a project but I still get on Ipad the interval only updating when the scrolling on a page stops. Am I missing something here?

Does it solve this issue in other ios systems. I tried using it in a project but I still get on Ipad the interval only updating when the scrolling on a page stops. Am I missing something here?

@jordw

This comment has been minimized.

Show comment
Hide comment
@jordw

jordw Oct 30, 2012

Has anyone filed a radar against this issue?

jordw commented Oct 30, 2012

Has anyone filed a radar against this issue?

@xu33

This comment has been minimized.

Show comment
Hide comment
@xu33

xu33 Nov 27, 2012

You can use web worker to slove this problem, set a timer in your worker js, and postMessage to frontpage.

xu33 commented Nov 27, 2012

You can use web worker to slove this problem, set a timer in your worker js, and postMessage to frontpage.

@pixelscript

This comment has been minimized.

Show comment
Hide comment
@pixelscript

pixelscript Nov 27, 2012

@jordanwilliams33 I have: http://openradar.appspot.com/12756410.

@jordanwilliams33 I have: http://openradar.appspot.com/12756410.

@netpoetica

This comment has been minimized.

Show comment
Hide comment
@netpoetica

netpoetica Dec 5, 2012

Thank you for this! I am continually getting an error just wondering if you might be able to diagnose it:

TypeError: 'undefined' is not a function (evaluating 'cb.apply(window, arguments)')

Looks like it's happening in the function that gets set during cb = args[0]

Thank you for this! I am continually getting an error just wondering if you might be able to diagnose it:

TypeError: 'undefined' is not a function (evaluating 'cb.apply(window, arguments)')

Looks like it's happening in the function that gets set during cb = args[0]

@novocaine

This comment has been minimized.

Show comment
Hide comment
@novocaine

novocaine Dec 6, 2012

this causes libraries such as jquery.transit to not fire their callback.

thanks for the code.

this causes libraries such as jquery.transit to not fire their callback.

thanks for the code.

@ronkorving

This comment has been minimized.

Show comment
Hide comment
@ronkorving

ronkorving Jan 30, 2013

Can anyone confirm is this problem has been resolved in iOS6.1?

Owner

ronkorving commented Jan 30, 2013

Can anyone confirm is this problem has been resolved in iOS6.1?

@ronkorving

This comment has been minimized.

Show comment
Hide comment
@ronkorving

ronkorving Feb 2, 2013

I've tried it on iOS6.1, and it seems that the problem has gone away!

Owner

ronkorving commented Feb 2, 2013

I've tried it on iOS6.1, and it seems that the problem has gone away!

@sarathrajan

This comment has been minimized.

Show comment
Hide comment
@sarathrajan

sarathrajan Feb 4, 2013

Hi.. I have added the fix.js in my header and issue seems to be fixed. But my images are inside a link (anchor tag), while clicking the image, its not redirecting. Just showing the loading icon and nothing happens after that. Please, need help on this :(

Hi.. I have added the fix.js in my header and issue seems to be fixed. But my images are inside a link (anchor tag), while clicking the image, its not redirecting. Just showing the loading icon and nothing happens after that. Please, need help on this :(

@zewt

This comment has been minimized.

Show comment
Hide comment
@zewt

zewt Feb 10, 2013

You don't need to track timers and intervals separately. You can pass interval IDs to clearTimeout, and the numbers will never overlap, since they use the same underlying list. http://www.whatwg.org/specs/web-apps/current-work/#dom-windowtimers-settimeout

novocaine: requestAnimationFrame isn't a great workaround, since it'll force a much longer delay for small timers (up to 16ms extra).

I wonder what the timing overhead of using a worker comes out to. The onscreen keyboard opening/closing inside a UIWebView (not in Safari) can also cause this issue.

zewt commented Feb 10, 2013

You don't need to track timers and intervals separately. You can pass interval IDs to clearTimeout, and the numbers will never overlap, since they use the same underlying list. http://www.whatwg.org/specs/web-apps/current-work/#dom-windowtimers-settimeout

novocaine: requestAnimationFrame isn't a great workaround, since it'll force a much longer delay for small timers (up to 16ms extra).

I wonder what the timing overhead of using a worker comes out to. The onscreen keyboard opening/closing inside a UIWebView (not in Safari) can also cause this issue.

@ronkorving

This comment has been minimized.

Show comment
Hide comment
@ronkorving

ronkorving Feb 26, 2013

@zewt I figured it wasn't required, but I also didn't want to introduce new cross-browser issues while trying to resolve one. Played it safe there. If all known browsers play nice with this, I guess that could be unified.

Owner

ronkorving commented Feb 26, 2013

@zewt I figured it wasn't required, but I also didn't want to introduce new cross-browser issues while trying to resolve one. Played it safe there. If all known browsers play nice with this, I guess that could be unified.

@niksy

This comment has been minimized.

Show comment
Hide comment
@niksy

niksy 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?

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

This comment has been minimized.

Show comment
Hide comment
@dsproject

dsproject Jul 16, 2014

not work for me ;(

not work for me ;(

@betesh

This comment has been minimized.

Show comment
Hide comment
@betesh

betesh 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.

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

This comment has been minimized.

Show comment
Hide comment
@yelhouti

yelhouti Jun 1, 2016

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

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