Ignore:
Timestamp:
10/26/12 09:17:48 (12 years ago)
Author:
pharms
Message:
  • added more events to handle
  • added support for differentiating input elements
  • added support for storing selected values in input elements
  • prohibited recording of data entries in password fields
  • added support for storing horizontal scroll position
  • added reusing of previous messages for selected values and scrolling to prevent the sending of too many messages
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/autoquest-htmlmonitor/src/main/js/autoquest-htmlmonitor.js

    r937 r944  
    5656 */ 
    5757var autoquestActionConfig = [ 
    58     { "tag": "body", "actions": [ "onunload", "onscroll" ] }, 
    59     { "tag": "a", "actions": [ "onclick", "ondblclick", "onfocus" ] }, 
    60     { "tag": "input", "actions": [ "onclick", "ondblclick", "onfocus" ] } 
     58    { "tag": "a", "actions": [ "onclick", 
     59                               "onfocus" ] }, 
     60    //{ "tag": "abbr", "actions": [  ] }, 
     61    //{ "tag": "address", "actions": [  ] }, 
     62    //{ "tag": "applet", "actions": [  ] }, 
     63    { "tag": "area", "actions": [ "onclick", 
     64                                  "onfocus" ] }, 
     65    //{ "tag": "article", "actions": [  ] }, 
     66    //{ "tag": "aside", "actions": [  ] }, 
     67    { "tag": "audio", "actions": [ "onplaying", 
     68                                   "onpause", 
     69                                   "ontimeupdate" ] }, 
     70    { "tag": "b", "actions": [ "onclick" ] }, 
     71    //{ "tag": "bdi", "actions": [  ] }, 
     72    //{ "tag": "bdo", "actions": [  ] }, 
     73    //{ "tag": "blockquote", "actions": [  ] }, 
     74    { "tag": "body", "actions": [ "onbeforeunload", 
     75                                  "onunload", 
     76                                  //"onerror", 
     77                                  "onscroll", 
     78                                  "onpagehide", 
     79                                  "onpageshow", 
     80                                  "onundo" ] }, 
     81    { "tag": "button", "actions": [ "onclick", 
     82                                    "onfocus" ] }, 
     83    { "tag": "canvas", "actions": [ "onclick" ] }, 
     84    //{ "tag": "caption", "actions": [  ] }, 
     85    { "tag": "cite", "actions": [ "onclick" ] }, 
     86    { "tag": "code", "actions": [ "onclick" ] }, 
     87    //{ "tag": "col", "actions": [  ] }, 
     88    //{ "tag": "colgroup", "actions": [  ] }, 
     89    { "tag": "command", "actions": [ "onclick", 
     90                                     "onfocus" ] }, 
     91    //{ "tag": "datalist", "actions": [  ] }, 
     92    { "tag": "dd", "actions": [ "onclick" ] }, 
     93    { "tag": "del", "actions": [ "onclick" ] }, 
     94    //{ "tag": "details", "actions": [  ] }, 
     95    { "tag": "dfn", "actions": [ "onclick" ] }, 
     96    { "tag": "div", "actions": [ "onclick" ] }, 
     97    //{ "tag": "dl", "actions": [  ] }, 
     98    { "tag": "dt", "actions": [ "onclick" ] }, 
     99    { "tag": "em", "actions": [ "onclick" ] }, 
     100    { "tag": "embed", "actions": [ "onclick" ] }, 
     101    //{ "tag": "fieldset", "actions": [  ] }, 
     102    //{ "tag": "figcaption", "actions": [  ] }, 
     103    //{ "tag": "figure", "actions": [  ] }, 
     104    //{ "tag": "footer", "actions": [  ] }, 
     105    //{ "tag": "form", "actions": [  ] }, 
     106    //{ "tag": "header", "actions": [  ] }, 
     107    //{ "tag": "hgroup", "actions": [  ] }, 
     108    { "tag": "h1", "actions": [ "onclick" ] }, 
     109    { "tag": "h2", "actions": [ "onclick" ] }, 
     110    { "tag": "h3", "actions": [ "onclick" ] }, 
     111    { "tag": "h4", "actions": [ "onclick" ] }, 
     112    { "tag": "h5", "actions": [ "onclick" ] }, 
     113    { "tag": "h6", "actions": [ "onclick" ] }, 
     114    //{ "tag": "hr", "actions": [  ] }, 
     115    { "tag": "i", "actions": [ "onclick" ] }, 
     116    //{ "tag": "iframe", "actions": [  ] }, 
     117    { "tag": "img", "actions": [ "onclick" ] }, 
     118    { "tag": "input_text", "actions": [ "onchange", 
     119                                        "onfocus" ] }, 
     120    { "tag": "input", "actions": [ "onchange", 
     121                                   "onfocus" ] }, 
     122    { "tag": "ins", "actions": [ "onclick" ] }, 
     123    { "tag": "kbd", "actions": [ "onclick" ] }, 
     124    { "tag": "keygen", "actions": [ "onchange", 
     125                                    "onfocus" ] }, 
     126    //{ "tag": "label", "actions": [  ] }, 
     127    //{ "tag": "legend", "actions": [  ] }, 
     128    { "tag": "li", "actions": [ "onclick" ] }, 
     129    //{ "tag": "map", "actions": [  ] }, 
     130    { "tag": "mark", "actions": [ "onclick" ] }, 
     131    { "tag": "menu", "actions": [ "onclick" ] }, 
     132    { "tag": "meter", "actions": [ "onclick" ] }, 
     133    //{ "tag": "nav", "actions": [  ] }, 
     134    //{ "tag": "noscript", "actions": [  ] }, 
     135    { "tag": "object", "actions": [ "onclick" ] }, 
     136    //{ "tag": "ol", "actions": [  ] }, 
     137    //{ "tag": "optgroup", "actions": [  ] }, 
     138    //{ "tag": "option", "actions": [  ] }, 
     139    { "tag": "output", "actions": [ "onclick" ] }, 
     140    { "tag": "p", "actions": [ "onclick" ] }, 
     141    //{ "tag": "param", "actions": [  ] }, 
     142    //{ "tag": "pre", "actions": [  ] }, 
     143    { "tag": "progress", "actions": [ "onclick" ] }, 
     144    { "tag": "q", "actions": [ "onclick" ] }, 
     145    //{ "tag": "rp", "actions": [  ] }, 
     146    //{ "tag": "rt", "actions": [  ] }, 
     147    //{ "tag": "ruby", "actions": [  ] }, 
     148    { "tag": "s", "actions": [ "onclick" ] }, 
     149    { "tag": "samp", "actions": [ "onclick" ] }, 
     150    //{ "tag": "section", "actions": [  ] }, 
     151    { "tag": "select", "actions": [ "onchange", 
     152                                    "onfocus" ] }, 
     153    { "tag": "small", "actions": [ "onclick" ] }, 
     154    //{ "tag": "source", "actions": [  ] }, 
     155    { "tag": "span", "actions": [ "onclick" ] }, 
     156    { "tag": "strong", "actions": [ "onclick" ] }, 
     157    //{ "tag": "sub", "actions": [  ] }, 
     158    //{ "tag": "summary", "actions": [  ] }, 
     159    //{ "tag": "sup", "actions": [  ] }, 
     160    //{ "tag": "table", "actions": [  ] }, 
     161    //{ "tag": "tbody", "actions": [  ] }, 
     162    { "tag": "td", "actions": [ "onclick" ] }, 
     163    { "tag": "textarea", "actions": [ "onchange", 
     164                                      "onfocus" ] }, 
     165    //{ "tag": "tfoot", "actions": [  ] }, 
     166    { "tag": "th", "actions": [ "onclick" ] }, 
     167    //{ "tag": "thead", "actions": [  ] }, 
     168    { "tag": "time", "actions": [ "onclick" ] }, 
     169    //{ "tag": "tr", "actions": [  ] }, 
     170    //{ "tag": "track", "actions": [  ] }, 
     171    { "tag": "u", "actions": [ "onclick" ] }, 
     172    //{ "tag": "ul", "actions": [  ] }, 
     173    { "tag": "var", "actions": [ "onclick" ] }, 
     174    { "tag": "video", "actions": [ "onplaying", 
     175                                   "onpause", 
     176                                   "ontimeupdate" ] }, 
     177    //{ "tag": "wbr", "actions": [  ] }, 
    61178]; 
    62179 
     
    139256 */ 
    140257function addEventHandlingAttributes(node, parentPath) { 
    141     var nodePath = getNodePath(node, parentPath); 
     258    var nodePath; 
    142259    var i; 
    143260    var k; 
     
    145262     
    146263    if (node.nodeType === Node.ELEMENT_NODE) { 
     264        nodePath = getNodePath(node, parentPath); 
     265         
    147266        for (i = 0; i < autoquestActionConfig.length; i++) { 
    148             if (node.tagName.toLowerCase() === autoquestActionConfig[i].tag.toLowerCase()) { 
     267            if (getTagName(node) === autoquestActionConfig[i].tag.toLowerCase()) { 
    149268                for (k = 0; k < autoquestActionConfig[i].actions.length; k++) { 
    150                     value = "handleEvent('" + autoquestActionConfig[i].actions[k] + "', '" + 
    151                         nodePath + "', event);"; 
     269                    value = "handleEvent(this, '" + 
     270                                         autoquestActionConfig[i].actions[k] + "', '" + 
     271                                         nodePath + "', event);"; 
    152272 
    153273                    if (!node.getAttribute(autoquestActionConfig[i].actions[k])) { 
     
    164284            } 
    165285        } 
    166     } 
    167      
    168     for (i = 0; i < node.childNodes.length; i++) { 
    169         addEventHandlingAttributes(node.childNodes[i], nodePath); 
     286         
     287        for (i = 0; i < node.childNodes.length; i++) { 
     288            addEventHandlingAttributes(node.childNodes[i], nodePath); 
     289        } 
    170290    } 
    171291} 
     
    174294 * generates a path through the DOM-structure of the HTML-site depending on a node and the path 
    175295 * to its parent node. The result is the parent path plus a path element for the provided node. 
    176  * If the node has a tag name, this is the first part of the path element generated for the node. 
    177  * If the node has an id, it becomes the second part of the path element. If the node does not have 
    178  * an id, the method calculates the index of the node within all children of the same type within 
    179  * the parent node. This index becomes then the second part of the path element generated for the 
    180  * node.  
     296 * The first part of the path element generated for the node is the tag name returned by 
     297 * {@link #getTagName(node)}. If the node has an id, it becomes the second part of the path 
     298 * element. If the node does not have an id, the method calculates the index of the node within 
     299 * all children of the same type within the parent node. This index becomes then the second part 
     300 * of the path element generated for the node.  
    181301 *  
    182302 * @param node       the node of the DOM structure for which the path shall be created 
     
    191311    var i = 0; 
    192312     
    193     if (node.nodeType === Node.ELEMENT_NODE) { 
    194         nodePath += node.tagName.toLowerCase(); 
    195          
    196         if ("input" === node.tagName.toLowerCase()) { 
    197             if (node.type && (node.type !== "")) { 
    198                 nodePath += "_" + node.type; 
    199             } 
    200             else { 
    201                 nodePath += "_text"; 
    202             } 
    203         } 
    204     } 
    205     else { 
    206         nodePath += "undefined"; 
    207     } 
     313    nodePath += getTagName(node); 
    208314     
    209315    if ((node.id) && (node.id !== "")) { 
     
    211317    } 
    212318    else { 
    213          
    214319        if (node.parentNode) { 
    215320            for (i = 0; i < node.parentNode.childNodes.length; i++) { 
     
    239344 * of the nodes. These attributes are generated by the 
    240345 * {@link #addEventHandlingAttributes(node,parentPath)} function. It creates a new Event object and 
    241  * add it to the list of <code>autoquestRecordedEvents</code>. If this list achieves the maximum 
     346 * adds it to the list of <code>autoquestRecordedEvents</code>. If this list achieves the maximum 
    242347 * <code>autoquestPackageSize</code> the events in the list are sent to the server asynchronously 
    243348 * through calling {@link #sendRequest()}. 
    244349 *  
     350 * @param node      the node that fired the event 
    245351 * @param eventName the name of the event, e.g. onscroll 
    246352 * @param nodePath  the path to the node in the HTML DOM on which the event occurred 
    247353 * @param event     the HTML event that occured 
    248354 */ 
    249 function handleEvent(eventName, nodePath, event) { 
    250     log("handling event " + eventName); 
    251      
     355function handleEvent(node, eventName, nodePath, event) { 
     356    var eventType; 
     357    var eventObj = null; 
     358    var tagName; 
     359 
    252360    if (!autoquestDestination) { 
    253361        // do nothing if we have no destination to send data to 
     
    255363    } 
    256364     
    257     var eventType = eventName.toLowerCase(); 
    258      
    259     var eventObj = autoquestRecordedEvents.pop(); 
    260      
    261     // reuse previous on scroll events to prevent too many events 
    262     if ((eventName !== "onscroll") || (!eventObj) || (eventObj.type !== "onscroll")) { 
    263         if (eventObj) { 
    264             autoquestRecordedEvents.push(eventObj); 
    265         } 
     365    log("handling event " + eventName + " on " + node); 
     366     
     367    eventType = eventName.toLowerCase(); 
     368 
     369    if (autoquestRecordedEvents.length > 0) { 
     370        eventObj = autoquestRecordedEvents[autoquestRecordedEvents.length - 1]; 
     371 
     372        // check if an event showed up several times either for the same or for a parent GUI element 
     373        if ((eventObj.type === eventName) && (eventObj.nodePath.indexOf(nodePath) === 0)) { 
     374            // the event is of the same type. 
     375            if (eventObj.nodePath.length > nodePath.length) { 
     376                // the same event showed up for the parent GUI element. This must not be handled. 
     377                // So ignore it 
     378                log("discarding event " + eventName + " on " + node + 
     379                    " as it is already handled by a child"); 
     380                return; 
     381            } 
     382            else if (eventName !== "onscroll") { 
     383                // we have the same event on the same element. If it is an onscroll, we should 
     384                // reuse it. But it is not an onscroll. So we ignore the existing event. 
     385                eventObj = null; 
     386            } 
     387        } 
     388        else { 
     389            // the event is not of an equal type as the previous one. So we will not reuse it 
     390            eventObj = null; 
     391        } 
     392    } 
     393     
     394    if (!eventObj) { 
     395        // create a new event and add it to the list 
    266396        eventObj = new Event(eventType, nodePath); 
    267     } 
    268      
     397        log("storing event " + eventName); 
     398        autoquestRecordedEvents.push(eventObj); 
     399    } 
     400 
     401    // now add further event parameters 
    269402    if ((eventType === "onclick") || (eventType === "ondblclick")) { 
    270403        eventObj.setClickCoordinates(getEventCoordinates(event)); 
    271404    } 
    272405 
    273     if ((eventType === "onkeypress") || (eventType === "onkeydown") || (eventType === "onkeyup")) { 
    274         eventObj.setKey(event.keyCode); 
     406    tagName = getTagName(node); 
     407     
     408    if ("input_password" !== tagName) { 
     409        if ((eventType === "onkeypress") || 
     410            (eventType === "onkeydown") || 
     411            (eventType === "onkeyup")) 
     412        { 
     413            eventObj.setKey(event.keyCode); 
     414        } 
     415        else if (eventType == "onchange") { 
     416            if ((tagName.indexOf("input") === 0) || 
     417                (tagName === "textarea") || 
     418                (tagName === "keygen")) 
     419            { 
     420                eventObj.setSelectedValue(node.value); 
     421            } 
     422            else if (tagName === "select") { 
     423                eventObj.setSelectedValue(node.options.item(node.options.selectedIndex)); 
     424            } 
     425        } 
    275426    } 
    276427     
    277428    if (eventType === "onscroll") { 
    278         if (window.pageYOffset) { 
    279             eventObj.setScrollPosition(window.pageYOffset); 
    280         } 
    281     } 
    282  
    283     log("storing event " + eventName); 
    284     autoquestRecordedEvents.push(eventObj); 
     429        eventObj.setScrollPosition(getScrollCoordinates(node)); 
     430    } 
    285431 
    286432    if (autoquestRecordedEvents.length >= autoquestPackageSize) { 
     
    295441} 
    296442 
     443/** 
     444 * determines a tag name of a node. If the node is an input element, the tag name includes the 
     445 * type of element. Otherwise the method simply returns the tag name. 
     446 *  
     447 * @param node the node for which the name must be determined 
     448 *  
     449 * @return the name as described 
     450 */ 
     451function getTagName(node) { 
     452    var tagName = null; 
     453     
     454    if (node.tagName) { 
     455        tagName = node.tagName.toLowerCase(); 
     456        if ("input" === tagName) { 
     457            if (node.type && (node.type !== "")) { 
     458                tagName += "_" + node.type; 
     459            } 
     460            else { 
     461                tagName += "_text"; 
     462            } 
     463        } 
     464    } 
     465 
     466    return tagName; 
     467} 
     468     
    297469/** 
    298470 * sends the collected data to the server, named in the destination-variable. For this it generates 
     
    310482        log("creating message"); 
    311483        eventList = autoquestRecordedEvents; 
    312         autoquestRecordedEvents = []; 
     484         
     485        // put the last event into the new list to allow for checks for reoccurence of the same 
     486        // event 
     487        autoquestRecordedEvents = [ eventList.pop() ]; 
    313488         
    314489        message = "{\"message\":{\"clientInfos\":{"; 
     
    354529        request.send(message); 
    355530    } 
     531} 
     532 
     533/** 
     534 * determines the scroll coordinates of the scrolled element 
     535 *  
     536 * @param node the element that was scrolled 
     537 *  
     538 * @returns the coordinates of the scrolling as an array with x and y coordinate 
     539 */ 
     540function getScrollCoordinates(node) { 
     541    if (node.scrollLeft || node.scrollTop) { 
     542        return [node.scrollLeft, node.scrollTop]; 
     543    } 
     544    else if ((node === window) && window.pageYOffset) { 
     545        return [window.pageXOffset, window.pageYOffset]; 
     546    } 
     547    else if ((node == document) || (node == document.body) || (node == document.documentElement)) {  
     548        if (document.body && (document.body.scrollLeft || document.body.scrollTop)) { 
     549            return [document.body.scrollLeft, document.body.scrollTop]; 
     550        } 
     551        else if (document.documentElement && 
     552                 (document.documentElement.scrollLeft || document.documentElement.scrollTop)) 
     553        { 
     554            return [document.documentElement.scrollLeft, document.documentElement.scrollTop]; 
     555        } 
     556    } 
     557 
     558    return [-1, -1]; 
    356559} 
    357560 
     
    478681    }; 
    479682     
     683    this.setSelectedValue = function(value) { 
     684        this.selectedValue = value; 
     685    }; 
     686   
    480687    this.setScrollPosition = function(scrollPosition) { 
    481           this.scrollPosition = scrollPosition; 
     688        this.scrollPosition = scrollPosition; 
    482689    }; 
    483690     
     
    495702        } 
    496703         
    497         if (this.scrollPosition) { 
    498             eventInJSON += ",\"scrollPosition\":" + this.scrollPosition; 
     704        if (this.selectedValue) { 
     705            eventInJSON += ",\"selectedValue\":\"" + this.selectedValue + "\""; 
     706        } 
     707         
     708        if ("onscroll" === this.type) { 
     709            eventInJSON += ",\"scrollPosition\":[" + this.scrollPosition + "]"; 
    499710        } 
    500711 
Note: See TracChangeset for help on using the changeset viewer.