function Browser() {

	///////////////////////////////////////////////////////////////////////
	// Begin Public Interface
	//

	this.isIE     = false;							// deprecated
	this.isIE5    = false;							// deprecated
	this.isIE5Mac = false;							// deprecated
	this.isNN     = false;							// deprecated
	this.isFF     = false;							// deprecated

	this.isFlashFriendly   = isFlashFriendly;		// deprecated
	this.getString         = getString;				// returns navigator.userAgent string
	this.getName           = getName;				// returns the name (i.e., "Internet Explorer") of the browser
	this.getVersion        = getVersion;			// returns the browser version (floating point number)
	this.isSupported       = isSupported;			// returns true if browser is among those supported by FPC
	this.getHandCursorName = getHandCursorName;		// returns 'hand' or 'pointer'
	this.getViewWidth      = getViewWidth;			// returns current width of the document viewing area
	this.getViewHeight     = getViewHeight;			// returns current height of the document viewing area

	// see also public static methods below the private implementation block, including:

	// Browser.addEvent(eventName, toElement, callbackFunction, capture)
	// Browser.removeEvent(eventName, fromElement, callbackFunction, capture)
	// Browser.getDocument(iframeElementOrWindow)

	//
	// End Public Interface
	///////////////////////////////////////////////////////////////////////
	// Begin Private Implementation
	//

	var UNKNOWN    = 'x';
	var IE         = 'i';
	var NETSCAPE   = 'n';
	var MOZILLA    = 'm';
	var SAFARI     = 's';
	var FIREFOX    = 'f';

	var WINDOWS    = 'w';
	var MAC        = 'm';
	var UNIX       = 'u';
	var LINUX      = 'l';

	var bstring    = navigator.userAgent;
	var btype      = UNKNOWN;
	var bname      = 'unknown';
	var bversion   = 0.0;
	var os         = UNKNOWN;
	var supported  = false;

	// parse the browser string (private utility method)
	parseString();

	// determine whether this browser is among those supported by FPC, based on browser string
	supported = supported || ( btype == SAFARI  && bversion >= 525.0 );
	supported = supported || ( btype == IE      && bversion >= 6.0   );
	supported = supported || ( btype == MOZILLA && bversion >= 1.9   );
	supported = supported || ( btype == FIREFOX && bversion >= 2.0   );

	// for backward compatibility
	this.isIE     = (btype == IE);
	this.isIE5    = (btype == IE && bversion >= 5.0 && bversion < 6.0);
	this.isIE5Mac = (btype == IE && os == MAC && bversion < 6.0);
	this.isNN     = (btype == NETSCAPE);
	this.isFF     = (btype == FIREFOX);

	function isFlashFriendly() {
		return (btype != SAFARI && btype != MOZILLA && !(btype == IE && bversion >= 5.0 && bversion < 6.0));
	}

	function getString() {
		return bstring;
	}

	function getName() {
		return bname;
	}

	function getVersion() {
		return bversion;
	}

	function isSupported() {
		return supported;
	}

	function getHandCursorName() {
		return ((this.isIE && this.version < 6.0) ? 'hand' : 'pointer');
	}

	function getViewHeight() {
		var h = 0;
		if (typeof window.innerHeight != 'undefined') {
			h = window.innerHeight;
		} else if (btype == IE) {
			if (document.compatMode && document.compatMode != 'BackCompat') {
				h = document.documentElement.clientHeight;
			} else {
				h = document.body.clientHeight;
			}
		}
		return h;
	}

	function getViewWidth() {
		var w = 0;
		if (typeof window.innerWidth != 'undefined') {
			w = window.innerWidth;
		} else if (btype == IE) {
			if (document.compatMode && document.compatMode != 'BackCompat') {
				w = document.documentElement.clientWidth;
			} else {
				w = document.body.clientWidth;
			}
		}
		return w;
	}

	function parseString() {
		var start = 0;

		var bs = bstring;
		var ieIndex = bs.indexOf("MSIE");
		var geckoIndex = bs.indexOf("Gecko");
		var nnIndex = bs.indexOf("Netscape");
		var safariIndex = bs.indexOf("Safari");
		var firefoxIndex = bs.indexOf("Firefox");

		if (ieIndex >= 0){
			btype = IE;
			bname = "Internet Explorer";
			if (bs.indexOf("Mac") >= 0) {
				os = MAC;
			} else {
				os = WINDOWS;
			}
			start = ieIndex;
		} else if (geckoIndex >= 0) {
			if (nnIndex >= 0) {
				btype = NETSCAPE;
				bname = "Netscape";
				start = nnIndex;
			} else if (firefoxIndex >= 0) {
				btype = FIREFOX;
				bname = "Firefox";
				start = firefoxIndex;
			} else if (safariIndex >= 0) {
				btype= SAFARI;
				bname = "Safari";
				start = safariIndex;
			} else {
				btype = MOZILLA;
				bname = "Mozilla";
				start = bs.indexOf("rv:");
			}
			if (bs.indexOf("Linux") >= 0) {
				os = LINUX;
			} else if (bs.indexOf("Windows") >= 0) {
				os = WINDOWS;
			} else if (bs.indexOf("Mac") >= 0) {
				os = MAC;
			}
		}

		// try to find the version with regular expression.
		// IE 8 may report version as IE 7.0
		// regard as unreliable...
		bs = bs.substring(start);
		var regExp = /\d+[.]\d*/g;
		var vers = regExp.exec(bs);
		if (vers != null) {
			bversion = parseFloat(vers[0]);
		}

	}

	/* For future use if desired...

	function browserGetCookie(cookieName) {
			var value = null;
			if (document.cookie.length > 0) {
					var index = document.cookie.indexOf(cookieName + "=");
					if (index >= 0) {
							var start = index + cookieName.length + 1;
							var end = document.cookie.indexOf(";", start);
							if (end == -1) end = document.cookie.length;
							value = document.cookie.substring(start, end);
					}
			}
			return value;
	}

	function browserSetCookie(cookieName, cookieValue, daysToLive) {
			document.cookie = cookieName + "=" + cookieValue + ";";
			var expDate = null;
			if (daysToLive != undefined && daysToLive != null) {
					expDate = new Date();
					expDate.setDate(expDate.getDate() + daysToLive);
					document.cookie += "expires=" + expDate;
			}
	}

	*/

	//
	// End Private Implementation
	///////////////////////////////////////////////////////////////////////

}


/**
* Binds a callback function to a specific event fired by a specific element
* using (in order of preference) W3C DOM Level 2 binding, IE event binding
* or event property binding.
*
* If event property binding must be used, the previously registered event
* handler, if any, will be replaced.
*
* Note that callbacks should not use 'this' keyword, as the meaning of 'this'
* is not stable across browsers in the callback context. When callbacks are
* within objects, try defining 'self' as a private variable within the object
* and setting 'self' equal to 'this' during object construction. Then construct
* callbacks as closures that have access to 'self'.
*
* Note that the only parameter that may (or may not) be passed to a callback is
* an event. This is not dependable across browsers, so wrap your event
* parameter with EventWrapper. Also, avoid 'event' as a parameter name, as this
* may conflict with IE's use of 'event'.
*
* This method may not be used until after the page has loaded.
*
* Written by Michael Volk (mvolk@volksys.com) on August 3, 2010 for the
* benefit of Northwest Media, Inc.
*
* @param evt the name of the event, case-insensitive, with or without "on" prefix.
* @param obj the element to which to bind the event handler.
* @param fn  the function to call when the event is triggered; an event object
*            may be passed an argument, but should be handled with Browser to
*            avoid cross-browser variations in the nature of that event object.
* @param cap optional; true to fire on capture phase if possible, false to fire
*            on bubbling phase; default is false. Events bubble in IE regardless
*            of the value provided.
* @return    true if probability of success is high, false otherwise.
*/
Browser.addEvent = function(evt, obj, fn, cap) {
	var r = true;
	var n = evt.toLowerCase();
	if (n.indexOf('on') == 0) n = n.substring(2);
	if (obj.addEventListener) {	 				// look for W3C event binding model
		cap = ( cap === true );
		obj.addEventListener(n, fn, cap);
	} else if (obj.attachEvent) {				// look for IE event binding model
		r = obj.attachEvent('on'+n, fn);
	} else {									// fallback to event property model
		obj['on'+n] = fn;
		r = false;
	}
	return r;
}


/**
* Unbinds a callback function from a specific event fired by a specific element
* using (in order of preference) W3C DOM Level 2 binding, IE event binding
* or event property binding.
*
* This method may not be used until after the page has loaded.
*
* Written by Michael Volk (mvolk@volksys.com) on August 3, 2010 for the
* benefit of Northwest Media, Inc.
*
* @param evt the name of the event, case-insensitive, with or without "on" prefix.
* @param obj the element from which to unbind the event handler.
* @param fn  the callback function to unbind.
* @param cap use same value provided to addEvent when this event was bound.
* @return    true if probability of success is high, false otherwise.
*/
Browser.removeEvent = function(evt, obj, fn, cap) {
	var r = true;
	var n = evt.toLowerCase();
	if (n.indexOf('on') == 0) n = n.substring(2);
	if (obj.removeEventListener) {	 			// look for W3C event binding model
		cap = ( cap === true );
		obj.removeEventListener(n, fn, cap);
	} else if (obj.detachEvent) {				// look for IE event binding model
		r = obj.detachEvent('on'+n, fn);
	} else {									// fallback to event property model
		if (obj['on'+n] === fn) {
			obj['on'+n] = null;
		} else {
			r = false;
		}
	}
	return r;
}


/**
* Returns a reference to the document object of the given window
* or iframe element object. Minimally recursive in some cases.
*
* Written by Michael Volk (mvolk@volksys.com) on August 3, 2010 for the
* benefit of Northwest Media, Inc.
*
* @param obj an iframe, frame, or window object reference.
* @return a document reference if possible or null otherwise.
*/
Browser.getDocument = function(obj) {
	var doc = null;
	if (obj) {
		if (obj.document) {
			doc = obj.document;
		} else if (obj.contentDocument) {
			doc = obj.contentDocument;
		} else if (obj.contentWindow) {
			doc = getDocument(obj.contentWindow);
		}
	}
	return doc;
}

/**
* Prevent selection of content of an element.
*/
Browser.disableSelection = function(element) {
	if ((typeof element == 'undefined') || element == null) return;
	element.onselectstart = function() { return false; };
	element.unselectable = "on";
	element.style.MozUserSelect = "none";
}

function ElementPosition(node) {
	this.top    = getOffsetTop(node, 0);	// deprecated - not universally reliable
	this.left   = getOffsetLeft(node, 0);	// deprecated - not universally reliable
	this.height = node.offsetHeight;
	this.width  = node.offsetWidth;

	/**
	* Recursively add up the offsetTop of all offsetParents.
	* WARNING: not universally reliable.
	*/
	function getOffsetTop(node, top) {
		if (!top) top = 0;
		top += node.offsetTop;
		if (node.offsetParent.tagName == "BODY") return top;
		else return getOffsetTop(node.offsetParent, top);
	}

	/**
	* Recursively add up the offsetLeft of all offsetParents.
	* WARNING: not universally reliable.
	*/
	function getOffsetLeft(node, left) {
		if (!left) left = 0;
		left += node.offsetLeft;
		if (node.offsetParent.tagName == "BODY") return left;
		else return getOffsetLeft(node.offsetParent, left);
	}

}

