Javascript - if(...) evaluating differently in Firefox and Safari - javascript

So I have come across a very strange issue in my code whereby equality is being deemed false in safari (despite the variables genuinely being equal) whilst in Firefox the same equality evaluation is being assessed as true. After a few hours using debugging tools, alas I arrive here...
I have this simple function:
function determineChosenLevel(){
var x = document.getElementById("level").children[1].innerText;
return x;
}
Then I use it in this place:
var level = determineChosenLevel();
console.log(level);
if(level === "GCSE"){
// do some things
}else if(level === "A Level"){
// do some things
}
Now I run this code on Firefox with determineChosenLevel returning GCSE on Firefox and the if statement if(level === "GCSE") evaluates to true and corresponding code evaluates successfully.
However.... on Safari, this is not the case. I add a breakpoint on Safari's tools on the first line of code in the second snippet and the console.log(level) indeed logs GCSE, but the computer decides to evaluate the if statement to false and skip over the //do some things.
The image attached shows the level in the browser debugging tools of Safari. The only glimmer of hope is that GCSE appears to have some strange arrow next to it which may be why equality is not holding, but I am not really sure.
Any help would be really appreciated.

The element have a line break, like this:
<div id="el">text
</div>
Which is always invisible in the output, but in Safari the innerText property have the line break on it, and on chrome haven't. To get rid of it, just use trim() or replace all instances with .replace(/[\r\n]/g, "")
var text = document.getElementById("el").innerText;
console.log(text);
console.log(text.trim());
<div id="el">text
</div>

Related

IE does not parse any JS after unsupported code?

I want to use certain JS code, but only according to whether the browser supports the #supports (CSS) and CSS.supports() (JS) properties/functions.
Now, Internet Explorer does not support CSS.supports() or #supports. This is fine - the idea is to build the JS code so that, if the browser doesn't support either of them, then it falls back to a working version.
This works flawlessly on all other browsers, even those which do not support #supports / CSS.supports().
But in IE, it parses the JS code all the way up to the first mention of CSS.supports(), and then it breaks entirely and does not parse a single character after that.
I'm working with IE 11, and here's the JS code I'm using (I'm also using jQuery):
$(function() {
var port = $(window);
var container = $('html, body');
if( !CSS.supports("-webkit-overflow-scrolling", "touch") & CSS.supports("perspective", "1px")) {
var port = $('.prllx');
var container = $('.prllx');
};
// OTHER CODE GOES HERE
});
However, as I said, IE does not read ANY code after the IF check.
I then tried to make a test code, to see what happens with a more simple example:
var helloworld = !CSS.supports("-webkit-overflow-scrolling", "touch");
$("#test").html(helloworld);
if (helloworld == true) {
$("#test2").html("HELLO WORLD FROM WITHIN IF");
};
But IE doesn't parse ANYTHING after the first line (I've been inserting $("#div").html("HELLO WORLD"); lines every other line to see at which point IE stops parsing JS, and it literally happens right after the first mention of CSS.supports.
I can't seem to get my head around this: In all other browsers which do not support CSS.supports(), the IF (in my first example) obviously fires false, and the variables remain unchanged. But they continue to parse the remaining code after that instance, even if they don't/can't parse CSS.supports() because they don't support the function. But IE stops parsing entirely.
I have to say that I am not particularly fluent with javascript, so I might be missing something really simple here - or it might actually be a bug, in which case, how do I go around it? How can I check whether the browser supports CSS.supports() without breaking the entire code in case it is IE?
Thanks a lot in advance!
Any browser that doesn't have a CSS object with a supports property that refers to a function will throw an exception where indicated below:
$(function() {
var port = $(window);
var container = $('html, body');
// vv Exception on this line
if( !CSS.supports("-webkit-overflow-scrolling", "touch") & CSS.supports("perspective", "1px")) {
var port = $('.prllx');
var container = $('.prllx');
};
// OTHER CODE GOES HERE
});
When an unhandled exception is thrown in a function, control is tranferred out of the function, and no code following the place where the exception occurred will run. This is not an IE thing, it's how JavaScript and most other languages with exceptions work.
Instead, put in a guard checking whether CSS exists and if so whether it has a supports that's a function:
$(function() {
var port = $(window);
var container = $('html, body');
if( typeof CSS === "object" &&
typeof CSS.supports === "function" &&
!CSS.supports("-webkit-overflow-scrolling", "touch") &&
CSS.supports("perspective", "1px")) {
var port = $('.prllx');
var container = $('.prllx');
}
// OTHER CODE GOES HERE
});
Some host-provided functions report typeof incorrectly. In that case, you may need to change the === "function" above to !== "undefined". That's a weaker guard, because not everything that's not undefined is callable, but sometimes host-provided functions don't report "function" as they should.
Side note: The logical "and" operator in JavaScript is && (as above), not &. The & is the bitwise "and" operator (coerces its operands to 32-bit integer numbers and combines them at the bit level). It matters primarily because && short-circuits, which means that if its left-hand operand is falsy (for instance, typeof CSS === "object" is not true), it doesn't evaluate its right-hand operand at all, so we can safely do something assuming the left-hand operand is truthy (such as looking at CSS's support property).
Side note 2: You don't put a ; after the end of the block attached to a control-flow statement. It's harmless, but pointless.

JavaScript execution order: why does this conditional execute after the code that follows it?

if(true) {
let m = "yo";
console.log(m);
}
console.log(m)
Output:
ReferenceError: m is not defined
yo
So the code on line 4 is being executed after the code on line 8.
Does my usage of let have anything to do with this?
EDIT: After reading comments I realised that this might be because of my runtime. Here's how I see it in Firefox nightly:
EDIT 2: If this is indeed just my runtime, then are there implications for production code because of something like this? Inconsistent behaviour across browsers? How do I guard against that?
So I think the behaviour of the FF runtime is OK. A cursory glance the spec (6.2.3.1 etc) indicates that the code should run line by line, until the second console.log(m) at which point a ReferenceError is thrown.
I suspect it only "looks funny" because of the order in which the console is choosing to render the first console.log and the exception message (it is the inverse of Chrome for instance).
Whether the rendering order to the console is considered a bug or not, I leave to others.
The following appears to confirm my analysis with the alert showing before the exception is logged.
if(true) {
let m = "yo";
alert(m);
}
console.log(m)

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;
}

Global variable not passed down in stack - IE8

I am working on a legacy application that is breaking in Internet Explorer. It has the following code:
Script 1: /* Call script 2 */; x.a(b)
Script 2: eval(script3)
Script 3: var x = x || {}; x.a = function(){ ... }
In Firefox, this works as expected.
In internet explorer, however, it fails by saying "x is undefined" (this is massively simplified). I traced it back to this point in the code and noted that x is defined in script 3 but then going back down in the stack to script 2 it loses its availability/scope (I tested this by putting console.log statements in both - IE was ok with the one in script 3 but crashed for the one in script 2).
What about internet explorer and/or the eval() function causes this to happen and what are my options for fixing it?
Why not set up a test and see for yourself?
Here is one: http://jsfiddle.net/Y3bf5/
It seems to work fine in IE7+.
You might want to change Script 3 to var x; x = x || {}; x.a = function(){ ... }
This might've caused due to preemptive usage of the variable before it's been declared in this case variable x.
The fact that variable x is unavailable in Script 2 is because the eval() method failed abruptly with "x is undefined"
Actually I understood only a lil' bit from the piece of code that refers Script 3
If I could get a glimpse of it, then I might be of better help to you than now.
Hope this helps

Javascript error in IE8: Not implemented

This one really puzzles me, as the code looks completely harmless.
IE8 halts script execution with a message:
Not implemented. map.js line:66 char:5
Here is a snip from the code:
63 if(data.map[x] !== undefined && data.map[x][y] !== undefined) {
64
65 left = (x - data.dim.x_min)*32 + 30;
66 top = (data.dim.y_max - y)*32 + 30;
67
68 /* do stuff */
XX }
debug info: x:263 data.dim.x_min:263 y:172 data.dim.y_max:174
Data is object returned from JQuery Ajax call. This works in Firefox 3.0 and 3.5, safari 4.0.2 and I've only found this error when viewing the page in IE8. Forcing IE8 into IE7 mode does not make the error go away.
I don't have IE7 to debug with, but I got a tester saying that it doesn't work in IE7 either.
The variable 'top' used in the code is an object of type DispHTMLWindow2 (outermost window object) and already in use by the browsers and that is causing the conflict, as that object cant be the target of the assignment operation. It seems that Firefox and Safari ignore this, while IE does not allow scripts to overwrite this.
Solutions for this:
1) Declare top you are using as local variable to define it's scope where it is used.
2) Rename the variable to something that doesn't conflict with this predefined global.
Description of other variable names you shouldn't use
IE 8 has a great javascript debugger. You might want to add a breakpoint somewhere before the error and step through the code to see if something is strange with the data. IE8 is picky with trailing commas in lists which might be why you only get the error in it. You can pull the debugger up with F12, click Script and choose start debugging. You can add a break point by clicking on the margin where the line numbers are.

Categories

Resources