miscoded

the web is a hack

Subscribe to RSS feed

Posts tagged with "ie"

Microsoft's Skydrive: gun, meet foot

, , , ...

I remember my first encounter with Microsoft's Atlas framework and the AtlasCompat.js file - being launched on start.com back in 2005 or so. Back in those early days of JavaScript frameworks - jQuery 1.0 wasn't shipped until a year later - I had still seen lots of libraries and common utility scripts. Most of them had one thing in common: they would work hard to work around Internet Explorer's DOM bugs and try to make it behave more according to the W3C specs.

AtlasCompat.js was different. With confusion and some amusement we noticed that it did exactly the opposite: attempted to implement Internet Explorer's non-standard features in all other browsers.

Actually, for Opera this approach was generally useless - we had already been forced to support document.all, event.srcElement and friends in the endless quest to get scripts out there working. On one particularly painful point we had stuck to the W3C spec though: the return values of event.button. In Opera and other W3C-loving browsers clicking the left mouse button sets event.button to 0, in Internet Explorer the value to indicate the left button was clicked is 1. With no clean way to tell whether a browser supported the W3C or the IE way, browser sniffing, confusion and broken stuff was the inevitable result.

Because Microsoft wanted to let all programmers write code the IE way, they defined a getter returning "1" for event.button as part of the Atlas framework. (Then a couple of years later, Hotmail was broken because their code expected event.button to be "1" but that piece of compat scripting didn't run in Opera for some reason..)

This week, I've been looking at the newly updated Microsoft Skydrive and their online version of Microsoft Office. The first problem was pretty noticeable: having opened a Word document in "preview only" mode, I could not open it in the Word webapp. In fact, no links or buttons would respond at all.

Wasn't there something familiar about this problem..? Much like Hotmail used to behave because of event.button problems..?

Stepping through the code a bit made me somewhat confused. Unlike all Microsoft's code from start.com up to and including the previous iteration of Hotmail, this code actually expects event.button to be 0. Even Microsoft's JavaScript coders use W3C's event.button values now?!? That's a sign of standardisation progress, I guess smile.

However, in Opera, only on Skydrive, event.button actually returned 1. Like in IE6. And I did double-check and triple-check browser.js to make sure we had no obsolete workarounds in there..

We don't. But Microsoft has:

w.Event.prototype.__defineGetter__("button", function() { 
 return this.which == 1 ? 1 : this.which == 3 ? 2 : 0 
 });


Gun, meet foot. So, first they tell us that event.button should be 1, then the rest of the code will only work as intended when it's 0?

It's sort of the opposite of what Hotmail used to do - Hotmail forgot the compat code and expected it to have some effect anyway, here they include the compat code but expect it to have no effect.

Well, why doesn't it break Skydrive in Chrome and Firefox? Presumably if it broke the site in one of the web browsers they actually test with, it would be detected and fixed?

Simply because Opera is trying to hard to be developer-friendly and easy to extend. You can define a 'button' getter on Event.prototype or on MouseEvent.prototype, either will work in Opera but only the latter works in Firefox and neither works in WebKit. Or, to quote the developer investigating this:

Interesting variation in behaviour on display here. If you use this instead:

Object.defineProperty(MouseEvent.prototype, "button", {get: function() { return 'b';}});
Object.defineProperty(Event.prototype, "type", {get: function() { return 't';}});
window.onclick = function(e) { document.write(e.type + e.button); };

- FF will pick up the getter for "button"..but not if you put the button getter on Event.prototype instead. Same for "type".
- WebKit doesn't invoke any of the getters, if provided on either Event.prototype or MouseEvent.prototype.
(In combination with FF's behaviour, I guess that explains the behaviour of the above.)
- Carakan will chase up the prototype chain looking for a getter, so it doesn't matter if the getter is defined on
MouseEvent or Event..it will be found.
- IE has another variation: it will use the getter for 'button' on MouseEvent and a getter for "type" at either MouseEvent or Event.

For some reason the web still works given that range of behaviour.



And the end result of that mess is that only Opera gives Skydrive the gun with which it shoots itself in the foot.

Don't look to IE anymore..

, ,

I've been looking at a bug reported for the Chinese site 56.com (what's up with China and all those numerical domain names - lucky numbers smile?) Here's a screenshot from a colleague, Opera on the left and Chrome on the right:


Digging a bit, it boils down to a failure in a piece of JavaScript that reads innerHTML of an element and does a regular expression match() that ends up matching nothing. They use a regular expression created from this string:

var sRepeat="<%begin_"+xLev+"[^>]*%>((.|\\n)+?)<%end_"+xLev+"%>"; 


and it's a problem with how the line endings are encoded. The site's code contains typical Windows line endings (newline + carriage returns) but this regular expression assumes that innerHTML will contain newlines and not carriage returns. (This is the \\n part of the regexp. The fix is simply replacing "\\n" with "\\n|\\r".)

Firefox and Chrome return innerHTML with normalized LF line endings. IE and Opera return CRLF in this case, and comments are broken both in IE (8) and Opera for me.

Wait..did I just say that? We break a shiny, mass-market production site because we follow IE closely on a piece of JavaScript IE invented and defined? And IE is broken too??

Wow! The times they are a-changing..

New adventures in compatibility testing

, , ,

I'm having some fun trying to figure out how sites use document.getElementsByName(), and thought some of you might be interested in the testing approach.

The bug I'm investigating is a small and ugly one hiding in the document.getElementsByName() implementation - getElementsByName('someID') will find an element with id="someID".

This is of course bad behaviour. That method has nothing to do with IDs and should find elements by name only.

The good news is that it's trivial to fix. The bad news is that it's there for a reason, and the reason is called Internet Explorer. We've been bug-compatible on purpose and while we'd like to remove the bug we have no idea how many sites will break if we do!

So, I'd like an answer to questions like these:
  • How many sites use getElementsByName() to find elements with an ID?
  • Do these sites break if we fix the bug?
  • Do they have alternate code paths for browsers doing it right? If yes, how do they figure out what code to use?

Tools at our disposal: the MAMA web code search engine (an internal Opera project), User JavaScript, and two ad-hoc Opera Unite services.

MAMA tracks sites that might be using document.getElementsByName(). It knows about roughly 45 000 sites where it has seen the string "getElementsByName" in script source code, and it generously provides 5000 random ones in a text file on my request. Naturally, MAMA does only static analysis of the scripts, it can't tell whether the method is actually called or what it was used for.

That information is a piece of cake to get with User JavaScript. A trivial custom script, trackGEBNabuse.js, overwrites the getElementsByName() method with one that will do a bit of debugging and logging on our behalf. And I'm playing with Opera Unite for the first time, with one logging service and one URL player that keeps track of which of the 5000 URLs were already visited and sends Opera to the next one.

(Opera Unite actually rocks! It's fun to write backend-type logic in JavaScript rather than PHP, and it's less hassle while developing to keep all the information, URL lists, log files and scripts locally on the hard drive. I've been undecided about Unite, not sure if it was more important than all the other things we should be spending time on - now I see it's maturing and making itself useful. Nice.)

To walk you through the main logic of things - here's the user JS that overwrites the native method to do logging - commented:

(function(gebn){/* "gebn" is a reference to the actual, native function */
	document.getElementsByName=function(name){ /* overwrite the real one */
		var elementList=gebn.apply(this, arguments); /* call the native function, record the list it returns */
		/* we want to know if anything in the elementList is there due to a matching id rather than a matching name */
		var abuse=[];
		for(var i=0,elm;elm=elementList[i];i++){ /* go through all returned elements */
			if( elm.getAttribute('name')!==name )abuse.push(elm.outerHTML); /* we found one that's probably in the list because of an ID attribute! */
		}
		if(abuse.length>0){
			/* log errors to some server... */
			(new Image()).src='http://hr-opera.hallvors.operaunite.com/logger/logGEBN/?data='+encodeURIComponent(abuse.join(', '))+'&href='+encodeURIComponent(location.href);
		}
		return elementList;/* don't forget to return the list of elements to the waiting script */
	}
})(document.getElementsByName); /* this is where we pass the real method as an argument to the function */

As you see, it uses the oldest trick in the book - new Image() - to ping the Unite service with some data. The data is then stored in the folder I told Opera Unite to use when installing the widget.

The only other interesting part is the code that requests the next URL from the URL player - as trivial as doing this from a load event listener:
	if(location.hostname!='hr-opera.hallvors.operaunite.com')
		setTimeout( function(){ location.href='http://hr-opera.hallvors.operaunite.com/urldriver/nexturl?'+Math.random(); }, 500 );

The urldriver service also accepts the "urllist=somefile.txt" query string argument, so a different user scripts could play URLs from a different file (though not at the same time since the index of what URL one has reached is not stored per-file. That's obviously a bug in my Unite service - keep in mind that these are ad-hoc throwaway services done in 30 minutes of cutting, pasting and typing last night, so don't expect QA and polish :-p).

And the results? Left an Opera 10.10 instance to surf on its own in 5 different tabs overnight, which generated this log file listing 6 unique sites and the HTML of the elements returned in response to a getElementsByName() call due to this bug. Analysing 6 out of 5000 URLs manually is certainly doable smile. I'm still worried about getElementsByName() usage that only happens during user interaction, but now at least we know that 0.12% of the sites out there might be at risk from any change and we have some real code to look at. And automated analysis of websites is a new and interesting use case for User JavaScript.

X-Talisman-Compatible: messup

, , ,

Yikes.

I'm seeing X-UA-Compatible abuse everywhere these days. It seems most of the big sites I look at have decided not to give the IE8 team half a chance to iron out their bugs, they've all simply decided to close their eyes and slap on a "behave like IE7" instruction instead. Guess if this is going to cause compat problems down the road when all non-IE browsers are forced to look at the META tags and figure out if they should be bug-compatible with IE7, 8 or 9 for any given site.. :-(

Here's a funny offender: adobe.com insists that if I'm going to show their "flashAbout_info_small.swf" animation correctly, then ..

GET /swf/software/flash/about/flashAbout_info_small.swf HTTP/1.0
Host: www.adobe.com
Referer: http://www.adobe.com/products/flash/about/

HTTP/1.1 200 OK
X-UA-Compatible: IE=7
Content-Type: application/x-shockwave-flash

Date: Sat, 21 Feb 2009 23:39:33 GMT
Content-Length: 594

... I have to render the .swf like IE7 would.

No problems, Adobe. Since I work for a browser vendor the server's wish should be my command. Here's your about Flash SWF rendered by IE in IE7 compat mode:



Beautifully compatible, no? Thanks for the X-UA-Compatible hint, I would never have figured that out on my own!

Microsoft listens

, ,

IE allows a semicolon between if block and else

,

Just for the brainparsers our there: A semicolon between a closing curly bracket of an if block and the following else keyword only works in IE:

ie-only-if-else.htm

Should this work or would you consider such JS code broken beyond repair?

quick spec for IEs document.activeElement

,

document.activeElement is an IE thing. Opera supports it partially, and this is basically what one would need to do to match IE's behaviour (after some very quick and cursory testing):

  • when the document is loaded, before any interaction activeElement is the body element (!)
  • activeElement is set after mousedown.
  • it is set to the event's target if it is "focusable" (A, INPUT, BUTTON etc.), otherwise it is set to the event's target's .offsetParent
  • it keeps pointing to the same element until another interaction with the document sets it again


It is also set when you tab through links and form fields. In Opera, spatial navigation and other keyboard navigation should of course set it too.

Haven't checked if the WHATWG or WebAPI are already embracing and/or extending it..

real *nix devs don't test in IE

, ,

Over at basket.kde.org they are understandably Windows-averse. I guess their website is not exactly targeted at IE users, but even so some cross-browser testing would uncover bugs and errors in their JS. Snippet:

    // Cross-browser implementation of element.addEventListener()
    function addEventListener(element, type, expression, bubbling) {
     bubbling = bubbling || false;
     if (window.addEventListener) { // Standard
      element.addEventListener(type, expression, bubbling);
      return true;
     } else if (window.attachEvent) { // IE
      element.attachEvent('on' + type, expression);
      return true;
     } else return false;
    }


Whoever wrote that obviously is confused about event bubbling versus event capturing (luckily it defaults to a sensible false!) but the main problem with this code is the line

if (window.addEventListener) { // Standard


Um, no, what you are seeing there is not the W3C standardised window.addEventListener1. You're actually checking for the existence of this very function - the one we're inside when we hit that statement. Naturally IE chokes on the next line and no event handlers are added. (If you ask, it should read if(element.addEventListener).)

So - a slick, good-looking production site that wasn't tested with IE - what a rarity! p

Edit - note 1: well, actually W3C didn't specify addEventListener for window in the first place, it remains a Gecko extension like I've complained about earlier so the comment "// Standard" is doubly wrong..