If you can't trust the Browser...

by Kenneth Tibbetts

Content and Control I like Opera- The browser, not the musical spectacle. I like that it puts small demands on your hardware. I like that it displays web pages quickly and accurately. I like that it has always been close to the standards suggested by the W3, especially for CSS styles.

Believe it or not...

It is always worthwhile to preview pages in Opera. Opera is a little weak in event handling, and it has been slow to adopt the document object model. But in terms of page presentation, Opera shows a web page the way it oughta look. It displays a great looking page, and it loads fast. And everybody likes the little guy...

The troubles I have with Opera, as a code writer, are all derived from a single bad habit of the browser.Opera is a little loose with its idea of who it is and what it can do.

In fact, Opera tells lies.

When I write code for webpages, I write the page with the dumbest browser in mind, and then add scripts for the clients that can use them. I use a kind of a sniffer tool called Yankee.iz to get the information from the browser.

Without using a tool like Yankee.iz, you can pre-condition every script to ask if the client supports some particular property or method: image objects, or layers, or whatever.

if(document.layers) or if(document.images) return true, the condition is supported, and you can pass on to the next step in your script.

Or you can directly query the navigator object for its appName or userAgent string, or test it against a string you know:

if(navigator.appName.indexOf('Microsoft') !=-1);

or

if(navigator.appName== 'Netscape');

or

if(navigator.userAgent.indexOf('Gecko')!=-1);

In the olden days I would see if the app's name was Netscape or Microsoft, and then check the version number, and that was it. Now I use Yankee.iz.


Yankee.iz source

Yankee.iz deals with the Other guy in the web page, the user; the client, the browser- the software that is translating and displaying your code. It's best to cultivate an amiable relationship with this character.

As the page writer, you have no idea who or what this guy is and what he is capable of, but you can ask a few questions.

function Yankee.iz(what){ // comments follow

if(!what) return navigator.appName+'; '+ navigator.userAgent;


        var agnt= navigator.userAgent.toLowerCase();


        var wot=what.toLowerCase();

switch (wot){

case 'df':return !!(document.createDocumentFragment);

case 'dom':return !!(document.createDocumentElement);

case 'id':return !!(document.getElementById);

case 'ie':return !!(agnt.indexOf('msie')!=-1);

case 'moz':return !!(agnt.indexOf('gecko')!=-1);

case 'agt':return agnt.toLowerCase()

default:return !!(agnt.indexOf(wot) !=-1);

}

}


Yankee.iz comment

If you use Yankee.iz with no argument, x=Yankee.iz();

x gets the value of the browser's name and userAgent string, separated by a semicolon.

IE6 returns:

Microsoft Internet Explorer; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)

Netscape 6.2:

Netscape; Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:0.9.4) Gecko/20011128 Netscape6/6.2.1

Netscape Navigator:

Netscape; Mozilla/4.08 [en] (WinNT; U ;Nav)

Send it one of the constants ('ID', 'DF', 'IE', 'MOZ' and my favorite, 'DOM'), and it returns true or false, depending on the results of the case statements that apply to the input string.

I take extra care with what gets returned from Yankee.iz. I want to get a 'real' true or false, and not a null or an empty string. Prefixing "!!" to a condition test forces the true/ false syntax to be returned.

Yankee.iz('ID') tests for document.getElementById, and Yankee.iz('DOM') returns true for document.createElement. 'IE' and 'MOZ' are shorthand for IE or a Mozilla browser.

Sending any other string, like Yankee.iz('Mac') or Yankee.iz('Opera') checks for the specified string in the userAgent string.

I ran up against Opera's little lie here- the user can present a fake id, claiming to be IE or Navigator. Your carefully crafted browser sniffer might as well be on break.


Opera's Little Lie

The user can change Opera's identifying user agent string in the preferences dialog. And starting with version 6 there is a 'Quick Preferences' dialog that makes it even easier.

Opera screen shots

Click the small image to view the screenshot.

Opera 6 File Menu Quick Preferences


Opera Preferences


These are the userAgent strings returned from Opera 6 in each disguise:
(the Windows XP bit would be replaced by whatever platform is used)


Identify as MSIE 5.0:

Mozilla/4.0 (compatible; MSIE 5.0; Windows XP) Opera 6.01 [en]


Identify as Mozilla 3.0:

Mozilla/3.0 (Windows XP; U) Opera 6.01 [en]


Identify as Mozilla 4.0:

Mozilla/4.78 (Windows XP; U) Opera 6.01 [en]


Identify as Mozilla 5.0:

Mozilla/5.0 (Windows XP; U) Opera 6.01 [en]


Identify as Opera:

Opera/6.01 (Windows XP; U) [en]

Another interesting thing about all this is when you install Opera for Windows on a Windows machine, it starts out life with the IE identification.

The point is, if you have some code that is just for IE, you can't rely on testing for the 'MSIE' in the uA string. The other ids just twiddle the version number, and don't cause the same potential for sleepless nights and unpleasant email.

You can definitely spot Opera by explicitly testing the string for the 'Opera', which is always tacked on the uA string, in a kind of "I was only fooling" tail.

if(Yankee.iz('IE') && navigator.userAgent.indexOf('Opera')==-1)

But that is just bad code. It's three times as long to write and makes the engine do two separate tests to find one thing. 

Opera doesn't say much about the reasons for this, although I believe there was a time when some large, well known sites didn't load all their pages if the userAgent failed the 'MSIE' test.

Opera just says, in their documentation:

"When a Web browser connects to a Web site, it tells the Web site which browser it is. In an ideal world, all browsers would work with all sites, but that is sadly not the case. Browsers work a bit differently, and some Web sites may intentionally or unintentionally shut out some browsers as a result. If you experience problems with a Web site, try changing the browser identification and reload the page."

Are there end users who actually play with these settings?

It feels like a programmers toy to me. Of course, I like toys. On the Opera site they have recently added some pretty good documentation, and it appears that a major reason for giving Opera the IE switch was to run a subset of Microsoft's j-script on top of the javascript that is standards supported. Now, J-script is another article...To get back to the point, sometimes Opera can handle the code written for IE. Some of the form elements, links and anchors, and images behave fairly well with basic scripts. But you can't count on it.

That's the little lie. It helped me switch my strategy for browser testing away from the userAgent and toward actual browser capability. It is better to test the browser for what it can do, not what its name is.Or might be. For example-


Finding Ids

Say you want to find an element by its id.


function Mr(hoo){

if(document.getElementById)

return document.getElementById(hoo);

else if (document.all)

return document.all[hoo];

else if (document.layers)

return document.layers[hoo];

else return false;

}

If you are using Opera or IE5+ or Mozilla/N6 the first if, getElementById , will snag your man. IE4 will discover him with the 'all' array, and Navigator might find the id, if the element is positioned and in the first level. If you write your code to run depending on the result of testing an if(Mr(hoo)) statement, you'll keep out the rif raff.

A test for (create.documentFragment) returns true for N6, Mozilla and IE6 but not IE5 or Navigator. This works for various methods in the document object model- Except-


The Big Lie

My favorite, separate the men from the boys, the wheat from the chaff, never fail, do or die browser test has been (document.createElement). If a browser client can create new elements in an open document, the page is wide open for scripting with document objects.

If the browser doesn't pass the document.createElement test, I either give it a document write to make new content, or a link to another page, or leave 'em the plain html- just like mother used to make.

I have a set of tests I use enough to keep in a single function, called Yankee.iz. Remember Yankee.iz? There is a set of strings you can send, to test for a few important abilities. My favorite is to test for (document.createElement).

I start countless scripts with a test like:

if(Yankee.iz('DOM')) do a lotta DOM code;
else do some simpler code.

It has always worked- until the latest Opera version... Guess what Opera tells us?

Yep. Opera claims to understand document.create Element. So it gets in the adult section and in a microsecond it is asked to create an element and that's all, folks...It never sees the code written for the browsers that can't use the DOM...

It goes to show, we cannot let up. No matter how carefully we test and retest, something will come along and break our code. We have to keep testing, keep checking, and never assume we know what is coming next...

So far Opera doesn't claim to support document.createTextNode, so with a quick edit, I recovered. My test in Yankee.iz for the DOM now reads:

case 'dom':return !!(document.createTextNode);

But I'm not really comfortable with Yankee.iz anymore...

Happy returns...


The End...

More Tips and Commented Code:

Up to speed with Opera 7

Wriggling Mozilla

Current Events

Waiting for the DOM...

 More Articles