Skip navigation.

miscoded

the web is a hack

STICKY POST

Introduction

My journal is Opera-related and technical. It will cover the main obstacles we come across when we use Opera on the Web as it is - the standard violations, the browser incompatibilities, the sniffers and faulty scripts. That is the whole mess a poor browser has to make sense of and believe me, Opera is doing a brilliant job.

'ESPN FLASH detection system' meets Flash 0

, , , ...

Even in the classy company of bad version detection scripts we've met since we started testing Opera 10, this Flash detection approach stands out. That script goes to great effort to require an update every.single.time Adobe releases another Flash version.

It starts with a bold claim to be the ESPN "Flash detection system", no less:

// Author: Danny Mavromatis
// Version: 2.07.0
// Created: 10/29/2001
// Updated: 3/6/2006
// ESPN.com FLASH detection system
var f2 = false;
var f3 = false;
var f4 = false;
var f5 = false;
var f6 = false;
var f7 = false;
var f8 = false;
var f9 = false;

Look at all those variables. What might they be used for? Read on:

var fD = navigator.plugins["Shockwave Flash" + isVersion2].description;
var fV = parseInt(fD.charAt(fD.indexOf(".") - 1));

So, first it extracts one single letter that precedes a dot in the plugin's description of itself. This is presumably the plugin's major version number, you know the 8 in "8.0", the 9 in "9.0", and, um, the 0 in "10.0". Then:
f2 = fV == 2;
f3 = fV == 3;
f4 = fV == 4;
f5 = fV == 5;
f6 = fV == 6;
f7 = fV == 7;
f8 = fV == 8;
f9 = fV == 9;


..it just sets the corresponding "f"-variable to true. If the major version was 8, f8 will be true and so on. A great way to make sure the code will require maintenance - new variables for each new version. And then comes the real gem:

for (var i = 2; i <= mV; i++) {
if (eval("f" + i) == true) aV = i;
}
// alert("version detected: " + aV);



Let's see, we just had a variable fV which contained Flash's major version number (or at least its least significant digit) - but this script has severe amnesia. What was that number again? Better use a loop and 8 eval() calls to check the value of that variable. You never know, do you?

Now, dear readers - Mr. Mavromatis clearly needs some help with this code. Obviously, complexity and maintenance requirements are among his design goals. The natural question is whether there are good, non-obvious ways this script could be improved to be more complex and require even more maintenance? Suggestions welcome in comments.

Update: Danny Mavromatis responded in comments and - though he no longer works on the ESPN site - has made sure that the page where we found this problem has been updated. Kudos to Mr. Mavromatis for his quick response and sense of responsibility. I wish more web developers would act this way!

No more personal attacks, please!

the day supporting document.onload became a bug

,

Like many things on the web, the load event seems simple. It originates from the web's stone age - supported by Netscape 2 in 1996 and fired billions and billions of times since. It's probably the most widely used event for web scripting. Can it have secrets or quirks after so many years?

You might think you could write a one-sentence spec for it, along the lines of "when the document is done loading, dispatch a load event to any listeners created from BODY onload attributes, window.onload property or addEventListener('load') calls" - and implement it interoperably in a few hours on top of an otherwise solid DOM/JS core.

Simplicity is deception. Actually, the cool thing about the load event is that it seems so simple but is so weird.

Discovering what I didn't know about the load event started at the white "please wait" box that covers the contents on grainger.com (this site is now patched in browser.js)



I noticed that they did something like
document.onload=function(){
// code that shows the "please wait" box, an obvious mistake
}
.
.
<body onload="">


Aha: if you assign a listener for the document's load event but afterwards have an empty onload attribute on body, document.onload will be removed when the browser sees onload="". Right? I wrote a test case confirming that assumption and moved on to the next bug.

However, colleague Ola was also looking at the bug and could prove that the onload="" was a red herring. Removing it made no difference. Assigning anything to document.onload was a no-op in all other browsers. What?? How come we never noticed that?

So, we got a developer to remove document.onload support. I wanted it fixed by just ignoring assignments to document.onload, but being a thorough person Daniel made sure document would no longer be an event target for the load event.

To my consolation I wasn't the only person saying that must be wrong! This fix makes document.addEventListener('load'...) a no-op too. We're absolutely sure document.addEventListener('load'...) must keep working. The DOM Events spec doesn't say much about the load event, but it sort of implies sending it to the document. Besides, not sending load events to document anymore broke opera:config and dozens of browser.js patches.

Then we wrote another test case or two.

And it's true. No browser (except Opera) sends load events to "document"! They all skip document and go straight for window. BODY onload="" creates listeners on window, not on body. Even though event.target is the document, the event itself avoids the document like it had swine flu or something.

I assume it's been like that since 1996 and we never thought of testing that in other browsers before. Wow.

So, early afternoon on May 12th 2009, our support for document.onload turned from a feature into a bug. Good bye to the possibility of capturing all load events inside a document with a capturing listener (won't work with window.addEventListener because Mozilla turned out to be a chicken and not a dinosaur). Edit: luckily I was wrong here, see comments.

Actually it feels a bit .. evil .. to suddenly turn around and kill an old feature that's been with us for years, just because it's required for web compatibility. Oh well. At least I can confirm that you learn something old every day..

Site patching article on the core concerns blog

If you follow this blog, little in my new article on the Core Concerns blog will be news to you. Nevertheless you might find it interesting. Have a look.

websites playing timing roulette

, ,

For a couple of days in November, the New York Times looked serenely white in Opera:


You might think that nothing newsworthy was happening on the planet. Unfortunately it was not the start of a new and peaceful world order, it was merely their JavaScript playing timing roulette:

if (typeof callback == 'function') { 
if (document.addEventListener) { 
  window.setTimeout(function(){ 
    document.write('<script type="text/javascript" charset="utf-8">(' + callback.toString() + ')();<\/script>'); 
  }, 0) 
}

What is that doing? This code says "have a 0 millisecond break, then add this SCRIPT tag to the document". (By "0 millisecond" it sort of means "as soon as you get around to it".) The problem with that is that if the browser happens to complete loading the document before running that document.write() statement it will replace the current document with a single, invisible SCRIPT tag.

Opera, doing its best at trying to show you the page as soon as possible did finish loading the document before getting back to the timeout - so the page was overwritten and disappeared.

Now, by the above it sounds like it is a drawback to be fast (again?). Wait until you see an extract of some code that broke image upload on Orkut:

<head>
<script>setTimeout( function(){ document.body.appendChild(el) }, 0 )</script>
<script>/*other stuff here*/</script>
</head><body>


In this case, if the timeout runs before we've seen the BODY tag - it will break, because no document.body exists yet. Wait, at New York Times parsing quickly was a mistake and here..we're punished because we didn't get to BODY yet when running the timeout?? So we're simultaneously too fast for NYTimes and too slow for Orkut..

This is JavaScript timing roulette - such things happen when websites are written according to the timing of specific browsers, or even the speed of the network connection the web site developer uses! What would happen if you're visiting Orkut on a really slow connection and the network has a small hiccup-pause between HEAD and BODY? By Orkut's code I would not be surprised if certain network delays - only a few milliseconds - would break the site entirely.

And it gets worse. AOL sites often try to launch slideshows in popup windows. In Opera that doesn't go very well:



AOL is another gambler addicted to playing the timing roulette. Bear with me, because this gets complex - but they have something like

<SCRIPT src="http://www.aolcdn.com/ke/swfobject/ke_kit_popup_includes.js" type="text/javascript" language="javascript" charset="utf-8"></SCRIPT> 
    <DIV id="gallery-holder"> 
       <DIV id="news-news_popup_foobar">


The popup_includes.js contains code that appends external scripts to HEAD. The final external scripts (ke_kit_refresh.js) contains further inline code that calls rederPopupPage() which calls a method in the opener window that eventually calls the embedswf() method which starts looking for the element with the ID "news-news_popup_foobar" (where 'foobar' is actually the short name for the article).

Since loading scripts block parsing, we have not yet reached the DIV when the slideshow is supposed to be inserted. So when the script asks "do you have the element news-news_popup_foobar?" Opera responds "no, not yet anyway" and the script just gives up.

So, apparently we're supposed to keep parsing forward while we're running scripts dynamically added earlier in the DOM?! The W3C never told us that, I think. It's the web's dark matter striking again.

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!

Dragonfly articles

,

Two tutorials on using Opera Dragonfly (and other debuggers) by yours truly are out: debugging DOM issues on thinkvitamin.com and the brand new advanced JavaScript debugging on A List Apart. Hope you'll find them worth reading.

Taleo - The Leader in On Demand Unbalanced Quotes

, , ,

Load getacooljob.com in Opera, see error page (http://aac.taleo.net/servlets/CareerSection?art_ip_action=FlowDispatcher&flowTypeNo=13&pageSeq=1&art_servlet_language=en&csNo=2'%3E%3C/head%3E%3Cbody%20bgcolor=). At first sight it might look like a standard browser sniffer saying "go away and don't dare becoming our customer until you've found a browser we like"... until you look a bit closer at that address. See the junk at the end?

&csNo=2'%3E%3C/head%3E%3Cbody%20bgcolor= decodes to
&csNo=2'></head><body bgcolor=
and it's caused by the lack of a closing quote for the META tag's content attribute on the previous page:

<META HTTP-EQUIV="refresh" content="01; url='http://aac.recruitsoft.com/servlets/CareerSection?art_ip_action=FlowDispatcher&amp;flowTypeNo=13&amp;pageSeq=1&amp;art_servlet_language=en&amp;csNo=2'>


The site apparently belongs to Taleo which is the "Leader in On Demand Talent Management". As they aren't talented enough to balance their quotes or use a validator to spot markup mistakes, the level of hype obviously exceeds their technical competence..

See also statements like "this Career Section does not allow the use of the BACK or FORWARD browser buttons during the application process" (source), the hideous sniffing here
function stopEvent() {
var userAgent = window.navigator.userAgent;
var appName = window.navigator.appName;
if ((appName.indexOf("Explorer") >= 0) && (userAgent.indexOf("Mozilla/3") >= 0) && (userAgent.indexOf("Mac") >= 0)) {
return (true);
} else {
return (false);
}
}

and their "anti-multiple-tabs" JavaScript
//---------------------------------------------------------------------
// This method increments the count of browser using the career section
// on the current session in the document's cookie.
// This code is inserted only when IE browser is used
//---------------------------------------------------------------------
function setMultiBrowserDetection()
{
if (window.navigator.cookieEnabled)
{
// The multibrowser problem doesn't occur where cookies are not available
var cs_cnt = -1;
var index = document.cookie.indexOf("cs_cnt");
var countbegin = -1;
var countend = -1;
if (index == -1)
{
cs_cnt = 1;
}
else
{
countbegin = (document.cookie.indexOf("=", index) + 1);
countend = document.cookie.indexOf(";", index);
if (countend == -1)
{
countend = document.cookie.length;
cs_cnt = eval(document.cookie.substring(countbegin, countend));
}
else
{
if (countend < countbegin)
{
cs_cnt = 0;
}
else
{
cs_cnt = eval(document.cookie.substring(countbegin, countend));
}
}
if (cs_cnt < 1)
{
cs_cnt = 1;
}
else
{
cs_cnt = cs_cnt + 1;
}
}
document.cookie = "cs_cnt="+cs_cnt;
if (cs_cnt > 1)
{
// More than one browser is using the current session
window.document.location = "http://aac.taleo.net/servlets/CareerSection?art_ip_action=MultipleBrowserConflict";
}
}
}
complete with eval() instead of parseInt() (meaning that anyone who can get a cookie set on this site can XSS them).. When you stop shaking your head, I'd like you to opine on how much less talented a web site could possibly get..