// ie constants function Constants() { } // all HTML events. // Constants.abort = "abort"; // Constants.activate = "activate"; // Constants.afterprint = "afterprint"; // Constants.afterupdate = "afterupdate"; // Constants.beforeactivate = "beforeactivate"; // Constants.beforecopy = "beforecopy"; // Constants.beforecut = "beforecut"; // Constants.beforedeactivate = "beforedeactivate"; // Constants.beforeeditfocus = "beforeeditfocus"; // Constants.beforepaste = "beforepaste"; // Constants.beforeprint = "beforeprint"; // Constants.beforeunload = "beforeunload"; // Constants.beforeupdate = "beforeupdate"; // Constants.blur = "blur"; // Constants.bounce = "bounce"; // Constants.cellchange = "cellchange"; // Constants.change = "change"; Constants.click = "click"; // Constants.contextmenu = "contextmenu"; // Constants.controlselect = "controlselect"; // Constants.copy = "copy"; // Constants.cut = "cut"; // Constants.dataavailable = "dataavailable"; // Constants.datasetchanged = "datasetchanged"; // Constants.datasetcomplete = "datasetcomplete"; // Constants.dblclick = "dblclick"; // Constants.deactivate = "deactivate"; // Constants.drag = "drag"; // Constants.dragend = "dragend"; // Constants.dragenter = "dragenter"; // Constants.dragleave = "dragleave"; // Constants.dragover = "dragover"; // Constants.dragstart = "dragstart"; // Constants.drop = "drop"; // Constants.error = "error"; // Constants.errorupdate = "errorupdate"; // Constants.filterchange = "filterchange"; // Constants.finish = "finish"; // Constants.focus = "focus"; // Constants.focusin = "focusin"; // Constants.focusout = "focusout"; // Constants.help = "help"; Constants.keyDown = "keyDown"; Constants.keyPress = "keyPress"; Constants.keyUp = "keyUp"; // Constants.layoutcomplete = "layoutcomplete"; // Constants.load = "load"; // Constants.losecapture = "losecapture"; Constants.mouseDown = "mouseDown"; // Constants.mouseenter = "mouseenter"; // Constants.mouseleave = "mouseleave"; Constants.mouseMove = "mouseMove"; // Constants.mouseout = "mouseout"; // Constants.mouseover = "mouseover"; Constants.mouseUp = "mouseUp"; // Constants.mousewheel = "mousewheel"; // Constants.move = "move"; // Constants.moveend = "moveend"; // Constants.movestart = "movestart"; // Constants.paste = "paste"; // Constants.propertychange = "propertychange"; // Constants.readystatechange = "readystatechange"; // Constants.reset = "reset"; // Constants.resize = "resize"; // Constants.resizeend = "resizeend"; // Constants.resizestart = "resizestart"; // Constants.rowenter = "rowenter"; // Constants.rowexit = "rowexit"; // Constants.rowsdelete = "rowsdelete"; // Constants.rowsinserted = "rowsinserted"; // Constants.scroll = "scroll"; // Constants.select = "select"; // Constants.selectionchange = "selectionchange"; // Constants.selectstart = "selectstart"; // Constants.start = "start"; // Constants.stop = "stop"; // Constants.submit = "submit"; // Constants.unload = "unload"; Constants.check = "check"; Constants.close = "close"; Constants.goBack = "goBack"; Constants.open = "open"; Constants.refresh = "refresh"; Constants.select = "select"; Constants.type = "type"; Constants.uncheck = "uncheck"; Constants.waitForPageToLoad = "waitForPageToLoad"; Constants.waitForPopUp = "waitForPopUp"; Constants.waitForPopUpClose = "waitForPopUpClose"; Constants.sleep = "sleep"; Constants.onabort = "onabort"; Constants.onactivate = "onactivate"; Constants.onafterprint = "onafterprint"; Constants.onafterupdate = "onafterupdate"; Constants.onbeforeactivate = "onbeforeactivate"; Constants.onbeforecopy = "onbeforecopy"; Constants.onbeforecut = "onbeforecut"; Constants.onbeforedeactivate = "onbeforedeactivate"; Constants.onbeforeeditfocus = "onbeforeeditfocus"; Constants.onbeforepaste = "onbeforepaste"; Constants.onbeforeprint = "onbeforeprint"; Constants.onbeforeunload = "onbeforeunload"; Constants.onbeforeupdate = "onbeforeupdate"; Constants.onblur = "onblur"; Constants.onbounce = "onbounce"; Constants.oncellchange = "oncellchange"; Constants.onchange = "onchange"; Constants.onclick = "onclick"; Constants.oncontextmenu = "oncontextmenu"; Constants.oncontrolselect = "oncontrolselect"; Constants.oncopy = "oncopy"; Constants.oncut = "oncut"; Constants.ondataavailable = "ondataavailable"; Constants.ondatasetchanged = "ondatasetchanged"; Constants.ondatasetcomplete = "ondatasetcomplete"; Constants.ondblclick = "ondblclick"; Constants.ondeactivate = "ondeactivate"; Constants.ondrag = "ondrag"; Constants.ondragend = "ondragend"; Constants.ondragenter = "ondragenter"; Constants.ondragleave = "ondragleave"; Constants.ondragover = "ondragover"; Constants.ondragstart = "ondragstart"; Constants.ondrop = "ondrop"; Constants.onerror = "onerror"; Constants.onerrorupdate = "onerrorupdate"; Constants.onfilterchange = "onfilterchange"; Constants.onfinish = "onfinish"; Constants.onfocus = "onfocus"; Constants.onfocusin = "onfocusin"; Constants.onfocusout = "onfocusout"; Constants.onhelp = "onhelp"; Constants.onkeydown = "onkeydown"; Constants.onkeypress = "onkeypress"; Constants.onkeyup = "onkeyup"; Constants.onlayoutcomplete = "onlayoutcomplete"; Constants.onload = "onload"; Constants.onlosecapture = "onlosecapture"; Constants.onmousedown = "onmousedown"; Constants.onmouseenter = "onmouseenter"; Constants.onmouseleave = "onmouseleave"; Constants.onmousemove = "onmousemove"; Constants.onmouseout = "onmouseout"; Constants.onmouseover = "onmouseover"; Constants.onmouseup = "onmouseup"; Constants.onmousewheel = "onmousewheel"; Constants.onmove = "onmove"; Constants.onmoveend = "onmoveend"; Constants.onmovestart = "onmovestart"; Constants.onpaste = "onpaste"; Constants.onpropertychange = "onpropertychange"; Constants.onreadystatechange = "onreadystatechange"; Constants.onreset = "onreset"; Constants.onresize = "onresize"; Constants.onresizeend = "onresizeend"; Constants.onresizestart = "onresizestart"; Constants.onrowenter = "onrowenter"; Constants.onrowexit = "onrowexit"; Constants.onrowsdelete = "onrowsdelete"; Constants.onrowsinserted = "onrowsinserted"; Constants.onscroll = "onscroll"; Constants.onselect = "onselect"; Constants.onselectionchange = "onselectionchange"; Constants.onselectstart = "onselectstart"; Constants.onstart = "onstart"; Constants.onstop = "onstop"; Constants.onsubmit = "onsubmit"; Constants.onunload = "onunload"; Constants.SCRIPT_REDIRECT = "script_redirect"; Constants.ONLOAD_SUBMIT = "onload_submit"; Constants.META_REDIRECT = "meta_redirect"; Constants.FRAME = "frame"; Constants.REQUEST_MODE = "requestMode"; // various states Constants.STOPPED = "stopped"; Constants.PLAYING = "playing"; Constants.RECORDING= "recording"; Constants.PAUSED = "paused"; Constants.INIT = "init"; Constants.RERECORDING="rerecording"; // playback methods Constants.METHOD_HTTP = "HTTP"; Constants.METHOD_DHTML= "DHTML"; Constants.SUC_NOT_FOUND = "SUC_NOT_FOUND"; Constants.FAIL_FOUND = "FAIL_FOUND"; Constants.BROWSER_ERROR = "BROWSER_ERROR"; Constants.DOM_ERROR = "DOM_ERROR"; Constants.GROUP = "GROUP"; Constants.CONTENT_TYPE_FORM_URLENCODED = "Content-Type: application/x-www-form-urlencoded"; Constants.CP_UTF8 = 65001; Constants.NON_SENSITIVE_VALUES = "nonsensitive_values"; Constants.SENSITIVE_VALUES = "sensitive_values"; Constants.WTI_NUM_CHARS = 8; /** * A set of basic HTML element related utilities. */ /** * @return true if the elem display css is none. */ function isHidden(elem) { return elem && elem.style ? elem.style.display == "none" : false; } /** * @return true if the elem display css is not none. */ function isVisible(elem) { return elem && elem.style ? elem.style.display != "none" : false; } /** * @return true if the elem is disabled. */ function isDisabled(elem) { return elem ? elem.disabled : false; } function isEnabled(elem) { return elem ? !elem.disabled : false; } function isReadOnly(elem) { return elem ? elem.readonly : false; } /** * makes the elem hidden. */ function hide(elem) { if (elem && elem.style) { elem.style.display = "none"; } } /** * makes the elem visible. */ function show(elem) { if (elem && elem.style) { elem.style.display = "inline"; } } /** * toggles the visible state of two elems, * so one is visible and one is invisible. */ function toggleVisible(elem1, elem2) { if (isVisible(elem2)) { // if both elements are visible, then // only show the first one. show(elem1); hide(elem2); } else { hide(elem1); show(elem2); } } /** * updates the text value of a button. */ /* function setValue(elem, value) { if (elem) { elem.value = value; } } */ /** * A string buffer implements a mutable sequence of characters. * Current implementation only allow string concatenation. */ function StringBuffer() { /** * buffer is stored as an array list of strings. * @private */ this.buffer=[]; } /** * Appends one or more strings to the buffer. */ StringBuffer.prototype.append = function() { for (var i =0; i < arguments.length; i++) { this.buffer[this.buffer.length]=arguments[i]; } } StringBuffer.prototype.copy = function(buf) { if (buf.buffer) { var buffer = buf.buffer; for (var i =0; i < buffer.length; i++) { this.buffer[this.buffer.length]=buffer[i]; } } else { throw ("Invalid input buffer object"); } } /** * Returns the concatenated string. * @tparam String delim an optional delimiter. * @treturn String the concatenated strings. */ StringBuffer.prototype.toString = function(delim) { return this.buffer.join(delim||''); } /** * Returns true if there are no strings appended. * @treturn bool */ StringBuffer.prototype.isEmpty = function() { return this.buffer.length == 0; } /* function getParent (elem) { if (elem && elem.parentElement) { return elem.parentElement; } else if (elem && elem.parentNodes) { return elem.parentNodes; } return null; } function getChildren (elem) { if (elem && elem.children) { return elem.children; } else if (elem && elem.childNodes) { return elem.childNodes; } return null; } */ function getFunctionName (obj, func, isShort) { var funcName = null; var prototype = false; if (obj) { for (var v in obj.prototype) { if (obj.prototype[v] == func) { funcName = v.toString(); prototype = true; break; } } if (!prototype) { for (var v in obj) { if (obj[v] == func) { funcName = v.toString(); break; } } } if (funcName != null) { if (typeof(obj) == "function") { var objStr = obj.toString(); var objNameStart = objStr.indexOf(" "); var objNameEnd = objStr.indexOf("("); if (objNameStart != -1 && objNameEnd != -1) { if (isShort) { return funcName; } else { return objStr.substring(objNameStart + 1, objNameEnd) + ".prototype." + funcName; } } } else { return funcName; } } } var funcBody = func.toString(); var funcNameStart = funcBody.indexOf(" "); var funcNameEnd = funcBody.indexOf("("); if (funcNameStart != -1 && funcNameEnd != -1) { return funcBody.substring(funcNameStart + 1, funcNameEnd); } else { return func.toString(); } } /* function setCookie(name, value, expiredays) { // Three variables are used to set the new cookie. // The name of the cookie, the value to be stored, // and finally the number of days until the cookie expires. // The first lines in the function convert // the number of days to a valid date. var ExpireDate = new Date (); ExpireDate.setTime(ExpireDate.getTime() + (expiredays * 24 * 3600 * 1000)); // The next line stores the cookie, simply by assigning // the values to the "document.cookie" object. // Note the date is converted to Greenwich Mean time using // the "toGMTstring()" function. document.cookie = name + "=" + escape(value) + ((expiredays == null) ? "" : "; expires=" + ExpireDate.toGMTString()); } function getCookie(name) { // First we check to see if there is a cookie stored. // Otherwise the length of document.cookie would be zero. if (document.cookie.length > 0) { // Second we check to see if the cookie's name is stored in the // "document.cookie" object for the page. // Since more than one cookie can be set on a // single page it is possible that our cookie // is not present, even though the "document.cookie" object // is not just an empty text. // If our cookie name is not present the value -1 is stored // in the variable called "begin". begin = document.cookie.indexOf(name+"="); if (begin != -1) // Note: != means "is not equal to" { // Our cookie was set. // The value stored in the cookie is returned from the function. begin += name.length+1; end = document.cookie.indexOf(";", begin); if (end == -1) end = document.cookie.length; return unescape(document.cookie.substring(begin, end)); } } return null; // Our cookie was not set. // The value "null" is returned from the function. } function persistElem(id) { var elem = getElementById(id); if (elem) { setCookie(id, elem.value, 100); } } */ /* function compose() { var funcs = []; for (var i = 0; i < arguments.length; i++) { funcs.unshift(arguments[i]); } return function(x) { var result = x; for (var i = 0; i < funcs.length; i++) { var func = funcs[i]; result = func(result); } return result; } } */ function foreach(arr, func) { if (!arr) return; if (arr.length != undefined) { for(var i = 0; i < arr.length; i++) { func(arr[i]); } } else { for(var p in arr) { func(p); } } } /* function repeat(times, func) { for (var i = 0; i < times; i++) { func(); } } function getElementById(x) { var elem = document.getElementById(x); if (!elem) { alert(x + " is not found"); } return elem; } function count(arr) { if (!arr) return 0; if (arr.length != undefined) { return arr.length; } else { var i = 0; for(var p in arr) { i++; } return i; } } */ /* function and(funcs) { if (funcs) { for (var i = 0; i < funcs.length; i++) { if (!funcs[i]()) { return false; } } } return true; } function or(funcs) { if (funcs) { for (var i = 0; i < funcs.length; i++) { if (funcs[i]()) { return true; } } } return false; } */ function escapeHTML(str) { str = str.replace(RegExp("&", "g"),'&'); str = str.replace(RegExp("<", "g"),'<'); str = str.replace(RegExp(">", "g"),'>'); return str; } function encodeNameValuePairDelimiters(str) { str = str.replace(RegExp("%", "g"), "%25"); str = str.replace(RegExp(",", "g"), "%2C"); str = str.replace(RegExp(";", "g"), "%3B"); // no need to encode '=', because in the // midtier, we are spliting on the first =. return str; } function merge1(array1, array2, func) { var result = []; var i1 = 0; var i2 = 0; var n1 = array1.length; var n2 = array2.length; while (i1 < n1 && i2 < n2) { var elem1 = array1[i1]; var elem2 = array2[i2]; var diff = func(elem1, elem2); if (diff <= 0) { // elem1 is smaller result.push(elem1); i1++; } else { // elem2 is smaller result.push(elem2); i2++; } } if (i1 < n1) { result = result.concat(array1.slice(i1)); } if (i2 < n2) { result = result.concat(array2.slice(i2)); } return result; } function merge2(array1, array2, func) { result = []; while (array1.length > 0 && array2.length > 0) { var diff = func(array1[0], array2[0]); if (diff <= 0) { result.push(array1.shift()); } else { result.push(array2.shift()); } } if (array1.length > 0) { return result.concat(array1); } if (array2.length > 0) { return result.concat(array2); } return result; } function handleException (module, func, e) { if (stderr && stderr.log) { stderr.log(getFunctionName(module, func) + " error: name=" + e.name + "\n message=" + e.message + "\n desc=" + e.description ); } } function handleStackTrace (module, func, msg) { var delta = ""; if (timing && timing.log && timing.getIsEnabled()) { // if timing is enabled, if (module._timing == null) { module._timing = {}; } var functionName = getFunctionName(module, func); var _lastTime = module._timing[functionName]; var _currTime = (new Date()).valueOf(); if (msg.indexOf(" start") == 0) { module._timing[functionName] = _currTime; } else if ( msg.indexOf(" end") == 0) { delta = " " + (_currTime - _lastTime); module._timing[functionName] = null; } } if (stackTrace && stackTrace.log && stackTrace.getIsEnabled()) { stackTrace.log(getFunctionName(module, func) + msg + delta); } } /* function delegate() { var length = arguments.length; if (length > 2) { var src = arguments[0]; var dst = arguments[1]; for (var i = 2; i < length; i++) { delegateFunc(src, dst, arguments[i]); } } } function delegateFunc(src, dst, name) { var dstFunc = dst[name]; var dstFuncLength = dstFunc.length; src[name] = function(args) { return dst[name](args); }; } */ function require(requiredFuncs, obj) { if (requiredFuncs) { for(var i = 0; i < requiredFuncs.length; i++) { var requiredFunc = obj[requiredFuncs[i]]; if (!requiredFunc || typeof(requiredFunc) != "function") { throw ("the object must support function " + requiredFuncs[i]); } } } } function createDOMDocument() { var objDOMDocument = null; // if (window.ActiveXObject) { // Microsoft objDOMDocument = getMSDOMDocument(); /* } else { // Mozilla | Netscape | Safari objDOMDocument = new DOMDocumentRequest(); if (objDOMDocument != null) { objDOMDocument.onload = handler; objDOMDocument.onerror = handler; } } */ return objDOMDocument; } function createXMLHTTP() { var objXmlHttp = null; if (window && window.XMLHttpRequest) { // IE7 or other browsers that support XMLHttpRequest objXmlHttp = new XMLHttpRequest(); } else { // Microsoft objXmlHttp = getMSXmlHttp(); } return objXmlHttp; } function getMSXmlHttp() { var xmlHttp = null; var clsids = [ "Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP.2.6", "Microsoft.XMLHTTP.1.0", "Microsoft.XMLHTTP.1", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP"]; for(var i=0; istr with illegal step name characters replaced * with replace * @public */ function createStepName(str, replace) { if (str) { return Util.trim(str.replace(/\|+/g, replace || "")); } return str; } /** * Returns a legal name-value property name based on the specified string. Any * illegal name-value property name charactes are removed and replaced with the * specified replacement string. If no replacement string is specified, "" is * used by default. * * @tparam String str string with character to be replaced * @tparam String replace replacement string [optional] * * @treturn String str with illegal name-value property name * characters replaced with replace * @public */ function createNameValuePropertyName(str, replace) { if (str) { // Replaces illegal NVP chars, which include whitespace and the // characters %,;={}[]<> return str.replace(/[%,;={}\[\]<>\s]+/g, replace || ""); } return str; } /** * @class CharRefs * A collection of character reference constants and lookup tables. * * @ctor CharRefs * @private */ function CharRefs() { } /** * A mapping of character entity references (for ASCII characters) to character * codes. * Source: http://www.w3.org/TR/html401/sgml/entities.html * * NOTE: Only this subset of references is currently supported by the agent. * @private */ CharRefs.ASCII_CHAR_ENTITY_REFS = { "quot" : "\u0022", "amp" : "\u0026", "lt" : "\u003C", "gt" : "\u003E", "nbsp" : "\u00A0", "iexcl" : "\u00A1", "cent" : "\u00A2", "pound" : "\u00A3", "curren" : "\u00A4", "yen" : "\u00A5", "brvbar" : "\u00A6", "sect" : "\u00A7", "uml" : "\u00A8", "copy" : "\u00A9", "ordf" : "\u00AA", "laquo" : "\u00AB", "not" : "\u00AC", "shy" : "\u00AD", "reg" : "\u00AE", "macr" : "\u00AF", "deg" : "\u00B0", "plusmn" : "\u00B1", "sup2" : "\u00B2", "sup3" : "\u00B3", "acute" : "\u00B4", "micro" : "\u00B5", "para" : "\u00B6", "middot" : "\u00B7", "cedil" : "\u00B8", "sup1" : "\u00B9", "ordm" : "\u00BA", "raquo" : "\u00BB", "frac14" : "\u00BC", "frac12" : "\u00BD", "frac34" : "\u00BE", "iquest" : "\u00BF", "Agrave" : "\u00C0", "Aacute" : "\u00C1", "Acirc" : "\u00C2", "Atilde" : "\u00C3", "Auml" : "\u00C4", "Aring" : "\u00C5", "AElig" : "\u00C6", "Ccedil" : "\u00C7", "Egrave" : "\u00C8", "Eacute" : "\u00C9", "Ecirc" : "\u00CA", "Euml" : "\u00CB", "Igrave" : "\u00CC", "Iacute" : "\u00CD", "Icirc" : "\u00CE", "Iuml" : "\u00CF", "ETH" : "\u00D0", "Ntilde" : "\u00D1", "Ograve" : "\u00D2", "Oacute" : "\u00D3", "Ocirc" : "\u00D4", "Otilde" : "\u00D5", "Ouml" : "\u00D6", "times" : "\u00D7", "Oslash" : "\u00D8", "Ugrave" : "\u00D9", "Uacute" : "\u00DA", "Ucirc" : "\u00DB", "Uuml" : "\u00DC", "Yacute" : "\u00DD", "THORN" : "\u00DE", "szlig" : "\u00DF", "agrave" : "\u00E0", "aacute" : "\u00E1", "acirc" : "\u00E2", "atilde" : "\u00E3", "auml" : "\u00E4", "aring" : "\u00E5", "aelig" : "\u00E6", "ccedil" : "\u00E7", "egrave" : "\u00E8", "eacute" : "\u00E9", "ecirc" : "\u00EA", "euml" : "\u00EB", "igrave" : "\u00EC", "iacute" : "\u00ED", "icirc" : "\u00EE", "iuml" : "\u00EF", "eth" : "\u00F0", "ntilde" : "\u00F1", "ograve" : "\u00F2", "oacute" : "\u00F3", "ocirc" : "\u00F4", "otilde" : "\u00F5", "ouml" : "\u00F6", "divide" : "\u00F7", "oslash" : "\u00F8", "ugrave" : "\u00F9", "uacute" : "\u00FA", "ucirc" : "\u00FB", "uuml" : "\u00FC", "yacute" : "\u00FD", "thorn" : "\u00FE", "yuml" : "\u00FF" }; /** * An array of ASCII characters (indexed by character code). */ CharRefs.ASCII_NUMERIC_CHARS = [ "\u0000", "\u0001", "\u0002", "\u0003", "\u0004", "\u0005", "\u0006", "\u0007", "\u0008", "\u0009", "\u000a", "\u000b", "\u000c", "\u000d", "\u000e", "\u000f", "\u0010", "\u0011", "\u0012", "\u0013", "\u0014", "\u0015", "\u0016", "\u0017", "\u0018", "\u0019", "\u001a", "\u001b", "\u001c", "\u001d", "\u001e", "\u001f", "\u0020", "\u0021", "\u0022", "\u0023", "\u0024", "\u0025", "\u0026", "\u0027", "\u0028", "\u0029", "\u002a", "\u002b", "\u002c", "\u002d", "\u002e", "\u002f", "\u0030", "\u0031", "\u0032", "\u0033", "\u0034", "\u0035", "\u0036", "\u0037", "\u0038", "\u0039", "\u003a", "\u003b", "\u003c", "\u003d", "\u003e", "\u003f", "\u0040", "\u0041", "\u0042", "\u0043", "\u0044", "\u0045", "\u0046", "\u0047", "\u0048", "\u0049", "\u004a", "\u004b", "\u004c", "\u004d", "\u004e", "\u004f", "\u0050", "\u0051", "\u0052", "\u0053", "\u0054", "\u0055", "\u0056", "\u0057", "\u0058", "\u0059", "\u005a", "\u005b", "\u005c", "\u005d", "\u005e", "\u005f", "\u0060", "\u0061", "\u0062", "\u0063", "\u0064", "\u0065", "\u0066", "\u0067", "\u0068", "\u0069", "\u006a", "\u006b", "\u006c", "\u006d", "\u006e", "\u006f", "\u0070", "\u0071", "\u0072", "\u0073", "\u0074", "\u0075", "\u0076", "\u0077", "\u0078", "\u0079", "\u007a", "\u007b", "\u007c", "\u007d", "\u007e", "\u007f", "\u0080", "\u0081", "\u0082", "\u0083", "\u0084", "\u0085", "\u0086", "\u0087", "\u0088", "\u0089", "\u008a", "\u008b", "\u008c", "\u008d", "\u008e", "\u008f", "\u0090", "\u0091", "\u0092", "\u0093", "\u0094", "\u0095", "\u0096", "\u0097", "\u0098", "\u0099", "\u009a", "\u009b", "\u009c", "\u009d", "\u009e", "\u009f", "\u00a0", "\u00a1", "\u00a2", "\u00a3", "\u00a4", "\u00a5", "\u00a6", "\u00a7", "\u00a8", "\u00a9", "\u00aa", "\u00ab", "\u00ac", "\u00ad", "\u00ae", "\u00af", "\u00b0", "\u00b1", "\u00b2", "\u00b3", "\u00b4", "\u00b5", "\u00b6", "\u00b7", "\u00b8", "\u00b9", "\u00ba", "\u00bb", "\u00bc", "\u00bd", "\u00be", "\u00bf", "\u00c0", "\u00c1", "\u00c2", "\u00c3", "\u00c4", "\u00c5", "\u00c6", "\u00c7", "\u00c8", "\u00c9", "\u00ca", "\u00cb", "\u00cc", "\u00cd", "\u00ce", "\u00cf", "\u00d0", "\u00d1", "\u00d2", "\u00d3", "\u00d4", "\u00d5", "\u00d6", "\u00d7", "\u00d8", "\u00d9", "\u00da", "\u00db", "\u00dc", "\u00dd", "\u00de", "\u00df", "\u00e0", "\u00e1", "\u00e2", "\u00e3", "\u00e4", "\u00e5", "\u00e6", "\u00e7", "\u00e8", "\u00e9", "\u00ea", "\u00eb", "\u00ec", "\u00ed", "\u00ee", "\u00ef", "\u00f0", "\u00f1", "\u00f2", "\u00f3", "\u00f4", "\u00f5", "\u00f6", "\u00f7", "\u00f8", "\u00f9", "\u00fa", "\u00fb", "\u00fc", "\u00fd", "\u00fe", "\u00ff" ]; /** * A mapping of character entity references to character codes. * Source: http://www.w3.org/TR/html401/sgml/entities.html * @private */ CharRefs.ALL_CHAR_ENITITY_REFS = { "nbsp" : "\u00A0", "iexcl" : "\u00A1", "cent" : "\u00A2", "pound" : "\u00A3", "curren" : "\u00A4", "yen" : "\u00A5", "brvbar" : "\u00A6", "sect" : "\u00A7", "uml" : "\u00A8", "copy" : "\u00A9", "ordf" : "\u00AA", "laquo" : "\u00AB", "not" : "\u00AC", "shy" : "\u00AD", "reg" : "\u00AE", "macr" : "\u00AF", "deg" : "\u00B0", "plusmn" : "\u00B1", "sup2" : "\u00B2", "sup3" : "\u00B3", "acute" : "\u00B4", "micro" : "\u00B5", "para" : "\u00B6", "middot" : "\u00B7", "cedil" : "\u00B8", "sup1" : "\u00B9", "ordm" : "\u00BA", "raquo" : "\u00BB", "frac14" : "\u00BC", "frac12" : "\u00BD", "frac34" : "\u00BE", "iquest" : "\u00BF", "Agrave" : "\u00C0", "Aacute" : "\u00C1", "Acirc" : "\u00C2", "Atilde" : "\u00C3", "Auml" : "\u00C4", "Aring" : "\u00C5", "AElig" : "\u00C6", "Ccedil" : "\u00C7", "Egrave" : "\u00C8", "Eacute" : "\u00C9", "Ecirc" : "\u00CA", "Euml" : "\u00CB", "Igrave" : "\u00CC", "Iacute" : "\u00CD", "Icirc" : "\u00CE", "Iuml" : "\u00CF", "ETH" : "\u00D0", "Ntilde" : "\u00D1", "Ograve" : "\u00D2", "Oacute" : "\u00D3", "Ocirc" : "\u00D4", "Otilde" : "\u00D5", "Ouml" : "\u00D6", "times" : "\u00D7", "Oslash" : "\u00D8", "Ugrave" : "\u00D9", "Uacute" : "\u00DA", "Ucirc" : "\u00DB", "Uuml" : "\u00DC", "Yacute" : "\u00DD", "THORN" : "\u00DE", "szlig" : "\u00DF", "agrave" : "\u00E0", "aacute" : "\u00E1", "acirc" : "\u00E2", "atilde" : "\u00E3", "auml" : "\u00E4", "aring" : "\u00E5", "aelig" : "\u00E6", "ccedil" : "\u00E7", "egrave" : "\u00E8", "eacute" : "\u00E9", "ecirc" : "\u00EA", "euml" : "\u00EB", "igrave" : "\u00EC", "iacute" : "\u00ED", "icirc" : "\u00EE", "iuml" : "\u00EF", "eth" : "\u00F0", "ntilde" : "\u00F1", "ograve" : "\u00F2", "oacute" : "\u00F3", "ocirc" : "\u00F4", "otilde" : "\u00F5", "ouml" : "\u00F6", "divide" : "\u00F7", "oslash" : "\u00F8", "ugrave" : "\u00F9", "uacute" : "\u00FA", "ucirc" : "\u00FB", "uuml" : "\u00FC", "yacute" : "\u00FD", "thorn" : "\u00FE", "yuml" : "\u00FF", "quot" : "\u0022", "amp" : "\u0026", "lt" : "\u003C", "gt" : "\u003E", "OElig" : "\u0152", "oelig" : "\u0153", "Scaron" : "\u0160", "scaron" : "\u0161", "Yuml" : "\u0178", "circ" : "\u02C6", "tilde" : "\u02DC", "ensp" : "\u2002", "emsp" : "\u2003", "thinsp" : "\u2009", "zwnj" : "\u200C", "zwj" : "\u200D", "lrm" : "\u200E", "rlm" : "\u200F", "ndash" : "\u2013", "mdash" : "\u2014", "lsquo" : "\u2018", "rsquo" : "\u2019", "sbquo" : "\u201A", "ldquo" : "\u201C", "rdquo" : "\u201D", "bdquo" : "\u201E", "dagger" : "\u2020", "Dagger" : "\u2021", "permil" : "\u2030", "lsaquo" : "\u2039", "rsaquo" : "\u203A", "euro" : "\u20AC", "fnof" : "\u0192", "Alpha" : "\u0391", "Beta" : "\u0392", "Gamma" : "\u0393", "Delta" : "\u0394", "Epsilon" : "\u0395", "Zeta" : "\u0396", "Eta" : "\u0397", "Theta" : "\u0398", "Iota" : "\u0399", "Kappa" : "\u039A", "Lambda" : "\u039B", "Mu" : "\u039C", "Nu" : "\u039D", "Xi" : "\u039E", "Omicron" : "\u039F", "Pi" : "\u03A0", "Rho" : "\u03A1", "Sigma" : "\u03A3", "Tau" : "\u03A4", "Upsilon" : "\u03A5", "Phi" : "\u03A6", "Chi" : "\u03A7", "Psi" : "\u03A8", "Omega" : "\u03A9", "alpha" : "\u03B1", "beta" : "\u03B2", "gamma" : "\u03B3", "delta" : "\u03B4", "epsilon" : "\u03B5", "zeta" : "\u03B6", "eta" : "\u03B7", "theta" : "\u03B8", "iota" : "\u03B9", "kappa" : "\u03BA", "lambda" : "\u03BB", "mu" : "\u03BC", "nu" : "\u03BD", "xi" : "\u03BE", "omicron" : "\u03BF", "pi" : "\u03C0", "rho" : "\u03C1", "sigmaf" : "\u03C2", "sigma" : "\u03C3", "tau" : "\u03C4", "upsilon" : "\u03C5", "phi" : "\u03C6", "chi" : "\u03C7", "psi" : "\u03C8", "omega" : "\u03C9", "thetasym" : "\u03D1", "upsih" : "\u03D2", "piv" : "\u03D6", "bull" : "\u2022", "hellip" : "\u2026", "prime" : "\u2032", "Prime" : "\u2033", "oline" : "\u203E", "frasl" : "\u2044", "weierp" : "\u2118", "image" : "\u2111", "real" : "\u211C", "trade" : "\u2122", "alefsym" : "\u2135", "larr" : "\u2190", "rarr" : "\u2192", "darr" : "\u2193", "harr" : "\u2194", "crarr" : "\u21B5", "lArr" : "\u21D0", "uArr" : "\u21D1", "rArr" : "\u21D2", "dArr" : "\u21D3", "hArr" : "\u21D4", "forall" : "\u2200", "part" : "\u2202", "exist" : "\u2203", "empty" : "\u2205", "nabla" : "\u2207", "isin" : "\u2208", "notin" : "\u2209", "ni" : "\u220B", "prod" : "\u220F", "sum" : "\u2211", "minus" : "\u2212", "lowast" : "\u2217", "radic" : "\u221A", "prop" : "\u221D", "infin" : "\u221E", "ang" : "\u2220", "and" : "\u2227", "or" : "\u2228", "cap" : "\u2229", "cup" : "\u222A", "int" : "\u222B", "there4" : "\u2234", "sim" : "\u223C", "cong" : "\u2245", "asymp" : "\u2248", "ne" : "\u2260", "equiv" : "\u2261", "le" : "\u2264", "ge" : "\u2265", "sub" : "\u2282", "sup" : "\u2283", "nsub" : "\u2284", "sube" : "\u2286", "supe" : "\u2287", "oplus" : "\u2295", "otimes" : "\u2297", "perp" : "\u22A5", "sdot" : "\u22C5", "lceil" : "\u2308", "rceil" : "\u2309", "lfloor" : "\u230A", "rfloor" : "\u230B", "lang" : "\u2329", "rang" : "\u232A", "loz" : "\u25CA", "spades" : "\u2660", "clubs" : "\u2663", "hearts" : "\u2665", "diams" : "\u2666" }; /** * A Logging framework. * @ctor Logger. * * Create a logger. */ function Logger(obj, className) { this.m_debug = obj; this.m_className = className; this.m_enabled = false; } Logger.prototype.log = function(str, messages, doEscape) { if (this.m_enabled) { if (typeof(messages) == "boolean") { this.m_debug.log(str, null, this.m_className, true); } else { this.m_debug.log(str, messages, this.m_className, doEscape); } } } Logger.prototype.logNode = function(node) { if (this.m_enabled) { this.m_debug.logNode(node, this.m_className); } } Logger.prototype.enable = function() { this.m_enabled = true; } Logger.prototype.disable = function() { this.m_enabled = false; } Logger.prototype.toggleEnableDisable = function() { this.m_enabled = !this.m_enabled; } Logger.prototype.getIsEnabled = function() { return this.m_enabled; } /** * @class OutputStream. * A base class for an output data stream. */ function OutputStream() { /** * disabled state * @private */ this.m_disabled = false; } /** * Disables the stream. */ OutputStream.prototype.disable = function() { this.m_disabled = true; } /** * Enables the stream. */ OutputStream.prototype.enable = function() { this.m_disabled = false; } /** * Gets the enable state. * @treturn bool true if enabled, false if disabled. */ OutputStream.prototype.isEnabled = function() { return !this.m_disabled; } /** * An output data stream encapsulated by a file. When data is written * to this object, the data appears in the corresponding file. * * @ctor OutputStreamFile. * Creates an output stream to a file. * @tparam String fileName the file name. * @treturn OutputStreamFile an output stream. */ function OutputStreamFile(fileName) { this.open(fileName); } OutputStreamFile.prototype = new OutputStream(); /** * Opens the file for writing the data stream. * @tparam String fileName the file name. */ OutputStreamFile.prototype.open = function (fileName) { try { // keep this // this.close(); if (fileName) { var fso = new ActiveXObject("Scripting.FileSystemObject"); var forWriting = 2; var create = true; this.m_fileHandle = fso.OpenTextFile(fileName, forWriting, create); this.m_opened = true; } else { this.m_fileHandle = null; this.m_opened = false; } } catch (e) { // keep this // purposely ignore this } } /** * Appends a string and optional messages to the data stream. * @tparam String str the top level string. * @tparam Object[] messages the detail messages. * @tparam String className not used. * @tparam bool doEscape FIXME not implemented yet. */ OutputStreamFile.prototype.print = function (str, messages, className, doEscape) { try { // keep this if (this.isEnabled()) { if (str && this.m_fileHandle) { this.m_fileHandle.WriteLine("- " + str); } if (messages && this.m_fileHandle) { if (typeof (messages) == "string") { this.m_fileHandle.WriteLine(" " + messages); } else { for (var i = 0; i < messages.length; i++) { var s = messages[i]; if (typeof (s) == "string") { this.m_fileHandle.WriteLine(" " + s); } } } this.m_fileHandle.WriteLine(""); } } } catch (e) { // keep this // purposely ignore this } } /** * Apends a XML node to the output stream. * @tparam XMLNode node * @tparam String className not used * @tparam int indent number of spaces. */ OutputStreamFile.prototype.printNode = function(node, className, indent) { try { // keep this if (this.isEnabled()) { if (this.m_fileHandle) { if (!indent) { indent = 0; } var text = new StringBuffer(); if (node.nodeType == 1 /* NODE_ELEMENT */) { if (indent > 0) { text.append(new Array(indent+1).join(" ")); } text.append("- "); text.append(node.tagName); var attrs = node.attributes; if (attrs) { text.append(" "); for (var i= 0; i < attrs.length; i++) { if (attrs.item(i).nodeValue != "") { text.append(attrs.item(i).nodeName, "=\"", attrs.item(i).nodeValue, "\"", " "); } } } } else if (node.nodeType == 4) { // FIXME handle nodeType == 4 } var outputText = text.toString(); this.m_fileHandle.WriteLine(outputText); var nodes = node.childNodes; if (nodes) { for (var i = 0; i < nodes.length; i++) { this.printNode(nodes.item(i), className, indent + 2); } } } } } catch (e) { // keep this // purposely ignore this } } /** * Closes the file and the data stream. */ OutputStreamFile.prototype.close = function () { try { // keep this if (this.m_fileHandle && this.m_opened) { this.m_opened = false; this.m_fileHandle.Close(); delete this.m_fileHandle; this.m_fileHandle = null; } } catch (e) { // keep this // purposely ignore this } } /** * An output data stream encapsulated by a XML HTTP object. When data is written * to this object, the data is posted to the corresponding URL. * * @ctor OutputStreamHTTP. * Creates an output stream to a remote URL. * @tparam String url the URL. * @treturn OutputStreamHTTP an OutputStreamHTTP object. */ function OutputStreamHTTP(url) { /** * the URL to post the data to. * @private */ this.m_url = url; this.m_text_data = null; } OutputStreamHTTP.prototype = new OutputStream(); OutputStreamHTTP.prototype.open = function () { this.m_text_data = []; } OutputStreamHTTP.prototype.close = function() { try { // keep this if (this.m_text_data && this.m_text_data.length > 0) { var xmlHttpReq = createXMLHTTP(); var that = this; // send asynchronously xmlHttpReq.open("POST", this.m_url, true); var xmlDoc = createDOMDocument(); if (xmlDoc) { xmlDoc.documentElement = xmlDoc.createElement("data"); var cdata = xmlDoc.createCDATASection(this.m_text_data.join("\n")); xmlDoc.documentElement.appendChild(cdata); xmlHttpReq.send(xmlDoc.xml); } this.m_text_data = null; } } catch (ex) { // keep this // purposely ignore this } } /** * Specifies the callback function. * @tparam Function func the callback function. * @tparam Object obj the context object, if not null, * the function will be called with obj as this. */ OutputStreamHTTP.prototype.setCallbackFunc = function (func, obj) { this.m_func = func; this.m_obj = obj; } /** * Sends a XML node to the remote server. * @tparam IXMLElement node */ OutputStreamHTTP.prototype.printNode = function(node) { try { // keep this var xmlHttpReq = createXMLHTTP(); var that = this; xmlHttpReq.onreadystatechange = function() { if (xmlHttpReq.readyState == 4) { if (that.m_func) { if (that.m_obj) { that.m_func.call(that.m_obj, xmlHttpReq); } else { that.m_func(xmlHttpReq); } } } } xmlHttpReq.open("POST", this.m_url, true); xmlHttpReq.send(node.xml); } catch (e) { // keep this // purposely ignore this } } OutputStreamHTTP.prototype.print = function (str, messages, className, doEscape) { try { // keep this if (this.isEnabled()) { if (str && this.m_text_data) { this.m_text_data.push("- " + str); } if (messages && this.m_text_data) { if (typeof (messages) == "string") { this.m_text_data.push(" " + messages); } else { for (var i = 0; i < messages.length; i++) { var s = messages[i]; if (typeof (s) == "string") { this.m_text_data.push(" " + s); } } } } } } catch (e) { // keep this // purposely ignore this } } /** * @class OutputStreamUI. * An output data stream encapsulated by a HTML element. * When data is written to this object, the data appears in the corresponding HTML element. * * @ctor OutputStreamUI. * Creates an output stream to a HTML element. * @tparam HTMLElement elem the HTML element. * @treturn OutputStreamUI an output stream. */ function OutputStreamUI(elem) { /** * The expand state. * @private */ this.m_expand = false; if (elem) { /** * The HTML element. * @private */ this.m_container = elem; /** * The HTML element's tag name. * @private */ this.m_tagName = elem.tagName.toLowerCase(); } } OutputStreamUI.prototype = new OutputStream(); /** * Appends a XML node to the output stream. * @tparam XMLNode node the XML node. * @tparam String className the css class name used to render the strings. * @tparam HTMLElement parentElem the HTML element where the content should be added to. */ OutputStreamUI.prototype.printNode = function(node, className, parentElem) { try { // keep this if (this.m_container && this.isEnabled()) { if (this.m_tagName == "textarea") { // FIXME handle OutputStreamUI.printNode to textarea } else { var doc = this.m_container.document ? this.m_container.document : this.m_container.ownerDocument; if (node) { // the parent. var d1 = doc.createElement("div"); // + or - var icon = doc.createElement("span"); // head line string var s1 = doc.createElement("span"); // contain i + s1 var p1 = doc.createElement("p"); // contain messages var div2 = doc.createElement("div"); if (!this.m_expand) { icon.innerHTML = "+"; } else { icon.innerHTML = "-"; } icon.className = "tree"; icon.onclick = function() { var m = this.parentNode.parentNode.childNodes[1]; if (m) { m.style.display = (m.style.display == "none" ) ? "" : "none"; this.innerHTML = (this.innerHTML == "-") ? "+" : "-"; } } var headLine = new StringBuffer(); if (node.nodeType == 1 /* NODE_ELEMENT */) { headLine.append(node.tagName); var attrs = node.attributes; if (attrs != null) { headLine.append(" "); for (var i = 0; i < attrs.length; i++) { if (attrs.item(i).nodeValue != "") { headLine.append(escapeHTML(attrs.item(i).nodeName)); headLine.append("="); headLine.append("\""); headLine.append(escapeHTML(attrs.item(i).nodeValue)); headLine.append("\""); headLine.append(" "); } } } s1.innerHTML = headLine.toString(); } else if (node.nodeType == 4 /* NODE_CDATA_SECTION */) { /*var textNode = doc.createTextNode(escapeHTML(node.data)); s1.appendChild(textNode);*/ } p1.appendChild(icon); p1.appendChild(s1); p1.className = "message"; var nodes = node.childNodes; if (nodes != null) { for (var i = 0; i < nodes.length; i++) { this.printNode(nodes.item(i), className, div2); } } div2.className = "desc"; if (!this.m_expand) { div2.style.display = "none"; } d1.appendChild(p1); d1.appendChild(div2); if (parentElem) { parentElem.appendChild(d1); } else { this.m_container.appendChild(d1); } if (className) { d1.className = className; } d1.scrollIntoView(); } } } } catch (e) { // keep this // purposely ignore this } } /** * Appends a string and optional messages to the HTML element. * @tparam String str the top level string. * @tparam Object[] messages the detail messages. * @tparam String className the css class name used to render the strings. * @tparam bool doEscape escapes the data so the source is displayed. */ OutputStreamUI.prototype.print = function(str, messages, className, doEscape) { try { // keep this if (this.m_container && this.isEnabled()) { if (this.m_tagName == "textarea") { if (str && messages) { this.m_container.value += str + "\n " + messages + "\n"; } else if (str) { this.m_container.value += str + "\n"; } } else { var doc = this.m_container.document ? this.m_container.document : this.m_container.ownerDocument; if (str && messages) { var p1 = doc.createElement("p"); var icon = doc.createElement("span"); var s1 = doc.createElement("span"); var div2 = doc.createElement("div"); var d1 = doc.createElement("div"); if (!this.m_expand) { icon.innerHTML = "+"; } else { icon.innerHTML = "-"; } icon.className = "tree"; icon.onclick = function() { var m = this.parentNode.parentNode.childNodes[1]; if (m) { m.style.display = (m.style.display == "none" ) ? "" : "none"; this.innerHTML = (this.innerHTML == "-") ? "+" : "-"; } } s1.innerHTML = doEscape ? escapeHTML(str) : str; p1.appendChild(icon); p1.appendChild(s1); p1.className = "message"; if (typeof (messages) == "string") { div2.innerHTML = doEscape ? escapeHTML(messages) : messages; } else { for (var i = 0; i < messages.length; i++) { var message = messages[i] var s = doEscape ? escapeHTML(message) : message; if (typeof (s) == "string") { var p3 = doc.createElement("p"); p3.innerHTML = s; div2.appendChild(p3); } else { div2.appendChild(s); } } } div2.className = "desc"; if (!this.m_expand) { div2.style.display = "none"; } d1.appendChild(p1); d1.appendChild(div2); this.m_container.appendChild(d1); if (className) { d1.className = className; } d1.scrollIntoView(); } else if (str) { var e = doc.createElement("div"); e.className = "message"; e.innerHTML = doEscape ? escapeHTML(str) : str; this.m_container.appendChild(e); if (className) { e.className = className; } e.scrollIntoView(); } } } } catch (e) { // keep this // purposely ignore this } } /** * Clears the content of the HTML elment. */ OutputStreamUI.prototype.clear = function() { try { // keep this if (this.m_container) { if (this.m_tagName == "textarea") { this.m_container.value = ""; } else { this.m_container.innerHTML = ""; } } } catch (e) { // keep this // purposely ignore this } } /** * Disables the output stream and the HTML element. */ OutputStreamUI.prototype.disable = function() { OutputStream.disable.call(this); if (this.m_container) { this.m_container.disabled = true; } } /** * Enables the output stream and the HTML element. */ OutputStreamUI.prototype.enable = function() { OutputStream.enable.call(this); if (this.m_container) { this.m_container.disabled = false; } } /** * Sets the expand state. * if expand is true, all new messages added will be hidden by default, (only the top level string will be visible). * * @tparam bool value true or false */ OutputStreamUI.prototype.setExpand = function(value) { this.m_expand = value; } /** * Gets the expand state. * @treturn bool */ OutputStreamUI.prototype.getExpand = function() { return this.m_expand; } /** * An output data stream encapsulated by a XML file. When data is written * to this object, the data appears in the corresponding file. * * @ctor OutputStreamXMLFile. * Creates an output stream to a file. * @tparam String fileName the file name. * @treturn OutputStreamXMLFile an output stream. */ function OutputStreamXMLFile(fileName) { this.open(fileName); } OutputStreamXMLFile.prototype = new OutputStream(); /** * Opens the file for writing the data stream. * @tparam String fileName the file name. */ OutputStreamXMLFile.prototype.open = function (fileName) { try { // keep this // this.close(); if (fileName) { var fso = new ActiveXObject("Scripting.FileSystemObject"); var forWriting = 2; var create = true; this.m_fileHandle = fso.OpenTextFile(fileName, forWriting, create); this.m_opened = true; } else { this.m_fileHandle = null; this.m_opened = false; } } catch (e) { // keep this // purposely ignore this } } /** * Write a XML node to the output stream. * @tparam XMLNode node */ OutputStreamXMLFile.prototype.printNode = function(node) { try { // keep this if (this.isEnabled()) { if (this.m_fileHandle) { var xml = node.xml.replace(/>\n<"); this.m_fileHandle.WriteLine(xml); } } } catch (e) { // keep this // purposely ignore this } } /** * Closes the file and the data stream. */ OutputStreamXMLFile.prototype.close = function () { try { // keep this if (this.m_fileHandle && this.m_opened) { this.m_opened = false; this.m_fileHandle.Close(); delete this.m_fileHandle; this.m_fileHandle = null; } } catch (e) { // keep this // purposely ignore this } } /** * @class InputStream. * A base class for an input data stream. */ function InputStream() { } /** * Reads data from the stream. * @treturn String[] each string correspond to a single line * from the data stream. */ InputStream.prototype.read = function () { return null; } /** * @class InputStreamFile. * An input data stream encapsulated by a file. The content of the file * can be read using this object. This is supported in Internet Explorer only. * * Note: This reads files from user's hard drive, and user prompt is required. * * @ctor InputStreamFile. * Creates an input stream from a file. * @tparam String fileName the file name. * @treturn InputStreamFile an input stream. */ function InputStreamFile(fileName) { try { // keep this /** * data array. * @private */ this.m_data = []; // constructor // if (window.ActiveXObject) { var fso = new ActiveXObject("Scripting.FileSystemObject"); var forReading = 1; var create = false; var fileHandle = fso.OpenTextFile(fileName, forReading, create); this.m_data = fileHandle.ReadAll(); fileHandle.Close(); delete fso; delete fileHandle; /* } else { // not supported in other browsers set. this.m_data = []; } */ } catch (e) { // keep this // purposely ignore this } } InputStreamFile.prototype = new InputStream(); /** * Reads data from the stream. * @treturn String[] A string array where each string correspond to a single line from the file. */ InputStreamFile.prototype.read = function() { return this.m_data; } /** * @class InputStreamHTTP. * An input data stream encapsulated by a HTTP url. * * @ctor InputStreamHTTP. * Creates an input stream from a HTTP url. * @tparam String url * @treturn InputStreamHTTP an input stream object. */ function InputStreamHTTP(url) { /** * URL. * @private */ this.m_url = url; } InputStreamHTTP.prototype = new InputStream(); InputStreamHTTP.prototype.open = function() { try { // keep this if (this.m_url) { this.m_xmlHttpReq = createXMLHTTP(); var that = this; if (this.m_xmlHttpReq) { this.m_xmlHttpReq.onreadystatechange = function() { if (that.m_xmlHttpReq.readyState == 4) { if (that.m_func) { if (that.m_obj) { that.m_func.call(that.m_obj, that.m_xmlHttpReq); } else { that.m_func(that.m_xmlHttpReq); } } else { stderr.log("InputStreamHTTP.prototype.open that.m_func is not defined"); } } } this.m_xmlHttpReq.open("GET", this.m_url, true); this.m_xmlHttpReq.send(""); } } else { stderr.log('InputStreamHTTP.m_url is null'); } } catch (e) { // keep this // purposely ignore this } } /** * Reads data from the input stream. * @treturn String */ InputStreamHTTP.prototype.read = function() { if (this.m_xmlHttpReq) { return this.m_xmlHttpReq.responseText; } else { return ""; } } /** * Specifies the callback function. * @tparam Function func the callback function. * @tparam Object obj the context object, if not null, * the function will be called with obj as this. */ InputStreamHTTP.prototype.setCallbackFunc = function (func, obj) { this.m_func = func; this.m_obj = obj; } /** * @class InputStreamUI. * An input data stream encapsulated by a HTML element. * * @ctor InputStreamUI. * Creates an input stream from a HTML element. * @tparam HTMLElement elem the HTML element. * @treturn InputStreamUI an input stream object. */ function InputStreamUI(elem) { /** * The HTML element. * @private */ this.m_container = elem; } InputStreamUI.prototype = new InputStream(); /** * Reads data from the input stream. * @treturn String[] Currently only support HTML elements with the value attribute. */ InputStreamUI.prototype.read = function() { if (this.m_container) { return this.m_container.value; } return ""; } /** * @class Mvmap. * A multi valued hash table implementation. * * Suppose user calls * thisObject.add("foo", 1) * thisObject.add("foo", 2) * thisObject.add("foo", 3) * * During retrieval, user simply call thisObject.item(key) * we internally keep an iterator so it will return different * values each time it is called. * * thisObject.item("foo"); returns 1 * thisObject.item("foo"); returns 2 * thisObject.item("foo"); returns 3 * thisObject.resetIterators(); * thisObject.item("foo"); returns 1 */ function Mvmap() { this.m_map = {}; this.m_itr = {}; this.m_count = 0; } /** * Adds an (key, item) entry to the multi-valued map. * Duplicate keys are allowed. Duplicate items are allowed. */ Mvmap.prototype.add = function(key, item) { var curr = this.m_map[key]; if (curr) { if (curr.push) { curr.push(item); } else { this.m_map[key] = [curr, item]; } } else { this.m_map[key] = item; } this.m_count++; } /** * Returns true if the key exists. */ Mvmap.prototype.exists = function(key) { return (this.m_map[key] != null); } /** * Returns the number of entries (duplicate keys count as multiple entries). */ Mvmap.prototype.count = function() { return this.m_count; } /** * Gets all key/values as two arrays. */ Mvmap.prototype.getAllValues = function() { var names = []; var values= []; var result = {"names" : names, "values": values}; if (this.m_count > 0) { for (var key in this.m_map) { var value = this.m_map[key]; if (value.push) { for (var i = 0; i < value.length; i++) { names.push(key); values.push(value[i]); } } else if (value) { names.push(key); values.push(value); } } } return result; } /** * Removes all items associated with the key. * Currently we do not support removing a specific item. */ Mvmap.prototype.remove = function(key) { var curr = this.m_map[key]; if (curr) { // testing for array if (curr.push) { this.m_count -= curr.length; } else { this.m_count -= 1; } } else { this.m_count -=1; } this.m_map[key] = null; } /** * Returns a item associated with the key. * Note: suppose a key is mapped to multiple items, * "foo" -> {0, 1, 2} * item("foo") will initially return 0, then 1, then 2. */ Mvmap.prototype.item = function(key) { // determine which item we should get for this key. var itrIndex = this.m_itr[key]; if (itrIndex) { this.m_itr[key] = ++itrIndex; } else { itrIndex = 1; this.m_itr[key] = itrIndex; } var curr = this.m_map[key]; if (curr) { if (itrIndex == 1) { // testing for array, yes if itrIndex > 1, this returns undefined, which as expected. if (curr.push) { return curr[itrIndex-1]; } else { return curr; // could be null } } else if (curr.push && curr.length >= itrIndex) { return curr[itrIndex-1]; } } return null; } /** * Retrieves the iterator corresponding to the key. * * Suppose we have foo -> {1, 2, 3} * Suppose iteratorIndex("foo") = 0 then * item("foo") will return 1. * Suppose iteratorIndex("bar") = -1 * item("bar") will return null. * Suppose iteratorIndex("foo") = 2 then * item("foo") will return 3 */ Mvmap.prototype.iteratorIndex = function(key) { var itrIndex = this.m_itr[key]; if (itrIndex) { return itrIndex; } else if (this.m_map[key]) { return 0; } else { return -1; } } /** * Resets all iterators so * thisObject.item(key) will return the first item */ Mvmap.prototype.resetIterators = function() { this.m_itr = {}; } Mvmap.prototype.compare = function(anotherMap) { var score = 0; for(var p in this.m_map) { if (anotherMap.exists(p)) { var thisData = this.m_map[p]; var thatData = anotherMap.m_map[p]; if (!thisData.push && !thatData.push) { score++; // single value case /* if (thisData != thatData) { return false; } */ } else if (thisData.length == thatData.length) { /* // multi value case var thisDataCopy = thisData.slice(0).sort(); var thatDataCopy = thatData.slice(0).sort(); for (var i = 0; i < thisDataCopy.length; i++) { if (thisDataCopy[i] != thatDataCopy[i]) { return false; } } */ score+=thisData.length; } else { // one map has more entries than others, // this is necessary for now. score++; } } else { } } return score; } /** * @class Player. * A web transaction Player. */ function Player() { } /** * Initializes data members. * @private */ Player.prototype.init = function() { this.m_locks = []; // never make m_locks null; this.m_tnManager = null; // never null after start this.m_pauseMode = false; this.m_steps = null; // never null after start this.m_stepIndex = 0; // m_stepIndex >= 0 && m_stepIndex < m_steps.length this.m_timeoutId = null; // time out id for controlling when to run the next step this.m_sleepTimeoutId = null; // time out id for controlling how much time to sleep this.m_timeoutPeriod = 10000; // default timeout = 30 seconds. this.m_stopFuncs = []; this.m_pauseFuncs = []; this.m_loopFuncs = []; this.m_started = false; this.m_traceMode = false; this.m_visibleMode = true; this.m_silentMode = false; this.m_debugMode = false; this.m_loopCount = 1; this.m_loopIndex = 0; this.m_getPasswordFunc = function (index) { return null }; } /** * Sets the data source where the transaction definition can be read. * @tparam InputStream inputStream the input data as a stream. */ Player.prototype.setInputStream = function(inputStream) { this.input_stream = inputStream; } /** * Sets the time in milliseconds to wait before timeout on * a wait for load action. * * @tparam int val the timeout value in milliseconds */ Player.prototype.setTimeoutPeriod = function(val) { this.m_timeoutPeriod = val; } /** * Sets the mode to determine how much time we should wait between steps. * @tparam String mode */ Player.prototype.setRunMode = function(mode) { this.m_runMode = mode; } /** * Sets the number of times playback should repeat. * * @tparam int loop can also be 'inf' */ Player.prototype.setLoopCount = function(loop) { if (loop && loop.toLowerCase() == 'inf') { this.m_loopCount = "inf"; } else if (loop < 1) { this.m_loopCount = 1; } else { this.m_loopCount = loop; } } Player.prototype.setDebugMode = function(mode) { this.m_debugMode = mode ? true : false; } Player.prototype.getDebugMode = function() { return this.m_debugMode; } /** * Adds a function to call when playback stops. * @tparam Function func a callback function without any parameters. */ Player.prototype.addOnStopCallback = function(func) { this.m_stopFuncs.push(func); } /** * Adds a function to call when playback pauses. * @tparam Function func a callback function without any parameters. */ Player.prototype.addOnPauseCallback = function(func) { this.m_pauseFuncs.push(func); } /** * Adds a function to call when playback finishes one loop. * @tparam Function func a callback function without any parameters. */ Player.prototype.addOnLoopCallback = function(func) { this.m_loopFuncs.push(func); } Player.prototype.setGetPasswordFunc = function(func) { this.m_getPasswordFunc = func; } /** * Performs a set of callback functions. * @private */ Player.prototype._oncallback = function(funcArray) { for (var i = 0; i < funcArray.length; i++) { try { funcArray[i](); } catch (e) { this._handleException(arguments.callee, e); } } } /** * Called when playback is stopped. * @private */ Player.prototype._onstop = function() { this._oncallback(this.m_stopFuncs); } /** * Called when playback is paused. * @private */ Player.prototype._onpause = function() { this._oncallback(this.m_pauseFuncs); } /** * Called when playback has completed one loop. * @private */ Player.prototype._onloop = function() { this._oncallback(this.m_loopFuncs); } /** * Sets the trace mode on or off. This should be set * for Play with Trace functionality. * * @tparam bool traceMode */ Player.prototype.setTraceMode = function(traceMode) { this.m_traceMode = traceMode; } /** * Sets the visibility mode on or off. This should be set * to true normally, and false for agent playback. */ Player.prototype.setVisible = function(mode) { this.m_visibleMode = mode; } /** * Sets the silent mode on or off. This should be set * to false normally, and true for agent playback. */ Player.prototype.setSilent = function(mode) { this.m_silentMode = mode; } /** * Starts play back. */ Player.prototype.start = function() { try { // clean up any state, just in case. this.stop(); this.m_started = true; this.m_loopIndex = 0; this.m_tnManager = new TxnManager(this.input_stream.read()); if (this.m_tnManager) { this.m_steps = this._readSteps(this.m_tnManager.readActions2()); if (this.m_steps && (this.m_steps.length > 0)) { this.doStart(); } else { stderr.log("Unable to read steps"); this._onstop(); } } else { stderr.log("Unable to create a transaction manager object"); this._onstop(); } } catch (e) { this._handleException(arguments.callee, e); this._onstop(); } } /** * Stops play back. */ Player.prototype.stop = function() { try { if (this.m_started) { // stop all future operations. this._clearTimeout(); this._clearSleepTimeout(); // give subclass to clean up itself. this.doStop(); // clear all the steps states // in the reverse order. this.m_stepIndex = 0; this.m_steps = null; this.m_tnManager = null; this.m_locks.splice(0, this.m_locks.length); this.m_started = false; } } catch (e) { this._handleException(arguments.callee, e); } } Player.prototype.abort = function () { try { if (this.m_brListener) { this.m_brListener.stop(); } if (this.m_httpListener) { this.m_httpListener.stop(); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Pauses play back. */ Player.prototype.pause = function() { try { this._clearTimeout(); this._clearSleepTimeout(); this.m_pauseMode = true; } catch (e) { this._handleException(arguments.callee, e); } } /** * Resumes play back. */ Player.prototype.resume = function() { this.m_pauseMode = false; this.tryContinue(); } /** * Performs a single action and pause. */ Player.prototype.step = function() { this.m_pauseMode = false; this.tryContinue(); this.m_pauseMode = true; } /** * Runs the next step. * @private */ Player.prototype._runStep = function() { try { if (this.m_stepIndex >= 0 && this.m_stepIndex < this.m_steps.length) { if (this.m_stepIndex == 0) { // start of another loop this._onloop(); } this._clearTimeout(); this._doRunStep(this.m_steps[this.m_stepIndex]); } else if (this.m_steps.length == 0) { this._onstop(); } else if (++this.m_loopIndex < this.m_loopCount || this.m_loopCount == "inf") { this.m_stepIndex = 0; this.tryContinue(); } else { window.setTimeout("if (player) {player._onstop();}", 1000); } } catch (e) { this._handleException(arguments.callee, e); } } Player.prototype._rerunStep = function() { try { if (this.m_stepIndex >= 0 && this.m_stepIndex < this.m_steps.length) { this._doRunStep(this.m_steps[this.m_stepIndex]); } else { window.setTimeout("if (player) {player._onstop();}", 1000); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Reads all the actions and converts them * into an array of JavaScript functions. * @private * * @tparam String str * @treturn Function[] */ Player.prototype._readSteps = function(str) { var inputs = str.split("\n"); var steps = new Array(); var currentAction = ""; var currentWait = ""; var currentSleep = ""; var i = 0; var sleep = false; var pwdIndex = 0; var pas = null; var quote = /\*\*\*\*\*\*/g; var pause = false; for (var j = 0; j < inputs.length; j++) { var step = Util.trim(inputs[j]); pause = (currentWait != ""); /* debug.log("currentAction " + currentAction); debug.log("currentWait " + currentWait); debug.log("currentSleep " + currentSleep); debug.log("step " + step); */ if (step.substring(0,9) == "// Action") { if (currentAction != "" || currentWait != "") { if (currentAction.indexOf("\*\*\*\*\*\*") != -1) { /* pas = this.m_getPasswordFunc(pwdIndex++); if (pas.length == 0 || pas == null) { throw new Error("Found the string ****** in the script. This means you need to enter a password"); } */ } currentAction = currentAction.replace(quote, pas); pas = null; if (pause) { steps[i++] = new Function("", currentWait + currentAction + "player.sleep()\n"); } else { steps[i++] = new Function("", currentWait + currentAction + "player.tryContinue()\n"); } currentAction = ""; currentWait = ""; } else if (currentSleep != "") { steps[i++] = new Function("", currentSleep); currentSleep = ""; } } else if (step.substring(0,7) == "// Wait") { if (currentWait == "") { currentWait += "// Wait\n"; } else if (currentSleep != "") { steps[i++] = new Function("", currentSleep); currentSleep = ""; } } else if (step.substring(0,8) == "// Sleep") { if (currentAction != "" || currentWait != "") { currentAction = currentAction.replace(quote, pas); if (pause) { steps[i++] = new Function("", currentWait + currentAction + "\n"); } else { steps[i++] = new Function("", currentWait + currentAction + "player.tryContinue()\n"); } currentAction = ""; currentWait = ""; } currentSleep += "// Sleep\n"; } else if (step != "") { if (currentWait != "") { currentWait += step + "\n"; } else if (currentSleep != "") { currentSleep += step + "\n"; } else { currentAction += step + "\n"; } } } pause = (currentWait != ""); if (currentAction != "" || currentWait != "") { if (pause) { steps[i++] = new Function("", currentWait + currentAction + "\n"); } else { steps[i++] = new Function("", currentWait + currentAction + "player.tryContinue()\n"); } currentAction = ""; currentWait = ""; } else if (currentSleep != "") { steps[i++] = new Function("", currentSleep); } return steps; } /** * Runs a single step. * @private */ Player.prototype._doRunStep = function(func) { try { var str = func.toString(); scripts.log("Run Step " + this.m_stepIndex, str.split("\n")); func(); this.m_stepIndex++; } catch (e) { // check if e is of certain type this._handleException(arguments.callee, e); } } /** * Clears time out. * @private */ Player.prototype._clearTimeout = function() { try { if (this.m_timeoutId != null) { window.clearTimeout(this.m_timeoutId); this.m_timeoutId = null; } } catch (e) { this._handleException(arguments.callee, e); } } /** * Clears sleep time out. * @private */ Player.prototype._clearSleepTimeout = function() { try { if (this.m_sleepTimeoutId != null) { window.clearTimeout(this.m_sleepTimeoutId); this.m_sleepTimeoutId = null; } this._removeLock('sleep', 0); } catch (e) { this._handleException(arguments.callee, e); } } /** * Gets the browser object at position index. * @tparam int windowIndex * @treturn IWebBrowser2 */ Player.prototype.getBrowserAt = function(windowIndex) { alert(getFunctionName(Player, arguments.callee) + " is not implemented."); } // -------------------------------------------------------------------------------- // Locks Implementation /** * Displays a set of locks. * @private */ Player.prototype._showLocks = function() { try { if (this.m_locks.length == 0) { // debug.log("No Locks"); window.txn.showLocks(this.m_locks.length); } else { var output = new Array(); for (var i = 0; i < this.m_locks.length; i++) { var mylock = this.m_locks[i]; output[i] = mylock.lockType + " " + mylock.windowIndex + mylock.frame; } window.txn.showLocks(this.m_locks.length); debug.log(this.m_locks.length + " Locks", output); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Waits for a particular event to occur. * * @tparam String type the type of event, possible values include "open", "close", "sleep" * @tparam int windowIndex the window index (only applicable for open and close events). */ Player.prototype.waitFor = function(type, windowIndex, frame) { try { if (this._canAddLock(type, windowIndex, frame)) { this._addLock(type, windowIndex, frame); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Adds a lock to the lock chain. * @tparam String locType * @tparam int windowIndex * @private */ Player.prototype._addLock = function(lockType, windowIndex, frame) { // stderr.log("_addLock " + lockType + " " + windowIndex + " " + frame); var lock = new Object(); lock.lockType = lockType; lock.windowIndex = windowIndex; if (frame == null) { frame = ""; } lock.frame = frame; this.m_locks.push(lock); this._showLocks(); } /** * Before adding a lock, we should always * check if the event that will open the lock * has already occurred. * @private */ Player.prototype._canAddLock = function(lockType, windowIndex, frame) { try { var checkType = null; // stderr.log("canAddLock " + lockType + " " + windowIndex + " " + frame); // to add a lock to wait for window open/close, // if there is already a lock to wait for window close/open (opposite event), // then we can safely add it if (lockType == "open") { checkType = "close"; } else if (lockType == "close") { // to add an wait on window close, // if there is already a lock on window open, // then we can safely add it. checkType = "open"; } for (var i = 0; i < this.m_locks.length; i++) { var lock = this.m_locks[i]; if (lock.lockType == checkType && lock.windowIndex == windowIndex) { return true; } } // it doesn't make sense to add a lock that // waits for a window open, but it is already opened. if (lockType == "open") { // to add a lock to wait for window open // the window should not already exist. return this.getBrowserAt(windowIndex) == null; } else if (lockType == "close") { // to add a lock to wait for window close // the window should be currently active. return this.getBrowserAt(windowIndex) != null; } // no other conditions, allow to add. return true; } catch (e) { this._handleException(arguments.callee, e); } } /** * Removes a lock from the lock chain. * @tparam String lockType * @tparam int windowIndex * @private */ Player.prototype._removeLock = function(lockType, windowIndex, frame) { try { // stderr.log("_removeLock " + lockType + " " + windowIndex + " " + frame); if (frame == null) { frame = ""; } for (var i = 0; i < this.m_locks.length; i++) { var lock = this.m_locks[i]; // stderr.log("show lock " + i + " " + lock.lockType + " " + lock.windowIndex + " " + lock.frame); if (lock.lockType == lockType && lock.windowIndex == windowIndex && lock.frame == frame) { this.m_locks.splice(i, 1); break; } } this._showLocks(); } catch (e) { this._handleException(arguments.callee, e); } } /** * Removes a lock and continue plays the web transaction. * @tparam String lockType * @tparam int windowIndex * @protected */ Player.prototype._unlock = function(lockType, windowIndex, frame) { try { this._removeLock(lockType, windowIndex, frame); this.tryContinue(); } catch (e) { this._handleException(arguments.callee, e); } } /** * Runs the next step, because time out has been elapsed. * @private */ Player.prototype._runStepAfterTimeout = function() { try { // when this function is called, // we have a lock that is not opened, // usually a load lock will cause this. // unopen a load lock stderr.log("Time out, remove a load or traffic lock."); this._wake(); for (var i = 0; i < this.m_locks.length; i++) { var lock = this.m_locks[i]; if (lock.lockType == "load") { this._unlock('load', lock.windowIndex, lock.frame); break; } } if (i == this.m_locks.length) { for (var i = 0; i < this.m_locks.length; i++) { if (lock.lockType == "traffic") { this._unlock('traffic', lock.windowIndex); break; } } } // this.locks.splice(0, this.locks.length); // this.runStep(); } catch (e) { this._handleException(arguments.callee, e); } } /** * Tries to continue the playback after time out (ms). */ Player.prototype.tryContinue = function() { try { if (this.m_pauseMode) { return; } if (this.m_locks.length == 0) { this._clearTimeout(); this.m_timeoutId = window.setTimeout("if (player) {player._runStep();}", 0); } else if (this.m_timeoutPeriod > 0) { // this should be configurable this._clearTimeout(); this.m_timeoutId = window.setTimeout("if (player) {player._runStepAfterTimeout();}", this.m_timeoutPeriod); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Gets the amount of time to sleep in milliseconds. * @private */ Player.prototype._getSleepTime = function() { if (this.m_runMode == "Run") { return 600; } else if (this.m_runMode == "Walk") { return 2000; } else if (this.m_runMode == "Crawl") { return 10000; } else { return null; } } /** * Sleeps for a specific amount of time, * or default amount of sleep time (depend on run or walk mode) * @tparam int time time to sleep in milliseconds. */ Player.prototype.sleep = function(time) { try { var sleepTime = time ? time :this._getSleepTime(); if (sleepTime) { // create a lock to be opened by wake() // in the future after specific amount of sleep time. this._clearSleepTimeout(); // debug.log("Sleeping for " + sleepTime + " milliseconds"); this.waitFor("sleep", 0); var that = this; this.m_sleepTimeoutId = window.setTimeout("if (player) {player._wake();}", sleepTime); } else { this._onpause(); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Wakes up from sleeping. * @private */ Player.prototype._wake = function() { this._clearSleepTimeout(); this.tryContinue(); } /** * Exception handling. * @private */ Player.prototype._handleException = function(func, e) { handleException(Player, func, e); } /** * Displays all the steps. * @private */ Player.prototype._showSteps = function() { try { if (this.m_stepIndex >= 0 && this.m_stepIndex < this.m_steps.length) { var str = this.m_steps[this.m_stepIndex].toString(); stderr.log("Step " + (this.m_stepIndex), str.split("\n")); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Abstract method for performing browser specific actions when playback starts. * @protected */ Player.prototype.doStart = function() { alert(getFunctionName(Player, arguments.callee) + " is not implemented."); } /** * Abstract method for performing browser specific actions when playback stops. * @protected */ Player.prototype.doStop = function() { alert(getFunctionName(Player, arguments.callee) + " is not implemented."); } /** * A simple play back engine does not need to record anything. igore * @tparam String tags * @tparam map attr * @tparam bool genId */ Player.prototype.createNode = function(tags, attr, genId) { // do nothing } /** * A simple play back engine does not need to record anything. igore * @tparam XMLElement node * @tparam int time */ Player.prototype.recordNode = function(node, time) { // do nothing } /** * Adds listener. * @see IEBrEventListener * @see IEHTTPEventListener * @tparam EventListener listener */ Player.prototype.addListener = function(listener) { try { if (listener) { var name = listener.getName(); if (false) { } else if (name.indexOf("BrEventListener") != -1) { this.m_brListener = listener; } else if (name.indexOf("HTTPEventListener") != -1) { this.m_httpListener = listener; } else if (name.indexOf("DomEventListener") != -1) { // do not add this; } else { throw "Unknown Listener " + name; } } } catch (e) { this._handleException(arguments.callee, e); } } /** * @class Recorder. * A web transaction Recorder. */ function Recorder() { } /** * Inits members. * @private */ Recorder.prototype.init = function() { try { /** * The set of functions to call when recording stops. * @private */ this.m_stopFuncs = []; this.m_setPasswordFunc = function(index) { return null; } /** * The trace mode. */ this.m_traceMode = false; /** * The starting url. */ this.m_startingUrl = null; /** * The record password mode. */ this.m_maskPassword = false; this.m_highlight = false; this.m_debugMode = false; /** * The txn name. */ this.m_name = "New Transaction"; this.setTimer = function(timer) { this.m_timer = timer; } this.getTimer = function() { return this.m_timer; } this.setControlListeners(true); this.getTxnManager = function() { return this.m_eventsManager.getTxnManager(); } this.getNodeWithId = function(id) { return this.m_eventsManager.getNodeWithId(id); } this.createNode = function(tag, attrs, genId) { return this.m_eventsManager.createNode(tag, attrs, genId); } this.writeNode = function(node, time) { return this.m_eventsManager.writeNode(node, time); } this.getWindowIndex = function(win) { return this.m_brListener.getWindowIndex(win); } /** * Transforms the recording XML into various output. * @tparam String xsl the location of the XSLT transformation. * @treturn IXMLElement or null * @private */ this._getTransformedRecording = function(xsl) { return this.m_eventsManager.getTxnManager().transform(xsl).documentElement; } } catch (e) { this._handleException(arguments.callee, e); } } /** * Starts recording. */ Recorder.prototype.start = function() { try { if (this.m_httpListener) { this.m_httpListener.setTraceMode(this.getTraceMode()); } if (this.m_brListener) { this.m_brListener.setStartingUrl(this.getStartingUrl()); } if (this.m_domListener) { this.m_domListener.setMaskPasswordMode(this.getMaskPasswordMode()); this.m_domListener.setHighlightMode(this.getHighlightMode()); } if (this.m_httpListener) { this.m_httpListener.start(); } if (this.m_brListener) { this.m_brListener.start(); } if (this.m_domListener) { this.m_domListener.start(); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Stops recording. */ Recorder.prototype.stop = function () { this.abort(); } /** * Aborts recording. */ Recorder.prototype.abort = function () { try { if (this.m_domListener) { this.m_domListener.stop(); } if (this.m_brListener) { this.m_brListener.stop(); } if (this.m_httpListener) { this.m_httpListener.stop(); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Specifies the ownership of listeners. * @tparam bool mode if true (default), this object is responsible for * creating/starting/stopping the listeners. if false, the listeners are * managed by something else, and must be added explicitly. * * @see #addListener */ Recorder.prototype.setControlListeners = function(mode) { this.m_controlListeners = mode; } /** * Specifies the analyzer object. * Analyzer analyzer knows how to analyze the recorded events. * @tparam Analyzer analyzer */ Recorder.prototype.setAnalyzer = function(analyzer) { this.m_analyzer = analyzer; } /** * Performs analysis. * @private */ Recorder.prototype.analyze = function() { if (this.m_eventsManager) { var xmlDoc = this.m_eventsManager.getXMLDocument(); if (xmlDoc) { this.m_analyzer.process(xmlDoc); } } } /** * Adds listener. * @tparam EventListener listener * * Client should call addListener if it also calls * setControlListeners(false). This way, client controls * the listeners. * * @see #setControlListeners * * Currently only supports the following listeners. * @see IEBrEventListener, FFBrEventListener, IEDomEventListener, * FFDomEventListener, IEHTTPEventListener, FFHTTPEventListener */ Recorder.prototype.addListener = function(listener) { try { if (listener) { var name = listener.getName(); if (false) { } else if (name.indexOf("BrEventListener") != -1) { this.m_brListener = listener; } else if (name.indexOf("HTTPEventListener") != -1) { this.m_httpListener = listener; } else if (name.indexOf("DomEventListener") != -1) { this.m_domListener = listener; } else { throw "Unknown Listener " + name; } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Performs a set of callbacks. * @private */ Recorder.prototype._oncallback = function(funcArray) { for (var i = 0; i < funcArray.length; i++) { try { funcArray[i](); } catch (e) { this._handleException(arguments.callee, e); } } } /** * Exception handling. * @private */ Recorder.prototype._handleException = function (func, e) { handleException(Recorder, func, e); } /** * Called when recording stops. * @private */ Recorder.prototype._onstop = function() { this._oncallback(this.m_stopFuncs); } /** * Adds a callback function to be performed when recording stops. */ Recorder.prototype.addOnStopCallback = function(func) { this.m_stopFuncs.push(func); } /** * Sets the record password mode. * If true, recorder will not display password in the recorded transaction. * * The default value is true. * @treturn bool */ Recorder.prototype.setMaskPasswordMode = function(val) { this.m_maskPassword = val ? true : false; } /** * Gets the record password mode. * @treturn bool */ Recorder.prototype.getMaskPasswordMode = function() { return this.m_maskPassword; } /** * Sets the starting URL. * The default value is null. * @tparam String url the starting url. */ Recorder.prototype.setStartingUrl = function(url) { this.m_startingUrl = url; } /** * Gets the starting URL. * @treturn String the starting url. */ Recorder.prototype.getStartingUrl = function() { return this.m_startingUrl; } /** * Sets the transaction name. * @tparam String name */ Recorder.prototype.setTxnName = function(name) { this.m_name = name; } /** * Gets the transaction name. * @treturn String */ Recorder.prototype.getTxnName = function() { return this.m_name; } /** * Sets the trace mode. * The default value is false. * @tparam bool mode the new trace mode. */ Recorder.prototype.setTraceMode = function(mode) { this.m_traceMode = mode ? true : false; } /** * Gets the trace mode. * @treturn bool the trace mode. */ Recorder.prototype.getTraceMode = function() { return this.m_traceMode; } Recorder.prototype.setSetPasswordFunc = function(func) { this.m_setPasswordFunc = func; } Recorder.prototype.recordPassword = function(pwd) { this.m_setPasswordFunc(pwd); } Recorder.prototype.setHighlightMode = function(mode) { this.m_highlight = mode ? true : false; } Recorder.prototype.getHighlightMode = function() { return this.m_highlight; } Recorder.prototype.setDebugMode = function(mode) { this.m_debugMode = mode ? true : false; } Recorder.prototype.getDebugMode = function() { return this.m_debugMode; } /** * Event listeners call this method when going to a URL needs to be recorded. * @tparam WebBrowser2 frame * @tparam String url * @tparam int time */ Recorder.prototype.onGotoUrl = function(frame, url, time) { try { var attrs = { "type": "open", "newValue": Util.escapeQuote(url), "windowIndex":this.m_brListener.getBrowserIndex(frame), "time": time }; var node = this.createNode("action", attrs, false); this.writeNode(node, time); } catch (e) { this._handleException(arguments.callee, e); } } /** * Event listeners call this method when a popup window is opened. * @tparam int windowIndex * @tparam int time */ Recorder.prototype.onNewWindow2 = function(windowIndex, time) { try { if (windowIndex > 0) { var attrs = { "type":Constants.waitForPopUp, "windowIndex":windowIndex, "time": time }; var node = this.createNode("action", attrs, false); this.writeNode(node, time); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Event listeners call this method when a popup window is closed. * @tparam int windowIndex * @tparam int time */ Recorder.prototype.onQuit = function(windowIndex, time) { try { // need to wait for child windows to close, // never need to wait for the master window to close. if (windowIndex && windowIndex > 0) { var attrs = { "type":Constants.waitForPopUpClose, "windowIndex":windowIndex, "time":time }; var node = this.createNode("action", attrs, false); this.writeNode(node, time); } if (this.m_brListener && this.m_brListener.getWindowCount() == 0) { this._onstop(); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Event listeners call this method when a DOM event is raised. * @tparam map attributes * @tparam int time */ Recorder.prototype.onDOMEvent = function(attributes, time) { try { var node = this.createNode("action", attributes, true); this.writeNode(node, time); return node; } catch (e) { this._handleException(arguments.callee, e); } } var _last_logged_evt = null; var _last_logged_msg = null; var _last_type = null; var _startTime = (new Date()).getTime(); function log(e) { if (messages && messages.getIsEnabled() && e) { var t = new StringBuffer(); var ms = []; if (true) { t.append(e.type + " "); for (var p in e) { if (e[p] != null && e[p] != false && e[p] != 0) { if (p == "screenX" || p == "screenY" || p == "clientX" || p == "clientY") { } else if (p == "boundElements") { stderr.log(e[p].name); ms[ms.length]= p + ":" + e[p].toString(); } else { ms[ms.length]= p + ":" + e[p]; } } } if (e.srcElement) { var tagName = (e.srcElement.tagName) ? e.srcElement.tagName.toLowerCase() : "N/A"; ms[ms.length] = tagName; } } /* if (e.altKey) m.append("altKey:" + e.altKey + " "); if (e.altLeft) m.append("altLeft:" + e.altLeft + " "); if (e.button) m.append("button:" + e.button + " "); if (e.cancelBubble) m.append("cancelBubble:" + e.cancelBubble + " "); if (e.clientX) m.append("clientX:" + e.clientX + " "); if (e.clientY) m.append("clientY:" + e.clientY + " "); if (e.ctrlKey) m.append("ctrlKey:" + e.ctrlKey + " "); if (e.ctrlLeft) m.append("ctrlLeft:" + e.ctrlLeft + " "); if (e.keyCode) m.append("e.keyCode:" + e.keyCode + " "); if (e.reason) m.append("reason:" + e.reason + " "); if (e.propertyName) m.append("propertyName:" + e.propertyName + " "); if (e.fromElement) m.append("fromElement:" + e.fromElement.tagName.toLowerCase() + " "); if (e.propertyName) m.append("property:" + e.propertyName + " "); if (e.propertyName) t.append("property:" + e.propertyName + " "); if (e.srcElement) { var tagName = (e.srcElement.tagName) ? e.srcElement.tagName.toLowerCase() : "N/A"; m.append("srcElement:" + tagName + " "); t.append(tagName + " "); if (e.srcElement.id) t.append("id:" + e.srcElement.id + " "); if (e.srcElement.name) t.append("name:" + e.srcElement.name + " "); // if (e.srcElement.innerText) m.append("text:" + e.srcElement.innerHTML + " "); } if (e.type == "mouseover" || e.type == "mouseenter") { if (e.srcElement && e.srcElement.children.length == 0) { // t.append("text:" + e.srcElement.innerHTML + " "); } // t.append("background:" + e.srcElement.runtimeStyle.backgroundColor + " "); //t.append("activeElement:" + e.srcElement.document.activeElement.innerText + " "); } if (e.type == "mouseout") { // t.append("activeElement:" + e.srcElement.document.activeElement.innerText + " "); } if(e.target) { if(netscape) netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect UniversalBrowserRead UniversalBrowserWrite'); var tagName = (e.target.tagName) ? e.target.tagName.toLowerCase() : "N/A"; m.append("target: " + tagName + " "); t.append("target: " + tagName + " "); //m.append("target: " + recorder.xPathGen.getXPath(e.target)+" "); if (e.target.hasAttribute&&e.target.hasAttribute("id")) t.append("id: " + e.target.getAttribute("id") + " "); if (e.target.hasAttribute&&e.target.hasAttribute("name")) t.append("name: " + e.target.getAttribute("name") + " "); } if(e.explicitOriginalTarget ) { if(netscape) netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect UniversalBrowserRead UniversalBrowserWrite'); var tgt = e.explicitOriginalTarget; //PlayerFF._printDOM(tgt); var tagName = (tgt.tagName) ? tgt.tagName.toLowerCase() : "N/A"; m.append("otarget: " + tagName + " "); t.append("otarget: " + tagName + " "); //m.append("otarget: " + recorder.xPathGen.getXPath(tgt)+" "); if (tgt.hasAttribute&&tgt.hasAttribute("id")) t.append("ot id: " + tgt.getAttribute("id") + " "); if (tgt.hasAttribute&&tgt.hasAttribute("name")) t.append("ot name: " + tgt.getAttribute("name") + " "); } if (e.keyCode) m.append("e.keyCode: " + e.keyCode + " "); if (e.fromElement) m.append("fromElement: " + e.fromElement.tagName.toLowerCase() + " "); */ var ts = t.toString(); messages.log(ts, ms); _last_logged_evt = ts; _last_logged_msg = ms; } } /** * @class EventsManager * Manages a collection of events that happens during recording or playback. */ function EventsManager() { } /** * Initializes members. * @private */ EventsManager.prototype.init = function() { this.m_eventId = 0; this.m_id2Nodes = []; this.m_nodes = []; this.m_tnManager = null; } /** * Gets a node with a particular id. * @tparam int id the node id. * @treturn IXMLElement the node with id or null. */ EventsManager.prototype.getNodeWithId = function (id) { return this.m_id2Nodes[id]; } /** * Creates a node. * @tparam String tag the node tag. * @tparam Object attrs a hashtable of name value pairs. * @tparam bool genId true means the node should have nodeId. * @treturn IXMLElement the newly created node, the node is not added. * @see #writeNode */ EventsManager.prototype.createNode = function(tag, attrs, genId) { try { var node = this.getTxnManager().createElement(tag); this.m_recProcessor.addProperties(node, tag, attrs, this.getTxnManager()); if (genId >= 0) { node.setAttribute("nodeId", this.m_eventId); this.m_id2Nodes[this.m_eventId] = node; this.m_eventId++; } return node; } catch (e) { this._handleException(arguments.callee, e); } } /** * Writes a node. * @tparam IXMLElement node the node to be appended. * @tparam int time the time of the event. */ EventsManager.prototype.writeNode = function(node, time) { try { if (node) { this.m_nodes.push({"time": time, "node" : node}); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Gets the XML Document. * @treturn IXMLDocument */ EventsManager.prototype.getXMLDocument = function() { return this.getTxnManager().documentElement(); } /** * Gets the object that manages the transaction. * @treturn TxnManager */ EventsManager.prototype.getTxnManager = function() { return this.m_tnManager; } /** * Gets the array of nodes managed by this transaction. * @treturn Array */ EventsManager.prototype.getNodes = function() { return this.m_nodes; } /** * Sorts the node array. * @tparam Function func the sorting function. */ EventsManager.prototype.sort = function(func) { this.m_nodes.sort(func); } /* we don't need this for now, is because the Timer object will always return unique time. EventsManager.prototype.sortFunc = function() { var tags = { "BN" : 0, "APP": 1, "Redirect": 2, "NC" : 3, "DC" : 4 } // sorting two sources var timeSortFunc = function(a,b) { if (a.time != b.time) { return a.time - b.time; } else { var a1 = tags[a.node.tagName]; var b1 = tags[b.node.tagName]; if (a1 && b1) { return a1 - b1; } else { return 0; } } }; } */ /** * Handles exception. * @private */ EventsManager.prototype._handleException = function(func, e) { handleException(EventsManager, func, e); } /** * @class IEEventsManager * Manages a collection of events that specific to Internet Explorer. */ function IEEventsManager(mediator) { this.init(mediator); } IEEventsManager.prototype = new EventsManager(); /** * Initialize members. * @private */ IEEventsManager.prototype.init = function(mediator) { EventsManager.prototype.init.call(this); this.m_htmlHelper = new ActiveXObject("OraBcnTxnUtil2.HTMLHelper"); this.m_recProcessor = new IERecProcessor(); this.m_tnManager = new TxnManager(); this.m_mediator = mediator; } /** * Event listener (Collegue) calls this method when a BeforeNavigate2 event occurs. * @tparam IWebBrowser2 frame * @tparam String url * @tparam int flags * @tparam String targetFrameName * @tparam String postdata * @tparam String headers * @tparam int time */ IEEventsManager.prototype.onBeforeNavigate2 = function(frame, url, flags, targetFrameName, postdata, headers, time, windowIndex) { try { // var windowIndex = this.m_mediator.getBrowserIndex(frame); var attrs = { "url":url, "flags":flags, "targetFrameName":targetFrameName, "postdata": postdata, "headers": headers, "windowIndex": windowIndex, "time": time }; var node = this.createNode("BN", attrs, true); this.writeNode(node, time); return node; } catch (e) { this._handleException(arguments.callee, e); } } /** * Event listener calls this method when a NavigateComplete2 event occurs. * @tparam IWebBrowser2 frame * @tparam String url * @tparam int time */ IEEventsManager.prototype.onNavigateComplete2 = function(frame, url, time, windowIndex) { try { var name = ""; try { var doc = this.m_mediator._getDocument(frame); if (doc) { name = doc.parentWindow.name; } } catch (e) { // ignore this._handleException(arguments.callee, e); } // var windowIndex = this.m_mediator.getBrowserIndex(frame); var attrs = { "url":url, "time":time, "windowIndex": windowIndex, "frame":name }; var node = this.createNode("NC", attrs, true); this.writeNode(node, time); return node; } catch (e) { this._handleException(arguments.callee, e); } } /** * Event listener calls this method when a DocumentComplete event occurs. * @tparam IWebBrowser2 frame * @tparam String url * @tparam int time */ IEEventsManager.prototype.onDocumentComplete = function(frame, url, time, title, windowIndex) { try { var charset = null; var name = null; try { if (frame.LocationURL == url) { frame.PutProperty("navState", "DC"); } var doc = this.m_mediator._getDocument(frame); if (doc) { charset = doc.charset; name = doc.parentWindow.name; } } catch (e) { // ignore this._handleException(arguments.callee, e); } // var windowIndex = this.m_mediator.getBrowserIndex(frame); var attrs = { "url":url, "time":time, "charset":charset, "title":title, "windowIndex": windowIndex, "frame":name }; var node = this.createNode("DC", attrs, true); this._myload(frame, node, time); this.writeNode(node, time); return node; } catch (e) { this._handleException(arguments.callee, e); } } /** * Event listener calls this method when a NavigateError event occurs. * @tparam IWebBrowser2 frame * @tparam String url * @tparam String targetFrameName * @tparam int statusCode * @tparam int time */ IEEventsManager.prototype.onNavigateError = function(frame, url, targetFrameName, statusCode, time, windowIndex) { try { var attrs = { "url":url, "time":time, "targetFrameName":targetFrameName, "statusCode":statusCode }; if (statusCode != 200) { var node = this.createNode(Constants.BROWSER_ERROR, attrs, true); this.writeNode(node, time); return node; } else { return null; } } catch (e) { this._handleException(arguments.callee, e); } } /** * Handles window.onload event, this event is triggered during DocumentComplete. * @private */ IEEventsManager.prototype._myload = function (frame, parentNode, time) { try { var doc = this.m_mediator._getDocument(frame); var frames = null; var framesLength = 0; try { // keep this if (doc) { frames = doc.frames; framesLength = frames.length; } } catch (e) { // keep this // ignore } if (frames) { for (var i = 0; i < framesLength; i++) { // if framesLength is non zero, then we should be able to access the frames array. var f = frames[i]; var url = f.src; var attrs = { "url":url, "name":f.name, "fid":f.id, "foundId": parentNode.getAttribute("nodeId"), "time": time }; var node = this.createNode("FRAME", attrs, true); this.writeNode(node, time); } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Gets the HTML helper object. * @treturn IHTMLHelper */ IEEventsManager.prototype.getHTMLHelper = function() { return this.m_htmlHelper; } /** * Handles exception. * @private */ IEEventsManager.prototype._handleException = function(func, e) { handleException(IEEventsManager, func, e); }; /** * @class TxnManager. * A transaction serializer, responsible for reading/writing transaction objects. */ function TxnManager(strArr) { var str = strArr ? strArr : null; /** * XML Document object. * @private. */ this.m_xmlDoc = null; // constructor // if (window.ActiveXObject) { // Internet Explorer Implementation this.m_xmlDoc = createDOMDocument(); this.m_xmlDoc.async = false; if (str) { if (!this.m_xmlDoc.loadXML(str)) { var err = this.m_xmlDoc.parseError; if (err) { throw new Error("Parse Error, reason:" + err.reason + " line:" + err.line); } } } if (!this.m_xmlDoc.documentElement) { this.m_xmlDoc.documentElement = this.m_xmlDoc.createElement("transaction"); } /* } else { // Mozilla Implementation if (str) { var parser = new DOMParser(); this.m_xmlDoc = parser.parseFromString(str, "text/xml"); var roottag = this.m_xmlDoc.documentElement; if (roottag.tagName == "parserError" || roottag.namespaceURI == "http://www.mozilla.org/newlayout/xml/parsererror.xml") { var serializer = new XMLSerializer(); var err = serializer.serializeToString(this.m_xmlDoc); if (err) { throw new Error("Parse Error, reason:" + err.reason + " line:" + err.line); } this.m_xmlDoc = null; } } if (!this.m_xmlDoc) { this.m_xmlDoc = document.implementation.createDocument("", "transaction", null); } } */ if (!this.m_xmlDoc) { throw new Error("Cannot Create a Transaction Manager using XML"); } } /** * Sets the transaction name. * @tparam String name */ TxnManager.prototype.setTxnName = function(name) { this.m_xmlDoc.documentElement.setAttribute("name", name); } /** * Creates an action node. * @treturn XMLElement a new action node. * * Note: * the node is not inserted into the XML. * @see #writeAction */ TxnManager.prototype.createAction = function() { return this.m_xmlDoc.createElement("action"); } /** * Creates a XML element with a specific tag name. * @treturn XMLElement a new element. * * Note: * the element is not inserted into the XML. * @see #writeNode */ TxnManager.prototype.createElement = function(tagName) { return this.m_xmlDoc.createElement(tagName); } /** * Creates a XML CDATA section with a specific value. * @treturn XMLCDATASection a new CDATA element. * * Note: * the element is not inserted into the XML. * @see #writeNode */ TxnManager.prototype.createCDATASection = function(value) { return this.m_xmlDoc.createCDATASection(value); } /** * Appends a node to the XML. * @tparam XMLElement node the node to append. */ TxnManager.prototype.writeNode = function(node) { this.m_xmlDoc.documentElement.appendChild(node); } /** * Reads all the actions. * @treturn String All the actions as JavaScript text. */ TxnManager.prototype.readActions = function() { // if (window.ActiveXObject) { var xslDoc = createDOMDocument(); xslDoc.async = false; xslDoc.load("js/xml2func.xsl"); var output = this.m_xmlDoc.transformNode(xslDoc); return output; /* } else { var xslDoc = document.implementation.createDocument("", "", null); xslDoc.async = false; xslDoc.load("js/xml2func.xsl"); var objXSLTProc = new XSLTProcessor(); objXSLTProc.importStylesheet(xslDoc); var newDoc = objXSLTProc.transformToDocument(this.m_xmlDoc); if (newDoc) { return newDoc.documentElement.textContent; } } */ } TxnManager.prototype.readActions2 = function() { var output = (new XML2Func()).process(this.m_xmlDoc.documentElement); return output; } /** * Returns the XML representation of the web transaction. * @treturn XMLDocument */ TxnManager.prototype.documentElement = function() { return this.m_xmlDoc.documentElement; } /** * Transforms XML document using the XSL. * @tparam String xsl the XSL path. * @treturn String the content after transformation. */ TxnManager.prototype.transform = function(xsl) { // if (window.ActiveXObject) { var xslDoc = createDOMDocument(); xslDoc.async = false; xslDoc.load(xsl); // var output = this.m_xmlDoc.transformNode(xslDoc); var result = createDOMDocument(); result.async = false; result.validateOnParse = true; this.m_xmlDoc.transformNodeToObject(xslDoc, result); return result; /* } else { var xslDoc = document.implementation.createDocument("", "", null); xslDoc.async = false; xslDoc.load(xsl); var objXSLTProc = new XSLTProcessor(); objXSLTProc.importStylesheet(xslDoc); return objXSLTProc.transformToDocument(this.m_xmlDoc); } */ } /** * Transforms XML document using the XSL. * @tparam String xsl the XSL path. * @treturn XMLDocument */ TxnManager.prototype.transform2 = function(xsl) { // if (window.ActiveXObject) { var xslDoc = createDOMDocument(); xslDoc.async = false; xslDoc.load(xsl); return this.m_xmlDoc.transformNode(xslDoc); // } // return null; } /** * Returns the serialized form of the XML, with a \\n between > and <. * @treturn String the web transaction as XML. */ TxnManager.prototype.toString = function() { // if (window.ActiveXObject) { var str = this.m_xmlDoc.xml; var reg = />\n<"); /* } else { var serializer = new XMLSerializer(); var str = serializer.serializeToString(this.m_xmlDoc); var reg = />\n<"); } */ } /** * @class Analyzer. * * The base analyzer class. */ function Analyzer() { } /** * Adds arbitrary number of analysing modules. * They are processed using arguments */ Analyzer.prototype.addChildren = function() { this.m_children = []; for (var i = 0; i < arguments.length; i++) { var analyzer = arguments[i]; require("process", analyzer); this.m_children.push(analyzer); } } /** * Base class processes the XML node. * @tparam XMLElement node */ Analyzer.prototype.process = function(node) { for (var i = 0; i < this.m_children.length; i++) { this.m_children[i].process(node); } } /** * @class IERecProcessor. * * A base class that performs a single processing step on a recorded web transaction in Internet Explorer. */ function IERecProcessor() { } IERecProcessor.prototype = new Analyzer(); IERecProcessor.prototype.isAPP = function(node) { return node.tagName == "APP"; } IERecProcessor.prototype.isNC = function(node) { return node.tagName == "NC"; } IERecProcessor.prototype.isDC = function(node) { return node.tagName == "DC"; } IERecProcessor.prototype.isBN = function(node) { return node.tagName == "BN"; } IERecProcessor.prototype.isAction = function(node) { return node.tagName == "action"; } IERecProcessor.prototype.isRedirect = function(node) { return node.tagName == "Redirect"; } IERecProcessor.prototype.isFrame = function(node) { return node.tagName == "FRAME"; } IERecProcessor.prototype.isHtml = function(node) { return node.tagName == "html"; } IERecProcessor.prototype.isForm = function(node) { return node.tagName == "form"; } IERecProcessor.prototype.getTagName = function(node) {return node.tagName; } IERecProcessor.prototype.getWindowIndex = function(node) { var windowIndexStr = node.getAttribute("windowIndex"); if (windowIndexStr == "") { return 0; } else { var windowIndex = parseInt(windowIndexStr); // windowIndex is suppressed when it should be 0. if (windowIndex > 0) { // usual case } else { // the NaN case. Cannot optimize this because // NaN > 0 is false // NaN < 0 is false windowIndex = 0; } } return windowIndex; } IERecProcessor.prototype.getPreviousNode = function(node, tagName, func) { var prev = node.previousSibling; while (prev) { if (tagName == prev.tagName) { if (!func) { return prev; } else if (func.call(this, prev)) { return prev; } } prev = prev.previousSibling; } return null; } IERecProcessor.prototype.getNextNode = function(node, tagName, func) { var next = node.nextSibling; while (next) { if (tagName == next.tagName) { if (!func) { return next; } else if (func.call(this, next)) { return next; } } next = next.nextSibling; } return null; } IERecProcessor.prototype.getFormAction = function(node) { return this._getProperty(node, "formAction", "value"); } IERecProcessor.prototype.getFormName = function(node) { return this._getProperty(node, "formName", "value"); } IERecProcessor.prototype.getFormId = function(node) { return this._getProperty(node, "formId", "value"); } IERecProcessor.prototype.getFormMethod = function(node) { return this._getProperty(node, "method", "value"); } IERecProcessor.prototype.getChildNode = function(node, name) { return node.selectSingleNode(name); } IERecProcessor.prototype.getTime = function(node) { return parseInt(node.getAttribute("time")); } IERecProcessor.prototype.getURL = function(node) { return this._getProperty(node, "url", "value"); } IERecProcessor.prototype.getFrame = function(node) { return node.getAttribute("frame"); } IERecProcessor.prototype.removeURL = function(node) { return this._removeProperty(node, "url", "value"); } IERecProcessor.prototype.setURL = function(node, value) { this._setProperty(node, "url", "value", value); } IERecProcessor.prototype.getPropertyNode = function(node, prop) { var childNodes = node.childNodes; if (childNodes) { for (var i = 0; i < childNodes.length; i++) { var childNode = childNodes[i]; if (childNode.tagName == prop) { return childNode; } } } return null; } IERecProcessor.prototype.getProperty = function(node, prop) { return this._getProperty(node, prop, "value"); } IERecProcessor.prototype.setProperty = function(node, prop, value) { this._setProperty(node, prop, "value", value); } IERecProcessor.prototype.appendProperty = function(node, prop, value) { var existingProp = this.getProperty(node, prop); if (existingProp && existingProp.length > 0) { var propBuffer = new StringBuffer(); propBuffer.append(existingProp, ","); propBuffer.append(value); this.setProperty(node, prop, propBuffer.toString()); } else { this.setProperty(node, prop, value); } } IERecProcessor.prototype.appendNameValueProperty = function(node, prop, name, value) { var existingProp = this.getProperty(node, prop); if (existingProp && existingProp.length > 0) { var propBuffer = new StringBuffer(); propBuffer.append(existingProp, ","); var newValue = encodeNameValuePairDelimiters(value); if (existingProp.indexOf(name + "=") != -1) { var pairs = existingProp.split(","); var newPair = name + "=" + encodeNameValuePairDelimiters(value); for (var i in pairs) { if (pairs[i] == newPair) { return false; } } stderr.log("entry " + name + " is already added."); } propBuffer.append(name + "=" + encodeNameValuePairDelimiters(value)); this.setProperty(node, prop, propBuffer.toString()); } else { var newEntry = name + "=" + encodeNameValuePairDelimiters(value); this.setProperty(node, prop, newEntry); } return true; } IERecProcessor.prototype.getName = function(node) { return this._getProperty(node, "name", "value"); } IERecProcessor.prototype.getTargetFrameName = function(node) { return node.getAttribute("targetFrameName"); } IERecProcessor.prototype.removeTargetFrameName = function(node) { node.removeAttribute("targetFrameName"); } IERecProcessor.prototype._getProperty = function (node, nodeName, attrName) { var childNodes = node.childNodes; if (childNodes) { for (var i = 0; i < childNodes.length; i++) { var childNode = childNodes[i]; if (childNode.tagName == nodeName) { return childNode.getAttribute(attrName); } } } return null; } IERecProcessor.prototype._setProperty = function (node, propertyName, attrName, value) { var childNodes = node.childNodes; if (childNodes) { for (var i = 0; i < childNodes.length; i++) { var childNode = childNodes[i]; if (childNode.tagName == propertyName) { childNode.setAttribute(attrName, value); return; } } var attrs = {}; attrs[propertyName] = value; var newNode = this.addProperties(node, node.tagName, attrs, this.m_mediator.getTxnManager()); } } IERecProcessor.prototype._removeProperty = function (node, nodeName, attrName) { var childNodes = node.childNodes; if (childNodes != null) { for (var i = 0; i < childNodes.length; i++) { var childNode = childNodes[i]; if (childNode.tagName == nodeName && childNode.getAttribute(attrName) != null) { node.removeChild(childNode); } } } } IERecProcessor.prototype.addTransactionProperty = function (node, propertyName, propertyValue, delim) { if (node) { var existingValue = node.getAttribute(propertyName); if (existingValue && existingValue.length > 0) { node.setAttribute(propertyName, existingValue + delim + propertyValue); } else { node.setAttribute(propertyName, propertyValue); } } } IERecProcessor.prototype.getAncestor = function(node, tagName) { var pNode = node.parentNode; while (pNode) { var tag = pNode.tagName; if (tag == tagName) { return pNode; } else if (tag == "transaction") { // root return null; } pNode = pNode.parentNode; } return null; } IERecProcessor.prototype.foreachNode = function(nodes, func) { for (var i = 0; i < nodes.length; i++) { func.call(this, nodes[i]); } } IERecProcessor.prototype.foreachNodeReverse = function(nodes, func) { for (var i = nodes.length - 1; i >= 0; i--) { func.call(this, nodes[i]); } } IERecProcessor.prototype.addProperties = function(node, tag, attrs, txnManager) { if (attrs) { for (var prop in attrs) { var value = attrs[prop]; if (value == null) { continue; } else if (prop == "windowIndex") { if (parseInt(value) > 0) { node.setAttribute(prop, "" + value); } } else if (prop == "time" && value > 0) { node.setAttribute(prop, "" + value); } else if (prop == "parentId" && value >= 0) { node.setAttribute(prop, "" + value); } else if (prop == "targetFrameName" && value != "") { node.setAttribute(prop, value); } else if (prop == "frame" && value != "") { node.setAttribute(prop, value); } else if (prop == "htmlId" && value >= 0) { node.setAttribute(prop, value); } else if (prop == "DCId" && value >= 0) { node.setAttribute(prop, value); } else if (prop == "state" && value != "") { node.setAttribute(prop, value); } else if (prop == "formParentId" && value >= 0) { node.setAttribute(prop, value); } else if (tag == "action" && value != "") { node.setAttribute(prop, value); } else if (tag == "step") { node.setAttribute(prop, value); } else if (tag == Constants.FAIL_FOUND) { node.setAttribute(prop, value); } else if (tag == Constants.SUC_NOT_FOUND) { node.setAttribute(prop, value); } else if (tag == Constants.BROWSER_ERROR) { node.setAttribute(prop, value); } else if (tag == Constants.DOM_ERROR) { node.setAttribute(prop, value); } else if (tag == Constants.GROUP) { node.setAttribute(prop, value); } else if (tag == "input") { if (value == null || value == undefined) { stderr.log('A form element value is null or undefined'); value = ""; } node.setAttribute(prop, value); } else if (value != "") { var childElement = txnManager.createElement(prop); childElement.setAttribute("value", value); node.appendChild(childElement); } } } } IERecProcessor.prototype._getRegexMatch = function(htmlHelper, regex, htmlId, caseSensitive) { try { var matches = new Object(); htmlHelper.checkForRegExp2(regex, htmlId, caseSensitive, matches); if (matches.length > 0) { return matches[matches.length - 1]; } } catch (e) { this._handleException(arguments.callee, e); } return null; } IERecProcessor.prototype._getRegexMatches = function(htmlHelper, regex, htmlId, caseSensitive) { var matches = new Object(); try { htmlHelper.checkForRegExp2(regex, htmlId, caseSensitive, matches); return matches; } catch (e) { this._handleException(arguments.callee, e); } return matches; } IERecProcessor.prototype.validateDescriptor = function(htmlHelper, htmlNode, descriptor) { try { if (descriptor && htmlNode) { var htmlId = parseInt(htmlNode.getAttribute("htmlId")); var descPatn = descriptor.getPattern(); var descRawVal = descriptor.getMarkedValue(false); var match = this._getRegexMatch(htmlHelper, descPatn, htmlId, true); if (match == descRawVal) { return true; } else { /* stderr.log("validateDescriptor failed with"); stderr.log("pattern =" + descPatn); stderr.log("expected value=" + descRawVal); stderr.log("matched value=" + match); */ } } } catch (e) { this._handleException(arguments.callee, e); } return false; } IERecProcessor.prototype.applyDescriptor = function(htmlHelper, bnNode, htmlNode, descriptor) { try { if (this.validateDescriptor(htmlHelper, htmlNode, descriptor)) { var url = this.getURL(bnNode); // descVal is the actual value after encoding. var descVal = descriptor.getMarkedValue(); var dcId = htmlNode.getAttribute("DCId"); if (dcId > 0) { var dcNode = this.m_mediator.getNodeWithId(dcId); if (dcNode) { // must make sure there is a BN as its parent. var sourceBNNode = this.getAncestor(dcNode, "BN"); if (sourceBNNode) { if (url && url.length > 0 && descVal && descVal.length > 0) { var lastIndex = url.indexOf(descVal); // stderr.log("lastIndex" + lastIndex); if (lastIndex != -1) { var urlBuffer = new StringBuffer(); // append everything before urlBuffer.append(url.substr(0, lastIndex)); // append the descriptor (variable) name urlBuffer.append("[", descriptor.getName(), "]"); // append everything after urlBuffer.append(url.substr(lastIndex + descVal.length)); // update all the properties at once. this.setProperty(bnNode, "query_param", descVal); this.setURL(bnNode, urlBuffer.toString()); if (this.appendNameValueProperty(htmlNode, "regex", descriptor.getName(), descriptor.getPattern())) { this.appendProperty(htmlNode, "regmd", descriptor.getEncoding()); } this._setURLProcessed(bnNode); } } else if (descriptor) { // although this is a case we need to handle, this is not common. stderr.log("Cannot match " + descriptor.getPattern() + " to " + descriptor.getMarkedValue(false), true); } } else { stderr.log("Cannot locate a parent BN node for DC with nodeId " + dcId); } } else { stderr.log("Cannot find a DC node with nodeId " + dcId); } } else { stderr.log("Cannot find a DC node for html with nodeId " + htmlNode.getAttribute("nodeId")); } } else { stderr.log("Cannot find match " + bnNode.getAttribute("nodeId")); } } catch (e) { this._handleException(arguments.callee, e); } } IERecProcessor.prototype.isPlayable = function (node) { var requestMode = node.getAttribute(Constants.REQUEST_MODE); if (!requestMode) { return true; } else if (requestMode == Constants.SCRIPT_REDIRECT) { return true; } else if (requestMode == Constants.ONLOAD_SUBMIT) { return true; } else { return false; } } /** * Checks if a URL is processed or not. * @private */ IERecProcessor.prototype._isURLProcessed = function(node) { return (node.getAttribute("OK") == "true"); } /** * Mark a URL as processed. * @private */ IERecProcessor.prototype._setURLProcessed = function(node) { node.setAttribute("OK", "true"); } IERecProcessor.prototype._isActionProcessed = function(node) { return (node.getAttribute("OK") == "true"); } IERecProcessor.prototype._setActionProcessed = function(node) { node.setAttribute("OK", "true"); } IERecProcessor.prototype._getTimeOffset = function() { return (new Date()).valueOf() - this.m_startTime; } /** * Handle exceptions. * @private */ IERecProcessor.prototype._handleException = function (func, e) { handleException(IERecProcessor, func, e); } /** * @class IERecCauseAnalyzer. * This module analyzes the causality between browser events. This module figures out which BeforeNavigate, NavigateComplete2 and DocumentComplete events are related. This is not simple because the URL user sends out may or may not match the URL for the corresponding NavigateComplete2 or DocumentComplete event (due to 302 redirects). Consider the following events on the same request: An important goal is to make sure the five events E, D, C, B and A are all chained. To achieve this, consider the following four simpler rules: It is possible to have multiple request and redirect events (ie A, B, C, B', C', D, E, where B' and C' are additional redirects. (Oracle SSO) Implementing these rules separately will be inefficient, as DOM traversal can be expensive. An efficient algorithm is to apply these four rules in reverse chronological order, where canCause encapsulates the rules specified above.
for (var i = nodes.length - 1; i >= 0; i--) {
  var node = nodes[i];
  var prev = node.previousSibling;
  while (prev) {
    if (this.canCause(prev, node)) {
      prev.appendChild(node);
    }
    prev = prev.previousSibling;
  }
}
This algorithm performs the following transformation for the case above:






	
in to

  
   
    
     
      
     
    
   
  

Average performance should be ~O(n), unless most of the events are totally unrelated, in which case it is O(n^2). We did some performance analysis, because initially this single rule was slow. For a simple web transaction with about 10 user actions, the recorder generated about 200 events, and this algorithm took around 20 seconds to complete. Performance profiling suggests the reason is due to large number of DOM access (getting and comparing node tag name, attributes). With some simple optimization, this algorithm runs in the order of few hundred milliseconds for analyzing 200 events. This algorithm is greedy. There may be multiple causality chains for a given set of events. This either means we didn’t capture enough information or it doesn't matter. For example, in the following example, it really doesn't matter which BeforeNavigate event matches with which DocumentComplete event.
BeforeNavigate url="a.jsp" time=100
BeforeNavigate url="a.jsp" time=00”
DocumentComplete url="a.jsp" time=00”
DocumentComplete url="a.jsp" time=10
*/ function IERecCauseAnalyzer(mediator) { /** * @private */ this.m_mediator = mediator; /** * @private */ this.m_startTime = (new Date()).getTime(); } IERecCauseAnalyzer.prototype = new IERecProcessor(); /** * Analyzes the web transaction. Any changes are applied * to the XML node directly. * @tparam XMLNode node the recorded web transaction in XML format. */ IERecCauseAnalyzer.prototype.process = function(node) { try { this._stackTrace(arguments.callee, " start"); this.foreachNodeReverse(node.selectNodes("APP|DC|NC|Redirect"), this._processNode); // for each FRAME node, find its corresponding BN. this.foreachNodeReverse(node.selectNodes("FRAME"), this._processFrame); // this has to execute after this._processFrame, // this is because some bad BNs may have a corresponding FRAME // association with a FRAME node. this.foreachNodeReverse(node.selectNodes("BN"), this._removeBadBN); // connect FRAME node and its corresponding BN and move it under the top level BN. var associateFrameFunc = function (foundIdNode) { var frameBNNode = this.getAncestor(foundIdNode, "BN"); if (frameBNNode) { var url = this.getURL(frameBNNode); /* No need to compare the URLs with the unsupported URLs * because it is already done in this._removeBadBN if (url == "about:blank") { // remove it frameBNNode.parentNode.removeChild(frameBNNode); } else if (url.indexOf("javascript:") == 0) { // remove it frameBNNode.parentNode.removeChild(frameBNNode); } else if (url.indexOf("res:") == 0) { // remove it frameBNNode.parentNode.removeChild(frameBNNode); } else */ { var foundId = foundIdNode.getAttribute("value"); var foundNode = this.m_mediator.getNodeWithId(foundId); if (foundNode) { var bnNode = this.getAncestor(foundNode, "BN"); if (bnNode) { bnNode.appendChild(frameBNNode); } else { debug.log("bnNode not found for foundId " + foundId); } } else { debug.log("foundNode not found " + foundId); } } } } this.foreachNode(node.selectNodes("//foundId"), associateFrameFunc); this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } } IERecCauseAnalyzer.prototype._processNode = function (node) { try { var prev = node.previousSibling; var parentId = node.getAttribute("parentId"); var isParentValid = parentId != null && parentId >= 0; var nodeURL = this.getURL(node); if (nodeURL) { nodeURL = nodeURL.toLowerCase(); } var windowIndex = this.getWindowIndex(node); var size = 0; while (prev) { if (isParentValid && parentId == prev.getAttribute("nodeId")) { prev.appendChild(node); return size; } if (node.getAttribute("nodeId") == "679" && prev.getAttribute("nodeId") == "670") { stderr.log("matching " + this._canCause(prev, node, nodeURL, windowIndex, null)); } if (this._canCause(prev, node, nodeURL, windowIndex, null)) { prev.appendChild(node); return size; } size++; prev = prev.previousSibling; } return 0; } catch (e) { this._handleException(arguments.callee, e); } } /** * TODO * @private */ IERecCauseAnalyzer.prototype._processFrame = function(node) { try { var prev = node.previousSibling; var parentId = node.getAttribute("parentId"); var nodeURL = this.getURL(node); if (nodeURL) { nodeURL = nodeURL.toLowerCase(); } var windowIndex = this.getWindowIndex(node); var nodeAttrs = {}; if (node.tagName == "FRAME") { // the foundId is the nodeId of the corresponding DC event, // this is not the DC event of the frame, but the // DC event of the parent frame. var foundId = parseInt(this.getProperty(node, "foundId")); if (foundId >= 0) { var dcNode = this.m_mediator.getNodeWithId(foundId); if (dcNode) { // the DC node should have come after the FRAME node // therefore, it should already have found a NC parent node. var bnNode = this.getAncestor(dcNode, "BN"); if (bnNode) { var bnNodeTime = parseInt(bnNode.getAttribute("time")); var dcNodeTime = parseInt(dcNode.getAttribute("time")); nodeAttrs["BNTime"] = bnNodeTime; nodeAttrs["DCTime"] = dcNodeTime; nodeAttrs["name"] = this.getProperty(node, "name"); } } } } var isParentValid = parentId != null && parentId >= 0; var size = 0; while (prev) { if (isParentValid && parentId == prev.getAttribute("nodeId")) { prev.appendChild(node); return size; } if (this._canCauseFrame(prev, node, nodeURL, windowIndex, nodeAttrs)) { prev.appendChild(node); return size; } size++; prev = prev.previousSibling; } return 0; } catch (e) { this._handleException(arguments.callee, e); } } /** * TODO * @private */ IERecCauseAnalyzer.prototype._canCause = function(prev, node, nodeURL, windowIndex, nodeAttrs) { var tagName = node.tagName; if (tagName == "APP") { return this._canCauseAPP(prev, node, nodeURL, windowIndex); } else if (tagName == "DC") { return this._canCauseDC(prev, node, nodeURL, windowIndex); } else if (tagName == "NC") { return this._canCauseNC(prev, node, nodeURL, windowIndex); } return false; } /** * TODO * @private */ IERecCauseAnalyzer.prototype._canCauseAPP = function(prev, node, nodeURL, windowIndex) { var result = false; if ((this.isBN(prev) || this.isAPP(prev) || this.isRedirect(prev))) { var url = this.getURL(prev); if (url) { url = url.toLowerCase(); } // APP nodes does not have windowIndex result = (url == nodeURL); // && (this.getWindowIndex(prev) == windowIndex); if (result) { this.removeURL(node); } } return result; } /** * TODO * @private */ IERecCauseAnalyzer.prototype._canCauseNC = function(prev, node, nodeURL, windowIndex) { var result = false; if (this.isAPP(prev) || this.isRedirect(prev) || (this.isBN(prev) && this.getChildNode(prev, "APP") == null && this.getChildNode(prev, "NC") == null)) { var url = this.getURL(prev); if (url) { url = url.toLowerCase(); } result = (url == nodeURL) && (this.getWindowIndex(prev) == windowIndex || this.isAPP(prev)); if (result) { this.removeURL(node); } } return result; } /** * TODO * @private */ IERecCauseAnalyzer.prototype._canCauseDC = function(prev, node, nodeURL, windowIndex) { var result = false; if (this.isNC(prev) && this.getChildNode(prev, "DC") == null) { var url = this.getURL(prev); if (url) { url = url.toLowerCase(); } result = (url == nodeURL) && (this.getWindowIndex(prev) == windowIndex); if (result) { this.removeURL(node); } } return result; } /** * TODO * @private */ IERecCauseAnalyzer.prototype._canCauseFrame = function(prev, node, nodeURL, windowIndex, nodeAttrs) { var result = false; // the cause of a frame is a BN event (for the frame itself). // the name must match // the BN must not already have a FRAME child // the window index must match. // if (nodeAttrs) { var name = nodeAttrs["name"]; if (this.isBN(prev) && this.getTargetFrameName(prev) == name && this.getChildNode(prev, "FRAME") == null && this.getWindowIndex(prev) == windowIndex) { var bnNodeTime = nodeAttrs["BNTime"]; var dcNodeTime = nodeAttrs["DCTime"]; var pvNodeTime = parseInt(prev.getAttribute("time")); // the NC node of the parent frame exist // we need to make sure the bnNode (for the frame) // comes after the NC node, // and come before the DC node. if (bnNodeTime <= pvNodeTime && pvNodeTime <= dcNodeTime) { prev.setAttribute(Constants.REQUEST_MODE, Constants.FRAME); this.removeTargetFrameName(node); result = true; // stderr.log("found " + node.getAttribute("nodeId")); } else { // stderr.log("inner if" + node.getAttribute("nodeId") + bnNodeTime + " " +pvNodeTime + " " +dcNodeTime); } } else { // stderr.log("inner if 2:" + node.getAttribute("nodeId") + " " + this.isBN(prev) + " " + this.getTargetFrameName(prev) + " " + name); } } else { // stderr.log("inner if 3"); } return result; } /** * node is the root node. */ IERecCauseAnalyzer.prototype._removeBadBN = function(bnNode) { try { var url = this.getURL(bnNode); if (url == "about:blank") { bnNode.parentNode.removeChild(bnNode); } else if (url.indexOf("javascript:") == 0) { bnNode.parentNode.removeChild(bnNode); } else if (url.indexOf("res:") == 0) { bnNode.parentNode.removeChild(bnNode); } } catch (e) { this._handleException(arguments.callee, e); } } /** * TODO * @private */ IERecCauseAnalyzer.prototype._getTimeOffset = function() { return (new Date()).valueOf() - this.m_startTime; } /** * Handles stack trace * @private */ IERecCauseAnalyzer.prototype._stackTrace = function (func, msg) { handleStackTrace(IERecCauseAnalyzer, func, msg); } /** * Handles exception * @private */ IERecCauseAnalyzer.prototype._handleException = function (func, e) { handleException(IERecCauseAnalyzer, func, e); } /** * @class IERecAnalyzer. This module analyzes a recorded web transaction. Events are manipulated as an in-memory XML. Initially, it is simply a linear list of events. We need to analyze them in order to derive useful information. During recording, we want to analyze the causality between events, remove redundant data, and extract out common information. For example, the following events are related. A click event => a form submission event => a before navigate event => a HTTP request event => a HTTP redirect event => a navigation complete event => a document complete event. (=> means caused) Having this information allows us to correlate between actual user actions and subsequent HTTP requests. During playback, we want to analyze the time of all the events to determine how quickly each step took to execute, and whether there are any errors.

Data

The events are stored in an in-memory XML. For example, suppose initially there are four events:






After analysis, if event B (a frame URL) was caused by event A, and event D (a redirect) was caused by event C, we may rewrite the XML document to something like this, and conclude that this web transaction has two steps.




Rules

The analysis phase is implemented as an order set of rules. Each rule modifies the XML document slightly. The effects are accumulative. There may be a lot of rules in the analysis phase, some only relevant to specific web applications. To effectively organize these rules, each JavaScript module implements a single method process(node), where the node parameter corresponds to the root of the XML. */ function IERecAnalyzer(mediator) { /** * TODO * @private */ this.m_mediator = mediator; } IERecAnalyzer.prototype = new Analyzer(); /** * Analysis processes * @tparam XMLElement node */ IERecAnalyzer.prototype.process = function(node) { var time = (new Date()).valueOf(); (new IERecCauseAnalyzer(this.m_mediator)).process(node); this._save(node, "c:\\stage1.xml"); (new HTMLAnalyzer(this.m_mediator)).process(node); this._save(node, "c:\\stage2.xml"); (new IERedirectAnalyzer(this.m_mediator)).process(node); this._save(node, "c:\\stage3.xml"); (new IERecRewriter(this.m_mediator)).process(node); this._save(node, "c:\\stage4.xml"); var delta = (new Date()).valueOf() - time; timing.log("Analyzed in " + delta + " milliseconds"); } IERecAnalyzer.prototype._save = function(node, fileName) { if (this.m_mediator.getDebugMode()) { var xmlWriter = new OutputStreamXMLFile(fileName); xmlWriter.printNode(node); xmlWriter.close(); } } /** * @class IERedirectAnalyzer. */ function IERedirectAnalyzer(mediator) { this.m_mediator = mediator; } IERedirectAnalyzer.prototype = new IERecProcessor(); // go through each IERedirectAnalyzer.prototype.process = function(node) { try { this._stackTrace(arguments.callee, " start"); var childRedirects = node.selectNodes("MetaRedirect"); this.foreachNode(childRedirects, this._processRedirect); var childNodes = node.selectNodes("BN"); this.foreachNodeReverse(childNodes, this._processBN); this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } } IERedirectAnalyzer.prototype._processBN = function(node) { try { this._stackTrace(arguments.callee, " start"); if (node.getAttribute(Constants.REQUEST_MODE)) { // already handled. return; } // If the time stamp of two a BN and the previous DC nodes // are very close and the only actions between them are wait or sleep, // // then the second BN is a redirect // it is a META if meta tag is found // it is a onload submit if there is a form action between // it is a script redirect otherwise. // always ignore the actual 302 redirect. var previousBN = this.getPreviousNode(node, "BN"); if (previousBN) { var aNode= previousBN.nextSibling; var htmlNodes = []; var prevBNWindowIndex = this.getProperty(previousBN, "windowIndex"); var currBNWindowIndex = this.getProperty(node, "windowIndex"); while (aNode != node) { if (this.isAction(aNode)) { var actionType = aNode.getAttribute("type"); if (actionType == Constants.sleep || actionType == Constants.waitForPageToLoad || actionType == Constants.waitForPopup || actionType == Constants.waitForPopupClose) { } else { // since there is a real action between this BN and the previous BN, // it cannot be an redirect. return; } } else if (this.isHtml(aNode)) { htmlNodes.push(aNode); } aNode = aNode.nextSibling; } // when we get here, // the second BN should be a redirect // // 2. check if there is a formNodeId if (prevBNWindowIndex == currBNWindowIndex) { if (node.getAttribute("formNodeId") != null) { node.setAttribute(Constants.REQUEST_MODE, Constants.ONLOAD_SUBMIT); node.setAttribute("redirectParentId", previousBN.getAttribute("nodeId")); } else { node.setAttribute(Constants.REQUEST_MODE, Constants.SCRIPT_REDIRECT); node.setAttribute("redirectParentId", previousBN.getAttribute("nodeId")); for (var i = 0; i < htmlNodes.length; i++) { var htmlNode = htmlNodes[i]; this._checkLocationRewrite(node, htmlNode); } } } } this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } } IERedirectAnalyzer.prototype._checkLocationRewrite = function(bnNode, htmlNode) { try { var htmlId = parseInt(htmlNode.getAttribute("htmlId")); var initialRegex = "<[^>]*window.location[^>]*>"; var htmlHelper = this.m_mediator.getHTMLHelper(); var tag = this._getRegexMatch(htmlHelper, initialRegex, htmlId, false); if (tag) { var descriptor = DescriptorGenerator.generateDescriptor(tag); this.applyDescriptor(htmlHelper, bnNode, htmlNode, descriptor); } } catch (e) { this._handleException(arguments.callee, e); } } IERedirectAnalyzer.prototype._processRedirect = function(node) { try { this._stackTrace(arguments.callee, " start"); var url = this.getURL(node); if (url) { url = url.toLowerCase(); } // temporarily set nextBN to the current node. var nextBN = node; while (nextBN = this.getNextNode(nextBN, "BN")) { var urlBN = this.getURL(nextBN); if (urlBN) { urlBN = urlBN.toLowerCase(); } // FIXME is this indexOf match sufficient? if (urlBN && urlBN.indexOf(url) != -1) { var timeout = this.getProperty(node, "timeout"); // beacon does not treat meta redirect with a positive timeout if (timeout > 0) { nextBN.setAttribute(Constants.REQUEST_MODE, Constants.SCRIPT_REDIRECT); } else { nextBN.setAttribute(Constants.REQUEST_MODE, Constants.META_REDIRECT); } nextBN.setAttribute("redirectParentId", node.getAttribute("nodeId")); break; } } this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } } /** * Handle exceptions. * @private */ IERedirectAnalyzer.prototype._handleException = function (func, e) { handleException(IERedirectAnalyzer, func, e); } /** * Handles stack trace * @private */ IERedirectAnalyzer.prototype._stackTrace = function (func, msg) { handleStackTrace(IERedirectAnalyzer, func, msg); } /** * @class IERecRewriter. This module simplifies a recorded web transaction in Internet Explorer.

After all the other rules are applied, the final phase is a rewrite rule. The rewrite rule does not generate any additional information, but it rearranges all the data gathered such that the result is similar to the step structure that we have today in a web transaction. It makes the XSLT to a web transaction template for recording faster and easier to write.

For agent/browser playback scenarios, the rewrite rule is not required. @see PBReport @see AgentReport */ function IERecRewriter(mediator) { this.m_mediator = mediator; this.m_startTime = (new Date()).getTime(); var requiredFuncs = ["getNodeWithId", "getTxnManager"]; require(requiredFuncs, this.m_mediator); } IERecRewriter.prototype = new IERecProcessor(); /** * Rewrites the web transaction. Any changes are applied * to the XML node directly. * @tparam XMLNode node the recorded web transaction in XML format. */ IERecRewriter.prototype.process = function (node) { try { this._stackTrace(arguments.callee, " start"); this._genFirstGoto(node); this._detectType(node); // this is moved to IERecCauseAnalyzer // this._removeBadBNs(node); this._appendNodeToParent(node); // _addNodeProp must happen after _appendNodeToParent // because BN nodes are relocated in _appendNodeToParent this._addNodeProp(node); this._removeRedundantNode(node); this._genRequiredProp(node); this._genActions(node); this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } } IERecRewriter.prototype._genFirstGoto = function(node) { try { // remove the very first node that waits for browser open // /* var childNodes = node.childNodes; if (childNodes.length > 0) { var childNode = childNodes[0]; if (childNode.tagName == "action" && childNode.getAttribute("type") == "wait" && childNode.getAttribute("for") == "open") { node.removeChild(childNode); } } */ childNodes = node.childNodes; if (childNodes.length > 0) { var childNode = childNodes[0]; var txnManager = this.m_mediator.getTxnManager(); if (childNode.tagName == "BN") { var gotoNode = txnManager.createElement("action"); gotoNode.setAttribute("type", Constants.open); var time = childNode.getAttribute("time"); gotoNode.setAttribute("time", time); gotoNode.setAttribute("newValue", this.getURL(childNode)); node.insertBefore(gotoNode, childNode); } } } catch (e) { this._handleException(arguments.callee, e); } } /** * This rule moves the form elements and html elements to its corresponding parent. */ IERecRewriter.prototype._appendNodeToParent = function(node) { try { // a function that append nodes to their parent. var parentIdAttrName = null; var appendNodeToParentFunc = function(nodeWithParentNode) { // each node has an attribute describing their parent id. var parentId = nodeWithParentNode.getAttribute(parentIdAttrName); if (parentId != "" && parentId >= 0) { var parentNode = this.m_mediator.getNodeWithId(parseInt(parentId)); if (parentNode) { parentNode.appendChild(nodeWithParentNode); } else { stderr.log('Cannot locate the parentNode with id' + parentId); } } } // move nodes

// to their parent. parentIdAttrName = "formParentId"; this.foreachNodeReverse(node.selectNodes("form[@formParentId]"), appendNodeToParentFunc); parentIdAttrName = "redirectParentId"; this.foreachNodeReverse(node.selectNodes("BN[@redirectParentId]"), appendNodeToParentFunc); // append nodes // to their parent. parentIdAttrName = "DCId"; this.foreachNodeReverse(node.selectNodes("html[@DCId]"), appendNodeToParentFunc); // move MetaRedirect nodes to the parent. var moveMetaRedirectBNFunc = function(metaRedirectNode) { var dcId= metaRedirectNode.getAttribute("DCId"); if (dcId >= 0) { var dcNode = this.m_mediator.getNodeWithId(dcId); var bnNode = this.getAncestor(dcNode, "BN"); if (bnNode) { var bns = metaRedirectNode.selectNodes("BN"); for (var i = 0; i < bns.length; i++) { bnNode.appendChild(bns[i]); } } } } this.foreachNodeReverse(node.selectNodes("MetaRedirect[@DCId]"), moveMetaRedirectBNFunc); } catch (e) { this._handleException(arguments.callee, e); } } /** * This rule moves all the interesting property nodes to the its first ancestor BN node. */ IERecRewriter.prototype._addNodeProp = function(node) { try { // move all other interesting step properties to the top level BN node. var addNodePropFunc = function (propNode) { try { var ancestorBNNode = this.getAncestor(propNode, "BN"); while (ancestorBNNode && !this.isPlayable(ancestorBNNode)) { var parentBNNode = this.getAncestor(ancestorBNNode, "BN"); if (parentBNNode) { ancestorBNNode = parentBNNode; } else { break; } } // this is no longer applicable /* // choose the previous BN that is a User Action (top level BN) // it is possible that some top level BN have a request mode attribute // which indicates that it is not a User Action. while (ancestorBNNode && ancestorBNNode.getAttribute(Constants.REQUEST_MODE)) { ancestorBNNode = this.getPreviousNode(ancestorBNNode, "BN"); } */ // add the property to the that BN, which must be a playable if (ancestorBNNode) { // stderr.log("appending " + ancestorBNNode.getAttribute("nodeId")); var existingRegex = this.getProperty(ancestorBNNode, "regex"); var regexValue = propNode.getAttribute("value"); var regmdValue = propNode.parentNode.selectNodes("regmd")[0].getAttribute("value"); if (regexValue && regmdValue) { var regexValues = regexValue.split(","); var regmdValues = regmdValue.split(","); for (var i = 0; i < regexValues.length && i < regmdValues.length; i++) { var regex = regexValues[i]; var regmd = regmdValues[i]; if (existingRegex) { if (existingRegex.indexOf(regex) == -1) { this.appendProperty(ancestorBNNode, "regex", regex); this.appendProperty(ancestorBNNode, "regmd", regmd); } else { // ignore this one, } } else { // set the whole thing since should be unique. this.appendProperty(ancestorBNNode, "regex", regex); this.appendProperty(ancestorBNNode, "regmd", regmd); } } } } } catch (e) { this._handleException(arguments.callee, e); } } this.foreachNodeReverse(node.selectNodes("//regex"), addNodePropFunc); var addNodePropFunc0 = function (propNode) { var ancestorBNNode = this.getAncestor(propNode, "BN"); while (ancestorBNNode && !this.isPlayable(ancestorBNNode)) { var parentBNNode = this.getAncestor(ancestorBNNode, "BN"); if (parentBNNode) { ancestorBNNode = parentBNNode; } else { break; } } /* hidden input hints should not be promoted to the top level BN. the VB playback searches for hidden input hint in all sub steps. the beacon require the hidden input hint to be placed on the playable step, which can be javascript redirect or onload submit steps. */ while (ancestorBNNode && !this.isPlayable(ancestorBNNode)) { ancestorBNNode = this.getPreviousNode(ancestorBNNode, "BN"); } // add the property to the top most BN. if (ancestorBNNode) { // FIXME: technically, this is not correct, if there are multiple forms // on the same page that require hidden hint substitution. this.appendProperty(ancestorBNNode, propNode.tagName, propNode.getAttribute("value")); } } this.foreachNodeReverse(node.selectNodes("//hidden_input_hint"), addNodePropFunc0); // move charset or title to their immediate BN var addNodePropFunc1 = function (propNode) { var ancestorBNNode = this.getAncestor(propNode, "BN"); // add the property to the top most BN. if (ancestorBNNode) { ancestorBNNode.appendChild(propNode); } } this.foreachNodeReverse(node.selectNodes("//charset"), addNodePropFunc1); this.foreachNodeReverse(node.selectNodes("//title"), addNodePropFunc1); } catch (e) { this._handleException(arguments.callee, e); } } /** * This rule removes all top level APP and form nodes, as they are no longer useful. */ IERecRewriter.prototype._removeRedundantNode = function(node) { try { // Remove all form, APP node at the top. var removeNodeFunc = function(nodeForRemove) { node.removeChild(nodeForRemove); } this.foreachNodeReverse(node.selectNodes("APP"), removeNodeFunc); this.foreachNodeReverse(node.selectNodes("form"), removeNodeFunc); } catch (e) { this._handleException(arguments.callee, e); } } /** * */ IERecRewriter.prototype._genRequiredProp = function(node) { try { // Generate all required step properties to each BN node. this.m_stepNameIndex = 1; this.m_stepNumber = 1; this.m_stepName2Index = {}; this.m_stepName2Index["Script Redirect"] = 1; this.m_stepName2Index["Onload Submit"] = 1; this.m_stepName2Index["Meta Redirect"] = 1; this.m_stepName2Index["Frame"] = 1; this.m_stepName2Index["Step"] = 1; this.foreachNode(node.selectNodes("//BN"), this._addStepPropsFunc); } catch (e) { this._handleException(arguments.callee, e); } } /** * TODO * @private */ IERecRewriter.prototype._addStepPropsFunc = function (bnNode) { try { bnNode.setAttribute("stepNumber", this.m_stepNumber++); var title = this.getProperty(bnNode, "title"); if (title && !bnNode.getAttribute(Constants.REQUEST_MODE)) { // add success string only to the top level BN this.setProperty(bnNode, "SuccessString", encodeNameValuePairDelimiters(title)); } // loop through to find any title properties of any HTML document. // last one within the BN node wins. this.foreachNode(bnNode.selectNodes("BN/title"), function(titleNode) { title = titleNode.getAttribute("value");}); var allPreviousActions = []; // generate title by the previous click link var previousAction = this.getPreviousNode(bnNode, "action"); if (previousAction) { var type = previousAction.getAttribute("type"); var tagName = previousAction.tagName; if (type == "click") { var text = previousAction.getAttribute("text"); if (text) { title = text; } } } if (title == null) { var requestMode = bnNode.getAttribute(Constants.REQUEST_MODE); if (requestMode == Constants.SCRIPT_REDIRECT) { title = "Script Redirect"; } else if (requestMode == Constants.ONLOAD_SUBMIT) { title = "Onload Submit"; } else if (requestMode == Constants.META_REDIRECT) { title = "Meta Redirect"; } else if (requestMode == Constants.FRAME) { title = "Frame"; } else { title = "Step"; } } // title should not be null at this point. var MAX_STEP_NAME_LENGTH = 64; // 6 is the length of " (nnn)" if (title) { title = createStepName(title); if (title.length > MAX_STEP_NAME_LENGTH - 6) { title = title.substr(0, MAX_STEP_NAME_LENGTH - 6); } } var counter = this.m_stepName2Index[title]; if (counter >= 0 ) { this.m_stepName2Index[title] = counter + 1; bnNode.setAttribute("stepName", title + " " + counter); } else { counter = 0; this.m_stepName2Index[title] = 1; bnNode.setAttribute("stepName", title); } } catch (e) { this._handleException(arguments.callee, e); } } /** * TODO * @private */ IERecRewriter.prototype._addSleepFunc = function(actionNodes) { try { // operate on the copy, not the original array. var actionNodesClone = []; if (actionNodes.length > 1) { this.foreachNode(actionNodes, function(n) { actionNodesClone.push(n); }); var i = 0; var prevNode = actionNodesClone[i++]; var txnManager = this.m_mediator.getTxnManager(); // loop through all the action nodes if (txnManager) { while (i < actionNodesClone.length) { var currNode = actionNodesClone[i]; if (currNode.getAttribute("type") != "wait") { // insert a sleep after the previous node. var currTime = parseInt(currNode.getAttribute("time")); var prevTime = parseInt(prevNode.getAttribute("time")); if (currTime > prevTime) { var sleepNode = txnManager.createElement("action"); sleepNode.setAttribute("type", "sleep"); sleepNode.setAttribute("for", (currTime - prevTime)); sleepNode.setAttribute("time", prevTime+1); prevNode.parentNode.insertBefore(sleepNode, prevNode.nextSibling); } } prevNode = currNode; i++; } } } } catch (e) { this._handleException(arguments.callee, e); } } IERecRewriter.prototype._genActions = function(node) { try { // populate all the previous actions first. this.foreachNodeReverse(node.selectNodes("BN"), this._genPrevActionsForBN); // a visitor function that moves all action node // to its top level BN var addNodePropFunc2 = function (propNode) { var ancestorBNNode = this.getAncestor(propNode, "BN"); var isFrame = false; if (ancestorBNNode && ancestorBNNode.getAttribute(Constants.REQUEST_MODE) == Constants.FRAME) { isFrame = true; } while (ancestorBNNode) { var parentBNNode = this.getAncestor(ancestorBNNode, "BN"); if (parentBNNode) { ancestorBNNode = parentBNNode; } else { break; } } if (isFrame && propNode.getAttribute("type") == Constants.waitForPageToLoad) { // ignore load events for frame } else if (ancestorBNNode) { // add the property to the top most BN. ancestorBNNode.appendChild(propNode); } } // move the action to its top level BN node first. this.foreachNodeReverse(node.selectNodes("//action"), addNodePropFunc2); this.foreachNode(node.selectNodes("BN"), this._addActionPropsFunc); } catch (e) { this._handleException(arguments.callee, e); } } IERecRewriter.prototype._genPrevActionsForBN = function(bnNode) { try { var allPreviousActions = []; // generate title by visit each top level action. var previousNode = this.getPreviousNode(bnNode, "action"); while (previousNode) { if (previousNode.tagName == "action") { allPreviousActions.push(previousNode); } else if (previousNode.tagName == "BN" && !this.getProperty(Constants.REQUEST_MODE)) { break; } previousNode = previousNode.previousSibling; } // the allPreviousActions is in reverse order. this.foreachNode(allPreviousActions, function(actionNode) { bnNode.appendChild(actionNode);}); } catch (e) { this._handleException(arguments.callee, e); } } /** * Concatenates multiple actions into a single actions attribute. * @private */ IERecRewriter.prototype._addActionPropsFunc = function(bnNode) { try { var actionNodes = bnNode.selectNodes("action"); var i = 0; var actionsData = new StringBuffer(); // loop through all the action nodes // and remove all temporaroy fields. while (i < actionNodes.length) { var currNode = actionNodes[i]; currNode.removeAttribute("nodeId"); currNode.removeAttribute("time"); currNode.removeAttribute("password"); currNode.removeAttribute("anchor"); currNode.removeAttribute("OK"); currNode.removeAttribute("oldValue"); currNode.removeAttribute("replaced"); currNode.removeAttribute("initialRegex"); if (!actionsData.isEmpty()) { actionsData.append(","); } var actionData = new StringBuffer(); actionsData.append(encodeNameValuePairDelimiters(currNode.xml)); i++; } if (!actionsData.isEmpty()) { this.setProperty(bnNode, "actions", actionsData.toString()); } } catch (e) { this._handleException(arguments.callee, e); } } IERecRewriter.prototype._detectType = function(node) { try { node.setAttribute("type", "HTTP"); var appCount = 0; var appNodes = node.selectNodes("APP"); for (var i = 0; i < appNodes.length; i++) { var appNode = appNodes[i]; var url = this.getURL(appNode); if (url) { url = url.toLowerCase(); var ignored_extensions = [".css", ".jpg", ".gif", ".png", ".js", ".swf", ".flv"]; var file = url; var addCount = true; if (url && url.indexOf("?") != -1) { file = url.substr(0, url.indexOf("?")); } // check if we should ignore this url. for (var j = 0; j < ignored_extensions.length; j++) { if (file.indexOf(ignored_extensions[j]) == (file.length - ignored_extensions[j].length)) { addCount = false; break; } } if (addCount) { appCount++; // stderr.log(appNode.getAttribute("nodeId") + " " + this.getURL(appNode)); } } } var actionCount = 0; var actionNodes = node.selectNodes("action"); for (var i = 0; i < actionNodes.length; i++) { var actionNode = actionNodes[i]; var actionType = actionNode.getAttribute("type"); if (actionType != Constants.waitForPageToLoad && actionType != Constants.waitForPopUp && actionType != Constants.waitForPopUpClose && actionType != Constants.open && actionType != Constants.type) { actionCount++; } } var bnNodes = node.selectNodes("BN"); var nonUserActionCount = 0; var nonUserActionCountFunc = function () { nonUserActionCount++; } this.foreachNodeReverse(node.selectNodes("BN[@" + Constants.REQUEST_MODE + "]"), nonUserActionCountFunc); // This is a very simple classifier. // // For Request Simulation mode, // # of user action BN should be at least twice as much as outstanding APP requests. // # of user action BN should be at least 3. // Otherwise, suggest Browser Simulation mode. var userActionBNCount = bnNodes.length - nonUserActionCount; if (actionCount > 0 && appCount * 2 >= userActionBNCount && userActionBNCount > 3) { node.setAttribute("type", "DHTML"); } } catch (e) { this._handleException(arguments.callee, e); } } /** * node is the root node. */ /* IERecRewriter.prototype._removeBadBNs = function(node) { try { var removeBNFunc = function(bnNode) { var url = this.getURL(bnNode); if (url == "about:blank") { // remove it node.removeChild(bnNode); } else if (url.indexOf("javascript:") == 0) { node.removeChild(bnNode); } else if (url.indexOf("res:") == 0) { node.removeChild(bnNode); } } this.foreachNodeReverse(node.selectNodes("BN"), removeBNFunc); } catch (e) { this._handleException(arguments.callee, e); } } */ /** * Handles exception * @private */ IERecRewriter.prototype._handleException = function (func, e) { handleException(IERecRewriter, func, e); } /** * stack tracing * @private */ IERecRewriter.prototype._stackTrace = function (func, msg) { handleStackTrace(IERecRewriter, func, msg); } /** * @class PlayerFuncsIE. * A object consist of functions available during playback. */ function PlayerFuncsIE(mediator) { var requiredFuncs = [ "createNode", "recordNode"]; require(requiredFuncs, mediator); this.m_mediator = mediator; } PlayerFuncsIE.prototype.setTimer = function(timer) { this.m_timer = timer; } PlayerFuncsIE.prototype.navigate2 = function(br, url) { try { br.navigate2(url); } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype._handleException = function(func, e) { if (stderr && stderr.log) { stderr.log(getFunctionName(PlayerFuncsIE, func) + " error: " + e.message); } } PlayerFuncsIE.prototype._checkNull = function(elem, args) { try { if (!elem && stderr && stderr.log) { var func = args.callee; var msg = new StringBuffer(); var sep = ""; msg.append(getFunctionName(PlayerFuncsIE, func)); msg.append("("); for (var i = 0; i < args.length; i++) { msg.append(sep); if (typeof(args[i]) == "string") { msg.append("\""); msg.append(args[i]); msg.append("\""); } else { msg.append(args[i]); } if (sep == "") { sep = ", "; } } msg.append(") returns null;"); stderr.log(msg.toString()); } } catch (e) { this._handleException(arguments.callee, e); } return elem; } PlayerFuncsIE.prototype.getElementById = function(doc, id) { try { return doc.getElementById(id); } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.getElementByIndex = function(doc, index) { try { var nodes = doc.all; if (nodes && index && nodes.length > index) { return doc.all[index]; } } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype._getRefTemplateFunc = function(nodes, condFunc, value, index) { try { if (nodes != null) { for (var i = 0; i < nodes.length; i++) { var n = nodes[i]; if (condFunc(n, value)) { if (index == null || index == 0) { return n; } else { index--; } } } } } catch (e) { this._handleException(arguments.callee, e); } return null; } PlayerFuncsIE.trueFunc = function(n, value) { return true; } PlayerFuncsIE.compareInnerHTML = function(n, value) { return n.innerHTML == value; } PlayerFuncsIE.prototype.getElementByInnerHTML = function(doc, tagName, innerHTML, index) { var elem = this._getRefTemplateFunc(doc.getElementsByTagName(tagName), PlayerFuncsIE.compareInnerHTML, innerHTML, index); return this._checkNull(elem, arguments); } PlayerFuncsIE.compareText = function(n, text) { return n.children.length == 0 && n.innerText == text; } PlayerFuncsIE.prototype.getElementByTextAndTag = function(doc, text, tagName, index) { var elem = this._getRefTemplateFunc(doc.getElementsByTagName(tagName), PlayerFuncsIE.compareText, text, index); return this._checkNull(elem, arguments); } PlayerFuncsIE.prototype.getElementByText = function(doc, text, index) { var elem = this._getRefTemplateFunc(doc.all, PlayerFuncsIE.compareText, text, index); return this._checkNull(elem, arguments); } PlayerFuncsIE.prototype.getElementByName = function(doc, name, index) { var elem = this._getRefTemplateFunc(doc.getElementsByName(name), PlayerFuncsIE.trueFunc, null, index); return this._checkNull(elem, arguments); } PlayerFuncsIE.compareSrcSuffix = function(n, src) { return Util.compareURLByComponentsTrailingAuthority(n.src, src); } PlayerFuncsIE.compareSrcRegExp = function(n, srcRegExp) { var nodeSrc = n.src; var regex = new RegExp(srcRegExp); if (regex.test(nodeSrc)) { return true; } else { return false; } } PlayerFuncsIE.prototype.getImageBySrc = function(doc, src, index, useRegExp) { var elem = this._getRefTemplateFunc(doc.getElementsByTagName("img"), useRegExp ? PlayerFuncsIE.compareSrcRegExp : PlayerFuncsIE.compareSrcSuffix, src, index); return this._checkNull(elem, arguments); } // FIXME should compareValue check for type == 'hidden'? PlayerFuncsIE.compareValue = function(n, value) { return n.value == value && n.type != 'hidden'; } PlayerFuncsIE.prototype.getButtonByValue = function(doc, value, index) { var elem = this._getRefTemplateFunc(doc.getElementsByTagName("input"), PlayerFuncsIE.compareValue, value, index); return this._checkNull(elem, arguments); } PlayerFuncsIE.compareTitle = function(n, title) { return n.title == title; } PlayerFuncsIE.prototype.getElementByTitleAndTag = function(doc, title, tagName, index) { var elem = this._getRefTemplateFunc(doc.getElementsByTagName(tagName), PlayerFuncsIE.compareTitle, title, index); return this._checkNull(elem, arguments); } PlayerFuncsIE.compareAlt = function(n, alt) { return n.alt == alt; } PlayerFuncsIE.prototype.getElementByAltAndTag = function(doc, alt, tagName, index) { var elem = this._getRefTemplateFunc(doc.getElementsByTagName(tagName), PlayerFuncsIE.compareAlt, alt, index); return this._checkNull(elem, arguments); } PlayerFuncsIE.compareOnclick = function(n, onclick) { var n_onclick = n.onclick; if (n_onclick != null) { return n_onclick.toString().indexOf(onclick) != -1; } return false; } PlayerFuncsIE.prototype.getElementByOnclickAndTag = function(doc, onclick, tagName, index) { var elem = this._getRefTemplateFunc(doc.getElementsByTagName(tagName), PlayerFuncsIE.compareOnclick, onclick, index); return this._checkNull(elem, arguments); } PlayerFuncsIE.prototype.verifySuccessString = function(doc, text, mode) { try { var reg = new ActiveXObject("OraBcnTxnUtil2.HTMLHelper"); reg.readDocument(doc); var result = null; if (mode == "regex") { result = reg.checkForRegExp(text); } else { result = reg.checkForString(text); } reg = null; return result; } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.verifyErrorString = function(doc, text, mode) { try { var reg = new ActiveXObject("OraBcnTxnUtil2.HTMLHelper"); reg.readDocument(doc); var result = null; if (mode == "regex") { result = reg.checkForRegExp(text); } else { result = reg.checkForString(text); } reg = null; return result; } catch (e) { this._handleException(arguments.callee, e); } } // commands PlayerFuncsIE.prototype.check = function(elem) { this._check(elem, true); } PlayerFuncsIE.prototype.click = function(elem, button, altKey, ctrlKey, shiftKey) { try { if (elem) { var e = elem.document.createEventObject(); e.button = this._resolveButton(button); // elem.style.backgroundColor = "#ffeee0"; elem.fireEvent(Constants.onmouseover, e); elem.fireEvent(Constants.onmousedown, e); elem.fireEvent(Constants.onbeforeactivate, e); elem.fireEvent(Constants.onactivate, e); elem.fireEvent(Constants.onfocus, e); elem.fireEvent(Constants.onfocusin, e); elem.fireEvent(Constants.onmouseup, e); elem.scrollIntoView(); if (elem.click) elem.click(); try { // keep this elem.fireEvent(Constants.onfocusout, e); elem.fireEvent(Constants.onblur, e); elem.fireEvent(Constants.onmouseout, e); } catch (e) { // keep this // ignore } } } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.close = function(br) { try { br.Quit(); } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.goBack = function(br) { try { br.GoBack(); } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.keyDown = function(elem, keyCode, altKey, ctrlKey) { try { if (elem) { var e = elem.document.createEventObject(); e.keyCode = parseInt(keyCode); e.altKey = altKey ? true : false; e.ctrlKey = ctrlKey ? true : false; elem.fireEvent(Constants.onkeydown, e); } } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.keyPress = function(elem, keyCode, altKey, ctrlKey) { try { if (elem) { var e = elem.document.createEventObject(); e.keyCode = parseInt(keyCode); e.altKey = altKey ? true : false; e.ctrlKey = ctrlKey ? true : false; elem.fireEvent(Constants.onmouseover, e); elem.fireEvent(Constants.onmousedown, e); elem.fireEvent(Constants.onbeforeactivate, e); elem.fireEvent(Constants.onactivate, e); elem.fireEvent(Constants.onfocus, e); elem.fireEvent(Constants.onfocusin, e); elem.fireEvent(Constants.onfocusout, e); elem.fireEvent(Constants.onkeydown, e); // FIXME handle this for only specific keycode elem.fireEvent(Constants.onkeypress, e); elem.fireEvent(Constants.onkeyup, e); elem.fireEvent(Constants.onblur, e); elem.fireEvent(Constants.onmouseout, e); if (e.keyCode == 13) { var parent = elem.parentElement; while (parent) { if (parent.tagName.toLowerCase() == "form") { parent.submit(); return; } parent = parent.parentElement; } } } } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.keyUp = function(elem, keyCode, altKey, ctrlKey) { try { if (elem) { var e = elem.document.createEventObject(); e.keyCode = parseInt(keyCode); e.altKey = altKey ? true : false; e.ctrlKey = ctrlKey ? true : false; elem.fireEvent(Constants.onkeyup, e); } } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype._check = function(elem, value) { try { var d = false; if (elem) { var e = elem.document.createEventObject(); e.button = 1; // elem.style.backgroundColor = "#ffeee0"; elem.fireEvent(Constants.onmouseover, e); elem.fireEvent(Constants.onmousedown, e); elem.fireEvent(Constants.onbeforeactivate, e); elem.fireEvent(Constants.onactivate, e); elem.fireEvent(Constants.onfocus, e); elem.fireEvent(Constants.onfocusin, e); elem.fireEvent(Constants.onfocusout, e); elem.fireEvent(Constants.onmouseup, e); elem.checked = value; elem.fireEvent(Constants.onchange, e); elem.fireEvent(Constants.onblur, e); elem.fireEvent(Constants.onmouseout, e); elem.scrollIntoView(); } } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.type = function(elem, value) { this.change(elem, value); } PlayerFuncsIE.prototype.change = function(elem, value) { try { var d = false; if (elem) { var e = elem.document.createEventObject(); e.button = 1; // elem.style.backgroundColor = "#ffeee0"; elem.fireEvent(Constants.onmouseover, e); elem.fireEvent(Constants.onmousedown, e); elem.fireEvent(Constants.onbeforeactivate, e); elem.fireEvent(Constants.onactivate, e); elem.fireEvent(Constants.onfocus, e); elem.fireEvent(Constants.onfocusin, e); elem.fireEvent(Constants.onfocusout, e); elem.fireEvent(Constants.onmouseup, e); elem.scrollIntoView(); elem.value = value; elem.fireEvent(Constants.onchange, e); elem.fireEvent(Constants.onblur, e); elem.fireEvent(Constants.onmouseout, e); } } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype._resolveButton = function(button) { if (!button || button == "" || button == "left") { return 1; } else if (button == "right") { return 2; } else if (button == "middle") { return 4; } else { return 1; } } PlayerFuncsIE.prototype.mouseMove = function(elem, button, altKey, ctrlKey, shiftKey) { try { if (elem) { var e = elem.document.createEventObject(); e.button = this._resolveButton(button); e.altKey = altKey ? true : false; e.ctrlKey = ctrlKey ? true : false; e.shiftKey = shiftKey ? true : false; elem.fireEvent(Constants.onmouseover, e); elem.fireEvent(Constants.onmouseout, e); } } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.mouseDown = function(elem, button, altKey, ctrlKey, shiftKey) { try { if (elem) { var e = elem.document.createEventObject(); e.button = this._resolveButton(button); e.altKey = altKey ? true : false; e.ctrlKey = ctrlKey ? true : false; e.shiftKey = shiftKey ? true : false; elem.fireEvent(Constants.onmouseover, e); elem.fireEvent(Constants.onmousedown, e); elem.fireEvent(Constants.onbeforeactivate, e); elem.fireEvent(Constants.onactivate, e); elem.fireEvent(Constants.onfocus, e); elem.fireEvent(Constants.onfocusin, e); } } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.mouseUp = function(elem, button, altKey, ctrlKey, shiftKey) { try { if (elem) { var e = elem.document.createEventObject(); e.button = this._resolveButton(button); e.altKey = altKey ? true : false; e.ctrlKey = ctrlKey ? true : false; e.shiftKey = shiftKey ? true : false; elem.fireEvent(Constants.onmouseup, e); elem.fireEvent(Constants.onfocusout, e); elem.fireEvent(Constants.onmouseout, e); } } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.refresh = function(br) { try { br.Refresh(); } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.select = function(elem, value) { try { if (elem) { for (var i = 0; i < elem.children.length; i++) { var child = elem.children[i]; if (child && child.tagName.toLowerCase() == "option") { if (child.innerHTML == value) { this.change(elem, child.value); try { // ignore this child.selected = true; } catch (e) { // ignore this child.setAttribute('selected', true); } elem.scrollIntoView(); break; } } } } } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.uncheck = function(elem) { this._check(elem, false); } PlayerFuncsIE.prototype.getFrame = function(br, frame) { try { if (frame == "") { return br.document; } else if (frame.indexOf(".document") != -1) { return eval("br.document" + frame); } else { var doc = br.document; var indices = frame.split("|"); for (var i = 0; i < indices.length; i++) { var name = null; var index = null; index = parseInt(indices[i]); if (isNaN(index)) { name = indices[i]; } else if (index >= 0 && index < doc.frames.length) { doc = doc.frames(index).document; } else { name = index; } if (name) { for (var j = 0; j < doc.frames.length; j++) { var frameObj = doc.frames[j]; if (frameObj.name == name) { doc = frameObj.document; break; } } } } return doc; } } catch (e) { this._handleException(arguments.callee, e); } } PlayerFuncsIE.prototype.markStep = function(name, stepId) { var time = this.getTime(); if (this.m_mediator) { this.m_mediator.resetStep(time); var node = this.m_mediator.createNode("step", {"name":name, "stepId":stepId, "time":time}, false); this.m_mediator.writeNode(node, time); } } PlayerFuncsIE.prototype.addSuccessString = function(str, mode, level) { if (this.m_mediator) { this.m_mediator.addSuccessString(str, mode, level); } } PlayerFuncsIE.prototype.addFailureString = function(str, mode, level) { if (this.m_mediator) { this.m_mediator.addFailureString(str, mode, level); } } PlayerFuncsIE.prototype.addStep2Group = function(stepName, groupName) { if (this.m_mediator) { this.m_mediator.addStep2Group(stepName, groupName); } } PlayerFuncsIE.prototype.onRecordNode = function(tag, attrs, genId) { var time = this.getTime(); if (this.m_mediator) { var node = this.m_mediator.createNode(tag, attrs, genId); this.m_mediator.recordNode(node, time); } } PlayerFuncsIE.prototype.getTime = function() { return this.m_timer.Time; } /** * @class IERecorder. * A web transaction Recorder for Internet Explorer. * * This object participates in the design pattern Mediator as a Mediator, * and coordinates all the event listeners as Colleagues. * @treturn IERecorder * * @see IEPlayer * * This class inherits the following methods. * * * This class overrides the following methods. * */ function IERecorder() { this.init(); } IERecorder.prototype = new Recorder(); /** * Initialize all members. * @private */ IERecorder.prototype.init = function() { Recorder.prototype.init.call(this); this.getHTMLHelper = function() { return this.m_eventsManager.getHTMLHelper(); }; this._getTimeOffset = function() { return this.m_timer.Time; } // event handlers required by listeners. this.onBeforeNavigate2 = function(frame, url, flags, targetFrameName, postdata, headers, time, windowIndex) { this.m_eventsManager.onBeforeNavigate2(frame, url, flags, targetFrameName, postdata, headers, time, windowIndex); } this.onNavigateError = function(frame, url, targetFrameName, statusCode, time, windowIndex) { this.m_eventsManager.onNavigateError(frame, url, targetFrameName, statusCode, time, windowIndex); } this.onNavigateComplete2 = function(frame, url, time, windowIndex) { var node = this.m_eventsManager.onNavigateComplete2(frame, url, time, windowIndex); if (node) { this.m_domListener.onNavigateComplete2(frame, url, time, node); } } this.findTitle = function(htmlId) { var title = null; try { var verifyMatches = new Object(); this.getHTMLHelper().checkForRegExp2("([^<>]*)", htmlId, false, verifyMatches); if (verifyMatches) { for (var i = 0; i < verifyMatches.length; i++) { title = verifyMatches[i]; } } } catch (e) { this._handleException(arguments.callee, e); } return title; } this.mapDocument = function(doc) { try { if (!doc) { return null; } return this.getHTMLHelper().mapDocument(doc); } catch (e) { this._handleException(arguments.callee, e); } return null; } this.onDocumentComplete = function(frame, url, time, windowIndex) { try { var doc = this._getDocument(frame); var htmlId = this.mapDocument(doc); if (htmlId >= 0) { var title = this.findTitle(htmlId); var node = this.m_eventsManager.onDocumentComplete(frame, url, time, title, windowIndex); if (node) { try { if (this.getDebugMode()) { this.getHTMLHelper().saveDocumentToFile(htmlId, "C:\\" + htmlId + ".html"); } } catch (ex) { // ignore } var frameName = ""; try { frameName = doc.parentWindow.name; } catch (ex) { // ignore } var htmlAttrs = { // "parentId" : node ? node.getAttribute("nodeId") : "", "url" : url, "frame":frameName, "time":time + 1, "htmlId" : htmlId, "DCId": node.getAttribute("nodeId") }; var htmlNode = this.createNode("html", htmlAttrs, true); this.writeNode(htmlNode, time); this.m_domListener.onDocumentComplete(frame, url, time, htmlNode); var ignored_extensions = [".css", ".jpg", ".gif", ".png", ".js"]; var file = url; var writeWait = true; if (url && url.indexOf("?") != -1) { file = url.substr(0, url.indexOf("?")); } // check if we should ignore this url. for (var i = 0; i < ignored_extensions.length; i++) { if (file.indexOf(ignored_extensions[i]) == (file.length - ignored_extensions[i].length)) { writeWait = false; break; } } // var windowIndex = this.m_brListener.getBrowserIndex(frame); // if the url contains query params, then perhaps we should wait for the load // otherwise, we are going to skip. // the above algorithm may not work, what we do instead is // go with an algorithm of storing all the waits, // but only remove those wait under BNs that are marked as FRAME. // // if (url.indexOf("?") != -1 && windowIndex == -1) { var win = doc.parentWindow; // FIXME, reimplementing waitForPageToLoad events. if (win != null) { win = win.top; } // windowIndex = this.m_brListener.getWindowIndex(win); // } if (windowIndex != -1 && writeWait) { // var docUrl = frame1.locationURL; // debug.log("checking for docUrl" + docUrl); // if (docUrl == url) var attrs = { "type" : Constants.waitForPageToLoad, "time" : time+1, "frame" : this.m_domListener.getFrameInfo(doc.parentWindow), "windowIndex" : windowIndex }; var wait = this.createNode("action", attrs, false); node.appendChild(wait); // FIXME use resource bundle. if (doc.title) { stdout.log("Page Loaded:" + doc.title); } // FIXME Rodney found that sometimes wait may not get recorded, // if the documentcomplete event is not fired before user's next action. } var metaRedirectNode = this.getMetaRedirect(doc, time + 1); if (metaRedirectNode) { metaRedirectNode.setAttribute("DCId", node.getAttribute("nodeId")); this.writeNode(metaRedirectNode, time + 1); } } } } catch (e) { this._handleException(arguments.callee, e); } } /* this._getTopBrowser = function (frame) { var doc = this._getDocument(frame); if (doc) { var win = doc.parentWindow; var topWin = win.top; // var windowIndex = this.getWindowIndex(topWin); return this.m_brListener.getBrowserAt(windowIndex); } else { return null; } } */ this.getBrowserIndex = function(frame) { return this.m_brListener.getBrowserIndex(frame); } this.recordFormInputs = function(form, state, index, parentNode, time) { try { var attrs = { "method" : form.method, "formName" : form.name, "formId" : form.id, "formAction" : form.action, "formIndex" : index, "formParentId" : parentNode ? parentNode.getAttribute("nodeId") : "", "state" : state, "time" : time + 1 }; var node = this.createNode("form", attrs, true); var inputs = form.getElementsByTagName("input"); if (inputs) { for (var i = 0; i < inputs.length; i++) { var input = inputs[i]; var childAttrs = { "name" : input.name, "value" : input.value, "type" : (input.type != "hidden") ? input.type : "" }; var childNode = this.createNode("input", childAttrs, false); node.appendChild(childNode); } } var selects = form.getElementsByTagName("select"); if (selects) { for (var i = 0; i < selects.length; i++) { var select = selects[i]; var childAttrs = { "name" : select.name, "value" : select.value, "type" : "select" }; var childNode = this.createNode("input", childAttrs, false); node.appendChild(childNode); } } var textareas = form.getElementsByTagName("textarea"); if (textareas) { for (var i = 0; i < textareas.length; i++) { var textarea = textareas[i]; var childAttrs = { "name" : textarea.name, "value" : textarea.value, "type" : "textarea" }; var childNode = this.createNode("input", childAttrs, false); node.appendChild(childNode); } } this.writeNode(node, time + 1); } catch (e) { this._handleException(arguments.callee, e); } } } /** * Starts recording. */ IERecorder.prototype.start = function () { try { this.m_eventsManager = new IEEventsManager(this); if (this.m_controlListeners) { this.addListener(new IEDomEventListener(this, this.getTimer())); this.addListener(new IEHTTPEventListener(this, this.getTimer())); this.addListener(new IEBrEventListener(this, this.getTimer())); Recorder.prototype.start.call(this); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Stops recording. */ IERecorder.prototype.stop = function() { try { Recorder.prototype.stop.call(this); var appNodes = this._getAPPHeaders(); var messages = []; if (appNodes.length == 0 && this.m_eventsManager.getNodes().length > 0) { stderr.log("No network request events found during recording."); } var tags = { "BN" : 0, "APP": 1, "Redirect": 2, "NC" : 3, "FRAME": 4, "DC" : 5 } // sorting two sources var timeSortFunc = function(a,b) { if (a.time != b.time) { return a.time - b.time; } else { var a1 = tags[a.node.tagName]; var b1 = tags[b.node.tagName]; if (a1 && b1) { return a1 - b1; } else { return 0; } } }; this.m_eventsManager.sort(timeSortFunc); appNodes.sort(timeSortFunc); // merge two sources var allNodes = merge2(this.m_eventsManager.getNodes(), appNodes, timeSortFunc); for (var i = 0; i < allNodes.length; i++) { this.m_eventsManager.getTxnManager().writeNode(allNodes[i].node); } this.m_eventsManager.getTxnManager().setTxnName(this.getTxnName()); if (this.getDebugMode()) { var xmlDoc = this.m_eventsManager.getXMLDocument(); if (xmlDoc) { var xmlWriter = new OutputStreamXMLFile("c:\\input.xml"); xmlWriter.printNode(xmlDoc); xmlWriter.close(); } } this.analyze(); } catch (e) { this._handleException(arguments.callee, e); } } /** * Gets all the APP headers. * @private */ IERecorder.prototype._getAPPHeaders = function() { var appNodes = []; try { // retrieve all HTTP headers. var headers = null; if (this.m_httpListener) { headers = this.m_httpListener.getHeaders(); } if (headers) { for(var i = 0; i < headers.Count; i++) { var header = headers.Item(i); var contentType = header.ContentType; var node = null; if (contentType == "image/gif" || contentType == "application/x-javascript" || contentType == "text/css" || contentType == "image/pjpeg" || contentType == "application/octet-stream" || contentType == "image/jpeg") { // ignore all trivial page elements. continue; } else { var attrs1 = { "url": header.URL, "time": header.StartTime, "ContentType":header.ContentType }; node = this.createNode("APP", attrs1, true); appNodes.push({"time": header.StartTime, "node": node}); if (header.RedirectedURL != "" && header.RedirectTime != 0) { var attrs2 = { "url": header.RedirectedURL, "time": header.RedirectTime, "parentId" : node? node.getAttribute("nodeId") : "" }; var redirectNode = this.createNode("Redirect", attrs2, true); appNodes.push({"time": header.RedirectTime, "node": redirectNode}); // node.appendChild(redirectNode); } } } } } catch (e) { this._handleException(arguments.callee, e); } return appNodes; } IERecorder.prototype.getMetaRedirect = function (doc, time) { try { var hcol1 = doc.getElementsByTagName("meta"); if (hcol1 && hcol1.length > 0) { for (var i1 = 0; i1 < hcol1.length; i1++) { var m1 = hcol1[i1]; var httpEquiv = m1.httpEquiv; if (httpEquiv) { httpEquiv = httpEquiv.toLowerCase(); if (httpEquiv.indexOf("refresh") != -1) { var content = m1.content; if (content) { content = content.toLowerCase(); var i2 = content.indexOf("url="); if (i2 != -1) { var attrs = { // i2 - 1 to remove the ; "timeout" : parseInt(content.substring(0, i2 - 1)), "url" : content.substring(i2+4), "time":time }; return this.createNode("MetaRedirect", attrs, true); } } } } } } } catch (e) { this._handleException(arguments.callee, e); } return null; } /** * Gets the recording as a Web Transaction template. * @treturn IXMLElement an XML element or null */ IERecorder.prototype.getRecordingTemplate = function() { return this._getTransformedRecording("xsl/toTemplate.xsl"); } /** * Gets the recording as a set of actions. * @treturn IXMLElement an XML element or null */ IERecorder.prototype.getRecordingActions = function() { return this._getTransformedRecording("xsl/toActions.xsl"); } /** * Gets the recording description. * @treturn String */ IERecorder.prototype.getRecordingDesc = function() { return this.m_eventsManager.getTxnManager().transform2("xsl/toDesc.xsl"); } IERecorder.prototype._getDocument = function (frame) { try { return frame.Document; } catch (e) { // ignore } } /** * Handle exceptions. * @private */ IERecorder.prototype._handleException = function (func, e) { handleException(IERecorder, func, e); } /** * @class IEPlayer. * * A web transaction Player for Internet Explorer. * This object participates in the design pattern Mediator as a Mediator, * and coordinates all the event listeners as Colleagues. * * @see IERecorder * * @ctor IEPlayer. * Constructor. * * This class also inherits the following methods: * */ function IEPlayer() { this.init(); } IEPlayer.prototype = new Player(); /** * Initializes members. * @private */ IEPlayer.prototype.init = function() { Player.prototype.init.call(this); this.m_controlListeners = true; this.m_analyzeMode = true; this.m_eventsManager = null; this.m_useApp = true; // an array of success strings not found so far // each item contains a value property, and a mode property. this.m_t_succs = []; this.m_s_succs = []; // an array of all failure strings to be found this.m_t_all_fails = []; this.m_s_all_fails = []; // an array of failure strings found so far this.m_t_fails = []; this.m_s_fails = []; // group -> step mapping this.m_group2step = new Mvmap(); this.getHTMLHelper = function() { return this.m_eventsManager.getHTMLHelper(); } this.getTxnManager = function() { return this.m_eventsManager.getTxnManager(); } this.getNodeWithId = function(id) { return this.m_eventsManager.getNodeWithId(id); } this.createNode = function(tag, attrs, genId) { return this.m_eventsManager.createNode(tag, attrs, genId); } this.writeNode = function(node, time) { return this.m_eventsManager.writeNode(node, time); } this.getBrowserAt = function(windowIndex) { return this.m_brListener.getBrowserAt(windowIndex); } this.getBrowserIndex = function(frame) { return this.m_brListener.getBrowserIndex(frame); } // the following handlers are required by the listeners. this.onGotoUrl = function(frame, url, time) { } this.onNewWindow2 = function(windowIndex, time) { // stderr.log("unlock open " + windowIndex); this._unlock('open', windowIndex, ""); } this.onQuit = function(windowIndex, time) { // stop playing when there are no more windows. if (this.m_brListener && this.m_brListener.getWindowCount() == 0) { this._onstop(); } else { // stderr.log("unlock close " + windowIndex); this._unlock('close', windowIndex, ""); } } this.onBeforeNavigate2 = function(frame, url, flags, targetFrameName, postdata, headers, time, windowIndex) { this.m_eventsManager.onBeforeNavigate2(frame, url, flags, targetFrameName, postdata, headers, time, windowIndex); } this.onNavigateComplete2 = function(frame, url, time, windowIndex) { this.m_eventsManager.onNavigateComplete2(frame, url, time, windowIndex); } this.onNavigateError = function(frame, url, targetFrameName, statusCode, time, windowIndex) { this.m_eventsManager.onNavigateError(frame, url, targetFrameName, statusCode, time, windowIndex); } this.onDocumentComplete = function(frame, url, time, windowIndex) { try { this.m_eventsManager.onDocumentComplete(frame, url, time, "", windowIndex); this._checkValidation(frame); // var windowIndex = this.m_brListener.getBrowserIndex(frame); var name = ""; try { // ignore this var doc = this._getDocument(frame); if (doc) { var win = doc.parentWindow; // windowIndex = this.m_brListener.getWindowIndex(win.top); name = IEDomEventListener.prototype.getFrameInfo.call(this, win); } } catch (e) { // ignore } // stderr.log("unlock load " + windowIndex + " " + name); this._unlock('load', windowIndex, name); } catch (e) { this._handleException(arguments.callee, e); } } this._getTimeOffset = function() { return this.m_timer.Time; } this.getTimer = function() { return this.m_timer; } this.setTimer = function(timer) { this.m_timer = timer; } } /** * Specifies the ownership of listeners. * @tparam bool mode if true (default), this object is responsible for * creating/starting/stopping the listeners. if false, the listeners are * managed by something else, and must be added explicitly. */ IEPlayer.prototype.setControlListeners = function(mode) { this.m_controlListeners = mode; } /** * Template method to perform IE specific actions when playback starts. * @protected */ IEPlayer.prototype.doStart = function() { try { this.m_eventsManager = new IEEventsManager(this); if (this.m_controlListeners) { if (this.m_useApp) { this.addListener(new IEHTTPEventListener(this, this.getTimer())); } this.addListener(new IEBrEventListener(this, this.getTimer())); if (this.m_httpListener) { this.m_httpListener.setTraceMode(this.m_traceMode); } if (this.m_brListener) { this.m_brListener.setVisible(this.m_visibleMode); this.m_brListener.setSilent(this.m_silentMode); this.m_brListener.start(); } if (this.m_httpListener) { this.m_httpListener.start(); } this.tryContinue(); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Template method to perform IE specific actions when playback stops. * @protected */ IEPlayer.prototype.doStop = function() { try { if (this.m_controlListeners) { if (this.m_httpListener) { this.m_httpListener.stop(); } if (this.m_brListener) { this.m_brListener.stop(); } } var time = this.getTimer().Time; for (var i = 0; i < this.m_t_succs.length; i++) { var succ = this.m_t_succs[i]; succ.time = time; var node = this.createNode(Constants.SUC_NOT_FOUND, succ); this.writeNode(node, time); } for (var i = 0; i < this.m_t_fails.length; i++) { var fail = this.m_t_fails[i]; fail.time = time; var node = this.createNode(Constants.FAIL_FOUND, fail); this.writeNode(node, time); } var groupAndSteps = this.m_group2step.getAllValues(); var groups = groupAndSteps.names; var steps = groupAndSteps.values; // names and values should be identical in length; for (var i = 0; i < groups.length && i < steps.length; i++) { // debug.log(groups[i] + " " + steps[i]); var attrs = {"group": groups[i], "step": steps[i], "time": time}; var node = this.createNode(Constants.GROUP, attrs); this.writeNode(node, time); } var appNodes = this._getAPPHeaders(); var messages = []; this.m_startTime = (new Date()).getTime(); var tags = { "BN" : 0, "APP": 1, "Redirect": 2, "NC" : 3, "DC" : 4 } // sorting two sources var timeSortFunc = function(a,b) { if (a.time != b.time) { return a.time - b.time; } else { var a1 = tags[a.node.tagName]; var b1 = tags[b.node.tagName]; if (a1 && b1) { return a1 - b1; } else { return 0; } } }; if (this.m_eventsManager) { this.m_eventsManager.sort(timeSortFunc); appNodes.sort(timeSortFunc); // merge two sources var allNodes = merge2(this.m_eventsManager.getNodes(), appNodes, timeSortFunc); var txnManager = this.m_eventsManager.getTxnManager(); for (var i = 0; i < allNodes.length; i++) { txnManager.writeNode(allNodes[i].node); } if (this.getDebugMode()) { var xmlDoc = this.m_eventsManager.getXMLDocument(); if (xmlDoc) { var xmlWriter = new OutputStreamXMLFile("c:\\input.xml"); xmlWriter.printNode(xmlDoc); xmlWriter.close(); } } debug.logNode(xmlDoc); } else { stderr.log("Nothing to playback"); } if (this.getDebugMode()) { var xmlDoc = this.m_eventsManager.getXMLDocument(); if (xmlDoc) { var xmlWriter = new OutputStreamXMLFile("c:\\input.xml"); xmlWriter.printNode(xmlDoc); xmlWriter.close(); } } /* messages.push("Analysis phase start:" + this._getTimeOffset()); (new IERecAnalyzer(this)).process(xmlDoc); messages.push("Analysis phase done :" + this._getTimeOffset()); debug.logNode(xmlDoc); for (var i = 0; i < messages.length; i++) { timing.log(messages[i]); } debug.logNode(xmlDoc); */ } catch (e) { this._handleException(arguments.callee, e); } } IEPlayer.prototype.getE2ETrace = function() { if (this.m_httpListener) { return this.m_httpListener.m_appMgr; } return null; } /** * Gets all the APP headers. * @private */ IEPlayer.prototype._getAPPHeaders = function() { var appNodes = []; try { // retrieve all HTTP headers. var headers = null; if (this.m_httpListener) { headers = this.m_httpListener.getHeaders(); } if (headers) { for(var i = 0; i < headers.Count; i++) { var header = headers.Item(i); var contentType = header.ContentType; var node = null; if (contentType == "image/gif" || contentType == "application/x-javascript" || contentType == "text/css" || contentType == "image/pjpeg" || contentType == "application/octet-stream" || contentType == "image/jpeg") { // ignore all trivial page elements. continue; } else { var attrs1 = { "url": header.URL, "time": header.StartTime, "ContentType":header.ContentType }; node = this.createNode("APP", attrs1, true); appNodes.push({"time": header.StartTime, "node": node}); if (header.RedirectedURL != "" && header.RedirectTime != 0) { var attrs2 = { "url": header.RedirectedURL, "time": header.RedirectTime, "parentId" : node? node.getAttribute("nodeId") : "" }; var redirectNode = this.createNode("Redirect", attrs2, true); appNodes.push({"time": header.RedirectTime, "node": redirectNode}); // node.appendChild(redirectNode); } } } } } catch (e) { this._handleException(arguments.callee, e); } return appNodes; } IEPlayer.prototype._getDocument = function (frame) { try { return frame.Document; } catch (e) { // ignore } } /** * Exception handling. * @private */ IEPlayer.prototype._handleException = function(func, e) { handleException(IEPlayer, func, e); } IEPlayer.prototype.setUseAPPMode = function(mode) { this.m_useApp = mode; } IEPlayer.prototype.getXMLDocument = function() { if (this.m_eventsManager) { return this.m_eventsManager.getXMLDocument(); } else { return null; } } IEPlayer.prototype.resetStep = function(time) { for (var i = 0; i < this.m_s_succs.length; i++) { var succ = this.m_s_succs[i]; succ.time = time; var node = this.createNode(Constants.SUC_NOT_FOUND, succ); this.writeNode(node, time); } this.m_s_succs = []; for (var i = 0; i < this.m_s_fails.length; i++) { var fail = this.m_s_fails[i]; fail.time = time; var node = this.createNode(Constants.FAIL_FOUND, fail); this.writeNode(node, time); } this.m_s_fails = []; this.m_s_all_fails = []; } IEPlayer.prototype.addSuccessString = function(str, mode, level) { if (str && mode) { if (level && level.toLowerCase() == "txn") { this.m_t_succs.push({"value":str, "mode":mode}); } else { this.m_s_succs.push({"value":str, "mode":mode}); } } } IEPlayer.prototype.addFailureString = function(str, mode, level) { if (str && mode) { if (level && level.toLowerCase() == "txn") { this.m_t_all_fails.push({"value":str, "mode":mode}); } else { this.m_s_all_fails.push({"value":str, "mode":mode}); } } } IEPlayer.prototype.addStep2Group = function(stepName, groupName) { if (stepName && groupName) { this.m_group2step.add(groupName, stepName); } } IEPlayer.prototype._checkValidation = function(frame) { try { var htmlHelper = this.getHTMLHelper(); if (htmlHelper == null) { stderr.log(getFunctionName(IEPlayer, arguments.callee) + ":htmlHelper is null"); return; } htmlHelper.readDocument(this._getDocument(frame)); var that = this; var checkSuccess = function(arr) { var i = 0; while (i < arr.length) { var succ = arr[i]; var str = succ.value; var mode = succ.mode; if (that._checkValidationEntry(frame, htmlHelper, str, mode)) { arr.splice(i, 1); } else { i++; } } } checkSuccess(this.m_t_succs); checkSuccess(this.m_s_succs); var checkFailure = function(arr, output) { for (var i = 0; i < arr.length; i++) { var fail = arr[i]; var str = fail.value; var mode = fail.mode; if (that._checkValidationEntry(frame, htmlHelper, str, mode)) { output.push(fail); } } } checkFailure(this.m_t_all_fails, this.m_t_fails); checkFailure(this.m_s_all_fails, this.m_s_fails); } catch (e) { this._handleException(arguments.callee, e); } } IEPlayer.prototype._checkValidationEntry = function(frame, htmlHelper, str, mode) { try { if (mode == "regex") { var match = htmlHelper.checkForRegExp(str); if (match && match.length > 0) { return true; } } else { var match = htmlHelper.checkForString(str); if (match) { return true; } } } catch (e) { this._handleException(arguments.callee, e); } return false; } /** * @class IERecordingPlayer * * A web transaction Player that also records. * @see IERecorder * @see IEPlayer */ function IERecordingPlayer(recorder, player) { this.init(recorder, player); } /** * Initializes members. * @protected * @tparam IERecorder recorder * @tparam IEPlayer player */ IERecordingPlayer.prototype.init = function(recorder, player) { if (recorder == null) { throw "Recorder argument is null " + getFunctionName(IERecordingPlayer, arguments.callee); } if (player == null) { throw "Player argument is null " + getFunctionName(IERecordingPlayer, arguments.callee); } this.m_player = player; this.m_recorder = recorder; // a bunch of functions delegate the responsibilities. this.onBeforeNavigate2 = function(frame, url, flags, targetFrameName, postdata, headers, time, windowIndex) { this.m_recorder.onBeforeNavigate2(frame, url, flags, targetFrameName, postdata, headers, time, windowIndex); this.m_player.onBeforeNavigate2(frame, url, flags, targetFrameName, postdata, headers, time, windowIndex); } this.onNavigateComplete2 = function(frame, url, time, windowIndex) { this.m_recorder.onNavigateComplete2(frame, url, time, windowIndex); this.m_player.onNavigateComplete2(frame, url, time, windowIndex); } this.onDocumentComplete = function(frame, url, time, windowIndex) { this.m_recorder.onDocumentComplete(frame, url, time, windowIndex); this.m_player.onDocumentComplete(frame, url, time, windowIndex); } this.onNewWindow2 = function(windowIndex, time) { // do not call m_recorder.onNewWindow2 because we don't want // to record this action. this.m_player.onNewWindow2(windowIndex, time); } this.onGotoUrl = function(frame, url, time) { // do nothing because player will handle this automatically. } this.onQuit = function(windowIndex, time) { // do not call m_recorder.onQuit because we don't need // to record this action. this.m_player.onQuit(windowIndex, time); } this.getWindowIndex = function(win) { return this.m_recorder.getWindowIndex(win); } this.createNode = function(tag, attrs, genId) { return this.m_recorder.createNode(tag, attrs, genId); } this.recordNode = function(node, time) { this.m_recorder.writeNode(node, time); } this.recordFormInputs = function(form, state, index, parentNode, time) { this.m_recorder.recordFormInputs(form, state, index, parentNode, time); } this.onDOMEvent = function(attributes, time) { // do nothing return null; } this.getTimer = function() { return this.m_timer; } this.setTimer = function(timer) { this.m_timer = timer; } this.resetStep = function() { this.m_player.resetStep(); } } /** * Adds listener. * @tparam EventListener listener * @protected */ IERecordingPlayer.prototype.addListener = function(listener) { if (listener) { var name = listener.getName(); if (false) { } else if (name.indexOf("BrEventListener") != -1) { this.m_brListener = listener; } else if (name.indexOf("HTTPEventListener") != -1) { this.m_httpListener = listener; } else if (name.indexOf("DomEventListener") != -1) { this.m_domListener = listener; } else { throw "Unknown Listener " + name; } } } /** * Starts playback and recording. */ IERecordingPlayer.prototype.start = function() { this.addListener(new IEDomEventListener(this, this.getTimer())); this.addListener(new IEHTTPEventListener(this, this.getTimer())); this.addListener(new IEBrEventListener(this, this.getTimer())); // should not set starting URL, because we will use what is // available during playback. // if (this.m_brListener) { // this.m_brListener.setStartingUrl(...); // } if (this.m_httpListener) { this.m_httpListener.setTraceMode(this.m_recorder.getTraceMode()); } if (this.m_domListener) { this.m_domListener.setMaskPasswordMode(this.m_recorder.getMaskPasswordMode()); } this.m_recorder.setControlListeners(false); this.m_player.setControlListeners(false); // keep recording in analyze mode // FIXME, there may be different analyze modes. this.m_recorder.setAnalyzeMode(true); this.m_player.setAnalyzeMode(false); this.m_recorder.addListener(m_brListener); this.m_recorder.addListener(m_httpListener); this.m_recorder.addListener(m_domListener); this.m_player.addListener(m_httpListener); this.m_player.addListener(m_brListener); this.m_recorder.start(); this.m_player.start(); this.m_brListener.start(); this.m_httpListener.start(); this.m_domListener.start(); this.m_player.tryContinue(); } /** * Stops playback and recording. */ IERecordingPlayer.prototype.stop = function() { this.m_player.stop(); this.m_recorder.stop(); this.m_domListener.stop(); this.m_httpListener.stop(); this.m_brListener.stop(); } /** * @class EventListener. * A base class of any event listeners. * This object participates in the design pattern Mediator as a Colleague. */ function EventListener() { } /** * Initializes members. * @protected */ EventListener.prototype.init = function(mediator, timer) { this.m_mediator = mediator; this.m_timer = timer; } /** * Checks to see if mediator implements all required functionalities. */ EventListener.prototype.checkRequiredFuncs = function(requiredFuncs) { if (this.m_mediator) { require(requiredFuncs, this.m_mediator); } } /** * Gets the timer object. * @protected */ EventListener.prototype.getTimer = function() { return this.m_timer; } /** * Gets the time elapsed. * @protected */ EventListener.prototype.getTime = function() { return this.m_timer.Time; } /** * Sets the listener name. * @tparam String name the name of the listener. * @protected */ EventListener.prototype.setName = function(name) { this.m_name = name; } /** * Gets the listener name. * @treturn String the name. * @protected */ EventListener.prototype.getName = function(name) { return this.m_name; } /** * Starts listening to events. */ EventListener.prototype.start = function() { alert(getFunctionName(EventListener, arguments.callee) + " is not implemented."); } /** * Stops listening to events. */ EventListener.prototype.stop = function() { alert(getFunctionName(EventListener, arguments.callee) + " is not implemented."); } /** * @class IEHTTPEventListener. * An event listener that captures relevant HTTP request/response headers. */ function IEHTTPEventListener(mediator, timer) { if (mediator && timer) { this.init(mediator, timer); } } IEHTTPEventListener.prototype = new EventListener(); /** * Initializes members. * @private */ IEHTTPEventListener.prototype.init = function(mediator, timer) { try { EventListener.prototype.init.call(this, mediator, timer); this.setName("IEHTTPEventListener"); this.m_traceMode = false; this.m_appMgr = new ActiveXObject("OraE2EUtil.OraE2ETrace"); } catch (e) { this._handleException(arguments.callee, e); } } /** * Starts listening to HTTP request/response events. */ IEHTTPEventListener.prototype.start = function () { try { if (this.m_appMgr) { this.m_appMgr.Timer = this.getTimer(); this.m_appMgr.Logging = true; this.m_appMgr.Trace = this.m_traceMode; this.m_appMgr.Start(); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Stops listening to HTTP request/response events. */ IEHTTPEventListener.prototype.stop = function () { try { if (this.m_appMgr) { this.m_appMgr.Stop(); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Gets a collection of HTTP headers. * @treturn IOraHTTPHeaders */ IEHTTPEventListener.prototype.getHeaders = function() { return (this.m_appMgr) ? this.m_appMgr.HTTPHeaders : null; } /** * Sets the trace mode. * This mode should be set to true for Adhoc Tracing * or Play with Trace functionality. * @tparam bool traceMode the new mode. */ IEHTTPEventListener.prototype.setTraceMode = function(traceMode) { this.m_traceMode = traceMode; } /** * Handles exception * @private */ IEHTTPEventListener.prototype._handleException = function (func, e) { handleException(IEHTTPEventListener, func, e); } /** * @class IEDomEventListener. An event listener that captures relevant DOM events such as onclick, onkeypress. It attaches event listeners to DOM elements Just in Time (when user's mouse cursor moves over/keyboard activates the element), and detaches event listeners when user's mouse/keyboard leaves the element. When the HTML document loads initially, a small set of event handlers: onmouseover, onmouseout, onkeydown, onkeypress, and onkeyup event handlers are attached to the document. Consider the example. a text input box and a search button. As user moves over the text input box or the search button, our onmouseover handler is called. We attach onclick, onfocus and other event handlers to the corresponding element just in time. As user moves out of the corresponding element, our onmouseout handler is called. We detach the event handlers just in time. This approach is required because Internet Explorer does not implement event capturing, thus the event handler has to be added to the element itself. Adding event handlers to all potential HTML elements can be extremely slow and resource hungry. One problem with this approach is that, application may explicitly stop event bubbling for mouseover, mouseout events. To solve this problem, when user moves over a parent HTML element, we also attach onmouseover, onmouseout event handlers to all immediate child elements. When application stops event bubbling for keyboard events, there is very little we can do. Luckily, this is rare for known applications.

Duplicate DOM event suppression

Our event handlers are not blocking. This means if a click action caused a URL to be fetched; our click handler may be called after the actual request. In addition, HTML element may be wired to handle onmousedown events, instead of onclick events. This is problematic, because at the time of the recording, we see the following stream of events.
onmouseover
onmousedown
onbeforeactivate
onactivate
onfocus
onfocusin
onmouseup
onclick
onfocusout
onblur
onmouseout
If we only care about onclick events, then recording will not work for application that goes to a subsequent page upon an onmousedown event. Due to time limitation, we have chosen a simple implementation for this release. Since we only capture onmousedown and onclick events, and onmousedown always occur before onclick, when we detect both events (on the same element) occur, and there are no other user action events in between them, we suppress the second event, and mark the first event as a click user action. This is important, because the click user action must appear before the subsequent network request event. Similar treatments need to be performed for onkeydown and onkeypress events.

HTML DOM Element Identification Algorithm

When user interacts with an HTML DOM element, we need to find a mechanism to describe the element uniquely, descriptively and stable. For example, describing an element using XPATH as /html/body/table[2]/tr/td/table[3]/tr/td/div[1] may be unique, but certainly is not descriptive. Our algorithm scans the attributes of the HTML element, and detects meaningful attributes that can be used to describe the element. For example, attributes such as id, name, title, alt, text, value, src, and innerHTML can be useful identifiers. For example, if an HTML image element has a title=”Login” attribute, we will identify the element using tagName=”img” title=”Login”. Since certain attributes (title) are more descriptive than others (src), our algorithm scans the attributes of a single element in a particular order and pick out those important attributes based on a predefined order. This does not solve the problem of uniqueness, as there may be multiple elements on the same page with the same attributes. Our algorithm makes a second pass over the entire document for the element that user can interacte with, and calculates an index value n. During playback, our algorithm finds the nth element that matches the identified attributes.

Limitations

If the key attribute we use to identify the element changes before and after the event, then our algorithm fails. For example, consider an expand icon (+), when user clicks on it, it becomes a collapsed icon -. If the element only has the src attribute, our algorithm would recognise the element as the collapse icon, rather than the expand icon, and thus would not be able to locate the element.

Inputs

// mediator – The caller object.
// timer – The timer object.
var m_Listener = new IEDOMEventListener(mediator, timer);
m_Listener.start();
...
m_Listener.stop();

Outputs

This module calls mediator.onDOMEvent(params, time) whenever a user action is recorded. The params parameter contains all the information about the user action (including command and attributes). */ function IEDomEventListener(mediator, timer) { this.init(mediator, timer); } IEDomEventListener.prototype = new EventListener(); /** * Initializes members. * @private */ IEDomEventListener.prototype.init = function(mediator, timer) { try { EventListener.prototype.init.call(this, mediator, timer); this.setName("IEDomEventListener"); var requiredFuncs = [ "recordFormInputs", "onDOMEvent", "getWindowIndex"]; this.checkRequiredFuncs(requiredFuncs); this.m_highlight = false; // this.m_canAddWaitTraffic = false; this.m_lastMouseActionNode = null; this.m_lastMouseAction = null; this.m_lastMouseElem = null; this.m_lastKeyboardActionNode = null; this.m_lastKeyboardAction = null; this.m_lastKeyboardElem = null; this.m_keypressedbuffer = null; this.m_keypressedtime = null; // TODO revisit in next release this.m_fnrex = new RegExp("[^\{]*\{\(.*\)}", "g"); this.m_maskPassword = false; var that = this; var events = [ Constants.onload, Constants.onunload, Constants.onmouseover, Constants.onmouseout, Constants.onmousedown, Constants.onkeydown, Constants.onkeypress, Constants.onkeyup, Constants.onsubmit, Constants.onclick, Constants.onchange, Constants.onfocus, Constants.onblur, Constants.ondblclick, Constants.onpropertychange]; /* for (var i = 0; i < events.length; i++) { var evtName = events[i]; that["m_"+evtName] = function(e) { that[evtName](e||window.event); }; } */ foreach(events, function(evtName) { that["m_"+evtName] = function(e) { that[evtName](e||window.event); }; }); this._toggleAttachEvents = function (obj, func) { var elemEvents = [ Constants.onclick, Constants.onmousedown, Constants.onblur, Constants.onfocus, Constants.ondblclick]; /** for (var i = 0; i < elemEvents.length; i++) { var evtname = elemEvents[i]; func(obj, evtName, that["m_" + evtName]); } */ foreach(elemEvents, function(evtName) { func(obj, evtName, that["m_"+evtName]); }); } this._detachImportantEvents = function (obj) { this._toggleAttachEvents(obj, this._detachEvent); } this._reattachImportantEvents = function (obj) { this._toggleAttachEvents(obj, this._reattachEvent); } this._reattachEvent = function (elem, evt, func) { if (elem && elem.detachEvent && elem.attachEvent && evt && func) { elem.detachEvent(evt, func); elem.attachEvent(evt, func); } } this._detachEvent = function (elem, evt, func) { if (elem && elem.detachEvent && evt && func) { elem.detachEvent(evt, func); } } this._matchTitle = function(n1, n2) { return n1.title == n2.title; } this._matchAlt = function(n1, n2) { return n1.alt == n2.alt; } this._matchAny = function(n1, n2) { return true; } this._matchValueAndType = function(n1, n2) { return n1.value == n2.value && n2.type == n2.type; } this._matchSrc = function(n1, n2) { try { return Util.compareURLByComponentsTrailingAuthority(n1.src, n2.src); } catch (e) { return n1.src == n2.src; } } this._getTagName = function(elem) { return elem.tagName ? elem.tagName.toLowerCase() : ""; } this._getType = function(elem) { return elem.type ? elem.type.toLowerCase() : ""; } } catch (e) { this._handleException(arguments.callee, e); } } /** * Starts listening to DOM events. */ IEDomEventListener.prototype.start = function () { } /** * Stops listening to DOM events. */ IEDomEventListener.prototype.stop = function () { } /** * Sets the mask password mode. If true, the password * value is masked out. * @tparam bool mode the mask password mode. */ IEDomEventListener.prototype.setMaskPasswordMode = function (mode) { this.m_maskPassword = mode; } /** * Sets the highlight mode. If true, any activated element would be highlighted. * Very useful for debugging. * @tparam bool mode the highlight mode */ IEDomEventListener.prototype.setHighlightMode = function (mode) { this.m_highlight = mode; } /** * Handles NC or DC event. * @private */ IEDomEventListener.prototype._onNCDC = function (frame, url) { try { var doc = this.m_mediator._getDocument(frame); if (doc) { this._reattachEvent(doc, Constants.onmouseover, this.m_onmouseover); this._reattachEvent(doc, Constants.onmouseout, this.m_onmouseout); this._reattachEvent(doc, Constants.onkeydown, this.m_onkeydown); this._reattachEvent(doc, Constants.onkeypress, this.m_onkeypress); this._reattachEvent(doc, Constants.onkeyup, this.m_onkeyup); this._reattachEvent(doc, Constants.onpropertychange, this.m_onpropertychange); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Handles the NavigateComplete2 event. */ IEDomEventListener.prototype.onNavigateComplete2 = function(frame, url, time, node) { /* var win = frame.Document.parentWindow; this._reattachEvent(win, Constants.onload, this.m_onload); this._reattachEvent(win, Constants.onunload, this.m_onunload); */ this._onNCDC(frame, url); } /** * Handles the DocumentComplete event. */ IEDomEventListener.prototype.onDocumentComplete = function (frame, url, time, node) { try { this._onNCDC(frame, url); this.m_keypressedbuffer = null; this.m_keypressedtime = null; var forms = null; var formsLength = 0; var doc = this.m_mediator._getDocument(frame); try { if (doc) { forms = doc.forms; formsLength = forms.length; } } catch (ex) { stderr.log("exception thrown when parsing doc.forms"); // ignore } var mediator = this.m_mediator; if (forms) { for (var i = 0, n = formsLength; i < n; i++) { var form = forms[i]; this.m_mediator.recordFormInputs(form, "before", i, node, time); form.old_submit = form.submit; var that = this; var formIndex = i; form.submit = function () { mediator.recordFormInputs(this, "submit", formIndex, null, that.getTime()); this.old_submit(); } this._reattachEvent(form, Constants.onsubmit, this.m_onsubmit); } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Handles onmouseover event. * @tparam HTMLEvent evt * * Registers all handled events to the element, this include all bubbled events * in case client's code contain cancelBubble = true. */ IEDomEventListener.prototype.onmouseover = function (evt) { try { if (evt) { log(evt); var elem = evt.srcElement; if (elem) { // in case application sets bubble to false for child nodes, // we still have a handle on onmouseover var children = elem.childNodes; for (var i = 0; i < children.length; i++) { var child = children[i]; if (child.onmouseover != null) { this._reattachEvent(child, Constants.onmouseover, this.m_onmouseover); this._reattachEvent(child, Constants.onmouseout, this.m_onmouseout); } } this._reattachImportantEvents(elem); // onchange event applies to these three tags only. var tagName = this._getTagName(elem); var type = this._getType(elem); if (elem.onchange || (tagName == "input" && type == "text") || tagName == "select" || tagName == "textarea") { this._reattachEvent(elem, Constants.onchange, this.m_onchange); } this._highlight(elem, true); } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Unregisters all handled events on a particular element. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onmouseout = function (evt) { try { if (evt) { log(evt); var elem = evt.srcElement; if (elem) { this._highlight(elem, false); // ---------------------------------------- // cannot detach onchange, because this will be called // before the onchange event. // ---------------------------------------- this._detachImportantEvents(elem); var children = elem.childNodes; for (var i = 0; i < children.length; i++) { var child = children[i]; if (child.onmouseover != null) { this._detachEvent(child, Constants.onmouseover, this.m_onmouseover); this._detachEvent(child, Constants.onmouseout, this.m_onmouseout); } } } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Highlights the element. * @private */ IEDomEventListener.prototype._highlight = function(elem, mode) { try { if (!this.m_highlight) { return; } if (mode) { var tagName = this._getTagName(elem); if (tagName == "img") { if (elem.style.filter != null) { elem.oldFilter = elem.style.filter; } if (elem.style.opacity != null) { elem.oldOpacity = elem.style.opacity; } elem.style.opacity = "0.5"; elem.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=50)"; } else if (tagName != "select" && tagName != "option") { if (elem.style.backgroundImage == "") { if (elem.style.backgroundColor != null) { elem.oldBackgroundColor = elem.style.backgroundColor; } elem.style.backgroundColor = "#ffffe0"; } } } else { if (elem.style.filter) { elem.style.filter = elem.oldFilter; } if (elem.style.backgroundColor) { elem.style.backgroundColor = elem.oldBackgroundColor; } if (elem.style.opacity) { elem.style.opacity = elem.oldOpacity; } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Handles property change events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onpropertychange = function (evt) { if (evt) { log(evt); } } /** * Handles keyboard events. * @private */ IEDomEventListener.prototype._onkeyevents = function (evt) { try { var time = this.getTime(); if (evt) { log(evt); var elem = evt.srcElement; if (!elem) return; var tagName = this._getTagName(elem); var type = this._getType(elem); var evtType = evt.type ? evt.type.toLowerCase() : ""; // treat enter keys carefully. if (evt.keyCode == 13 && evtType == "keyup") { if (tagName != "textarea") { // Enter Key // /* the following should be handled * in the keydown phase. if (this.m_keypressedbuffer != elem) { this._ontextchanged(this.m_keypressedbuffer, evt, this.m_keypressedtime); this.m_keypressedbuffer = null; this.m_keypressedtime = null; } */ // process enter key directly // this._ontextchanged(elem, evt, time); // this._onkeypress(elem, evt, time); return; } } // a keypress or keyup is generated on elem, // but we have a different object receiving keyboard action. // record the value of that other element first. // afterwards, keypressed buffer/time should be null. if (this.m_keypressedbuffer && this.m_keypressedbuffer != elem) { this._ontextchanged(this.m_keypressedbuffer, evt, this.m_keypressedtime); } // now register keyboard action with the current object. if (this.m_keypressedbuffer == null) { this.m_keypressedbuffer = elem; this.m_keypressedtime = this.getTime(); } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Handles onkeyup events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onkeyup = function(evt) { this._onkeyevents(evt); } /** * Handles onkeydown events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onkeydown = function(evt) { var time = this.getTime(); if (evt) { log(evt); var elem = evt.srcElement; if (!elem) return; var tagName = this._getTagName(elem); var type = this._getType(elem); var evtType = evt.type ? evt.type.toLowerCase() : ""; if (evt.keyCode == 13 && evtType == "keydown") { if (tagName != "textarea") { // Enter Key // I think this should generate a click event // after if (this.m_keypressedbuffer != elem) { this._ontextchanged(this.m_keypressedbuffer, evt, this.m_keypressedtime); this.m_keypressedbuffer = null; this.m_keypressedtime = null; } // process enter key directly this._ontextchanged(elem, evt, time); this._write(Constants.keyPress, elem, "" + evt.keyCode, evt, time); } } } } /** * Handles onkeypress events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onkeypress = function(evt) { if (evt) { log(evt); this._onkeyevents(evt); } } /** * Handles onblur events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onblur = function(evt) { if (evt) { log(evt); } } /** * Handles onfocus events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onfocus = function(evt) { if (evt) { log(evt); } } /** * Handles ondblclick events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.ondblclick = function(evt) { if (evt) { log(evt); } } /** * Handles onmousedown events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onmousedown = function(evt) { if (evt) { log(evt); // return this._action(evt, true, Constants.mousedown); var time = this.getTime(); var elem = evt.srcElement; this._write(Constants.mouseDown, elem, null, evt, time); } } /** * Handles onclick events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onclick = function (evt) { try { var time = this.getTime(); if (evt) { log(evt); var elem = evt.srcElement; if (!elem) return; this._ontextchanged(this.m_keypressedbuffer, evt, this.m_keypressedtime); var tagName = this._getTagName(elem); if (tagName == "select" ) { this._ontextchanged(this.m_keypressedbuffer, evt, this.m_keypressedtime); this.m_keypressedbuffer = elem; this.m_keypressedtime = this.getTime(); } else if (tagName == "a") { if (elem.href) { // debug.log(elem.href); /* html = html.replace(/&/g, "&"); */ /* var html = escapeHTML(elem.outerHTML); debug.log(html); */ } this._write(Constants.click, elem, null, evt, time); } else { /* var html = escapeHTML(elem.outerHTML); debug.log(html); */ this._write(Constants.click, elem, null, evt, time); } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Handles a click or mouse down action. * @private */ IEDomEventListener.prototype._action = function (evt, check_typing, func) { try { var time = this.getTime(); if (evt) { log(evt); var elem = evt.srcElement; if (!elem) return; if (check_typing) { this._ontextchanged(this.m_keypressedbuffer, evt, this.m_keypressedtime); } var tagName = this._getTagName(elem); if (tagName == "select" && func == Constants.click) { this._ontextchanged(this.m_keypressedbuffer, evt, this.m_keypressedtime); this.m_keypressedbuffer = elem; this.m_keypressedtime = this.getTime(); } else { this._write(func, elem, null, evt, time); } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Handles onsubmit events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onsubmit = function (evt) { try { var time = this.getTime(); if (evt) { log(evt); var form = evt.srcElement; if (!form) return; var formIndex = 0; var doc = form.document; if (doc) { var forms = doc.forms; if (forms) { for (var i = 0; i < forms.length; i++) { if (forms[i] == form) { formIndex = i; break; } } } } this.m_mediator.recordFormInputs(form, "onsubmit", formIndex, null, time); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Records a single action. * @private */ IEDomEventListener.prototype._write = function (func, elem, value, evt, time) { try { if (!elem || !func) { return; } var create = true; // since our onclick and onkeypress handlers are executed // after the real click and the real key press, there is // a problem: // // T1 real click // T2 BeforeNavigate2 (recorded) // T3 our onclick handler (recorded) // // T3 should be less than T2, but in the above case, it is not. // // We use a trick that that also records mousedown handlers. // The solution is change T2 to a onclick handler, and // not record T5. // // T1 real mousedown // T2 our mousedown handler (recorded) // T3 real click // T4 BeforeNavigate2 (recorded) // T5 our click handler (recorded) // // The assumption here is that T2 and T5. if (this.m_lastMouseElem == elem) { if (this.m_lastMouseAction == Constants.mouseDown && func == Constants.click && this.m_lastMouseActionNode) { // use the last time of mousedown this.m_lastMouseActionNode.setAttribute("type", Constants.click); this.m_lastMouseAction = Constants.click; return; } } if (this.m_lastKeyboardElem == elem) { if (this.m_lastKeyboardAction == Constants.keyDown && func == Constants.keyPress && this.m_lastKeyboardActionNode) { // use the last time of the keydown this.m_lastKeyboardActionNode.setAttribute("type", Constants.keyPress); this.m_lastKeyboardAction = Constants.keyPress; return; } } // if a click event causes an onchange event for select // (typing actions are accounted for else where), // we need to move the change event before the mousedown event // because the mousedown event may be changed to a click event by // our onclick handler. /* if (func == Constants.select) { if (this.m_lastMouseAction == Constants.mouseDown && this.m_lastMouseActionNode) { time = parseInt(this.m_lastMouseActionNode.getAttribute("time"))-1; } else if (this.m_lastKeyboardAction == Constants.keyDown && this.m_lastKeyboardActionNode) { time = parseInt(this.m_lastKeyboardActionNode.getAttribute("time"))-1; } } */ var tagName = this._getTagName(elem); var type = this._getType(elem); // a mouse action on a (text, file, password, textarea) is not recorded. if ((func == Constants.click || func == Constants.mouseDown ) && (tagName == "textarea" || (tagName == "input" && (type == "file" || type == "password" || type == "text")))) { create = false; } if (create) { var attrs = { "type" : func, "newValue": value, "time": time } if (tagName == "area") { if (evt.clientX) attrs.clientX = evt.clientX; if (evt.clientY) attrs.clientY = evt.clientY; } if (func.indexOf("mouse") != -1 || func.indexOf("click") != -1) { if (evt.button == 2) { attrs.button = "right"; } else if (evt.button == 3 || evt.button == 4) { attrs.button = "middle"; } else { // default button is left and 1. } if (evt.altKey) { attrs.altKey = "true"; } if (evt.ctrlKey) { attrs.ctrlKey = "true"; } if (evt.shiftKey) { attrs.shiftKey = "true"; } } if (func.indexOf("key") != -1) { if (evt.altKey) { attrs.altKey = "true"; } if (evt.ctrlKey) { attrs.ctrlKey = "true"; } // no need for shift key, it is already in the keyCode. } this._getRef(attrs, elem); var node = this.m_mediator.onDOMEvent(attrs, time); if (func.indexOf("mouse") != -1 || func.indexOf("click") != -1) { this.m_lastMouseAction = func; this.m_lastMouseElem = elem; this.m_lastMouseActionNode = node; // FIXME add altKeys, etc } else if (func.indexOf("key") != -1) { this.m_lastKeyboardAction = func; this.m_lastKeyboardElem = elem; this.m_lastKeyboardActionNode = node; // FIXME add altKeys, etc } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Handles onchange events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onchange = function (evt) { try { var time = this.getTime(); if (evt) { log(evt); var elem = evt.srcElement; var tagName = this._getTagName(elem); var type = this._getType(elem); // suppress keypress buffer if (tagName == "select") { for (var i = 0; i < elem.children.length; i++) { var child = elem.children[i]; if (child && this._getTagName(child) == "option") { if (child.value == elem.value) { this._write(Constants.select, elem, child.innerHTML, evt, time); } } } } /* } else if (tagName == "input" && type == "text") { this._ontextchanged(elem, evt, time); } else if (tagName == "textarea") { this._ontextchanged(elem, evt, time); } */ } } catch (e) { this._handleException(arguments.callee, e); } } /** * Handles text changed event. * @private */ IEDomEventListener.prototype._ontextchanged = function (elem, evt, time) { try { if (elem) { var tagName = this._getTagName(elem); var type = this._getType(elem); var record = false; var value = elem.value; // stderr.log("calling _ontextchanged:" + value + " at " + time); if (tagName == "input" && type == "text") { record = true; } else if (tagName == "input" && type == "file") { record = true; } else if (tagName == "input" && type == "password") { record = true; if (this.m_maskPassword) { value = "******"; } } else if (tagName == "textarea") { record = true; } else { record = false; } if (record) { this._write(Constants.type, elem, value, evt, time); if (elem == this.m_keypressedbuffer) { this.m_keypressedbuffer = null; this.m_keypressedtime = null; } } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Handles key pressed event. * @tparam HTMLEvent evt * @private */ IEDomEventListener.prototype._onkeypress = function (elem, evt, time) { try { if (elem) { // stderr.log("calling _onkeypress :" + evt.keyCode + " at " + time); this._write(Constants.keyPress, elem, "" + evt.keyCode, evt, time); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Handles onload events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onload = function(evt) { } /** * Handles onunload events. * @tparam HTMLEvent evt */ IEDomEventListener.prototype.onunload = function (evt) { try { var time = this.getTime(); if (evt) { log(evt); if (this.m_keypressedbuffer && evt.elem && evt.elem.document == this.m_keypressedbuffer.document) { this._ontextchanged(this.m_keypressedbuffer, evt, this.m_keypressedtime); } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Locator that depends on the text value. * @private */ IEDomEventListener.prototype._getRefByText = function (attrs, elem) { try { // go with the actual text var index = 0; var nodes = elem.document.getElementsByTagName(elem.tagName); var innerText = elem.innerText; for (var i = 0; i < nodes.length; i++) { var n = nodes[i]; if (n.children.length > 0) { // continue; } else if (n == elem) { this._getInfo(attrs, elem, "text", index); break; } else if (n.innerText == innerText) { index++; } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Locator that depends on an arbitrary condition * @private */ IEDomEventListener.prototype._getRefTemplateFunc = function (node, elem, nodes, condFunc, funcType) { try { var index = 0; if (nodes != null) { for (var i = 0; i < nodes.length; i++) { var n = nodes(i); if (n == elem) { this._getInfo(node, elem, funcType, index); break; } else if (condFunc(n, elem)) { index++; } } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Locator selection. * @private */ IEDomEventListener.prototype._getRef = function (attrs, elem) { try { var tagName = this._getTagName(elem); var innerText = elem.innerText; var doc = elem.document; if (false) { } else if (elem.children.length == 0 && tagName != "input" && tagName != "textarea" && innerText && innerText.length > 0) { this._getRefByText(attrs, elem); } else if (elem.name && elem.name != "") { var nodes = doc.getElementsByName(elem.name); this._getRefTemplateFunc(attrs, elem, nodes, this._matchAny, "name"); } else if (elem.title && elem.title != "") { var nodes = doc.getElementsByTagName(tagName); this._getRefTemplateFunc(attrs, elem, nodes, this._matchTitle, "title"); } else if (elem.alt && elem.alt != "") { var nodes = doc.getElementsByTagName(tagName); this._getRefTemplateFunc(attrs, elem, nodes, this._matchAlt, "alt"); } else if (elem.id && elem.id != "") { this._getInfo(attrs, elem, "id"); } else if (tagName == "input") { var type = this._getType(elem); if (type == "button" || type == "submit") { var nodes = doc.getElementsByTagName(elem.tagName); this._getRefTemplateFunc(attrs, elem, nodes, this._matchValueAndType, "buttonValue"); } else if (type == "checkbox" || type == "file" || type == "image" || type == "password" || type == "radio" || type == "reset") { // FIXME need to support these types stderr.log("unprocessed input " + type); } else if (type == "text") { // FIXME need to support type == "text" stderr.log("unprocessed input " + type); } else { stderr.log("unknown input " + type); } } else if (tagName == "img") { var nodes = doc.getElementsByTagName(elem.tagName); this._getRefTemplateFunc(attrs, elem, nodes, this._matchSrc, "src"); } else if (tagName == "area") { var nodes = doc.getElementsByTagName(elem.tagName); var condFunc = function(n1, n2) { return false;} // FIXME need to support tag = area this._getRefTemplateFunc(attrs, elem, nodes, condFunc, "area"); } else if (elem.id && elem.id != "") { this._getInfo(attrs, elem, "id"); /* } else if (elem.onclick && elem.onclick != "") { var nodes = doc.getElementsByTagName(tagName); var condFunc = function(n1, n2) { var n1_onclick = n1.onclick; var n2_onclick = n2.onclick; if (n1_onclick && n2_onclick) { return (Util.trim(n1_onclick.replace(this.m_fnrex, "$1")) == Util.trim(n2_onclick.replace(this.m_fnrex, "$1"))); } else { return false; } } this._getRefTemplateFunc(attrs, elem, nodes, condFunc, "onclick"); */ } else { /* var hasChildren = elem.children.length > 0; if (hasChildren) { this._getInfo(attrs, elem, "index", elem.sourceIndex); } else {*/ // debug.log(elem.innerHTML); if (elem.innerHTML.length < 20) { var nodes = doc.getElementsByTagName(elem.tagName); var condFunc = function(n1, n2) { return n1.innerHTML == n2.innerHTML;} this._getRefTemplateFunc(attrs, elem, nodes, condFunc, "InnerHTML"); } else { // FIXME need to add innerText before using index. this._getInfo(attrs, elem, "index", elem.sourceIndex); } } } catch (e) { this._handleException(arguments.callee, e); } } /** * Locator Generator. * @private */ IEDomEventListener.prototype._getInfo = function(attrs, elem, type, index) { try { var addIndex = true; var addTagName = false; var addValue = false; var addSrc = false; var addName = false; var addId = false; var addText = false; var addCoord = false; var addTitle = false; var addAlt = false; var addOnclick = false; var addPwd = false; if (type == "InnerHTML") { addTagName = true; attrs.html = elem.innerHTML; } else if (type == "index") { } else if (type == "text") { addTagName = true; addText = true; } else if (type == "title") { addTagName = true; addTitle = true; if (elem.getAttribute("name")) { addName = true; } } else if (type == "alt") { addTagName = true; addAlt = true; } else if (type == "name") { addName = true; } else if (type == "id") { addId = true; } else if (type == "buttonValue") { addValue = true; addTagName = true; } else if (type == "area") { addTagName = true; addCoord = true; } else if (type == "onclick") { addTagName = true; addOnclick = true; } else if (type == "src") { addTagName = true; addSrc = true; } if (addIndex && index && index > 0) { attrs.index = index; } var tagName = this._getTagName(elem); if (addTagName && tagName) { attrs.tagName = tagName; } if (tagName != "a") { var parentElem = elem.parentElement; while (parentElem) { var parentTag = this._getTagName(parentElem); if (parentTag == "a") { attrs.anchor = "true"; break; } else if (parentTag == "td") { break; } parentElem = parentElem.parentElement; } } if (tagName == "input" && elem.getAttribute("type") == "password") { addPwd = true; } if (addValue) { var value = elem.value; if (value) { attrs.value = value; } } if (addSrc) { // FIXME remove host and protocol from the src. var urlPart = Util.splitURL(elem.src); var src = urlPart ? urlPart.PATH : elem.src; if (src) { attrs.src = src; } } if (addName) { var name = elem.name; if (name) { attrs.name = name; } } if (addId) { var id = elem.id; if (id) { attrs.id = id; } } if (addText) { var text = elem.innerText; if (text) { attrs.text = text; } } if (addTitle) { var title = elem.title; if (title) { attrs.title = title; } } if (addAlt) { var alt = elem.alt; if (alt) { attrs.alt = alt; } } if (addOnclick) { var onclick = elem.onclick; if (onclick) { onclick = onclick.replace(this.m_fnrex, "$1"); } if (onclick) { attrs.onclick = Util.trim(onclick); } } if (addPwd) { attrs.password = "true"; } this._getFrameTreeIndicies(attrs, elem.document.parentWindow); } catch (e) { this._handleException(arguments.callee, e); } } /** * Frame Locator. * @private */ IEDomEventListener.prototype._getFrameTreeIndicies = function (attrs, win) { try { // FIXME this concats all the frame names. var result = this.getFrameInfo(win); if (result != "") { attrs.frame = result; } var windowIndex = this.m_mediator.getWindowIndex(win.top); if (windowIndex && windowIndex > 0) { attrs.windowIndex = windowIndex; } } catch (e) { this._handleException(arguments.callee, e); } } /** * Determine the position of a window relative to the top window. * @private */ IEDomEventListener.prototype.getFrameInfo = function (win) { var result = ""; try { // exit when win.parent is itself while (win.parent != win) { // at every loop, determine the position of the child window // from the parent window. var parent = win.parent; for (var i = 0; i < parent.frames.length; i++) { if (parent.frames(i) == win) { if (result.length == 0) { if (win.name != "") { result = win.name + ""; } else { result = i + ""; } } else if (win.name != "" && isNaN(parseInt(win.name))) { result = win.name + "|" + result; } else { result = i + "|" + result; } } } win = win.parent; } } catch (e) { this._handleException(arguments.callee, e); } return result; } /** * Handles exception. * @private */ IEDomEventListener.prototype._handleException = function (func, e) { handleException(IEDomEventListener, func, e); } /** * @class IEBrEventListener. * An event listener that captures relevant browser events, such * as DocumentComplete, NavigateComplete2, BeforeNavigate2. * * The mediator object must support these functions * */ function IEBrEventListener(mediator, timer) { if (mediator && timer) { this.init(mediator, timer); } } IEBrEventListener.prototype = new EventListener(); /** * Initializes members. * @private */ IEBrEventListener.prototype.init = function(mediator, timer) { try { EventListener.prototype.init.call(this, mediator, timer); this.setName("IEBrEventListener"); var requiredFuncs = [ "onBeforeNavigate2", "onNavigateComplete2", "onDocumentComplete", "onGotoUrl", "onNewWindow2", "onNavigateError", "onQuit" ]; this.checkRequiredFuncs(requiredFuncs); /** * Array of browsers. * @private */ this.m_ies = []; /** * Starting URL. * @private */ this.m_startingUrl = null; /** * IE Manager. * @private */ this.m_ieMgr = new ActiveXObject("OraIEMgr.IEManager"); /** * by default, browsers should be visible. */ this.setVisible(true); /** * by default, browser should not be silent. */ this.setSilent(false); } catch (e) { this._handleException(arguments.callee, e); } } /** * Sets the starting URL. * @tparam String url the starting url. */ IEBrEventListener.prototype.setStartingUrl = function (url) { this.m_startingUrl = url; } /** * Sets the visibility of the browsers */ IEBrEventListener.prototype.setVisible = function (mode) { this.m_visible = mode; } /** * Sets the silent of the browsers */ IEBrEventListener.prototype.setSilent = function (mode) { this.m_silent = mode; } /** * Starts listening to browser events. */ IEBrEventListener.prototype.start = function () { try { /** * This is a simple work around for the perl js2doxy.pl. * The perl script confuses the following lines as function definitions. */ var that = this; var callbacks = { "BeforeNavigate2": that._onBeforeNavigate2, "NavigateComplete2" : that._onNavigateComplete2, "DocumentComplete" : that._onDocumentComplete, "NewWindow2" : that._onNewWindow2, "OnQuit" : that._onQuit, "OnGotoURL" : that._gotoUrl, "NavigateError": that._onNavigateError }; var sink = DynamicSinkFactory.createSink(callbacks); if (sink && this.m_ieMgr) { sink.Advise(this.m_ieMgr, this); this.m_ieMgrSink = sink; } if (this.m_startingUrl) { this.m_ieMgr.StartingURL = this.m_startingUrl; } if (this.m_ieMgr) { this.m_ieMgr.Timer = this.getTimer(); this.m_ieMgr.Visible = this.m_visible; this.m_ieMgr.Silent = this.m_silent; this.m_ieMgr.Start(); } } catch (e) { this._handleException(arguments.callee, e); } } /** * Stops recording. */ IEBrEventListener.prototype.stop = function () { try { if (this.m_ies) { while (this.m_ies.length > 0) { var ie = this.m_ies.pop(); try { // keep this if (ie) ie.Quit(); } catch (e) { /** * An exception could occur if this message * came from user clicking the Quit icon on the popup window. */ debug.log(e); } } } if (this.m_ieMgr) { this.m_ieMgr.Stop(); } if (this.m_ieMgrSink) { this.m_ieMgrSink.Unadvise(); this.m_ieMgrSink = null; } } catch (e) { this._handleException(arguments.callee, e); } } /** * Handles the BeforeNavigate2 event. * @private */ IEBrEventListener.prototype._onBeforeNavigate2 = function (frame, url, flags, targetFrameName, postdata, headers, time, windowIndex) { this.m_mediator.onBeforeNavigate2(frame, url, flags, targetFrameName, postdata, headers, time, windowIndex); } /** * Handles the NavigateComplete2 event. * @private */ IEBrEventListener.prototype._onNavigateComplete2 = function (frame, url, time, windowIndex) { this.m_mediator.onNavigateComplete2(frame, url, time, windowIndex); } /** * Handles the DocumentComplete event. * @private */ IEBrEventListener.prototype._onDocumentComplete = function (frame, url, time, windowIndex) { this.m_mediator.onDocumentComplete(frame, url, time, windowIndex); } /** * Handles the NavigateError event. * @private */ IEBrEventListener.prototype._onNavigateError = function (frame, url, targetFrameName, statusCode, time, windowIndex) { this.m_mediator.onNavigateError(frame, url, targetFrameName, statusCode, time, windowIndex); } /** * Records an explicit URL action. * @private */ IEBrEventListener.prototype._gotoUrl = function (frame, url, time) { this.m_mediator.onGotoUrl(frame, url, time); } /** * Handles a new window open event. * @private */ IEBrEventListener.prototype._onNewWindow2 = function (br, windowIndex, time) { if (this._onWindowOpen(br, windowIndex)) { this.m_mediator.onNewWindow2(windowIndex, time); } } /** * Handles a window close event. * @private */ IEBrEventListener.prototype._onQuit = function (windowIndex, time) { if (this._onWindowClosed (windowIndex)) { this.m_mediator.onQuit(windowIndex, time); } } /** * Evaluates if a new window event should be recorded. * @private */ IEBrEventListener.prototype._onWindowOpen = function (br, index) { try { if (br) { this.m_ies[index] = br; return true; } } catch (e) { this._handleException(arguments.callee, e); } return false; } /** * Evaluates if a window clsoe event should be recorded. * @private */ IEBrEventListener.prototype._onWindowClosed = function (index) { try { if (index >= 0) { this.m_ies[index] = null; return true; } } catch (e) { this._handleException(arguments.callee, e); } return false; } /** * Gets the number of currently visible browser windows. * * This object only knows about browser windows opened for the recording, * it ignores any browser windows in the same process opened via other means. * * For a simple web tranasction flow with no popups, this method returns 1. * * @treturn int the number of window browsers currently visible. */ IEBrEventListener.prototype.getWindowCount = function () { var count = 0; try { for (var i = 0; i < this.m_ies.length; i++) { var ie = this.m_ies[i]; if (ie) { count++; } } } catch (e) { this._handleException(arguments.callee, e); } return count; } /** * Gets the index of a top level HTMLWindow object. * @tparam HTMLWindow win * @treturn int */ IEBrEventListener.prototype.getWindowIndex = function (win) { try { if (win) { for (var i = 0; i < this.m_ies.length; i++) { var ie = this.m_ies[i]; if (ie && ie.document && win == ie.document.parentWindow) { return i; } } } } catch (e) { this._handleException(arguments.callee, e); } return -1; } /** * Gets the index of the given browser object. * * @tparam IWebBrowser2 br the browser object. * @treturn int the index of the given browser object, or -1 if not found. * in the current set. */ IEBrEventListener.prototype.getBrowserIndex = function (br) { try { if (br) { for (var i = 0; i < this.m_ies.length; i++) { var ie = this.m_ies[i]; if (ie && ie == br) { return i; } } } } catch (e) { this._handleException(arguments.callee, e); } return -1; } /** * Gets the browser object at position i. * @tparam int i the index * @treturn IWebBrowser2 */ IEBrEventListener.prototype.getBrowserAt = function (i) { return this.m_ies[i]; } /** * Handle events. * @private */ IEBrEventListener.prototype._handleException = function (func, e) { handleException(IEBrEventListener, func, e); } /** * This module performs correlation analysis using HTML source. *

Query Parameter Regular Expression Substitution

This rule generates a regular expression for query parameters on an anchor link. This rule is not compatible with 10.2.0.3 agent.

  1. From a given BN event, collect an array of user actions and an array of HTML sources prior to the BN event, but after the document load event.
  2. For each user action, generate a regular expression that act as an initial guess based on the important attributes of the user action.
  3. Use the initial regular expression to find a HTML tag that matches the element associated with the user action.
  4. Use the regular expression generator, and a hint to generate a regular expression that should be suitable for the query parameter portion of the hyper link.
  5. Apply the regular expression on the original HTML source, to see if the query parameter substituted is indeed identical to the query parameter in the BN event.
  6. *
  7. If a match is found, substitute the query parameter in the BN event with a variable placeholder, e.g. [P_LOGOUT]

Form Destination Regular Expression Substitution

This rule generates a regular expression for form destination. This rule is not compatible with 10.2.0.3 agent.

  1. From a given BN event, search for an earlier form submit that correspond to the actual form submission.
  2. From the form submit event, search for an earlier form load event that is recorded during Document Complete, that matches it by action, method, and name.
  3. From the HTML source node that contains the form load event, generate a regular expression that act as an initial guess based on the important attributes of the form.
  4. Use the initial regular expression to find the HTML form tag that matches the form.
  5. Use the regular expression generator, and a hint to generate a regular expression that should be suitable for the query parameter portion of the form destination attribute.
  6. Apply the regular expression on the original HTML source, to see if the query parameter substituted is indeed identical to the query parameter in the BN event.
  7. If a match is found, substitute the query parameter in the BN event with a variable placeholder, e.g. [P_FORM_DEST]

Hidden Input/Ignore Input Hint Calculation

In 10.2.0.3 and earlier versions of recorder, there is a heuristic algorithm used to calculate the 'hidden input hint' property, and the 'ignore input hint' iproperty. These two properties tell the beacon to perform/ignore runtime value substitution in the post data. It is a heuristic because the match is based on the form name and values.

In this version of the recorder, the algorithm is more deterministic:

  1. From a given a BN event, search for an earlier form submit event that correspond the actual form submission.
  2. From the form submit event, search for an earlier form load event that is recorded during Document Complete, that matches it by action, method, and name.
  3. Compare the submitted values in the post data (method=POST), or in the query string (method=GET) with the values in the form load event.
  4. If a submitted value matches the value of a hidden input field during page load, then it is a 'hidden input hint' property.
  5. If a submitted value does not match the value of a hidden input field during page load, then it is either an 'ignore input hint' property (for recording that needs to be compatible with 10.2.0.3 beacons), or we perform another next step.
  6. Search for all user actions field between the page load event, and form submit event. For each user action, search for the HTML tag (from the HTML source) that correspond to the target of the user action.

Automatic User Data Parameterizatoin

A recorded web transaction may need to work in multiple environments of the same application with minimal modification, for example, in Test and Production environments. Often the user credentials, the application data are different between these environments. Suppose web transaction involves:
  1. open newValue='http://testapp.com/app'
  2. type name='username' newValue='sysadmin'
  3. type name= 'password' newValue= 'sysadmin'
  4. click title= 'Login' tagName='img'
  5. waitForPageToLoad
  6. click text='Logout'
  7. waitForPageToLoad
One of the steps contains a POST data with ...&username=sysadmin&password=sysadmin&.... Since we can perform correlation on the user actions and the corresponding POST data: The POST data property should be rewritten to ...&username=[P_USERNAME]&password=[P_PASSWORD]&... The action properties should be rewritten to
type name='username' newValue='[P_USERNAME]'
type name='password' newValue='[P_PASSWORD]'.
A nonsensitive value property should be added: P_USERNAME = sysadmin. A sensitive value property should be added: P_PASSWORD = ****** (real value is masked from the user). This makes it easy for user when credentials are changed, or when creating a web transaction template, as sensitive and nonsensitive values are automatically converted to variables. Changes only need to happen in one place. Passwords should be automatically extracted, and stored in the sensitive value table, with the real values masked. Any user editing the web transaction (who may be different from the original creator) cannot see the password value. The algorithm works as follows:
  1. For a given BN event, identify when the corresponding form was first loaded
  2. Locate all user actions after the form load event, and the BN event
  3. Correlate the value user entered with the corresponding value within the POST data:
  4. The correlation algorithm is tricky, because the element user interacted may or may not have the name identifier.
  5. If the HTML element has the name attribute, locate the corresponding entry in the POST data by name.
  6. Otherwise, locate the corresponding entry in the POST databy value.
  7. If multiple entries in the POST data match by the same name or value, match the index as well.
* */ function HTMLAnalyzer(recorder) { this.init(recorder); } HTMLAnalyzer.prototype = new IERecProcessor(); /** * Initializes this object. * @private */ HTMLAnalyzer.prototype.init = function(recorder) { this.m_mediator = recorder; this.m_htmlHelper = recorder.getHTMLHelper(); this.m_startTime = (new Date()).getTime(); this.m_plus = /\+/g; this.m_space = /%20/g; this.m_tilda = /%7E/g; this.m_excla = /%21/g; this.m_pcent = /@/g; this.m_paral = /%28/g; this.m_para2 = /%29/g; this.m_quote = /%27/g; } /** * Analyzes HTML content and generate regular expressions * for dynamic URLs or form submits. * @tparam XMLElement node the document node. */ HTMLAnalyzer.prototype.process = function (node) { try { this._stackTrace(arguments.callee, " start"); var submitFormConditions = "form[@state = 'submit' or @state = 'onsubmit']"; this.foreachNodeReverse(node.selectNodes(submitFormConditions), this._processSubmitFormNode); this.foreachNodeReverse(node.selectNodes("BN"), this._processBNNode); this.addSessionParameters(node); this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } } /** * Processes a single form submit node. * @private */ HTMLAnalyzer.prototype._processSubmitFormNode = function(node) { try { this._stackTrace(arguments.callee, " start"); // go up to find a form node with corresponding values // upon document load. var previousForm = this._getPreviousForm(node); // go to its parent DC node and get the charset. var dcNode = this._getDCNodeFromFormBefore(previousForm); var charset = null; if (dcNode) { charset = this.getProperty(dcNode, "charset"); } if (previousForm) { // debug.log("previousForm " + previousForm.getAttribute("nodeId")); // start from the current form submission node, // go down to find the corresponding BN and a map of submitted // form name/values. var nextBNandMap = this._getNextBNAndMap(node, charset); if (nextBNandMap) { // nextBNandMap is a hashtable where // "node" -> next BN // "map" -> a multi-valued map of submitted values. var nextBN = nextBNandMap["node"]; var matchingBNMap = nextBNandMap["map"]; var codePage = nextBNandMap["codePage"]; if (nextBN && matchingBNMap && codePage) { // debug.log("nextBN nodeId " + nextBN.getAttribute("nodeId")); // specify the relationship between // the BN ndoe and the current form node. nextBN.setAttribute("formNodeId", node.getAttribute("nodeId")); this.setProperty(nextBN, "is_form_submit", "yes"); // we have a match, process this matching. this._processPreviousFormAndBN(previousForm, nextBN, matchingBNMap, codePage); } } } this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } } /** * Locate the corresponding BN node and the submitted values, * from a form submission. * @private */ HTMLAnalyzer.prototype._getNextBNAndMap = function(node, charset) { try { this._stackTrace(arguments.callee, " start"); var formMap = this._getFormMap(node); var nodeId = node.getAttribute("nodeId"); var formAction = this.getFormAction(node); var formMethod = this.getFormMethod(node); var formName = this.getFormName(node); var formId = this.getFormId(node); var codePage = this._getCodePage(charset); // the following value is set by matchingBNFunc for the map of // submitted form name/values in the POST data (form method=post) or (form method=get). var matchingBNMap = null; var matchingBNCandidates = []; // the function that we use to evaluate each BN node. // this has to be an inner function because we are evaluating // matchingBNMap as well var matchingBNFunc = function (bn) { var url = this.getURL(bn); // FIXME url may prefix with ./ or ../ if (formAction && !Util.compareURLByComponentsTrailingAuthority(url, formAction)) { // ok return; } var bnSubmits = null; var bnPostdata = this.getProperty(bn, "postdata"); var bnHeaders = this.getProperty(bn, "headers"); if (!bnPostdata) { bnPostdata = ""; } // derive the submitted data from either the postdata payload // or from query parameter. // // Beware: in the case of get, the set of query parameters // mab be more than what is in the form itself. if (formMethod == "post" && bnHeaders && bnHeaders.indexOf(Constants.CONTENT_TYPE_FORM_URLENCODED) != -1) { // post submission bnSubmits = bnPostdata; } else if (formMethod == "get" && !bnPostdata) { // find the first ?. var queryIndex = url.indexOf("?"); if (queryIndex != -1) { bnSubmits = url.substr(queryIndex+1); } else { bnSubmits = ""; } } // FIXME get the character set. if (bnSubmits != null) { // generate a map of name value pairs. debug.log("comparing submitMap from nodeId " + bn.getAttribute("nodeId")); var submitsArr = bnSubmits.split("&"); var submitsMap = new Mvmap(); for (var i = 0; i < submitsArr.length; i++) { submitsArr[i] = submitsArr[i].split("="); if (submitsArr[i].length == 2) { var name = this.URLEncode2Unicode(submitsArr[i][0]); var value = this.URLEncode2Unicode(submitsArr[i][1]); submitsMap.add(name, value); } else { // hmm... we have a case where // = is not present, purposely ignore. } } // FIXME compute score ?? and get the best one. var score = formMap.compare(submitsMap); matchingBNCandidates.push({"node" : bn, "score" : score, "map" : submitsMap}); // debug.log("BN node " + bn.getAttribute("nodeId") + " form node " + node.getAttribute("nodeId") + " score:" + score); return false; } else { return false; } } // end of matchingBN this.getNextNode(node, "BN", matchingBNFunc); if (matchingBNCandidates.length > 0) { debug.log("matchingBNCandidates " + matchingBNCandidates.length); var maxEntry = null; var maxScore = -1; var maxMap = null; for (var i = 0; i < matchingBNCandidates.length; i++) { var score = matchingBNCandidates[i].score; if (score > maxScore) { maxEntry = matchingBNCandidates[i].node; maxScore = matchingBNCandidates[i].score; maxMap = matchingBNCandidates[i].map; } } } var result = {}; result["node"] = maxEntry; result["map"] = maxMap; result["codePage"] = codePage; this._stackTrace(arguments.callee, " end"); return result; } catch (e) { this._handleException(arguments.callee, e); } } /** * Generates a map of name value pairs from a form node. * @private */ HTMLAnalyzer.prototype._getFormMap = function(node) { try { this._stackTrace(arguments.callee, " start"); var formMap = new Mvmap(); // an inline function that is used to populate an input node. var genFormNVMapFunc = function (inputNode) { // each input node must have a name and a value. // this is checked else where and no need to check here. var type = inputNode.getAttribute("type"); var name = inputNode.getAttribute("name"); var value = inputNode.getAttribute("value"); // the name, values are already in unicode. var add = false; switch (type) { // checkbox may or may not be submitted. case "password": add = true; break; case "text": add = true; break; case "hidden": add = true; break; case "textarea": add = true; break; case "select": add = true; break; case "checkbox": add = true; break; // because it may not be selected (thus not posted). case "radio": add = true; break; // because it may not be selected (thus not posted). case "file": break; case "image": break; case "button": break; case "reset": break; case "submit": break; default: add = true; break; } if (name != "" && add) { formMap.add(name, value); } } // end genFormNVMapFunc // populate formMap this.foreachNode(node.selectNodes("input"), genFormNVMapFunc); this._stackTrace(arguments.callee, " end"); return formMap; } catch (e) { this._handleException(arguments.callee, e); } } /** * Finds the form node (recorded during document load) * that matches the form node (recorded when form is submitted). * @private */ HTMLAnalyzer.prototype._getPreviousForm = function(node) { try { this._stackTrace(arguments.callee, " start"); // go down to find a BN that matches it by URL // go up to find a form that matches it by location. var formAction = this.getFormAction(node); var formMethod = this.getFormMethod(node); var formName = this.getFormName(node); var formId = this.getFormId(node); // locate the form as it is during page load. var matchingFormFunc = function(form) { var state = form.getAttribute("state"); if (state == "before") { return (this.getFormAction(form) == formAction && this.getFormMethod(form) == formMethod && this.getFormName(form) == formName && this.getFormId(form) == formId); } else { return false; } } var result = this.getPreviousNode(node, "form", matchingFormFunc); this._stackTrace(arguments.callee, " end"); return result; } catch (e) { this._handleException(arguments.callee, e); } } /** * Processes the previous form and the BN node to determine * hidden input hint property, * ignore input hint property, * or any regular expressions substitution. * @private */ HTMLAnalyzer.prototype._processPreviousFormAndBN = function(previousForm, nextBN, matchingBNMap, codePage) { try { this._stackTrace(arguments.callee, " start"); // debug.log("processPreviousFormAndBN nextBN nodeId " + nextBN.getAttribute("nodeId")); // debug.log("processPreviousFormAndBN previousForm nodeId " + previousForm.getAttribute("nodeId")); var url = this.getURL(nextBN); // set the hidden input hint to the previous form. var hints = this._processHints(previousForm, nextBN, matchingBNMap); if (hints) { // these must be StringBuffer objects. var hiddenInputHint = hints["hiddenInputHint"]; var ignoreInputHint = hints["ignoreInputHint"]; var postdata = hints["postdata"]; if (hiddenInputHint && !hiddenInputHint.isEmpty()) { // hiddenInputHint must be in the form of value1,value2. // append the form name (even if it is empty) : var formName = this.getFormName(previousForm); this.setProperty(previousForm, "hidden_input_hint", (formName ? formName : "") + ":" + hiddenInputHint.toString()); } // compute the delta between postdata and form in Document Complete // to determine the ignore hint if (nextBN) { if (ignoreInputHint && !ignoreInputHint.isEmpty()) { this.setProperty(nextBN, "ignore_input_hint", ignoreInputHint.toString()); } if (postdata && postdata != "") { this.setProperty(nextBN, "postdata", postdata); } var htmlNodeId = previousForm.getAttribute("formParentId"); var htmlNode = this.m_mediator.getNodeWithId(htmlNodeId); // rewrite the nextBN url to the form destination if (htmlNode) { this._processFormDestAndBN(nextBN, previousForm, htmlNode); } } } // analyse all the user actions between previous form and // next BN, see if any one of them are submitted. var inputParams = this._processUserParameters(previousForm, nextBN, matchingBNMap, codePage); if (inputParams) { var normValues = inputParams[Constants.NON_SENSITIVE_VALUES]; var sensValues = inputParams[Constants.SENSITIVE_VALUES]; if (normValues) { for (var i = 0; i < normValues.length; i++) { var normValue = normValues[i]; this.appendNameValueProperty(nextBN, Constants.NON_SENSITIVE_VALUES, normValue.name, normValue.value); } } if (sensValues) { for (var i = 0; i < sensValues.length; i++) { var sensValue = sensValues[i]; this.appendNameValueProperty(nextBN, Constants.SENSITIVE_VALUES, sensValue.name, sensValue.value); } } } this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } } /** * Generates hidden input hints. * @private */ HTMLAnalyzer.prototype._processHints = function(previousForm, nextBN, matchingBNMap) { try { this._stackTrace(arguments.callee, " start"); if (matchingBNMap) { matchingBNMap.resetIterators(); } var matchingBNPostdata = this.getProperty(nextBN, "postdata"); var postdataChanged = false; nextBN.setAttribute("formNodeId", previousForm.getAttribute("nodeId")); var previousFormHTMLNode = null; try { // ignore this var previousFormHTMLNodeId = parseInt(previousForm.getAttribute("formParentId")); if (previousFormHTMLNodeId >= 0) { previousFormHTMLNode = this.m_mediator.getNodeWithId(previousFormHTMLNodeId); } } catch (e) { // ignore parseInt error } var actionAndHTMLNodes = []; var htmlNodes = []; var actionNodes = []; var aNode= previousFormHTMLNode; while (aNode != nextBN) { if (aNode.tagName == "action") { actionAndHTMLNodes.push(aNode); actionNodes.push(aNode); } else if (aNode.tagName == "html") { actionAndHTMLNodes.push(aNode); htmlNodes.push(aNode); } aNode = aNode.nextSibling; } // FIXME what does this DO var allMatches = []; // debug.log("nodeId anchor length" + actionAndHTMLNodes.length); for (var i = actionAndHTMLNodes.length - 1; i >=0; i--) { var aNode = actionAndHTMLNodes[i]; if (aNode.tagName == "action") { var anchor = aNode.getAttribute("anchor") ? "a" : null; var initialRegex = this._getInitialRegex(aNode, anchor); // debug.log("initialRegex " + initialRegex); for (var j = i - 1; j >=0; j--) { var previousNode = actionAndHTMLNodes[j]; if (previousNode.tagName == "html") { var htmlId = parseInt(previousNode.getAttribute("htmlId")); // debug.log("nodeId htmlId " + htmlId); var regexMatches = this._getRegexMatches(this.m_htmlHelper, initialRegex, htmlId, false); for (var k = 0; k < regexMatches.length; k++) { // debug.log("match " + regexMatches[k]); allMatches.push(regexMatches[k]); } } } } } // generate hidden input hint based on previousForm. var hiddenInputHint = new StringBuffer(); var ignoreInputHint = new StringBuffer(); var macList = {}; var macListInputs = this._getMatchingHTML(htmlNodes, "]*name=(['\"]?)FORM_MAC_LIST\\1[^>]*>"); for (var i in macListInputs) { if (macListInputs[i].length > 0) { var value = DescriptorGenerator.getAttribute(macListInputs[i][0], "value"); if (value) { // 24 bytes are added to the end of the FORM_MAC_LIST value = value.substr(0, value.length - 24); // this is very strange. sometimes this special token messes up the parsing var specialToken = /(?:\*\*\*)?@@@FORM_MAC_LIST\*\*\*@@@/g; value = value.replace(specialToken, "^"); // debug.log("NEW MAC" + value); var macInputNames = value.split("^"); for (var j in macInputNames) { var macInputName = macInputNames[j]; if (macInputName.lastIndexOf(";T") == macInputName.length - 2) { macInputName = macInputName.substr(0, macInputName.length - 2); } if (macInputName == "event" || macInputName == "source") { } else { macList[macInputName] = true; } // debug.log("MAC " + macInputName); } } } } var addToHintFunc = function(inputNode) { this._stackTrace(arguments.callee, " start"); // get hidden inputs only var inputType = inputNode.getAttribute("type"); var name = inputNode.getAttribute("name"); var value= inputNode.getAttribute("value"); // debug.log("nodeId " + name + " " + value); if (matchingBNMap) { var postedValueIndex = matchingBNMap.iteratorIndex(name); var postedValue = matchingBNMap.item(name); // debug.log("nodeId " + name + " p->" + postedValue); // debug.log("name " + name); // debug.log("postedIndex " + postedValueIndex); // debug.log("postedValue " + postedValue); // debug.log("inputType " + inputType); if (!inputType) { if (!hiddenInputHint.isEmpty()) { hiddenInputHint.append(","); } hiddenInputHint.append(inputNode.getAttribute("name")); } if (postedValue != null) { // debug.log(name + " in MAC =" + macList[name]); if ((postedValue.indexOf("{!!") != -1) || (macList[name] && inputType != "text" && postedValue.length > Constants.WTI_NUM_CHARS) ) { // handle EBS R12 encrypted case/mac-ed values /* // debug.log("posted " + postedValue); // debug.log("name " + name); // debug.log("value " + value); // debug.log("type " + inputType); // debug.log(""); */ var partialPostedValuePattern; var partialPostedValueHtmlFragments; var postedValuePattern; var postedValueHtmlFragments; var postedValueHtmlMatch; var postedValueHtmlNode; if ((inputType == "select") || (inputType == "radio") || (inputType == "checkbox") || (!inputType && (postedValue != value))) { // value is the original form input // postedValue is what gets posted. // !inputType means it is hidden input. // // if they are not equal, // assume that the postedValue appears on the page as it is, // as it should not be url encoded. if (inputType == "select") { // generate partial pattern for select input partialPostedValuePattern = [ "]*", Util.regexEscape(name), "[^>]*>.*]*", Util.regexEscape(postedValue), "[^>]*>.*" ].join(""); // debug.log(partialPostedValuePattern); } else if (inputType == "radio") { partialPostedValuePattern = [ "]*", "value=[\"']", Util.regexEscape(postedValue), "[\"'][^>]*>"].join(""); } else if (inputType == "checkbox") { partialPostedValuePattern = [ "]*", "value=[\"']", Util.regexEscape(postedValue), "[\"'][^>]*>"].join(""); } else { // generate partial pattern for hidden input partialPostedValuePattern = "<[^>]*" + Util.regexEscape(postedValue) + "[^>]*>"; } // look in html nodes for tags containing posted values var partialPostedValueHtmlFragments = this._getMatchingHTML(htmlNodes, partialPostedValuePattern); postedValueLoop: for (var i in partialPostedValueHtmlFragments) { if (partialPostedValueHtmlFragments[i].length > 0) { postedValuePattern = partialPostedValuePattern; // for tag types that require a closing tag (e.g. a, select), // get html fragment for enclosing tags and innner contents // (i.e. ) var tagType = DescriptorGenerator.getTagType(partialPostedValueHtmlFragments[i][0]); if (tagType) { var lcTagType = tagType.toLowerCase(); // FIXME should button be handled here as well? if (lcTagType == "a" || lcTagType == "button") { postedValuePattern = [partialPostedValuePattern, ".*"].join(""); postedValueHtmlFragments = this._getMatchingHTML(htmlNodes, postedValuePattern); for (var j in postedValueHtmlFragments) { if (postedValueHtmlFragments[j].length > 0) { postedValueHtmlMatch = postedValueHtmlFragments[j][0]; // debug.log(postedValueHtmlMatch); postedValueHtmlNode = htmlNodes[j]; break postedValueLoop; } } } else { postedValueHtmlMatch = partialPostedValueHtmlFragments[i][0]; postedValueHtmlNode = htmlNodes[i]; break; } } else { stderr.log("Cannot parse partialPostedValueHtmlFragments[i][0]"); } } } } var hint = { PARAM_NAME: name, PARAM_VAL: postedValue, HINT_TYPE: DescriptorGenerator.HINT_TYPES.PARAM_NVPAIR }; if (postedValueHtmlMatch && postedValueHtmlNode) { var descObj = DescriptorGenerator.generateDescriptor(postedValueHtmlMatch, hint, actionNodes); var isDescValid = this.validateDescriptor(this.m_htmlHelper, postedValueHtmlNode, descObj); if (!isDescValid) { // generate better match for WTI case if initial pattern failed hint.WTI = true; descObj = DescriptorGenerator.generateDescriptor(postedValueHtmlMatch, hint, actionNodes); isDescValid = this.validateDescriptor(this.m_htmlHelper, postedValueHtmlNode, descObj); } // stderr.log("validating:" + postedValueHtmlMatch + "|" + name + "=" + postedValue + "|" + descObj.getPattern()); if (isDescValid) { /* debug.log("nodeId desc "); debug.log("nodeId desc :" + descObj.getType()); debug.log("nodeId desc :" + descObj.getName()); debug.log("nodeId desc :" + descObj.getPattern()); debug.log("nodeId desc :" + descObj.getEncoding()); */ var encodedPostedValue = this.Unicode2URLEncode(postedValue); var encodedPostedName = this.Unicode2URLEncode(name); // replace value in the postdata with [descObj.getPattern()]; // debug.log("nodeId " + encodedPostedName + "=" + encodedPostedValue); // we call jsRegexEscape because encodedPostedName/encodedPostedValue could contain +, // which is a special regex character. var descName = createNameValuePropertyName("P_" + name); matchingBNPostdata = matchingBNPostdata.replace(encodedPostedName + "=" + encodedPostedValue, encodedPostedName + "=[" + descName + "]"); postdataChanged = true; if (this.appendNameValueProperty(postedValueHtmlNode, "regex", descName, descObj.getPattern())) { this.appendProperty(postedValueHtmlNode, "regmd", DescriptorGenerator.ENCODING_MODES.URL_ENCODE); } } else { if (descObj) { // stderr.log("Cannot find match " + descObj.getPattern() + " in " + postedValueHtmlNode.getAttribute("htmlId")); } } debug.log("nodeId desc end ----------------------- "); } else { // postedValueHtmlMatch or postedValueHtmlNode is null // this means the HTML fragment is not found. debug.log("postedValueHtmlMatch or postedValueHtmlNode not found for:" + name + "=" + postedValue); debug.log("postedValuePattern:" + postedValuePattern); debug.log("postedValueHtmlFragments:" + postedValueHtmlFragments); } } else if (value && postedValue != value && value != "" && !inputType) { if (!ignoreInputHint.isEmpty()) { ignoreInputHint.append(","); } ignoreInputHint.append(name, ":", postedValueIndex); } // debug.log("nodeId ignoreInput " + name + ":" + postedValue); } } this._stackTrace(arguments.callee, " end"); } this.foreachNode(previousForm.selectNodes("input"), addToHintFunc); var result = { "hiddenInputHint": hiddenInputHint, "ignoreInputHint": ignoreInputHint }; if (postdataChanged) { result["postdata"] = matchingBNPostdata; } this._stackTrace(arguments.callee, " end"); return result; } catch (e) { this._handleException(arguments.callee, e); } } /** * Extract out user data parameters. * @private * @param previousForm - we are only interested in user action events after this action. * @param nextBN - the BN node that should perform the form submission. * @param matchingBNMap - the name/value pair of submitted values */ HTMLAnalyzer.prototype._processUserParameters = function(previousForm, nextBN, matchingBNMap, codePage) { try { this._stackTrace(arguments.callee, " start"); // get all action nodes after previousForm, but before next BN, and are typed. var actionNodes = []; var node = previousForm.nextSibling; var normValues = []; var sensValues = []; // debug.log("previousForm nodeId" + previousForm.getAttribute("nodeId")); // debug.log("processUserParameter nextBN nodeId" + nextBN.getAttribute("nodeId")); // FIXME check for post var queryParam = this.getURL(nextBN); var postdata = this.getProperty(nextBN, "postdata"); var post = (postdata != ""); var hasPassword = false; while (node && node != nextBN) { if (node.tagName == "action" && node.getAttribute("type") == Constants.type) { if (node.getAttribute("password") == "true") { hasPassword = true; } actionNodes.push(node); } node = node.nextSibling; } // if there are no actions involving password, // then leave the user entered fields as they are, // because we can get it wrong. if (!hasPassword) { return; } var paramNames2Index = new Mvmap(); // keep a map of user entered value to number of // times they have appeared so far. for (var i = 0; i < actionNodes.length; i++) { var actionNode = actionNodes[i]; // there is a problem here where, // in Peoplesoft's login, the username/password // is actually posted twice. if (actionNode.getAttribute("replaced") == "true") { // continue; } var name = actionNode.getAttribute("name"); var value = actionNode.getAttribute("newValue"); var oldValue = actionNode.getAttribute("oldValue"); if (oldValue) { value = oldValue; } // debug.log("name "+ name); // debug.log("value "+ value); var password = actionNode.getAttribute("password"); var urlEncodedName = this.Unicode2URLEncode(name); var urlEncodedValue= this.Unicode2URLEncode(value); // debug.log(urlEncodedName); // debug.log(urlEncodedValue); var param = null; if (name != null && matchingBNMap.exists(name)) { // found an entry. // locate name=value in nextBN // replace it with name=[name] param = createNameValuePropertyName("V_" + name.toUpperCase(), "_"); } else { // hmmm, since name is not found, we have to use the value // loop through all values in the matchingBNMap // see if anything can be found. // replace it with name=[identifying attribute] param = createNameValuePropertyName("V_" + this._getParamName(actionNode).toUpperCase(), "_"); } paramNames2Index.add(param, "true"); paramNames2Index.item(param); var idx = paramNames2Index.iteratorIndex(param); if (idx != 1) { param = param + "_" + (idx-1); } if (post) { if (name != null && matchingBNMap.exists(name)) { postdata = postdata.replace (urlEncodedName + "=" + urlEncodedValue, urlEncodedName + "=" + "[" + param + "]"); } else { // we need to check for the entire value var toReplace = "=" + urlEncodedValue + "&"; if (postdata.indexOf(toReplace) != -1) { postdata = postdata.replace (toReplace, "=" + "[" + param + "]" + "&"); } else { postdata = postdata.replace ("=" + urlEncodedValue, "=" + "[" + param + "]" + "&"); } } } else { if (name != null && matchingBNMap.exists(name)) { queryParam = queryParam.replace(urlEncodedName + "=" + urlEncodedValue, urlEncodedName + "=" + "[" + param + "]"); } else { // we need to check for the entire value var toReplace = "=" + urlEncodedValue + "&"; if (postdata.indexOf(toReplace) != -1) { queryParam = queryParam.replace (toReplace, "=" + "[" + param + "]" + "&"); } else { queryParam = queryParam.replace ("=" + urlEncodedValue, "=" + "[" + param + "]" + "&"); } } } if ("true" == password) { sensValues.push({name: param, value : value}); } else { normValues.push({name: param, value : value}); } // debug.log("processUserParameter actionNodeid " + actionNode.getAttribute("nodeId")); actionNode.setAttribute("oldValue", actionNode.getAttribute("newValue")); actionNode.setAttribute("newValue", "[" + param + "]"); actionNode.setAttribute("replaced", "true"); } if (post) { this.setProperty(nextBN, "postdata", postdata); } else { this.setURL(nextBN, queryParam); } var result = {}; result[Constants.SENSITIVE_VALUES] = sensValues; result[Constants.NON_SENSITIVE_VALUES] = normValues; this._stackTrace(arguments.callee, " end"); return result; } catch (e) { this._handleException(arguments.callee, e); } } /** * Guess a parameter name. * @private */ HTMLAnalyzer.prototype._getParamName = function(actionNode) { this._stackTrace(arguments.callee, " start"); var result = ""; try { var title = actionNode.getAttribute("title"); var alt = actionNode.getAttribute("alt"); var name = actionNode.getAttribute("name"); var id = actionNode.getAttribute("id"); var index = actionNode.getAttribute("index"); if (title) { result = title; } else if (alt) { result = alt; } else if (name) { result = name; } else if (id) { result = id; } else if (index) { result = "PARAM" + index; } else { // hmmm nothing about that element exists. result = "PARAM"; } this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } return result; } /** * Processes a bn node and form node. * @private */ HTMLAnalyzer.prototype._processFormDestAndBN = function(bnNode, formNode, htmlNode) { try { this._stackTrace(arguments.callee, " start"); var formAction = this.getFormAction(formNode); if (formAction) { if (formAction.indexOf("?") == -1) { // no need to process this. // debug.log("_setURLProcessed for bnNode " + bnNode.getAttribute("nodeId")); this._setURLProcessed(bnNode); this._stackTrace(arguments.callee, " end"); return; } } var initialRegex = this._getInitialRegexForFormDest(formNode); // debug.log("initial for form dest" + initialRegex); if (this._processRegexInURL(initialRegex, bnNode, htmlNode, "form", null)) { // it worked. } else { // debug.log("Doh " + bnNode.getAttribute("nodeId") + " " + formNode.getAttribute("nodeId")); } this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } } /** * Processes all remaining BN (that are not form submissions) * @private */ HTMLAnalyzer.prototype._processBNNode = function(node) { try { this._stackTrace(arguments.callee, " start"); // suppose we have a set of events such as // action // ... // html // html // action // action // BN // html // html // action // action <- prevAction // BN <- this node // ... var done = false; // debug.log("_processBNNode " + node.getAttribute("nodeId")); if (this._isURLProcessed(node)) { // debug.log("processed" + node.getAttribute("OK")); done = true; } var url = this.getURL(node); if (url.indexOf("?") == -1) { // debug.log("processed no ?"); this._setURLProcessed(node); done = true; } if (!done) { var thisFrameName = this.getTargetFrameName(node); var thisWindowIndex = this.getWindowIndex(node); var earliestBNFunc = function earliestBNFunc(bnNode) { this._stackTrace(arguments.callee, " start"); var targetFrameName = this.getTargetFrameName(bnNode); var windowIndex = this.getWindowIndex(bnNode); // debug.log("try " + bnNode.getAttribute("nodeId")); // debug.log("|" + targetFrameName + "| |" + thisFrameName + "|"); // debug.log("|" + thisWindowIndex + "| |" + windowIndex + "|"); if (targetFrameName == "_pprIFrame") { return false; } else if (thisWindowIndex < windowIndex) { return false; } else if (thisWindowIndex > windowIndex) { return true; } else if (targetFrameName == "" || targetFrameName == null) { return true; } else if (targetFrameName == thisFrameName) { return true; } else { return false; } this._stackTrace(arguments.callee, " end"); } var actionAndHTMLNodes = []; // stderr.log("finding earliestBN for " + node.getAttribute("nodeId")); var earliestBN = this.getPreviousNode(node, "BN", earliestBNFunc); if (earliestBN) { // stderr.log("earliestBN for " + node.getAttribute("nodeId") + " is " + earliestBN.getAttribute("nodeId")); } else { // stderr.log("earliestBN for " + node.getAttribute("nodeId") + " is null"); } var prev = node.previousSibling; var size = 0; while (prev) { if (prev == earliestBN) { // debug.log("size " + size + " " + node.getAttribute("nodeId") + " " + earliestBN.getAttribute("nodeId")); break; } if (this.isAction(prev)) { var windowIndex = this.getWindowIndex(prev); // only interested in action nodes that are on the same window or from parent window. if (windowIndex <= thisWindowIndex) { actionAndHTMLNodes.push(prev); } } else if (this.isHtml(prev)) { actionAndHTMLNodes.push(prev); // only interested in html nodes that are on the same window or from parent window. var windowIndex = this._getWindowIndexForHTML(prev); if (windowIndex <= thisWindowIndex) { actionAndHTMLNodes.push(prev); } } else { // proceed. } size++; prev = prev.previousSibling; } // debug.log("BN " + node.getAttribute("nodeId") + ":actionAndHTMLNodes #=" + actionAndHTMLNodes.length); this._processActionAndBN(node, actionAndHTMLNodes); } this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } } /** * Correlate a BN node and the previous set of action and HTML nodes. * BN 0 * NC * html 0 * * BN 1 * NC * html 1 * Html 2 * * BN 2 (frame) * NC * html 3 * * this BN * * in this case, we want all. * @private */ HTMLAnalyzer.prototype._processActionAndBN = function(bnNode, actionAndHTMLNodes) { try { this._stackTrace(arguments.callee, " start"); for (var i = 0; i < actionAndHTMLNodes.length; i++) { var actionOrHtmlNode = actionAndHTMLNodes[i]; // stderr.log("actionOrHtmlNode tag=" + actionOrHtmlNode.tagName + " " + actionOrHtmlNode.getAttribute("nodeId")); } var postdata = this.getProperty(bnNode, "postdata"); var headers = this.getProperty(bnNode, "headers"); if (postdata && headers) { // form submit // this case should have been handled else where } else if (actionAndHTMLNodes) { // var url = this.getURL(bnNode); // go through all possible action and html nodes. // debug.log("actionAndHTMLNodes " + actionAndHTMLNodes.length); // A N^2 solution. // for each action node, examine all prior HTML node. for (var i = actionAndHTMLNodes.length - 1; i >= 0; i--) { var node = actionAndHTMLNodes[i]; var tagName = node.tagName; if (tagName == "action") { var anchor = node.getAttribute("anchor") ? "a" : null; // based on the action node, // make a guess. // stderr.log("performing _getInitialRegex on " + node.getAttribute("nodeId") + " BN=" + bnNode.getAttribute("nodeId")); var regex = this._getInitialRegex(node, anchor); // stderr.log("_getInitialRegex result " + regex); if (regex != null) { for (var j = i; j < actionAndHTMLNodes.length; j++) { var htmlNode = actionAndHTMLNodes[j]; var tagName2 = htmlNode.tagName; if (tagName2 == "html") { // stderr.log("Loop 1" + htmlNode.getAttribute("htmlId") + " " + bnNode.getAttribute("nodeId")); // node is an action here. if (this._processRegexInURL(regex, bnNode, htmlNode, anchor, [node])) { // great, we have a match. // stderr.log("we have a match"); return; } } } for (var j = 0; j < i; j++) { var htmlNode = actionAndHTMLNodes[j]; var tagName2 = htmlNode.tagName; if (tagName2 == "html") { // stderr.log("Loop 2" + htmlNode.getAttribute("htmlId") + " " + bnNode.getAttribute("nodeId")); // node is an action here. if (this._processRegexInURL(regex, bnNode, htmlNode, anchor, [node])) { // great, we have a match. debug.log("we have a match"); return; } else { } } } } } } } this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } } /** * Gets the regular expression for finding form tags. * @private */ HTMLAnalyzer.prototype._getInitialRegexForFormDest = function(formNode) { try { this._stackTrace(arguments.callee, " start"); var initialRegex = "]*"; var formName = this.getFormName(formNode); if (formName) { initialRegex += "name=['\"]" + formName + "['\"][^<>]*>"; } this._stackTrace(arguments.callee, " end"); return initialRegex; } catch (e) { this._handleException(arguments.callee, e); } } /** * Generates an initial regular expression guess that figures out * based on action node. It is used to locate the corresponding * HTML tag for further analysis. * @private */ HTMLAnalyzer.prototype._getInitialRegex = function(actionNode, anchor) { try { this._stackTrace(arguments.callee, " start"); var initialRegex = actionNode.getAttribute("initialRegex"); if (initialRegex) { // debug.log("return initialRegex " + initialRegex); return initialRegex; } var tagName = actionNode.getAttribute("tagName"); var type = actionNode.getAttribute("type"); var index = 0; try { var indexAttr = actionNode.getAttribute("index"); if (indexAttr) { index = parseInt(indexAttr); } } catch (e) { // ignore, as default value is 0; } var value = actionNode.getAttribute("html"); if (!value) { value = actionNode.getAttribute("text"); } if (value) { var regex = new StringBuffer(); if (anchor) { regex.append("]*>"); } regex.append("<", tagName,"[^<>]*>",Util.regexEscape(value), ""); if (anchor) { regex.append(""); } var regexStr = regex.toString(); // FIXME this code below is wrong /* if (index > 0) { regex.append("{" + index + "}"); } */ // debug.log("regexStr " + regexStr, true); actionNode.setAttribute("initialRegex", regexStr); this._stackTrace(arguments.callee, " end"); return regexStr; } var attrs = ["title", "alt", "value", "onchange", "onclick", "src"]; for (var i = 0; i < attrs.length; i++) { var attr = attrs[i]; value = actionNode.getAttribute(attr); if (value) { var regex = new StringBuffer(); if (anchor) { regex.append("]*>"); } regex.append("<", tagName, "[^<>]*", attr, "=", "[\"']", Util.regexEscape(value), "[\"']", "[^>]*>"); if (anchor) { regex.append(".*"); } // FIXME this code below is wrong /* if (index > 0) { regex.append("{" + index + "}"); } */ var regexStr = regex.toString(); // debug.log(regexStr, true); actionNode.setAttribute("initialRegex", regexStr); this._stackTrace(arguments.callee, " end"); return regexStr; } } attrs = ["name", "id"]; for (var i = 0; i < attrs.length; i++) { var attr = attrs[i]; value = actionNode.getAttribute(attr); if (value) { var regex = new StringBuffer(); regex.append("<[^<>]*", attr, "=", "[\"']", Util.regexEscape(value), "[\"']", "[^<>]*", ">", ".*", "]*>"); // FIXME this code below is wrong /* if (index > 0) { regex.append("{" + index + "}"); }*/ var regexStr = regex.toString(); // debug.log(regexStr, true); actionNode.setAttribute("initialRegex", regexStr); this._stackTrace(arguments.callee, " end"); return regexStr; } } this._stackTrace(arguments.callee, " end"); // FIXME // alert('_getInitialRegex return null'); } catch (e) { this._handleException(arguments.callee, e); } return ""; } /** * See if a given regular expression is found in a given HTML node. * @tparam initialRegex the regular expression * @tparam bnNode the BN node * @tparam htmlNode the HTML node * @tparam tag usually null, otherwise, this means the initialRegex will likely match * multiple fields, so pick the last one. * @private */ HTMLAnalyzer.prototype._processRegexInURL = function (initialRegex, bnNode, htmlNode, tag, actionNodes) { try { this._stackTrace(arguments.callee, " start"); var matchFound = false; var htmlId = parseInt(htmlNode.getAttribute("htmlId")); // stderr.log(initialRegex + " BN=" + bnNode.getAttribute("nodeId") + " HTML=" + htmlNode.getAttribute("htmlId") + " tag=" + tag + " actionNodes.length=" + (actionNodes ? actionNodes.length : 0)); if (htmlId >= 0) { var matches = this._getRegexMatches(this.m_htmlHelper, initialRegex, htmlId, false); if (matches[0]) { for (var k = 0; k < matches.length; k++) { var match = matches[k]; if (tag) { var lastAnchorIndex = match.lastIndexOf("<" + tag); if (lastAnchorIndex != -1) { match = match.substr(lastAnchorIndex); } } // stderr.log("htmlId " + htmlId + " match " + match); var descriptor = DescriptorGenerator.generateDescriptor(match, null, actionNodes); if (descriptor) { // stderr.log("descriptor found"); var descEncoding = descriptor.getEncoding(); var descPattern = descriptor.getPattern(); // stderr.log("pattern " + descPattern); // stderr.log("raw val " + descriptor.getMarkedValue(false)); // stderr.log("encod val " + descriptor.getMarkedValue(true)); if (descEncoding == DescriptorGenerator.ENCODING_MODES.URL_ENCODE) { matchFound = this._checkRegex(bnNode, htmlNode, descriptor); } else if (descEncoding == DescriptorGenerator.ENCODING_MODES.RAW_TEXT) { matchFound = this._checkRegex(bnNode, htmlNode, descriptor); } else if (descEncoding == DescriptorGenerator.ENCODING_MODES.CHAR_REF_DECODE) { matchFound = this._checkRegex(bnNode, htmlNode, descriptor); } } else { // debug.log("descriptor not found"); } } } } this._stackTrace(arguments.callee, " end"); // stderr.log("what is match " + matchFound); return matchFound; } catch (e) { this._handleException(arguments.callee, e); } return false; } /** * Double checks the regular expression, and rewrite * the bnNode to use the regular expression mode. * @private */ HTMLAnalyzer.prototype._checkRegex= function(bnNode, htmlNode, descriptor) { try { this._stackTrace(arguments.callee, " start"); var url = this.getURL(bnNode); var descURL = descriptor.getTargetURL(); // debug.log("descriptor URL " + descURL); // debug.log("BN URL " + url); if (descURL && Util.compareURLByComponentsTrailingAuthority(descURL, url)) { debug.log("BN and descriptor match"); this.applyDescriptor(this.m_htmlHelper, bnNode, htmlNode, descriptor); this._stackTrace(arguments.callee, " end"); return true; } else { // try url encode the descriptor value if (descriptor.getEncoding() == DescriptorGenerator.ENCODING_MODES.CHAR_REF_DECODE) { var rawVal = descriptor.getMarkedValue(false); // debug.log("rawVal" + rawVal); // debug.log(rawVal); var encoded = DescriptorGenerator.applyEncodingMode(rawVal, DescriptorGenerator.ENCODING_MODES.CHAR_REF_URL_ENCODE); // debug.log(encoded ); var encodedPos = url.indexOf(encoded); if (encodedPos != -1) { descriptor.m_encoding = DescriptorGenerator.ENCODING_MODES.CHAR_REF_URL_ENCODE; this.applyDescriptor(this.m_htmlHelper, bnNode, htmlNode, descriptor); return true; } } } this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } return false; } /** * Handles exception * @private */ HTMLAnalyzer.prototype._handleException = function (func, e) { handleException(HTMLAnalyzer, func, e); } /** * stack tracing * @private */ HTMLAnalyzer.prototype._stackTrace = function (func, msg) { handleStackTrace(HTMLAnalyzer, func, msg); } /** * Get the DC node from a form (before) * @private */ HTMLAnalyzer.prototype._getDCNodeFromFormBefore = function(form) { try { this._stackTrace(arguments.callee, " start"); if (form) { var formParentId = parseInt(form.getAttribute("formParentId")); var htmlNode = this.m_mediator.getNodeWithId(formParentId); if (htmlNode) { var dcId = parseInt(htmlNode.getAttribute("DCId")); return this.m_mediator.getNodeWithId(dcId); } } this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } return null; } /** * Converts charset to the code page value. * @private */ HTMLAnalyzer.prototype._getCodePage = function(charset) { try { return this.m_htmlHelper.readCodePage(charset); } catch (e) { this._handleException(arguments.callee, e); } return Constants.CP_UTF8; } HTMLAnalyzer.prototype._getMatchingHTML = function(htmlNodes, pattern) { var allMatches = []; try { this._stackTrace(arguments.callee, " start"); for (var i in htmlNodes) { var htmlId = parseInt(htmlNodes[i].getAttribute("htmlId")); var matches = this._getRegexMatches(this.m_htmlHelper, pattern, htmlId, false); allMatches.push(matches); } this._stackTrace(arguments.callee, " end"); } catch (e) { this._handleException(arguments.callee, e); } return allMatches; } HTMLAnalyzer.prototype.Unicode2URLEncode = function(str) { return Util.Unicode2URLEncode(str); } HTMLAnalyzer.prototype.URLEncode2Unicode = function(str) { return Util.URLEncode2Unicode(str); } HTMLAnalyzer.prototype._getWindowIndexForHTML = function (htmlNode) { try { var windowIndex = htmlNode.getAttribute("windowIndex"); if (windowIndex == null) { var dcId = htmlNode.getAttribute("DCId"); if (dcId) { var dcNode = this.m_mediator.getNodeWithId(dcId); if (dcNode) { windowIndex = this.getProperty(dcNode, "windowIndex"); htmlNode.setAttribute("windowIndex", windowIndex ? windowIndex : 0); } } } return windowIndex; } catch (e) { this._handleException(arguments.callee, e); } } HTMLAnalyzer.prototype.addSessionParameters = function(node) { try { var found_ti = false; var found_transactionid = false; var searchSessionParams = function(bnNode) { if (found_ti && found_transactionid) { return; } var query_param = this.getProperty(bnNode, "query_param"); if (query_param ) { if (!found_ti) { if (query_param.indexOf("&_ti=") != -1 && query_param.indexOf("&oas=") == -1) { this.addTransactionProperty(node, "session_parameters", "_ti", ","); found_ti = true; } } if (!found_transactionid) { if (query_param.indexOf("&transactionid=") != -1 && query_param.indexOf("&oas=") == -1) { this.addTransactionProperty(node, "session_parameters", "transactionid", ","); found_transactionid = true; } } } } this.foreachNode(node.selectNodes("BN"), searchSessionParams); } catch (e) { this._handleException(arguments.callee, e); } } /** * @class DynamicSinkFactory. */ function DynamicSinkFactory() { } DynamicSinkFactory.createSink = function (callbacks) { try { // stackTrace.log(DynamicSinkFactory._getFunctionName(arguments.callee)); var sink = new ActiveXObject('OraEventsSink.OraDynamicSink'); var func = null; func = callbacks.BeforeNavigate2; sink.BeforeNavigate2 = func ? func : new function() {}; func = callbacks.NavigateComplete2; sink.NavigateComplete2 = func ? func : new function() {}; func = callbacks.DocumentComplete; sink.DocumentComplete = func ? func : new function() {}; func = callbacks.NewWindow2; sink.NewWindow2 = func ? func : new function() {}; func = callbacks.OnQuit; sink.OnQuit = func ? func : new function() {}; func = callbacks.OnGotoURL; sink.OnGotoURL = func ? func : new function() {}; func = callbacks.NavigateError; sink.NavigateError = func ? func : new function() {}; // stackTrace.log(DynamicSinkFactory._getFunctionName(arguments.callee) + " returns " + sink); return sink; } catch (e) { DynamicSinkFactory._handleException(arguments.callee, e); } return null; } DynamicSinkFactory._handleException = function (func, e) { if (stderr && stderr.log) { stderr.log(DynamicSinkFactory._getFunctionName(func) + " error: " + e.message); } } DynamicSinkFactory._getFunctionName = function (func) { return getFunctionName(DynamicSinkFactory, func); } /////////////////////////////////////////////////////////////////////////////// // ARGUMENT EXTRACTOR /////////////////////////////////////////////////////////////////////////////// /** * @class ArgumentExtractor * A collection of static functions for extracting arguments from function * calls. * * @ctor ArgumentExtractor * Creates an argument extractor. * * @treturn ArgumentExtractor an argument extractor * @private */ function ArgumentExtractor() { } /** *
 * Mapping: funcName -> [parserFunc, parserFuncArgs...] where 
 *     funcName      : name of a function,
 *     paserFunc     : function for parsing information from a call to function
 *                     'funcName'
 *     paserFuncArgs : zero or more arguments taken by 'parserFunc' (in addtion
 *                     to string containing call to function 'funcName')
 * 
* * @private */ ArgumentExtractor._EXTRACTOR_INFO = { "ignoreWarnAboutChanges" : ["_extractSingleArg", 0], "openWindow" : ["_extractSingleArg", 1], "window.location.replace" : ["_extractSingleArg", 0], "_updateCal" : ["_extractSingleArg", 1], "submitForm" : ["_extractFromSubmitForm", 2], "_LovInputVTF" : ["_extractSingleArgFromHash", 5, "D"] }; /** * Returns a copy of argument extractor information for the function with the * specified name. * @see ArgumentExtractor#_EXTRACTOR_INFO * * @tparam String funcName name of function * * @treturn Array relevant argument extractor information * @private */ ArgumentExtractor._getExtractorInfo = function(funcName) { if (funcName) { var extractorInfo = ArgumentExtractor._EXTRACTOR_INFO[funcName]; return extractorInfo ? extractorInfo.slice(0) : extractorInfo; } return null; } /** * Extracts the relevant argument(s) from the arguments list of a function * call. The argument(s) extracted are determined by the particular function. * * Note that this function simply returns the string representation of any * argument. * * @tparam String funcName name of function called * @tparam String funcArgsStr args from function call * @tparam String info additonal information for finding the relevant * argument(s) [optional] * * @treturn String string representation of relevant argument(s) * @public */ ArgumentExtractor.extract = function(funcName, funcArgsStr, info) { if (funcName && funcArgsStr) { var extractorInfo = ArgumentExtractor._getExtractorInfo(funcName); if (extractorInfo) { // use default extraction information (in addition to any user // provided information) var extractor = ArgumentExtractor[extractorInfo.shift()]; if (info) { extractorInfo = extractorInfo.concat(info); } return extractor(funcName, funcArgsStr, extractorInfo); } else { // rely on user provided information only return ArgumentExtractor._extractArgAuto(funcName, funcArgsStr, info); } } return null; } /** * Extracts the relevant argument(s) from the arguments list of a function * call. The argument(s) to be extracted are determined by the provided hint. * * Note that this function simply returns the string representation of any * argument. * @see DescriptorGenerator.HINT_TYPES * * @tparam String funcName name of function called * @tparam String funcArgsStr args from function call * @tparam Object hint description of argument(s) to be extracted * * @treturn String string representation of relevant argument(s) * @public */ ArgumentExtractor.extractByHint = function(funcName, funcArgsStr, hint) { if (funcArgsStr && hint) { if (hint.HINT_TYPE == DescriptorGenerator.HINT_TYPES.PARAM_STR) { return ArgumentExtractor._extractArgContaining(funcName, funcArgsStr, hint.PARAMS); } else if (hint.HINT_TYPE == DescriptorGenerator.HINT_TYPES.PARAM_NVPAIR) { return ArgumentExtractor._extractHashEntry(funcName, funcArgsStr, hint.PARAM_NAME, hint.PARAM_VAL); } Util.debug("Found unknown/unsupported hint type: " + hint.HINT_TYPE, ArgumentExtractor); } return null; } /** * Extracts the relevant argument(s) from the arguments list of a function * call. The argument(s) extracted are determined by a specified list of * index and/or (hash) key locators. * * Note that this function simply returns the string representation of any * argument. * * @tparam String funcName name of function called * @tparam String funcArgsStr args from function call * @tparam String info information for finding the relevant argument(s) * * @treturn String string representation of relevant argument(s) * @private */ ArgumentExtractor._extractArgAuto = function(funcName, funcArgsStr, info) { if (funcArgsStr && (info && info.length >= 1)) { // stuff args into an array as initial locator will always be an index var remaining = "[" + Util.trim(funcArgsStr) + "]"; for (var loc, locType, i = 0; i < info.length; i++) { loc = info[i]; locType = typeof(loc); if (locType == "number") { // handle index locator var arr = Parser.parseArray(remaining); if (!arr) { return null; } remaining = arr[loc]; } else if (locType == "string") { // handle key locator var hash = Parser.parseHash(remaining); if (!hash) { return null; } remaining = hash[loc]; } else { // handle unknown locator type Util.debug("Found unknown type of argument locator: " + loc, ArgumentExtractor); return null; } } return remaining; } return null; } /** * Extracts an argument containing the specified value. * * Note that this function simply returns the string representation of the * specified argument. * * @tparam String funcName name of function called * @tparam String funcArgsStr args from function call * @tparam String str substring (partial contents) of the argument to extract * * @treturn String string representation of specified argument * @private */ ArgumentExtractor._extractArgContaining = function(funcName, funcArgsStr, str) { if (funcArgsStr && str) { var regexMarkedArg = new RegExp("(?:^|[\\s:,\\[])([^\\s,{}\\[\\]]*?" + str + "[^\\s,{}\\[\\]]*?)(?:[\\s,}\\]]|$)"); var result = regexMarkedArg.exec(funcArgsStr); return result ? result[1] : null; } return null; } /** * Extracts an entry from a hash that is an argument to a function call * (e.g. "arg1,{arg2nm1:arg2val1,arg2nm2:arg2val2},arg3"). * * Note that this function simply returns the string representation of the * specified argument. * * @tparam String funcName name of function called * @tparam String funcArgsStr args from function call * @tparam String name hash entry name * @tparam String val hash entry value * * @treturn String string representation of specified entry from hash argument * @private */ ArgumentExtractor._extractHashEntry = function(funcName, funcArgsStr, name, val) { if (funcArgsStr && name && val) { var escapedName = Util.jsRegexEscape(name); var escapedVal = Util.jsRegexEscape(val); var regexMarkedHashEntry = new RegExp("(?:^|[\\s,{])([^\\s,{}\\[\\]]*?" + escapedName + "[^,{}\\[\\]]*?" + escapedVal + "[^\\s,{}\\[\\]]*?)(?:[,\\s}]|$)"); var result = regexMarkedHashEntry.exec(funcArgsStr); return result ? result[1] : null; } return null; } /** * Extracts a single argument from the arguments list of a function call. * * Note that this function simply returns the string representation of the * specified argument. * * @tparam String funcName name of function called * @tparam String funcArgsStr args from function call * @tparam Array varargs [argIndex] where 'argIndex' is the index of the * argument to be extracted * * @treturn String string representation of specified argument * @private */ ArgumentExtractor._extractSingleArg = function(funcName, funcArgsStr, varargs) { if (funcArgsStr && (varargs && varargs.length >= 1)) { var argIndex = varargs[0]; var args = Util.splitElements(funcArgsStr); if (args && (0 <= argIndex && argIndex < args.length)) { return args[argIndex]; } } return null; } /** * Extracts a value from a hash that is an argument to a function call. * * Note that this function simply returns the string representation of the * specified argument. * * @tparam String funcName name of function called * @tparam String funcArgsStr args from function call * @tparam Array varargs [argIndex, argHashKey] where 'argIndex' is the index * of the hash argument and 'argHashKey' is the key of the value to be * extracted from the hash * * @treturn String string representation of specified value from hash argument * @private */ ArgumentExtractor._extractSingleArgFromHash = function(funcName, funcArgsStr, varargs) { if (funcArgsStr && (varargs && varargs.length >= 2)) { var argIndex = varargs[0]; var argHashKey = varargs[1]; var hashStr = ArgumentExtractor._extractSingleArg(funcName, funcArgsStr, [argIndex]); if (hashStr) { var hash = Parser.parseHash(hashStr); if (hash) { return hash[argHashKey]; } } } return null; } /** * Extracts a value from a call to the function 'submitForm'. * * Note that this function simply returns the string representation of the * specified argument. * * @tparam String funcName name of function called * @tparam String funcArgsStr args from function call * @tparam Array varargs [argIndex, argHashKey] where 'argIndex' is the index * of the hash argument and 'argHashKey' is the key of the value to be * extracted from the hash * * @treturn String string representation of specified value from hash argument * @private */ ArgumentExtractor._extractFromSubmitForm = function(funcName, funcArgsStr, varargs) { if (funcArgsStr && (varargs && varargs.length >= 2)) { return ArgumentExtractor._extractSingleArgFromHash(funcName, funcArgsStr, varargs); } return null; } /////////////////////////////////////////////////////////////////////////////// // DESCRIPTOR GENERATOR /////////////////////////////////////////////////////////////////////////////// /** * @class DescriptorGenerator * A HTML descriptor generator. * * @ctor DescriptorGenerator * Creates a HTML descriptor generator. * @private */ function DescriptorGenerator() { } /** * A mapping of tag types to functions that generate an HTML descriptor for an * HTML fragment where the enclosing tag is of that type. (Note that the empty * string is used to represent text existing outside of a tag.) * @private */ DescriptorGenerator._DESCRIPTOR_GENERATORS = { "a" : "_generateAnchorDescriptor", "form" : "_generateFormDescriptor", "img" : "_generateImageDescriptor", "" : "_generateTextDescriptor", "button" : "_generateButtonDescriptor", "body" : "_generateBodyDescriptor", "select" : "_generateSelectDescriptor", "option" : "_generateOptionDescriptor", "input" : "_generateInputDescriptor" }; /** * A mapping of tag types to arrays of tag attributes that are useful for * deriving a parameter name. (Note that the empty string is used to represent * text existing outside of a tag.) * @private */ DescriptorGenerator._NAME_ATTRIBUTES = { "a" : ["title"], "img" : ["title", "alt"], "" : [], "form" : ["name"], "button" : ["name", "value", "title"], "body" : ["title"], "select" : ["name", "title"], "option" : ["label", "value"], "input" : ["name", "title", "alt"] }; /** * A mapping of HTML tag attributes to functions that generate a regular * expression pattern for matching a particular attribute instance. * @private */ DescriptorGenerator._ATTR_HANDLERS = { "href" : { "a" : "_handleAttrHref" }, "action" : { "form" : "_handleAttrAction" }, "value" : { "option" : "_handleAttrValue", "input" : "_handleAttrValue" } }; /** * Collection of different types/encodings of quotation marks. * @private */ DescriptorGenerator._QUOTES = ["'", "\"", """]; /** * Encoding modes for HTML fragments. * @private */ DescriptorGenerator.ENCODING_MODES = { URL_ENCODE : "urlEncode", RAW_TEXT : "rawText", CHAR_REF_DECODE : "charRefDecode", CHAR_REF_URL_ENCODE : "charRefUrlEncode"}; /** * Hint types. * * An example of the expected structure for a hint of each hint type is * provided below: * * PARAM_STR -> { HINT_TYPE: "paramString", PARAMS: "nm1=val1&nm2=val2&nm3=val3" } * PARAM_NVPAIR -> { HINT_TYPE: "paramNVPair", PARAM_NAME: "nm1", PARAM_VAL: "val1", WTI: false } * @public */ DescriptorGenerator.HINT_TYPES = { PARAM_STR : "paramString", PARAM_NVPAIR : "paramNVPair" }; /** * Description of JavaScript code. * @private */ DescriptorGenerator._JS_DESC = { FUNC_CALL : "funcCall", LOC_VAR_ASSIGN : "locVarAssign" }; /** * Returns an associative array of tag types to descriptor generators for the * specified tag types. * * If no tag types are specified (i.e. this method is called with no * argument), then the returned associative array will contain mappings for * all supported tag types. * * @tparam Array tagTypes tag types to be included in mapping * * @treturn Object associative array mapping tag types to descriptor generators * for specified tag types * @private */ DescriptorGenerator._getGenerators = function(tagTypes) { if (tagTypes) { return Util.clone(Util.slice(DescriptorGenerator._DESCRIPTOR_GENERATORS, tagTypes), true); } return Util.clone(DescriptorGenerator._DESCRIPTOR_GENERATORS, true); } /** * Returns an associative array of tag types to attributes that are relevant * for generating a parameter name. * * If no tag types are specified (i.e. this method is called with no * argument), then the returned associative array will contain mappings for * all supported tag types. * * @tparam Array tagTypes tag types to be included in mapping * * @treturn Object associative array mapping tag types to attributes that are * useful in generating a parameter name * @private */ DescriptorGenerator._getNameAttributes = function(tagTypes) { if (tagTypes) { return Util.clone(Util.slice(DescriptorGenerator._NAME_ATTRIBUTES, tagTypes), true); } Util.clone(DescriptorGenerator._NAME_ATTRIBUTES, true); } /** * Returns the handler (i.e. regular expression pattern generator) for the * specified HTML tag attribute (e.g href, alt). * @see DescriptorGenerator#_ATTR_HANDLERS * * @tparam String type HTML tag type (e.g. a, form) * @tparam String attr HTML tag attribute name (e.g. href, action) * * @treturn function handler function HTML tag attribute * @private */ DescriptorGenerator._getAttributeHandler = function(type, attr) { if (type && attr) { var typeToHandler = DescriptorGenerator._ATTR_HANDLERS[attr.toLowerCase()]; if (typeToHandler) { var handler = typeToHandler[type.toLowerCase()]; if (handler) { return DescriptorGenerator[handler]; } } } return null; } /** * Generates a descriptor for an HTML fragment. * * @tparam String html HTML fragment * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML fragment [optional] * * @treturn HTMLDescriptor descriptor for HTML fragment * @public */ DescriptorGenerator.generateDescriptor = function(html, hint, actions) { var generators = DescriptorGenerator._getGenerators(); return DescriptorGenerator._generateDescriptor(html, generators, hint, actions); } /** * Generates a descriptor for an HTML fragment. * * @tparam String html HTML fragment * @tparam Object generators associative array mapping tag types to descriptor * generators * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML fragment [optional] * * @treturn HTMLDescriptor descriptor for HTML fragment * @private */ DescriptorGenerator._generateDescriptor = function(html, generators, hint, actions) { if (html && generators) { // lookup generator function for tag type var type = DescriptorGenerator.getTagType(html).toLowerCase(); var generator = generators[type]; if (generator) { return DescriptorGenerator[generator](html, hint, actions); } } return null; } /** * Generates a descriptor for an HTML fragment with enclosing tags * (e.g. a, button) that do not require special processing of inner content. * For example, a "select" HTML fragment requires special handling of the * "option" tags of its inner content. * * @tparam String html HTML fragment * @tparam String type type of enclosing tags (e.g. a, button) * @tparam Array defaultAttrs default set of attributes included in pattern if * no action is specified * @tparam Array contentTagTypes types of important inner content tags (e.g. img) * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML fragment [optional] * * @treturn HTMLDescriptor descriptor for HTML fragment * @private */ DescriptorGenerator._generateBasicEnclosingTagsDescriptor = function(html, type, defaultAttrs, contentTagTypes, hint, actions) { if (html && type && DescriptorGenerator.isOpeningTagOfType(html, type) && defaultAttrs && contentTagTypes) { // break HTML fragment into parts (i.e. link open tag, contents, link close tag) var tokens = DescriptorGenerator._splitHTML(html); var linkOpen = tokens.shift(); var token, contents = []; while ((token = tokens.shift()) && !DescriptorGenerator.isClosingTagOfType(token, type)) { contents.push(token); } var linkClose = token ? token : ""; // get pattern for link open tag var linkOpenDesc = DescriptorGenerator._handleTag(linkOpen, defaultAttrs, hint, actions); var linkOpenPattern = linkOpenDesc.PATTERN; // get url and parameter information if available var targetURL, params = ""; if (linkOpenDesc.INFO) { targetURL = linkOpenDesc.INFO.URL; params = linkOpenDesc.INFO.PARAMS; } // get pattern for link contents var contentsPatternDesc = DescriptorGenerator._getContentsPattern(contents, contentTagTypes, hint, actions); var contentsPattern = contentsPatternDesc.PATTERN; // get pattern for link closing tag var linkClosePattern = Util.regexEscape(linkClose); // combine link tag and contents patterns to produce overall pattern var pattern = [linkOpenPattern, contentsPattern, linkClosePattern].join(""); var encoding = DescriptorGenerator._getEncodingMode(params); var name = DescriptorGenerator._formatParamName(DescriptorGenerator._getParamNameFromContents(contents, contentTagTypes)); return new HTMLDescriptor(type, name, pattern, encoding, params, targetURL); } return null; } /** * Generates a descriptor for an HTML fragment where anchor tags are the outer * most (enclosing) tags. * * @tparam String html HTML fragment * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML fragment [optional] * * @treturn HTMLDescriptor descriptor for anchor HTML fragment * @private */ DescriptorGenerator._generateAnchorDescriptor = function(html, hint, actions) { // default set of attributes included in pattern if no action is specifed var DEFAULT_ATTRS = ["onclick", "href", "title", "name", "id"]; // relevent tag types for anchor contents var CONTENTS_TAG_TYPES = ["img", ""]; return DescriptorGenerator._generateBasicEnclosingTagsDescriptor(html, "a", DEFAULT_ATTRS, CONTENTS_TAG_TYPES, hint, actions); } /** * Generates a descriptor for an HTML fragment where button tags are the outer * most (enclosing) tags. * * @tparam String html HTML fragment * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML fragment [optional] * * @treturn HTMLDescriptor descriptor for a button HTML fragment * @private */ DescriptorGenerator._generateButtonDescriptor = function(html, hint, actions) { // default set of attributes included in pattern if no action is specifed var DEFAULT_ATTRS = ["onclick", "title", "name", "id"]; // relevant tag types for button contents var CONTENTS_TAG_TYPES = ["img", ""]; return DescriptorGenerator._generateBasicEnclosingTagsDescriptor(html, "button", DEFAULT_ATTRS, CONTENTS_TAG_TYPES, hint, actions); } /** * Generates a descriptor for an HTMl fragment where body tags are the outer * most (enclosing) tags. * * @tparam String html HTML fragment * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML fragment [optional] * * @treturn HTMLDescriptor descriptor for a body HTML fragment * @private */ DescriptorGenerator._generateBodyDescriptor = function(html, hint, actions) { var DEFAULT_ATTRS = ["onload", "title"]; var CONTENTS_TAG_TYPES = []; return DescriptorGenerator._generateBasicEnclosingTagsDescriptor(html, "body", DEFAULT_ATTRS, CONTENTS_TAG_TYPES, hint, actions); } /** * Generates a descriptor for an image tag. * * @tparam String html image tag HTML fragment * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML fragment [optional] * * @tparam HTMLDescriptor descriptor for image tag * @private */ DescriptorGenerator._generateImageDescriptor = function(html, hint, actions) { // filters for selecting action related to image HTML tag var ACTION_FILTERS = { "type" : "click", "tagName" : "img" }; // all possible image tag attributes referenced by action var ACTION_ATTRS = ["onclick", "title", "alt", "name", "src", "id"]; // default set of relevant attributes to use if no action is specified var DEFAULT_ATTRS = ["onclick", "title", "alt", "name", "id"]; if (html && DescriptorGenerator.isTagOfType(html, "img")) { var img = html; // find image attribute specified by relevant action, else look for one // of the default image attributes var attrs = []; if (actions) { var found = Util.findActionAttributes(actions, ACTION_FILTERS, ACTION_ATTRS); for (var attr in found) { attrs.push(attr); } } if (attrs.length == 0) { attrs = DEFAULT_ATTRS; } var imgDesc = DescriptorGenerator._handleTag(img, attrs, hint, actions); var imgPattern = imgDesc.PATTERN; // get url and parameter information if available (i.e. onclick // attribute exists) var targetURL, params = ""; if (imgDesc.INFO) { targetURL = imgDesc.INFO.URL; params = imgDesc.INFO.PARAMS; } var encoding = DescriptorGenerator._getEncodingMode(params); var name = DescriptorGenerator._formatParamName(DescriptorGenerator._getParamNameFromContents([img], ["img"])); return new HTMLDescriptor("img", name, imgPattern, encoding, params, targetURL); } return null; } /** * Generates a descriptor for an HTML fragment where form tags are the outer * most (enclosing) tags. * * @tparam String html HTML fragment * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML fragment [optional] * * @treturn HTMLDescriptor descriptor for form HTML fragment * @private */ DescriptorGenerator._generateFormDescriptor = function(html, hint, actions) { var DEFAULT_ATTRS = ["action", "name", "id"]; // function assumes that only the form opening tag (i.e. no form contents // or form closing tag) is contained in the HTML fragment if (html && DescriptorGenerator.isOpeningTagOfType(html, "form")) { var formOpen = html; var formOpenDesc = DescriptorGenerator._handleTag(formOpen, DEFAULT_ATTRS, hint, actions); var formOpenPattern = formOpenDesc.PATTERN; // get url and parameter information if available var targetURL, params = ""; if (formOpenDesc.INFO) { targetURL = formOpenDesc.INFO.URL; params = formOpenDesc.INFO.PARAMS; } var encoding = DescriptorGenerator._getEncodingMode(params); var name = DescriptorGenerator._formatParamName(DescriptorGenerator._getParamNameFromContents([formOpen], ["form"])); return new HTMLDescriptor("form", name, formOpenPattern, encoding, params, targetURL); } return null; } /** * Generates a descriptor for text. * * @tparam String html text * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML fragment [optional] * * @tparam HTMLDescriptor descriptor for text * @private */ DescriptorGenerator._generateTextDescriptor = function(html, hint, actions) { if (html && (DescriptorGenerator.getTagType(html) == "")) { // if html consists only of whitespace, use "match whitespace" pattern; // otherwise use original html with regular expression special chars // escaped var textPattern = (Util.trim(html).length > 0) ? Util.regexEscape(html) : "\\s*"; var encoding = DescriptorGenerator._getEncodingMode(html); var name = DescriptorGenerator._formatParamName(html); return new HTMLDescriptor("", name, textPattern, encoding); } return null; } /** * Generates a descriptor for an HTML fragment where select tags are the outer * most (enclosing) tags. * * @tparam String html HTML fragment * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML fragment [optional] * * @treturn HTMLDescriptor descriptor for select HTML fragment * @private */ DescriptorGenerator._generateSelectDescriptor = function(html, hint, actions) { // default set of attributes included in pattern if no action is specifed var DEFAULT_ATTRS = ["name"]; // relevent tag types for select contents var CONTENTS_TAG_TYPES = ["option"]; if (html && DescriptorGenerator.isOpeningTagOfType(html, "select")) { // break HTML fragment into parts (i.e. select open, option open/close // tags (and inner contents), select close tags) var tokens = DescriptorGenerator._splitHTML(html); var selectOpen = tokens.shift(); // ASSUMPTION option tags are well formatted (i.e. format is option // open tag, inner text, option close tag) var optionToken = "", inOption = false; var token, contents = []; while ((token = tokens.shift()) && !DescriptorGenerator.isClosingTagOfType(token, "select")) { // FIXME add support for optgroup tags if (inOption || DescriptorGenerator.isOpeningTagOfType(token, "option")) { inOption = true; optionToken += token; if (DescriptorGenerator.isClosingTagOfType(token, "option")) { inOption = false; contents.push(optionToken); optionToken = ""; } } else { contents.push(token); } } var selectClose = token ? token : ""; // get pattern for select open tag var selectOpenDesc = DescriptorGenerator._handleTag(selectOpen, DEFAULT_ATTRS, hint, actions); var selectOpenPattern = selectOpenDesc.PATTERN; // get pattern for select contents (and replace redundant match-all // tokens) var contentsPatternDesc = DescriptorGenerator._getContentsPattern(contents, CONTENTS_TAG_TYPES, hint, actions); var contentsPattern = contentsPatternDesc.PATTERN.replace(/(?:\.\*)+/g, ".*"); // get pattern for select closing tag var selectClosePattern = Util.regexEscape(selectClose); // combine select tag and contents patterns to produce overall pattern var pattern = [selectOpenPattern, contentsPattern, selectClosePattern].join(""); var params = contentsPatternDesc.INFO ? contentsPatternDesc.INFO.PARAMS : ""; var encoding = DescriptorGenerator._getEncodingMode(params); var name = DescriptorGenerator._formatParamName(DescriptorGenerator._getParamNameFromContents([selectOpen], ["select"])); return new HTMLDescriptor("select", name, pattern, encoding, params); } return null; } /** * Generates a descriptor for an HTML fragment where option tags are the outer * most (enclosing) tags. * * @tparam String html HTML fragment * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML fragment [optional] * * @treturn HTMLDescriptor descriptor for option HTML fragment * @private */ DescriptorGenerator._generateOptionDescriptor = function(html, hint, actions) { // default set of attributes included in pattern if no action is specifed var DEFAULT_ATTRS = ["label", "value"]; // relevent tag types for option contents var CONTENTS_TAG_TYPES = [""]; if (hint && (hint.PARAM_VAL != DescriptorGenerator.getAttribute(html, "value"))) { // Return generic pattern (i.e. match all) for insignificant option tags // (as determined by the parameter value hint) return new HTMLDescriptor("option", "", ".*", DescriptorGenerator.ENCODING_MODES.RAW_TEXT); } else { return DescriptorGenerator._generateBasicEnclosingTagsDescriptor(html, "option", DEFAULT_ATTRS, CONTENTS_TAG_TYPES, hint, actions); } } // TODO add function comments DescriptorGenerator._generateInputDescriptor = function(html, hint, actions) { // defaulte set of attributes included in pattern if no action is specified var DEFAULT_ATTRS = ["type", "name", "value", "title", "alt"]; // relevant tag types for input contents var CONTENTS_TAG_TYPES = []; //Util.debug(html, DescriptorGenerator); //Util.debug("hint: " + (hint ? hint.PARAM_NAME + ":" + hint.PARAM_VAL: ""), DescriptorGenerator); return DescriptorGenerator._generateBasicEnclosingTagsDescriptor(html, "input", DEFAULT_ATTRS, CONTENTS_TAG_TYPES, hint, actions); } /** * Returns (1) a regular expression pattern that matches the specified URL and * marks its query string; and (2) the query string of the specified URL and * the specified URL. * * For example, given the URL * * "OA.jsp?_ti=1947836210&oapc=3" * * the function would produce the following: * * { PATTERN : "\S*OA\.jsp\?(\S*)", * INFO : { PARAMS : "_ti=1947836210&oapc=3", * URL : "OA.jsp?_ti=1947836210&oapc=3" } } * * @tparam String url a URL * @tparam Object hint associative array containing a "hint" as to which * parameters in the URL should be marked [optional] * * @treturn Object associative array containing (1) a regular expression * pattern that matches 'url' and marks its query string, and (2) the matched * query string and the value of 'url' * @private */ DescriptorGenerator._handleURL = function(url, hint) { // regex matches a path that ends in a query character ("?", "$") var REGEX_ENDS_WITH_QUERY_CHAR = /[$?]$/; // pattern for regex that matches and marks entire query string var PATTERN_MARKED_ALL_QUERY = "(\\S*)"; // check whether url contains a query string var urlParts = Util.splitURL(url); if (urlParts && REGEX_ENDS_WITH_QUERY_CHAR.test(urlParts.PATH)) { var queryMatch = PATTERN_MARKED_ALL_QUERY; if (hint) { // handle parameter hint based on type if (hint.HINT_TYPE == DescriptorGenerator.HINT_TYPES.PARAM_STR) { // check for hint parameters and set query string pattern to // mark all parameters queryMatch = (urlParts.QUERY == hint.PARAMS) ? PATTERN_MARKED_ALL_QUERY : null; } else if (hint.HINT_TYPE == DescriptorGenerator.HINT_TYPES.PARAM_NVPAIR) { // check for single hint parameter and set query string pattern // to mark parameter queryMatch = null; var pair = hint.PARAM_NAME + "=" + hint.PARAM_VAL; var params = urlParts.QUERY.split("&"); for (var i in params) { if (params[i] == pair) { var paramValPattern; if (hint.WTI) { // for WTI case, include parameter value (w/o // random trailing characters) in pattern paramValPattern = Util.regexEscape(Util.stripWTIChars(hint.PARAM_VAL)) + "[^&]*"; } else { paramValPattern = "[^&]*"; } queryMatch = ["\\S*", Util.regexEscape(hint.PARAM_NAME), "=(", paramValPattern, ")\\S*"].join(""); } } } else { Util.debug("Found unknown/unsupported hint type: " + hint.HINT_TYPE, DescriptorGenerator); queryMatch = null; } } // if hint provided, return pattern only if hint parameter(s) found if (queryMatch) { var pattern = ["\\S*", Util.regexEscape(urlParts.PATH), queryMatch].join(""); return { PATTERN: pattern, INFO: { PARAMS: urlParts.QUERY, URL: url } }; } } return null; } /** * Returns (1) a regular expression pattern that matches the specified * JavaScript code fragment; and, if they exist, (2) the relevant parameters * and the relevant target URL. The function assumes that the JavaScript code * matches one of the supported types (e.g. function call, location variable * assignment). The returned pattern will contain a * parenthesized subexpression that marks the relevant parameters, as specified * by the hint, or, by default, in the query string of the target URL. * * For example, given the JavaScript code (and no hint) * * "openWindow(self, 'frameRedirect.jsp?redirect=/OA_HTML/OA.jsp&retainAM=Y'); return false" * * the function would return the following: * * { PATTERN : * "[^>]*openWindow\([^>]*'\S*frameRedirect\.jsp\?(\S*)'[^>]*", * INFO : { PARAMS : * "redirect=/OA_HTML/OA.jsp&retainAM=Y", * URL : * "frameRedirect.jsp?redirect=/OA_HTML/OA.jsp&retainAM=Y" } } * * @see DescriptorGenerator#_JS_DESC * * @tparam String js JavaScript code fragment * @tparam Object hint associative array containing a "hint" as to which * parameters in the code fragment should be marked [optional] * * @treturn Object associative array containing (1) a regular expression * pattern that matches 'js' and marks relevant parameters, and (2) the marked * parameters and (if it exists) the target URL * @private */ DescriptorGenerator._handleJavaScript = function(js, hint) { // regex matches a function and marks its parts (name, argument list) var REGEX_MARKED_FUNCTION = /([_a-zA-Z\$][\w\$\.]*?)\((.*)\)/; // regex matches explicit location assignment (i.e. // document.location='http://www.oracle.com') var REGEX_MARKED_LOC_VAR_ASSIGN = /((?:\w+?\.)+location)=(.*)/; if (js) { // Extract function name and arguments from JavaScript code fragment... var jsType = DescriptorGenerator._JS_DESC.FUNC_CALL; var jsParts = REGEX_MARKED_FUNCTION.exec(js); if (!jsParts) { // else, extract variable name and values from JavaScript code // fragment jsType = DescriptorGenerator._JS_DESC.LOC_VAR_ASSIGN; jsParts = REGEX_MARKED_LOC_VAR_ASSIGN.exec(js); } if (jsParts) { if (jsType == DescriptorGenerator._JS_DESC.FUNC_CALL) { var funcName = jsParts[1]; var funcArgs = jsParts[2]; return DescriptorGenerator._handleJavaScriptFuncCall(funcName, funcArgs, hint); } else if (jsType == DescriptorGenerator._JS_DESC.LOC_VAR_ASSIGN) { var varName = jsParts[1]; var varValue = jsParts[2]; return DescriptorGenerator._handleJavaScriptVarAssign(varName, varValue, hint); } } } return null; } /** * Returns (1) a regular expression pattern that matches the specified * JavaScript code fragment; and, if they exist, (2) the relevant parameters * and the relevant target URL. The function assumes that the JavaScript code * consists of a function call. The returned pattern will contain a * parenthesized subexpression that marks the relevant parameters, as specified * by the hint, or, by default, in the query string of the target URL. * * For example, given the following function name and arguments (and no hint) * * "openWindow", "self, 'frameRedirect.jsp?redirect=/OA_HTML/OA.jsp&retainAM=Y'" * * the function would return the following: * * { PATTERN : * "[^>]*openWindow\([^>]*'\S*frameRedirect\.jsp\?(\S*)'[^>]*", * INFO : { PARAMS : * "redirect=/OA_HTML/OA.jsp&retainAM=Y", * URL : * "frameRedirect.jsp?redirect=/OA_HTML/OA.jsp&retainAM=Y" } } * * @tparam String funcName name of relevant function * @tparam String funcArgs arguments used in relevant function call * @tparam Object hint associative array containing a "hint" as to which * parameters in the code fragment should be marked [optional] * * @treturn Object associative array containing (1) a regular expression * pattern that matches function call and marks relevant parameters, and (2) * the marked parameters and (if it exists) the target URL * @private */ DescriptorGenerator._handleJavaScriptFuncCall = function(funcName, funcArgs, hint) { if (funcName) { funcArgs = funcArgs ? funcArgs : ""; if (hint) { // handle hint based on type if (hint.HINT_TYPE == DescriptorGenerator.HINT_TYPES.PARAM_STR) { var str = ArgumentExtractor.extractByHint(funcName, funcArgs, hint); if (str) { return DescriptorGenerator._handleURLFuncArg(funcName, str, hint); } } else if (hint.HINT_TYPE == DescriptorGenerator.HINT_TYPES.PARAM_NVPAIR) { var paramValPattern; if (hint.WTI) { // for WTI case, include parameter value (w/o random trailing // characters) in pattern paramValPattern = "(" + Util.regexEscape(Util.stripWTIChars(hint.PARAM_VAL)) + "\\S*)"; } else { paramValPattern = "(\\S*)"; } var pattern; var str = ArgumentExtractor.extractByHint(funcName, funcArgs, hint); if (str) { // handle case where parameter name-value pair forms an // entry in a hash argument of a function var nvPattern = Util.regexEscape(str).replace(Util.regexEscape(hint.PARAM_VAL), paramValPattern); pattern = ["[^>]*", Util.regexEscape(funcName), "\\([^>]*", nvPattern, "[^>]*"].join(""); } else { // handle case where parameter value is an argument of a // function var testParamValPattern = ["^(", DescriptorGenerator._QUOTES.join("|"), ")?", Util.jsRegexEscape(hint.PARAM_VAL), "\\1$"].join(""); var testParamValRegex = new RegExp(testParamValPattern); var argPatterns = []; var args = Util.splitElements(funcArgs); for (var i in args) { if (testParamValRegex.test(args[i])) { // add pattern for hinted argument... var argPattern = Util.regexEscape(args[i]).replace(Util.regexEscape(hint.PARAM_VAL), paramValPattern); argPatterns.push(argPattern); // ...and break as trailing arguments are // unnecessary break; } else { // add patterns for preceding (positional) arguments argPatterns.push("[^,]*"); } } pattern = ["[^>]*", Util.regexEscape(funcName), "\\(", argPatterns.join(","), "[^>]*"].join(""); } return { PATTERN: pattern , INFO: { PARAMS: hint.PARAM_VAL }}; } } else { // parse URL argument from 'funcName' function call and create // appropriate pattern var quotedURL = ArgumentExtractor.extract(funcName, funcArgs); return DescriptorGenerator._handleURLFuncArg(funcName, quotedURL, hint); } } return null; } /** * Returns (1) a regular expression pattern that matches the specified * JavaScript code fragment; and, if they exist, (2) the relevant parameters * and the relevant target URL. The function assumes that the JavaScript code * consists of an assignment to a location variable. The returned pattern will * contain a parenthesized subexpression that marks the relevant parameters, * as specified by the hint, or, by default, in the query string of the target * URL. * * For example, given the following variable name and value (and no hint) * * "document.location", "'OA.jsp?OAFunc=OIECREATEPAGE&_ti=1913686602&oapc=6&oas=BTuGqEMgJ3TIG1gcTd5e-w..'" * * the function would return the following: * * { PATTERN : * "[^>]*document\.location='\S*OA\.jsp\?(\S*)'[^>]*", * INFO : { PARAMS : * "OAFunc=OIECREATEPAGE&_ti=1913686602&oapc=6&oas=BTuGqEMgJ3TIG1gcTd5e-w..", * URL : * "OA.jsp?OAFunc=OIECREATEPAGE&_ti=1913686602&oapc=6&oas=BTuGqEMgJ3TIG1gcTd5e-w.." } } * * @tparam String varName name of relevant location variable (ex: * document.location) * @tparam String varVal value assigned to location variable * @tparam Object hint associative array containing a "hint" as to which * parameters in the code fragment should be marked [optional] * * @treturn Object associative array containing (1) a regular expression * pattern that matches function call and marks relevant parameters, and (2) * the marked parameters and (if it exists) the target URL * @private */ DescriptorGenerator._handleJavaScriptVarAssign = function(varName, varVal, hint) { if (varName) { varVal = varVal ? varVal : ""; if (hint) { // handle hint based on type var str = ArgumentExtractor.extractByHint(varName, varVal, hint); if (str) { if (hint.HINT_TYPE == DescriptorGenerator.HINT_TYPES.PARAM_STR) { return DescriptorGenerator._handleURLVarVal(varName, str, hint); } else if (hint.HINT_TYPE == DescriptorGenerator.HINT_TYPES.PARAM_NVPAIR) { // Hint type not supported for variable assignment Util.debug("Hint type not supported for variable assignment: " + hint.HINT_TYPE, DescriptorGenerator); /* var nvPattern = Util.regexEscape(str).replace(Util.regexEscape(hint.PARAM_VAL), "(\\S*)"); var pattern = ["[^>]*", Util.regexEscape(varName), "=\\S*", nvPattern, "[^>]*"].join(""); return { PATTERN: pattern , INFO: { PARAMS: hint.PARAM_VAL }}; */ } } } else { // parse URL argument from 'varName' variable assignment and create // appropriate pattern var quotedURL = ArgumentExtractor.extract(varName, varVal, [0]); return DescriptorGenerator._handleURLVarVal(varName, quotedURL, hint); } } } /** * Returns (1) a regular expression pattern that matches the specified * function call; and, if they exist, (2) the relevant parameters * and the relevant target URL. The function assumes that the JavaScript code * consists of a function call containing an argument that is the target URL. * The returned pattern will contain a parenthesized subexpression that marks * the relevant parameters of the target URL, as specified by the hint. * * For example, given the following arguments (and no hint) * * "openWindow", "frameRedirect.jsp?redirect=/OA_HTML/OA.jsp&retainAM=Y" * * the function would return the following: * * { PATTERN : * "[^>]*openWindow\([^>]*'\S*frameRedirect\.jsp\?(\S*)'[^>]*", * INFO : { PARAMS : * "redirect=/OA_HTML/OA.jsp&retainAM=Y", * URL : * "frameRedirect.jsp?redirect=/OA_HTML/OA.jsp&retainAM=Y" } } * * @tparam String funcName name of function containing target URL argument * @tparam String arg URL function argument * @tparam Object hint associative array containing a "hint" as to which * parameters in the code fragment should be marked [optional] * * @treturn Object associative array containing (1) a regular expression * pattern that matches the specifed function call and marks relevant parameters, * and (2) the marked parameters and the target URL * @private */ DescriptorGenerator._handleURLFuncArg = function(funcName, arg, hint) { // regex matches a leading/opening quotation mark var REGEX_MARKED_LEADING_QUOTE = new RegExp("^\\s*(" + DescriptorGenerator._QUOTES.join("|") + ")"); if (funcName && arg) { var url = Parser.parse(arg); var urlDesc = DescriptorGenerator._handleURL(url, hint); if (urlDesc) { // get quotation mark type surrounding url var quoteResult = REGEX_MARKED_LEADING_QUOTE.exec(arg); var quote = quoteResult ? quoteResult[1] : ""; var pattern = ["[^>]*", Util.regexEscape(funcName), "\\([^>]*", quote, urlDesc.PATTERN, quote, "[^>]*"].join(""); return { PATTERN: pattern, INFO: urlDesc.INFO }; } } return null; } /** * Returns (1) a regular expression pattern that matches the specified * variable assignment; and, if they exist, (2) the relevant parameters * and the relevant target URL. The function assumes that the JavaScript code * consists of an assignment of the target URL to a location variable. * The returned pattern will contain a parenthesized subexpression that marks * the relevant parameters of the target URL, as specified by the hint. * * For example, given the following variable name and value (and no hint) * * "document.location", "'OA.jsp?OAFunc=OIECREATEPAGE&_ti=1913686602&oapc=6&oas=BTuGqEMgJ3TIG1gcTd5e-w..'" * * the function would return the following: * * { PATTERN : * "[^>]*document\.location='\S*OA\.jsp\?(\S*)'[^>]*", * INFO : { PARAMS : * "OAFunc=OIECREATEPAGE&_ti=1913686602&oapc=6&oas=BTuGqEMgJ3TIG1gcTd5e-w..", * URL : * "OA.jsp?OAFunc=OIECREATEPAGE&_ti=1913686602&oapc=6&oas=BTuGqEMgJ3TIG1gcTd5e-w.." } } * * @tparam String varName name of relevant location variable (ex: * document.location) * @tparam String varVal value assigned to location variable * @tparam Object hint associative array containing a "hint" as to which * parameters in the code fragment should be marked [optional] * * @treturn Object associative array containing (1) a regular expression * pattern that matches the specifed variable assignment and marks relevant * parameters, and (2) the marked parameters and the target URL * @private */ DescriptorGenerator._handleURLVarVal = function(varName, varVal, hint) { // regex matches a leading/opening quotation mark var REGEX_MARKED_LEADING_QUOTE = new RegExp("^\\s*(" + DescriptorGenerator._QUOTES.join("|") + ")"); if (varName) { varVal = varVal ? varVal : ""; var url = Parser.parse(varVal); var urlDesc = DescriptorGenerator._handleURL(url, hint); if (urlDesc) { // get quotation mark type surrounding url var quoteResult = REGEX_MARKED_LEADING_QUOTE.exec(varVal); var quote = quoteResult ? quoteResult[1] : ""; var pattern = ["[^>]*", Util.regexEscape(varName), "=", quote, urlDesc.PATTERN, quote, "[^>]*"].join(""); return { PATTERN: pattern, INFO: urlDesc.INFO }; } } return null; } /** * Handles the generation of a regular expression pattern to match an instance * of a generic HTML tag attribute. The function returns (1) a regular * expression pattern that matches the attribute, (2) the attribute name * (as formatted in the HTML tag), and (3) the attribute value. * * For example, given the following arguments * * "", "title" * * the function would return * * { ATTR : "TITLE", * ATTR_VAL : "An Image", * PATTERN : "TITLE='An Image'" } * * @tparam String tag HTML tag containing attribute * @tparam String attr attribute name * * @treturn Object associative array containing a pattern, which matches the * attribute with the name 'attr', and the attribute name and value as * represented in 'tag' * @private */ DescriptorGenerator._handleAttrGeneric = function(tag, attr) { if (tag && attr) { // regex matches and marks tag attribute and its value var regexMarkedAttr = new RegExp(".*?(" + attr + ")=([\"'])(.*?)\\2.*", "i"); var result = regexMarkedAttr.exec(tag); if (result) { var attrFromTag = result[1]; var quote = result[2]; var attrVal = result[3]; var pattern = [Util.regexEscape(attrFromTag), "=", quote, Util.regexEscape(attrVal), quote].join(""); return { ATTR: attrFromTag, ATTR_VAL: attrVal, PATTERN: pattern }; } } return null; } /** * Handles the generation of a regular expression pattern to match an instance * of an instrinsic event HTML tag attribute (e.g. onClick, onLoad). The * function returns (1) a regular expression pattern that matches the attribute * (and marks any parameters), (2) the attribute name (as formatted in the HTML * tag), (3) the attribute value, and if they exist, (4) any relevant * parameters and target URL. * * The formatting of the elements of the returned associative array mirrors * the formating described for _handleAttrGeneric() and _handleJavaScript(). * @see DescriptorGenerator#_handleAttrGeneric * @see DescriptorGenerator#_handleJavaScript * * @tparam String tag HTML tag containing intrinsic event attribute * @tparam String attr attribute name * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * * @treturn Object associative array containing pattern that matches intrinsic * event attribute (and marks any parameters), the intrinsic event attribute * name and value as represented in 'tag', and, if they exist, any relevant * parameters and target URL * @private */ DescriptorGenerator._handleAttrOnEventGeneric = function(tag, attr, hint) { var onEventDesc = null; if (tag && attr) { // regex matches and marks tag attribute and its value var regexMarkedOnEventAttr = new RegExp(".*?(" + attr + ")=([\"'])(.*?)\\2.*", "i"); // get default description for onClick attribute onEventDesc = DescriptorGenerator._handleAttrGeneric(tag, attr); if (onEventDesc) { var onEventValDesc = DescriptorGenerator._handleJavaScript(onEventDesc.ATTR_VAL, hint); if (onEventValDesc) { onEventDesc.PATTERN = tag.replace(regexMarkedOnEventAttr, "$1=$2" + onEventValDesc.PATTERN + "$2"); onEventDesc.INFO = onEventValDesc.INFO; } } } return onEventDesc; } /** * Handles the generation of a regular expression pattern to match an instance * of an href HTML tag attribute. The function returns (1) a regular * expression pattern that matches the attribute (and marks any parameters), * (2) the attribute name (as formatted in the HTML tag), (3) the attribute * value, and if they exist, (4) any relevant parameters and target URL. * * The formatting of the elements of the returned associative array mirrors * the formating described for _handleAttrGeneric(), _handleJavaScript(), and * _handleURL(). * @see DescriptorGenerator#_handleAttrGeneric * @see DescriptorGenerator#_handleJavaScript * @see DescriptorGenerator#_handleURL * * @tparam String tag HTML tag containing href attribute * @tparam String attr attribute name [included for method signature * consistency] * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * * @treturn Object associative array containing pattern that matches href * attribute (and marks any parameters), the href attribute name and value * as represented in 'tag', and, if they exist, any relevant parameters and * target URL * @private */ DescriptorGenerator._handleAttrHref = function(tag, attr, hint) { // regex matches and marks href attribute and its value var REGEX_MARKED_HREF = /.*?(href)=(["'])(.*?)\2.*/i; // regex matches and marks JavaScript protocol and code var REGEX_MARKED_JAVASCRIPT = /(javascript)\:(.*)/i; var hrefDesc = null; if (tag) { // get default description for href attribute var hrefDesc = DescriptorGenerator._handleAttrGeneric(tag, "href"); if (hrefDesc) { // get default pattern for href value var hrefValPattern = Util.regexEscape(hrefDesc.ATTR_VAL); // check for "javascipt" protocol var jsResult = REGEX_MARKED_JAVASCRIPT.exec(hrefDesc.ATTR_VAL); if (jsResult) { // (a) handle href:JavaScript case var jsCode = jsResult[2]; var jsCodeDesc = DescriptorGenerator._handleJavaScript(jsCode, hint); if (jsCodeDesc) { hrefValPattern = hrefDesc.ATTR_VAL.replace(REGEX_MARKED_JAVASCRIPT, "$1:" + jsCodeDesc.PATTERN); hrefDesc.INFO = jsCodeDesc.INFO; } } else { // (b) handle href:URL case var url = hrefDesc.ATTR_VAL; var urlDesc = DescriptorGenerator._handleURL(url, hint); if (urlDesc) { hrefValPattern = urlDesc.PATTERN; hrefDesc.INFO = urlDesc.INFO; } } hrefDesc.PATTERN = tag.replace(REGEX_MARKED_HREF, "$1=$2" + hrefValPattern + "$2"); } } return hrefDesc; } /** * Handles the generation of a regular expression pattern to match an instance * of an action HTML tag attribute. The function returns (1) a regular * expression pattern that matches the attribute (and marks any parameters), * (2) the attribute name (as formatted in the HTML tag), (3) the attribute * value, and if they exist, (4) any relevant parameters and target URL. * * The formatting of the elements of the returned associative array mirrors * the formating described for _handleAttrGeneric() and _handleURL(). * @see DescriptorGenerator#_handleAttrGeneric * @see DescriptorGenerator#_handleURL * * @tparam String tag HTML tag containing action attribute * @tparam String attr attribute name [included for method signature * consistency] * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * * @treturn Object associative array containing pattern that matches action * attribute (and marks any parameters), the action attribute name and value * as represented in 'tag', and, if they exist, any relevant parameters and * target URL * @private */ DescriptorGenerator._handleAttrAction = function(tag, attr, hint) { // regex matches and marks action attribute and its value var REGEX_MARKED_ACTION = /.*?(action)=(["'])(.*?)\2.*/i; var actionDesc = null; if (tag) { // get default description for action attribute var actionDesc = DescriptorGenerator._handleAttrGeneric(tag, "action"); if (actionDesc) { // get default pattern for action value var actionValPattern = Util.regexEscape(actionDesc.ATTR_VAL); var urlDesc = DescriptorGenerator._handleURL(actionDesc.ATTR_VAL, hint); if (urlDesc) { actionValPattern = urlDesc.PATTERN; actionDesc.INFO = urlDesc.INFO; } actionDesc.PATTERN = tag.replace(REGEX_MARKED_ACTION, "$1=$2" + actionValPattern + "$2"); } } return actionDesc; } /** * Handles the generation of a regular expression pattern to match an instance * of a "value" HTML tag attribute. The function returns (1) a regular * expression pattern that matches the attribute (and marks any parameters), * (2) the attribute name (as formatted in the HTML tag), (3) the attribute * value, and if they exist, (4) any relevant parameters. * * The formatting of the elements of the returned associative array mirrors * the formating described for _handleAttrGeneric() and _handleURL(). * @see DescriptorGenerator#_handleAttrGeneric * @see DescriptorGenerator#_handleURL * * @tparam String tag HTML tag containing "value" attribute * @tparam String attr attribute name [included for method signature * consistency] * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * * @treturn Object associative array containing pattern that matches value * attribute (and marks any parameters), the "value" attribute name and value * as represented in 'tag', and, if they exist, any relevant parameters * @private */ DescriptorGenerator._handleAttrValue = function(tag, attr, hint) { var valueDesc = null; if (tag) { var valueDesc = DescriptorGenerator._handleAttrGeneric(tag, "value"); if (valueDesc) { if (hint && (hint.HINT_TYPE == DescriptorGenerator.HINT_TYPES.PARAM_NVPAIR) && (hint.PARAM_VAL == valueDesc.ATTR_VAL)) { valueDesc.INFO = { PARAMS: valueDesc.ATTR_VAL }; var attrValPattern; if (hint.WTI) { attrValPattern = "(" + Util.regexEscape(Util.stripWTIChars(hint.PARAM_VAL)) + "[^>]*)"; } else { attrValPattern = "([^>]*)"; } valueDesc.PATTERN = valueDesc.PATTERN.replace(Util.regexEscape(valueDesc.ATTR_VAL), attrValPattern); } } } return valueDesc; } /** * Handles the generation of a regular expression pattern to match an instance * of a generic HTML tag. The function returns (1) a regular expression * pattern that matches the tag (and marks any parameters) and, if they exist, * (2) any relevant parameters and target URL. * * The formatting of the elements of the returned associative array mirrors * the formating described for _handleJavaScript(). * @see DescriptorGenerator#_handleJavaScript * * @tparam String tag HTML tag * @tparam Array attrs array of names of attributes that, if found, should be * included in generated tag pattern * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML fragment should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML fragment [optional] * * @treturn Object associate array containing a pattern that matches 'tag', * and, if they exist, any relevant parameters and target URL * @private */ DescriptorGenerator._handleTag = function(tag, attrs, hint, actions) { // pattern matches all characters within an HTML tag var PATTERN_TAG_MATCH_ALL = "[^>]*"; // regex matches all intrinsic event atttribue names (e.g. onLoad, onClick) var REGEX_INTRINSIC_EVENT_ATTR_NAME = /^on\w+$/i; if (tag && (attrs instanceof Array)) { var type = DescriptorGenerator.getTagType(tag); var sortedAttrs = DescriptorGenerator._sortAttributesByTagOrder(attrs, tag); var info; var attrPatterns = []; for (var attrHandler, attrDesc, i = 0; i < sortedAttrs.length; i++) { var attr = sortedAttrs[i]; attrHandler = DescriptorGenerator._getAttributeHandler(type, attr); if (attrHandler) { attrDesc = attrHandler(tag, attr, hint); } else if (REGEX_INTRINSIC_EVENT_ATTR_NAME.test(attr)) { attrDesc = DescriptorGenerator._handleAttrOnEventGeneric(tag, attr, hint); } else { attrDesc = DescriptorGenerator._handleAttrGeneric(tag, attr); } if (attrDesc) { // add the single pattern for each attribute attrPatterns.push(attrDesc.PATTERN); if (attrDesc.INFO) { // NOTE assumes that at most one attribute per tag will // provide "unique" additional information info = attrDesc.INFO; } } } var tagPattern; if (attrPatterns.length == 0) { // FIXME also matches tags where 'type' is a leading substring of // tag name (e.g. "" and "") tagPattern = ["<", Util.regexEscape(type), PATTERN_TAG_MATCH_ALL, ">"].join(""); } else { var tagAttrsPattern = attrPatterns.join(PATTERN_TAG_MATCH_ALL); tagPattern = ["<", Util.regexEscape(type), " ", PATTERN_TAG_MATCH_ALL, tagAttrsPattern, PATTERN_TAG_MATCH_ALL, ">"].join(""); } return { PATTERN: tagPattern, INFO: info }; } return null; } /** * Generates a regular expression pattern for tag contents. The function * returns (1) a regular expression pattern that matches the tag contents (and * marks any parameters) and, if they exist, (2) any relevant parameters and * target URL. * * The formatting of the elements of the returned associative array mirrors * the formating described for _handleJavaScript(). * @see DescriptorGenerator#_splitHTML * @see DescriptorGenerator#_handleJavaScript * * @tparam Array contents array of tokenized HTML content for a tag * @tparam Array tagTypes relevant/important tag types * @tparam Object hint associative array containing a "hint" as to which * parameters in the HTML content should be marked [optional] * @tparam Array actions array of XML nodes describing the action(s) associated * with the HTML content [optional] * * @treturn Object associate array containing a pattern that matches tag * contents and, if they exist, any relevant parameters and target URL * @private */ DescriptorGenerator._getContentsPattern = function(contents, tagTypes, hint, actions) { // FIXME handle var descriptorGenerators = DescriptorGenerator._getGenerators(tagTypes); // create pattern for contents by concatinating patterns // for content elements var info; var contentPatterns = []; for (var i = 0; i < contents.length; i++) { // use return descriptor pattern if available var descriptor = DescriptorGenerator._generateDescriptor(contents[i], descriptorGenerators, hint, actions); if (descriptor) { contentPatterns[i] = descriptor.getPattern(); if (descriptor.getMarkedValue(false)) { info = { PARAMS: descriptor.getMarkedValue(false), URL: descriptor.getTargetURL() }; } } else { var type = DescriptorGenerator.getTagType(contents[i]); if (type == "") { // use "match all" pattern for text contentPatterns[i] = "[^<]*"; } else if (type == "b" || type == "i" || type == "u") { // use literal html for formatting tags contentPatterns[i] = contents[i]; } else { // use "match all" pattern for tag contentPatterns[i] = "<[^>]*>"; } } } return { PATTERN: contentPatterns.join(""), INFO: info }; } /** * Generates a parameter name from tag contents. * *
 * The tag contents array [<img src="image.jpg" title="Logout">], for example,
 * would produce the following name, if "img" was specified as a relevant tag
 * type:
 *
 *     "Logout"
 * 
* * @tparam Array contents tokenized tag contents * @tparam Array tagTypes tag types that may serve as a basis for generating a * parameter name * * @treturn String parameter name based on contents * @private */ DescriptorGenerator._getParamNameFromContents = function(contents, tagTypes) { // get relevant attributes for specified tag types var tagAttrs = DescriptorGenerator._getNameAttributes(tagTypes); for (var type, i = 0; i < contents.length; i++) { type = DescriptorGenerator.getTagType(contents[i]); if (tagAttrs[type]) { if (tagAttrs[type].length == 0) { // use entire contents token if attributes array is empty (i.e. // text) if (Util.trim(contents[i]).length > 0) { // ...but avoid returning whitespace return contents[i]; } } else { // else, use value of first relevant attribute found for (var attrVal, j = 0; j < tagAttrs[type].length; j++) { if (attrVal = DescriptorGenerator.getAttribute(contents[i], tagAttrs[type][j])) { return attrVal; } } } } } return ""; } /** * Returns a formatted parameter name based on the specified name. * * @tparam String name base name for parameter * * @treturn String formatter parameter name * @private */ DescriptorGenerator._formatParamName = function(name) { var MAX_NAME_LENGTH = 20; if (!name) { // FIXME append random/sequential number to base name name = "elem"; } // Replace special name-value pair characters var paramName = createNameValuePropertyName("paramFor_" + Util.trim(name), "_"); return paramName.substring(0, MAX_NAME_LENGTH); } /** * Returns the encoding mode appropriate for the specified HTML fragment. * @see DescriptorGenerator#ENCODING_MODES * * @tparam String html HTML fragment * * @treturn String appropriate encoding mode for the HTML fragment * @private */ DescriptorGenerator._getEncodingMode = function(html) { // FIXME implement test for "url encode" mode // regex matches any kind of character reference: character entity // reference (e.g. "å"), decimal/hexadecimal numeric character // reference (e.g. "å", "å") var REGEX_CHAR_REF = /&[\dA-Z]+;|&#\d+;|&#x[\dA-F]+;/gi; // regex matches a URL-encoded character var REGEX_URL_ENCODING = /%[\dA-F]{2}/i; var mode; if (html) { if (REGEX_CHAR_REF.test(html)) { // "character reference decode" mode mode = DescriptorGenerator.ENCODING_MODES.CHAR_REF_DECODE; } else if (REGEX_URL_ENCODING.test(html)) { // "raw text" mode mode = DescriptorGenerator.ENCODING_MODES.RAW_TEXT; } } // use "raw text" as default mode return mode ? mode : DescriptorGenerator.ENCODING_MODES.RAW_TEXT; } /** * Returns an Array containing tokens of the specified HTML fragments. This * function works by splitting the HTML code into tokens on tag boundaries. * *
 * The HTML fragment 
 * <a href="index.html"><b>Home</b></a>, for example, 
 * yields the following result:
 *
 *     [<a href="index.html">, <b>, Home, </b>, </a>]
 * 
* * @tparam String html properly formatted HTML fragment * * @treturn Array an array containing the tokens created from splitting the * HTML fragment on tag boundaries. * @private */ DescriptorGenerator._splitHTML = function(html) { // FIXME handle embeded brackets (i.e. in JavaScript code) var BRACKET = ["<", ">"]; if (html) { var trimmedHTML = Util.trim(html); var token; var tokens = []; var offset = 0; var index, lastIndex = 0; while ((index = trimmedHTML.indexOf(BRACKET[offset], lastIndex)) != -1) { token = trimmedHTML.substring(lastIndex, index + offset); if (token.length > 0) { tokens.push(token); } lastIndex = index + offset; // offset determines whether matched bracket (<, >) is included in // current substring (i.e. closing bracket) or next substring (i.e. // opening bracket) offset = (offset + 1)%2; } // handle ending token token = trimmedHTML.substring(lastIndex); if (token.length > 0) { tokens.push(token); } return tokens; } return null; } /** * Returns the value (without quotes) of the attribute for the specified tag. * * @tparam String tag HTML tag * @tparam String attr attribute of HTML tag * * @treturn String value of specified attribute of tag or null if tag does not * contain a value for the attribute * @private */ DescriptorGenerator.getAttribute = function(tag, attr) { if (tag && attr) { var pattern = attr + "=([\"']?)(.*?)\\1[\\s\\/>]"; var regex = new RegExp(pattern, "i"); var result = regex.exec(tag); return result ? result[2] : null; } return null; } /** * Sorts the specified attributes by the order of their appearance in the HTML * tag. Note that attributes not found in the tag will not be included in the * sorted array. * * @tparam Array attrs attributes to be sorted * @tparam String tag HTML tag * * @treturn Array new array of attributes sorted by order of appearance in the * HTML tag * @private */ DescriptorGenerator._sortAttributesByTagOrder = function(attrs, tag) { if (attrs && tag) { // create a regular expression that matches any of specified attributes var attrRegexPattern = "\\s(" + attrs.join("|") + ")="; var attrRegex = new RegExp(attrRegexPattern, "gi"); var result; var sortedAttrs = []; while ((result = attrRegex.exec(tag)) != null) { sortedAttrs.push(result[1]); } return sortedAttrs; } return null; } /** * Returns the type of a tag (e.g. a, img). * * @tparam String tag HTML tag * * @treturn String type of tag or empty string if not valid tag * @private */ DescriptorGenerator.getTagType = function(tag) { // tag type regular expression (e.g "") var REGEX_TAG_TYPE = /<\/?(\w+).*>/; if (tag) { var result = REGEX_TAG_TYPE.exec(tag); return result ? result[1] : ""; } return null; } /** * Returns true if the HTML tag is a tag of the specified type (e.g. a, form, * img). * * @tparam String tag HTML tag * @tparam String type HTML tag type * * @treturn boolean true if tag is of the specified type * @private */ DescriptorGenerator.isTagOfType = function(tag, type) { if (tag && type) { return type.toLowerCase() == DescriptorGenerator.getTagType(tag).toLowerCase(); } return false; } /** * Returns true if the HTML tag is an opening tag (e.g
). * * Note that this function returns true if given a self-closing tag as the * argument (e.g. ). * * @tparam String tag the HTML tag * * @treturn boolean true if tag is an opening tag * @private */ DescriptorGenerator.isOpeningTag = function(tag) { if (tag) { return tag.search(/^\s*<[^\/]/) != -1; } return false; } /** * Returns true if the HTML tag is an opening tag of the specified type (e.g. a, * form, img). * * Note that the value returned by this function is equivalent to the value of * the value of the expression "isTagOfType() && isOpeningTag()". * @see DescriptorGenerator#isOpeningTag * @see DescriptorGenerator#isTagOfType * * @tparam String tag HTML tag * @tparam String type HTML tag type * * @treturn boolean true if tag is an opening tag of the specified type * @private */ DescriptorGenerator.isOpeningTagOfType = function(tag, type) { return DescriptorGenerator.isOpeningTag(tag) && DescriptorGenerator.isTagOfType(tag, type) } /** * Returns true if the HTML tag is a closing tag (e.g </a>). * * Note that this function returns true if given a self-closing tag as the * argument (e.g. <img src="image.jpg" />). * * @tparam String tag the HTML tag * * @treturn boolean true if tag is a closing tag * @private */ DescriptorGenerator.isClosingTag = function(tag) { if (tag) { return tag.search(/<\/|\/>/) != -1; } return false; } /** * Returns true if the HTML tag is a closing tag of the specified type (e.g. a, * form, img). * * Note that the value returned by this function is equivalent to the value of * the value of the expression "isTagOfType() && isClosingTag()". * @see DescriptorGenerator#isClosingTag * @see DescriptorGenerator#isTagOfType * * @tparam String tag HTML tag * @tparam String type HTML tag type * * @treturn boolean true if tag is a closing tag of the specified type * @private */ DescriptorGenerator.isClosingTagOfType = function(tag, type) { return DescriptorGenerator.isClosingTag(tag) && DescriptorGenerator.isTagOfType(tag, type) } /** * Apply the encoding mode (e.g. character reference decode) to the specified * string. * @see DescriptorGenerator#ENCODING_MODES * * @tparam String str string to which encoding mode is to be applied * @tparam String encoding relevant encoding mode to apply * * @treturn String string with encoding mode applied * @private */ DescriptorGenerator.applyEncodingMode = function(str, encoding) { if (str && encoding) { if (encoding == DescriptorGenerator.ENCODING_MODES.CHAR_REF_DECODE) { return Util.charRefDecode(str); } else if (encoding == DescriptorGenerator.ENCODING_MODES.URL_ENCODE) { return Util.Unicode2URLEncode(str); } else if (encoding == DescriptorGenerator.ENCODING_MODES.RAW_TEXT) { return str; } else if (encoding == DescriptorGenerator.ENCODING_MODES.CHAR_REF_URL_ENCODE) { return Util.Unicode2URLEncode(Util.charRefDecode(str)); } } return str; } /////////////////////////////////////////////////////////////////////////////// // PARSER /////////////////////////////////////////////////////////////////////////////// /** * @class Parser * A collection of static functions for string parsing. * * @ctor Parser * Creates a paser. * * @treturn Parser a parser * @private */ function Parser() { } /** * Parses contents of a string. * * By default, this function performs a shallow parsing of the specified * string, however there is an optional flag to allow deep parsing of * arrays and hashes (associative arrays). * * @tparam String str string to be parsed * @tparam boolean deep indicates if a deep parsing should be performed * * @treturn Object parsed contents of string or string itself if it contains no * parsable content * @public */ Parser.parse = function(str, deep) { var result; if ((result = Parser.parseString(str)) || (result = Parser.parseInt(str)) || (result = Parser.parseFloat(str)) || (result = Parser.parseBoolean(str)) || (result = Parser.parseArray(str, deep)) || (result = Parser.parseHash(str, deep))) { return result; } if (Parser.isNull(str)) { return null; } if (Parser.isUndefined(str)) { return undefined; } return str; } /** * Parses the integer literal represented by the specified string. * * @tparam String str string to be parsed * * @treturn Number integer value expressed by 'str' or null if 'str' * does not represent a valid integer literal * @public */ Parser.parseInt = function(str) { // regex that matches and marks a base-10 (or octal) integer literal var REGEX_MARKED_INT10 = /^\s*((?:\+|\-)?\d+)\s*$/; // regex that matches and marks a hexadecimal integer literal var REGEX_MARKED_INT16 = /^\s*((?:\+|\-)?0X[0-9A-F]+)\s*$/i; if (str) { var result; if ((result = REGEX_MARKED_INT10.exec(str)) || (result = REGEX_MARKED_INT16.exec(str))) { return parseInt(result[1]); } } return null; } /** * Parses the floating-point literal represented by the specified string. * * @tparam String str string to be parsed * * @treturn Number floating-point value expressed by 'str' or null if 'str' * does not represent a valid floating point literal * @public */ Parser.parseFloat = function(str) { // regex that matches a floating-point literal var REGEX_MARKED_FLOAT = /^\s*((?:\+|\-)?\d*(?:\.\d*)?(?:E(?:\+|\-)?\d+)?)\s*$/i; if (str) { var result; if (result = REGEX_MARKED_FLOAT.exec(str)) { return parseFloat(result[1]); } } return null; } /** * Parses the boolean literal represented by the specified string. * * @tparam String str string to be parsed * * @treturn boolean the boolean value expressed by 'str' or null if 'str' * does not represent a valid boolean literal * @public */ Parser.parseBoolean = function(str) { // regex that matches the reserved word "true" (ignoring leading/trailing // whitespace) var REGEX_TRUE = /^\s*true\s*$/; // regex that matches the reserved word "false" (ignoring leading/trailing // whitespace) var REGEX_FALSE = /^\s*false\s*$/; if (str) { if (REGEX_TRUE.test(str)) { return true; } if (REGEX_FALSE.test(str)) { return false; } } return null; } /** * Parses the string expressed by the specified string. * * For example, if given the argument * * "'a string'" * * the function would return the following string: * * "a string" * * @tparam String str string to be parsed * * @treturn String string expressed by 'str' or null if 'str' does not * represent a properly formatted string literal * @public */ Parser.parseString = function(str) { // pattern for regex that matches and marks a quotation mark var PATTERN_MARKED_QUOTE = "(" + DescriptorGenerator._QUOTES.join("|") + ")"; // regex that matches a string literal (e.g. "'a string'", ""another // string"") and marks the quote contents var REGEX_MARKED_STRING = new RegExp("^\\s*" + PATTERN_MARKED_QUOTE + "(.*?)\\1\\s*$"); if (!str) { return null; } var result = REGEX_MARKED_STRING.exec(str); return result ? result[2] : null; } /** * Parses the hash (associative array) expressed by the specified string. * * By deafault, this function performs a shallow parsing of the specified * string, however there is an optional flag to allow deep parsing of * the contents of the hash (associative array). * * @tparam String str string to be parsed * @tparam boolean deep indicates if a deep parsing should be performed on the * contents of the hash * * @treturn Object hash expressed by 'str' or null if 'str' does not represent * a properly formatted hash literal * @public */ Parser.parseHash = function(str, deep) { // regex that matches a hash literal (e.g. "{'key1':1, key2:6}") and // marks the quote contents (ignoring leading/trailing whitespace) var REGEX_MARKED_HASH_CONTENTS = /^\s*\{([^\}]*)\}\s*$/; if (!str) { return null; } var result = REGEX_MARKED_HASH_CONTENTS.exec(str); if (result) { var keyValPairStrs = Util.splitElements(result[1]); if (keyValPairStrs) { var hash = {}; for (var key, val, keyValPair, i = 0; i < keyValPairStrs.length; i++) { keyValPair = Util.splitElements(keyValPairStrs[i], ":"); if (!keyValPair || (keyValPair.length != 2)) { Util.debug("Error parsing hash key-value pair: " + keyValPairStrs[i], Parser); return null; } // get key, which may or may not be a string ("'key'" or "key") if (!(key = Parser.parseString(keyValPair[0]))) { key = keyValPair[0]; } // get value (with deep parsing if indicated) val = deep ? Parser.parse(keyValPair[1], deep) : keyValPair[1]; hash[key] = val; } return hash; } } return null; } /** * Parses the array expressed by the specified string. * * By deafault, this function performs a shallow parsing of the specified * string, however there is an optional flag to allow deep parsing of * the contents of the array. * * @tparam String str string to be parsed * @tparam boolean deep indicates if a deep parsing should be performed on the * contents of the array * * @treturn Array array expressed by 'str' or null if 'str' does not represent * a properly formatted array literal * @public */ Parser.parseArray = function(str, deep) { // regex that matches an array literal (e.g. "[0, 1, 3]") and marks the // quote contents (ignoring leading/trailing whitespace) var REGEX_MARKED_ARRAY_CONTENTS = /^\s*\[([^\]]*)\]\s*$/; if (!str) { return null; } var result = REGEX_MARKED_ARRAY_CONTENTS.exec(str); if (result) { var elemStrs = Util.splitElements(result[1]); if (elemStrs) { var arr = []; for (var elem, i = 0; i < elemStrs.length; i++) { elem = deep ? Parser.parse(elemStrs[i], deep) : elemStrs[i]; arr[i] = elem; } return arr; } } return null; } /** * Returns true if the specified string represents "null". * * @tparam String str string to tested * * @treturn boolean true if string contains "null" * @public */ Parser.isNull = function(str) { // regex that matches the reserved word "null" (ignoring // leading/trailing whitespace) var REGEX_NULL = /^\s*null\s*$/; if (str) { return REGEX_NULL.test(str); } return false; } /** * Returns true if the specified string represents "undefined". * * @tparam String str string to tested * * @treturn boolean true if string contains "undefined" * @public */ Parser.isUndefined = function(str) { // regex that matches the word "undefined" (ignoring // leading/trailing whitespace) var REGEX_UNDEFINED = /^\s*undefined\s*$/; if (str) { return REGEX_UNDEFINED.test(str); } return false; } /** * @class Util. * A collection of static utility functions. */ function Util() { } /** * Splits a URL into its component parts: protocol, authority, path, query, * fragment. * * For example, given the following URL * * "http://www.example.com:1234/path_to_file/file.jsp?param1=val1¶m2=val2#fragval" * * this method would return * * { PROTOCOL : "http", * AUTHORITY : "www.example.com:1234", * PATH : "/path_to_file/file.jsp?", * QUERY : "param1=val1¶m2=val2", * FRAGMENT : "fragval" } * * @tparam String url URL to split * @param {String} url URL to split * * @treturn Object URL parts * @returns {Object} URL parts */ Util.splitURL = function(url) { // regex matches a URL and marks its parts (i.e. protocol, authority, path, // query, fragment) // var REGEX_MARKED_URL_PARTS = /(?:(?:([^:]+)\:\/\/)?([^\/]+)(?=\/))?(.+[\$\?])(.*)/; var REGEX_MARKED_URL_PARTS = /(?:(?:([^:]*)\:\/\/)?([^/$?#]+)(?=\/))?([^$?#]*[$?]?)?(?:([^#]*)#?)?(.+)?/; if (url) { var result = REGEX_MARKED_URL_PARTS.exec(url); if (result) { for (var i = 1; i < result.length; i++) { if (!result[i]) { result[i] = ""; } } return { PROTOCOL: result[1], AUTHORITY: result[2], PATH: result[3], QUERY: result[4], FRAGMENT: result[5] }; } } return null; } /** * Compares two URLs by path, query and fragment components. The method tests * whether the query and fragment components of two URLs are identical and the * path of one URL is a trailing substring of the path of the other URL (i.e. * "/foo/bar/page.jsp?nm1=val1#frag", "/bar/page.jsp?nm1=val1#frag"). * @see Util#splitURL * * @tparam String url1 first URL to be compared * @tparam String url2 second URL to be compared * * @treturn boolean true if (1) query and fragment components of the two URLs * are equal and the path of one URL ends with the path of the other URL * OR (2) url1 == url2 would evaluate to true * @public */ Util.compareURLByComponentsTrailingAuthority = function(url1, url2) { try { if (url1 && url2) { var urlComp1 = Util.splitURL(url1); var urlComp2 = Util.splitURL(url2); var path1 = urlComp1.PATH; var path2 = urlComp2.PATH; return (Util.matchExtension(path1, path2) || Util.matchExtension(path2, path1)) && urlComp1.QUERY == urlComp2.QUERY && urlComp1.FRAGMENT == urlComp2.FRAGMENT; } else { return url1 == url2; } } catch (e) { return url1 == url2; } } /** * Returns true if the first str is a suffix of the second string. */ Util.matchExtension = function (extension, url) { return (url.length >= extension.length) ? url.substr(url.length - extension.length, extension.length) == extension : false; } /** * input is a string contains double or single quote, * escape it by prefix a \ */ Util.escapeQuote = function(str) { return str.replace(/(\\|\'|\")/g,"\\$1"); } Util.escapeDoubleQuote = function(str) { return str.replace(/(\\|\")/g,"\\$1"); } Util.quotify = function(str) { if (str) { return ["\"", Util.escapeDoubleQuote(str), "\""].join(""); } else { return "\"\""; } } Util.trim = function(str) { return str.replace(/^\s+|\s+$/g, ''); } /** * Splits a deliminator-separated string of elements (e.g. arguments). * * Note that the specified deliminator cannot be same as or a substring of a * quote character/string (e.g. ', ", "). If no deliminator is specified * or an empty string deliminator is specified, a comma will be used as the * default deliminator. * * @tparam String elemsStr elements deliminated by 'delim' * @tparam String delim deliminator for 'elemsStr' * * @treturn Array an array of elements contained in elements string * @public */ Util.splitElements = function(elemsStr, delim) { // FIXME handle nested/multiple braces/brackets // pattern for regex that matches and marks a quotation mark var PATTERN_MARKED_QUOTE = "(" + DescriptorGenerator._QUOTES.join("|") + ")"; if (!delim) { delim = ","; } var regexMarkedQuotedArg = new RegExp("^\\s*(" + PATTERN_MARKED_QUOTE + ".*?\\2)\\s*(?:" + delim + "|$)"); var regexMarkedUnquotedArg = new RegExp("^\\s*([^" + delim + "]+)\\s*(?:" + delim + "|$)"); var regexMarkedBracketedArg = new RegExp("^\\s*([\\[].*[\\]])\\s*(?:" + delim + "|$)"); var regexMarkedBracedArg = new RegExp("^\\s*([\\{].*[\\}])\\s*(?:" + delim + "|$)"); if (elemsStr || elemsStr == "") { var result; var args = []; var str = elemsStr; while (str.length > 0) { // NOTE the order of these checks is important if ((result = regexMarkedQuotedArg.exec(str)) || (result = regexMarkedBracketedArg.exec(str)) || (result = regexMarkedBracedArg.exec(str)) || (result = regexMarkedUnquotedArg.exec(str))) { args[args.length] = result[1]; str = str.substring(result[0].length); } else { Util.debug("Found unparsable arg: " + str, Util); return null; } } return args; } return elemsStr; } /** * Escapes any characters in the specified string that have special meaning * (i.e. not intepreted as literal characters) within a regular expression. * * Note that this function IS NOT meant to be used to escape expressions for * use with JavaScript regular expression. * * @tparam String str a string * * @treturn String string with regular expression special characters escaped * @tpublic */ Util.regexEscape = function(str) { // regex matches and markes all regular expression special chars var REGEX_MARKED_SPECIAL_CHARS = /([\^\$\.\*\+\?\|\\\(\)\[\]\{\}])/g; if (str) { return str.replace(REGEX_MARKED_SPECIAL_CHARS, "\\$1"); } return str; } /** * Escapes any characters in the specified string that have special meaning * (i.e. not intepreted as literal characters) within a regular expression. * * Note that this function IS meant to be used to escape expressions for * use with JavaScript regular expression. * * @tparam String str a string * * @treturn String string with regular expression special characters escaped * @tpublic */ Util.jsRegexEscape = function(str) { // regex matches and markes all regular expression special chars var REGEX_MARKED_SPECIAL_CHARS = /([\^\$\.\*\+\?\=\!\:\|\\\/\(\)\[\]\{\}])/g; if (str) { return str.replace(REGEX_MARKED_SPECIAL_CHARS, "\\$1"); } return str; } /** * Returns the portion of a string matched by the specified marked * (parenthesized) subexpression of a pattern. * * @tparam String str a given string * @tparam String pattern pattern to apply against 'str' * @tparam int index one-based index of marked subexpression to return * * @treturn String 'index'-th marked subexpression that results from applying * 'pattern' to 'str' * @private */ Util.getMarkedExpr = function(str, pattern, index) { if ((str || str == "") && pattern && index) { var result = str.match(pattern); if (result && result.length > index) { return result[index]; } } return null; } /** * Returns a slice of an associative array as determined by the specified * keys. * * @tparam Object hash associative array to be sliced * @tparam Array keys keys of mappings to be included in new associative array * * @treturn Object slice of associative array with specified subset of mappings * @public */ Util.slice = function(hash, keys) { if (hash && keys) { var hashSlice = {}; for (var i = 0; i < keys.length; i++) { if (keys[i] in hash) { hashSlice[keys[i]] = hash[keys[i]]; } } return hashSlice; } return null; } /** * Returns a clone of the specified argument. * * Note that this method should be used to clone functions or objects that * contain functions. * * @param item to be cloned * @tparam boolean deep indicates if deep cloing should be performed * * @return cloned item */ Util.clone = function(obj, deep) { var cloned = obj; if (obj) { var type = typeof(obj); if (type == "object") { // handle arrays and associative arrays var cloned; if (obj instanceof Array) { cloned = new Array(obj.length); for (var i = 0; i < obj.length; i++) { cloned[i] = deep ? Util.clone(obj[i], deep) : obj[i]; } } else { cloned = new Object(); for (var elem in obj) { cloned[elem] = deep ? Util.clone(obj[elem], deep) : obj[elem]; } } } else if (type == "function") { // handle functions Util.debug("Function clone attempt", Util); } } return cloned; } /** * Returns all the permutations of the specified set of elements. * * @tparam Array elems set of elements to permute * * @treturn Array set of arrays representing all the permutations the elements * @public */ Util.permute = function(elems) { if (elems instanceof Array) { var perms = []; if (elems.length == 1) { // return copy of array containing single element perms[perms.length] = elems.slice(0); } else if (elems.length > 1) { var head, subElems, subPerms; for (var i = 0; i < elems.length; i++) { // pick next head element and remove from elements subElems = elems.slice(0); head = subElems.splice(i, 1); // permute set of non-head elements and then add head element // to each subPerms = Util.permute(subElems); for (var j = 0; j < subPerms.length; j++) { perms[perms.length] = head.concat(subPerms[j]); } } } return perms; } return null; } /** * Returns the cartesian product the specified sets (that are represented as an * Array of Arrays). * * For example, the input * * [["a1"],["b1","b2"],["c1"]] * * would result in the output * * [["a1", "b2", "c1"], ["a1", "b1", "c1"]] * * @tparam Array sets sets from which Cartesian product will be produced * * @treturn Array Cartesian product (represented as an Array of Arrays) */ Util.getCartesianProduct = function(sets) { if (sets instanceof Array) { // (1) create duplicate of sets with empty arrays removed and (2) create // indices array for iterating over all combinations var setsDup = [], indices = []; for (var i = 0; i < sets.length; i++) { if (sets[i].length > 0) { indices.push(sets[i].length - 1); setsDup[setsDup.length] = sets[i]; } } // add combinations... var arr, product = []; while (parseInt(indices.join(""), 10) > 0) { arr = []; for (var borrow = true, i = 0; i < setsDup.length; i++) { arr[arr.length] = setsDup[i][indices[i]]; if (borrow) { --indices[i]; } if (indices[i] < 0) { indices[i] = setsDup[i].length - 1; borrow = true; } else { borrow = false; } } product[product.length] = arr; } // ...including final (default) combination (of all first elements of // each group) arr = []; for (var i = 0; i < setsDup.length; i++) { arr[arr.length] = setsDup[i][0]; } product[product.length] = arr; return product; } return null; } /** * Applies the token replacement function to each element in the array (or * associative array). Each element of the array is expected to be a string * and the replacement function is expected to have the following signature * * String function(String) * * @tparam Array arr array of strings * @tparam Function func token replacement function to be applied to each * array element * * @treturn Array a new array where each element is the result of applying the * token replacement function to the element in the original array * @public */ Util.replaceTokens = function(arr, func) { if (!arr || !func) { return Util.clone(arr); } var replaceArr = []; for (var key in arr) { replaceArr[key] = func(arr[key]); } return replaceArr; } /** * Prints the specified string. * * @tparam String str string to be printed * @tparam Object obj calling object * @public */ Util.debug = function(str, obj) { var funcName = getFunctionName(obj, Util.debug.caller, true); WScript.Echo("[DEBUG] " + funcName + ": " + str); } /** * Returns the specified string with any character references replaced with the * associated characters. Note that this function only supports the decoding * of character references for ASCII characters. * * @tparam String string to be decoded * * @treturn String string with ASCII character references replaced by actual * characters * @public */ Util.charRefDecode = function(str) { // regex matches (and marks) any kind of character reference: character // entity reference (e.g. "å"), decimal/hexadecimal numeric character // reference (e.g. "å", "å") var REGEX_MARKED_CHAR_REF = /&([\dA-Z]+);|&#(\d+);|&#x([\dA-F]+);/gi; var CHAR_REF_ALL_INDEX = 0; var CHAR_REF_ENTITY_INDEX = 1; var CHAR_REF_DEC_INDEX = 2; var CHAR_REF_HEX_INDEX = 3; if (str) { var result; var lastIndex = 0; var buffer = new StringBuffer(); while ((result = REGEX_MARKED_CHAR_REF.exec(str)) != null) { buffer.append(str.substring(lastIndex, REGEX_MARKED_CHAR_REF.lastIndex - result[0].length)); var c, charRef; if (charRef = result[CHAR_REF_ENTITY_INDEX]) { c = CharRefs.ASCII_CHAR_ENTITY_REFS[charRef]; } else if (charRef = result[CHAR_REF_DEC_INDEX]) { c = CharRefs.ASCII_NUMERIC_CHARS[parseInt(charRef, 10)]; } else if (charRef = result[CHAR_REF_HEX_INDEX]) { c = CharRefs.ASCII_NUMERIC_CHARS[parseInt(charRef, 16)]; } // Append decoded character (or original character reference if // character is outside ASCII character set) buffer.append(c ? c : result[CHAR_REF_ALL_INDEX]); lastIndex = REGEX_MARKED_CHAR_REF.lastIndex; } buffer.append(str.substring(lastIndex, str.length)); return buffer.toString(); } return str; } /** * Returns the value of the specified attribute for the action node. * * @tparam Object action action XML node * @tparam String attr relevant attribute * * @treturn String action attribute value or null if the specifed attribute * does not exist * @public */ Util.getActionAttribute = function(action, attr) { if (action && attr) { return action.getAttribute(attr); } return null; /* var utility = new IERecProcessor(); if (attr && action && utility.isAction(action)) { return utility.getProperty(action, attr); } return null; */ } /** * Finds the values of the specified attributes for the action that passes the * specified set of filters. * * Note that the set of filters are applied against the array of actions in * reverse order and returns the values of the specified attributes for the * first action that passes the set of filters. * * @tparam Array actions relevant list of actions * @tparam Array filters set of filters to be applied to actions * @tparam Array attrs desired attributes for filtered action * * @treturn Object associative array of attributes (and their values) for the * action that passes the specified filters */ Util.findActionAttributes = function(actions, filters, attrs) { if ((actions instanceof Array) && (filters instanceof Array) && (attrs instanceof Array)) { var foundAttrs = {}; actionsLoop: for (var iActions = actions.length - 1; iActions >= 0; iActions--) { filtersLoop: for (var filterAttr in filters) { if (Util.getActionAttribute(actions[iActions], filterAttr) != filters[filterAttr]) { continue actionsLoop; } } attrsLoop: for (var attrVal, iAttrs = 0; iAttrs < attrs.length; iAttrs++) { if (attrVal = Util.getActionAttribute(actions[iActions], attrs[iAttrs])) { foundAttrs[attrs[iAttrs]] = attrVal; } } break actionsLoop; } return foundAttrs; } return null; } Util.Unicode2URLEncode = function(str) { var result = str; result = encodeURIComponent(str); result = result.replace(/~/g, "%7E"); result = result.replace(/!/g, "%21"); result = result.replace(/%40/g, "@"); result = result.replace(/%28/g, "("); result = result.replace(/%29/g, ")"); result = result.replace(/'/g, "%27"); result = result.replace(/%20/g, "+"); return result; } Util.URLEncode2Unicode = function(str) { // for malformed string that cannot be decoded. // ignore exceptions purposely var r_plus = /\+/g; try { // keep this var result = str.replace(r_plus, " "); return decodeURIComponent(result); } catch (e) { // keep this } return str.replace(r_plus, " "); } /** * Strips the random characters added to a parameter value as part of WTI. * * @tparam String str WTI parameter value * * @treturn String input string with WTI random characters removed */ Util.stripWTIChars = function(str) { if (str && (str.length > Constants.WTI_NUM_CHARS)) { return str.substring(0, str.length - Constants.WTI_NUM_CHARS); } return str; } /////////////////////////////////////////////////////////////////////////////// // HTML DESCRIPTOR /////////////////////////////////////////////////////////////////////////////// /** * @class HTMLDescriptor * A wrapper for elements describing an HTML fragment. * * @ctor HTMLDescriptor * Creates a HTML descriptor object. * * @tparam String type type of enclosing (outer) tag of HTML fragment * (e.g. a, img) * @tparam String name name of descriptor * @tparam String pattern regular expression pattern that matches the HTML * fragment * @tparam String encoding encoding mode (e.g. rawText, urlEncode) * @tparam String val marked value of regex * @tparam String url target URL of HTML fragment * (i.e. href value for Link) * * @treturn HTMLDescriptor an HTML descriptor * @public */ function HTMLDescriptor(type, name, pattern, encoding, val, url) { this.m_type = type; this.m_name = name; this.m_pattern = pattern; this.m_encoding = encoding; this.m_val = val; this.m_url = url; } /** * Returns the type of HTML fragment described by this descriptor * (ex: "a", "img"). * * Note that the empty string ("") is used to denote plain text that * appears in an HTML fragment (i.e. the inner html of the following * anchor tag: Link). * * @treturn String HTML fragment type * @public */ HTMLDescriptor.prototype.getType = function() { return this.m_type; } /** * Returns the name of the descriptor. * * @treturn String name of descriptor * @public */ HTMLDescriptor.prototype.getName = function() { return this.m_name; } /** * Returns a regular expression pattern that matches the HTML fragment * described by this descriptor. * * @treturn String regular expression pattern that matches HTML fragment * @public */ HTMLDescriptor.prototype.getPattern = function() { return this.m_pattern; } /** * Returns the encoding mode. (e.g. rawText, urlEncode). * * @treturn String encoding mode * @public */ HTMLDescriptor.prototype.getEncoding = function() { return this.m_encoding; } /** * Returns the value marked by regular expression pattern (with the associated * encoding mode applied by default). * @see HTMLDescriptor#getEncoding * * @tparam boolean applyEncoding indicates if encoding mode should be applied * (default: true) * * @treturn String value marked by regular expression * @public */ HTMLDescriptor.prototype.getMarkedValue = function(applyEncoding) { if ((arguments.length == 1) && !applyEncoding) { return this.m_val; } else { return DescriptorGenerator.applyEncodingMode(this.m_val, this.m_encoding); } } /** * Returns the target (destination) URL of the HTML fragment (with the * associated encoding mode applied by default). * @see HTMLDescriptor#getEncoding * * @tparam boolean applyEncoding indicates if encoding mode should be applied * (default: true) * * @treturn String target (destination) URL of the HTMl fragment * @public */ HTMLDescriptor.prototype.getTargetURL = function(applyEncoding) { if ((arguments.length == 1) && !applyEncoding) { return this.m_url; } else { return DescriptorGenerator.applyEncodingMode(this.m_url, this.m_encoding); } } /** * A base adapter class for a simple interface of managing the flow of recording/playing back a transaction. * @ctor UIController. */ function UIController() { } /** * Initializes members. * @private */ UIController.prototype.init = function() { } /** * Sets the console object. * @param {Console} console */ UIController.prototype.setConsole = function(console) { this.m_console = console; } /** * Gets the console object. * @return the console object. * @type {Console} */ UIController.prototype.getConsole = function() { return this.m_console; } /** * Indicates that UI should be refreshed according to the state. * This method can be over written if the UI need to be updated when * recording/playback changes state. * * @param {String} state possible values include *
 * Constants.STOPPED, 
 * Constants.PAUSED, 
 * Constants.PLAYING, 
 * Constants.RECORDING
 * 
* * @protected */ UIController.prototype.onChangeState = function (state) { } /** * Creates a timer object for event synchronization. * @return Timer A timer object. * @protected */ UIController.prototype.createTimer = function() { if (window.ActiveXObject) { return new ActiveXObject("OraBcnTxnUtil2.Timer"); } else { var o = new Object(); o.start = function() { } o.Time = 0; return o; } } /** * Creates a recorder object for the active browser. * This method must be over written if the UI need to support recording. * @return A recorder object. * @type Recorder * @protected */ UIController.prototype.createRecorder = function() { if (window.ActiveXObject) { recorder = new IERecorder(); recorder.setAnalyzer(new IERecAnalyzer(recorder)); } else { recorder = new FFRecorder(); } return recorder; } /** * Creates a player object for the active browser. * This method must be over written if the UI need to support playback. * @return A player object. * @type Player * @protected */ UIController.prototype.createPlayer = function() { var p = null; if (window.ActiveXObject) { p = new IEPlayer(); } else { p = new PlayerFF(); } p.setInputStream(this.m_input_stream); return p; } /** * Creates a player object for the active browser. * This method must be over written if the UI need to support playback. * @param mediator * @return a PlayerFunctions object. * @type PlayerFuncsIE * @protected */ UIController.prototype.createPlayerFunctions = function(mediator) { var p = null; if (window.ActiveXObject) { p = new PlayerFuncsIE(mediator); } else { p = new PlayerFuncsFF(mediator); } return p; } /** * Starts playback. */ UIController.prototype.play = function () { var timer = this.createTimer(); player = this.createPlayer(); pf = this.createPlayerFunctions(player); if (player && pf) { player.setTimer(timer); pf.setTimer(timer); this.onStartPlay(player); this.onChangeState(Constants.PLAYING); timer.start(); player.start(); } } /** * Starts recording while a pre-recorded transaction is played back. */ UIController.prototype.rerecord = function() { var timer = this.createTimer(); recorder = this.createRecorder(); if (recorder) { recorder.setTimer(timer); this.onStartRecord(recorder); } player = this.createPlayer(); if (player) { player.setTimer(timer); this.onStartPlay(player); } this.onChangeState(Constants.RERECORDING); recordingPlayer = new IERecordingPlayer(recorder, player); pf = this.createPlayerFunctions(recordingPlayer); if (pf) { pf.setTimer(timer); } if (recordingPlayer) { recordingPlayer.setTimer(timer); timer.start(); recordingPlayer.start(); } } /** * Starts recording. */ UIController.prototype.start = function() { var timer = this.createTimer(); recorder = this.createRecorder(); if (recorder) { recorder.setTimer(timer); this.onStartRecord(recorder); this.onChangeState(Constants.RECORDING); timer.start(); recorder.start(); } } /** * Stops recording or playback. */ UIController.prototype.stop = function() { if (this.m_stopping == true) { return; } this.m_stopping = true; if (recordingPlayer) { recordingPlayer.stop(); this.onChangeState(Constants.STOPPED); } else if (recorder) { recorder.stop(); this.onChangeState(Constants.STOPPED); } else if (player) { player.stop(); this.onChangeState(Constants.STOPPED); } if (recorder) { this.onStopRecord(recorder); recorder = null; } if (player) { this.onStopPlay(player); player = null; } if (recordingPlayer) { recordingPlayer = null; } if (pf) { pf = null; } this.m_stopping = false; } /** * Aborts recording or playback. */ UIController.prototype.abort = function() { if (this.m_stopping == true) { return; } this.m_stopping = true; if (recordingPlayer) { recordingPlayer.abort(); this.onChangeState(Constants.STOPPED); } else if (recorder) { recorder.abort(); this.onChangeState(Constants.STOPPED); } else if (player) { player.abort(); this.onChangeState(Constants.STOPPED); } if (recorder) { this.onAbortRecord(recorder); recorder = null; } if (player) { this.onAbortPlay(player); player = null; } if (recordingPlayer) { recordingPlayer = null; } if (pf) { pf = null; } this.m_stopping =false; } /** * Pauses the current playback. */ UIController.prototype.pause = function () { if (player) { player.pause(); this.onChangeState(Constants.PAUSED); } } /** * Resumes the current playback. */ UIController.prototype.resume = function () { if (player) { player.resume(); this.onChangeState(Constants.PLAYING); } } /** * Allows sub class to take control before recording starts. * @param {Recorder} recorder * @protected */ UIController.prototype.onStartRecord = function (recorder) { if (this.m_console) { this.m_console.open(); } } /** * Allows sub class to take control after recording stops * and user does not want to save the recording. * @protected * @param {Recorder} recorder */ UIController.prototype.onStopRecord = function (recorder) { if (this.m_console) { this.m_console.close(); } } /** * Allows sub class to take control after recording stops * and user wants to save the recording. * @protected * @param {Recorder} recorder */ UIController.prototype.onSaveRecord = function (recorder) { if (this.m_console) { this.m_console.close(); } } /** * Allows sub class to take control after recording aborts * @protected * @param {Recorder} recorder */ UIController.prototype.onAbortRecord = function (recorder) { if (this.m_console) { this.m_console.close(); } } /** * Allows sub class to take control before playback starts. * @protected * @param {Player} player */ UIController.prototype.onStartPlay = function (player) { if (this.m_console) { this.m_console.open(); } } /** * Allows sub class to take control after playback stops. * @protected * @param {Player} player */ UIController.prototype.onStopPlay = function (player) { if (this.m_console) { this.m_console.close(); } } /** * Allows sub class to take control after playback aborts. * @protected * @param {Player} player */ UIController.prototype.onAbortPlay = function (player) { if (this.m_console) { this.m_console.close(); } } /** * Displays the number of locks currently the playback engine is waiting. */ UIController.prototype.showLocks = function(numLocks) { if (this.m_console) { this.m_console.showLocks(numLocks); } } /** * Checks if debug mode is on. */ UIController.prototype.checkDebugMode = function () { this.m_debug = false; /* var query = location.search.substring(1); var pairs = query.split("&"); for (var i = 0; i < pairs.length; i++) { var pos = pairs[i].indexOf('='); if (pos == -1) continue; var argname = pairs[i].substring(0,pos); var value = pairs[i].substring(pos+1); if ("debug" == argname && value == "true") { this.m_debug = true; break; } } */ } var recorder = null; var player = null; var pf = null; var recordingPlayer = null; /** * A simple pure HTML interface for managing the flow of recording/playing back a transaction. * @ctor HTMLController. * @treturn HTMLController */ function HTMLController() { this.init(); } HTMLController.prototype = new UIController(); /** * Initializes members. * @private */ HTMLController.prototype.init = function() { this.setConsole(new HTMLConsole()); this.getStopButton = function() { return document.getElementById("txnStop"); } this.getSaveButton = function() { return document.getElementById("txnSave"); } this.getRecordButton = function() { return document.getElementById("txnRecord"); } this.getRerecordButton= function() { return document.getElementById("txnRerecord"); } this.getPlayButton = function() { return document.getElementById("txnPlay"); } this.getClearButton = function() { return document.getElementById("txnClear"); } this.getData = function() { return document.getElementById("txnData"); } this.getStartingUrl = function() { return document.getElementById("txnStartingUrl"); } this.getPassword = function() { return document.getElementById("txnPassword"); } this.getLoopCountInput= function() { return document.getElementById("txnLoopCount");} this.getCurrentLoop = function() { return document.getElementById("txnCurrentLoop");} this.getTraceCheckBox = function() { return document.getElementById("txnTraceCheckBox");} this.getRecPwdCheckBox= function() { return document.getElementById("txnRecPwdCheckBox");} /** * html element where recorded txn can be found. */ this.m_input_stream = new InputStreamUI(this.getData()); /** * html element where txn will be recorded to. */ this.m_output_stream = new OutputStreamUI(this.getData()); /** * The loop index. */ this.m_loopIndex = 0; var that = this; var runModesContainer = document.getElementById("runModes"); if (runModesContainer) { var runModesInfo = { run: ["Run", false], walk: ["Walk", true], crawl:["Crawl",false], step: ["Step", false] } for (var prop in runModesInfo) { var radio = null; try { // keep this radio = document.createElement(''); } catch (e) { // keep this // delibrately added it here. } if (!radio) { // Non-IE browser; use canonical method to create named element radio = document.createElement("input"); radio.name = "txnRunModes"; } var label = document.createElement("label"); var value = runModesInfo[prop][0]; label.innerHTML = value; radio.id = "txn" + value; radio.type = "radio"; radio.onclick = function() { that.setRunMode(this.innerHTML); }; if (runModesInfo[prop][1]) { radio.defaultChecked = radio.checked = "checked"; this.setRunMode(value); } runModesContainer.appendChild(radio); runModesContainer.appendChild(label); } } } /** * makes the elem enabled. */ HTMLController.prototype.enable = function(elem) { if (elem) { elem.disabled = false; } } HTMLController.prototype.disable = function(elem) { if (elem) { elem.disabled = true; } } /** * Sets the run mode. * @tparam String value changes the speed of the play back. */ HTMLController.prototype.setRunMode = function (value) { this.m_runMode = value; var radio = document.getElementById("txn" + value); if (radio) { radio.checked = true; } if (player) { player.setRunMode(value); } } /** * Runs before recording starts. * @tparam Recorder recorder * @protected */ HTMLController.prototype.onStartRecord = function (recorder) { UIController.prototype.onStartRecord.call(this, recorder); if (recorder) { var that = this; var startingUrlInputBox = this.getStartingUrl(); if (startingUrlInputBox) { recorder.setStartingUrl(startingUrlInputBox.value); } var traceModeCheckBox = this.getTraceCheckBox(); if (traceModeCheckBox) { recorder.setTraceMode(traceModeCheckBox.checked); } var recPwdCheckBox = this.getRecPwdCheckBox(); if (recPwdCheckBox) { recorder.setSetPasswordFunc(function(val) { that.getPassword().value = val;}); recorder.setMaskPasswordMode(recPwdCheckBox.checked); } recorder.setHighlightMode(true); recorder.setDebugMode(true); recorder.addOnStopCallback(function () { that.stop(); }); } } /** * Runs before play back starts. * @tparam Player player * @protected */ HTMLController.prototype.onStartPlay = function (player) { UIController.prototype.onStartPlay.call(this, player); if (player) { var that = this; this.m_loopIndex = 0; var traceModeCheckBox = this.getTraceCheckBox(); if (traceModeCheckBox) { player.setTraceMode(traceModeCheckBox.checked); } player.setRunMode(this.m_runMode); player.addOnStopCallback (function() { that.stop(); }); player.addOnPauseCallback(function() { that.pause(); }); player.addOnLoopCallback (function() { that.loopNext(); }); player.setDebugMode(true); // player.setGetPasswordFunc(function(index) { return that.getPassword().value; }); } } /** * Runs after play back stops. * @tparam Player player * @protected */ HTMLController.prototype.onStopPlay = function(player) { var currentLoop = this.getCurrentLoop(); if (currentLoop) { currentLoop.innerHTML = ""; } UIController.prototype.onStopPlay.call(this, player); } /** * Runs when recording is saved. * @tparam Recorder recorder * @protected */ HTMLController.prototype.onStopRecord = function (recorder) { if (recorder) { var template = recorder.getRecordingTemplate(); stdout.logNode(template); var xmlWriter = new OutputStreamXMLFile("c:\\template.xml"); xmlWriter.printNode(template); xmlWriter.close(); var desc = recorder.getRecordingDesc(); if (desc) { desc = desc.split("\n"); stdout.log("description", desc); } else { stderr.log("no description generated"); } var actions = recorder.getRecordingActions(); stdout.logNode(actions); if ( this.m_output_stream) { var actionsXML = recorder.getRecordingActions().xml; actionsXML = actionsXML.replace(/>\n *
  • a string.
  • *
  • a string with additional messages as child nodes.
  • *
  • a XML node.
  • * */ function Console() { } /** * Initializer. * @private */ Console.prototype.init = function () { this._getData = function () { return document.getElementById("debugData"); } this._getFileNameInput = function () { return document.getElementById("debugFileName");} this._getLocks = function () { return document.getElementById("debugLocks"); } this.m_fileStream = null; this.m_output_stream = null; this.m_writeToFile = false; this.m_writeToUI = true; } /** * Starts writing to the external file. */ Console.prototype.open = function () { this.m_output_stream = new OutputStreamUI(this._getData()); var fileNameInput = this._getFileNameInput(); if (fileNameInput && this.m_writeToFile) { this.m_fileStream = new OutputStreamFile(fileNameInput.value); } } /** * Stops writing to the external file. */ Console.prototype.close = function () { if (this.m_fileStream) { this.m_fileStream.close(); } } /** * Writes a string and optional messages. * @tparam String str * @tparam Object[] message * @tparam String className * @tparam bool doEscape */ Console.prototype.log = function (str, message, className, doEscape) { if (this.m_output_stream && this.m_writeToUI) { this.m_output_stream.print(str, message, className, doEscape); } if (this.m_fileStream && this.m_writeToFile) { this.m_fileStream.print(str, message, className, doEscape); } } /** * Writes a XML node. * @tparam XMLNode node * @tparam String className */ Console.prototype.logNode = function (node, className) { if (this.m_writeToUI) { this.m_output_stream.printNode(node, className); } if (this.m_fileStream && this.m_writeToFile) { this.m_fileStream.printNode(node, className); } } /** * Clears the console area. */ Console.prototype.clear = function () { if (this.m_output_stream) { this.m_output_stream.clear(); } } /** * Toggles Write to file. */ Console.prototype.toggleWriteToFile = function () { this.m_writeToFile = !this.m_writeToFile; var fileNameInput = this._getFileNameInput(); if (fileNameInput) { fileNameInput.disabled = !fileNameInput.disabled; } } /** * Toggles write to UI. */ Console.prototype.toggleWriteToUI = function() { this.m_writeToUI = !this.m_writeToUI; } /** * Toggles Expand/Collapse state. */ Console.prototype.toggleExpandAll = function () { var expand = !this.m_output_stream.getExpand(); this.m_output_stream.setExpand(expand); var childNodes = this._getData().childNodes; this._expandChildren(childNodes, expand); } /** * Displays the number of locks that are preventing playback * from proceeding. */ Console.prototype.showLocks = function (numLocks) { var locks = this._getLocks(); if (locks) { locks.innerHTML = numLocks; } } /** * Expands child nodes. * @private */ Console.prototype._expandChildren = function (childNodes, expand) { for (var i = 0; i < childNodes.length; i++) { var entry = childNodes[i]; if (entry.className == "tree") { if (entry.innerHTML == "+" && expand) { entry.onclick(); } else if (entry.innerHTML == "-" && !expand) { entry.onclick(); } } this._expandChildren(entry.childNodes, expand); } } /** * @class HTMLConsole. * A console for a simple HTML page. */ function HTMLConsole() { this.init(); } HTMLConsole.prototype = new Console(); HTMLConsole.prototype.init = function() { Console.prototype.init.call(this); var debugWriteToFile = document.getElementById("debugWriteToFileCheckBox"); if (debugWriteToFile && debugWriteToFile.defaultChecked) { this.toggleWriteToFile(); } var debugWriteToUI = document.getElementById("debugWriteToUICheckBox"); if (debugWriteToUI && !debugWriteToUI.defaultChecked) { this.toggleWriteToUI(); } var filtersContainer = document.getElementById("debugFilters"); var debugFiltersInfo = { debug: ["Debug", "dbg",false], messages: ["Events", "evt",false], timing: ["Timing", "tim",true], scripts: ["Steps", "scr",false], /* traffic: ["HTTP", "htp",false], */ /* urlPostData: ["URL/PostData", "url",false],*/ stackTrace: ["Trace", "trc",true], stdout: ["Stdout", "out",true], stderr: ["Stderr", "err",true] }; for (var prop in debugFiltersInfo) { var checkBox = document.createElement('input'); var label = document.createElement("label"); label.innerHTML = debugFiltersInfo[prop][0]; label.className = debugFiltersInfo[prop][1]; checkBox.type = "checkbox"; checkBox.className = debugFiltersInfo[prop][1]; checkBox.id = prop + "CheckBox"; checkBox.onclick = new Function ("", prop + ".toggleEnableDisable()"); checkBox.title = "Add log" + label.innerHTML + "=true will enable this"; window[prop] = new Logger(this, checkBox.className); if (debugFiltersInfo[prop][2]) { checkBox.defaultChecked = checkBox.checked = "checked"; window[prop].enable(); } else { window[prop].disable(); } if (filtersContainer) { filtersContainer.appendChild(checkBox); filtersContainer.appendChild(label); } } } HTMLConsole.prototype.toggleExpandAll = function () { Console.prototype.toggleExpandAll.call(this); var btn = document.getElementById("debugToggleExpandAll"); if (btn) { if (btn.value == "Expand All") { btn.value = "Collapse All"; } else { btn.value = "Expand All"; } } } function PBReport() { } PBReport.SUCCESS = 0; PBReport.HTTP_ERROR = 1; PBReport.TXN_FAIL_STR_FOUND = 2; PBReport.TXN_SUC_STR_NOT_FOUND = 3; PBReport.STEP_FAIL_STR_FOUND = 4; PBReport.STEP_SUC_STR_NOT_FOUND = 5; PBReport.BROWSER_ERROR = 6; PBReport.DOM_ERROR = 7; PBReport.USER_ACTION_PB_MODE = 1; PBReport.HTTP_REDIR_PB_MODE = 2; PBReport.META_REDIR_PB_MODE = 3; PBReport.SCRIPT_REDIR_PB_MODE = 4; PBReport.ONLOAD_SUBMIT_PB_MODE = 5; PBReport.FRAME_PB_MODE = 6; PBReport.UNDEF_PB_MODE = 7; PBReport.prototype.process = function(node) { try { var buf = new StringBuffer(); // the DHTML playback always has an extra // step at the end. var steps = node.selectNodes("step"); var stepsLength = steps.length; this.append(buf, "vbsteps", stepsLength - 1); // steps must be processed first. var stepBuf = new StringBuffer(); for (var i = 0; i < stepsLength - 1; i++) { var step = steps[i]; var nextStep = steps[i+1]; var result = this.processStep(step, nextStep); if (result != "") { stepBuf.append("&", result); } } if (this.m_step_failed) { // a step failed this.append(buf, "txnstatus", PBReport.HTTP_ERROR); } else if (stepsLength > 0) { var result = this.processValidations(steps[stepsLength-1], null); var succNotFound = result[Constants.SUC_NOT_FOUND]; var failed = result[Constants.FAIL_FOUND]; if (failed && failed.length > 0) { // error string check failure this.append(buf, "txnstatus", PBReport.TXN_FAIL_STR_FOUND); this.append(buf, "errd", failed.join(",")); } else if (succNotFound && succNotFound.length > 0) { // success string check failure this.append(buf, "txnstatus", PBReport.TXN_SUC_STR_NOT_FOUND); this.append(buf, "errd", succNotFound.join(",")); } else { // success, status = 0 this.append(buf, "txnstatus", PBReport.SUCCESS); } } buf.append(stepBuf.toString()); return buf.toString(); } catch (e) { this._handleException(arguments.callee, e); } } /** * The node is the step event. */ PBReport.prototype.processStep = function(step, nextStep) { try { var step_id = step.getAttribute("stepId"); var buf = new StringBuffer(); var result = this.processValidations(step, nextStep); var succNotFound = result[Constants.SUC_NOT_FOUND]; var failed = result[Constants.FAIL_FOUND]; var bErrors = result[Constants.BROWSER_ERROR]; var dErrors = result[Constants.DOM_ERROR]; if (failed && failed.length > 0) { this.append1(buf, "stepst", step_id, PBReport.STEP_FAIL_STR_FOUND); this.append1(buf, "steperrd", step_id, failed.join(",")); this.m_step_failed = true; } else if (succNotFound && succNotFound.length > 0) { // error description is needed, if some strings are not found this.append1(buf, "stepst", step_id, PBReport.STEP_SUC_STR_NOT_FOUND); this.append1(buf, "steperrd", step_id, succNotFound.join(",")); this.m_step_failed = true; } else if (bErrors && bErrors.length > 0 ) { var bError = parseInt(bErrors[0]); if (bError < 1000 && bError > 0) { this.append1(buf, "stepst", step_id, PBReport.HTTP_ERROR); this.append1(buf, "steperrd", step_id, bError[0]); // error description expecting only one status this.m_step_failed = true; } else { this.append1(buf, "stepst", step_id, PBReport.BROWSER_ERROR); this.append1(buf, "steperrd", step_id, bErrors[0]); // error description expecting only one status this.m_step_failed = true; } } else if (dErrors && dErrors.length > 0) { this.append1(buf, "stepst", step_id, PBReport.DOM_ERROR); this.append1(buf, "steperrd", step_id, dErrors.join(",")); } else { this.append1(buf, "stepst", step_id, PBReport.SUCCESS); } var time = step.getAttribute("time"); this.append1(buf, "stepstart", step_id, time); return buf.toString(); } catch (e) { this._handleException(arguments.callee, e); } } PBReport.prototype.processValidations = function(step, nextStep) { try { var succNotFound = []; var failed = []; var bErrors = []; var dErrors = []; var nextNode = step.nextSibling; while(nextNode != nextStep) { if (nextNode.tagName == Constants.FAIL_FOUND) { failed.push(nextNode.getAttribute("value")); } else if (nextNode.tagName == Constants.SUC_NOT_FOUND) { succNotFound.push(nextNode.getAttribute("value")); } else if (nextNode.tagName == Constants.BROWSER_ERROR) { bErrors.push(nextNode.getAttribute("statusCode")); } else if (nextNode.tagName == Constants.DOM_ERROR) { dErrors.push(nextNode.getAttribute("message")); } nextNode = nextNode.nextSibling; } var result = {}; result[Constants.SUC_NOT_FOUND] = succNotFound; result[Constants.FAIL_FOUND] = failed; result[Constants.BROWSER_ERROR] = bErrors; result[Constants.DOM_ERROR] = dErrors; return result; } catch (e) { this._handleException(arguments.callee, e); } } /** * Appends name=value */ PBReport.prototype.append = function(buf, name, value) { if (!buf.isEmpty()) { buf.append("&"); } buf.append(name, "=", value); } /** * Appends name[index]=value */ PBReport.prototype.append1 = function(buf, name, index, value) { if (!buf.isEmpty()) { buf.append("&"); } buf.append(name, index, "=", value); } /** * Appends a hashtable of values. */ PBReport.prototype.appendMap = function(buf, map) { var appendAmp = !buf.isEmpty(); for (var prop in map) { var value = map[prop]; if (value) { if (appendAmp) { buf.append("&"); } buf.append(prop, "=", map[prop]); appendAmp = true; } } } /** * Exception handling. * @private */ PBReport.prototype._handleException = function (func, e) { handleException(PBReport, func, e); } PBReport.prototype.getHTTPErrorMessage = function(statusCode) { switch (statusCode) { case 400: return "400: The request could not be processed by the server due to invalid syntax."; case 401: return "401: The requested resource requires user authentication."; case 402: return "402: Not currently implemented in the HTTP protocol."; case 403: return "403: The server understood the request, but is refusing to fulfill it."; case 404: return "404: The server has not found anything matching the requested URI (Uniform Resource Identifier)."; case 405: return "405: The HTTP verb used is not allowed."; case 406: return "406: No responses acceptable to the client were found."; case 407: return "407: Proxy authentication required."; case 408: return "408: The server timed out waiting for the request."; case 409: return "409: The request could not be completed due to a conflict with the current state of the resource. The user should resubmit with more information."; case 410: return "410: The requested resource is no longer available at the server, and no forwarding address is known."; case 411: return "411: The server refuses to accept the request without a defined content length."; case 412: return "412: The precondition given in one or more of the request header fields evaluated to false when it was tested on the server."; case 413: return "413: The server is refusing to process a request because the request entity is larger than the server is willing or able to process."; case 414: return "414: The server is refusing to service the request because the request URI (Uniform Resource Identifier) is longer than the server is willing to interpret."; case 415: return "415: The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method."; case 449: return "449: The request should be retried after doing the appropriate action."; case 500: return "500: The server encountered an unexpected condition that prevented it from fulfilling the request."; case 501: return "501: The server does not support the functionality required to fulfill the request."; case 502: return "502: The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request."; case 503: return "503: The service is temporarily overloaded."; case 504: return "504: The request was timed out waiting for a gateway."; case 505: return "505: The server does not support, or refuses to support, the HTTP protocol version that was used in the request message."; default: return "Unknown Error"; } } PBReport.prototype.getNavErrorMessage = function(navError) { switch (navError) { case 1: return "The URL is not valid."; case 2: return "No Internet session was established."; case 3: return "The attempt to connect to the Internet has failed."; case 4: return "The server or proxy was not found."; case 5: return " The object was not found."; case 6: return "An Internet connection was established, but the data cannot be retrieved."; case 7: return "The download has failed (the connection was interrupted)."; case 8: return "Authentication is needed to access the object."; case 9: return "The object is not in one of the acceptable Multipurpose Internet Mail Extensions (MIME) types."; case 10: return "The Internet connection has timed out."; case 11: return "The request was invalid."; case 12: return "The protocol is not known and no pluggable protocols have been entered that match."; case 13: return "A security problem was encountered. "; case 14: return "The object could not be loaded. "; case 15: return "Unable to create an instance of the object. "; case 16: return "The redirect request failed. "; case 17: return "The request is being redirected to a directory. "; case 18: return "The requested resource could not be locked."; case 19: return "Reissue request with extended binding. "; case 20: return "Binding was terminated."; case 21: return "The component download was declined by the user. "; case 22: return "The binding has already been completed and the result has been dispatched, so your abort call has been canceled."; case 23: return "The exact version requested by a component download cannot be found. "; default: return "Unknown Error"; } } PBReport.prototype.getNavErrorCode = function(win_err) { switch (win_err) { case -2146697214: // INET_E_INVALID_URL error_code = 1; break; case -2146697213: // INET_E_NO_SESSION error_code = 2; break; case -2146697212: // INET_E_CANNOT_CONNECT error_code = 3; break; case -2146697211: // INET_E_RESOURCE_NOT_FOUND error_code = 4; break; case -2146697210: // INET_E_OBJECT_NOT_FOUND error_code = 5; break; case -2146697209: // INET_E_DATA_NOT_AVAILABLE error_code = 6; break; case -2146697208: // INET_E_DOWNLOAD_FAILURE error_code = 7; break; case -2146697207: // INET_E_AUTHENTICATION_REQUIRED error_code = 8; break; case -2146697206: // INET_E_NO_VALID_MEDIA error_code = 9; break; case -2146697205: // INET_E_CONNECTION_TIMEOUT error_code = 10; break; case -2146697204: // INET_E_INVALID_REQUEST error_code = 11; break; case -2146697203: // INET_E_UNKNOWN_PROTOCOL error_code = 12; break; case -2146697202: // INET_E_SECURITY_PROBLEM error_code = 13; break; case -2146697201: // INET_E_CANNOT_LOAD_DATA error_code = 14; break; case -2146697200: // INET_E_CANNOT_INSTANTIATE_OBJECT error_code = 15; break; case -2146697196: // INET_E_REDIRECT_FAILED error_code = 16; break; case -2146697195: // INET_E_REDIRECT_TO_DIR error_code = 17; break; case -2146697194: // INET_E_CANNOT_LOCK_REQUEST error_code = 18; break; case -2146697193: // INET_E_USE_EXTEND_BINDING error_code = 19; break; case -2146697192: // INET_E_TERMINATED_BIND error_code = 20; break; case -2146697960: // INET_E_CODE_DOWNLOAD_DECLINED error_code = 21; break; case -2146696704: // INET_E_RESULT_DISPATCHED error_code = 22; break; case -2146696448: // INET_E_CANNOT_REPLACE_SFP_FILE error_code = 23; break; default: // Unknown Error error_code = 1000; } return error_code; } function XML2Func() { } XML2Func.WAIT = "// Wait"; XML2Func.SLEEP= "// Sleep"; XML2Func.ACTION="// Action"; // translate a string containing javascript XML2Func.prototype.process = function (node) { var buf = new StringBuffer(); var nodes = node.selectNodes("action"); for (var i = 0; i < nodes.length; i++) { buf.append(this.processAction(nodes[i])); } return buf.toString(); } XML2Func.prototype.generateFunc = function (func, params) { return [func, "(", params.join(", "), ");"].join(""); } XML2Func.prototype.generateCall = function (type, func, params) { return [type, this.generateFunc(func, params), ""].join("\r\n"); } XML2Func.prototype.generateAction = function(func, params, node) { return [XML2Func.ACTION, this.getBrowser(node), this.generateFunc(func, params), ""].join("\r\n"); } XML2Func.prototype.generateElemAction = function(func, params, node) { return [XML2Func.ACTION, this.getBrowser(node), this.getElement(node), this.generateFunc(func, params), ""].join("\r\n"); } XML2Func.prototype.processAction = function (node) { var type = node.getAttribute("type"); if (false) { // ---------------------------- wait actions } else if (type == Constants.waitForPageToLoad) { return this.generateCall(XML2Func.WAIT, "player.waitFor", ["'load'", this.getWindow(node), this.getAttr(node, "frame")]); } else if (type == Constants.waitForPopUp) { return this.generateCall(XML2Func.WAIT, "player.waitFor", ["'open'", this.getWindow(node)]); } else if (type == Constants.waitForPopupClose) { return this.generateCall(XML2Func.WAIT, "player.waitFor", ["'close'",this.getWindow(node)]); // ---------------------------- sleep action below } else if (type == Constants.sleep) { return this.generateCall(XML2Func.SLEEP, "player.sleep", [node.getAttribute("timeout")]); // ---------------------------- implicit actions below } else if (type == "add" && node.getAttribute("for") == "success") { return this.generateCall(XML2Func.ACTION, "pf.addSuccessString", [this.getAttr(node, "value"), this.getAttr(node, "mode"), this.getAttr(node, "level")]); } else if (type == "add" && node.getAttribute("for") == "failure") { return this.generateCall(XML2Func.ACTION, "pf.addFailureString", [this.getAttr(node, "value"), this.getAttr(node, "mode"), this.getAttr(node, "level")]); } else if (type == "step") { return this.generateCall(XML2Func.ACTION, "pf.markStep", [this.getAttr(node, "name"), this.getAttr(node, "stepId")]); } else if (type == "addStep") { return this.generateCall(XML2Func.ACTION, "pf.addStep2Group", [this.getAttr(node, "name"), this.getAttr(node, "group")]); // ---------------------------- explicit action below } else if (type == "open") { return this.generateAction("pf.navigate2", ["br", this.getAttr(node, "newValue")], node); } else if (type == Constants.select || type == Constants.type) { return this.generateElemAction("pf." + type, ["elem", this.getAttr(node, "newValue")], node); } else if (type == Constants.check || type == Constants.uncheck) { return this.generateElemAction("pf." + type, ["elem"], node); } else if (type == Constants.click || type == Constants.mouseMove || type == Constants.mouseUp || type == Constants.mouseDown) { return this.generateElemAction("pf." + type, ["elem", this.getAttr(node,"button"), this.getAttr(node,"altKey"), this.getAttr(node,"ctrlKey"), this.getAttr(node,"shiftKey")], node); } else if (type == Constants.keyUp || type == Constants.keyDown || type == Constants.keyPress) { return this.generateElemAction("pf." + type, ["elem", this.getAttr(node,"newValue"), this.getAttr(node,"altKey"), this.getAttr(node,"ctrlKey")], node); } else if (type == Constants.close || type == Constants.goBack || type == Constants.refresh) { return this.generateAction("pf." + type, ["br"], node); } } XML2Func.prototype.getAttr = function(node, name) { return Util.quotify(node.getAttribute(name)); } XML2Func.prototype.getBrowser = function (node) { return ["var br = player.getBrowserAt(" + this.getWindow(node) + ");", "if (!br) { player.tryContinue(); return; }", ""].join("\r\n"); } XML2Func.prototype.getElement = function (node) { return ["var elem = " + this.getElementFunc(node), "if (!elem) { player.tryContinue(); return; }", ""].join("\r\n"); } XML2Func.prototype.getElementFunc = function (node) { var tagName = node.getAttribute("tagName"); var text = node.getAttribute("text"); var title = node.getAttribute("title"); var id = node.getAttribute("id"); var value = node.getAttribute("value"); var name = node.getAttribute("name"); var src = node.getAttribute("src"); var alt = node.getAttribute("alt"); if (text != null && tagName != null) { return ["pf.getElementByTextAndTag(", this.getFrame(node) + ",", Util.quotify(text) + ",", Util.quotify(tagName) + ",", this.getIndex(node) + ")"].join("\r\n"); } else if (name != null) { return ["pf.getElementByName(", this.getFrame(node) + ",", Util.quotify(name) + ",", this.getIndex(node) + ")"].join("\r\n"); } else if (text != null) { return ["pf.getElementByText(", this.getFrame(node) + ",", Util.quotify(text) + ",", this.getIndex(node) + ")"].join("\r\n"); } else if (title != null && tagName != null) { return ["pf.getElementByTitleAndTag(", this.getFrame(node) + ",", Util.quotify(title) + ",", Util.quotify(tagName) + ",", this.getIndex(node) + ")"].join("\r\n"); } else if (alt != null && tagName != null) { return ["pf.getElementByAltAndTag(", this.getFrame(node) + ",", Util.quotify(alt) + ",", Util.quotify(tagName) + ",", this.getIndex(node) + ")"].join("\r\n"); } else if (id != null) { return ["pf.getElementById(", this.getFrame(node) + ",", Util.quotify(id) + ")"].join("\r\n"); } else if (value != null && tagName == "input") { return ["pf.getButtonByValue(", this.getFrame(node) + ",", Util.quotify(value) + ",", this.getIndex(node) + ")"].join("\r\n"); } else if (src != null && tagName == "img") { return ["pf.getImageBySrc(", this.getFrame(node) + ",", Util.quotify(src) + ",", this.getIndex(node) + ",", this.getRegExp(node)+ ")"].join("\r\n"); } else if (node.getAttribute("html") != null) { var html = node.getAttribute("html"); return ["pf.getElementByInnerHTML(", this.getFrame(node) + ",", Util.quotify(tagName) + ",", Util.quotify(html) + ",", this.getIndex(node) + ")"].join("\r\n"); } else if (node.getAttribute("onclick") != null && tagName != null) { var onclick = node.getAttribute("onclick"); return ["pf.getElementByOnclickAndTag(", this.getFrame(node) + ",", Util.quotify(onclick) + ",", Util.quotify(tagName) + ",", this.getIndex(node) + ")"].join("\r\n"); } else { return ["pf.getElementByIndex(", this.getFrame(node) + ",", this.getIndex(node) + ")"].join("\r\n"); } } XML2Func.prototype.getFrame = function (node) { var frame = node.getAttribute("frame"); if (frame != null) { return "pf.getFrame(br, " + Util.quotify(frame) + ")"; } else { return "br.document"; } } XML2Func.prototype.getIndex = function (node) { var index = node.getAttribute("index"); if (index != null) { return index; } else { return "0"; } } XML2Func.prototype.getWindow = function (node) { var index = node.getAttribute("windowIndex"); if (index != null) { return index; } else { return "0"; } } XML2Func.prototype.getRegExp = function (node) { var reg = node.getAttribute("regexp"); if (reg != null) { return Util.quotify(reg); } else { return "false"; } } /** * @class UIXConsole. * A console for a UIX page. */ function UIXConsole() { this.init(); } UIXConsole.prototype = new Console(); UIXConsole.prototype.init = function() { Console.prototype.init.call(this); var expandButton = document.getElementById("debugExpand"); var collapeButton = document.getElementById("debugCollapse"); show(expandButton); hide(collapeButton); var debugWriteToFile = document.getElementById("debugWriteToFileCheckBox"); if (debugWriteToFile && debugWriteToFile.defaultChecked) { this.toggleWriteToFile(); } var debugFiltersInfo = { debug: ["Debug", "dbg",true], messages: ["Events", "evt",false], timing: ["Timing", "tim",true], scripts: ["Steps", "scr",false], stdout: ["Stdout", "out",true], stderr: ["Stderr", "err",true], stackTrace: ["Trace", "trc",true] }; for (var prop in debugFiltersInfo) { var filter = debugFiltersInfo[prop]; window[prop] = new Logger(this, filter[1]); if (filter[2]) { window[prop].enable(); } else { window[prop].disable(); } } var errorURLElem = document.getElementsByName("errorURL"); if (errorURLElem && errorURLElem[0]) { this.m_error_stream = new OutputStreamHTTP(errorURLElem[0].value); } var warnURLElem = document.getElementsByName("warnURL"); if (warnURLElem && warnURLElem[0]) { this.m_warn_stream = new OutputStreamHTTP(warnURLElem[0].value); } var debugURLElem = document.getElementsByName("debugURL"); if (debugURLElem && debugURLElem[0]) { this.m_debug_stream = new OutputStreamHTTP(debugURLElem[0].value); } } /** * Writes a string and optional messages. * @tparam String str * @tparam Object[] message * @tparam String className * @tparam bool doEscape */ UIXConsole.prototype.log = function (str, message, className, doEscape) { if (this.m_error_stream && className == "err") { // this is commented because, in record, play // we don't want errors to be show in the UI. // Console.prototype.log.call(this, str, message, className, doEscape); this.m_error_stream.print(str, message, className, doEscape); } if (this.m_warn_stream && className == "out") { Console.prototype.log.call(this, str, message, className, doEscape); this.m_debug_stream.print(str, message, className, doEscape); } if (this.m_debug_stream && className == "dbg") { this.m_debug_stream.print(str, message, className, doEscape); } } /** * Writes a XML node. * @tparam XMLNode node * @tparam String className */ UIXConsole.prototype.logNode = function (node, className) { if (this.m_error_stream && className == "err") { Console.prototype.logNode.call(this, node, className); this.m_error_stream.printNode(node); } if (this.m_warn_stream && className == "out") { this.m_warn_stream.printNode(node); } if (this.m_debug_stream && className == "dbg") { this.m_debug_stream.printNode(node); } } UIXConsole.prototype.toggleExpandAll = function() { Console.prototype.toggleExpandAll.call(this); var expandButton = document.getElementById("debugExpand"); var collapeButton = document.getElementById("debugCollapse"); toggleVisible(expandButton, collapeButton); } UIXConsole.prototype.open = function() { Console.prototype.open.call(this); if (this.m_error_stream) { this.m_error_stream.open(); } if (this.m_warn_stream) { this.m_warn_stream.open(); } if (this.m_debug_stream) { this.m_debug_stream.open(); } } UIXConsole.prototype.close = function() { if (this.m_error_stream) { this.m_error_stream.close(); } if (this.m_warn_stream) { this.m_warn_stream.close(); } if (this.m_debug_stream) { this.m_debug_stream.close(); } Console.prototype.close.call(this); }