reptile7's JavaScript blog
Saturday, October 31, 2009
 
Follow the Error
Blog Entry #161

In our last episode, we applied a basic error-handling function to the jserrorpage.html demo page of HTML Goodies' "The onerror Event Handler" tutorial: that function duly caught jserrorpage.html's document.wrte("dog") error and returned the error line number in the jserrorpage.html source. But let's set the bar a bit higher, shall we? What if we were dealing with a page/script that contains two, three, or even a whole bunch of errors?

Consider, for example, the assignment script for HTML Goodies' JavaScript Primers #2 ("Error Messages"):

...x
dothis = new Date( );
month = dothis.getMonth( );
month = (month * 1) + 1;
day = dothis.getDate( );
year = dothis.getFullYear( );
document.wrte(" ", month, "/", day, "/", year, " ");


There are two problematic lines in the above code: the first line will throw a syntax error whereas the last line contains a misspelled-method* run-time error. (Technically, there are three syntax errors and also a latent 'x is not defined' run-time error on the first line, but the syntax error associated with the first . period character is all that's necessary to bring the script to a halt.) My error-handling function only catches the initial syntax error, which is perhaps not so surprising given that the default error-handling mechanisms for the browsers on my computer also only report the syntax error.
(*"Object member expected" is Microsoft's current description for this type of error.)

A browser's HTML layout engine is designed to cope with invalid HTML (e.g., it will typically ignore unrecognized element tags and attributes), but its JavaScript engine is nowhere near as forgiving. I find that a syntax error will shut down an entire script, even if the error line
(a) is preceded by valid code, or
(b) appears in a to-be-called-later function and the script also contains valid top-level code.
Run-time errors are less controlling: a top-level run-time error will kill top-level code that follows it, but will not interfere with preceding top-level code nor with functionized code.

So, as long as the ...x and document.wrte(" ", month, "/", day, "/", year, " "); lines are in the same script, we're not going to see an error for the latter line. However, if we were to put these lines in separate scripts, then we'd see errors for both of them.

When it introduced the onerror event handler in JavaScript 1.1, Netscape also rolled out an "Example 3: Error handling function" that intercepts JavaScript errors, the implication being that the code thereof flags all** of the script errors, internal and external, associated with a document. In fact, and as might be expected from the foregoing discussion, Netscape's Example 3 will not flag ≥2 errors in a given script - at least it won't do so with Netscape 4.x and later Netscape/Mozilla browsers - but it does a better job of collecting and displaying error information than my error-handling function does, so I thought we would run through it.
(**"We never said all," Netscape might protest. Fair enough, but if you meant "some" and not "all", then you should have made that explicit in the example's description.)

Example 3 collects error information via the code below:
window.onerror = myOnError;

msgArray = new Array( );
urlArray = new Array( );
lnoArray = new Array( );

function myOnError(msg, url, lno) {
   msgArray[msgArray.length] = msg;
   urlArray[urlArray.length] = url;
   lnoArray[lnoArray.length] = lno;
   return true;
}
The beginning window.onerror = myOnError; statement coassociates the window object, error events, and the myOnError( ) error-handling function.

We next create three arrays for holding information about error events: msgArray, urlArray, and lnoArray. Initially these arrays are empty and their lengths are all 0.

The myOnError( ) function is triggered directly by error events and serves to populate the msgArray/urlArray/lnoArray arrays with information about those events:

• For each error detected by the JavaScript engine, the message, file URL, and line number of the error are sent to myOnError( ) and respectively assigned to myOnError( )'s arguments[0], arguments[1], and arguments[2] - msg, url, and lno in this case - as detailed in the previous entry.

• For a first error, myOnError( ) respectively assigns msg, url, and lno to msgArray[0], urlArray[0], and lnoArray[0]. These assignments effectively increment the length of each array to 1, so that for a second error, msg, url, and lno are assigned to msgArray[1], urlArray[1], and lnoArray[1], which in turn increments the array lengths to 2; for a third error, msg, url, and lno are assigned to msgArray[2], urlArray[2], and lnoArray[2], incrementing the array lengths to 3; and so on.

myOnError( )'s concluding return true; statement plays no role in the collection of error information; rather, its purpose is to suppress the browser's default notification mechanism for the detected errors - it can be removed if you don't feel the need to do this.

Example 3 displays its error information via the code below:
function displayErrors( ) {
   win2 = window.open('', 'window2', 'scrollbars=yes');
   win2.document.writeln('<b>Error Report</b><p>');

   for (var i = 0; i < msgArray.length; i++) {
      win2.document.writeln('<b>Error in file:</b> ' + urlArray[i] + '<br>');
      win2.document.writeln('<b>Line number:</b> ' + lnoArray[i] + '<br>');
      win2.document.writeln('<b>Message:</b> ' + msgArray[i] + '<p>');
   }
   win2.document.close( );
}
The displayErrors( ) function opens a new window and codes therein an Error Report that catalogs myOnError( )'s error information. Appropriately, myOnError( )'s array holdings are written to the new window's document via a for loop. Each loop iteration prints out an 'error block': in order, the file URL, line number, and message for a detected error, with a corresponding label - Error in file:, Line number:, and Message:, respectively - prepended to each value. Formatting-wise, the error blocks are given a double-spacing separation via p elements, whereas within each block the lines are given a single-spacing separation via br elements.

Somewhat curiously, Netscape applies the above functions not to a conventional script but to event handler commands in the example's document body code:
<body onload="noSuchFunction( );">
<form>
<br><input type="button" value="This button has a syntax error"
onclick="alert('unterminated string)">

<p><input type="button" value="Display Error Report"
onclick="displayErrors( );">
</form>
(1) The body element start-tag holds an onload attribute that calls a nonexistent noSuchFunction( ) function, which will throw a run-time error.

(2-3) The first-in-source-order input element's onclick="alert('unterminated string)" attribute could throw one or two syntax errors, depending on whether the browser perceives the ) right parenthesis character as (a) the end of the alert( ) command or (b) part of the alert( ) parameter string. In either case, you should see a "you're missing a closing single quote" type of error; in the (b) case, you might also see a "we're expecting a ) to close the alert( ) call" type of error.

In practice - September 2016 update

The div below contains an Example 3 demo: for best results, try it with either Google Chrome or Opera. In recasting my original example3demo.html demo as a self-contained div, I've replaced the <body onload="noSuchFunction( );"> HTML tag with a window.onload = noSuchFunction; JavaScript statement.

<title>Demo for Netscape's "Example 3: Error handling function"</title>




When I click the button, the Error Report in the new window duly displays an error for the window.onload = noSuchFunction; statement when using Firefox, Google Chrome, IE, Opera, or Safari. Firefox's return is:

Error Report

Error in file: http://reptile7.blogspot.com/2009/10/follow-error.html
Line number: 592
Message: ReferenceError: noSuchFunction is not defined

With Firefox, Google Chrome, and Opera, clicking the button and then reclicking the button adds to the report a second error for the alert('unterminated string) command. Google Chrome and Opera return:

Error in file: http://reptile7.blogspot.com/2009/10/follow-error.html
Line number: 594
Message: Uncaught SyntaxError: Unexpected token ILLEGAL

For whatever reason, Firefox reports that the second error occurs on the first line of the source:

Error in file: http://reptile7.blogspot.com/2009/10/follow-error.html
Line number: 1
Message: SyntaxError: unterminated string literal

Netscape's own Example 3 report lists three errors in the following order:
(1) an unterminated string literal error on line number 34 for the alert('unterminated string) command;
(2) a missing ) after argument list error on line number 34 for the alert('unterminated string) command; and
(3) a noSuchFunction is not defined error on line number 30 for the noSuchFunction( ); call.
To make a long story short, you'd have to go back to Netscape 3.x to generate this output; I am able to reproduce Netscape's report (the messages and the order thereof, if not their line numbers) when using Netscape 3.04, but not Netscape 4.61, in the SheepShaver environment. À la Firefox, Netscape 4.61 detects a single unterminated string literal syntax error for the alert('unterminated string) command.

BTW: When applied to the Primer #2 assignment script, Example 3 only reports a first-line syntax error with all of the browsers on my computer that support it.

In sum, Netscape's "Example 3: Error handling function" is of limited power and is certainly not a panacea for catching script errors, but it's not useless, either. If you had a document that incorporated five external scripts, and if there were an error in each script, then Example 3 - at least when using Firefox, Google Chrome, or Opera - would catch all five of those errors. And now that I think about it, Example 3 could - with some patience - be used to sort out multiple errors in a single script: Example 3 would catch the first error in a first run of the script; after fixing the first error, Example 3 would catch the second error in a second run of the script; and so on. At the end of the day, however, I'm not convinced that, as a debugging tool, Example 3 represents an improvement over (or, indeed, does any more than reproduce) the browser's error console.

Although there are other JavaScript error-related topics we could take up at this point (e.g., the throw and try...catch statements introduced in JavaScript 1.4, and also the core Error objects added to JavaScript 1.5), I would really rather move on to the next Beyond HTML : JavaScript tutorial, "So, You Want A JavaScript Clock, Huh?", which we'll go over in the following entry.

reptile7

Wednesday, October 21, 2009
 
Error Mechanics
Blog Entry #160

We continue today our discussion of HTML Goodies' "The onerror Event Handler" tutorial. In the tutorial's fourth paragraph, Joe passingly notes:
The way I've seen [onerror] used online is to tell the user what line the error came from. I guess that would be good for the developer, but I'm more concerned about the users.
Joe doesn't explain how to tell the user what line the error came from, so we'll do that, and analyze errors as objects more generally, in this post.

We previously detailed the Netscape-cum-W3C event model in Blog Entry #107. Let's recap briefly with the code snippet below:

function getCoordinates(e) {
window.alert("You clicked at (" + e.pageX + "," + e.pageY + ")."); }

document.onclick = getCoordinates;


An event-handling function can be registered with a particular object to listen for a particular type of event via an object.onevent = funcRef; assignment statement, which can precede or follow the event-handling function in the source. When such an event occurs, a corresponding event object is passed as a single parameter to the event-handling function, which can then probe and make use of the event object's properties. In the code above, the document.onclick = getCoordinates; statement coassociates the getCoordinates( ) function, click events, and the document object. When the user clicks anywhere in the document content area, a click event object is passed to getCoordinates( ) and given the identifier e. Subsequently, getCoordinates( ) displays the values of e's pageX/pageY properties - i.e., the click event's viewport coordinates - on an alert( ) box:

The getCoordinates( ) alert( ) box for a click event when using Firefox

Classical JavaScript equipped the event object with fifteen properties, only two of which could be used with an error object: target and type. Mozilla's DOM event Reference currently lists thirty event object properties, nine of which can be used with an error object: bubbles, cancelable, currentTarget, eventPhase, explicitOriginalTarget, originalTarget, target, timeStamp, and type. (More specifically, these are the properties that do not return undefined for an error object. This property list was determined via trial and error on my part - you won't find it in Mozilla's materials.)

The Netscape event model is applicable to the onerror event handler if onerror is paired with the image object/element:

<img id="myImage" src="whatever..gif" alt="" />

<script type="text/javascript">

document.getElementById("myImage").onerror = eventData;
function eventData(e) {
window.alert(e.cancelable); // displays true
window.alert(e.originalTarget); // displays [object HTMLImageElement]
window.alert(e.type); // displays error }

</script>


For an error event associated with the window object, however, a registered handler function is not parametrically passed an error object but rather three property-like pieces of information about the error in the following order:
arguments[0]: a message that briefly describes the error
arguments[1]: the full URL of the document that holds the script containing the error
arguments[2]: the document line number of the error

window.onerror = errorInfo;
function errorInfo(errorMessage, fileURL, lineNumber) {
document.write("This page throws a script error: " + errorMessage);
document.write("<br>The error appears in the file located at: " + fileURL);
document.write("<br>The error appears on line #: " + lineNumber); }


Getting back to "The onerror Event Handler", if we were to replace the redirect( ) script in the jserrorpage.html demo document with the preceding script, then the printed output when using Firefox would be:

This page throws a script error: document.wrte is not a function
The error appears in the file located at: http://www.htmlgoodies.com/legacy/beyond/javascript/jserrorpage.html
The error appears on line #: 33

At least with the Mozilla/Netscape family of browsers, the above errorInfo( ) function cannot be used to query the standard/non-standard* properties of the event object (forget for a moment that these properties are not all that useful for an error object). If we were to declare errorInfo( ) with a normal Error e parameter signature

function errorInfo(e) { ... }

then e, as arguments[0], would return the error message rather than [object Event].

*About half of Mozilla's event object properties can be considered "standard" in that they appear in the DOM Level 2 Events Specification.

Microsoft gives its window.onerror handler functions the same String errorMessage, String fileURL, int lineNumber parameter signature as did Netscape - check out the first example on the MSDN Library's onerror page. Microsoft's differing event model, which we outlined in Blog Entry #108, allows these functions to query properties of the event object. (Microsoft's event object properties are listed here; again, most of these properties will return undefined for an error object.) For example, if we replace the jserrorpage.html redirect( ) script with the script below

window.onerror = errorInfo;
function errorInfo(errorMessage, fileURL, lineNumber) {
document.write("This page throws a script error: " + errorMessage);
document.write("<br>The object that fired the error event was: " + event.srcElement); }


then the printed output when using MSIE is:

This page throws a script error: Object doesn't support this property or method
The object that fired the error event was: [object SCRIPT]

(Why [object SCRIPT] and not [object Window]? Dunno, but that's what I observe on my computer.)

N.B. For an error in an external myScript.js file, I find that Firefox and MSIE implement the fileURL and lineNumber parameters differently. For fileURL, Firefox returns the URL of the myScript.js file whereas MSIE returns the URL of the main document. Relatedly, for lineNumber, Firefox returns the error line number in the myScript.js file whereas MSIE returns the line number in the main document that references the myScript.js file, i.e., the line number of the <script type="text/javascript" src="myScript.js"> tag.

More than one error?

I said last time that I was interested in going through Netscape's "Example 3: Error handling function", which deserves its own entry, so we'll give it the full monty in the next post.

reptile7

Monday, October 12, 2009
 
Error! Error, Will Robinson!
Blog Entry #159

Directly or indirectly, individually or in combination, user mouse and keyboard events underpin JavaScript's event handlers in almost all situations. However, there is one JavaScript event handler for which the user normally plays no role at all: onerror, which we'll briefly discuss today in looking over HTML Goodies' "The onerror Event Handler" tutorial.

References
(1) The onerror event handler was first implemented by Netscape in JavaScript 1.1; go here for the onerror section in the JavaScript 1.3 Client-Side Reference.
(2) onerror was subsequently picked up by Microsoft for MSIE 4; the current MSDN Library onerror page is here.
(3) As for the W3C, onerror does not appear in the "Intrinsic events" section of the HTML 4.01 Specification or in any of the XHTML 1.0 DTDs; however, the error event does crop up in the "HTML event types" subsection of the DOM Level 2 Events Specification.

Quoting Netscape, onerror [e]xecutes JavaScript code when an error event occurs; that is, when the loading of a document or image causes an error. Netscape associated onerror with the window object (not the document object) and the image object/element, as does Microsoft; in approximate correspondence, the W3C ties the error event to the HTML body, frameset, and object elements.

With respect to the window object, onerror is meant to flag syntax and run-time script errors of the types that we discussed all the way back in Blog Entry #4, and both Netscape* and Microsoft present an onerror example that uses onerror to display such errors to the user. The MSDN Library states, The onerror event fires for run-time errors, but not for compilation [syntax] errors. However, I can confirm that window.onerror does indeed catch syntax errors when using MSIE 5.2.3 for Mac OS X if the error and the error-handling function are in separate scripts:

<script type="text/javascript">
window.onerror = myBad;
function myBad( ) { window.alert("Syntax error!"); }
</script>
<script type="text/javascript">
document.write("This is a test.";
</script>


*Netscape alleges that its "Example 3: Error handling function", which we may go through later, will catch all of the script errors in a document; in practice, I am unable to reproduce Example 3's output with the browsers on my computer - only the noSuchFunction is not defined message, with an incorrect line number, shows up in the "Error Report" when using Firefox or MSIE.

window.onerror does not flag (X)HTML syntax errors, I'm sorry to report, e.g.:

<p id="p1'>This is a paragraph.</p>
<p id=="p1">This is a paragraph.</p>
<p id="p1">This is a paragraph.<\p>


With respect to the image object/element, onerror springs into action if either (a) there's something wrong with the image src value, or (b) the image file itself is corrupted in some way - check out Netscape's "Example 4: Event handler calls a function", which offers basic onerror code for bringing an image-loading problem to the user's attention.

Looking at the "Applies To" box at the bottom of the MSDN onerror page, I see that Microsoft claims onerror support for a number of other objects/elements; unfortunately, Microsoft provides no examples for these cases. (For example, Microsoft specifies that onerror can be paired with the script object/element. In this case, it's not clear if onerror is meant to catch bad content in an internal script and/or a bad src value for an external script. I tried both possibilities; neither attempt was successful, but perhaps this is a Windows thing.)

In "The onerror Event Handler", Joe uses onerror to take the user to a new page in response to a script error on the original page. Demo-wise, Joe provides a link to a jserrorpage.html page (you won't see this page if the demo works for you) whose source holds the scripts below:

<script type="text/javascript"><!--
window.onerror = redirect;
function redirect( ) { window.location = "errorfreepage.html"; }
// -->
</script>
<script type="text/javascript">
document.wrte("dog");
</script>


The first script element defines a redirect( ) function and also coassociates that function with onerror and the window object. In the second script element, the document.write( ) command is misspelled: when the browser hits this line, a run-time error is thrown and redirect( ) is triggered. Finally, redirect( ) sends the user to an errorfreepage.html page by assigning errorfreepage.html to the location property of the window object.

Firefox and MSIE run Joe's demo without incident, whereas Safari and Opera stall at the jserrorpage.html page. Subsequent experimentation has revealed that neither Safari nor Opera supports onerror for the window object, although they both support it for the image object/element.

Practical notes

• In another example, Netscape says, The onerror event handler for windows cannot be expressed in HTML. Therefore, you must spell it all lowercase and set it in a script [element]. On the other hand, the "Notes" section of Mozilla's current window.onerror page intimates that an onerror event handler is deployable as an attribute in the body element start-tag. I find that the window.onerror = redirect; script statement can indeed be replaced by

<body onerror="redirect( );">

when using Firefox but not when using MSIE, which is not so surprising given that the MSDN onerror page doesn't list the body object/element in the aforementioned "Applies To" box.

• Near the beginning of the tutorial, Joe says, JavaScript 1.1 allowed a new event handler named onerror. I've also seen it written onError and OnERROR. All three worked on my machines. It's true that alternate onerror capitalizations would be OK for an onerror HTML body or img element attribute - [HTML] Attribute names are always case-insensitive, the W3C declares - but in an object.onerror = funcRef; script statement, onerror must be all-lowercase per the Netscape quote in the preceding bullet point; the error-handling function will not 'listen' for error events if this is not the case.

• In its onerror "Description", Netscape states, window.onerror applies only to errors that occur in the window containing window.onerror, not in other windows. This does not mean that window.onerror will not catch errors in external scripts, which it will.

• Joe pitches his script as a means of preempting the display of error messages to the user:
It is my opinion that a user would much rather not see an error, that's why I like this simple little script so much...Because of the nature of the [script], the user may never even know that an error has occurred. There won't be an error box. There won't be the nasty little yellow triangle in the status bar. There won't be anything...except a new page.
Actually, MSIE users who have checked the Show scripting error alerts checkbox in the Preferences pane will still get an Object doesn't support this property or method run-time error dialog box before they are sent to errorfreepage.html; less obtrusively, Firefox users will see a document.wrte is not a function error message upon checking the Error Console. If you do want to 'turn off' these error notifications, however, you can do so by adding to the error-handling function a return true statement, which causes the function to override the browser's default error-handling mechanism:

function redirect( ) { window.location = "errorfreepage.html"; return true; }

Now, you may be thinking, "What is the point of this? A misspelling? You should be able to catch that before you upload your page" - at least that was my reaction when I first read the tutorial. Upon reading Joe's text more closely, however, a sentence in the first paragraph - No matter how careful you are, sometimes your pages throw errors - reminded me of something I observed regularly back in the day when I surfed the Web with my iMac 350: perfectly good code can throw errors with a browser that does not support that code.

In illustration, consider the following old-school example:

Let's suppose that it's March 1999 and that MSIE 5 has just been released. You augment your well-trafficked Web page with a script that utilizes the DOM getElementById( ) method, which is supported by MSIE 5 but not by earlier versions of MSIE. MSIE 4 users - and there'll be plenty of them out there - would definitely see an error message (if the Show scripting error alerts preference is enabled) when MSIE 4 hits the (first) getElementById( ) line in your script.

Joe's onerror script could then be employed as a sort of 'browser sniffer' to load an alternate page that won't cause any problems for MSIE 4 users who haven't upgraded to MSIE 5 yet. To take this concept a bit further, Joe himself recommends that his script be used to send users to a non-JavaScript version of the original page.

It is left to you to adapt the above example to other, more current situations; I won't tell you that onerror is an all-purpose tool for dealing with unsupported (new and/or proprietary) JavaScript features, but you should be able to use it for that purpose some of the time.

We'll discuss the use of error-handling functions to probe the details of error objects in the following entry.

reptile7


Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)