// Browser abstraction

function BrowserAbstractionFailed(sMessage)
{
	alert('[' + sMessage + '] Your browser does not meet the requirements to implement our service (navigator.appName=' + navigator.appName + ', navigator.appVersion=' + navigator.appVersion + ')');
}

// ----------------
//  iPhone Url Bar
// ----------------

function IPhoneUrlBarScroll()
{
	window.scrollTo(0, 0);
}

function IPhoneUrlBarTrick()
{
	window.setTimeout(IPhoneUrlBarScroll, 0);
	window.setTimeout(IPhoneUrlBarScroll, 1000);
}

// --------
//  Events
// --------

// You can attach an event in any browser with ctrl.onevent = callback
// But you can only attach one callback this way.
// If you need to attach more than one callback to an event, use the following
function BrowserAttachEvent(ctrl, event, callback)
{
	if (ctrl.addEventListener) //FireFox
	{
		ctrl.addEventListener(event, callback, false);
	}
	else if (ctrl.attachEvent) //IE
	{
		ctrl.attachEvent('on' + event, callback);
	}
	else
	{
		BrowserAbstractionFailed('BrowserAttachEvent');
	}
}

function BrowserDetachEvent(ctrl, event, callback)
{
	if (ctrl.removeEventListener) //FireFox
	{
		ctrl.removeEventListener(event, callback, false);
	}
	else if (ctrl.detachEvent) //IE
	{
		ctrl.detachEvent('on' + event, callback);
	}
	else
	{
		BrowserAbstractionFailed('BrowserDetachEvent');
	}
}

function BrowserEvent(e)
{
	if (window.event) // IE
	{
		return window.event;
	}
	else
	{
		return e;
	}
}

// ------
//  DOM
// ------

// @THB 02/2010 MSXML best match on IE (version 6 to 8)
// From experimenting and reading docs:
// - It is generally advised NOT to use MSXML version 5.0 and 4.0
// - 'MSXML2.DOMDocument.3.0', 'MSXML2.DOMDocument', 'Microsoft.XMLDOM' all return version 3.0 at best
// - window.XMLHttpRequest also returns MSXML2.XMLHTTP.3.0 at best, so it's compatible with the first 3 XML DOMDocument PROGIDs above
// - If you really want to use MSXML2.DOMDocument.6.0, then you MUST use MSXML2.XMLHTTP.6.0, NOT window.XMLHttpRequest which will be incompatible (because it's version 3.0)
// CONCLUSION: We skip MSXML2.DOMDocument.6.0 and we stick to window.XMLHttpRequest whenever possible in IE
// NO SO FAST: We've changed our mind ! It would seem MSXML2.XMLHTTP.3.0 has gone to hell on IE 6 and 7,
// so we reinstate MSXML2.XMLHTTP.6.0 and MSXML2.DOMDocument.6.0 ! And we pray it remains stable !
var MSXML_XMLHTTP_PROGIDS = new Array('MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP');
var MSXML_XMLDOM_PROGIDS = new Array('MSXML2.DOMDocument.6.0', 'MSXML2.DOMDocument.3.0', 'MSXML2.DOMDocument', 'Microsoft.XMLDOM');
// Cache the best MSXML match when found
var g_MSXML_XMLHTTP_PROGID = null;
var g_MSXML_XMLDOM_PROGID = null;

function ieCreateXmlDoc()
{
	var xmlDoc = null;

	if (g_MSXML_XMLDOM_PROGID != null)
	{
		xmlDoc = new window.ActiveXObject(g_MSXML_XMLDOM_PROGID);
	}
	else
	{
		// Look for the best Msxml2.DOMDocument version
		for (var i = 0; i < MSXML_XMLDOM_PROGIDS.length; i++)
		{
			try
			{
				xmlDoc = new window.ActiveXObject(MSXML_XMLDOM_PROGIDS[i]);
				g_MSXML_XMLDOM_PROGID = MSXML_XMLDOM_PROGIDS[i];
				break;
			}
			catch (exception)
			{
			}
		}
	}
	if (xmlDoc == null)
	{
		BrowserAbstractionFailed('No supported MSXML version');
	}

	return xmlDoc;
}

function ieCreateXmlHttp()
{
	var xmlHttp = null;

	if (g_MSXML_XMLHTTP_PROGID != null)
	{
		xmlHttp = new window.ActiveXObject(g_MSXML_XMLHTTP_PROGID);
	}
	else
	{
		for (var i = 0; i < MSXML_XMLHTTP_PROGIDS.length; i++)
		{
			try
			{
				xmlHttp = new window.ActiveXObject(MSXML_XMLHTTP_PROGIDS[i]);
				g_MSXML_XMLHTTP_PROGID = MSXML_XMLHTTP_PROGIDS[i];
				break;
			}
			catch (exception)
			{
			}
		}
	}
	if (xmlHttp == null)
	{
		BrowserAbstractionFailed('No supported MSXML version');
	}

	return xmlHttp;
}

function BrowserCreateXMLHttpObj()
{
	var xmlHttp = null;

	if (window.ActiveXObject != undefined) //IE
	{
		xmlHttp = ieCreateXmlHttp();
	}
	else if (window.XMLHttpRequest != undefined)
	{
		xmlHttp = new window.XMLHttpRequest();
		if (xmlHttp == null)
		{
			BrowserAbstractionFailed('XMLHttpRequest');
		}
	}
	else
	{
		BrowserAbstractionFailed('BrowserCreateXMLHttpObj');
	}

	return xmlHttp;
}

function BrowserCreateXmlDoc()
{
	//alert('BrowserCreateXmlDoc');
	var xmlDoc = null;

	if (window.ActiveXObject != undefined) //IE
	{
		xmlDoc = ieCreateXmlDoc();
	}
	else if (document.implementation != undefined && document.implementation.createDocument != undefined) //Firefox
	{
		try
		{
			// The standard say that default (null) parameters should produce an XML Document
			//xmlDoc = document.implementation.createDocument('http://www.w3.org/XML/1998/namespace', 'xml', null);
			xmlDoc = document.implementation.createDocument(null, null, null);
		}
		catch (exception)
		{
			BrowserAbstractionFailed('BrowserCreateXmlDoc document.implementation.createDocument exception=' + exception + ' exception.code=' + exception.code + ' exception.message=' + exception.message);
		}
	}
	else
	{
		BrowserAbstractionFailed('BrowserCreateXmlDoc');
	}

	if (xmlDoc != null)
	{
		xmlDoc.async = false;
	}

	return xmlDoc;
}

function BrowserCreateXmlDocFromNode(xmlNode)
{
	var xmlDoc = BrowserCreateXmlDoc();

	if (xmlDoc != null)
	{
		if (window.ActiveXObject != undefined) //IE
		{
			xmlDoc.documentElement = xmlNode.cloneNode(true);
		}
		else if (document.implementation != undefined && document.implementation.createDocument != undefined) //Firefox
		{
			try
			{
				var xmlNewNode = xmlDoc.importNode(xmlNode, true);
				if (xmlDoc.documentElement)
				{
					xmlDoc.replaceChild(xmlNewNode, xmlDoc.documentElement);
				}
				else
				{
					xmlDoc.appendChild(xmlNewNode);
				}
			}
			catch (exception)
			{
				BrowserAbstractionFailed('BrowserCreateXmlDocFromNode exception=' + exception + ' exception.code=' + exception.code + ' exception.message=' + exception.message);
			}
		}
		else
		{
			BrowserAbstractionFailed('BrowserCreateXmlDocFromNode');
		}
	}

	return xmlDoc;
}

function BrowserParseXmlDoc(xmlString)
{
	var xmlDoc = null;

	if (window.ActiveXObject != undefined) //IE
	{
		xmlDoc = ieCreateXmlDoc();

		try
		{
			xmlDoc.async = false;
			xmlDoc.loadXML(xmlString);
		}
		catch (exception)
		{
			alert('Microsoft.XMLDOM.loadXML failed ' + exception);
			xmlDoc = null;
		}
	}
	else if (document.implementation != undefined && document.implementation.createDocument != undefined && window.DOMParser != undefined) //Firefox
	{
		try
		{
			xmlDoc = (new window.DOMParser()).parseFromString(xmlString, 'text/xml');
		}
		catch (exception)
		{
			alert('DOMParser.parseFromString failed ' + exception);
		}
	}
	else
	{
		BrowserAbstractionFailed('BrowserParseXmlDoc');
	}

	return xmlDoc;
}

function BrowserAppendXMLChild(xmlNode, xmlChild)
{
	if (window.ActiveXObject != undefined) //IE
	{
		xmlNode.appendChild(xmlChild.cloneNode(true));
	}
	else if (document.implementation != undefined && document.implementation.createDocument != undefined) //Firefox
	{
		xmlNode.appendChild(xmlNode.ownerDocument.importNode(xmlChild, true));
	}
	else
	{
		BrowserAbstractionFailed('BrowserAppendXMLChild');
	}
}

function BrowserReplaceXMLChild(xmlNode, xmlOldNode)
{
	if (window.ActiveXObject != undefined) //IE
	{
		xmlOldNode.parentNode.replaceChild(xmlNode.cloneNode(true), xmlOldNode);
	}
	else if (document.implementation != undefined && document.implementation.createDocument != undefined) //Firefox
	{
		xmlOldNode.parentNode.replaceChild(xmlNode.ownerDocument.importNode(xmlNode, true), xmlOldNode);
	}
	else
	{
		BrowserAbstractionFailed('BrowserReplaceXMLChild');
	}
}

var oSerializer = null;

function BrowserGetXMLString(xmlNode)
{
	var sXml = '';

	if (window.ActiveXObject != undefined) //IE
	{
		sXml = xmlNode.xml;
	}
	else if (document.implementation != undefined && document.implementation.createDocument != undefined && window.XMLSerializer != undefined) //Firefox
	{
		try
		{
			// @THB 04/2009 We should not need to allocate one every time
			if (oSerializer == null)
				oSerializer = new window.XMLSerializer();
			sXml = oSerializer.serializeToString(xmlNode);
		}
		catch (exception)
		{
			alert('XMLSerializer.serializeToString failed ' + exception.message);
		}
	}
	else
	{
		BrowserAbstractionFailed('BrowserGetXMLString');
	}

	return sXml;
}

function BrowserGetXMLText(xmlNode)
{
	var sText = '';

	if (window.ActiveXObject != undefined) //IE
	{
		sText = xmlNode.text;
	}
	else if (document.implementation != undefined && document.implementation.createDocument != undefined) //Firefox
	{
		try
		{
			// Look for the best Msxml2.XMLHTTP version
			for (var i = 0; i < xmlNode.childNodes.length; i++)
			{
				if (xmlNode.childNodes[i].hasChildNodes())
				{
					sText += BrowserGetXMLText(xmlNode.childNodes[i]);
				}
				else
				{
					sText = xmlNode.childNodes[i].nodeValue;
				}
			}
		}
		catch (exception)
		{
			alert('BrowserGetXMLText ' + exception.message);
		}
	}
	else
	{
		BrowserAbstractionFailed('BrowserGetXMLText');
	}

	return sText;
}

function BrowserGetXmlDoc(xmlHttp)
{
	var xmlDoc = null;

	if (xmlHttp.responseXML != null)
	{
		xmlDoc = xmlHttp.responseXML;
	}
	else
	{
		// @THB 04/2009 We should not need a Microsoft.XMLDOM
		// Microsoft.XMLHTTP already returns a fully parsed document, as long as the server sends a well formed one ! (for example with ContentType="application/xml")
		//if (window.ActiveXObject != undefined) //IE
		//{
		//	var xmlDocument2 = new window.ActiveXObject('Microsoft.XMLDOM');
		//	try
		//	{
		//		xmlDocument2.loadXML(xmlHttp.responseText);
		//		return xmlDocument2;
		//	}
		//	catch (exception)
		//	{
		//		alert('Microsoft.XMLDOM.loadXML failed ' + exception);
		//		return null;
		//	}
		//}
		// @THB 04/2009 We should not need a DOMParser
		// XMLHttpRequest already returns a fully parsed document, as long as the server sends a well formed one ! (for example with ContentType="application/xml")
		// Also DOMParser is not available on some browser (BlackBerry)
		//else if (window.DOMParser != undefined) //FireFox
		//{
		//	xmlDocument2 = (new window.DOMParser()).parseFromString(xmlString, 'text/xml');
		//}
		//else
		//{
		//	BrowserAbstractionFailed('BrowserGetXmlDoc');
		//}
	}

	return xmlDoc;
}

var _BrowserXSLSupported = (window.ActiveXObject != undefined || (document.implementation != undefined && document.implementation.createDocument != undefined && window.XSLTProcessor != undefined));

function BrowserLoadXSLTransform(url)
{
	var xslProc = null;

	if (window.ActiveXObject != undefined) //IE
	{
		// In IE the XSL Document and the XLS Processor are the same object
		xslProc = ieCreateXmlDoc();
		
		try
		{
			xslProc.async = false;
			xslProc.load(url);
		}
		catch (exception)
		{
			alert('Microsoft.XSLTemplate.load failed ' + exception);
			xslProc = null;
		}
	}
	else if (document.implementation != undefined && document.implementation.createDocument != undefined && window.XSLTProcessor != undefined) //Firefox
	{
		try
		{
			// In Firefox the XSL Document must be compiled into an XLS Processor
			//alert('BrowserLoadXSLTransform url= ' + url);
			xslProc = new window.XSLTProcessor();
			try
			{
				var xmlhttp = BrowserCreateXMLHttpObj();
				xmlhttp.open('GET', url, false);
				// Server side modification already handled by timestamp in URL
				// Inappropriate use of 'If-Modified-Since' in application code :
				// - 'If-Modified-Since' requires to handle 'HTTP Error 304 - Not modified'
				// - 'If-Modified-Since' would only make sense within some sort of application level caching
				// And it was crashing IIS 7.5, LOL !
				//xmlhttp.setRequestHeader('If-Modified-Since', 'infinity');
				xmlhttp.send(null);
				// xmlhttp.responseXML is a DOMDocument, not a string !
				//if (xmlhttp.responseXML != null && xmlhttp.responseXML != '')
				if (xmlhttp.responseXML != null)
				{
					xslProc.importStylesheet(xmlhttp.responseXML);
				}
				else if (xmlhttp.responseText != null && xmlhttp.responseText != '')
				{
					//alert('Are you sure your XSL template is well formed ? ' + url);
					xslProc.importStylesheet(xmlhttp.responseText);
				}
				else
				{
					alert('XSL Request returned NULL document: ' + xmlhttp.status + ' ' + xmlhttp.statusText + ' ' + xmlhttp.responseText);
					xslProc = null;
				}
			}
			catch (exception)
			{
				alert('XSL Request failed ' + exception + ' xmlhttp.responseText= ' + xmlhttp.responseText);
				xslProc = null;
			}
		}
		catch (exception)
		{
			BrowserAbstractionFailed('BrowserLoadXSLTransform XSLTProcessor ' + exception);
		}
	}
	else
	{
		// What else can we do ?
		BrowserAbstractionFailed('BrowserLoadXSLTransform');
	}

	return xslProc;
}

function BrowserTransformDoc(xmlDoc, xslProc)
{
	var sResult = '';

	if (window.ActiveXObject != undefined) //IE
	{
		try
		{
			sResult = xmlDoc.transformNode(xslProc);
		}
		catch (exception)
		{
			alert('transformNode failed ' + exception.message);
		}
		if (sResult == null || sResult == '')
		{
			var exception = new Error();
			exception.name = 'BrowserTransformFailed';
			exception.message = 'transformNode returned an empty result';
			throw exception;
		}
	}
	else if (document.implementation != undefined && document.implementation.createDocument != undefined && window.XSLTProcessor != undefined) //Firefox
	{
		try
		{
			// @THB 06/2009 for some reason xslProc.transformToDocument(xmlDoc) fails on iPhone Safari if the user clics on the screen
			// The work around is to use transformToFragment
			var htmlFragment = xslProc.transformToFragment(xmlDoc, document);
			sResult = BrowserGetXMLString(htmlFragment);
		}
		catch (exception)
		{
			alert('transformToFragment failed ' + exception.message);
		}
		if (sResult == null || sResult == '')
		{
			var exception = new Error();
			exception.name = 'BrowserTransformFailed';
			exception.message = 'transformToFragment returned an empty result';
			throw exception;
		}
	}
	else
	{
		// What else can we do ?
		BrowserAbstractionFailed('BrowserTransformDoc');
	}

	return sResult;
}

function BrowserInsertContent(objectToUpdate, newHTML)
{
	try
	{
		if (window.ActiveXObject != undefined) // IE executes script defer when inserted
		{
			objectToUpdate.innerHTML = newHTML;
		}
		else // FireFox does not execute script defer when inserted
		{
			var tTabCommandeEval = ParseContentScripts(newHTML);

			// On execute le code HTML + les scripts
			objectToUpdate.innerHTML = newHTML;

			EvalContentScripts(tTabCommandeEval);
		}
	}
	catch (exception)
	{
		alert('BrowserInsertContent ' + exception + ' HTML=' + newHTML);
	}
}

function BrowserReplaceContent(objectToUpdate, newHTML)
{
	try
	{
		if (window.ActiveXObject != undefined) // IE know outerHTML and executes script defer when inserted
		{
			objectToUpdate.outerHTML = newHTML;
		}
		else // FireFox does not know outerHTML and does not execute script defer when inserted
		{
			var tTabCommandeEval = ParseContentScripts(newHTML);

			// We can't just use DOM replaceChild, because we may be replacing ONE node with MULTIPLE nodes
			// So we must make MULTIPLE insertBefore and ONE removeChild
			var tmp = document.createElement(objectToUpdate.parentNode.tagName);
			tmp.innerHTML = newHTML;
			for (var i = 0; i < tmp.childNodes.length; i++)
			{
				objectToUpdate.parentNode.insertBefore(document.importNode(tmp.childNodes[i], true), objectToUpdate);
			}
			objectToUpdate.parentNode.removeChild(objectToUpdate);

			EvalContentScripts(tTabCommandeEval);
		}
	}
	catch (exception)
	{
		alert('BrowserReplaceContent ' + exception + ' HTML=' + newHTML);
	}
}

function ParseContentScripts(newHTML)
{
	var sBeginToken = '<script defer>';
	var sEndToken = '</script>';

	var tTabCommandeEval = new Array();
	var nIndexBeginToken = newHTML.indexOf(sBeginToken);
	var nIndexEndToken = newHTML.indexOf(sEndToken);

	while ((nIndexBeginToken != -1) && (nIndexEndToken != -1)) // si on trouve des <script defer></script> dans newHTML
	{
		// We build an array of scripts to run after pasting HTML
		var result = newHTML.substr(nIndexBeginToken + sBeginToken.length, nIndexEndToken - (nIndexBeginToken + sBeginToken.length));

		tTabCommandeEval.push(result);
		newHTML = newHTML.substr(0, nIndexBeginToken) + newHTML.substr(nIndexEndToken + sEndToken.length, newHTML.length - (nIndexEndToken + sEndToken.length));

		nIndexBeginToken = newHTML.indexOf(sBeginToken);
		nIndexEndToken = newHTML.indexOf(sEndToken);
	}

	return tTabCommandeEval;
}

function EvalContentScripts(tTabCommandeEval)
{
	for (i = 0; i < tTabCommandeEval.length; i++)
	{
		try
		{
			// eval can easily fail (javascript syntax error, etc)
			eval(tTabCommandeEval[i]);
		}
		catch (exception)
		{
			alert('eval ' + exception + ' script=' + tTabCommandeEval[i]);
		}
	}
}

// Some debug helper to test mobile device screen viewport/rotation
var _timeoutSizeDebug = null;

function hideSizeDebug(pevent)
{
	var sizeDebug = document.getElementById('sizeDebug');
	if (sizeDebug != null)
	{
		sizeDebug.className = 'sizeDebugDiv';
	}
}

function showSizeDebug(pevent)
{
	//alert('showSizeDebug pevent = ' + pevent + ' window.event = ' + window.event);
	if (_timeoutSizeDebug != null)
	{
		window.clearTimeout(_timeoutSizeDebug);
		_timeoutSizeDebug = null;
	}

	var sSize = '';
	
	try
	{
		sSize += 'document.body.offset<br />top = ' + document.body.offsetTop + ' left = ' + document.body.offsetLeft + ' width = ' + document.body.offsetWidth + ' height = ' + document.body.offsetHeight + '<br />';
		sSize += 'document.body.scroll<br />top = ' + document.body.scrollTop + ' left = ' + document.body.scrollLeft + ' width = ' + document.body.scrollWidth + ' height = ' + document.body.scrollHeight + '<br />';
		sSize += 'document.forms[0].offset<br />top = ' + document.forms[0].offsetTop + ' left = ' + document.forms[0].offsetLeft + ' width = ' + document.forms[0].offsetWidth + ' height = ' + document.forms[0].offsetHeight + '<br />';
		sSize += 'document.forms[0].scroll<br />top = ' + document.forms[0].scrollTop + ' left = ' + document.forms[0].scrollLeft + ' width = ' + document.forms[0].scrollWidth + ' height = ' + document.forms[0].scrollHeight + '<br />';
		sSize += 'window.orientation = ' + window.orientation + '<br />';
	}
	catch (exception)
	{
		sSize += exception.message;
	}

	var sizeDebug = document.getElementById('sizeDebug');
	//alert(sizeDebug);
	if (sizeDebug != null)
	{
		sizeDebug.innerHTML = sSize
		sizeDebug.className = 'sizeDebugDivVisible';
		//_timeoutSizeDebug = window.setTimeout(hideSizeDebug, 3000);
	}
}

function registerSizeDebug()
{
	window.onscroll = window.showSizeDebug;
	window.onresize = window.showSizeDebug;
	window.onorientationchange = window.showSizeDebug;
	showSizeDebug();
}

function BrowserReportFeatures()
{
	var sBrowser = '';

	try
	{
		sBrowser += ('navigator.userAgent: <b>' + navigator.userAgent + '</b><br/>');
		sBrowser += ('navigator.appCodeName: <b>' + navigator.appCodeName + '</b><br/>');
		sBrowser += ('navigator.appName: <b>' + navigator.appName + '</b><br/>');
		sBrowser += ('navigator.appVersion: <b>' + navigator.appVersion + '</b><br/>');

		sBrowser += ('window.ActiveXObject: <b>' + window.ActiveXObject + '</b><br/>');

		if (window.ActiveXObject != undefined)
		{
			for (var i = 0; i < MSXML_XMLDOM_PROGIDS.length; i++)
			{
				try
				{
					var xmldoc = new ActiveXObject(MSXML_XMLDOM_PROGIDS[i]);
					sBrowser += ('ActiveXObject ' + MSXML_XMLDOM_PROGIDS[i] + ': <b>' + (xmldoc != null) + '</b><br/>');
				}
				catch (exception)
				{
					sBrowser += ('ActiveXObject ' + MSXML_XMLDOM_PROGIDS[i] + ': <b>' + exception.message + '</b><br/>');
				}
			}

			for (var i = 0; i < MSXML_XMLHTTP_PROGIDS.length; i++)
			{
				try
				{
					var xmlHttp = new ActiveXObject(MSXML_XMLHTTP_PROGIDS[i]);
					sBrowser += ('ActiveXObject ' + MSXML_XMLHTTP_PROGIDS[i] + ': <b>' + (xmlHttp != null) + '</b><br/>');
				}
				catch (exception)
				{
					sBrowser += ('ActiveXObject ' + MSXML_XMLHTTP_PROGIDS[i] + ': <b>' + exception.message + '</b><br/>');
				}
			}
		}

		sBrowser += ('window.XMLHttpRequest: <b>' + window.XMLHttpRequest + '</b><br/>');
		sBrowser += ('window.DOMParser: <b>' + window.DOMParser + '</b><br/>');
		sBrowser += ('window.XMLSerializer: <b>' + window.XMLSerializer + '</b><br/>');
		sBrowser += ('window.XSLTProcessor: <b>' + window.XSLTProcessor + '</b><br/>');

		if (document.implementation != undefined)
		{
			sBrowser += ('document.implementation: <b>' + document.implementation + '</b><br/>');
			sBrowser += ('document.implementation.createDocument: <b>' + document.implementation.createDocument + '</b><br/>');
			if (document.implementation.hasFeature != undefined)
			{
				sBrowser += ('hasFeature HTML: <b>' + document.implementation.hasFeature('HTML', null) + '</b><br/>');
				sBrowser += ('hasFeature HTML "1.0": <b>' + document.implementation.hasFeature('HTML', '1.0') + '</b><br/>');
				sBrowser += ('hasFeature HTML "2.0": <b>' + document.implementation.hasFeature('HTML', '2.0') + '</b><br/>');
				sBrowser += ('hasFeature XML: <b>' + document.implementation.hasFeature('XML', null) + '</b><br/>');
				sBrowser += ('hasFeature XML "1.0": <b>' + document.implementation.hasFeature('XML', '1.0') + '</b><br/>');
				sBrowser += ('hasFeature XML "2.0": <b>' + document.implementation.hasFeature('XML', '2.0') + '</b><br/>');
				sBrowser += ('hasFeature StyleSheets: <b>' + document.implementation.hasFeature('StyleSheets', null) + '</b><br/>');
				sBrowser += ('hasFeature StyleSheets "2.0": <b>' + document.implementation.hasFeature('StyleSheets', '2.0') + '</b><br/>');
				sBrowser += ('hasFeature CSS: <b>' + document.implementation.hasFeature('CSS', null) + '</b><br/>');
				sBrowser += ('hasFeature CSS "2.0": <b>' + document.implementation.hasFeature('CSS', '2.0') + '</b><br/>');
				sBrowser += ('hasFeature CSS2: <b>' + document.implementation.hasFeature('CSS2', null) + '</b><br/>');
				sBrowser += ('hasFeature CSS2 "2.0": <b>' + document.implementation.hasFeature('CSS2', '2.0') + '</b><br/>');
				sBrowser += ('hasFeature XPath: <b>' + document.implementation.hasFeature('XPath', null) + '</b><br/>');
			}
			else
			{
				sBrowser += ('document.implementation.hasFeature: <b>undefined</b><br/>');
			}
			//var doc = BrowserCreateXmlDoc();
			//sBrowser += ('document.implementation.createDocument: <b>' + doc + '</b><br/>');
		}
		else
		{
			sBrowser += ('document.implementation: <b>undefined</b><br/>');
		}
	}
	catch (exception)
	{
		sBrowser += ('exception: <b>' + exception + '</b><br/>');
	}

	return sBrowser;
}