Mysterious failure of jQuery.each() and Underscore.each() on iOS Mysterious failure of jQuery.each() and Underscore.each() on iOS ios ios

Mysterious failure of jQuery.each() and Underscore.each() on iOS


This isn't an answer but rather an analysis of what's going on under the covers after much testing. I hope that, after reading this, someone on either safari mobile side or the JavaScript VM on iOS side can take a look and correct the issue.

We can confirm that the _.each() function is treating js objects {} as arrays [] because the safari browser returns the 'length' property of an object as an integer. BUT ONLY IN CERTAIN CASES.

If we use an object map where the keys are integers:

var obj = {  23:'some value',  24:'some value',  25:'some value'}obj.hasOwnProperty('length'); //...this comes out as 'false'var length = obj.length; //...this returns 26!!!

Inspecting with the debugger on mobile Safari browser we clearly see that obj.length is "undefined". However stepping to next line:

var length = obj.length;

the length variable is clearly being assigned the value 26 which is an integer. The integer part is important because the bug in underscore.js occurs at these two lines in the underscore.js code:

var i, length = obj.length;if (length === +length) { //... it treats an object as an array because                           //... it has assigned the obj (which has no own                          //... 'length' property) an actual length (integer)

However if we were to change the object in question just slightly and add a key-value pair where the key is a string (and is the last item in object) such as:

var obj = {  23:'some value',  24:'some value',  25:'some value',  'foo':'bar'}obj.hasOwnProperty('length'); //...this comes out as 'false'var length = obj.length; //...this now returns 'undefined'

More interestingly, if we change the object again and a key-value pair such as:

var obj = {  23:'some value',  24:'some value',  25:'some value',  75:'bar'}obj.hasOwnProperty('length'); //...this comes out as 'false'var length = obj.length; //...this now returns 76!

It appears that the bug (wherever it is happening: Safari/JavaScript VM) looks at the key of last item in the object and if it is an integer adds one (+1) to it and reports that as a length of the object...even though obj.hasOwnProperty('length') comes back as false.

This occurs on:

  • some iPads (but NOT ALL that we have) with iOS version 8.1.1, 8.1.2, 8.1.3
  • the iPads that it does occur on, it happens consistently...every time
  • only Safari browser on iOS

This does not occur on:

  • any iPhones we tried with iOS 8.1.3 (using both Safari and Chrome)
  • any iPads with iOS 7.x.x (using both Safari and Chrome)
  • chrome browser on iOS
  • any js fiddles we attempted to create using the above mentioned iPads that consistently created the error

Because we can't really prove it with a jsFiddle we did the next best thing and got a screen capture of it stepping through the debugger. We posted the video on youTube and it can be seen at this location:

https://www.youtube.com/watch?v=IR3ZzSK0zKU&feature=youtu.be

As stated above this is just an analysis of the problem in more detail. We are hoping someone with more understanding under the hood can comment on the situation.

One simple solution is to NOT USE _.each function (or the jQuery equivalent). We can confirm that using angular.js forEach function remedies this issue. However, we use underscore.js pretty extensively and _.each is used nearly everywhere we iterate through arrays/collections.

Update

This was confirmed as a bug and there is now a fix for this bug on WebKit as of 2015-03-27:

fix: http://trac.webkit.org/changeset/182058

original bug report: https://bugs.webkit.org/show_bug.cgi?id=142792


For anyone looking at this and using jQuery, just a heads' up that this has now been fixed in versions 2.1.4 and 1.11.3, which specifically only contain a hot-fix to the above issue:

http://blog.jquery.com/2015/04/28/jquery-1-11-3-and-2-1-4-released-ios-fail-safe-edition/


Upgrading to the latest version of lodash (3.10.0) fixed this problem for me.

Take note that there is a breaking change in this lodash version with _.first vs _.take.

For anyone who isn't familiar with lodash - it's a fork of underscore that is nowadays (imo) a better solution.

Really a big thanks to @OzSolomon for explaning, describing and figuring out this problem.

If I would be able to give a bounty to a question I would've done it.