"For in" loop doesn't iterate over screen object in IE - javascript

This works in all browsers:
for (var i in navigator) {
console.log(i, ':', navigator[i]);
}
But this doesn't work in IE 6 or 7: (loops zero times)
for (var i in screen) {
console.log(i, ':', screen[i]);
}
What's different about the screen object?
And more importantly, how would I go about looping through it in IE?
Demo: http://jsfiddle.net/Atepq/

What's different is that it's different...
MDN points out that screen is not actually part of any spec...
https://developer.mozilla.org/en/DOM/window.screen#Specification

When I run tests, IE9 lets you get all the properties off the screen object with:
for (var prop in screen) {
console.log(prop);
}
But, it won't let you access some of the properties to get their value. When you do so, it stops JS execution. It doesn't even throw an exception that you can catch.
Yes, this is brain dead, but it's not out of character with things that IE does, even in IE9.
If you really need to do this, you could one at a time figure out which properties IE doesn't work with and code to test for those and avoid them. It might have to be updated from version to version, but it could get you going for whatever you're trying to do.

Related

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

A brief summary for anyone landing here from Google: There is a bug in iOS8 (on 64-bit devices only) that intermittently causes a phantom "length" property to appear on objects that only have numeric properties. This causes functions such as $.each() and _.each() to incorrectly try to iterate your object as an array.
I have filed an issue report (really a workaround request) with jQuery (https://github.com/jquery/jquery/issues/2145), and there is a similar issue on the Underscore tracker (https://github.com/jashkenas/underscore/issues/2081).
Update: This is a confirmed webkit bug. A fix was comitted on 2015-03-27, but there is no indication as to which version of iOS will have the fix. See https://bugs.webkit.org/show_bug.cgi?id=142792. Currently iOS 8.0 - 8.3 are known to be affected.
Update 2: A workaround for the iOS bug can be found in jQuery 2.1.4+ and 1.11.3+ as well as Underscore 1.8.3+. If you're using any of these versions, then the library itself will behave properly. However, it's still up to you to ensure that your own code isn't affected.
This question can also be called: "How can an object without a length have a length?"
I'm having a twilight zone kind of issue with mobile Safari (seen on both iPhones and iPads running iOS 8). My code has a lot of intermittent failures using the "each" implementation of both jQuery ($.each()) and Underscore (_.each()).
After some investigation, I discovered that in all cases of failure, the each function was treating my object as an array. It would then try to iterate it like an array (obj[0], obj[1], etc.) and would fail.
Both jQuery and Underscore use the length property to determine if an argument is an object or an array/array-like collection. For example, Underscore uses this test:
if (length === +length) { ... this is an array
My objects had no length parameter, yet they were triggering the above if statements. I double validated that there was no length by:
Sending the value of obj.length to the server for logging prior to calling each() (confirming that length was undefined)
Calling delete obj.length prior to calling each() (this didn't change anything.)
I have finally been able to capture this behavior in the debugger with an iPhone attached to Safari on a Mac.
The following picture shows that $.isArrayLike thinks that length is 7.
However, a console trace shows that length is undefined, as expected:
At this point I believe this is a bug in iOS Safari, especially since it's intermittent. I'd love to hear from others who's seen this problem and perhaps found a way to counter it.
Update
I was asked to create a fiddle of this, but unfortunately I can't. There seems to be a timing issue (which may even differ between devices) and I can't reproduce it in a fiddle. This is the minimum set of code I was able to repro the problem with, and it requires an external .js file. With this code happens 100% of the time on my iPhone 6 running 8.1.2. If I change anything (e.g. making the JS inline, removing any of the unrelated JS code, etc), the problem goes away.
Here is the code:
index.html
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="script.js"></script>
</head>
<body>
Should say 3:
<div id="res"></div>
<script>
function trigger_failure() {
var obj = { 1: '1', 2: '2', 3: '3' };
print_last(obj);
}
$(window).load(trigger_failure);
</script>
</body>
</html>
script.js
function init_menu()
{
var elemMenu = $('#menu');
elemMenu
.on('mouseenter', function() {})
.on('mouseleave', function() {});
elemMenu.find('.menu-btn').on('touchstart', function(ev) {});
$(document).on('touchstart', function(ev) { });
return;
}
function main_init()
{
$(document).ready(function() {
init_menu();
});
}
function print_last(obj)
{
var a = $($.parseHTML('<div></div>'));
var b = $($.parseHTML('<div></div>'));
b.append($.parseHTML('foo'));
$.each(obj, function(key, btnText) {
document.getElementById('res').innerHTML = ("adding " + btnText);
});
}
main_init();
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.
I have worked a while with something that could be similar or the same problem. The company I work for have a game that uses KineticJS 4.3.2. Kinetic makes intensive use of Arrays when grouping graphic objects on a html5 canvas.
The problems that occurred was that push was missing on the array sometimes or that properties on the object that was stored in the array was missing. The problem occurred in Safari and when run from homescreen in iOS8. For some reason the problem did not occur when run in Chrome on iOS. The problem also did not occur with a debugger connected.
After a lot of testing and searching on the net we think this is a JIT optimisation bug in webkit on iOS. A colleague found the following links.
TypeError: Attempted to assign to readonly property. in Angularjs application on iOS8 Safari
https://github.com/angular/angular.js/issues/9128
http://tracker.zkoss.org/browse/ZK-2487
Currently we have made a workaround by removing dot-notation when accessing the array that did not work (.children was replaced with [ "children" ]).
I have not been able to create a jsFiddle.
The problem is the user code, not iOS8 webkit.
var obj = {
23: 'a',
24: 'b',
25: 'c'
}
The map keys above are integers, when strings should be used. It's not a valid map by the javascript standard so the behaviour is undefined, and Apple is choosing to interpret `obj' as an array of 26 elements (indexes 0 to 25 inclusive).
Use string keys and the length problem will go away:
var obj = {
'23': 'a',
'24': 'b',
'25': 'c'
}

IE8 Object doesn't support property or method 'on' - object.listenTo - (Backbone.js)

Having some issues with IE8 compatibility with my javascript code that I cannot seem to run down. The code works fine in IE9+, Chrome and FF. I have a backbone.js collection that is listening to a series of other backbone collections for changes in their models. IE 8 is giving an error when applying the event listeners. The code is;
for(var k in this.referenceTables){
this.listenTo(this.referenceTables[k], 'change', this.fetch);
}
and IE8 (note: IE10 in IE8 browser mode, document mode IE8 standards) is returning in console the error Object doesn't support property or method 'on' at line 2 of the above code.
The above code block is in the initialize function of an a backbone collection.extend.
this.referenceTables is assigned previously in the initialize function with
this.referenceTables = options.referenceTables // options.referenceTables is an array of backbone collections
I am a bit stumped so any ideas would be greatly appreciated!
for...in is for iterating over all enumerable properties of an object, if you want to iterate over the values in an array, which referenceTables is, you should use a normal for loop.
for(var k = 0; k < this.referenceTables.length; k++){
The problem is that your for...in loop is picking up other things from your array object that are not array items, and thus aren't models.
If you were to stick a console.log(k) in there, you would see that it isn't just 0...N.

Disabling JIT in Safari 6 to workaround severe Javascript JIT bugs

We found a severe problem with the interpretation of our Javascript code that only occurs on iOS 5/Safari 6 (then current iPad release) that we think is due to critical bug in the Just in Time JS compiler in Safari. (See updates below for more affected versions and versions that seem to now contain a fix).
We originally found the issue in our online demos of our library: the demos crash more or less randomly but this happens only the second time (or even later) that the same code is executed. I.e. if you run the part of the code once, everything works OK, however subsequent runs crash the application.
Interestingly executing the same code in Chrome for iOS the problem does not show, which we believe is due to the missing JIT capabilities of the Webview that is used in Chrome for iOS.
After a lot of fiddling we finally think we found at least one problematic piece of code:
var a = 0; // counter for index
for (var b = this.getStart(); b !== null; b = b.getNext()) // iterate over all cells
b.$f = a++; // assign index to cell and then increment
In essence this is a simple for loop that assigns each cell in a linked list data structure its index. The problem here is the post-increment operation in the loop body. The current count is assigned to the field and updated after the expression is evaluated, basically the same as first assigning a and then incrementing it by one.
This works OK in all browsers we tested and in Safari for the first couple of times, and then suddenly it seems as if the counter variable a is incremented first and then the result is assigned, like a pre-increment operation.
I have created a fiddle that shows the problem here: http://jsfiddle.net/yGuy/L6t5G/
Running the example on an iPad 2 with iOS 6 and all updates the result is OK for the first 2 runs in my case and in the third identic run suddenly the last element in the list has a value assigned that is off by one (the output when you click the "click me" button changes from "from 0 to 500" to "from 0 to 501")
Interestingly if you switch tabs, or wait a little it can happen that suddenly the results are correct for two or so more runs! It seems as if Safari sometimes resets is JIT caches.
So since I think it may take a very long for the Safari team to fix this bug (which I have not yet reported) and there may be other similar bugs like this lurking in the JIT that are equally hard to find, I would like to know whether there is a way to disable the JIT functionality in Safari. Of course this would slow down our code (which is very CPU intensive already), but better slow than crashing.
Update:
Unsurprisingly it's not just the post increment operator that is affected, but also the post decrement operator. Less surprisingly and more worryingly is that it makes no difference if the value is assigned, so looking for an assignment in existing code is not enough. E.g. the following the code b.$f = (a++ % 2 == 0) ? 1 : 2; where the variables value is not assigned but just used for the ternary operator condition also "fails" in the sense that sometimes the wrong branch is chosen. Currently it looks as if the problem can only be avoided if the post operators are not used at all.
Update:
The same issue does not only exist in iOS devices, but also on Mac OSX in Safari 6 and the latest Safari 5:
These have been tested and found to be affected by the bug:
Mac OS 10.7.4, Safari 5.1.7
Mac OS X 10.8.2, WebKit Nightly r132968: Safari 6.0.1 (8536.26.14, 537+). Interestingly these do not seem to be affected: iPad 2 (Mobile) Safari 5.1.7, and iPad 1 Mobile Safari 5.1. I have reported these problems to Apple but have not received any response, yet.
Update:
The bug has been reported as Webkit bug 109036. Apple still has not responded to my bug report, all current (February 2013) Safari versions on iOS and MacOS are still affected by the problem.
Update 27th of February 2013:
It seems the bug has been fixed by the Webkit team here! It was indeed a problem with the JIT and the post-operators! The comments indicate that more code might have been affected by the bug, so it could be that more mysterious Heisenbugs have been fixed, now!
Update October 2013:
The fix finally made it into production code: iOS 7.0.2 at least on iPad2 does not seem to suffer from this bug anymore. I did not check all of the intermediate versions, though, since we worked around the problem a long time ago.
Try-catch blocks seem to disable the JIT compiler on Safari 6 on Lion for the part directly inside the try block (this code worked for me on Safari 6.0.1 7536.26.14 and OS X Lion).
// test function
utility.test = function(){
try {
var a = 0; // counter for index
for (var b = this.getStart(); b !== null; b = b.getNext()) // iterate over all cells
b.$f = a++; // assign index to cell and then increment
}
catch (e) { throw e }
this.$f5 = !1; // random code
};
This is at least a documented behavior of the current version of Google's V8 (see the Google I/O presentation on V8), but I don't know for Safari.
If you want to disable it for the whole script, one solution would be to compile your JS to wrap every function's content inside a try-catch with a tool such as burrito.
Good job on making this reproducible!
IMO, the correct solution is to report the bug to Apple, then workaround it in your code (surely using a separate a = a + 1; statement will work, unless the JIT is even worse than you thought!). It does indeed suck, though. Here's a list of common things you can also try throwing in to the function to make it de-optimise and not use JIT:
Exceptions
'with' statement
using arguments object, e.g. arguments.callee
eval()
The problem with those is if the Javascript engine is optimised to JIT them before they fix that bug, in which case you're back to crashing. So, report and workaround!
Actually, the FOR loop bug is still present in Safari on iOS 7.0.4 in iPhone 4 and iPad 2. The loop failing can be significantly simpler than the illustration above, and it rakes several passes through the code to hit. Changing to a WHILE loop allows proper execution.
Failing code:
function zf(num,digs)
{
var out = "";
var n = Math.abs(num);
for (digs; digs>0||n>0; digs--)
{
out = n%10 + out;
n = Math.floor(n/10);
}
return num<0?"-"+out:out;
}
Successful code:
function zf(num,digs)
{
var out = "";
var n = Math.abs(num);
do
{
out = n%10 + out;
n = Math.floor(n/10);
}
while (--digs>0||n>0)
return num<0?"-"+out:out;
}

Is there any way to force the Javascript Garbage Collector in webkit based browsers?

In internet explorer we can force the Javascript garbage collection to execute with this method: CollectGarbage();
That method is undefined on Firefox. Do you know if there is some kind of equivalent?
Thanks.
(Not just limiting this answer to WebKit-based browsers...)
Chrome: if you launch it from a command line/terminal with --js-flags="--expose-gc", then it provides window.gc().
Firefox I think requires clicking the "Free memory" buttons in about:memory.
Opera has window.opera.collect().
Edge has window.CollectGarbage().
Safari, unknown.
Note that you shouldn't be manually running the GC. I've only posted this because it's useful for development testing.
Visit about:memory.
From page's documentation on MDN:
about:memory is a special page within Firefox that lets you view, save,
load, and diff detailed measurements of Firefox's memory usage. It also
lets you do other memory-related operations like trigger GC and CC
I've been just trying to force GC and it seems that regardless of the actual browser relatively good way of doing so is to run following piece of code:
function gc(max) {
var arr = [];
for (var i = 0; i < max; i++) {
arr.push(i);
}
return arr.length;
}
for (var i = 0; ; i++) {
// repeat until you have enough:
gc(Math.pow(2, i));
}
Of course, one issue is to know when to stop calling gc(...): for that one needs some external way to detect the GC is successfully over. In my case I was using embedded WebView and I could really check before forcing next round with bigger array.
I'm not sure if this is not off topic but there is add-on for firefox called FreeMemory (https://addons.mozilla.org/en-US/firefox/addon/freememory/) to run garbage or cycle collection without visiting about:memory pane, with configurable timer. I believe there are alternatives for other browsers out there.

Using JavaScript with Internet Explorer, how do I clear memory without refreshing the page?

I have an AJAX-based website using JavaScript on the client. Certain operations on the site cache large result sets from service calls in the browser (i.e. hundreds of megabytes). These are throw-away results. They will be viewed for a short time and then need to be cleared from memory.
I've written a small test site that loads a bunch of junk in memory and then calls JavaScript's delete method. This works great in Firefox (memory almost instantly gets returned). Internet Explorer 8 (haven't tried 7) doesn't free the memory until the page is refreshed or closed.
Does anyone know how to drop IE's memory usage using JavaScript/Ajax (no page refreshes)?
Below is my sample client code:
function load() {
var x = ['dfjasdlfkjsa;dflkjsad;flkjsadf;lj'];
for( var i = 0; i < 10000000; ++i ) {
x.push('asdfasfasfsfasdfkasjfslafkjslfjsalfjsaldfkjasl;dfkjsadfl;kjsdflskajflskfjslakfjaslfkjsaldfkjsaldfksdfjk');
}
alert('deleting'); // <--- memory usage around 500mb
delete x; // <--- immediate results in Firefox 3.5 (not IE8)
alert('done');
}
UPDATE: Setting the variable to 'null' does not immediately clear the memory (as that is left up to the garbage collector). Also, setting a variable null only gets a single reference where there might be multiple references.
IE (well, technically, JScript) has an undocumented CollectGarbage method, which supposedly forces garbage collector to run immediately. You might want to play with that, but from my experience, nulling references is enough most of the time.
Instead of using the delete method, set the variable to null. This seems to be the best way to clear up memory cross-browser (delete is notoriously flaky). It works the same as my other answer here.
alert('deleting'); // <--- memory usage around 500mb
x = null;
alert('done');
And if you try "x= null;" instead of delete ?
Just assign null to the variable:
x = null;
I realize you mentioned the data is throw away, but, when you delete the nodes, even if just setting x to null, you need to make certain that no event handlers are attached to any of the nodes, otherwise that node cannot be garbage collected.
Here is a function I use in my own code:
http://javascript.crockford.com/memory/leak.html

Categories

Resources