Skip navigation.

miscoded

the web is a hack

Posts tagged with "compatibility"

Onestat.com's browser sniffer older than my son

, , ,

Seems the boss has been browsing web stats again - I came across a bug report from him saying a DHTML menu on onestat.com only works once. (Load page, click any "demo" link at the bottom and try the menu on the left.)

I'm not paying that much attention to stats myself, being too busy doing the work that will hopefully smoothen out compatibility problems and make it possible to grow our usage share. It's exactly the type of brokenness Jon found on onestat.com I fear the most - a bit hidden away, subtle, annoying when the user needs that page to work but perhaps not disastrous enough to notify us of. Too many of these issues, and we've lost a user! (Unless, of course, the user happens to be our CEO :smile: - luckily he's an active surfer who comes across more compat problems than any average user and eagerly reports them :wink:).

Now, deep inside the Onestat scripts is some code to handle browser differences. The script assumes that a browser below a certain level of DOM support can't be expected to create menus after the document loaded. That's of course not a bad assumption to make - until you see the sniffing they use to determine whether you have sufficient DOM support. Here is the relevant part:
if(kh.indexOf("Opera/7")>-1||kh.indexOf("Opera 7")>-1)return "Op7"; 
. 
. 
else if(kh.indexOf("Opera")>-1)return "Default";


Check the calendar, sir: we're in late 2009. Opera 7 - the only Opera version this script thinks is capable of opening the menu a second time - was released in January 2003. That's a 6 year old browser sniffer! I hope the script that generates their statistics is a bit more up to date!

And it's more than a little ironic that a web statistics company contributes to Opera's low usage share by running such old and broken code on their website. :frown:

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..

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.

Google Maps vs. DOM2 specification 1-0

, , ,

Since users reported that seeing saved addresses and getting directions on Google Maps was broken in Opera I had to do some digging. Actually a lot of digging. Eventually, I ended up here:

xO.Tia=function(a){if(!a||a==window||!a.hasOwnProperty||xO.uSa(a))return xO.Type.NATIVE;if(xO.jTa(a))return xO.Type.PROTO;if(a.constructor===Function)return xO.Type.FUNCTION;if(a.constructor===Array)return xO.Type.ARRAY;if(a.constructor===Object)return xO.Type.OBJECT;return xO.Type.NATIVE};

..which in its unwrapped and scrambled glory is a method to figure out what sort of object "a" is. The bit we didn't handle like Maps expected was:
if(a.constructor===Object)return xO.Type.OBJECT;

If you pass a DOM node to this function that if clause is true and it will conclude that it is of type "Object". Further along there was some logic detecting that this node was an Object and thus not able to have event listeners.

So, no event listener is added and nothing at all happens when clicking the triangle to view your search history:


In Firefox, a node's constructor property points to the corresponding DOM2 HTML interface. To explain that in real English, the DOM 2 HTML specification says that for each element in HTML (BODY, A, DIV, P and so on) there should be a JavaScript object named window.HTML+name of element+Element (HTMLBodyElement, HTMLAnchorElement, HTMLDivElement, HTMLParagraphElement and so on).

What's the point of that? These are "prototypes" of the DOM. Through JavaScript's prototype inheritance feature you can add properties and methods to for example HTMLDivElement.prototype and all DIVs in your document will appear to have those properties and methods. For example
HTMLDivElement.prototype.getExternalLinks = function(){
    for(var list=[],links=this.getElementsByTagName('a'),l,i=0;l=links[i];i++)
        if((new RegExp( "https?:\/\/(?!"+location.hostname+")" )).test(l.getAttribute('href')))list.push(l);
    return list;
}

lets you call div.getExternalLinks() for any DIV in your DOM to list the anchors inside that DIV that point to a an external URL. Or maybe you want an isExternalLink() method on A nodes instead? No problem:
HTMLAnchorElement.prototype.isExternalLink = function(){
    return (new RegExp( "https?:\/\/(?!"+location.hostname+")" )).test(this.getAttribute('href'));
}

As you see, the DOM2 HTML standard gives you quite a lot of power to extend the DOM.

Back to our problem. When Google Maps in Firefox looks at div.constructor it points to the HTMLDivElement object.

In Opera, it points to window.Object.

The obvious question is: is Opera wrong?

The answer: No. We are perfectly standards-compliant. We follow ECMAScript 2.62 4.3.4 Constructor property spec, DOM2 Core, and DOM 2 HTML.

The problems is: none of these specifications really cover what the constructor property of a DOM node should return.

Opera and Google Maps compatibility is a victim of spec fragmentation. The DOM specifications over there are not sufficiently aware of the details in that other spec over there to cover the issue.

The funny thing is that the public-html mailing list has had loooong-winded discussions where a vocal group argued that the HTML5 spec was too comprehensive and should be split into multiple stand-alone specifications.

Looking beyond the "what I'm not interested in should not be in the spec" shoot-out, they have a point: the spec as-is might not be "reviewer-friendly" to someone who, for example, is only interested in discussing the semantics of the HTML language. ("Spec reviewers" are completely ignored in the HTML5 "priority of constituencies" design principle).

However, fragmenting the spec increases the risk of dropping another "constructor" underspecified on the floor. Google Maps basically found an omission in DOM2's ECMAScript bindings. Opera simply followed the spec and implemented the omission..

Let's do whatever it takes to make the next HTML spec comprehensive enough. To quote Jamie Zawinski: "your needs are big because the Internet is big". So fasten your seat belts, HTML5 will be huge, it'll be complex, it'll have the weird DOM bits and parser bits - and those bits are necessary to make it a really solid specification.

10 is the one

,

So we're busy preparing the major upgrade from 9.5x and 9.6x - and what's more obvious than calling it Opera 10? What's in a name, or a version number?

Apparently a lot of trouble.

As Andrew Gregory already noticed, we're the first browser ever to release with a two-digit version number. If websites assume that version numbers always have a one-letter "major" part and look for a single digit, they are going to "detect" Opera 1!

Since we released the first preview of Opera 10, we're seeing the bug reports come in. Web sites go belly up because of their bad sniffing. Some of them aren't even ashamed of it..



Thanks, Bank of America. Do you feel like it's 1995 again? Yeah, me too.

You'd think that with the intense development Microsoft has been lavishing on live.com they would have found somebody capable of writing a usable browser sniffer (or ideally a person clever enough to say "wait, we don't really need one - what if we just use feature detection instead?"). Think again..


..and for further evidence that their backend version detection is an odd piece of software engineering, read their cookies closely - Opera 9.62's request first, then Opera 10:
GET /mail/browsersupport.aspx 
Host: co109w.col109.mail.live.com 
User-Agent: Opera/9.62 (Windows NT 5.1; U; en) Presto/2.2.0 

Set-Cookie: BrowserSense=Win=1&Downlevel=0&WinIEOnly=0&Firefox=0&Opera=1&OperaVersion=9.2&Safari=0; domain=.live.com; path=/


GET /mail/browsersupport.aspx 
Host: co109w.col109.mail.live.com 
User-Agent: Opera/10.00 (Windows NT 5.1; U; en) Presto/2.2.0 

Set-Cookie: BrowserSense=Win=1&Downlevel=1&WinIEOnly=0&Firefox=0&Opera=1&OperaVersion=&Safari=0; domain=.live.com; path=/


Did you spot the missing version value? We really confused them by adding 0.37 to our previous value, didn't we?

Speaking of cookies, they are the main reason we added the feature that lets you hide Opera's identity. Back then in 1996 or so, some sites would do browser sniffing and send cookies only to known browsers. On the next page, if you didn't serve it cookies the site would say "hey, you don't support cookies so go away". What would Joseph Heller make of that, I wonder?

So, rewind to meet the catch-22 server, re-born at Bank South Australia:
GET /InternetBanking/ HTTP/1.1
User-Agent: Opera/9.62 (Windows NT 5.1; U; en) Presto/2.1.1
Host: ibanking.banksa.com.au

HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=000019WTazsWAk-lB38OrmKD3kR:13l3ifhnq;Path=/
Set-Cookie: bhCookieSess=1;Path=/
Set-Cookie: bhCookiePerm=1;Expires=Sat, 20-Dec-2008 23:35:40 GMT;Path=/

..and Opera 10..:
GET /InternetBanking/ HTTP/1.1
User-Agent: Opera/10.00 (Windows NT 5.1; U; en) Presto/2.2.0
Host: ibanking.banksa.com.au

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Content-Language: en-AU
Date: Thu, 18 Dec 2008 23:34:37 GMT
Connection: close


Um, I'm hungry. Where are your cookies? Predictably the next page looks like this:



This is probably just the beginning. :frown:

Will the web ever learn?

shortest incompatible script challenge

, ,

We all know that the interoperability situation on the Web has been abysmal. Web developers everywhere are vocally voicing their complaints of wasted time and money due to browser differences. Standards bodies that are meant to solve the interoperability problem become battle grounds of special interests, or create ivoryish spec-monsters that end up fragmenting the web even more. The dazzling bells and whistles of plugins threaten open standards..

So, let's have some incompatibility fun.

Here's a challenge for JavaScript-skilled readers: Who can come up with the shortest possible JavaScript that produces 4 different results in the top 4 engines?

(For the purpose of this exercise let's define the top 4 browsers as IE, Firefox, Safari/WebKit and Opera - latest available final versions).

The winner gets fame and fortune. (Well, I think I can afford a reward of ISK1000 these days if anyone is interested - certainly our friends in Iceland really need someone wanting to buy their money.. p: So the grand price is a souvenir 1000 krónar bill from Jon's country of origin.)

Ladies and gentlemen, post your answers in the comments.

MS' Virtualearth claims Opera has no SVG support

, , , ...

As seen in this discussion, the BBC site uses a map from maps.live.com - and all we Opera users get is an error message:

We're sorry but your browser does not support BBC Sport's Olympic map.



Looking into it, first there is some Opera-sniffing from the BBC itself that detects Opera and displays the warning instead of the map.

With that sniffing neutralised, the map loads - but shows the United States, not Beijing. Come one Live Maps, admit you're wrong by several thousand kilometers.. Or?

Not in the mood to admit any fault, the script instead spits out an error laying the blame on us:

JavaScript - http://news.bbc.co.uk/sport2/hi/olympics/7493757.stm
Timeout thread: delay 10 ms
Error:
name: Msn.Drawing.Exception
message: Your Web browser does not support SVG or VML. Some graphics features may not function properly.
stacktrace:   Line 1 of linked script http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.1


So, my web browser does not support SVG, eh? Let's have a closer look at how you figured that out..

if(document.all)
         return new Msn.Drawing.VMLGraphic(e,b);
else{if(navigator.userAgent.indexOf("KHTML")!==-1)
         return new Msn.Drawing.SVGGraphic(e,b);
var c=0,f=0,g=new RegExp("Firefox/(.*)"),d=g.exec(navigator.userAgent);
if(d&&d.length>=2){
         var a=d[1].split(".");
         if(a){c=a[0];
         f=a[1];
         if(parseInt(c)>0&&parseInt(f)>=5||parseInt(c)>=2)
                  return new Msn.Drawing.SVGGraphic(e,b)

         }
}
throw new Msn.Drawing.Exception(L_GraphicsInitError_Text)


Yes, nothing but browser sniffing.. Not a single attempt at intelligent feature detection. Basically, by claiming Opera doesn't support SVG and can't load the map Microsoft is lying to the BBC and to our users.

Release blues

,

I'm getting used to this. I've even learnt to expect it and prepare myself mentally. It's still true: releasing something is the most depressing job for somebody doing quality assurance.

So we finally have a new flagship desktop version. The long-awaited Opera 9.5 launched with - I hope - all the hype and thunder we could drum up. Literally thousands of bug fixes since the 9.2x versions, a fancy new skin, new features like getters and setters and Dragonfly that will make much of my work much easier - and yet I can think of few other things than the REMAINING bugs that we should have fixed. It's QA release blues time. Happened every time since Opera 6 final or so.

And with a new release comes new problems. The worst current compatibility issue is a problem with the TinyMCE editor, where legacy versions of TinyMCE will re-arrange your sentences in somewhat unpredictable ways when you press enter. (This is caused by TinyMCE detecting Opera to work around a bug in earlier Opera versions - we've now fixed the bug and unfortunately their workaround still runs and messes things up). To put this problem into the right perspective, TinyMCE is the default editor for Wordpress, and all admin screens in the millions of Wordpress installations out there are now suddenly broken in Opera. Ouch, Sir. What a gotcha!

This is fortunately precisely the sort of things we have browser.js for. There is a fix in already, needs some more tweaking to run in Wordpress' admin screen but I'll get that done. So thank God and Lars Erik for browser.js - when I can throw some fixes in after the final launch, release blues isn't quite as bad as it used to be :smile:.

I'm crossing my fingers that the patch will eventually be robust enough to work with most legacy TinyMCE installations out there. Wish me luck - or even better, tell me where it fails ;-o