// dbgcons-2.0.8
// Copyright (C) 1999-2005 Simon Lodal
// Licensed under GNU GPL, see COPYING.

function DbgCons()
{
	this.queue = [];
}

/** config */
/// poll timeout for flush()
DbgCons.prototype.flushretry = 1000;

/** public data, can be set at any time */
/// add timestamp is added to each message?
DbgCons.prototype.show_time = 1;


/** private data */
/// DbgCons version number, used for default path to html files.
DbgCons.prototype.version = "2.0.8";
/// Identification string for this DbgCons object. Prepended to each message.
/// Useful if several window write simultaneously to the same console.
DbgCons.prototype.idstr = "";
/// next debug console in the chain. Can either be another DbgCons object,
/// or the core object running in the console window.
DbgCons.prototype.ndc = null;
/// debug console's window object
DbgCons.prototype.wnd = null;
/// true if this dc owns the console window and it is opened.
DbgCons.prototype.opened = 0;
/// queue of waiting messages
DbgCons.prototype.queue = null;
/// current queue length
DbgCons.prototype.qlen = 0;
/// setTimeout() ID for flush()'s polling loop
DbgCons.prototype.flush_tid = 0;
/// regexp for removing trailing whitespace
DbgCons.prototype.trailingwhite = /(\t| |\n|\r)*$/;
/// message counter
DbgCons.prototype.msgnr = 0;
/// string used as datatype on an exception error.
DbgCons.prototype.errtyp = "### ERROR ###";
/// old window.onerror handler
DbgCons.prototype.oldhdl = null;



/**
   Public: Initialize the debug console. Must be called before any messages
   are sent to it.
   @param idstr Identification string for this dbgcons object.
   @param ndc Next debug console, if any (another DbgCons object). If defined,
	this DbgCons object will not open it's own console window, instead it
	will route messages to the ndc.
	This is useful if your app runs in a frameset and you want all frames
	to output to the same console: Include dbgcons.js in each frame and in
	the frameset document (so there is a $dc object in each). In the
	frameset document, you just call $dc.init(), so it opens the console
	window. In each frame, you call $dc.init(window.top.$dc), so each
	frame connects to the frameset's console.
   @param path Path where dbgcons files reside. Default is "dbgcons-$version".
   @param wndnm Window name of debug console. Default is "dbgcons". You may
	want to change it if you run multiple different apps in the same
	browser which all use a dbgcons, to avoid that they try to use the
	same window.
 */
DbgCons.prototype.init = function(idstr, ndc, path, wndnm)
{
	if (idstr) this.idstr = idstr+":";

	if (ndc) {
		this.ndc = ndc;
		this.opened = 1;
	} else {
		var parms = "resizable=1";
		if (!document.layers) parms += ",left=0,top=0,width=600,height=700";
		if (!path) path = "/dbgcons-"+this.version
		if (!wndnm) wndnm = "dbgcons";
		this.opened = 1;
		// try to reuse existing window without reloading it.
		// in NS4 there is no way to check if the window contains our
		// document, so always open with full url.
		if (document.layers) {
			this.wnd = window.open(path+"/dbgcons_main.html", wndnm, parms);
		} else {
			this.wnd = window.open("", wndnm, parms);
			if (!/\/dbgcons_main\.html$/.test(this.wnd.location.href)) {
				this.wnd = window.open(path+"/dbgcons_main.html", wndnm, parms);
			}
		}
		// a hacked clear()+route(); inserts the clear command first in
		// the queue. Since this function is not necessarily called
		// before everything else, some load time routines could have
		// put() messages in the queue already.
		this.queue = ["#clear#"].concat(this.queue);
		this.qlen++;
//		this.flush(); // NS4 goes "out of memory" if we hit it before it has initialized the window
		setTimeout("$dc.flush()",1000);
	}
}


/**
   Public: Clear the console.
 */
DbgCons.prototype.clear = function()
{
	this.route("#clear#");
}


/**
   Public: Write a message to the console.
   @param msg Text message to output.
 */
DbgCons.prototype.put = function(msg)
{
	var i,d="";

//	if (!this.opened) this.init();
	if (!msg) msg = "";
	msg = msg.replace(this.trailingwhite,"");
	this.msgnr++;
	if (this.show_time) {
		d = ""+new Date().getTime() % 10000;
		while (d.length < 4) d = "0"+d;
		d = ": "+d.substr(0,1)+"."+d.substr(1);
	}
	this.route("["+this.idstr+this.msgnr+d+"] "+msg);
}


/**
   Public: Dump a variable to the console, with recursion, sorting, filtering.
   @param label Explaining text, fx. the variable's name.
   @param data The variable.
   @param recurse (optional) Number of levels to recurse. 0 means you only get
	the datatype of the object, 1 (default) means all it's properties are
	dumped too, 2 their child properties too, etc.
   @param filter (optional) An anonymous object with more options (all
	optional):
	.s: Sort the properties by their key; the property name in objects,
	    the index in arrays. Default=0 (no sorting), else 1 (ascending) or
	    -1 (descending).
	.f: Show functions too (default=false, except for 0. level (the
	    immediate 'data' parameter).
	.i: Include filter (a regexp); only matching property names are dumped.
	.e: Exclude filter (a regexp); all properties (that are left after the
	    include filter has been applied, if any) are dumped except those
	    matching the exclude filter.
	.l: Limit; max number of properties to dump.
	.n: An anonymous object containing all the same parameters for the next
	    recursion level.
	Examples:
	- {s:1} : Sort 1. level properties.
	- {n:{s:1}} : Sort 2. level properties, but not 1. level.
	- {e:/^[A-Z]/} : Exclude 1. level properties starting with A-Z.
	- {i:/target/i, n:{s:1, i:/offset/i}} : Include all properties containing
		"target" in their name, when printing them (that is 2. level),
		use sorting, and print only those containing "offset" in their
		name.
 */
DbgCons.prototype.dump = function(label, data, recurse, filter)
{
	this.put(this.str_dump(label, data, recurse, filter, ""));
}


/**
   Public: The workhorse of .dump(). Most parameters: .See dump().
   @param fill String prepended to each output line.
   @param typ (optional) Type info about data, as returned from typeinfo()
	(see below). To avoid calling typeinfo() twice on the same variable; it
	can be an expensive operation.
   @return String ready to be fed to the console, or whatever.
 */
DbgCons.prototype.str_dump = function(label, data, recurse, filter, fill, typ)
{
	var	res="";

	if (typeof(recurse) == "undefined") recurse = 1;
	if (!(fill)) fill = "";
	if (!typ) {
// see insert_exception_handlers()
if (this.str_dump) {
	typ = this.typeinfo(data);
}
if (!this.str_dump) {
	return fill + label + " : ### ERROR ###\n";
}
	}

	switch (typ[0]) {
	case "object":
		res += fill + label + " ";
		if (data == null) {
			return res + "= null\n";
		}

		// core data objects
		switch (typ[1]) {
		case "Boolean":
		case "Date":
		case "Number":
			return res + "("+typ[1]+") = " + data.toString() + "\n";
		case "RegExp":
			return res + "(RegExp) = " + data.toString() + "\n";
		case "String":
			return res + "(String) = '" + data + "'\n";
		case "Array":
			res += "(Array ["+data.length+"])";
			break;
		case "Object":
		case "":
		default:
			res += "{" + (typ[1] ? typ[1] : "object");
			res += ((typeof(data.length)!="undefined")?" ["+data.length+"]":"")+"}";
		}

		if (recurse) {
			var	sorting=1,
				showfn=0,
				limit=0,
				nfill = fill+"        ",
				names = [],
				ntyp,typs = [],
				keys = {},
				i;

			if (filter) {
				if (typeof(filter.s) != "undefined")
					sorting = filter.s;
				if (filter.f)
					showfn = filter.f;
				if (filter.l)
					limit = filter.l;
			}

			res += ":\n";
			// get objects property names
			// Konq 3.3.2 bug: If we only write "for (key in data)",
			// toString() only outputs "for ( in data)", using
			// "var key" works around that bug.
			for (var key in data) {
				// dupe protection (Konq3 bug)
				if (keys[key]) continue;
				if (filter) {
					if (filter.i && !filter.i.test(key)) continue;
					if (filter.e && filter.e.test(key)) continue;
				}
				keys[key] = 1;
				names[names.length] = key;
			}
			if (sorting != 0) {
				names.sort();
				if (sorting < 0) names.reverse();
			}
			// get typeinfos
			for (i=0; i<names.length; i++) {
// see insert_exception_handlers()
if (this.str_dump) {
				ntyp = this.typeinfo(data[names[i]]);
}
if (!this.str_dump) {
				ntyp = [this.errtyp];
}
				typs[typs.length] = ntyp;
			}
			// print
			print:
			while (1) {
				for (i=0; i<names.length; i++) {
					ntyp = typs[i];
					if ((ntyp[0] == "function") && !showfn)
						continue;
					if ((ntyp[0] != "function") && (showfn == 1))
						continue;

					if (typs[i][0] == this.errtyp) {
						// show errors along with the normal properties
						if (showfn != 1)
							res += nfill + names[i] + ": "+this.errtyp+"\n";
					} else {
						res += this.str_dump(names[i],
								     data[names[i]],
								     recurse-1,
								     filter&&filter.n?filter.n:null,
								     nfill,
								     ntyp);
					}
					limit--;
					if (!limit) break print;
				}
				if (!showfn) break;
				showfn = 0;
			}
		} else {
			res += "\n";
		}
		return res;
	case "function":	// almindeligt definerede functions
	if (1) {
		res += fill + label;
		// print " @ funcname" if we have it and it is not already
		// contained in label.
		var print_fname = 1;
		if (!typ[1])
			print_fname = 0;
		else if (label == typ[1])
			print_fname = 0;
		else {
			var re1,re2;
			re1 = new RegExp("\\."+typ[1]+"$");
			re2 = new RegExp("\\[\\s*['\"]"+typ[1]+"['\"]\\s*\\]\\s*$");
			if (re1.test(label) || re2.test(label))
				print_fname = 0;
		}
		if (print_fname) res += " @ "+typ[1];
		res += " ( "+(typ[2]?typ[2].toString().replace(/,/g,", "):"")+" )\n";
		return res;
	}
	case "string":
		return res + fill + label + " ["+data.length+"] = '"+data+"'\n";
	case "number":
	case "boolean":
		return res + fill + label + " = "+data+"\n";
	case "undefined":
		return res + fill + label + " : " + typ[0] + "\n";
	default:
		return res + fill + label + " : [unknown datatype:'"+typ[0]+"']\n";
	}
}


/**
  Public: Get the real type of a variable, plus class/function name. Used by
  dump().
  The reason for this function is that typeof() sometimes returns wrong type.
  For all plain data types (string, number, boolean etc) it is not a problem.
  The problem is with objects and functions. Each browser has it's own cases
  of returning the wrong value, a few examples:
    - Konqueror 3.2.2 and Opera 8.0: typeof(document.applets) => "function"
      (funny, toString() returns "[object HTMLCollection]"). Same for other
      collections in document.
    - Firefox, Mozilla and Netscape 4: typeof(/[a-z]/) => "function". All other
      browsers think this is a (RegExp) "object".
  Since typeof() is unreliable, we use heuristics to determine the real type.
  And while we are at it, we also try to find the function/class name.
  The heuristics tries different ways of figuring it out, but in the end if
  the browser does not provide any method to get information about an object
  there is no way to find out, and this function may return wrong results.
  One example is document.writeln in IE, this built-in supports none of the
  normal methods to get information about it, such as toString, toSource,
  constructor, or prototype. Yet the browser returns "object" from typeof().
  And so does this function since it has no way to know otherwise.

  If possible, you should catch exceptions thrown from this function. All
  browsers have bugs in rarely visited places where doing typeof(), toString,
  toSource() etc on some variable triggers an error. We could catch errors
  in this function, but that would not be enough, since sometimes the
  exception happens as soon as you try to pass the variable as parameter to
  this function. Mozilla/Firefox is particularly infamous, throwing these
  NS_ERROR_NOT_IMPLEMENTED exceptions for even looking at some variable.

  @param data The variable.
  @return An array with one or more fields:
	[0] contains the typename; one of the values that typeof() can return
	(object, function, string, number, boolean, undefined).
	The value is usually what typeof() returned, but not always! As
	explained above, typeof() sometimes mixes up "object" and "function",
	and in cases where typeinfo() detects an error, [0] will contain the
	correct type. Note however that it may not always be possible to detect
	an error, so you may still get an incorrect type.
	[1] When the type is "object" or "function", [1] is set to the class
	name or function name respectively, if that information can be found,
	else it is empty.
	[2] When type is "function", [2] contains either the number of
	arguments expected, or (if available) a string with it arguments.
  Notes about object of built-in types: JavaScript distinguishes between simple
  data types as really simple or as objects. If you create an Array, Boolean,
  Date, Number, RegExp or String (or Object) with 'new', you get a variable
  where type() returns "object" (and that object's constructor is set to the
  relevant constructor function). All of these can also be created as literals
  (or by calling the constructor function without 'new'!), in which case you
  get something very different:
  Boolean, Number, String: A variable where typeof() returns "boolean",
	"number" or "string", respectively.
  Date: A variable where typeof() returns "string".
  Array, Object, RegExp: No matter if you create these literally or with new,
	you get an "object" (exception: In Netscape 4, Mozilla and Firefox,
	typeof() returns "function" for a RegExp, a bug I believe; typeinfo()
	corrects this to "object").
 */
DbgCons.prototype.typeinfo = function(data)
{
	var	typ,rtyp="",rnm="",
		ctor=null,str,cstr,args,m;

	typ = typeof(data);
	if ((typ != "object") && (typ != "function")) return [typ];
	if (data == null) return ["object",""]

// known objects that cause exceptions if we use toString(); return known
// object types so str_dump() can traverse their properties.
	// IE
	if (data == window.navigator) return ["object","Navigator"];
	if (data == window.clientInformation) return ["object","Navigator"];
	// In some browsers window.location is a string, but in those we do not
	// even get here, so not a problem.
	if (data == window.location) return ["object","Object"];
	// Firefox 1.0.3
//	if (data == document.domConfig) return ["undefined",""];


	// If the direct constructor is one of the built-in core constructors,
	// we have a 100% certain type identification. A very large number of
	// elements are matched this way. Elements that are missed by this test
	// are:
	// 1) Elements having no constructor (all objects should have one, but
	//    in some cases the browsers do not provide one on certain built-
	//    in's).
	// 2) Objects created by other constructors, including all custom
	//    objects.
	// 3) Enclosed functions may have another constructor that Function;
	//    in NS4 the constructor is Closure().
	if (data.constructor) {
		ctor = data.constructor;
		// this a certain proof; Function() can only return functions,
		// so if typeof() calls it an "object" it is wrong.
		if (ctor == Function) rtyp = "function";
		// anonymous objects; however some built-in objects also appear
		// with Object() as constructor, fx. objects returned from
		// document.createElement().
		else if (ctor == Object) rtyp = "object";
		else {
			if (ctor == Array) return ["object","Array"];
			if (ctor == Boolean) return ["object","Boolean"];
			if (ctor == Date) return ["object","Date"];
			if (ctor == Number) return ["object","Number"];
			if (ctor == RegExp) return ["object","RegExp"];
			if (ctor == String) return ["object","String"];
		}
	}

	// toString() or toSource(), the latter is only supported in Netscape
	// family of browser, and I have not seen it provide information that
	// was not available in toString() as well. But who knows how future
	// browsers will behave.
	str = data.toString ? data.toString() : (data.toSource ? data.toSource() : "");

	// if type has not been found yet, try to parse it out of toString()
	// or toSource().
	var re_typ_func = /^\s*[\[\(]?\s*function[\s\]\(]/;
	var re_typ_obj = /^\[object[\s\]]/;

	// find name of function, or class (constructor) name of object.
	var re_funcname = /^\s*[\[\(]?\s*function\s\s*([a-zA-Z_$][a-zA-Z0-9_$]*)?\s*\(([^\)]*)\)/;
	var re_clsname = /^\[object\s*([a-zA-Z_$][a-zA-Z0-9_$]*)/;

	if (!rtyp) {
		if (re_typ_func.test(str))
			rtyp = "function";
		else if (re_typ_obj.test(str)) {
			rtyp = "object";
		}
	}

	// find function name
	if ((rtyp == "function") || !rtyp) {
		m = re_funcname.exec(str);
		// 'new Function()' returns a "function anonymous() { ... }" in
		// some cases, other times it is just a "function() { ... }".
		// Turn "anonymous" into empty string.
		if (m&&m.length) {
			rnm = (m[1]&&(m[1]!="anonymous")) ? m[1] : "";
			args = m[2] ? m[2] : "";
			args = args.replace(/\s/g,"");
			if (!args) args = data.length;
			return ["function",rnm,args];
		}
	}

	if ((rtyp == "object") || !rtyp) {
		m = re_clsname.exec(str);
		if (m&&m.length&&m[1]) {
			rtyp = "object";
			rnm = m[1];
			if (rnm != "Object") return ["object", rnm];
		}
		// if we only get "Object", or nothing, try to get the function
		// name of the constructor. The risk in this is if data really
		// is a function; we may end up representing it by it's
		// constructor's name this way. Furtunately, there exists very
		// few function constructor functions, in fact only Function()
		// (which if found has set rtyp="function" so we never get
		// here), and in NS4 a strange thing called Closure(), which is
		// the constructor of enclosed functions. So we just check and
		// ignore that.
		if (ctor) {
			cstr = ctor.toString ? ctor.toString() : (ctor.toSource ? ctor.toSource() : "");
			m = re_funcname.exec(cstr);
			if (m&&m.length&&m[1]) {
				if (m[1] != "Closure") return ["object",m[1]];
			}
		}
	}

	// last resort: trust typeof()
	if (!rtyp) rtyp = typ;

	return [rtyp,rnm];
} // typeinfo()


/**
   Public: Dump property names of an object/array to the console. Much like
   .dump(), except it does not show values or datatypes (and can therefore not
   be recursive).
   It can be useful in cases where dump() is unable to show an object. All
   browsers have bugs in some properties so that just looking at them triggers
   a script error, and if the browser does not support exceptions, dump() will
   just fail. keys() also looks at the properties, though less intense, thus
   with greater chance to avoid script errors.
   So, if dump() fails, try keys(), then at least you get a list of property
   names, which you try filtering out, one by one, when calling dump(), until
   you figure out which ones provokes the error.
   @param label Explaining text, fx. the variable's name.
   @param data The variable.
   @param filter A filter expression like in .dump() (see above), except it can
	not be nested (since there is no recursion). The functions flag ("f")
	is ignored; since this function does not look at datatypes it does not
	even know if something is a function.
 */
DbgCons.prototype.keys = function(label, data, filter)
{
	this.put(this.str_keys(label, data, filter, ""));
}


/**
   Public: The workhorse of .keys(). Parameters: .See keys().
   @param fill String prepended to each output line.
   @return String ready to be fed to the console, or whatever.
 */
DbgCons.prototype.str_keys = function(label,data,filter)
{
	var res = "";
	if (!("object"==typeof(data))) {
		res = label+": [not an object!]\n";
		return res;
	}

	var names = [];
	for (var nm in data) {
		if (filter && filter.i && !filter.i.test(nm)) continue;
		if (filter && filter.e && filter.e.test(nm)) continue;
		names[names.length] = nm;
	}

	var sorting = 1;
	if (filter && (typeof(filter.s) != "undefined")) sorting = filter.s;
	if (sorting) names.sort();
	if (sorting < 0) names.reverse();

	var limit = (filter && filter.l) ? filter.l : 0;
	res = label+" ("+names.length+"):\n";
	for (var i=0; i<names.length; i++) {
		res += "        "+names[i]+"\n";
		limit--;
		if (!limit) break;
	}

	return res;
}


/**
   Public: Dump a DOM/XML node and all it's children to the console. This
   is different from dump() in that it prints objects as XML elements, not
   as JavaScript objects. It is really just a very simple XML serializer.
   @param domobj The object to be dumped.
   @return String ready to be fed to the console, or whatever.
 */
DbgCons.prototype.dom = function(label,domobj)
{
	var txt = label ? label+":\n" : "";
	txt += this.str_dom(domobj);
	this.put(txt);
}


/**
   Public: The workhorse of .dom(). Parameters: .See dom().
   @return String ready to be fed to the console, or whatever.
 */
DbgCons.prototype.str_dom = function(domobj)
{
	var txt="",i,attr;

	switch (domobj.nodeType) {
	case 1: // ELEMENT_NODE
		txt += "<"+domobj.nodeName;
		// only element nodes can have attributes
		for (i=0; i<domobj.attributes.length; i++) {
			attr = domobj.attributes.item(i);
			txt += " "+attr.name+"=\""+attr.value+"\"";
		}
		if (!domobj.hasChildNodes()) {
			txt += " />";
		} else {
			txt += ">";
		}
	case 9: // DOCUMENT_NODE
	case 11: // DOCUMENT_FRAGMENT_NODE
		// traverse childnodes.
		for (i=0; i<domobj.childNodes.length; i++) {
			txt += this.str_dom(domobj.childNodes.item(i));
		}
		if ((domobj.nodeType == 1) && domobj.hasChildNodes()) {
			txt += "</"+domobj.nodeName+">";
		}
		return txt;
	case 2: // ATTRIBUTE_NODE
		return "[UNEXPECTED ATTRIBUTE NODE:"+domobj.nodeName+"=\""+domobj.nodeValue+"\"]";
	case 3: // TEXT_NODE
		return domobj.nodeValue;
	case 4: // CDATA_SECTION_NODE
		return "<![CDATA["+domobj.nodeValue+"]]>";
	case 5: // ENTITY_REFERENCE_NODE
// FIXME
		return "";
	case 6: // ENTITY_NODE
// FIXME
		return "";
	case 7: // PROCESSING_INSTRUCTION_NODE
		return "<?"+domobj.nodeValue+"?>";
	case 8: // COMMENT_NODE
		return "<!--"+domobj.nodeValue+"-->";
	case 10: // DOCUMENT_TYPE_NODE
// FIXME: Correct syntax, fx <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
		txt = "<!DOCTYPE name='"+domobj.name+"' publicId='"+domobj.publicId+" systemId='"+domobj.systemId+"' internalSubset='"+domobj.internalSubset+"'>";
		return txt;
	case 12: // NOTATION_NODE
		return "[UNEXPECTED NOTATION NODE: systemId='"+domobj.systemId+"' publicId='"+domobj.publicId+"']";
	default:
		return "[UNKNOWN("+domobj.nodeType+"):"+domobj.nodeName+"]";
	}
}


/**
   Semiprivate: Route message to the parent dc, or to the console if we own it.
   Never call from app code. It is called by other dc's for which this is parent.
   @param msg Text message to output.
 */
DbgCons.prototype.route = function(msg)
{
	this.queue[this.qlen++] = msg;
	this.flush();
}


/**
   Private: Main function for passing messages on to their destination.
 */
DbgCons.prototype.flush = function()
{
	var i;

	// clear timer; flush() could be called directly even if another call
	// is scheduled in the future.
	if (this.flush_tid) {
		clearTimeout(this.flush_tid);
		this.flush_tid = 0;
	}

	// do we not have a ndc yet?
	if (!this.ndc) {
		// flush() could be called before init(), in that case we have
		// no ndc, so we can just let the queue build up until init()
		// is called.
		if (!this.opened) {
			return;
		}
		// we have opened the console window, but have not got it's dc
		// object yet. Try again, else start/continue polling loop.
		if (this.wnd && this.wnd.dc) {
			this.ndc = this.wnd.dc;
		} else {
			this.flush_tid = setTimeout("$dc.flush()", this.flushretry);
			return;
		}
	}

	// a dc must always be ready to receive messages (as soon as it comes
	// into existence), and at least queue them if it can not deliver them
	// to next hop immediately.
	for (i=0; i<this.qlen; i++) {
		this.ndc.route(this.queue[i]);
	}
	this.qlen = 0;
}


/*######################## EXCEPTION HANDLING STUFF ########################*/

/**
   Generic function (not particularly tied to DbgCons, only used by it) that
   parses a function's code and replaces certain special expressions with
   exception handling blocks; "try { ... } catch (e) { ... }".
   The reason for this functionality at all is that we do want to use
   exceptions, but some old browsers do not support them, and if they see any
   "try" or "catch" statements in the code they will generate a hard script
   error at load time.
   The solution is to write the code without try and catch, and instead insert
   them later (using this function) under more controlled circumstances. This
   function fails with an exception if exception handling is not supported,
   but then at least the caller has a chance to handle (ignore) it using
   window.onerror. And the function in question will remain unchanged; still
   runnable. In other words, the caller really should install a window.onerror
   handler before calling this function, and of course make sure to remove it
   afterwards, even if this function has failed with an exception.
   The only case where the user will see an error is if the browser does not
   support exceptions and it does not support window.onerror.

   Insertion of try and catch blocks is done using some special expressions:
	if (this.funcname) {
		// stuff to try
	}
	if (!this.funcname) {
		// stuff to do on exception
	}
   If the browser supports exceptions, the above will be replaced with:
	try {
		// stuff to try
	}
	catch (e) {
		// stuff to do if try block fails.
	}
   This way, the try block will always be executed. If the browser does not
   support exceptions, the exception handling block will never be executed.

   Note on the 'if (this.funcname)' expression: funcname is the function itself
   (a literal; not a string). If the function is global, the expression will
   just be 'if (funcname)'.
   It looks very weird, but there are good reasons for it. Basically we need
   a marker in the code to tell us where to insert exception handling blocks.
   The test for the existence of the function itself was chosen because:
   1) It is unique with a reasonable probability. Why would you ever, in normal
      code, test for the existence of the function that is currently executing?
   2) It is resistant to the browser's JS optimizations. Most browsers optimize
      the function's code, so toString() output may look very different from
      the original. Fx it usually does not contain any comments. But the
      browsers may go further and optimize constants, unused labels, empty
      blocks and other stuff out of the way. However, a reference to the
      function itself can never be optimized away since it could in theory be
      delete()'d.

   @param obj Object context of the function. If the function is global, use
	null.
   @param fname Name of the function (as a string).
   @return Nothing, but it will fail with an exception if the browser does not
	support exception handling.
*/
DbgCons.prototype.insert_exception_handlers = function(obj,fname)
{
	var	fn,code,m,p;

	// regexp'es for finding statements to be replaced with "try" and "catch".
	var re_try = new RegExp("(if\\s*\\(\\s*"+(obj?"this\.":"")+fname+"\\s*\\))","g");
	var re_catch = new RegExp("(if\\s*\\(\\s*!\\s*"+(obj?"this\.":"")+fname+"\\s*\\))","g");

	// get function and it's code
	fn = obj ? obj[fname] : window[fname];
	code = fn.toString();
	// insert try/catch
	code = code.replace(re_try, "try");
	code = code.replace(re_catch, "catch (e)");

	// Konqueror 3.3.2 trouble: toString() does not output valid JS code;
	// it replaces literal newlines with real newlines, indistinguishable
	// from ordinary newlines between code lines. We have to replace them
	// again with literal newlines for the code to be valid.
	code = this.reinsert_newlines(code);

	// Trouble! 'eval(code)' does not seem to work, we have to use
	// 'new Function()' instead. That means parsing out the arguments so
	// we can provide them individually to Function(), and removing the
	// function declaration so only it's body code is left. Ugly.
	var re_fnbeg = /^\s*function[^(]*\(\s*([^)]*)\)\s*\{/;
	var re_fnend = /\}\s*$/;
	// get arguments
	m = re_fnbeg.exec(code);
	p = m[1];
	// strip whitespace so we do not get arguments names with whitespace in them
	p = p.replace(/\s/g, "");
	p = p.split(",");
	// remove function declaration and terminating }.
	code = code.replace(re_fnbeg,"");
	code = code.replace(re_fnend,"");

	fn = null;
	switch (p.length) {
	case 0: fn = new Function(code); break;
	case 1: fn = new Function(p[0],code); break;
	case 2: fn = new Function(p[0],p[1],code); break;
	case 3: fn = new Function(p[0],p[1],p[2],code); break;
	case 4: fn = new Function(p[0],p[1],p[2],p[3],code); break;
	case 5: fn = new Function(p[0],p[1],p[2],p[3],p[4],code); break;
	case 6: fn = new Function(p[0],p[1],p[2],p[3],p[4],p[5],code); break;
	case 7: fn = new Function(p[0],p[1],p[2],p[3],p[4],p[5],p[6],code); break;
	case 8: fn = new Function(p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],code); break;
	default: alert("replace_code("+fname+"): Too many arguments."); return;
	}

	if (!fn) {
		alert("replace_code("+fname+"): BUG! New function is undefined, but no errors have been thrown!?");
		return;
	}

	if (obj) {
		obj[fname] = fn;
	} else {
		window[fname] = fn;
	}
}


/**
   Helper function for insert_exception_handlers(), see comments in it.
   WARNING: This function is not a complete JS parser, it can fail in several
   ways:
   1) It does not observe comments, so if you have unmatched quotes inside
      comments it will fail.
   2) It also does not observe literal regular expressions, so if any of those
      contain an unmatched quote (even if it is escaped), this function will
      fail.
*/
DbgCons.prototype.reinsert_newlines = function(code)
{
	var	lines,i,cont=0,
		dqpos=-1,sqpos=-1,pos,len,
		m;

	// regexps for finding literal string terminators, works with escaping
	// inside the string. Could be simpler but then NS4 barks.
	var re_sqstr = /^\'|([^\\\'][^\\\']*|(\\\\)(\\\\)*|(\\([^a\\]|[^b\\])))*\'/;
	var re_dqstr = /^\"|([^\\\"][^\\\"]*|(\\\\)(\\\\)*|(\\([^a\\]|[^b\\])))*\"/;
	var re_cur = null;

	// make sure we only have \n line breaks.
	code = code.replace(/\r\n/g,"\n").replace(/\n\r/g,"\n").replace(/\r/g,"\n");
	lines = code.split("\n");

	cont = 0;
	for (i=0; i<lines.length; i++) {
		pos = 0;
		while (1) {
			// if we are continuing a search from previous line,
			// we start from index 0; if we are not continuing,
			// we must first find the beginning of a string.
			if (!cont) {
				sqpos = lines[i].indexOf("'", pos);
				dqpos = lines[i].indexOf('"', pos);
				if ((sqpos >= 0) && ((dqpos < 0) || (sqpos < dqpos))) {
					pos = sqpos + 1;
					re_cur = re_sqstr;
				}
				else if ((dqpos >= 0) && ((sqpos < 0) || (dqpos < sqpos))) {
					pos = dqpos + 1;
					re_cur = re_dqstr;
				}
				// the line contains only finished strings;
				// append a real newline.
				else {
					lines[i] += "\n";
					break;
				}
			}
			// search for end of string
			m = re_cur.exec(lines[i].substr(pos));
			// found end of string?
			if (m) {
				pos += m[0].length;
				// we have finished a string
				cont = 0;
			} else {
				// we have reached end of line while still
				// inside a string; append a literal newline.
				lines[i] += "\\n";
				// set continue flag so next iteration will not
				// look for the beginning of a string
				cont = 1;
				break;
			}
		}
	}
	return lines.join("");
}


DbgCons.prototype.init_exceptions = function()
{
	this.oldhdl = window.onerror;
	window.onerror = function() { return true; };
	this.insert_exception_handlers(this, "str_dump");
}


DbgCons.prototype.init_exceptions_cleanup = function()
{
	window.onerror = this.oldhdl;
}


/*############################# INITIALIZATION #############################*/
// global singleton. app must call $dc.init() later.
var $dc = new DbgCons();
$dc.init_exceptions();
